Can't draw a circle moving natually on the screen [duplicate] - pygame

This question already has an answer here:
How can I move the ball instead of leaving a trail all over the screen in pygame?
(1 answer)
Closed 1 year ago.
I would like to draw a white circle moving naturally on the screen, but I get lots of white circles drawn in a discontinuous way without giving the illusion of movement. How can I modify this code?
import pygame
from sys import exit
import math
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
class Game():
def __init__(self):
pygame.init()
self.window = pygame.display.set_mode((500, 500), 0, 32)
pygame.display.set_caption('Jumping ball')
self.clock = pygame.time.Clock()
def mainloop(self):
while True:
EVENTS = pygame.event.get()
for event in EVENTS:
if event.type == pygame.QUIT:
exit()
time = self.clock.tick(60) # [seconds]
x, y = 100 + 50 * math.sin(time), 100 + 50 * math.cos(time)
x, y = int(x), int(y)
radius = 5
pygame.draw.circle(self.window, WHITE, (x, y), radius)
#self.screen.blit(ball, (x, y))
pygame.display.update()
if __name__ == '__main__':
game = Game()
game.mainloop()

You have to first fill the screen in black and then draw on it again.
def mainloop(self):
while True:
EVENTS = pygame.event.get()
for event in EVENTS:
if event.type == pygame.QUIT:
exit()
time = self.clock.tick(60) # [seconds]
x, y = 100 + 50 * math.sin(time), 100 + 50 * math.cos(time)
x, y = int(x), int(y)
radius = 5
self.window.fill((0, 0, 0)) #Black
pygame.draw.circle(self.window, WHITE, (x, y), radius)
#self.screen.blit(ball, (x, y))

You should clear the screen every tick, e.g. by filling it with a solid color.
Also, you set x and y new every tick, which does not allow you to create most movement patterns. I suggest introducing x and y variables that you can modify in small increments each tick, like so:
import pygame
from sys import exit
import math
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
class Game():
def __init__(self):
pygame.init()
self.window = pygame.display.set_mode((500, 500), 0, 32)
pygame.display.set_caption('Jumping ball')
self.clock = pygame.time.Clock()
self.ball_x = 100
self.ball_y = 100
def mainloop(self):
while True:
self.window.fill(BLACK)
EVENTS = pygame.event.get()
for event in EVENTS:
if event.type == pygame.QUIT:
exit()
time = self.clock.tick(60) # [seconds]
self.ball_x -= math.sin(time)
self.ball_y -= math.cos(time)
x, y = int(self.ball_x), int(self.ball_y)
radius = 5
pygame.draw.circle(self.window, WHITE, (x, y), radius)
# self.screen.blit(ball, (x, y))
pygame.display.update()
if __name__ == '__main__':
game = Game()
game.mainloop()

Related

Pygame - How to make mouse gesture movement smoother?

