I've been making pong with pygame and I got the ball to bounce around the screen and on the paddles. However, the speed is too high and I want to decrease it. This is what the code looks like for the Ball object:
import pygame as pg
BLACK = (0, 0, 0)
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height, radius):
super().__init__()
self.x_vel = 1
self.y_vel = 1
self.image = pg.Surface([width * 2, height * 2])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
pg.draw.circle(self.image, color, center=[width, height], radius=radius)
self.rect = self.image.get_rect()
def update_ball(self):
self.rect.x += self.x_vel
self.rect.y += self.y_vel
If I try to set the velocity as a float, it stops the ball completely. Can someone help me?
Use pygame.time.Clock to control the frames per second and thus the game speed.
The method tick() of a pygame.time.Clock object, delays the game in that way, that every iteration of the loop consumes the same period of time. See pygame.time.Clock.tick():
This method should be called once per frame.
That means that the loop:
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
runs 100 times per second.
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the movement of the object is assigned to the Rect object.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:
class Ball(pg.sprite.Sprite):
def __init__(self, color, width, height, radius):
# [...]
self.rect = self.image.get_rect()
self.x = self.rect.x
self.y = self.rect.y
def update_ball(self):
self.x += self.x_vel
self.y += self.y_vel
self.rect.x = round(self.x)
self.rect.y = round(self.y)
Related
I am teaching myself pygame and am looking at making my character able to rotate and then move in the direction they are facing.
I can do the rotation but cannot get the character to move in the direction the image is then facing.
The code is on Trinket HERE
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
super().__init__()
self.image = pygame.Surface([width , height])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
#Loading the image for the character
self.img = pygame.image.load("char.jfif")
#creating a copy of the image
self.img_orig = self.img.copy()
#defining the starting angle of the character image
self.angle = 0
self.velocity = 5
self.rect = self.img_orig.get_rect()
def moveLeft(self):
self.angle += 1
self.img = pygame.transform.rotate(self.img_orig, self.angle)
def moveRight(self):
self.rect.x += self.velocity
if self.rect.x > 485:
self.rect.x = 485
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
pSprite.moveForward()
if keys[pygame.K_DOWN]:
pSprite.moveDown()
if keys[pygame.K_LEFT]:
pSprite.moveLeft()
if keys[pygame.K_RIGHT]:
pSprite.moveRight()
#---- Game Logic Here
#--- Drawing Code Here
#Reset the screen to blank
screen.fill(BLUE)
#Draw Play Area
#Draw Sprites
screen.blit(pSprite.img,(pSprite.rect.x, pSprite.rect.y))
You can use pygame's Vector2 class instead of calculating the position of your sprite yourself.
I also suggest to let the sprite itself handle its movement instead of doing so in the main loop and using a clock for constant framerates and easy control of the speed of your sprites.
You also probably want to use an image format with alpha channel (like PNG).
Here's a simple example:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, pos, *grps):
super().__init__(*grps)
self.image = pygame.image.load('char.png').convert_alpha()
self.image_org = self.image.copy()
self.rect = self.image.get_rect(center=pos)
self.pos = pygame.Vector2(pos)
self.direction = pygame.Vector2((0, -1))
def update(self, events, dt):
pressed = pygame.key.get_pressed()
# if a is pressed, rotate left with 360 degress per second
if pressed[pygame.K_a]: self.direction.rotate_ip(dt * -360)
# if d is pressed, rotate right with 360 degress per second
if pressed[pygame.K_d]: self.direction.rotate_ip(dt * 360)
# check if should move forward or backward
movement = 0
if pressed[pygame.K_w]: movement = 1
if pressed[pygame.K_s]: movement = -1
movement_v = self.direction * movement
if movement_v.length() > 0:
movement_v.normalize_ip()
# move 100px per second in the direction we're facing
self.pos += movement_v * dt * 100
# rotate the image
self.image = pygame.transform.rotate(self.image_org, self.direction.angle_to((0, -1)))
self.rect = self.image.get_rect(center=self.pos)
def main():
pygame.init()
screen = pygame.display.set_mode((600, 480))
sprites = pygame.sprite.Group()
Actor((100, 100), sprites)
clock, dt = pygame.time.Clock(), 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill('grey')
sprites.draw(screen)
sprites.update(events, dt)
pygame.display.flip()
dt = clock.tick(60) / 1000
main()
char.png
Rotate the player around its center (see How do I rotate an image around its center using PyGame?):
self.angle += 1
self.img = pygame.transform.rotate(self.img_orig, self.angle)
self.rect = self.img.get_rect(center = self.rect.center)
Use an attribute x and y to store the position of the player with floating point accuracy.
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
# [...]
self.x, self.y = self.rect.center
Compute the direction of the player dependent on the angle with the trgonometric function math.sin and math.cos. Change the position attributes and update the rect attribute:
self.x += self.velocity * math.cos(math.radians(self.angle + 90))
self.y -= self.velocity * math.sin(math.radians(self.angle + 90))
self.rect.center = round(self.x), round(self.y)
The y-axis needs to be reversed (-dy) as the y-axis is generally pointing up, but in the PyGame coordinate system the y-axis is pointing down. In addition, a correction angle must be deducted (+ 90). Since the "angle" is 0 ° when the sprite is looking up, you need to add 90 ° to the angle for the calculation of the direction vector.
See also te in pygame while moving with the keys](How to turn the sprite in pygame while moving with the keys.
Class Bob:
import pygame
import math
BLACK = (0,0,0)
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
super().__init__()
self.image = pygame.Surface([width , height])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
#Loading the image for the character
self.img = pygame.image.load("char.jfif")
#creating a copy of the image
self.img_orig = self.img.copy()
#defining the starting angle of the character image
self.angle = 0
self.velocity = 5
self.rect = self.img_orig.get_rect()
self.x, self.y = self.rect.center
def rotate(self, change_angle):
self.angle += change_angle
self.img = pygame.transform.rotate(self.img_orig, self.angle)
self.rect = self.img.get_rect(center = self.rect.center)
def move(self, distance):
self.x += distance * math.cos(math.radians(self.angle + 90))
self.y -= distance * math.sin(math.radians(self.angle + 90))
self.rect.center = round(self.x), round(self.y)
def moveLeft(self):
self.rotate(1)
def moveRight(self):
self.rotate(-1)
def moveForward(self):
self.move(self.velocity)
def moveDown(self):
self.move(-self.velocity)
When setting the starting position of the player, you need to set the x, y and rect attribute:
pSprite = Bob(WHITE , 25,25)
pSprite.rect.x = 50
pSprite.rect.y = 50
pSprite.x, pSprite.y = pSprite.rect.center
I am making a game where a player moves through a level, the target moves randomly through the level and there are guards (not part of game yet). I am having trouble implementing a wall collision as my sprites are circles and my walls are rectangles so I do not know how to do this also, the walls and the sprites are in different classes and groups.
I've thought about using masks for my sprites but I am not confident with how to do this. I also don't know how to design the method for wall collisions as the current one sends my player (I have not got it working for the target as well) to the bottom of any wall.
class characters(pygame.sprite.Sprite):
def __init__(self, colour, width, height):
super().__init__()
self.colour = colour
self.width = width
self.height = height
self.image = pygame.Surface([self.width, self.height])
self.image.fill(white)
self.image.set_colorkey(white)
self.rect = self.image.get_rect()
pygame.draw.circle(self.image, self.colour, [int(self.width/2), int(self.height/2)], int(self.width/2))
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, pixels):
self.rect.y -= pixels
def moveDown(self, pixels):
self.rect.y += pixels
def moveRandom(self, pixels, clockrate, count, xdirection, ydirection):
self.rect.x += pixels * xdirection
self.rect.y += pixels * ydirection
#print(self.rect.x,self.rect.y)
def wallCollide(self):
x = self.rect.x
for xpos in [-5,5]:
self.rect.x += xpos
if pygame.sprite.spritecollide(self, wallSet, False, pygame.sprite.collide_rect):
self.rect.x -= xpos
else:
self.rect.x = x
y = self.rect.y
for ypos in [-5,5]:
self.rect.y += ypos
if pygame.sprite.spritecollide(self, wallSet, False, pygame.sprite.collide_rect):
self.rect.y -= ypos
else:
self.rect.y = y
I attempted a mask in the wall class below, don't think it works.
class walls(pygame.sprite.Sprite):
def __init__(self, x, y, width, height):
super().__init__()
self.width = width
self.height = height
self.x = x
self.y = y
self.image = pygame.Surface([width,height])
self.image.fill(black)
#self.image.set_colorkey(black) Using this will make the walls invisible as I have textured walls drawn over
self.rect = self.image.get_rect(topleft=(self.x, self.y))
pygame.mask.from_surface(self.image)
I have only given one wall and reduced the resolution as it is far easier whilst I write my code. The resolution should be 1920 by 1080. Use 600 by 650 to see the two walls.
characterSet = pygame.sprite.Group()
player = characters(black, 30, 30)
target = characters(green, 30, 30)
characterSet.add(player, target)
wallSet = pygame.sprite.Group()
Wall1 = walls(288,176, 200,142)
Wall2 = walls(286,427, 201,140)
wallSet.add(Wall1,Wall2)
The main game loop
while not end:
for event in pygame.event.get():
if event.type == pygame.QUIT:
end = True
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.moveLeft(5)
if keys[pygame.K_RIGHT]:
player.moveRight(5)
if keys[pygame.K_UP]:
player.moveUp(5)
if keys[pygame.K_DOWN]:
player.moveDown(5)
if count%clockrate == 0:
xdirection = random.choice(direction)
ydirection = random.choice(direction)
target.moveRandom(2, clockrate, count, xdirection, ydirection)
count += 1
if pygame.sprite.spritecollide(player, wallSet, False, pygame.sprite.collide_rect):
player.wallCollide()
gameDisplay.blit(level1,[0,0])
wallSet.draw(gameDisplay)
gameDisplay.blit(innerWalls,[0,0])
characterSet.draw(gameDisplay)
If I understand correctly you have a list of wall objects and a list of character objects and you want to check collision between all of them. this can be accomplished quickly by looping over both lists:
for wall in WallSet:
for character in CharacterSet:
character.checkCollision(wall)
The checkCollision() method can be very simple, checking if the rectangles around your objects overlap would just be a few if statements. If you want this checkCollisions() method to work well for rounded characters you'll need to write something fancier. I would suggest:
Write a function that will give the point inside a rectangle that is closest to another point (the hard part! Though I'm sure there are tutorials on how to do this online)
Take that point inside the wall's rectangle, and check if that is far enough away from the character's middle position to not be colliding.
Hopefully this helps! For future questions I would suggest trying to very clearly ask 1 question and reduce the code you are posting further. For the question you are asking, the amount of code you posted is too much, and probably turned away potential answerers for these past 2 days.
I'm still pretty new to pygame, and coding in general. I'm making a game that requires collision detection, and I seem to be having a problem with it. Every time I run my program, it detects non-existent collisions. Here are some snippets from my code:
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
self.right = False
self.left = False
self.up = False
self.down = False
self.surf = pygame.Surface((50,50))
self.rect = self.surf.get_rect()
def draw(self):
pygame.draw.rect(screen, (0,0,0), (self.x, self.y, self.width, self.height))
def collision_test(self):
if pygame.sprite.collide_rect(self, block1):
print("a collision is detected")
The above is my player class.
class Block1(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.width = width
self.height = height
self.surf = pygame.Surface((self.width,self.height))
self.rect = self.surf.get_rect()
def draw(self):
pygame.draw.rect(screen, (150,150,150), (self.x, self.y, self.width, self.height))
And that's my class for the obstacle that my player is supposed to collide with. I'm running a print command in the collision detection for debugging. Like I said, it just constantly prints the message I gave it, even when they aren't colliding. There are no error messages though. Any help would be appreciated! Thanks in advance :)
EDIT:
I changed the collision_test method and added the block1 argument. Now it is this:
def collision_test(self, block1):
if pygame.sprite.collide_rect(self, block1):
print("a collision is detected")
My player and block1 sprites are initiated just before the mainloop, and look like this:
player = Player(50,50,50,50)
block1 = Block1(200, 200, 100, 100)
I am calling the function, collision_test, at the end of the mainloop. In case you need it, here is my full code: https://pastebin.com/LTQdLMuV
What happens is that you forgot to update the position of the rectangles of your objects.
From pygame docs:
get_rect()
get the rectangular area of the Surface
get_rect(**kwargs)-> Rect
Returns a new rectangle covering the entire surface. This rectangle will always start at 0, 0 with a width. and height the same size as the image.
In both classes Player and Block1 you have a line:
self.rect = self.surf.get_rect()
To use colliderect() the rect attribute must be updated to the position (in pixels) of the image on the screen, otherwise there is a mismatch between the coordinates used by your draw() method and the rectangle used to check the collisions. Do instead:
self.rect = self.surf.get_rect().move(x, y)
so that, when the object is created, the rect attribute corresponds to the real position of the object on the screen.
Remember to update the position of player.rect when you move the player square. Edit your move_player() function too, for example by adding:
player.rect.x = player.x
player.rect.y = player.y
So that rect corresponds on what you have on the screen.
EDIT after comments
If your goal is how to preveent orverlapping between surfaces, it's more complicated. Detecting the collision is only part of the process. The full steps are:
move the player object.
detecting not only if there is a collision, but also the sides which collided.
once the side is detected, move back the player object on that axis.
redraw.
I'm trying to learn game programming (and programming in general), therefore im making a simple sidescroller in Python using pygame.
The problem is, when I'm moving the character (a simple cube) to the left it moves faster than when im moving to the right, as shown in console later on.
Here is picture of the game.
The main class with the loop (I've cut out some of it, because it doesn't have relevance, I think, but I can post it if necessary):
player.speed = 135
player = Player.Cube([255,0,0],600, screen.get_height()*0.8-screen.get_height()*0.125,screen.get_height()*0.125,screen.get_height()*0.125)
running = True
clock = pygame.time.Clock()
while running:
deltaTime = clock.tick(60) / 1000
screen.blit(background, (0, 0))
screen.blit(player.image,(player.rect.x,player.rect.y))
screen.blit(grass,(0,screen.get_height()-grass.get_height()))
keystate = pygame.key.get_pressed()
if keystate[K_RIGHT]:
player.moveRight(deltaTime)
elif keystate[K_LEFT]:
player.moveLeft(deltaTime)
pygame.display.flip()
Cube class:
class Cube(pygame.sprite.Sprite):
speed = 0
def __init__(self, color, left,top, width, height):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.left = left
self.top = top
self.image = pygame.Surface([width,height])
self.image.fill(color)
self.rect = pygame.Rect(left, top, width, height)
def moveRight(self,deltaTime):
oldX=self.rect.x
self.rect.x += self.speed*deltaTime
print(self.speed*deltaTime)
print("deltaX: " + str(self.rect.x-oldX))
def moveLeft(self,deltaTime):
oldX = self.rect.x
self.rect.x -= self.speed*deltaTime
print(self.speed * deltaTime)
print("deltaX: " + str(self.rect.x-oldX))
As you can see, I'm trying to print out, how many pixels I moved to the right vs how many to the left:
Speed times deltaTime, right: 2.2950000000000004
deltaX, right: 2
exiting game
Speed times deltaTime, left: 2.2950000000000004
deltaX, left: -3
I don't understand one bit of this, does anyone now what the cause of this is?
Also, my movement is stuttering a bit, why is that?
That happens because pygame.Rects can only have integers as their coordinates, and if you add or subtract a floating point number the result gets truncated afterwards.
For example if the rect.x is 10 and you add 1.5 to rect.x the new value is 11.5 which is then converted to 11. If you subtract 1.5 from 10 you get 8.5 and after truncating it 8. So in the first case you move 1 px to the right and in the second you move 2 px to the left.
The solution is to store the actual position as a separate variable or attribute, add the speed to it and then assign this value to the rect.
pos_x += speed
rect.x = pos_x
I usually use vectors to store the position and the velocity.
import pygame as pg
from pygame.math import Vector2
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(pos)
def update(self):
self.pos += self.vel
self.rect.center = self.pos
I'm trying to handle collision checking on multiple sprites checking it against a player character. Here's the relevant code, the Enemy class creates a new sprite that is supposed to be represented by an image, and the Character class is similar, except it is the sprite the player can control. Here's the relevant code I've snipped from the project.
self.all_sprites_list = pygame.sprite.Group()
sprite = Character(warrior, (500, 500), (66, 66))
enemies = []
for i in range(10):
enemy = Enemy("evilwizard")
enemies.append(enemy)
self.all_sprites_list.add(enemy)
self.all_sprites_list.add(sprite)
class Enemy(pygame.sprite.Sprite):
# This class represents the types of an enemy possible to be rendered to the scene
def __init__(self, enemy_type):
super().__init__() # Call sprite constructor
# Pass in the type of enemy, x/y pos, and width/height (64x64)
self.image = pygame.Surface([76, 76])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
self.rect.x = random.randrange(10, 1150) # random start
self.rect.y = random.randrange(10, 590) # random start
self.speed = 2
self.move = [None, None] # x-y coordinates to move to
self.image = pygame.image.load(FILE_PATH_ENEMY + enemy_type + ".png").convert_alpha()
self.direction = None # direction to move the sprite`
class Character(pygame.sprite.Sprite):
def __init__(self, role, position, dimensions):
"""
:param role: role instance giving character attributes
:param position: (x, y) position on screen
:param dimensions: dimensions of the sprite for creating image
"""
super().__init__()
# Call the sprite constructor
# Pass in the type of the character, and its x and y position, width and height.
# Set the background color and set it to be transparent.
self.image = pygame.Surface(dimensions)
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.image = pygame.image.load(FILE_PATH_CHAR + role.title + ".png").convert_alpha()
# Draw the character itself
# position is the tuple (x, y)
self.rect = self.image.get_rect()
self.rect.x, self.rect.y = position
self.attack = role.attack
self.health = role.health
self.title = role.title
Pygame has pygame.sprite.spritecollide to check collision between pygame.sprite.Sprite() and pygame.sprite.Group()
It has also other functions to check collisions - see doc for Sprite