Pygame's Rect shows strange behaviour - pygame

Note on versions: Python 3.4 with Pygame 1.9.2 for Python 3.3 (but it works well so far)
I have tiles in my sidescroller which are made up by 64x64 pixel rects, looking somewhat like this:
class Tile(object):
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, x + 64, y + 64)
(There's of course more going on in this class, but that's not linked to the question now)
In the program I update the tiles' positions by changing their x and y values:
def move(self, dx, dy):
for tile in self.tilelist:
tile.rect.x += dx
tile.rect.y += dy
Long story short: Only the x and y values change, the width and height remain, so the dimensions of the rectangle are changed and not its position. I have this workaround so far:
def move(self, dx, dy):
for tile in self.tilelist:
tile.rect.x += dx
tile.rect.y += dy
tile.rect.w = tile.rect.x + 64
tile.rect.h = tile.rect.y + 64
But that's not as I understood this paragraph of the rectangle documentation:
"Assigning to size, width or height changes the dimensions of the rectangle; all other assignments move the rectangle without resizing it"
From here:
http://www.pygame.org/docs/ref/rect.html
What am I doing wrong? How do I make changes to a rect to move it, if not by what I did there?
I'd appreciate any hint to what I'm mistaking here :/ Thanks!
Pat
//Edit
This is the code of my collision detection. Based upon the theory how pygame rects should work, this should as well. It somehow does, but only when I adjust w and h value of the rect every single frame as well.
def move(self, dx, dy):
dx *= self.dt * self.speed
dy *= self.dt * self.speed
for tile in self.coll_list:
if dx > 0: # Moving LEFT
if self.player.dir != "l":
self.player.dir = "l"
if tile.rect.y < self.player.rect.h and \
tile.rect.h > self.player.rect.y:
if tile.rect.w + dx > self.player.rect.x and \
tile.rect.x < self.player.rect.w:
if tile.category == "solid":
dx = self.player.rect.x - tile.rect.w
elif tile.category == "item":
if tile.func != None:
_func = eval(tile.func[0])
_args = eval(tile.func[1])
_func(_args, tile)
elif dx < 0: # Moving RIGHT
if self.player.dir != "r":
self.player.dir = "r"
if tile.rect.y < self.player.rect.h and \
tile.rect.h > self.player.rect.y:
if tile.rect.x + dx < self.player.rect.w and \
tile.rect.w > self.player.rect.x:
if tile.category == "solid":
dx = self.player.rect.w - tile.rect.x
elif tile.category == "item":
if tile.func != None:
_func = eval(tile.func[0])
_args = eval(tile.func[1])
_func(_args, tile)
if dy > 0: # Moving UP
if tile.rect.x < self.player.rect.w and \
tile.rect.w > self.player.rect.x:
if tile.rect.h + dy > self.player.rect.y and \
tile.rect.y < self.player.rect.h:
if tile.category == "solid":
dy = self.player.rect.y - tile.rect.h
if self.player.jumping != 1:
self.player.jumping = 1
elif tile.category == "item":
if tile.func != None:
_func = eval(tile.func[0])
_args = eval(tile.func[1])
_func(_args, tile)
elif dy < 0: # Moving DOWN
if tile.rect.x < self.player.rect.w and \
tile.rect.w > self.player.rect.x:
if tile.rect.y + dy < self.player.rect.h and \
tile.rect.h > self.player.rect.y:
if tile.category == "solid":
self.collisions["bottom"] = True
dy = self.player.rect.h - tile.rect.y
elif tile.category == "item":
if tile.func != None:
_func = eval(tile.func[0])
_args = eval(tile.func[1])
_func(_args, tile)
for category in ["solid", "deco", "actor", "item"]:
for tile in self.tiles[category]:
tile.rect.x += dx
tile.rect.y += dy
tile.rect.w = tile.rect.x + 64
tile.rect.h = tile.rect.y + 64

w and h are the width and height and should not depend on position. Thus:
tile.rect.x += dx
tile.rect.y += dy
tile.rect.w = 64
tile.rect.h = 64

assuming dx and dy are supposed to be relative and not absolute coordinates, why not use the built-in move function
def move(self, dx, dy):
for tile in self.tilelist:
tile.rect.move_ip(dx, dy)

Related

Have issues with collision detection in pygame

def movement(self):
prevX = self.x
prevY = self.y
key = pygame.key.get_pressed()
if key[pygame.K_w]:
self.y -= 32
elif key[pygame.K_s]:
self.y += 32
elif key[pygame.K_a]:
self.x -= 32
elif key[pygame.K_d]:
self.x += 32
x = int(self.x / 32)
y = int(self.y / 32)
if TEXT_LEVEL[x][y] != "=":
print("wall")
print(x,y)
print(TEXT_LEVEL[x][y])
self.rect.topleft = (self.x, self.y)
self.x = prevX
self.y = prevY
So I have an array that stores the map as a text file as such:
============.===============
=..........=...............=
=..........=...............=
=...........*..............=
=....===============.......=
=....=.....................=
=....=.............=.......=
.....===============........
=..................*.......=
=..........................=
=....===============.......=
=....*.............*.......=
=........=====.............=
= .........................=
============.===============
And what I'm doing is checking if the current position my sprite is in matches to the coordinates of a wall. If it does I reset the x and y and don't update its location. For some reason, this is not working and I'm stuck as to why it's failing.
The full project can be found on my github if anything needs to be referenced: https://github.com/rob-roibu/Pac-man
You need to update self.rect.topleft after changing self.x and self.y:
self.x = prevX
self.y = prevY
self.rect.topleft = (self.x, self.y)

