Puzzled by my sprite's unequal +/ - velocity - pygame

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?

Related

Pygame joystick doesn't show multiple joysticks [duplicate]

import pygame
import os
import random
import time
import json
from pygame import joystick
pygame.font.init()
pygame.init()
WIDTH, HEIGHT = 1920, 1000
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("space invaders")
WHITE = (255, 255, 255)
#load images
RED_SPACE_SHIP = pygame.transform.scale(pygame.image.load(os.path.join("Assets", "pixel_ship_red_small.png")), (125, 100))
GREEN_SPACE_SHIP = pygame.transform.scale(pygame.image.load(os.path.join("Assets", "pixel_ship_green_small.png")), (125, 100))
BLUE_SPACE_SHIP = pygame.transform.scale(pygame.image.load(os.path.join("Assets", "pixel_ship_blue_small.png")), (125, 100))
#player
YELLOW_SPACE_SHIP = pygame.transform.rotate(pygame.transform.scale(pygame.image.load(os.path.join("Assets", "spaceship_yellow.png")), (154, 121)), 180)
#lasers
RED_LASER = pygame.image.load(os.path.join("Assets", "pixel_laser_red.png"))
BLUE_LASER = pygame.image.load(os.path.join("Assets", "pixel_laser_blue.png"))
GREEN_LASER = pygame.image.load(os.path.join("Assets", "pixel_laser_green.png"))
#player laser
YELLOW_LASER = pygame.image.load(os.path.join("Assets", "pixel_laser_yellow.png"))
#background
BG = pygame.transform.scale(pygame.image.load(
os.path.join('Assets', 'space.png')), (WIDTH, HEIGHT))
class Laser():
def __init__(self, x, y, img):
self.x = x
self.y = y
self.img = img
self.mask = pygame.mask.from_surface(self.img)
def draw(self, window):
window.blit(self.img, (self.x, self.y))
def move(self, vel):
self.y += vel
def off_screen(self, height):
return not(self.y <= height and self.y >= 0)
def collision(self, obj):
return collide(self, obj)
class Ship:
COOLDOWN = 30
def __init__(self, x, y, health = 100):
self.x = x
self.y = y
self.health = health
self.ship_img = None
self.laser_img = None
self.lasers = []
self.cool_down_counter = 0
def draw(self, window):
window.blit(self.ship_img, (self.x, self.y))
for laser in self.lasers:
laser.draw(window)
def move_lasers(self, vel, obj):
self.cooldown()
for laser in self.lasers:
laser.move(vel)
if laser.off_screen(HEIGHT):
self.lasers.remove(laser)
elif laser.collision(obj):
obj.health -= 10
self.lasers.remove(laser)
def cooldown(self):
if self.cool_down_counter >= self.COOLDOWN:
self.cool_down_counter = 0
elif self.cool_down_counter > 0:
self.cool_down_counter += 1
def shoot(self):
if self.cool_down_counter == 0:
laser = Laser(self.x, self.y, self.laser_img)
self.lasers.append(laser)
self.cool_down_counter = 1
def get_width(self):
return self.ship_img.get_width()
def get_height(self):
return self.ship_img.get_height()
class Player(Ship):
def __init__(self, x, y, health = 100):
super().__init__(x, y, health)
self.ship_img = YELLOW_SPACE_SHIP
self.laser_img = YELLOW_LASER
self.mask = pygame.mask.from_surface(self.ship_img)
self.max_health = health
def shoot(self):
if self.cool_down_counter == 0:
laser = Laser(self.x + 26, self.y - 57, self.laser_img)
self.lasers.append(laser)
self.cool_down_counter = 1
def move_lasers(self, vel, objs):
self.cooldown()
for laser in self.lasers:
laser.move(vel)
if laser.off_screen(HEIGHT):
self.lasers.remove(laser)
else:
for obj in objs:
if laser.collision(obj):
objs.remove(obj)
if laser in self.lasers:
self.lasers.remove(laser)
def draw(self, window):
super().draw(window)
self.healthbar(window)
def healthbar(self, window):
pygame.draw.rect(window, (255, 0, 0), (self.x, self.y + self.ship_img.get_height() + 10, self.ship_img.get_width(), 10))
pygame.draw.rect(window, (0, 255, 0), (self.x, self.y + self.ship_img.get_height() + 10, self.ship_img.get_width()* (self.health/self.max_health),10))
class Enemy(Ship):
COLOR_MAP = {
"red": (RED_SPACE_SHIP, RED_LASER),
"green": (GREEN_SPACE_SHIP, GREEN_LASER),
"blue": (BLUE_SPACE_SHIP, BLUE_LASER)
}
def __init__(self, x, y, color, health = 100):
super().__init__(x, y, health)
self.ship_img, self.laser_img = self.COLOR_MAP[color]
self.mask = pygame.mask.from_surface(self.ship_img)
def move(self, vel):
self.y += vel
def shoot(self):
if self.cool_down_counter == 0:
laser = Laser(self.x - 3, self.y + 20, self.laser_img)
self.lasers.append(laser)
self.cool_down_counter = 1
def collide(obj1, obj2):
offset_x = obj2.x - obj1.x
offset_y = obj2.y - obj1.y
return obj1.mask.overlap(obj2.mask, (offset_x, offset_y)) != None
def main():
run = True
FPS = 60
level = 0
lives = 5
lost = False
lost_count = 0
main_font = pygame.font.SysFont("comicsans", 75)
lost_font = pygame.font.SysFont("comicsans", 500)
enemies = []
wave_length = 5
player_vel = 7
laser_vel = 7
enemy_vel = 1
player = Player(WIDTH // 2, 650)
clock = pygame.time.Clock()
def redraw_window():
WIN.blit(BG, (0,0))
#draw text
level_label = main_font.render(f"Level: {level}", 1, WHITE)
lives_label = main_font.render(f"Lives: {lives}", 1, WHITE)
WIN.blit(lives_label, (10, 10))
WIN.blit(level_label, (WIDTH - level_label.get_width() - 10, 10))
player.draw(WIN)
for enemy in enemies:
enemy.draw(WIN)
if lost:
lost_label = lost_font.render("You Lost!", 1, (WHITE))
WIN.blit(lost_label, (WIDTH/2 - lost_label.get_width()/2, HEIGHT / 2 - lost_label.get_height() / 2))
pygame.display.update()
joysticks = []
for i in range(pygame.joystick.get_count()):
joysticks.append(pygame.joystick.Joystick(i))
for joystick in joysticks:
pygame.joystick.init()
print(pygame.joystick.get_init())
with open(os.path.join("ps4_keys.json"), 'r+') as file:
button_keys = json.load(file)
# 0: Left analog horizonal, 1: left analog verticle, 2: right analog horizonal
# 3: right analog verticle, 4: left Triger, 5: Right Trigger
analog_keys = {0:0, 1:0, 2:0, 3:0, 4:-1, 5:-1}
while run:
clock.tick(FPS)
redraw_window()
if lives <= 0 or player.health <= 0:
lost = True
lost_count += 1
if lost:
if lost_count > FPS * 3:
run = False
else:
continue
if len(enemies) == 0:
level += 1
wave_length += 5
for i in range(wave_length):
enemy = Enemy(random.randrange(100, WIDTH-100), random.randrange(-1500, -100), random.choice(["red", "blue", "green"]))
enemies.append(enemy)
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.JOYAXISMOTION:
analog_keys[event.axis] = event.value
print(analog_keys)
if abs(analog_keys[0]) > .4:
if analog_keys[0] < -.7:
player.x -= 7
else:
continue
if analog_keys[0] < .7:
player.x += 7
for enemy in enemies[:]:
enemy.move(enemy_vel)
enemy.move_lasers(laser_vel, player)
if random.randrange(0, 120) == 1:
enemy.shoot()
if collide(enemy, player):
player.health -= 10
enemies.remove(enemy)
elif enemy.y + enemy.get_height() > HEIGHT:
lives -= 1
enemies.remove(enemy)
player.move_lasers(-laser_vel, enemies)
def main_menu():
title_font = pygame.font.SysFont("comicsans", 150)
run = True
while run:
WIN.blit(BG, (0,0))
title_label = title_font.render("Click the mouse to begin...", 1, WHITE)
WIN.blit(title_label, (WIDTH / 2 - title_label.get_width() / 2, HEIGHT / 2 - title_label.get_height() / 2))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
main()
pygame.quit()
main_menu()
^^^
so this is my code (here is the JSON file as well):
{
"x": 0,
"circle": 1,
"square": 2,
"triangle": 3,
"share": 4,
"PS": 5,
"options": 6,
"left_stick_click": 7,
"right_stick_click": 8,
"L1": 9,
"R1": 10,
"up_arrow": 11,
"down_arrow": 12,
"left_arrow": 13,
"right_arrow": 14,
"touchpad": 15
}
I'm trying to make the player be controlled by the controller left joystick and it returns no errors but my player does not move and it is printing true from the print(pygame.joystick.get_init()) and printing the joystick amounts from: print(analog_keys) but the player does not move. Any idea why?
Do not use the JOYAXISMOTION event. The event does not occur continuously, it only occurs once when the axis changes.
Use pygame.joystick.Joystick.get_axis to get the current position of the axis and move the player depending on the axis:
def main():
# [...]
while run:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if joysticks:
joystick = joysticks[0]
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
player.x += round(7 * axis_x)
if abs(axis_y) > 0.1:
player.y += round(7 * axis_y)
# [...]
Minimal example:
import pygame
from pygame.locals import *
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
x, y = window.get_rect().center
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
x = (x + round(7 * axis_x)) % window.get_width()
if abs(axis_y) > 0.1:
y = (y + round(7 * axis_y)) % window.get_height()
window.fill(0)
pygame.draw.circle(window, (255, 0, 0), (x, y), 10)
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()

How to calculate the minimum translation vector between a circle and a rectangle?

I am aware of how to detect collision with circle and rectangles but I am not sure how to find the minimum translation vector between the two. I know how to do it with the SAT collision detection algorithm, but that is too complicated for my simple implementation right now.
I am really not sure what to do except change the x-coordinate appropriately.
Here is the code. When you pressed the down button, what I would want is that the circle automatically is "shoved" to the left (since it is already positioned a bit left to the center) when constantly having the down button pressed i.e. it is moving down but sliding to the left.
import pygame
if __name__ == "__main__":
pygame.init()
display = pygame.display.set_mode((500, 500))
display.fill((255, 255, 255))
circle_x = 240
circle_y = 50
pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
pygame.display.update()
vel = 1
is_down_held = False
clock = pygame.time.Clock()
while True:
pressed_keys = pygame.key.get_pressed()
if pressed_keys[pygame.K_DOWN]:
circle_y += vel
display.fill((255, 255, 255))
pygame.draw.circle(display, (0, 0, 255), (circle_x, circle_y), 50)
pygame.draw.rect(display, (0, 255, 255), (240, 250, 20, 250))
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
dt = clock.tick(60)
dt /= 1000
I tried moving the x-coordinate with some constant, however, it looks unrealistic and is actually passing through the rectangle sometimes (since it actually doesn't detect the rectangle).
Okay, it seems like I had to implement SAT collision detection at the end anyways. To calculate the minimum translation vector between a rectangle and a circle, you go through the necessary axes for the shapes (see https://jcharry.com/blog/physengine10 and https://www.sevenson.com.au/actionscript/sat/ for an explanation on that) then you take the smallest overlap axis for when there is a collision. Below is the code using pygame GUI library:
import math
import pygame
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
self.magnitude = math.sqrt(x ** 2 + y ** 2)
if self.magnitude != 0:
self.direction_x = -x / self.magnitude
self.direction_y = -y / self.magnitude
else:
self.direction_x = 0
self.direction_y = 0
def normalize(self):
if self.magnitude != 0:
self.x /= self.magnitude
self.y /= self.magnitude
self.magnitude = 1
def project_vector(vector1, vector2):
return get_dot_product(vector1, get_unit_vector(vector2))
def get_dot_product(vector1, vector2):
return (vector1.x * vector2.x) + (vector1.y * vector2.y)
def get_normal(vector):
return Vector(vector.y, -vector.x)
def get_vector(point):
return Vector(point[0], point[1])
def scale_vector(vector, magnitude):
return Vector(vector.x*magnitude, vector.y*magnitude)
def get_unit_vector(vector):
if vector.magnitude != 0:
return scale_vector(vector, 1 / vector.magnitude)
else:
return scale_vector(vector, 0)
def get_closest_point(circle_centre, rectangle_points):
closest_distance = float('inf')
closest_point = None
for point in rectangle_points:
distance = (circle_centre[0] - point[0])**2 + (circle_centre[1] - point[1])**2
if distance <= closest_distance:
closest_distance = distance
closest_point = point
return closest_point
def is_collision(circle_centre, rectangle_points):
closest_point = get_closest_point(circle_centre, rectangle_points)
rectangle_edge_vectors = []
for point in rectangle_points:
rectangle_edge_vectors += [get_vector(point)]
rectangle_edge_normals = []
for i in range(len(rectangle_points) - 1):
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
axes = rectangle_edge_normals
vectors = rectangle_edge_vectors
for axis in axes:
current_rect_max_x = float('-inf')
current_rect_min_x = float('inf')
for vector in vectors:
current_rect_projection = project_vector(vector, axis)
if current_rect_projection >= current_rect_max_x:
current_rect_max_x = current_rect_projection
if current_rect_projection <= current_rect_min_x:
current_rect_min_x = current_rect_projection
current_circle_projection = project_vector(get_vector(circle_centre), axis)
current_circle_max_x = current_circle_projection + 25
current_circle_min_x = current_circle_projection - 25
if current_rect_min_x > current_circle_max_x or current_circle_min_x > current_rect_max_x:
return False
return True
def get_minimum_translation_vector(circle_centre, rectangle_points):
closest_point = get_closest_point(circle_centre, rectangle_points)
rectangle_edge_vectors = []
for point in rectangle_points:
rectangle_edge_vectors += [get_vector(point)]
rectangle_edge_normals = []
for i in range(len(rectangle_points) - 1):
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[i + 1][0] - rectangle_points[i][0], rectangle_points[i + 1][1] - rectangle_points[i][1])))]
rectangle_edge_normals += [get_normal(get_vector((rectangle_points[0][0] - rectangle_points[len(rectangle_points) - 1][0], rectangle_points[0][1] - rectangle_points[len(rectangle_points) - 1][1])))]
rectangle_edge_normals += [get_vector((circle_centre[0] - closest_point[0], circle_centre[1] - closest_point[1]))]
axes = rectangle_edge_normals
for axis in axes:
axis.normalize()
vectors = rectangle_edge_vectors
minimum_translation_vector = Vector(axes[0].x, axes[0].y)
minimum_translation_vector.magnitude = float('inf')
current_minimum_translation_vector = Vector(axes[0].x, axes[0].y)
current_minimum_translation_vector.magnitude = float('inf')
for axis in axes:
current_rect_max_x = float('-inf')
current_rect_min_x = float('inf')
for vector in vectors:
current_rect_projection = project_vector(vector, axis)
if current_rect_projection >= current_rect_max_x:
current_rect_max_x = current_rect_projection
if current_rect_projection <= current_rect_min_x:
current_rect_min_x = current_rect_projection
current_circle_projection = project_vector(get_vector(circle_centre), axis)
current_circle_max_x = current_circle_projection + 25
current_circle_min_x = current_circle_projection - 25
current_minimum_translation_vector = axis
current_minimum_translation_vector.magnitude = abs(current_circle_min_x - current_rect_max_x)
if current_minimum_translation_vector.magnitude <= minimum_translation_vector.magnitude:
minimum_translation_vector = axis
minimum_translation_vector.magnitude = current_minimum_translation_vector.magnitude
return minimum_translation_vector
if __name__ == "__main__":
pygame.init()
display = pygame.display.set_mode((500, 500))
rectangle_points_main = [(250, 250), (300, 250), (300, 300), (250, 300)]
circle_centre_main = (0, 0)
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
circle_centre_main = (pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1])
display.fill((255, 255, 255))
if is_collision(circle_centre_main, rectangle_points_main):
pygame.draw.circle(display, (255, 0, 0), circle_centre_main, 25)
minimum_translation_vector_main = get_minimum_translation_vector(circle_centre_main, rectangle_points_main)
dx = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_x
dy = minimum_translation_vector_main.magnitude * minimum_translation_vector_main.direction_y
rectangle_points_main = [(rectangle_points_main[0][0] + dx, rectangle_points_main[0][1] + dy),
(rectangle_points_main[1][0] + dx, rectangle_points_main[1][1] + dy),
(rectangle_points_main[2][0] + dx, rectangle_points_main[2][1] + dy),
(rectangle_points_main[3][0] + dx, rectangle_points_main[3][1] + dy)]
else:
pygame.draw.circle(display, (0, 0, 255), circle_centre_main, 25)
pygame.draw.rect(display, (0, 255, 0), (rectangle_points_main[0][0], rectangle_points_main[0][1], 50, 50))
dt = clock.tick(60)
dt /= 1000
pygame.display.update()

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

