Powerup variables in a platform game - pygame

I've made a platform game that has powerups in it. One of the powerups is a lightning bolt that is meant to increase your speed. However, when I tell it to increase the speed it increases it but makes my player move without use of pressing down my arrow keys. Any help? And I know you can't see it but I have included player.update, etc. in main, but I didn't show it, so the code would be shortened.
Thanks
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width = 65, height = 35):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.hspeed,self.vspeed = 0,0
self.speed = 2
self.Jump = 10
self.images=[]
r0 = pygame.image.load("Images\Player\i1.png")
r1 = pygame.image.load("Images\Player\i2.png")
r2 = pygame.image.load("Images\Player\i3.png")
r3 = pygame.image.load("Images\Player\i4.png")
self.hurt = pygame.image.load("Images\Player\Hurt.png")
self.images.append(r0)
self.images.append(r1)
self.images.append(r2)
self.images.append(r3)
self.rotatedimages = []
rr0 = pygame.transform.flip(r0 ,True, False)
rr1 = pygame.transform.flip(r1 ,True, False)
rr2 = pygame.transform.flip(r2 ,True, False)
rr3 = pygame.transform.flip(r3 ,True, False)
self.rotatedimages.append(rr0)
self.rotatedimages.append(rr1)
self.rotatedimages.append(rr2)
self.rotatedimages.append(rr3)
self.deadimages = [self.hurt]
self.gravity = 0.35
self.index = 0
self.image = self.images[self.index]
self.rect = pygame.Rect(self.x,self.y,width,height)
self.TimeNum=0
self.TimeTarget=10
self.Timer = 0
def update(self, event = None):
self.calcgravity()
self.rect.x += self.hspeed
self.rect.y += self.vspeed
#Walking animation of animation when left or right key is pressed.
key = pygame.key.get_pressed()
if self.Timer >= 0:
if key[pygame.K_RIGHT]:
self.TimeNum+=1
if self.TimeNum == self.TimeTarget:
self.index +=1
if self.index >= len(self.images):
self.index = 0
self.image = self.images[self.index]
self.TimeNum = 0
if key[pygame.K_LEFT]:
self.TimeNum+=1
if self.TimeNum == self.TimeTarget:
self.index +=1
if self.index >= len(self.rotatedimages):
self.index = 0
self.image = self.rotatedimages[self.index]
self.TimeNum = 0
# if you go outside the boundaries of the game.
if player.rect.x < 0:
GameOver()
def move(self, hspeed, vspeed):
self.hspeed += hspeed
self.vspeed += vspeed
def Level1Coll(self,PowerUps):
PowerUpsCollision = pygame.sprite.spritecollide(self,PowerUps,True )
for speedboost in PowerUpsCollision:
self.speed = 3
def main()
GameExit = False
while GameExit==False:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.move(-player.speed,0)
if event.key == pygame.K_RIGHT:
player.move(player.speed,0)
if event.key == pygame.K_UP:
player.move(0,-player.Jump)
JumpSound.play()
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.move(player.speed,0)
if event.key == pygame.K_RIGHT:
player.move(-player.speed,0)
if event.key == pygame.K_UP:
player.move(0,0)

You set self.speed to be equal to 3, which usually means that the sprite will move on its own with a speed of 3. Instead create a new variable:
extra_speed = 0
and apply that to the increase of speed of the sprite:
self.rect.y += (self.vspeed + extra_speed)
Now when the sprite doesn't have the power-up, nothing changes as the speed increases by nothing. But when the sprite does get the powerup, simply change extra_speed to 3 and reset back to 0 when the sprite is finished with the power-up. To meet the problem of instant running, use another variable to determine when to run and use it before you check to run.
running = False
#A few lines later into your update function
if running:
self.rect.x += self.hspeed
self.rect.y += (self.vspeed + extra_speed)
The above code means that if running is True, then move the sprite appropriately. Otherwise, don't move the sprite.

Related

my pygame bullet sprites only fire once each time through loop. would like to fire multiple times

