How do i stop an image from moving by the edges of the screen in Pygame? - 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

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()

How do i make the player look like in the back of the tree? [duplicate]

This question already has answers here:
Pygame image transparency confusion
(1 answer)
How can I make an Image with a transparent Backround in Pygame?
(2 answers)
Closed last year.
i have this python with pygame please don't mind the comment with ignore. every things work fine but i need to know how to make the player lower than the tree. I already tried to display character first and then the tree but the problem is when i import another TILED TMX it has a blackbackground i don't see the setting of being transparent.
brief information: LoadFileMap is where tmx opened and then it display the map here:
makemaps = LoadFileMap.make_map(scroll[0],scroll[1])
display.blit(makemaps,(0,0))
Example
my Main code:
import pygame, sys, os, random, math
clock = pygame.time.Clock()
from pygame.locals import *
import data.engine as e
import data.Mapload as Map
import data.Makemap as M
#Setting of the Windows
pygame.init()
pygame.display.set_caption("Pineapple")
WINDOWS_SIZE = (800, 608)
screen = pygame.display.set_mode(WINDOWS_SIZE,0,32)
display=pygame.Surface((304,256))
# pygame.mouse.set_visible(False)
#Loading the Animations & Particles
e.load_animations('data/images/Entities/')\
#Create random map
# LoadFileMap = Map.createfileworld("data/Map") Ignore
LoadFileMap = M.TilledMap('data/untitled.tmx')
tile_rects=[]
#Variables for the character
'''Player Status'''
for tile_object in LoadFileMap.tmxdata.objects:
if tile_object.name == 'Player':
player = e.entity(tile_object.x,tile_object.y,14,26,'player')
if tile_object.name == 'Tree':
tile_rects.append(pygame.Rect(tile_object.x,tile_object.y,tile_object.width,tile_object.height))
'''Camera'''
true_scroll = [player.x-304/2,player.y-256/2]
'''Character Movement'''
moving_right = False
moving_left = False
moving_up = False
moving_down =False
while True:
display.fill((146,244,255))
#Scroll Camera movement
true_scroll[0] += ((player.get_center()[0]-304/2)-true_scroll[0])/10
true_scroll[1] += ((player.get_center()[1]-256/2)-true_scroll[1])/10
if true_scroll[0] < 0:
true_scroll[0] = 0
if true_scroll[0] > 496:
true_scroll[0] = 496
if true_scroll[1] > 544:
true_scroll[1] = 544
if true_scroll[1] < 0:
true_scroll[1] = 0
scroll = true_scroll.copy()
scroll[0] = int(scroll[0])
scroll[1] = int(scroll[1])
#Rendering the map and rects
makemaps = LoadFileMap.make_map(scroll[0],scroll[1])
display.blit(makemaps,(0,0))
#Character Movement
player_movement = [0,0]
if moving_up == True:
player_movement[1] -= 2
if moving_down == True:
player_movement[1] += 2
if moving_right == True:
player_movement[0] += 2
if moving_left == True:
player_movement[0] -= 2
collision_types= player.move(player_movement,tile_rects)
player.display(display,scroll)
#Buttons
for event in pygame.event.get():
if event.type==QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_d:
moving_right = True
if event.key == K_a:
moving_left = True
if event.key == K_w:
moving_up = True
if event.key == K_s:
moving_down =True
if event.type == KEYUP:
if event.key == K_d:
moving_right = False
if event.key == K_a:
moving_left = False
if event.key == K_w:
moving_up = False
if event.key == K_s:
moving_down =False
screen.blit(pygame.transform.scale(display,WINDOWS_SIZE),(0,0))
pygame.display.update()
clock.tick(60)
this is the file where it renders the map (Makemaps.py)
import pygame, sys, os
from pygame.locals import *
import pytmx
class TilledMap:
def __init__(self, filename):
tm = pytmx.load_pygame(filename, pixelalpha=True)
self.width= tm.width*tm.tilewidth
self.height= tm.height*tm.tileheight
self.tmxdata = tm
def render(self,Surface,scrollx,scrolly):
ti = self.tmxdata.get_tile_image_by_gid
for layer in self.tmxdata.visible_layers:
test = str(layer)
if isinstance(layer,pytmx.TiledTileLayer):
for x, y, gid, in layer:
tile = ti(gid)
if tile:
Surface.blit(tile,(x*self.tmxdata.tilewidth-scrollx,y*self.tmxdata.tileheight-scrolly))
def make_map(self,scrollx,scrolly):
temp_surface = pygame.Surface((self.width,self.height))
self.render(temp_surface,scrollx,scrolly)
return temp_surface
class obstacle:
def __init__(game,x,y,w,h):
self.group = game.Tree
# pygame.sprite.Sprite.__init__(self.groups)
self.game = game
self.rect = pygame(x,y,w,h)
self.x = x
self.y = y
self.rect.x=x
self.rect.y=y

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

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?

Powerup variables in a platform game

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.