Pygame shapes cannot take non integer arguments - python

Does it matter that in the Pygame module, the shapes cannot take float values as their arguments?
This is raised due to the fact that I am currently making a relatively basic physics simulation, and using pygame to do the graphics, and in a physics simulation, it rarely/never happens that an object is centred such that it has an integer value.
I am wondering mainly if this would have a significant effect on the accuracy of the simulation?

I usually recommend storing the position and velocity of the game objects as vectors (which contain floats) to keep the physics accurate. Then you can first add the velocity to the position vector and afterwards update the rect of the object which serves as the blit position and can be used for collision detection. You don't have to convert the position vector to ints before you assign it to the rect, since pygame will do that automatically for you.
Here's a little example with an object that follows the mouse.
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, 30))
self.image.fill(pg.Color('steelblue2'))
self.rect = self.image.get_rect(center=pos)
self.direction = Vector2(1, 0)
self.pos = Vector2(pos)
def update(self):
radius, angle = (pg.mouse.get_pos() - self.pos).as_polar()
self.velocity = self.direction.rotate(angle) * 3
# Add the velocity to the pos vector and then update the
# rect to move the sprite.
self.pos += self.velocity
self.rect.center = self.pos
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
font = pg.font.Font(None, 30)
color = pg.Color('steelblue2')
all_sprites = pg.sprite.Group()
player = Player((100, 300), all_sprites)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
txt = font.render(str(player.pos), True, color)
screen.blit(txt, (30, 30))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

In simulation terms/accuracy wise, you should always keep your data as float. You should just round them, and cast them to int at the very point you are calling Pygame's functions that need integers.
Just do something like:
pygame.draw.rect(surface, color, (int(x), int(y), width, height))
for drawing a rectangle, for example.

Related

How do I keep objects inside pygame surface when changing squared shape to a rectangle? [duplicate]

This question already has answers here:
Setting up an invisible boundary for my sprite
(1 answer)
Use vector2 in pygame. Collide with the window frame and restrict the ball to the rectangular area
(1 answer)
Create a border in PyGame
(1 answer)
Not letting the character move out of the window
(2 answers)
Closed 13 days ago.
In playing around with pygame capabilities I prepare to code a game of my own. While most of pygame's examples display surfaces like squares, I'd like to run with a rectangular shape resembling that of a cell phone screen. Initially, I'd expect that simply reshaping a GfG example picked up on the internet would do the job, yet I realize that objects do not stay inside the new rectangular shape when moving a sprite around with keyboard arrows.
I attempted to adjust width and height of surface (changed from (500, 500)):
# Global Variables
COLOR = (255, 100, 98)
SURFACE_COLOR = (167, 255, 100)
WIDTH = 580
HEIGHT = 250
But the squared object that I can control keeps continuing outside the new rect shape.
Any suggestions on how to proceed?
My playground code picked up on www.geeksforgeeks.org looks as follows:
import random
import pygame
# Global Variables
COLOR = (255, 100, 98)
SURFACE_COLOR = (167, 255, 100)
WIDTH = 580
HEIGHT = 250
# Object class
class Sprite(pygame.sprite.Sprite):
def __init__(self, color, height, width):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(SURFACE_COLOR)
self.image.set_colorkey(COLOR)
pygame.draw.rect(self.image,
color,
pygame.Rect(0, 0, width, height))
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveForward(self, speed):
self.rect.y += speed * speed/10
def moveBack(self, speed):
self.rect.y -= speed * speed/10
pygame.init()
RED = (255, 0, 0)
size = (WIDTH, HEIGHT)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Controlling Sprite")
all_sprites_list = pygame.sprite.Group()
playerCar = Sprite(RED, 20, 30)
playerCar.rect.x = 150
playerCar.rect.y = 150
all_sprites_list.add(playerCar)
exit = True
clock = pygame.time.Clock()
while exit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_x:
exit = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerCar.moveLeft(5)
if keys[pygame.K_RIGHT]:
playerCar.moveRight(5)
if keys[pygame.K_DOWN]:
playerCar.moveForward(5)
if keys[pygame.K_UP]:
playerCar.moveBack(5)
all_sprites_list.update()
screen.fill(SURFACE_COLOR)
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()

Pygame overlapping Sprites (draw order) based on location