Pygame character face mouse

I am creating a basic 2d overhead view game, and am currently working to make the character "face" the mouse as I move it.
Here is my code so far:
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Player,self).__init__()
self.image=player_image
self.original_image=self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self,rect):
player.rotate()
self.rect.x+=self.change_x
self.rect.y+=self.change_y
screen.blit(self.image,(rect.x,rect.y))
def check(self):
global tick
if pygame.mouse.get_pressed()[0]:
for tree in resources:
tree_rect=tree.rect
if self.rect.colliderect(tree_rect) and tick>=9:
self.inventory['wood']+=1
tick=0
def rotate(self):
mouse_x, mouse_y = pygame.mouse.get_pos()
rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = pygame.transform.rotate(self.original_image, int(angle))
self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))
To do this, I followed this answer. It supposedly calculated the vector between the mouse and the player, and turned the character around toward the mouse each step, but for me the sprite wobbled around when the mouse moved, and turned invisible.
Full code:
import pygame
from pygame.locals import *
import sys
import math
import pygame.gfxdraw
import random
pygame.init()
black=(0,0,0)
white=(255,255,255)
forest=(34,139,34)
red=(255,0,0)
blue=(0,0,255)
green=(0,255,0)
light_green=(0,120,0)
dark_green=(0,90,0)
skin=(255,224,189)
yellow=(255,255,0)
wood_image=pygame.image.load('./assets/images/wood.png')
stone_image=pygame.image.load('./assets/images/stone.png')
player_image=pygame.image.load('./assets/images/player.png')
tick=0
def terminate():
pygame.quit()
sys.exit()
def drawTextcenter(text,font,screen,x,y,color):
textobj=font.render(text,True,color)
textrect=textobj.get_rect(center=(x,y))
screen.blit(textobj,textrect)
def drawText(text, font, surface, x, y,color):
textobj=font.render(text, 1, color)
textrect=textobj.get_rect()
textrect.topleft=(x, y)
surface.blit(textobj, textrect)
class Button(object):
global screen_width,screen_height,screen
def __init__(self,x,y,width,height,text_color,background_color,text):
self.rect=pygame.Rect(x,y,width,height)
self.x=x
self.y=y
self.width=width
self.height=height
self.text=text
self.text_color=text_color
self.background_color=background_color
self.angle=0
def check(self):
return self.rect.collidepoint(pygame.mouse.get_pos())
def draw(self):
pygame.draw.rect(screen, self.background_color,(self.rect),0)
drawTextcenter(self.text,font,screen,self.x+self.width/2,self.y+self.height/2,self.text_color)
pygame.draw.rect(screen,self.text_color,self.rect,3)
class Bar(object):
def __init__(self,x,y,length,color):
self.rect=pygame.Rect(x,y,length,17.5)
self.image=pygame.draw.rect(screen,white,(self.rect),)
self.x=x
self.y=y
self.width=100
self.height=17.5
self.color=color
self.multiplier=length/100
def draw(self,num):
rect=pygame.Rect(self.x,self.y,num*self.multiplier,self.height)
pygame.draw.rect(screen,self.color,self.rect,3)
pygame.draw.rect(screen,self.color,rect,0)
class Player(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Player,self).__init__()
self.image=player_image
self.original_image=self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self,rect):
self.rotate(camera)
self.rect.x += self.change_x
self.rect.y += self.change_y
rect = camera.apply(self)
screen.blit(self.image, rect)
def check(self):
global tick
if pygame.mouse.get_pressed()[0]:
for tree in resources:
tree_rect=tree.rect
if self.rect.colliderect(tree_rect) and tick>=9:
self.inventory['wood']+=1
tick=0
def rotate(self,camera):
mouse_x, mouse_y = pygame.mouse.get_pos()
mouse_x -= camera.state.x
mouse_y -= camera.state.y
rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
angle = -math.degrees(math.atan2(rel_y, rel_x))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Tree(pygame.sprite.Sprite):
def __init__(self,x,y):
super(Tree,self).__init__()
self.rect=pygame.Rect(x,y,100,100)
self.rect.x=x
self.rect.y=y
def update(self,rect):
pygame.gfxdraw.filled_circle(screen,rect.x,rect.y,80,light_green)
pygame.gfxdraw.aacircle(screen,rect.x,rect.y,80,light_green)
class Rock(pygame.sprite.Sprite):
def __init__(self):
pass
class Camera(object):
def __init__(self,camera_func,width,height):
self.camera_func=camera_func
self.state=pygame.Rect(0,0,width,height)
def apply(self,target):
return target.rect.move(self.state.topleft)
def update(self,target):
self.state=self.camera_func(self.state,target.rect)
def complex_camera(camera, target_rect):
l, t = target_rect.center
_,_,w,h = camera
l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h
l = min(0, l)
l = max(-(camera.width-screen_width), l)
t = max(-(camera.height-screen_height), t)
t = min(0, t)
return pygame.Rect(l, t, w, h)
clock=pygame.time.Clock()
font=pygame.font.SysFont(None,40)
screen_width=1440
screen_height=800
screen=pygame.display.set_mode([screen_width,screen_height])
pygame.display.set_caption('Survival')
total_level_width=screen_width*5
total_level_height=screen_height*5
camera = Camera(complex_camera, total_level_width, total_level_height)
player=Player(random.randint(100,7900),random.randint(100,5900))
friendlies=pygame.sprite.Group()
friendlies.add(player)
player_health_bar=Bar(125,15,200,green)
player_health=100
player_food_bar=Bar(437.5,15,200,red)
player_food=100
player_thirst_bar=Bar(750,15,200,blue)
player_thirst=100
player_energy_bar=Bar(1100,15,200,yellow)
player_energy=100
resources=pygame.sprite.Group()
wood=False
inventory={}
for i in range(1000):
tree=Tree(random.randint(100,7900),random.randint(100,5900))
resources.add(tree)
done1=False
while not done1:
screen.fill(black)
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size('Survival')
drawText('Survival', font, screen, (screen_width/2-text_width/2), (screen_height / 2-375),white)
font=pygame.font.SysFont(None, 40)
start_button=Button(screen_width/2-125,650,250,50,white,black,'Start')
start_button.draw()
back_button=Button(screen_width/2-125,725,250,50,white,black,'Back')
back_button.draw()
pygame.display.flip()
done2=False
while not done2:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if start_button.check()==True:
done3=False
pause_back=False
while not done3:
tick+=1
for event in pygame.event.get():
if event.type==pygame.QUIT:
terminate()
elif event.type==pygame.KEYDOWN:
if event.key==pygame.K_a:
player.changespeed(-(player.speed), 0)
elif event.key==pygame.K_d:
player.changespeed(player.speed, 0)
elif event.key==pygame.K_w:
player.changespeed(0, -(player.speed))
elif event.key==pygame.K_s:
player.changespeed(0, player.speed)
elif event.key==pygame.K_p:
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size('Paused')
drawText('Paused', font, screen, (screen_width / 2-(text_width/2)), (screen_height / 2-375),white)
resume_button=Button(screen_width/2-125,650,250,50,white,black,'Resume')
resume_button.draw()
back_button.draw()
pygame.display.flip()
back=False
while not back:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if resume_button.check()==True:
back=True
elif back_button.check()==True:
done3=True
done2=True
pause_back=True
back=True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_a:
player.changespeed(player.speed, 0)
elif event.key == pygame.K_d:
player.changespeed(-(player.speed), 0)
elif event.key == pygame.K_w:
player.changespeed(0, player.speed)
elif event.key == pygame.K_s:
player.changespeed(0, -(player.speed))
camera.update(player)
if player.rect.x<0:
player.rect.x=0
if player.rect.right>total_level_width:
player.rect.right=total_level_width
if player.rect.y<0:
player.rect.y=0
if player.rect.bottom>total_level_height:
player.rect.bottom=total_level_height
screen.fill(dark_green)
player.update(camera)
for resource in resources:
resource.update(camera.apply(resource))
player_health_bar.draw(player_health)
player_food_bar.draw(player_food)
player_thirst_bar.draw(player_thirst)
#player_endergy_bar.draw(player_energy)
font=pygame.font.SysFont(None, 40)
drawText('Health:',font,screen,20,10,green)
drawText('Food:',font,screen,350,10,red)
drawText('Thirst:',font,screen,650,10,blue)
#drawText('Energy:', font, screen, 975, 10, yellow)
pygame.draw.rect(screen,forest,(50,675,100,100))
pygame.draw.rect(screen,forest,(175,675,100,100))
pygame.draw.rect(screen,forest,(300,675,100,100))
#player.check()
for item in player.inventory:
if item=='wood' and player.inventory['wood']>0:
wood=True
if wood:
screen.blit(wood_image,(62.5,687.5))
drawTextcenter(str(player.inventory['wood']),font,screen,100,735,black)
pygame.display.flip()
clock.tick(100)
if pause_back==True:
break
if pause_back==True:
break
screen.fill(black)
font=pygame.font.SysFont(None, 90)
text_width,text_height=font.size("Game Over")
drawText('Game Over', font, screen, (screen_width/2-text_width/2), (screen_height / 2-200),white)
font=pygame.font.SysFont(None, 40)
retry_button=Button(screen_width/2-125,650,250,50,white,black,'Retry')
retry_button.draw()
back_button.draw()
pygame.display.flip()
back=False
while not back:
for event in pygame.event.get():
if event.type==QUIT:
terminate()
elif event.type==pygame.MOUSEBUTTONDOWN:
if retry_button.check()==True:
back=True
if back_button.check()==True:
back=True
done2=True
done3=True
elif back_button.check()==True:
done2=True
done1=True
I haven't checked out your complete program because it's too much code, but I can see a mistake in the Player class. You're assigning the (self.rect.x,self.rect.y) (the top left) coordinates to the center of the new rect in the rotate method, but you have to assign the center coords of the previous rect.
self.rect = self.image.get_rect(center=(self.rect.x,self.rect.y))
Change the line above to:
self.rect = self.image.get_rect(center=self.rect.center)
Here's a minimal, complete example:
import math
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super(Player,self).__init__()
x, y = pos
self.image = pg.Surface((50, 30), pg.SRCALPHA)
pg.draw.polygon(
self.image,
pg.Color('dodgerblue1'),
((1, 1), (49, 15), (1, 29)))
self.original_image = self.image
self.rect=self.image.get_rect(center=(x,y))
self.change_x=0
self.change_y=0
self.speed=7.5
self.inventory={'wood':0}
def changespeed(self,x,y):
self.change_x+=x
self.change_y+=y
def update(self):
self.rotate()
self.rect.x+=self.change_x
self.rect.y+=self.change_y
def rotate(self):
mouse_x, mouse_y = pg.mouse.get_pos()
rel_x, rel_y = mouse_x - self.rect.x, mouse_y -self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = pg.transform.rotate(self.original_image, angle)
# rect.x and .y are the topleft coords, but you need the to
# pass the center coordinates of the previous rect to the new one.
self.rect = self.image.get_rect(center=self.rect.center)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
player = Player((300, 200))
all_sprites.add(player)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_d:
player.change_x = 4
elif event.key == pg.K_a:
player.change_x = -4
elif event.type == pg.KEYUP:
if event.key == pg.K_d:
player.change_x = 0
elif event.key == pg.K_a:
player.change_x = 0
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
The problem is caused by the camera. You have to apply the camera offset to the mouse position as well.
Here's a simple solution. First, pass the camera instead of the rect to the player.update method.
player.update(camera)
Pass it to the rotate method as well.
def update(self, camera):
self.rotate(camera)
self.rect.x += self.change_x
self.rect.y += self.change_y
rect = camera.apply(self)
screen.blit(self.image, rect)
In the rotate method you have to subtract the camera position from the mouse position to get the world coordinates.
def rotate(self, camera):
mouse_x, mouse_y = pygame.mouse.get_pos()
# Subtract the camera offset from the mouse position.
mouse_x -= camera.state.x
mouse_y -= camera.state.y
# Use the center coordinates of the rect.
rel_x, rel_y = mouse_x - self.rect.centerx, mouse_y -self.rect.centery
angle = -math.degrees(math.atan2(rel_y, rel_x))
self.image = pygame.transform.rotate(self.original_image, angle)
# Set the new center to the previous center coords.
self.rect = self.image.get_rect(center=self.rect.center)
I also had to modify the complex_camera function, because it should work with the center coordinates.
def complex_camera(camera, target_rect):
# The camera works with the center instead of
# the topleft coordinates now.
l, t = target_rect.center
_,_,w,h = camera
l,t,_,_ = -l+screen_width/2, -t+screen_height/2, w, h
l = min(0, l)
l = max(-(camera.width-screen_width), l)
t = max(-(camera.height-screen_height), t)
t = min(0, t)
return pygame.Rect(l, t, w, h)

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.