i'm very new to coding, and i'm trying to get a ship to fire multiple bullets, but every time i push spacebar the bullet sort of re-triggers and doesn't make it to the end of the screen. it seems maybe only one instance of my Bullet class is called each time but i don't know how to fix it. here is the code i have going so far:
import sys
import pygame
from pygame.sprite import Sprite
class Sideship():
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((1200,800))
self.screen_rect = self.screen.get_rect()
pygame.display.set_caption("Side Ship")
self.bg_color = (50, 50, 255)
self.bullets = pygame.sprite.Group()
self.jet = Jet()
def run_game(self):
self.jet.rect.x = -20
self.jet.rect.y = 290
self.bullet = Bullet()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
self.jet.moving_down = True
if event.key == pygame.K_UP:
self.jet.moving_up = True
if event.key == pygame.K_SPACE:
self.new_bullet = Bullet()
self.bullets.add(self.new_bullet)
self.new_bullet.rect.x = self.jet.rect.x+200
self.new_bullet.rect.y = self.jet.rect.y+30
if event.type == pygame.KEYUP:
if event.key == pygame.K_DOWN:
self.jet.moving_down = False
if event.key == pygame.K_UP:
self.jet.moving_up = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
sys.exit()
self.jet.update()
self.bullets.update()
self.screen.fill(self.bg_color)
self.bullets.update()
for bullet in self.bullets.sprites():
pygame.draw.rect(self.screen, self.new_bullet.bullet_color, self.new_bullet.rect)
self.screen.blit(self.jet.image, self.jet.rect)
pygame.display.flip()
class Jet():
def __init__(self):
self.image = pygame.image.load('jet.bmp')
self.rect = self.image.get_rect()
self.moving_down = False
self.moving_up = False
self.rect.y = float(self.rect.y)
self.rect.x = float(self.rect.x)
def update(self):
if self.moving_down and self.rect.bottom < 801:
self.rect.y += 1.9
if self.moving_up and self.rect.top > -14:
self.rect.y -= 1.4
class Bullet(Sprite):
def __init__(self,):
super().__init__()
self.bullet_width = 30
self.bullet_height = 5
self.bullet_color = (250,250,250)
self.rect = pygame.Rect(0,0, self.bullet_width, self.bullet_height)
def update(self):
self.rect.x += 4
side_ship = Sideship()
side_ship.run_game()
The issue is in the loop, which draws the bullets. When you iterate through the bullets, the current element is referenced by bullet. Hence, you have to draw bullet, rather than self.new_bullet:
pygame.draw.rect(self.screen, self.new_bullet.bullet_color, self.new_bullet.rect)
for bullet in self.bullets.sprites():
pygame.draw.rect(self.screen, bullet.bullet_color, bullet.rect)

How do i stop an image from moving by the edges of the screen in Pygame?