I wanted to be able to "scroll" around a pygame window using just mouse gestures, or in this case, "two fingers scrolling" (don't know the right term for this).
I managed to make an example implementation:
import pygame
pygame.init()
size = (width, height) = (800, 600)
screen = pygame.display.set_mode(size)
class SceneElement:
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
class Scene:
def __init__(self):
self.elements = [
SceneElement(150, 150, 200, 200, (55, 55, 10, 0.3)),
SceneElement(250, 300, 200, 200, (155, 200, 10, 0.5)),
]
def render(self, offset):
screen.fill((255, 255, 255))
for element in self.elements:
x = element.x + offset[0]
y = element.y + offset[1]
pygame.draw.rect(screen, element.color, (x, y, element.width, element.height))
scene = Scene()
offset = [0, 0]
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == 1027:
if event.x == 0 and event.y == -1:
print(event.x, event.y)
offset[1] -= 10
elif event.x == -1 and event.y == 0:
offset[0] += 10
print(event.x, event.y)
elif event.x == 1 and event.y == 0:
offset[0] -= 10
print(event.x, event.y)
elif event.x == 0 and event.y == 1:
offset[1] += 10
print(event.x, event.y)
scene.render(offset)
pygame.display.flip()
pygame.quit()
The above works. Here is a gif showing that it works.
Now problem is, I don't think this is how this is supposed to be implemented. I didn't see any example or existing code online that did this.
So while the above works, it doesn't feel "smooth". Trying to move in a circle or diagonally feels very unnatural (as can be seen in the gif near the end). Is there a better way to do the above (moving around using mouse gestures) or is this the right implementation?
Mainly what you have to deal with here is that your diagonal movement is not normalized, to fix this the easiest would be to just use pygame.Vector2 for positions and such, it also has a normalized method that will do the normalizing for you:
scene = Scene()
offset = pygame.Vector2()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEWHEEL:
direction = pygame.Vector2(event.x, event.y).normalize()
offset += direction * 10
scene.render(offset)
pygame.display.flip()
This won't affect functionality of the rest of the code as vectors can be indexed just like lists, however, I'd suggest you use pygame.Vector2 for positions and velocities and accelerations and other physics related things in general as they are faster and far more convenient.
Also use constants instead of some arbitrary integer values for event types and other stuff as it makes reading the code a lot easier.
Thanks to Matiiss's answer, I managed to find a clue on how to do this:
import pygame
pygame.init()
size = (width, height) = (800, 600)
screen = pygame.display.set_mode(size)
class SceneElement:
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
class Scene:
def __init__(self):
self.elements = [
SceneElement(150, 150, 200, 200, (55, 55, 10, 0.3)),
SceneElement(250, 300, 200, 200, (155, 200, 10, 0.5)),
]
def render(self, offset):
screen.fill((255, 255, 255))
for element in self.elements:
x = element.x + offset[0]
y = element.y + offset[1]
pygame.draw.rect(screen, element.color, (x, y, element.width, element.height))
scene = Scene()
offset = pygame.Vector2()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEWHEEL:
print(event.x, event.y)
if event.x == 0 and event.y == 1:
direction = pygame.Vector2(event.x, event.y).normalize()
offset += direction * 10
elif event.x == 0 and event.y == -1:
direction = pygame.Vector2(event.x, event.y).normalize()
offset += direction * 10
elif event.x == -1 and event.y == 0:
direction = pygame.Vector2(1, event.y).normalize()
offset += direction * 10
elif event.x == 1 and event.y == 0:
direction = pygame.Vector2(-1, event.y).normalize()
offset += direction * 10
scene.render(offset)
pygame.display.flip()
pygame.quit()
This seems to work much more smoothly, but I feel like this could be improved still: https://imgur.com/a/7WNNQIl
EDIT: Based on Matiiss's comment, using their answer and replacing event.x with -event.x seems to work like the above without if/elif:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEWHEEL:
direction = pygame.Vector2(-event.x, event.y).normalize()
offset += direction * 10
scene.render(offset)
pygame.display.flip()

Pygame - Issues creating projectiles, "add() argument after * must be an iterable, not int"

I am creating an Asteroids type game using Pygame and am having trouble firing projectiles. I have a sprite group for the projectile object and some code to add the projectile.
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
Problem is when I press space in game to fire the projectile I get the error "add() argument after * must be an iterable, not int".
I have a very similar statement for adding asteroids when the game first starts without any issues so I'm not really sure what the problem is. I'll leave the rest of the code below. The add statement giving issues is in the main function near the bottom. Any help is appreciated.
#Import Modules
import pygame
import math
import random
#Movement Function
def calculate_new_xy(old_xy,speed,direction):
new_x = old_xy[0] + (speed*math.cos(direction))
new_y = old_xy[1] + (speed*math.sin(direction))
return new_x, new_y
#Collision Function
def isColliding(x, y, xTo, yTo, size):
if x > xTo - size and x < xTo + size and y > yTo - size and y < yTo + size:
return True
return False
#Draw Text Function
def drawText(msg, color, x, y, s, center=True):
screen_text = pygame.font.SysFont("Impact", s).render(msg, True, color)
if center:
rect = screen_text.get_rect()
rect.center = (x, y-50)
else:
rect = (x, y)
display.blit(screen_text, rect)
#Initialize Variables
#Colors
white = (255, 255, 255)
black = (0, 0, 0)
#Display Height/Width
display_width = 800
display_height = 600
#Asteroid Class
class Asteroid(pygame.sprite.Sprite):
#Initialize values
def __init__(self, pos=(0, 0)):
#Initialize sprite class
pygame.sprite.Sprite.__init__(self)
#Asteroid sprite
self.asteroid = pygame.image.load("asteroid.png").convert()
self.image = self.asteroid
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize random starting angle
self.angle = random.randint(0, 360)
#Asteroid random Speed
self.speed = random.randint(2, 3)
#Asteroid random direction
self.direction = math.radians(random.randrange(0, 360, 3))
#Update asteroid object
def update(self):
#Constantly rotate asteroid
self.angle -= 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.asteroid, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Move Asteroid
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the asteroid to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -20
elif self.rect.x < -20:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -20
elif self.rect.y < -20:
self.rect.y = display_height
#Projectile Class
class Projectile(pygame.sprite.Sprite):
#Initialize values
def _init_(self,x,y,direction):
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
#Update projectile object
def update(self):
#Changing direction
self.x += projectilespd * math.cos(self.direction)
self.y += projectilespd * math.sin(self.direction)
#Draw projectile
pygame.draw.circle(display, white, (self.x,self.y),1)
#Screen Border
if self.x > display_width:
self.x = 0
elif self.x < 0:
self.x = display_width
elif self.y > display_height:
self.y = 0
elif self.y < 0:
self.y = display_height
self.ttl -= 1
#Player Class
class Player(pygame.sprite.Sprite):
#Initialize ship sprite, angle lines, and rectangle
def __init__(self, pos=(0, 0), size=(200, 200)):
#Player sprite
self.ship = pygame.image.load("ship.png").convert()
self.image = self.ship
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize angle
self.angle = 0
#Initialize direction
self.direction = 0
#Update player object
def update(self):
#Rotation
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]: self.angle -= 3 % 360
if pressed[pygame.K_RIGHT]: self.angle += 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.ship, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Convert angle to radians
self.direction = math.radians(self.angle-90)
#Increase speed if Up is pressed
if pressed[pygame.K_UP]: self.speed = 5
else: self.speed = 0
#Move Ship
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the player to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -50
elif self.rect.x < -50:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -50
elif self.rect.y < -50:
self.rect.y = display_height
#Main Function
def main(gameState):
#Player starting position
player = Player(pos=(400, 300))
#Asteroid group
asteroids = pygame.sprite.Group()
#Projectile group
projectiles = pygame.sprite.Group()
#Create asteroids
for x in range(8):
asteroids.add(Asteroid(pos=(100 + (x*120), 100 + (x*20))))
while True:
for event in pygame.event.get():
#closes game
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
#Game Menu
while gameState == "Menu":
#Fill background
display.fill((0,0,0))
#Display menu text
drawText("ASTEROIDS", white, display_width / 2, display_height / 2, 150)
drawText("Press any key to START", white, display_width / 2, display_height / 2 + 120, 40)
#Check game start or end
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
gameState = "Playing"
pygame.display.update()
#Low frame rate for menu
clock.tick(5)
#Get key inputs
pressed = pygame.key.get_pressed()
#Fill background
display.fill(black)
#Check for player collision with asteroid
for asteroid in asteroids:
if gameState != "Game Over":
if isColliding(player.rect.x, player.rect.y, asteroid.rect.x, asteroid.rect.y, 30):
gameState = "Game Over"
#Update and draw player if not game over
if gameState != "Game Over":
#Update player
player.update()
#Draw player
display.blit(player.image, player.rect)
#Update asteroids
asteroids.update()
#Draw asteroids
asteroids.draw(display)
#Fire Projectiles
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
#Update projectiles
projectiles.update()
#Draw projectiles
projectiles.draw(display)
#Display Game Over and restart option
if gameState == "Game Over":
drawText("GAME OVER", white, display_width / 2, display_height / 2, 150)
drawText("Press R to restart", white, display_width / 2, display_height / 2 + 120, 40)
if pressed[pygame.K_r]:
main(gameState = "Playing")
#Makes updates to the game screen
pygame.display.update()
#Frame rate
clock.tick(60)
#Initialize Game
if __name__ == '__main__':
#initialize pygame
pygame.init()
#initialize display settings
display = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Asteroids')
#initialize game clock
clock = pygame.time.Clock()
#start main function
main(gameState = "Menu")
The issue is not the pygame.sprite.Group.add operation, but the obejct you want to add is not a pygame.sprite.Sprite object, because the object is not constructed at all.
You missed to the super call in the constructor of Projectile. Furthermore the name of the constructor has to be __init__ rather _init_:
class Projectile(pygame.sprite.Sprite):
#Initialize values
def __init__(self,x,y,direction):
super.__init__()
self.x = x
self.y = y
self.dir = direction
self.ttl = 30