I'm still relatively new to Pygame and Python in general, so hopefully this isn't too out there.
I'm making a top-down RPG, and I have two Sprite objects with images that look (for example) like these:
that use rects that do not represent the entirety of the image:
class NPC(pygame.sprite.Sprite):
def __init__(self, image, pos, *groups):
super().__init__(*groups)
self.image = pygame.load(image)
self.rect = self.image.get_rect()
self.collisionRect = pygame.Rect(self.rect.left, self.rect.top + 12, self.image.get_width(), self.image.get_width())
#12 is the number of pixels that will overlap in the y-dimension
I'm doing this because I want the top few pixels of the NPC to overlap with other sprites. The collisionRect in each object is used over the rect whenever I detect a collision, so that I can create this effect.
However, I need a way to redraw them within my update() function so that one draws over the other based on their relative locations to each other.
So, when one NPC is above the other it looks like this:
But, when it's the other way around, it should look like this:
Which means that the images need to be drawn in a different order depending on which sprite is 'below' the other.
I've thought about possibly cutting the sprites into separate sprites and just have the 'head' sprites draw last, but I was wondering if there was a simpler (or at least a reliable) way to detect whether a sprite should be drawn last or not, based on whether or not it is both overlapping another sprite and immediately below it in the y-dimension.
I apologize if this question is too broad or needs more context; can provide those if needed.
As Kingsley already said in a comment, sorting your sprites by their y coordinate is a common way to do this.
Here's a full, running example (I named your images guy.png and gal.png). Since you already use sprites, I used a simple pygame.sprite.Group-subclass:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, image, pos):
super().__init__()
self.image = image
self.pos = pygame.Vector2(pos)
self.rect = self.image.get_rect(center=self.pos)
def update(self, events, dt):
pass
class Player(Actor):
def __init__(self, image, pos):
super().__init__(image, pos)
def update(self, events, dt):
pressed = pygame.key.get_pressed()
move = pygame.Vector2((0, 0))
if pressed[pygame.K_w]: move += (0, -1)
if pressed[pygame.K_a]: move += (-1, 0)
if pressed[pygame.K_s]: move += (0, 1)
if pressed[pygame.K_d]: move += (1, 0)
if move.length() > 0: move.normalize_ip()
self.pos += move*(dt/5)
self.rect.center = self.pos
class YAwareGroup(pygame.sprite.Group):
def by_y(self, spr):
return spr.pos.y
def draw(self, surface):
sprites = self.sprites()
surface_blit = surface.blit
for spr in sorted(sprites, key=self.by_y):
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
self.lostsprites = []
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
dt = 0
sprites = YAwareGroup(Player(pygame.image.load('guy.png').convert_alpha(), (100, 200)),
Actor(pygame.image.load('gal.png').convert_alpha(), (200, 200)))
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()
If you need custom drawing logic, it's usually not the worst idea to subclass pygame's Group classes. You can find their source here to see how they work.

Sprite not working/not being displayed (Python/Pygame)