How to calculate the minimum translation vector between a circle and a rectangle?

I am aware of how to detect collision with circle and rectangles but I am not sure how to find the minimum translation vector between the two. I know how to do it with the SAT collision detection algorithm, but that is too complicated for my simple implementation right now.
I am really not sure what to do except change the x-coordinate appropriately.
Here is the code. When you pressed the down button, what I would want is that the circle automatically is "shoved" to the left (since it is already positioned a bit left to the center) when constantly having the down button pressed i.e. it is moving down but sliding to the left.
import pygame
if __name__ == "__main__":
pygame.init()
display = pygame.display.set_mode((500, 500))
display.fill((255, 255, 255))
circle_x = 240
circle_y = 50
pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
pygame.display.update()
vel = 1
is_down_held = False
clock = pygame.time.Clock()
while True:
pressed_keys = pygame.key.get_pressed()
if pressed_keys[pygame.K_DOWN]:
circle_y += vel
display.fill((255, 255, 255))
pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
dt = clock.tick(60)
dt /= 1000
I tried moving the x-coordinate with some constant, however, it looks unrealistic and is actually passing through the rectangle sometimes (since it actually doesn't detect the rectangle).
Okay, it seems like I had to implement SAT collision detection at the end anyways. To calculate the minimum translation vector between a rectangle and a circle, you go through the necessary axes for the shapes (see https://jcharry.com/blog/physengine10 and https://www.sevenson.com.au/actionscript/sat/ for an explanation on that) then you take the smallest overlap axis for when there is a collision. Below is the code using pygame GUI library:
import math
import pygame
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
self.magnitude = math.sqrt(x ** 2 + y ** 2)
if self.magnitude != 0:
self.direction_x = -x / self.magnitude
self.direction_y = -y / self.magnitude
else:
self.direction_x = 0
self.direction_y = 0
def normalize(self):
if self.magnitude != 0:
self.x /= self.magnitude
self.y /= self.magnitude
self.magnitude = 1
def project_vector(vector1, vector2):
return get_dot_product(vector1, get_unit_vector(vector2))
def get_dot_product(vector1, vector2):
return (vector1.x * vector2.x) + (vector1.y * vector2.y)
def get_normal(vector):
return Vector(vector.y, -vector.x)
def get_vector(point):
return Vector(point[0], point[1])
def scale_vector(vector, magnitude):
return Vector(vector.x*magnitude, vector.y*magnitude)
def get_unit_vector(vector):
if vector.magnitude != 0:
return scale_vector(vector, 1 / vector.magnitude)
else:
return scale_vector(vector, 0)
def get_closest_point(circle_centre, rectangle_points):
closest_distance = float('inf')
closest_point = None
for point in rectangle_points:
distance = (circle_centre[0] - point[0])**2 + (circle_centre[1] - point[1])**2
if distance <= closest_distance:
closest_distance = distance
closest_point = point
return closest_point
def is_collision(circle_centre, rectangle_points):
closest_point = get_closest_point(circle_centre, rectangle_points)
rectangle_edge_vectors = []
for point in rectangle_points:
rectangle_edge_vectors += [get_vector(point)]
rectangle_edge_normals = []
for i in range(len(rectangle_points) - 1):
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
axes = rectangle_edge_normals
vectors = rectangle_edge_vectors
for axis in axes:
current_rect_max_x = float('-inf')
current_rect_min_x = float('inf')
for vector in vectors:
current_rect_projection = project_vector(vector, axis)
if current_rect_projection >= current_rect_max_x:
current_rect_max_x = current_rect_projection
if current_rect_projection <= current_rect_min_x:
current_rect_min_x = current_rect_projection
current_circle_projection = project_vector(get_vector(circle_centre), axis)
current_circle_max_x = current_circle_projection + 25
current_circle_min_x = current_circle_projection - 25
if current_rect_min_x > current_circle_max_x or current_circle_min_x > current_rect_max_x:
return False
return True
def get_minimum_translation_vector(circle_centre, rectangle_points):
closest_point = get_closest_point(circle_centre, rectangle_points)
rectangle_edge_vectors = []
for point in rectangle_points:
rectangle_edge_vectors += [get_vector(point)]
rectangle_edge_normals = []
for i in range(len(rectangle_points) - 1):
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
axes = rectangle_edge_normals
for axis in axes:
axis.normalize()
vectors = rectangle_edge_vectors
minimum_translation_vector = Vector(axes[0].x, axes[0].y)
minimum_translation_vector.magnitude = float('inf')
current_minimum_translation_vector = Vector(axes[0].x, axes[0].y)
current_minimum_translation_vector.magnitude = float('inf')
for axis in axes:
current_rect_max_x = float('-inf')
current_rect_min_x = float('inf')
for vector in vectors:
current_rect_projection = project_vector(vector, axis)
if current_rect_projection >= current_rect_max_x:
current_rect_max_x = current_rect_projection
if current_rect_projection <= current_rect_min_x:
current_rect_min_x = current_rect_projection
current_circle_projection = project_vector(get_vector(circle_centre), axis)
current_circle_max_x = current_circle_projection + 25
current_circle_min_x = current_circle_projection - 25
current_minimum_translation_vector = axis
current_minimum_translation_vector.magnitude = abs(current_circle_min_x - current_rect_max_x)
if current_minimum_translation_vector.magnitude <= minimum_translation_vector.magnitude:
minimum_translation_vector = axis
minimum_translation_vector.magnitude = current_minimum_translation_vector.magnitude
return minimum_translation_vector
if __name__ == "__main__":
pygame.init()
display = pygame.display.set_mode((500, 500))
rectangle_points_main = [(250, 250), (300, 250), (300, 300), (250, 300)]
circle_centre_main = (0, 0)
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
circle_centre_main = (pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1])
display.fill((255, 255, 255))
if is_collision(circle_centre_main, rectangle_points_main):
pygame.draw.circle(display, (255, 0, 0), circle_centre_main, 25)
minimum_translation_vector_main = get_minimum_translation_vector(circle_centre_main, rectangle_points_main)
dx = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_x
dy = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_y
rectangle_points_main = [(rectangle_points_main[0][0] + dx, rectangle_points_main[0][1] + dy),
(rectangle_points_main[1][0] + dx, rectangle_points_main[1][1] + dy),
(rectangle_points_main[2][0] + dx, rectangle_points_main[2][1] + dy),
(rectangle_points_main[3][0] + dx, rectangle_points_main[3][1] + dy)]
else:
pygame.draw.circle(display, (0, 0, 255), circle_centre_main, 25)
pygame.draw.rect(display, (0, 255, 0), (rectangle_points_main[0][0], rectangle_points_main[0][1], 50, 50))
dt = clock.tick(60)
dt /= 1000
pygame.display.update()

enemy AI collision detection isn't working

My enemy AI isn't detecting collisions. I have been using the code that I use for my player collision detection. I tweaked it to fit the enemy, however it isn't working:
class Enemy(Entity):
def __init__(self, x, y,player):
pygame.sprite.Sprite.__init__(self)
self.image = Surface((32, 32))
self.xvel = 0
self.yvel = 0
self.image.fill(Color("#FF0000")) #Enemy is red
self.onGorund = False
#Enemy is 32 * 32 pixels
self.image.convert()
self.rect = Rect(x, y, 32, 32)
self.counter = 0 #counter variable
self.player = player
def move(self, speed = 5): # chase movement
if self.rect.x > self.player.rect.x: # Movement along x direction
self.rect.x -= speed
elif self.rect.x < self.player.rect.x:
self.rect.x += speed
if self.rect.y < self.player.rect.y: # Movement along y direction
self.rect.y += speed
elif self.rect.y > self.player.rect.y:
self.rect.y -= speed
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, Player_class):
pygame.quit()
sys.exit()
if xvel > 0:
self.rect.right = p.rect.left
print ("collide right")
if xvel < 0:
self.rect.left = p.rect.right
print ("collide left")
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
self.yvel = 0
if yvel < 0:
self.rect.top = p.rect.bottom
def update(self, platforms):
if up:
if self.onGround: self.yvel -= 10 #only jump if player is on the ground
if down:
pass
if running:
self.xvel = 12
if left:
self.xvel = -8
if right:
self.xvel = 8
if not self.onGround:
self.yvel += 0.3 #only accelerate with gravity if in the air
if self.yvel > 100: self.yvel = 100 #terminal velocity = 100
if not(left or right):
self.xvel = 0
self.rect.left += self.xvel #falls or jumps
self.collide(self.xvel, 0, platforms) #creates collisions along the x axis
self.rect.top += self.yvel #creates collisions along the y axis
self.onGround = False; #assumes that the player is in the air
# do y-axis collisions
self.collide(0, self.yvel, platforms)
I have been trying to make the window close whenever the enemy touches the player, problem being, if the enemy doesn't know that it's touching the player, it can't close the window. Please ask if more code is required.
There are a few things that need to be changed.
In the move method you should set the velocity (self.xvel and self.yvel) instead of moving the rect directly.
Then call the move method in the update method, move the rect of the sprite and handle the collisions. Also, you need to call the update method in the main function now instead of move and pass the platforms list:
enemy_list.draw(screen)
for e in enemy_list:
e.update(platforms)
In the collide method you check if one of the platforms is the player instance:
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, Player_class):
pygame.quit()
sys.exit()
However, the player sprite isn't in this list, so it can't collide.
The enemy actually has a reference to the player (self.player) so you can just do this to check if they collide:
if pygame.sprite.collide_rect(self, self.player):
or:
if self.rect.colliderect(self.player.rect):
Here's a complete example (I took the code from your previous question and tried to improve a few more things):
import sys
import pygame
from pygame.locals import *
win_height = 750 #height of window is 750 pixles
win_width = 1050 #height of window is 1050 pixels
half_win_width = int(win_width / 2) #will be used to centre camera
half_win_height = int(win_height / 2)
display = (win_width, win_height) #creates the window as 500*500 pixels
depth = 32 #prevents infinate recursion
flags = 0 #message to Les: I don't really know what this does, however I have seen it in many places being used, therefore I assumed that it was important
camera_slack = 30 #how many pixels the player can move before the camera moves with them
pygame.init()
def main(): #main game function
global cameraX, cameraY
pygame.init()
screen = pygame.display.set_mode(display, flags, depth)
pygame.display.set_caption("Super Castlevania Man")
timer = pygame.time.Clock()
move_cameraX = 0
move_cameraY = 0
up = down = left = right = running = False
background = pygame.Surface((32,32)) #the background takes up space on the screen
background.convert()
background.fill(pygame.Color("#000000")) #background is black
entities = pygame.sprite.Group()
player = Player_class(32, 32*15) #the player is 32*32 pixels large
platforms = []
x = y = 0
level = [
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
"P P",
"P E P",
"P P",
"P PPPPPPPPP P",
"P P",
"P P",
"P P",
"P PPPPPPPP P",
"P P",
"P PPPP P",
"P PPPPPP P",
"P PPPPPPP P",
"P P",
"P PPPPPP P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P PPPPPPPPPPP P",
"P P",
"P P",
"P P",
"P P",
"PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP",
]
#builds the level
for row in level:
for col in row:
if col == "P":
p = Platform(x, y) #makes P a solid object
platforms.append(p)
entities.add(p)
if col == "E":
e = Exit_block(x, y)
platforms.append(e)
entities.add(e)
x += 32
y += 32
x = 0
entities.add(player)
enemy = Enemy(60, 200, player) #Spawns enemy
enemy_list = pygame.sprite.Group() #creates an enemy group
enemy_list.add(enemy) #Add an enemy to the group
while 1:
timer.tick(60) #makes game run at 60 frames per second
for e in pygame.event.get(): #shortens event to e
if e.type == QUIT:
return
if e.type == KEYDOWN and e.key == K_ESCAPE:
return
if e.type == KEYDOWN and e.key == K_UP:
up = True
move_cameraY = -10
if e.type == KEYDOWN and e.key == K_DOWN:
down = True
move_cameraY = 10
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
move_cameraX = -10
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
move_cameraX = 10
if e.type == KEYDOWN and e.key == K_SPACE:
running = True
if e.type == KEYUP and e.key == K_UP:
up = False
move_cameraY = 0
if e.type == KEYUP and e.key == K_DOWN:
down = False
move_cameraY = 0
if e.type == KEYUP and e.key == K_RIGHT:
right = False
move_cameraX = 0
if e.type == KEYUP and e.key == K_LEFT:
left = False
move_cameraX = 0
if e.type == KEYUP and e.key == K_RIGHT:
right = False
# Update the game.
for e in enemy_list:
e.update(platforms)
player.update(up, down, left, right, running, platforms)
# Draw everything.
for y in range(32): #draws the background
for x in range(32):
screen.blit(background, (x * 32, y * 32))
entities.draw(screen)
enemy_list.draw(screen)
pygame.display.flip() # You need only one flip or update call per frame.
class Entity(pygame.sprite.Sprite): #makes player a sprite
def __init__(self):
pygame.sprite.Sprite.__init__(self) #sets sprite to initiate
class Player_class(Entity): #defines player class
def __init__(self, x, y): #x is the player x coordinate, y is the player y coordinate
Entity.__init__(self) #the player is an entity
self.xvel = 0 #how fast the player is moving left and right
self.yvel = 0 #how fast the player is moving up and down
self.onGround = False #assumes the player is in the air
self.image = pygame.Surface((32,32)) #the player is 32*32 pixels
self.image.fill(pygame.Color("#0000FF")) #makes the player blue
self.rect = pygame.Rect(x, y, 32, 32)
self.x = x
self.y = y
def update(self, up, down, left, right, running, platforms):
if up:
if self.onGround:
self.yvel -= 10 #only jump if player is on the ground
if down:
pass
if running:
self.xvel = 12
if left:
self.xvel = -8
if right:
self.xvel = 8
if not self.onGround:
self.yvel += 0.3 #only accelerate with gravity if in the air
if self.yvel > 100: self.yvel = 100 #terminal velocity = 100
if not(left or right):
self.xvel = 0
self.rect.left += self.xvel #falls or jumps
self.collide(self.xvel, 0, platforms) #creates collisions along the x axis
self.rect.top += self.yvel #creates collisions along the y axis
self.onGround = False; #assumes that the player is in the air
# do y-axis collisions
self.collide(0, self.yvel, platforms)
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, Exit_block):
pygame.quit()
sys.exit()
if xvel > 0:
self.rect.right = p.rect.left
if xvel < 0:
self.rect.left = p.rect.right
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
self.yvel = 0
if yvel < 0:
self.rect.top = p.rect.bottom
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.image = pygame.Surface((32, 32))
self.image.fill(pygame.Color("#FFFFFF"))
self.rect = pygame.Rect(x, y, 32, 32)
class Exit_block(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image.fill(pygame.Color("#FF7700"))#exit block is orange
class Enemy(Entity):
def __init__(self, x, y,player):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((32, 32))
self.xvel = 0
self.yvel = 0
self.image.fill(pygame.Color("#FF0000")) #Enemy is red
self.rect = pygame.Rect(x, y, 32, 32)
self.player = player
def move(self, speed=5): # chase movement
if self.rect.x > self.player.rect.x: # Movement along x direction
self.xvel = -speed
elif self.rect.x < self.player.rect.x:
self.xvel = speed
if self.rect.y < self.player.rect.y: # Movement along y direction
self.yvel = speed
elif self.rect.y > self.player.rect.y:
self.yvel = -speed
def collide(self, xvel, yvel, platforms):
# Check if the enemy collides with the player.
if self.rect.colliderect(self.player.rect):
pygame.quit()
sys.exit()
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if xvel > 0:
self.rect.right = p.rect.left
if xvel < 0:
self.rect.left = p.rect.right
if yvel > 0:
self.rect.bottom = p.rect.top
if yvel < 0:
self.rect.top = p.rect.bottom
def update(self, platforms):
self.move() # Set the velocity.
self.rect.left += self.xvel
self.collide(self.xvel, 0, platforms) #creates collisions along the x axis
self.rect.top += self.yvel #creates collisions along the y axis
self.collide(0, self.yvel, platforms)
if __name__ == "__main__":
main()
pygame.quit()

Puzzled by my sprite's unequal +/ - velocity

I have two sprites in my game. The zombie sprite works perfectly, moving in all directions at a velocity of 1.0. My player sprite however, despite moves more slowly in the positive x/y direction, despite all values in all directions being 2.25.
For the life of me I can't seem to see what is wrong here.
Full working code:
import pygame
import random
import sys
import itertools
import math
import time
from datetime import datetime
from librarymodified import *
from pygame.locals import *
# prints text using the supplied font
def print_text(font, x, y, text, color=(255,255,255)):
imgText = font.render(text, True, color)
DISPLAYSURF.blit(imgText, (x,y))
# MySprite class extends pygame.sprite.Sprite
class MySprite(pygame.sprite.Sprite):
def __init__(self, target):
pygame.sprite.Sprite.__init__(self) #extend the base Sprite class
self.master_image = None
self.frame = 0
self.old_frame = -1
self.frame_width = 1
self.frame_height = 1
self.first_frame = 0
self.last_frame = 0
self.columns = 1
self.last_time = 0
self.direction = 0
self.times_hit = 0
self.direction = 0
self.velocity = Point(0.0,0.0)
# times_hit property
def _get_times_hit(self): return self.times_hit
def _set_times_hit(self, hits): self.times_hit += hits
number_hits_taken = property(_get_times_hit, _set_times_hit)
#X property
def _getx(self): return self.rect.x
def _setx(self,value): self.rect.x = value
X = property(_getx,_setx)
#Y property
def _gety(self): return self.rect.y
def _sety(self,value): self.rect.y = value
Y = property(_gety,_sety)
#position property
def _getpos(self): return self.rect.topleft
def _setpos(self,pos): self.rect.topleft = pos
position = property(_getpos,_setpos)
def load(self, filename, width, height, columns):
self.master_image = pygame.image.load(filename).convert_alpha()
self.frame_width = width
self.frame_height = height
self.rect = Rect(0,0,width,height)
self.columns = columns
#try to auto-calculate total frames
rect = self.master_image.get_rect()
self.last_frame = (rect.width // width) * (rect.height // height) - 1
def update(self, current_time, rate=30):
#update animation frame number
if current_time > self.last_time + rate:
self.frame += 1
if self.frame > self.last_frame:
self.frame = self.first_frame
self.last_time = current_time
#build current frame only if it changed
if self.frame != self.old_frame:
frame_x = (self.frame % self.columns) * self.frame_width
frame_y = (self.frame // self.columns) * self.frame_height
rect = Rect(frame_x, frame_y, self.frame_width, self.frame_height)
self.image = self.master_image.subsurface(rect)
self.old_frame = self.frame
def __str__(self):
return str(self.frame) + "," + str(self.first_frame) + \
"," + str(self.last_frame) + "," + str(self.frame_width) + \
"," + str(self.frame_height) + "," + str(self.columns) + \
"," + str(self.rect)
#Point class
class Point(object):
def __init__(self, x, y):
self.__x = x
self.__y = y
#X property
def getx(self): return self.__x
def setx(self, x): self.__x = x
x = property(getx, setx)
#Y property
def gety(self): return self.__y
def sety(self, y): self.__y = y
y = property(gety, sety)
def __str__(self):
return "{X:" + "{:.0f}".format(self.__x) + \
",Y:" + "{:.0f}".format(self.__y) + "}"
def calc_velocity(direction, vel = 1.0):
velocity = Point(0, 0)
if direction == 0: # North
velocity.y = -vel
elif direction == 2: # East
velocity.x = vel
elif direction == 4: # south
velocity.y = vel
elif direction == 6: # west
velocity.x = -vel
return velocity
def reverse_direction(sprite):
if sprite.direction == 0:
sprite.direction = 4
elif sprite.direction == 2:
sprite.direction = 6
elif sprite.direction == 4:
sprite.direction = 0
elif sprite.direction == 6:
sprite.direction = 2
# main
pygame.init()
DISPLAYSURF = pygame.display.set_mode((800,600))
pygame.display.set_caption("Collision Detection")
font = pygame.font.SysFont(None, 36)
fpsclock = pygame.time.Clock()
fps = 30
# create sprite groups
zombie_group = pygame.sprite.Group()
player_group = pygame.sprite.Group()
health_group = pygame.sprite.Group()
# create player sprite
player = MySprite(DISPLAYSURF)
player.load("farmer walk.png", 96, 96, 8)
player.position = (80,80)
player.direction = 4
player_group.add(player)
# create zombie sprite
zombie_image = pygame.image.load("zombie walk.png").convert_alpha()
for i in range(1):
zombie = MySprite(DISPLAYSURF)
zombie.load("zombie walk.png", 96, 96, 8)
zombie.position = (random.randint(0, 700), random.randint(0, 500))
zombie.direction = random.randint(0,3) * 2
zombie_group.add(zombie)
# create health sprite
health = MySprite(DISPLAYSURF)
health.load("health.png", 32, 32, 1)
health.position = (400, 300)
health_group.add(health)
game_over = False
player_moving = False
player_health = 100
# colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 0)
YELLOW = (255, 255, 0)
##DISPLAYSURF.fill(BLACK)
##pygame.mouse.set_visible(True)
# event loop
while True:
ticks = pygame.time.get_ticks() # ms since pygame.init() called
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEMOTION:
mousex, mousey = event.pos
# keyboard polling
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]:
pygame.quit()
sys.exit()
elif keys[K_UP] or keys[K_w]:
player.direction = 0
player_moving = True
elif keys[K_RIGHT] or keys[K_d]:
player.direction = 2
player_moving = True
elif keys[K_LEFT] or keys[K_a]:
player.direction = 6
player_moving = True
elif keys[K_DOWN] or keys[K_s]:
player.direction = 4
player_moving = True
else:
player_moving = False
# these things should not happen if game is over
if not game_over:
# update player sprite
player_group.update(ticks, 50)
# use player direction to calculate frame range
player.first_frame = player.direction * player.columns
player.last_frame = player.first_frame + player.columns-1
if player.frame < player.first_frame:
player.frame = player.first_frame
if not player_moving:
# stop animating when player is not moving
player.frame = player.first_frame = player.last_frame
else:
# move player in that direction
player.velocity = calc_velocity(player.direction, 1.5)
player.velocity.x *= 1.5
player.velocity.y *= 1.5
# manually move player
if player_moving:
player.X += player.velocity.x
player.Y += player.velocity.y
if player.X <0: player.X = 0
elif player.X > 700: player.X = 700
if player.Y <0: player.Y = 0
elif player.Y > 500: player.Y = 500
# update zombie sprites
zombie_group.update(ticks, 50)
# manually update zombies
for z in zombie_group:
# set zombie animation range
z.first_frame = z.direction * z.columns
z.last_frame = z.first_frame + z.columns-1
if z.frame < z.first_frame:
z.frame = z.first_frame
z.velocity = calc_velocity(z.direction)
# keep zombie on screen
z.X += z.velocity.x
z.Y += z.velocity.y
if z.X < 0 or z.X > 700 or z.Y < 0 or z.Y > 500:
reverse_direction(z)
# check for sprite collision
attacker = 0
attacker = pygame.sprite.spritecollideany(player, zombie_group)
if attacker != None:
# more precise check
if pygame.sprite.collide_rect_ratio(0.5)(player, attacker):
player_health -= 10
if attacker.X < player.X: attacker.X -= 10
elif attacker.X > player.X: attacker.X += 10
else:
attacker = None
# update health drop
health_group.update(ticks, 50)
# check for collision with health
if pygame.sprite.collide_rect_ratio(0.5)(player, health):
player_health += 30
if player_health >100: player_health = 100
health.X = random.randint(0, 700)
health.Y = random.randint(0, 500)
# is player dead?
if player_health <= 0:
game_over = True
# clear screen
DISPLAYSURF.fill((50,50,100))
# draw sprites
player_group.draw(DISPLAYSURF)
zombie_group.draw(DISPLAYSURF)
health_group.draw(DISPLAYSURF)
# draw energy bar
pygame.draw.rect(DISPLAYSURF, WHITE, (299, 555, 203, 31), 2)
pygame.draw.rect(DISPLAYSURF, GREEN, (301, 557, player_health * 2, 28))
# print zombie and player velocities for purpose of testing
print_text(font, 350, 460, "Zombie X vel: " +\
str(zombie.velocity.x) + "\nY vel: " +\
str(zombie.velocity.y))
print_text(font, 350, 500, "Player X vel: " +\
str(player.velocity.x) + "\nY vel: " +\
str(player.velocity.y))
if game_over:
print_text(font, 300, 200, "G A M E O V E R")
pygame.display.update()
fpsclock.tick(fps)
Problem
The sprite group is drawing your sprite using the sprite's rect attribute. A pygame Rect object can only hold integers, so it'll truncate all floating point numbers.
Let's say you have a x = 5.
If you add 1.1: x += 1.1 <=> x = x + 1.1 <=> x = 5 + 1.1 <=> x = 6.1 which will be truncated to x = 6. It have increased by 1.
If you subtract 1.1: x -= 1.1 <=> x = x - 1.1 <=> x = 5 - 1.1 <=> x = 3.9 which will be truncated to x = 3. It have decreased by 2.
In other words: You'll move faster in the left direction than the right (the same principle applies to negative numbers). Here's an example demonstrating it:
import pygame
pygame.init()
class Player(pygame.sprite.Sprite):
def __init__(self, group):
super(Player, self).__init__(group)
self.image = pygame.Surface((32, 32))
self.rect = self.image.get_rect()
screen = pygame.display.set_mode((100, 100))
group = pygame.sprite.Group()
player = Player(group)
clock = pygame.time.Clock()
while True:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
x = player.rect.x + 1.1
print("Actual x:", x)
player.rect.x = player.rect.x + 1.1
print("Truncated x:", player.rect.x)
elif event.key == pygame.K_LEFT:
x = player.rect.x - 1.1
print("Actual x:", x)
player.rect.x = player.rect.x - 1.1
print("Truncated x:", player.rect.x)
screen.fill((255, 255, 255))
group.draw(screen)
pygame.display.update()
Solution
Using floating point numbers for position is great; it makes it possible to move a sprite less than a pixel every frame (if your game updates 120 times per second and you want your sprite to move only 30 pixels per second).
However, you have to compensate for the fact that the rect objects cannot hold them. The most straightforward solution is to have an attribute position which keep track of the position of the sprite in floating point precision. Then at every update change the rect to the position of the attribute. Like this:
import pygame
pygame.init()
class Player(pygame.sprite.Sprite):
def __init__(self, group):
super(Player, self).__init__(group)
self.image = pygame.Surface((32, 32))
self.rect = self.image.get_rect()
self.position = self.rect.x # Or whatever point of the rect you want the position to be.
screen = pygame.display.set_mode((100, 100))
group = pygame.sprite.Group()
player = Player(group)
clock = pygame.time.Clock()
while True:
clock.tick(10)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
player.position += 1.1
player.rect.x = player.position
elif event.key == pygame.K_LEFT:
player.position -= 1.1
player.rect.x = player.position
screen.fill((255, 255, 255))
group.draw(screen)
pygame.display.update()
I've only showed how this movement works in the x-axis, but it's exactly the same on the y-axis.
Ok. I think the problem is that the number of pixels that the sprite is moved each update is rounded down, so that 2.25 becomes 2 pixels and -2.25 becomes -3 pixels. Moving by a fractional number of pixels doesn't make sense I think.
If you change lines 229 - 233 to
else:
# move player in that direction
player.velocity = calc_velocity(player.direction, 2.0)
player.velocity.x *= 2.0
player.velocity.y *= 2.0
The velocity is now an integer and there would be no rounding problems. It is faster though. Is there some reason why you don't just have the velocity as an integer instead of a float squared?

New to programming pong game paddle collisions in pygame?

Hi I am extremely new to coding in general and have been trying to improve my fluency. Thats why I started coding a pong game with Pygame because I wanted something visual. so I have created a pong design with working paddles and a ball that I have bouncing off walls but am having trouble getting the paddle collisions corect. I have it so it prints Paddle when my paddle and ball are aligned but it doesnt always detect it. How can I make it have more sensitivity and add bounce to the paddle. Once again I am very new so any syntax correction or advice on making the code easier to read or simplified would be great: the code is as follows :
import sys, pygame, pygame.mixer, random, math
from pygame.locals import *
pygame.init()
screen_size = width, height = 600, 400
black = 0,0,0
red = (200,0,0)
blue = 0,0,200
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption("PONG WANNABE")
screen.fill(black)
paddle = pygame.image.load("paddle.png")
divider = pygame.image.load("divider.png")
def addVectors((angle1, length1), (angle2, length2)):
x = math.sin(angle1) * length1 + math.sin(angle2) * length2
y = math.cos(angle1) * length1 + math.cos(angle2) * length2
length = math.hypot(x, y)
angle = 0.5 * math.pi - math.atan2(y, x)
return (angle, length)
class Ball:
def __init__(self):
self.size = 8
self.x = width/2
self.y = height/2
self.colour = (255, 255, 255)
self.thickness = 0
self.speed = 0.05
self.angle = math.pi/2
def display(self):
pygame.draw.circle(screen,self.colour,int(self.x),int(self.y)),
self.size,self.thickness)
def move(self):
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.angle = - self.angle
elif self.x < self.size:
self.x = 2*self.size - self.x
self.angle = - self.angle
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.angle = math.pi - self.angle
elif self.y < self.size:
self.y = 2*self.size - self.y
self.angle = math.pi - self.angle
ball = Ball()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.display.quit()
sys.exit()
elif event.type == KEYDOWN and event.key == K_SPACE:
black = blue
elif event.type == KEYUP and event.key == K_SPACE:
black = 0,0,0
screen.fill(black)
mx,my = pygame.mouse.get_pos()
screen.blit(divider,(width/2, 0))
screen.blit(paddle,(10,my-35))
screen.blit(paddle,(width-20,365-my))
ball.move()
if ball.x < my + 73 and ball.x > my:
print "Paddle"
ball.bounce()
ball.display()
pygame.display.flip()
I would honestly try to use Rectangles and check if the Rectangles collide. Full documentation can be found at http://www.pygame.org/docs/ref/rect.html but I'll still explain directly how in your case.
In your Ball class you will probably want to create a Rect.
class Ball:
def __init__(self):
#all of your x, y etc.
#add in a react at the end of init
self.rect = pygame.Rect(self.x, self.y, self.size, self.size)
#self.size is the width and height
Then at the end of the function move you need to update the rect
def move(self):
#move ball with what you already have
#now update the rect
self.rect.x = x
self.rect.y = y
Now you'll want to create a rectangle for the paddle, now I dont know how wide your paddle is, but I'm going to just say 10 and you can adjust that as necessary.
So now to add the rectangle. In the while loop right after you get the x and y of the paddle add this line to create a rectangle
mrect = pygame.Rect(mx, my, 10, 73)
So now you have a rectangle to work with to check for a collide point. Now instead of this line if ball.x < my + 73 and ball.x > my: you need to change this to check for a collide point between your two rectangles. We're going to do this by using a function that is part of the class pygame.Rect called colliderect
So change the line if ball.x < my + 73 and ball.x > my: to this if ball.rect.colliderect(mrect): this will check if the rectangles, the ball or the paddle collide at any point.
Good luck!
This is for double ball pong. name ball instances circle_m and circle_mc. name paddle instances rectangle and rectangle_mc. name boundaries boundary1 and boundary. name goals goal1 and goal2. name text boxes textField_1,textField_2, and textField_3. Then use this code and have fun. (must be made in adobe flash obviously)
//1.
var xDir2 = 1;
var yDir2 = 1;
stage.addEventListener(Event.ENTER_FRAME, circleHit);
var xDir = 1;
var yDir = 1;
var score = 0;
var score2 = 0;
//2.
function circleHit(event:Event):void
{
{
circle_mc.x += 10 * xDir;
circle_mc.y += 10 * yDir;
circle_m.x += 5 * xDir2;
circle_m.y += 5 * yDir2;
if (circle_mc.hitTestObject(rectangle_mc))
{
xDir *= -1;
}
};
if (circle_mc.hitTestObject(rectangle))
{
xDir *= -1;
}
if (circle_mc.hitTestObject(boundary))
{
yDir *= -1;
}
if (circle_mc.hitTestObject(boundary1))
{
yDir *= -1;
}
if (circle_mc.hitTestObject(goal2))
{
score2++;
circle_mc.y = 161.95;
circle_mc.x = 273;
}
textField_2.text = score2;
textField_1.text = score;
if (circle_mc.hitTestObject(goal1))
{
score++;
circle_mc.y = 161.95;
circle_mc.x = 273;
}
if (circle_m.hitTestObject(goal2))
{
score2++;
circle_m.x = 273;
circle_m.y = 161.95;
}
if (circle_m.hitTestObject(goal1))
{
score++;
circle_m.x = 273;
circle_m.y = 161.95;
}
if (circle_m.hitTestObject(rectangle_mc))
{
xDir2 *= -1;
}
if (circle_m.hitTestObject(rectangle))
{
hSiz2 *= -2;
xDir2 *= -1;
}
if (circle_m.hitTestObject(boundary))
{
yDir2 *= -1;
}
if (circle_m.hitTestObject(boundary1))
{
yDir2 *= -1;
}
}
//This section creates variables to store the condition of each key A(up or down)
var keyUP:Boolean = false;
var keyDOWN:Boolean = false;
var keyRIGHT:Boolean = false;
var keyLEFT:Boolean = false;
This section creates event listeners. The first two check if any key has been pressed down or come back up. The third event listener controls a repeating loop
stage.addEventListener(KeyboardEvent.KEY_UP,key_Up);
stage.addEventListener(KeyboardEvent.KEY_DOWN,key_Down);
stage.addEventListener(Event.ENTER_FRAME,loop);
These functions changes the value of the variables if the key comes up.The switch statement; checks for which key is being processed. The case statements provide the options for each key with the number representing a key. break ends the case.
function key_Up(event:KeyboardEvent)
{
switch (event.keyCode)
{
case Keyboard.W :
keyLEFT = false;
break;
case 38 :
keyUP = false;
break;
case Keyboard.S :
keyRIGHT = false;
break;
case 40 :
keyDOWN = false;
}
}
function key_Down(event:KeyboardEvent)
{
switch (event.keyCode)
{
case Keyboard.W :
keyLEFT = true;
break;
case 38 :
keyUP = true;
break;
case Keyboard.S :
keyRIGHT = true;
break;
case 40 :
keyDOWN = true;
}
}
This function will repeat every time a frame is entered. It uses the values of the variables to control actions. The trace statement will display a word in the output window
function loop(event:Event)
{
if (keyUP)
{
rectangle_mc.y -= 20;
}
if (keyDOWN)
{
rectangle_mc.y -= -20;
}
if (keyLEFT)
{
rectangle.y -= 20;
}
if (keyRIGHT)
{
rectangle.y -= -20;
}
if (score ==10)
{
textField_3.text = "Left side WINS!!!!!";
circle_mc.y = -5000;
circle_mc.x = -5000;
}
if (score2 ==10)
{
textField_3.text = "Right side WINS!!!!!";
circle_mc.y = -5000;
circle_mc.x = -5000;
}
if (score ==10)
{
textField_3.text = "Left side WINS!!!!!";
circle_m.y = -5000;
circle_m.x = -5000;
}
if (score2 ==10)
{
textField_3.text = "Right side WINS!!!!!";
circle_m.y = -5000;
circle_m.x = -5000;
}
}