Pygame, why does my rectangle not move

I don't get why the rectangle is not changing it's y position when i press the up key. I don't get any errors and everything is showing up.
import pygame
from pygame.locals import *
class SnekHead(object):
def __init__(self, screensize):
self.screensize = screensize
self.center_x = int(screensize[0]*0.5)
self.center_y = int(screensize[1]*0.5)
self.width = 50
self.height = 50
self.rect = pygame.Rect(self.center_x-25, self.center_y-50, self.width, self.height)
self.color = (100, 255, 100)
self.speed = 10
self.direction = 0
def update(self):
self.center_y += self.direction*self.speed
def render(self, screen):
pygame.draw.rect(screen, self.color, self.rect, 0)
def run_game():
pygame.init()
screensize = (640, 480)
background_image = pygame.image.load('Sky_back_layer.png')
screen = pygame.display.set_mode(screensize)
clock = pygame.time.Clock()
snake = SnekHead(screensize)
running = True
while running:
clock.tick(64)
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.key == K_UP:
snake.direction = -1
snake.update()
screen.blit(background_image, (0, 0))
snake.render(screen)
pygame.display.flip()
pygame.quit()
run_game()
Ask yourself this question. What is the value of self.rect after the key is pressed? (You decrement snake.direction when the key is pressed, then you update snake.center_y, but snake.rect remains the same and so does the position of the rectangle because that is what you are passing to pygame.draw.rect() in your render() function)

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?