Please help me I am starting a game and my sprite is not showing on screen. Take a look, I am using two files, which include pygame and classes. I hope that's enough information.
Adventure.py --
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
from LittleMan import LittleMan
playerSprite = LittleMan(CL, 200, 300)
size = (1000, 600)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
all_sprites_list = pygame.sprite.Group()
playerSprite.rect.x = 200
playerSprite.rect.y = 300
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.draw(screen)
all_sprites_list.add()
all_sprites_list.update()
pygame.display.flip()
clock.tick(60)
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
LittleMan.moveLeft(5)
if keys[pygame.K_RIGHT]:
LittleMan.moveRight(5)
LitlleMan.py --
import pygame
CL = (156,102,31)
WHITE = (255,255,255)
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
pygame.draw.rect(self.image, CL, [0, 0, width, height])
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
Anyone know why this could be? I've looked everywhere but I've done it in two files and no-one seems to have an answer to that and if there is a decent answer please link it. Thank you.
I think the real crux of the problem is the code is not adding playerSprite to the all_sprites_list. If a sprite is not in this list, the sprite update and paint calls do not include it. At first I thought the initial position of the sprite may be off-screen, so I parameterised the screen dimensions, and positioned the sprite in the middle.
There's a bunch of other indentation issues in the question's code too, but I think these may be from pasting the question into SO.
I cleaned-up and re-organised the code, it seems to run, and pressing Left/right moves the brown box.
I merged both files together to make my debugging easier, my apologies.
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
WHITE = (255,255,255)
WINDOW_WIDTH=500
WINDOW_HEIGHT=500
# Setup the pyGame window
size = (WINDOW_WIDTH, WINDOW_HEIGHT)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
self.rect.center = ( WINDOW_WIDTH//2 , WINDOW_HEIGHT//2 )
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
# Create the player sprite
playerSprite = LittleMan(CL, 200, 300)
# Add user sprite into PyGame sprites list
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(playerSprite);
clock = pygame.time.Clock()
carryOn = True
while carryOn:
# Handle user input
for event in pygame.event.get():
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerSprite.moveLeft(5)
if keys[pygame.K_RIGHT]:
playerSprite.moveRight(5)
# Update and Reapint the screen
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.update()
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
The LittleMan class does not include an update() function, which all_sprites_list.update() would normally call. I expect you just haven't needed this part yet.
EDIT: More notes on the sprite update() function ~
The sprite's update() function is called by pygame during the all_sprites_list.update() function. So that means any sprite added to this group, has its update run quasi-automatically. Ideally all sprites have an update function, and it handles the look, position and collisions (etc.) of the sprite.
The idea behind this function is to do any updates to the sprite. So of you had a sprite that was moving, this function would calculate the next position, and set the sprite's self.rect. Or perhaps that sprite is animated - the update function would set the sprite's image to the next frame of the animation based on the time.
Obviously all this work can be performed outside of the update function. But it provides a simple and clean programming mechanism for sprite mechanics.

Pygame how to change image layer using LayeredUpdates()

I'm having trouble figuring out how to change the layers of images. What I try to achieve is moving a player around the screen and when it comes across an object, box, stone, etc., the player appears on top of the object when its bottom coord is bigger than the object and behind when the coord is smaller than the object. Instead it's always in front or behind the object.
I've searched online and couldn't quite get what I want and I had found a post on Stack Overflow which demonstrated the usage of pygame.sprite.LayeredUpdates(). It explained the idea but I still can't figure out how to change the image layer while running the program. A simple code demonstration would be nice.
You can find a working example of LayeredUpdates here.
Note that the documentation of pygame is wrong: your sprites need a _layer attribute, not layer.
To actually change the layer of a sprite, you have to use the functions of LayeredUpdates, like change_layer or switch_layer.
Give your objects a self._layer attribute and set it to the self.rect.bottom coord. Sprites with a higher layer number appear above the sprites with lower numbers. When the player moves, you can call the change_layer method of the LayeredUpdates group to set the layer of the sprite in the group to the current rect.bottom position. Here's a complete example:
import random
import pygame as pg
PLAYER_IMG = pg.Surface((30, 50))
PLAYER_IMG.fill(pg.Color('dodgerblue1'))
TRIANGLE_IMG = pg.Surface((50, 50), pg.SRCALPHA)
pg.draw.polygon(TRIANGLE_IMG, (240, 120, 0), [(0, 50), (25, 0), (50, 50)])
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = PLAYER_IMG
self.rect = self.image.get_rect(center=pos)
# The sprite will be added to this layer in the LayeredUpdates group.
self._layer = self.rect.bottom
class Triangle(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = TRIANGLE_IMG
self.rect = self.image.get_rect(center=pos)
# The sprite will be added to this layer in the LayeredUpdates group.
self._layer = self.rect.bottom
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.LayeredUpdates()
player = Player((50, 80))
all_sprites.add(player)
for _ in range(20):
all_sprites.add(Triangle((random.randrange(600), random.randrange(440))))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
if keys[pg.K_d]:
player.rect.x += 5
if keys[pg.K_a]:
player.rect.x -= 5
if keys[pg.K_w]:
player.rect.y -= 5
if keys[pg.K_s]:
player.rect.y += 5
# If any of the wasd keys are pressed, change the layer.
if any((keys[pg.K_w], keys[pg.K_a], keys[pg.K_s], keys[pg.K_d])):
# Set the layer of the player sprite to its rect.bottom position.
all_sprites.change_layer(player, player.rect.bottom)
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

About releasing memory when using pygame and Python

For example, a projectile flies off screen, does the program still compute its location, speed, etc.?
If so, how to release it?
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename)
x = 0.
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.blit(background, (0,0))
screen.blit(sprite, (x, 100))
x+= 10.
pygame.display.update()
Yes, the location, speed, etc. still have to be computed, otherwise no object that is off-screen could ever enter the screen area again. Pygame is smart enough not to attempt to render these objects.
It's usually advisable to use pygame sprites and sprite groups which allow you to remove sprites simply by calling self.kill(). You could also use lists or sets to store your objects, but then you have to write a bit more code yourself.
So I'd first define a pygame.Rect (the game_area) with the size of your screen or a bit larger (in the example below I use a smaller one). Rects have a contains method that you can use to check if your sprite's rect is inside the game_area rect. If the sprite is outside, just call self.kill() and pygame will remove the sprite from all associated sprite groups.
import random
import pygame as pg
from pygame.math import Vector2
class Projectile(pg.sprite.Sprite):
def __init__(self, pos, game_area):
super().__init__()
self.image = pg.Surface((5, 5))
self.image.fill(pg.Color('aquamarine2'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(2, 0).rotate(random.randrange(360))
self.pos = Vector2(pos)
self.game_area = game_area
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if not self.game_area.contains(self.rect):
self.kill()
def main():
screen = pg.display.set_mode((640, 480))
game_area = pg.Rect(60, 60, 520, 360)
game_area_color = pg.Color('aquamarine2')
clock = pg.time.Clock()
all_sprites = pg.sprite.Group(Projectile(game_area.center, game_area))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.add(Projectile(game_area.center, game_area))
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.draw.rect(screen, game_area_color, game_area, 2)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Yes it does, but since you have only a single projectile (incremented using x), you can easily choose what to do using a few if statements. The process becomes harder when there are multiple projectiles (which you need to store in a container), you should apply this.
Here is an example
for projectile in projectile_list:
# Check if the position is inside the screen
if 0 < projectile.x < WIDTH and 0 < projectile.y < HEIGHT:
# Do position operations
This way, you only process what is required. You can apply similar method to remove unused projectiles from the list or whatever container you are using.

Categories