I just started with pygame and want to make a minigame with a weapon (pixelart). I made it move by arrow keys on the keyboard, but the image moves by the edges, how do I block it from going further than the edges? The dimensions of my "character" is 220x116 pixels
#!/usr/bin/env python
import pygame
from pygame.locals import *
from sys import exit
background_image = 'mario bg.png'
weapon = 'scar.png'
pygame.init()
SCREEN_SIZE = (800, 800)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
pygame.display.set_caption("SCAR MINIGAME")
background = pygame.image.load(background_image).convert()
player = pygame.image.load(weapon).convert_alpha()
x, y = 150, 353
MOVE_RIGHT = x=+10
MOVE_LEFT = x=-10
MOVE_UP = y=-10
MOVE_DOWN = y=+10
direction = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_LEFT:
MOVE_LEFT = False
elif event.key == K_RIGHT:
MOVE_RIGHT = False
elif event.key == K_DOWN:
MOVE_DOWN = False
elif event.key == K_UP:
MOVE_UP = False
elif event.type == KEYUP:
if event.key == K_LEFT:
MOVE_LEFT = True
elif event.key == K_RIGHT:
MOVE_RIGHT = True
elif event.key == K_UP:
MOVE_UP = True
elif event.key == K_DOWN:
MOVE_DOWN = True
if(direction == MOVE_LEFT):
x-=0.4
elif(direction == MOVE_RIGHT):
x+=0.4
elif(direction == MOVE_UP):
y-=0.4
elif(direction == MOVE_DOWN):
y+=0.4
screen.blit(background, (0, 0))
screen.blit(player, (x, y))
pygame.display.update()
You simply need to check the co-ordinates of the player, and restrict them to on-screen, or make them wrap around.
I find it's handy to keep the window dimensions in global variables, and use these in setup functions, and later calculations:
WINDOW_WIDTH=800
WINDOW_HEIGHT=800
screen = pygame.display.set_mode( (WINDOW_WIDTH,WINDOW_HEIGHT) , 0, 32)
So when you update the position of player:
# Check player is on-screen
if (player.x < 0):
player.x = 0
elif (player.x > WINDOW_WIDTH):
player.x = WINDOW_WIDTH
if (player.y < 0):
player.y = 0
elif (player.y > WINDOW_HEIGHT):
player.y = WINDOW_HEIGHT
screen.blit(background, (0, 0))
screen.blit(player, (x, y))
It would be a more complete answer to include the width of the sprite for the > WINDOW_WIDTH-sprite_width check so it still shows on-screen, but that exercise is left for the reader.
So back to the code. The new function keepPointOnScreen() takes a co-ordinate and checks that it's on the screen, modifying it if not. Calling this function each time the player position is updated keeps the sprite on-screen. Since the sprite is painted from the left-side, it's still possible to "hide" the sprite on the right & bottom. To fix this, the keeper function needs to take into account the bitmap width.
#!/usr/bin/env python
import pygame
from pygame.locals import *
from sys import exit
background_image = 'mario bg.png'
weapon = 'scar.png'
# Keep these as variables for checking later on the code
WINDOW_WIDTH=800
WINDOW_HEIGHT=800
# Setup the window
pygame.init()
SCREEN_SIZE = (WINDOW_WIDTH, WINDOW_HEIGHT)
screen = pygame.display.set_mode(SCREEN_SIZE, 0, 32)
pygame.display.set_caption("SCAR MINIGAME")
# load images & set starting positions
background = pygame.image.load(background_image).convert()
player = pygame.image.load(weapon).convert_alpha()
player_width = player.get_width()
player_height = player.get_height()
print("Player image is [%u x %u]" % (player_width, player_height))
x, y = 150, 353
direction = 0
clock = pygame.time.Clock()
###
### Given a co-ordinate, ensure the co-ordinate is inside the screen
### bounds. If it's outside the screen area, reduce it to inside,
### or wrap it to the other side
###
### The code uses the WINDOW_WIDTH and WINDOW_HEIGHT globals,
### Assuming the window is 0 to WIDTH-1 in pixels, and similiarly for height
####
def keepPointOnScreen(x,y, bm_width, bm_height, wrap=False):
global WINDOW_WIDTH, WINDOW_HEIGHT
if ( wrap == False ):
# Block on the edges of the screen (no wrapping)
if ( x < 0 ):
x = 0
elif ( x >= WINDOW_WIDTH-bm_width ):
x = WINDOW_WIDTH-bm_width-1
if ( y < 0 ):
y = 0
elif ( y >= WINDOW_HEIGHT-bm_height ):
y = WINDOW_HEIGHT-bm_height-1
else:
# Wrap-around from side to side and top to bottom
if ( x < 0 ):
x = WINDOW_WIDTH-bm_width-1
elif ( x >= WINDOW_WIDTH-bm_width ):
x = 0
if ( y < 0 ):
y = WINDOW_HEIGHT-bm_height-1
elif ( y >= WINDOW_HEIGHT-bm_width ):
y = 0
return x, y
while True:
for event in pygame.event.get():
if ( event.type == QUIT ):
exit()
# Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_LEFT] ):
x -= 1
elif ( keys[pygame.K_RIGHT] ):
x += 1
elif ( keys[pygame.K_UP] ):
y -= 1
elif ( keys[pygame.K_DOWN] ):
y += 1
# make sure the point is still on-screen
x,y = keepPointOnScreen( x, y, player_width, player_height )
# re-paint the window
screen.blit(background, (0, 0))
screen.blit(player, (x, y))
pygame.display.update()
clock.tick(60) # no more than 60 FPS

Automatically quits