Pygame lab: creating Rectangle class with "draw" & "move" method

I'm working on a lab 8 on programarcadegames: http://programarcadegames.com/index.php?chapter=lab_classes_and_graphics - and got stuck with move method in a Rectangle class. Will appreciate any hints.
import pygame
black = ( 0, 0, 0)
white = ( 255, 255, 255)
green = ( 0, 255, 0)
red = ( 255, 0, 0)
class Rectangle():
def draw(self, x, y, height, width, screen):
self.x = x
self.y = y
self.height = height
self.width = width
self.screen = screen
pygame.draw.rect(self.screen, white, [self.x, self.y, self.height, self.width])
print "meth draw: x,y", self.x, self.y
def move(self, change_x, change_y):
self.change_x = change_x
self.change_y = change_y
self.x += self.change_x
self.y += self.change_y
if self.x > 300 or self.x < 0:
self.change_x = -self.change_x
if self.y > 300 or self.y < 0:
self.change_y = -self.change_y
print "x,y,s_x,s_y", self.x, self.y, self.change_x, self.change_y # debug
pygame.init()
size = [300,300]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
done = False
clock = pygame.time.Clock()
myObject = Rectangle()
# -------- Main Program Loop -----------
while done == False:
# ALL EVENT PROCESSING SHOULD GO BELOW THIS COMMENT
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done = True # Flag that we are done so we exit this loop
screen.fill(black)
myObject.draw(100, 100, 50, 50, screen)
myObject.move(10, 10)
pygame.display.flip()
clock.tick(20)
pygame.quit()
You have to change draw() because in every loop you draw retangle in the same place so you can't see result of your move.
class Rectangle:
def __init__(self, x, y, width, height, screen):
self.x = x
self.y = y
self.width = width
self.height = height
self.screen = screen
def draw(self):
pygame.draw.rect(self.screen, white, [self.x, self.y, self.height, self.width])
print "meth draw: x,y", self.x, self.y
def move(self, change_x, change_y):
# rest of your code
# rest of your code
myObject = Rectangle(100, 100, 50, 50, screen)
# rest of your code
myObject.move(10, 10) # move to new place
myObject.draw() # draw in new place
Very good hints furas - especially on order of drawing on screen. I did play a little bit more, set few debugs (prints) and eventually got it sorted. The issue was with "move" method - as each time the screen was drawn - I was resetting "change_x" and "change_y" to values given in method call. Of course that was wrong. What I did is I created extra method "speed" and I'm calling it BEFORE main program loop.
The correct version is listed below. For the sake of clarity I changed "change_x" and "change_y" to "speed_x" and "speed_y".
We may close this topic
import pygame
# Define some colors
black = ( 0, 0, 0)
white = ( 255, 255, 255)
green = ( 0, 255, 0)
red = ( 255, 0, 0)
class Rectangle():
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.height = height
self.width = width
def speed(self,*speed):
self.speed_x = speed[0]
self.speed_y = speed[1]
def draw(self, screen):
self.screen = screen
pygame.draw.rect(self.screen, white, [self.x, self.y, self.height, self.width])
print "meth draw: x,y", self.x, self.y
def move(self):
print "self.x", self.x
print "self.speed_x:", self.speed_x
self.x += self.speed_x
self.y += self.speed_y
if self.x > 250 or self.x < 0:
self.speed_x = -self.speed_x
if self.y > 250 or self.y < 0:
self.speed_y = -self.speed_y
pygame.init()
# Set the width and height of the screen [width,height]
size = [300,300]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
done = False
clock = pygame.time.Clock()
myObject = Rectangle(100, 100, 50, 50,)
myObject.speed(2,4)
# -------- Main Program Loop -----------
while done == False:
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done = True # Flag that we are done so we exit this loop
screen.fill(black)
myObject.move()
myObject.draw(screen)
pygame.display.flip()
clock.tick(20)
pygame.quit()