I have two sprites: a robot sprite and an obstacle sprite. I am using mask.overlap to determine if there is an overlap to prevent the robot from moving into the area of the obstacle (it functions as a blocking obstacle). Below is a portion of the movement evaluation code. It tests to see if the movement will cause a collision:
if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
# adjust the speed to make the objects perfectly collide
This code works. If the movement would cause a collision, then it prevents the robot from moving.
ISSUE
For high speeds, the code prevents movement like it should, but it leaves a visual gap between the robot and the obstacle.
For example: if the speed is 30 and the two obstacles are 20 pixels away, the code will prevent the movement because a collision would be caused. But leaves a 20 pixel gap.
GOAL
If there were to be a collision, adjust the speed to the remaining pixel distance (20px like in the example) so that the robot and the obstacle perfectly collide. The robot can't move 30, but he can move 20. How can I calculate that remaining distance?
Here's what I described in the comment. Check if the sprites are colliding (I use spritecollide and the pygame.sprite.collide_mask functions here), and then use the normalized negative velocity vector to move the player backwards until it doesn't collide with the obstacle anymore.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((800, 600))
GRAY = pg.Color('gray12')
CIRCLE_BLUE = pg.Surface((70, 70), pg.SRCALPHA)
pg.draw.circle(CIRCLE_BLUE, (0, 0, 230), (35, 35), 35)
CIRCLE_RED = pg.Surface((170, 170), pg.SRCALPHA)
pg.draw.circle(CIRCLE_RED, (230, 0, 0), (85, 85), 85)
class Player(pg.sprite.Sprite):
def __init__(self, pos, key_left, key_right, key_up, key_down):
super().__init__()
self.image = CIRCLE_BLUE
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(self.rect.topleft)
self.dt = 0.03
self.key_left = key_left
self.key_right = key_right
self.key_up = key_up
self.key_down = key_down
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == self.key_left:
self.vel.x = -230
elif event.key == self.key_right:
self.vel.x = 230
elif event.key == self.key_up:
self.vel.y = -230
elif event.key == self.key_down:
self.vel.y = 230
elif event.type == pg.KEYUP:
if event.key == self.key_left and self.vel.x < 0:
self.vel.x = 0
elif event.key == self.key_right and self.vel.x > 0:
self.vel.x = 0
elif event.key == self.key_down and self.vel.y > 0:
self.vel.y = 0
elif event.key == self.key_up and self.vel.y < 0:
self.vel.y = 0
def update(self, dt):
self.pos += self.vel * dt
self.rect.center = self.pos
class Obstacle(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = CIRCLE_RED
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)
class Game:
def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = screen
self.player = Player((100, 50), pg.K_a, pg.K_d, pg.K_w, pg.K_s)
obstacle = Obstacle((300, 240))
self.all_sprites = pg.sprite.Group(self.player, obstacle)
self.obstacles = pg.sprite.Group(obstacle)
def run(self):
while not self.done:
self.dt = self.clock.tick(60) / 1000
self.handle_events()
self.run_logic()
self.draw()
pg.quit()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 2:
print(BACKGROUND.get_at(event.pos))
self.player.handle_event(event)
def run_logic(self):
self.all_sprites.update(self.dt)
collided_sprites = pg.sprite.spritecollide(
self.player, self.obstacles, False, pg.sprite.collide_mask)
for obstacle in collided_sprites:
# The length of the velocity vector tells us how many steps we need.
for _ in range(int(self.player.vel.length())+1):
# Move back. Use the normalized velocity vector.
self.player.pos -= self.player.vel.normalize()
self.player.rect.center = self.player.pos
# Break out of the loop when the masks aren't touching anymore.
if not pg.sprite.collide_mask(self.player, obstacle):
break
def draw(self):
self.screen.fill(GRAY)
self.all_sprites.draw(self.screen)
pg.display.flip()
if __name__ == '__main__':
Game().run()
You can pretty easily get a precise (if not exact) solution via a bisection search: once the collision is detected at the end of the full step, try half a step, and then either one or three quarters, and so on. This is treating the collision test as a boolean-valued function of the movement distance and looking for a “zero” (really the transition from miss to hit).
Note that this does nothing to resolve the issue of clipping through a thin wall or corner (where the initial collision test fails to detect the obstacle) and with complicated obstacles will find an arbitrary edge (not necessarily the first) to stop at.
I decided to go with the approach suggested by skrx in his comment: to basically back up by 1px until there is no longer a collision.
if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
for step_speed in range(1, speed - 1):
collision[1] -= 1
offset_x = collision[0] - obstacle.rect.x
offset_y = collision[1] - obstacle.rect.y
overlap_adj = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap_adj is None:
robot.rect.y += (speed - step_speed)
break
This is a bit of a clumsy approach, but it will satisfy what i need for now and keep vector math at bay. For those who are looking for the proper way to approach this using normalized vectors and such, i would recommend using the answer skrx provided. I will likely come back to this and update it in the future. But for now, this will give users a couple options on how to proceed with perfect collision.
Related
Is there a way in pygame to look for a collision between the a particular side of a sprite and a particular side of another sprite in pygame? For example, if the top of sprite A collides with the bottom of Sprite B, return True.
I am certain there is a way to do this, but I can't find any particular method in the documentation.
Thanks!
There is no function to get sides collision in PyGame.
But you could try to use pygame.Rect.collidepoint to test if A.rect.midleft, A.rect.midright, A.rect.midtop, A.rect.midbottom, A.rect.topleft, A.rect.bottomleft , A.rect.topright, A.rect.bottomright are inside B.rect (pygame.Rect).
EDIT:
Example code. Use arrows to move player and touch enemy.
(probably it is not optimal solution)
import pygame
WHITE = (255,255,255)
BLACK = (0 ,0 ,0 )
RED = (255,0 ,0 )
GREEN = (0 ,255,0 )
BLUE = (0 ,0 ,255)
class Player():
def __init__(self, x=0, y=0, width=150, height=150):
self.rect = pygame.Rect(x, y, width, height)
self.speed_x = 5
self.speed_y = 5
self.move_x = 0
self.move_y = 0
self.collision = [False] * 9
self.font = pygame.font.SysFont("", 32)
self.text = "";
def set_center(self, screen):
self.rect.center = screen.get_rect().center
def event_handler(self, event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.move_x -= self.speed_x
elif event.key == pygame.K_RIGHT:
self.move_x += self.speed_x
elif event.key == pygame.K_UP:
self.move_y -= self.speed_y
elif event.key == pygame.K_DOWN:
self.move_y += self.speed_y
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
self.move_x += self.speed_x
elif event.key == pygame.K_RIGHT:
self.move_x -= self.speed_x
elif event.key == pygame.K_UP:
self.move_y += self.speed_y
elif event.key == pygame.K_DOWN:
self.move_y -= self.speed_y
def update(self):
self.rect.x += self.move_x
self.rect.y += self.move_y
def draw(self, screen):
pygame.draw.rect(screen, WHITE, self.rect, 2)
self.draw_point(screen, self.rect.topleft, self.collision[0])
self.draw_point(screen, self.rect.topright, self.collision[1])
self.draw_point(screen, self.rect.bottomleft, self.collision[2])
self.draw_point(screen, self.rect.bottomright, self.collision[3])
self.draw_point(screen, self.rect.midleft, self.collision[4])
self.draw_point(screen, self.rect.midright, self.collision[5])
self.draw_point(screen, self.rect.midtop, self.collision[6])
self.draw_point(screen, self.rect.midbottom, self.collision[7])
self.draw_point(screen, self.rect.center, self.collision[8])
def draw_point(self, screen, pos, collision):
if not collision:
pygame.draw.circle(screen, GREEN, pos, 5)
else:
pygame.draw.circle(screen, RED, pos, 5)
def check_collision(self, rect):
self.collision[0] = rect.collidepoint(self.rect.topleft)
self.collision[1] = rect.collidepoint(self.rect.topright)
self.collision[2] = rect.collidepoint(self.rect.bottomleft)
self.collision[3] = rect.collidepoint(self.rect.bottomright)
self.collision[4] = rect.collidepoint(self.rect.midleft)
self.collision[5] = rect.collidepoint(self.rect.midright)
self.collision[6] = rect.collidepoint(self.rect.midtop)
self.collision[7] = rect.collidepoint(self.rect.midbottom)
self.collision[8] = rect.collidepoint(self.rect.center)
def render_collision_info(self):
text = "collision: "
print "collision:",
if self.collision[0] or self.collision[2] or self.collision[4]:
text += "left "
print "left",
if self.collision[1] or self.collision[3] or self.collision[5]:
text += "right "
print "right",
if self.collision[0] or self.collision[1] or self.collision[6]:
text += "top "
print "top",
if self.collision[2] or self.collision[3] or self.collision[7]:
text += "bottom "
print "bottom",
if self.collision[8]:
text += "center "
print "center",
print
self.text = self.font.render(text, 1, WHITE)
def draw_collision_info(self, screen, pos):
screen.blit(self.text, pos)
#----------------------------------------------------------------------
class Game():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode( (800,600) )
pygame.display.set_caption("Side Collision")
self.player = Player()
self.enemy = Player()
self.enemy.set_center(self.screen)
def run(self):
clock = pygame.time.Clock()
RUNNING = True
while RUNNING:
# --- events ----
for event in pygame.event.get():
if event.type == pygame.QUIT:
RUNNING = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
RUNNING = False
self.player.event_handler(event)
# --- updates ---
self.player.update()
self.enemy.update()
self.player.check_collision(self.enemy.rect)
self.enemy.check_collision(self.player.rect)
self.player.render_collision_info()
self.enemy.render_collision_info()
# --- draws ----
self.screen.fill(BLACK)
self.player.draw(self.screen)
self.enemy.draw(self.screen)
self.player.draw_collision_info(self.screen, (0,0))
self.enemy.draw_collision_info(self.screen, (0,32))
pygame.display.update()
# --- FPS ---
clock.tick(30)
pygame.quit()
#----------------------------------------------------------------------
Game().run()
EDIT (08.2016): version with colllisions rect, rect_ratio, circle
GitHub: furas/python-examples/pygame/collisions
The logic behind collision is like that:
#Assume two surfaces
objectA
objectB
if objectA.right > objectB.left and objectA.left < objectB.right and objectA.top < objectB.bottom and objectA.bottom > objectB.top
#Collision happens
if you want to detect side collision (as if objectA hitted objectB on the side) you can do the following:
#Here is the code where objectA moves
objectA.X += 10
#Here check Collision, if collision happens objectA hitted objectB from the side
objectA.Y += 10
#Here check Collision again, if collision is true then objectA hitted object B from top/bottom
I solved this by creating multiple collide boxes. This should help a lot of people.
https://youtu.be/W-Vz6le1YUg
Code:
if tanner.axe.colliderect(oak.red) and.
tanner.playerHitBox.colliderect(oak.leftHit) and.
keys_pressed[pygame.K_SPACE]:
Number_of_Hits_Left += 1
print(Number_of_Hits_Left)
if tanner.axe.colliderect(oak.red) and.
tanner.playerHitBox.colliderect(oak.rightHit) and.
keys_pressed[pygame.K_SPACE]:
Number_of_Hits_Right += 1
print(Number_of_Hits_Right)
So I have a total of 5 hit boxes to accomplish said mission. And really all you would have to do is create your main hit box, then create 2 side boxes on the left and right side of main hit box, so that they barely overlap. So let's say you shoot a bullet your code would be something like above. "When bullet collides with side box AND when bullet collides with main box, do something."
Like Furas has said, no, there is not way to get side collisions in Pygame past the point system he set up. And even that one wont give you what you want, because you can never be sure which direction the collision happened when dealing with rows, columns or corners of Rectangles.
This is why most tutorials recommend saving your sprites initial direction. then moving in the opposite direction in case of a collision.
For objectA give the object this method:
def is_collided_with(self, sprite):
return self.rect.colliderect(sprite.rect)
This return statement returns either True or False
then in the main loop for collisions just do:
if objectA.is_collided_with(ObjectB):
Collision happened!
I'm trying to play the ouch sound when the object ball collides with an angryball, but the sound doesn't always play. It starts on the first collision when the two objects are on the same x or y coordinate but after that it doesn't play correctly for future collisions.
Maybe it's my fault on how I manage to handle their coordinates to check for the collision?
import pygame
import os
import random
size = width, height = 750, 422
screen = pygame.display.set_mode(size)
img_path = os.path.join(os.getcwd())
background_image = pygame.image.load('background.jpg').convert()
bg_image_rect = background_image.get_rect()
pygame.mixer.pre_init(44100, 16, 2, 4096)
pygame.display.set_caption("BallGame")
class Ball(object):
def __init__(self):
self.image = pygame.image.load("ball.png")
self.image_rect = self.image.get_rect()
self.image_rect.x
self.image_rect.y
self.facing = 'LEFT'
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 5
if key[pygame.K_DOWN] and self.image_rect.y < 321:
self.facing = 'DOWN'
self.image_rect.y += dist
elif key[pygame.K_UP] and self.image_rect.y > 0:
self.facing = 'UP'
self.image_rect.y -= dist
if key[pygame.K_RIGHT] and self.image_rect.x < 649:
self.facing = 'RIGHT'
self.image_rect.x += dist
elif key[pygame.K_LEFT] and self.image_rect.x > 0:
self.facing = 'LEFT'
self.image_rect.x -= dist
def draw(self, surface):
if self.facing == "RIGHT":
surface.blit(pygame.transform.flip(self.image, True, False),(self.image_rect.x,self.image_rect.y))
elif self.facing == "DOWN":
surface.blit(pygame.image.load("ball_down.png"),(self.image_rect.x,self.image_rect.y))
if self.facing == "UP":
surface.blit(pygame.image.load("ball_up.png"),(self.image_rect.x,self.image_rect.y))
elif self.facing == "LEFT":
surface.blit(self.image,(self.image_rect.x,self.image_rect.y))
mob_images = [pygame.image.load("image1.png").convert_alpha(),pygame.image.load("image2.png").convert_alpha(),pygame.image.load("image3.png").convert_alpha(),pygame.image.load("image4.png").convert_alpha(),pygame.image.load("image5.png").convert_alpha()]
class Angryball(pygame.sprite.Sprite):
def __init__(self, mob_images, pos_x, pos_y):
super(Angryball, self).__init__()
self.mob_images = mob_images
self.image = random.choice(self.mob_images)
self.rect = self.image.get_rect(x=pos_x, y=pos_y)
self.facing = 'LEFT'
def update(self, screen):
if self.rect.x <= 0:
self.rect.right = screen.get_rect().width
self.rect.top = random.randint(0, screen.get_rect().height)
self.image = random.choice(self.mob_images)
else:
self.rect.move_ip(-5, 0)
pygame.init()
screen = pygame.display.set_mode((750, 422))
ball = Ball()
angryball = Angryball(mob_images , 700, random.randrange(400))
sprites = pygame.sprite.Group()
sprites.add(angryball)
clock = pygame.time.Clock()
pygame.mixer.music.load("bg_music.mp3")
pygame.mixer.music.play(-1, 0.0)
ouch = pygame.mixer.Sound("border_sound.wav")
running = True
while running:
esc_key = pygame.key.get_pressed()
for event in pygame.event.get():
if esc_key[pygame.K_ESCAPE]:
pygame.display.quit()
pygame.quit()
running = False
if ball.image_rect.x == angryball.rect.x:
ouch.play()
if ball.image_rect.y == angryball.rect.y:
ouch.play()
ball.handle_keys()
screen.blit(background_image, bg_image_rect)
screen.blit(background_image, bg_image_rect.move(bg_image_rect.width, 0))
bg_image_rect.move_ip(-2, 0)
if bg_image_rect.right <= 0:
bg_image_rect.x = 0
sprites.update(screen)
sprites.draw(screen)
ball.draw(screen)
pygame.display.update()
clock.tick(60)
You need to replace these lines,
if ball.image_rect.x == angryball.rect.x:
ouch.play()
if ball.image_rect.y == angryball.rect.y:
ouch.play()
with this one (to check if the two rects collide):
if ball.image_rect.colliderect(angryball.rect):
ouch.play()
The problem now is that the sound will be played every frame in which the two objects collide and therefore can become quite loud (it will be played in up to 8 channels simultaneously). Also, if all channels are occupied, the sound playback can get skipped if several objects collide in short succession.
To prevent this, you could give the Angryball a collided attribute (boolean) and set it to True after the first collision. Then reset it to False after some time interval or at the same time when you reset the position.
if ball.image_rect.colliderect(angryball.rect) and not angryball.collided:
angryball.collided = True
ouch.play()
As suggested, i replaced the collision detection with
if ball.image_rect.colliderect(angryball.rect):
and it worked
You could also check for collisions at a specific point such as:
ball.rect.collidepoint(angryball.rect.x, angryball.rect.y)
What it does is test if a point is in fact inside a rect of a sprite. I find that it works nicely, it’s simple, and clear to use and understand.
I'm doing the Pong game. However instead of 2 players, I'm doing it for 4 (one on each side of the screen). I need to have the ball randomly "choose" the direction it has to go.
import pygame
import random
class Ball(object):
#classmethod
def init(cls, SCREEN_WIDTH, SCREEN_HEIGHT):
cls.radius = 20
cls.centerx = SCREEN_WIDTH*0.5
cls.centery = SCREEN_HEIGHT*0.5
cls.rect = pygame.Rect(cls.centerx - cls.radius,
cls.centery - cls.radius,
cls.radius * 2,
cls.radius * 2)
# x , y
cls.direction = [random.choice([1, -1]), random.choice([1, -1])]
cls.speed = [5, 8] # x, y
# left, right, top, bottom
cls.hit_edge = [False, False, False, False]
#classmethod
def update(cls, player1, player2, player3, player4, SCREEN_WIDTH,
SCREEN_HEIGHT):
cls.centerx += cls.direction[0] * cls.speed[0]
cls.centery += cls.direction[1] * cls.speed[1]
cls.rect.center = (cls.centerx, cls.centery)
#detects if someone losses
if cls.rect.left <= 0:
cls.hit_edge[0] = True
elif cls.rect.right >= SCREEN_WIDTH-1:
cls.hit_edge[1] = True
elif cls.rect.top <= 0:
cls.hit_edge[2] = True
elif cls.rect.bottom >= SCREEN_HEIGHT-1:
cls.hit_edge[3] = True
#detects collision between players & the ball
if cls.rect.colliderect(player1.rect):
cls.direction[0] = 1
cls.up_speed()
elif cls.rect.colliderect(player2.rect):
cls.direction[0] = -1
cls.up_speed()
elif cls.rect.colliderect(player3.rect):
cls.direction[1] = 1
cls.up_speed()
elif cls.rect.colliderect(player4.rect):
cls.direction[1] = -1
cls.up_speed()
#classmethod
def up_speed(cls):
cls.speed[0] += random.uniform(0, 0.25)
cls.speed[1] += random.uniform(0, 0.25)
#classmethod
def render(cls, SCREEN, color):
pygame.draw.circle(SCREEN, color, cls.rect.center, cls.radius, 0)
To take into account: I had the idea to add a "0" in every random.choice(), although if I do this only function at the beginning, then it will not be able to move in the axis where the "0" . Also I have two types of speeds in X and Y, could be solved by putting a "0.1" in random.choice () but this would make when the game starts the ball goes very slow. As you would do for the ball to start in a random direction (taking into account that the speed of the ball at the start must be the same for all players. If the ball goes at the beginning to the left,and later (in another game) when it starts but the ball goes up has to go at the same speed)
This may be a little over-complicating things, but if you know the speed you want the ball to start with overall, you could use something like this:
Generate random number between 0-1
angle = 360 * random number
xSpeed = startSpeed * sin(angle)
ySpeed = startSpeed * cos(angle)
This will mean that your ball will always travel at the same speed. The only thing that is random is the direction it travels in.
I recommend to use vectors. For the velocity you can just pick an arbitrary start speed like (8, 0) and then rotate the vector by a random angle.
position = pg.math.Vector2(100, 200)
velocity = pg.math.Vector2(8, 0).rotate(random.randrange(360))
To update the position:
position += velocity
Here's an example program that spawns balls with random color and velocity.
import sys
import math
from random import randrange
import pygame as pg
class Ball(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(groups)
self.image = pg.Surface((30, 30), pg.SRCALPHA)
col = randrange(256), randrange(256), randrange(256)
pg.draw.circle(self.image, col, (15, 15), 15)
self.rect = self.image.get_rect(center=pos)
self.vel = pg.math.Vector2(8, 0).rotate(randrange(360))
self.pos = pg.math.Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if self.rect.left < 0 or self.rect.right > 640:
self.vel.x *= -1
if self.rect.top < 0 or self.rect.bottom > 480:
self.vel.y *= -1
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
sprite_group = pg.sprite.Group()
ball = Ball((320, 240), sprite_group)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
sprite_group.add(Ball((320, 240)))
sprite_group.update()
screen.fill((30, 30, 30))
sprite_group.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
Can anyone show me how to add a rectangle wherever i click somewhere and make the ball bounce off it for example when i click somewhere on the screen it place a rectangle at that coordinate and when they make the ball collide with it the ball will bounce.
Also my is their a way of making it HD?
import sys
import pygame
import os
image_file = os.path.expanduser("ball.png")
delta = {
pygame.K_LEFT: (-20, 0),
pygame.K_RIGHT: (+20, 0),
pygame.K_UP: (0, -20),
pygame.K_DOWN: (0, +20),
}
gravity = +1
class Ball(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.speed = [0, 0]
area = pygame.display.get_surface().get_rect()
self.width, self.height = area.width, area.height
def update(self):
self.rect = self.rect.move(self.speed)
if self.rect.left < 0 or self.rect.right > self.width:
self.speed[0] = -self.speed[0]
if self.rect.top < 0 or self.rect.bottom > self.height:
self.speed[1] = -self.speed[1]
self.rect.left = clip(self.rect.left, 0, self.width)
self.rect.right = clip(self.rect.right, 0, self.width)
self.rect.top = clip(self.rect.top, 0, self.height)
self.rect.bottom = clip(self.rect.bottom, 0, self.height)
def clip(val, minval, maxval):
return min(max(val, minval), maxval)
class Main(object):
def __init__(self):
self.setup()
def setup(self):
pygame.init()
size = (self.width, self.height) = (640,360)
self.screen = pygame.display.set_mode((1000,1000),pygame.FULLSCREEN)
self.ball = Ball()
self.setup_background()
def setup_background(self):
self.background = pygame.Surface(self.screen.get_size())
self.background = self.background.convert()
self.background.fill((0, 0, 0))
self.screen.blit(self.background, (0, 0))
pygame.display.flip()
def draw(self):
self.screen.blit(self.background, (0, 0))
self.screen.blit(self.ball.image, self.ball.rect)
pygame.display.flip()
def event_loop(self):
ball = self.ball
friction = 1
while True:
for event in pygame.event.get():
if ((event.type == pygame.QUIT) or
(event.type == pygame.KEYDOWN and
event.key == pygame.K_ESCAPE)):
sys.exit()
elif event.type == pygame.KEYDOWN:
deltax, deltay = delta.get(event.key, (0, 0))
ball.speed[0] += deltax
ball.speed[1] += deltay
friction = 1
elif event.type == pygame.KEYUP:
friction = 0.99
ball.speed = [friction*s for s in ball.speed]
ball.speed[1] += gravity
ball.update()
self.draw()
pygame.time.delay(10)
if __name__ == '__main__':
app = Main()
app.event_loop()
You have to be able to perform several steps:
Detect when the mouse changes from unpressed to pressed state.
If that happens:
Add a Rectange object to a list (consists of x, y, width and height).
In each loop:
For each Rectange in the list: Check if it intersects with the ball. You can model the ball as a rectangle, which is a little bit easier, or as a circle. A circle touches a rectangle if a corner of the rectange is inside of it or if the center is below/above/beneath the rectangle and less than its radius away. (It's easier to explain with a picture.)
If the ball collides: Change the speed as if it had touched the corner of the screen.
Do you have further problems regarding one of these steps?
"Also my is their a way of making it HD?"
I was able to change the resolution to 1600 x 900. Each graphics card only supports a certain set of fullscreen resolutions and 1000x1000 didn't work for mine.
I'm a newb when it comes to programming, but I'm learned a lot so far and I'm trying to make a very basic RPG.
I want to move my image object oPC with a mouse click. I've been able to accomplish this with the code I'm sharing below, however, no matter where I click on the screen the image takes the same amount of steps/time to get there. For instance, if I click a few inches away from the object it will gradually shift across the screen towards the target location just as fast as if I'd click right off the image.
I've been stuck trying to figure out a way to solve this issue for the last few days. Is there someway to use time for movement as opposed to the steps I've used?
Thanks!
import pygame, sys
import oPC
pygame.init()
WINDOWSIZE = (1000, 800)
BLACK = (0, 0, 0)
screen = pygame.display.set_mode((WINDOWSIZE))
pygame.display.set_caption("Game")
screen.fill(BLACK)
terrain = pygame.image.load("terrain.jpg").convert()
terrainRect = terrain.get_rect()
terrain = pygame.transform.scale(terrain, ((WINDOWSIZE)))
screen.blit(terrain, terrainRect)
oPC = oPC.Player()
oPC.draw(screen)
pygame.display.flip()
running = True
n_steps = 80
while running == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
mlocX, mlocY = pygame.mouse.get_pos()
while mlocX != oPC.rect.x and mlocY != oPC.rect.y:
clock.tick(60)
oPC.update(mlocX, mlocY, n_steps)
if n_steps > 1:
screen.fill(BLACK)
screen.blit(terrain, terrainRect)
n_steps -= 1
oPC.draw(screen)
pygame.display.flip()
n_steps = 80
pygame.quit()
#sys.exit()
import pygame, sys
class Player(object):
def __init__(self):
self.image = pygame.image.load("tipping over s0000.bmp").convert()
self.rect = self.image.get_rect()
self.name = " "
self.stats = [0, 0, 0, 0, 0] #str, int, dex, health, mana
self.admin = False # False = Member / True = Administrator
def draw(self, screen):
self.image = pygame.transform.scale(self.image, (75, 75))
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self, mlocX, mlocY, n_steps):
self.rect.x += (mlocX - self.rect.x) / n_steps
self.rect.y += (mlocY - self.rect.y) / n_steps
Your design has a few flaws.
You are calling clock.tick() only on the MOUSEBUTTONDOWN event. It should be called on every frame.
Your bliting and display.update should also be done in the loop, not in the event queue.
The player update should also be called in the loop.
You scale your image each time you call draw. I think you may want to do that in the init method only. Since draw should be called repeatedly in the loop.
As for the step counter, I suggest to have a player state, and a step counter there. Something like this:
# for easier state access
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
player_states = enum('STATIONARY', 'MOVING')
STEPS = 30
def __init__(self):
self.state = player_states.STATIONARY
self.steps = 0
self.dest = None
def move(self,mlocX,mlocY):
if self.state != player_states.MOVING:
self.state = player_state.MOVING
self.steps = STEPS
self.dest = (mlocX,mlocY)
def update(self):
if self.steps != 0:
self.rect.x += (self.dest[0] - self.rect.x) / STEPS
self.rect.y += (self.dest[1] - self.rect.y) / STEPS
self.steps -= 1
else:
self.state = player_states.STATIONARY
As for you question with steps, you can use physics and the famous distance = velocity * time equation.
The clock.tick() method returns the number of ms passed since the last call to tick().
If you pass this to the update method, you can than change the moving equations to:
def update(self,delta):
self.rect.x += PLAYER_SPEED * direction * delta
Then pick PLAYER_SPEED to something that will suit you.