I'm trying to write a snake game in python but my game automatically quites after inputing out of boundaries code.
I don't understand how the coordinates of my rect are >= than the width of my game, because in the screen they are not. I think that's what happens because the game automatically quits.
I'll post my code, below, to be even more clearer
import pygame
from random import randint
pygame.init()
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
class Game():
width, height = 800, 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Second snake game.")
def __init__(self):
pass
game = Game()
class Player(pygame.sprite.Sprite):
lead_x = game.width/2
lead_y = game.height/2
lead_x_change = 0
lead_y_change = 0
velocity = 0.2
block_size = 10
def __init__(self):
pygame.sprite.Sprite.__init__(self)
def draw_character(self):
self.cube = pygame.draw.rect(game.screen, black, [self.lead_x, self.lead_y, self.block_size, self.block_size])
def change_x_right(self):
self.lead_x_change += -self.velocity
def change_x_left(self):
self.lead_x_change += self.velocity
def change_y_up(self):
self.lead_y_change += -self.velocity
def change_y_down(self):
self.lead_y_change += self.velocity
def move_x(self):
self.lead_x += self.lead_x_change
def move_y(self):
self.lead_y += self.lead_y_change
def stop_moving_x(self):
self.lead_x_change = 0
def stop_moving_y(self):
self.lead_y_change = 0
class Apple():
lead_x = randint(100, 700)
lead_y = randint(100, 500)
block_size = 10
def __init__(self):
pass
def spawn_apple_after_eaten(self):
self.lead_x = randint(100, 700)
self.lead_y = randint(100, 500)
self.apple = pygame.draw.rect(game.screen, red, [self.lead_x, self.lead_y, self.block_size, self.block_size])
def spawn_apple(self):
self.apple = pygame.draw.rect(game.screen, red, [self.lead_x, self.lead_y, self.block_size, self.block_size])
apple = Apple()
player = Player()
gameExit = False
while not gameExit:
game.screen.fill(white)
apple.spawn_apple()
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_d:
player.change_x_left()
player.stop_moving_y()
if event.key == pygame.K_a:
player.change_x_right()
player.stop_moving_y()
if event.key == pygame.K_w:
player.change_y_up()
player.stop_moving_x()
if event.key == pygame.K_s:
player.change_y_down()
player.stop_moving_x()
if event.type == pygame.KEYUP:
if event.key == pygame.K_w or event.key == pygame.K_s:
player.stop_moving_y()
if event.key == pygame.K_a or event.key == pygame.K_d:
player.stop_moving_x()
player.move_x()
player.move_y()
player.draw_character()
if player.cube.colliderect(apple.apple):
apple.spawn_apple_after_eaten()
if player.lead_x + player.block_size >= game.width or player.lead_x <= game.width:
gameExit = True
pygame.display.update()
pygame.quit()
quit()
You're setting gameExit to True if the x-coordinate is greater than or equal to game.width or if it's less than or equal to game.width, so it's always True. The second part of the conditional statement should be or player.lead_x <= 0.
if player.lead_x + player.block_size >= game.width or player.lead_x <= 0:
gameExit = True

Python sprite player control issue

I have a game that is supposed to allow two players to play. However, the second player does not move when the user pushes keys down.
(Plane2 is the second player)
class Plane(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("plane.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
if not pygame.mixer:
print "problem with sound"
else:
pygame.mixer.init()
self.sndDing = pygame.mixer.Sound("ding.ogg")
self.sndCrash = pygame.mixer.Sound("planeCrash.ogg")
def update(self):
mousex, mousey = pygame.mouse.get_pos()
self.rect.center = (mousex, mousey)
class Plane2(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("plane2.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.rect.centerx = 320
self.rect.centery = 240
def update(self):
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.rect.centerx -= 8
if event.key == pygame.K_RIGHT:
self.rect.centerx += 8
if event.key == pygame.K_UP:
self.rect.centery += 8
if event.key == pygame.K_DOWN:
self.rect.centery -= 8
I tried moving the update function for Plane2 into the game loop, but that did not fix the problem.
def gameEndless():
pygame.display.set_caption("Mail Pilot!")
background = pygame.Surface(screen.get_size())
background.fill((0, 0, 0))
screen.blit(background, (0, 0))
plane = Plane()
island = Island()
ocean = Ocean()
scoreboard = Scoreboard()
oceanSprites = pygame.sprite.Group(ocean)
islandSprites = pygame.sprite.Group(island)
planeSprite = pygame.sprite.Group(plane)
scoreSprite = pygame.sprite.Group(scoreboard)
bulletSprites = pygame.sprite.Group()
enemySprites = pygame.sprite.Group()
bossSprite = pygame.sprite.Group()
bossWeaponSprites = pygame.sprite.Group()
if gameType == "coop":
scoreboard.lives = 10
player2 = Plane2()
planeSprite.add(player2)
I also tried making the second player its own sprite group, but that didn't help either.
class Plane2(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("plane2.gif")
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.rect.centerx = 320
self.rect.centery = 240
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
self.rect.centery -= 8
if keys[pygame.K_DOWN]:
self.rect.centery += 8
if keys[pygame.K_LEFT]:
self.rect.centerx -= 8
if keys[pygame.K_RIGHT]:
self.rect.centerx += 8
fixed code
Create update() without for loop but with argument event
def update(self, event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.rect.centerx -= 8
elif event.key == pygame.K_RIGHT:
self.rect.centerx += 8
elif event.key == pygame.K_UP:
self.rect.centery += 8
if event.key == pygame.K_DOWN
and in main loop use update() inside for loop
for event in pygame.event.get():
player1.update(event)
player2.update(event)
This way you can handle different events in update() - not only key events but also mouse events or user-defined events.
Some people use name handle_event for this function. And they use update() to change this elements which doesn't need events - ie. set next frame of animation.
# --- events ---
for event in pygame.event.get():
player1.handle_event(event)
player2.handle_event(event)
# --- updates ---
player1.update()
player2.update()

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?