I don't understand why the sprite collision detection is not taking the image rotation into account.
I tried different functions but they didn't work out for me.
CarSprites.py:
import pygame, math, random
class CarSprite(pygame.sprite.Sprite):
MIN_FORWARD_SPEED = 5
ACCELERATION = 2
TURN_SPEED = 5
IS_DUMMY = False
def __init__(self, image, position, direction):
pygame.sprite.Sprite.__init__(self)
self.src_image = pygame.transform.scale(pygame.image.load(image), (51, 113))
self.position = position
self.rect = self.src_image.get_rect()
self.rect.center = self.position
self.speed = 0
self.direction = direction
self.k_left = self.k_right = self.k_down = self.k_up = 0
def update(self, deltat):
# SIMULATION
#speed
self.speed += (self.k_up + self.k_down)
if self.speed < self.MIN_FORWARD_SPEED:
self.speed = self.MIN_FORWARD_SPEED
self.speed += (self.k_up + self.k_down)
if self.speed > self.MIN_FORWARD_SPEED * 2:
self.speed = self.MIN_FORWARD_SPEED * 2
#direction
self.direction += (self.k_right + self.k_left)
x, y = self.position
rad = self.direction * math.pi / 180
x += -self.speed*math.sin(rad)
y += -self.speed*math.cos(rad)
self.position = (x, y)
self.image = pygame.transform.rotate(self.src_image, self.direction)
self.rect = self.image.get_rect()
self.rect.center = self.position
#Emulate friction with road and wind
if self.speed > self.MIN_FORWARD_SPEED :
self.speed += -0.1
class DummyCarSprite(pygame.sprite.Sprite):
#MIN_FORWARD_SPEED = 5
#MIN_REVERSE_SPEED = 10.1
#MAX_FORWARD_SPEED_ABOVE_MIN = 5
#ACCELERATION = 2
#TURN_SPEED = 5
def __init__(self, image, position, direction):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(pygame.image.load(image), (51, 113))
self.position = position
self.rect = self.image.get_rect()
self.rect.center = self.position
self.speed = 0
self.direction = direction
self.k_left = self.k_right = self.k_down = self.k_up = 0
if random.randint(0,1) == 1 :
self.direction = self.direction + 180
game.py
def GetDummyCars() :
allDummyCars = [
#Row1
#DummyCarSprite(getCarImage(), (211.9915431212928, 209.36603413022453), 180),
#DummyCarSprite(getCarImage(), (268.9915431212928, 209.36603413022453), 180),
DummyCarSprite(getCarImage(), (325.9915431212928, 209.36603413022453), 180),
DummyCarSprite(getCarImage(), (382.9915431212928, 209.36603413022453), 180)
#etc. etc.
]
dummyCars = []
for dummyCar in allDummyCars :
if random.randint(0,1) == 1 :
dummyCars.append(dummyCar)
return pygame.sprite.RenderPlain(*dummyCars)
playerCar = CarSprite(playerCarImagePath, (1550, 100), 90)
playerCar_group = pygame.sprite.RenderPlain(playerCar)
dummyCar_group = GetDummyCars()
#collisions with dummy cars
dummyCarCollisions = pygame.sprite.groupcollide(playerCar_group, dummyCar_group)
if dummyCarCollisions != {}:
lose_condition = True
playerCar.src_image = pygame.image.load('images/collision.png')
seconds = 0
playerCar.speed = 0
playerCar.MIN_FORWARD_SPEED = 0
playerCar.MAX_FORWARD_SPEED_ABOVE_MIN = 0
playerCar.k_right = 0
playerCar.k_left = 0
I would like to find a way to detect collision between the sprites in the 2 car groups, or collision between the player sprite and the dummycar_group (each would work out for me) that takes the rotation of the image into account.
What happens now is when I steer the car, the car image rotates but it looks like the collision detection doesn't see that.
Is there a better function i can use that could handle this?
My full source code: dropbox
I found this question very interesting and fun to work on and fix! Thanks for posting!
I have found the problem and it is rather unfortunate. Your code runs perfectly from what I have seen. The problem is that pygame uses rectangles for collision detection which are not precise enough.
You are applying the rotation to the image but that just makes it bigger and less accurate. I have highlighted the problem with the addition of rendiering debug lines in the GameLoop function.
# draw some debug lines
pygame.draw.rect(screen, (255, 0, 0), playerCar.rect, 1)
for dummyCar in dummyCar_group.sprites():
pygame.draw.rect(screen, (0, 0, 255), dummyCar.rect, 1)
Add these lines in and you shall see for yourself.
The only solution that I can think of is to add in the functionality to use polygons for collision detection yourself.
The way I would implement this is to:
Stop using the rect attribute of all Sprites for collision detection and stop using any methods for collision detection that use the underlying Rects, e.g pygame.sprite.spritecollide().
add a pointlist field to all sprites that need it which will store all the points of the polygon
Implement your own function that takes in two lists of points and returns if they overlap
I hope that this answer helped you and if you have any further questions please feel free to post a comment below!
Related
class Entity():
def __init__(self, char_type, x, y, scale):
self.char_type = char_type
self.flip = False
self.direction = 1
self.vel_y = 0
self.jump = False
self.attacking = False
self.animation_list = []
self.frame_index = 0
self.action = 0
self.update_time = pygame.time.get_ticks()
#load all images
animation_types = ['idle', 'run', 'jump', 'attack', 'death', 'hit']
for animation in animation_types:
#reset temporary list of images
temp_list = []
#count number of files in the folder
num_of_frames = len(os.listdir(f"img/{self.char_type}/{animation}"))
for i in range(num_of_frames-2):
img = pygame.image.load(f"img/{self.char_type}/{animation}/{i}.png")
img = pygame.transform.scale(img, (img.get_width()*scale,img.get_height()*3))
temp_list.append(img)
self.animation_list.append(temp_list)
self.image = self.animation_list[self.action][self.frame_index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
This is my first time attempting a pygame project and i'm having issues. Basically the frames are really big so the rectangle I made with self.rect = self.image.get_rect() to create the hitbox for my playable character is massive. what it looks like I tried solving this by using self.rect = self.image.get_bounding_rect()This did solve the issue with having a massive rectangle however it made the image which I drew using the following method
def draw(self, surface):
img = pygame.transform.flip(self.image, self.flip, False)
pygame.draw.rect(surface, (255, 0 , 0), self.rect)
surface.blit(img, self.rect)
to not be centered over the rectangle which should be its hitbox. That ended up looking like this. I think that the issue is that
See How to get the correct dimensions for a pygame rectangle created from an image and How can I crop an image with Pygame?:
Use pygame.Surface.get_bounding_rect() and pygame.Surface.subsurface to get a cropped region from your image.
e.g.:
original_image = pygame.image.load(f"img/{self.char_type}/{animation}/{i}.png")
crop_rect = original_image.get_bounding_rect()
cropped_image = original_image.subsurface(crop_rect).copy()
img = pygame.transform.scale(cropped_image,
(cropped_image.get_width()*scale, cropped_image.get_height()*3))
I create the sprite group land. For that, i use the class Landschaft and the while loop
How can i get from first sprite in the sprite group land rect.x , rect.y rect.w and rect.h ?
class Landschaft(pygame.sprite.Sprite):
def __init__(self,image):
pygame.sprite.Sprite.__init__(self)
self.image = image
#self.mask = pygame.mask.from_surface(self.image )
self.rect = self.image.get_rect()
#pygame.draw.ellipse(self.image, red, [self.rect.x,self.rect.y,self.rect.width,self.rect.height],3)
x=random.randrange(0, breite -60)
y=random.randrange(200, hoehe - 200)
self.rect.center = (x,y)
land = pygame.sprite.Group()
while len(land) < anzahl_gegenstaende:
ii = len(land)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
if ii == 0:
img = pygame.transform.scale(img,(breite))
else:
zb,zh = img.get_rect().size
scale_hoehe =100
scale_breite = int(100 * zb / zh)
#img = img.convert_alpha()
img = pygame.transform.scale(img,(scale_breite,scale_hoehe))
m = Landschaft(img)
if not pygame.sprite.spritecollide(m, land, False):
land.add(m)
A list of sprites from a pygame.sprite.Group can be obtained with the sprites() method:. Therefore the first sprite in the Group is:
first_sprite = group.sprites()[0]
Problem?
Your land = pygame.sprite.Group() prevents you from getting the first element from a sprite group in pygame. This is a fairly common problem. To get it you have to use sprites() as you did, but in a different way. You approached the solution with your code. You came close.
You have to write, as a solution to your problem, group.sprites() (with [0]) (not pygame.sprite.Group ()) to get from first sprite in the sprite group land rect.x, rect.y rect.w and rect.h
Solution?
The solution to your problem is:
class Landschaft(pygame.sprite.Sprite):
def __init__(self,image):
pygame.sprite.Sprite.__init__(self)
self.image = image
#self.mask = pygame.mask.from_surface(self.image )
self.rect = self.image.get_rect()
#pygame.draw.ellipse(self.image, red, [self.rect.x,self.rect.y,self.rect.width,self.rect.height],3)
x=random.randrange(0, breite -60)
y=random.randrange(200, hoehe - 200)
self.rect.center = (x,y)
land = group.sprites()[0]
while len(land) < anzahl_gegenstaende:
ii = len(land)
img = pygame.image.load(f"Bilder/Gegenstaende/geg{ii}.png")
if ii == 0:
img = pygame.transform.scale(img,(breite))
else:
zb,zh = img.get_rect().size
scale_hoehe =100
scale_breite = int(100 * zb / zh)
#img = img.convert_alpha()
img = pygame.transform.scale(img,(scale_breite,scale_hoehe))
m = Landschaft(img)
if not pygame.sprite.spritecollide(m, land, False):
land.add(m)
In my game I am creating a turret (a type of machine gun you use on the ground). The problem is that I am using a joystick to move the character, when the joystick is downwards the y speed is positive (so it can move downwards) the opposite is for if you move upwards. Then if checks your current angle and sees which direction you are pointing in if one of the if statements happen then it will allow you to move. What the main issue is that when I move my joystick upwards the gun points dowanrds.
I have already tried making a variable that stores the direction but that lead to the same problem so I discarded that idea and went back to the one I had before. There is also a turret stand where the turret is drawn onto
joystick = pygame.joystick.Joystick(0)
joystick.init()
turret_stand_pic = pygame.image.load("C:/knuckles_pictures/turret_stand.png").convert_alpha()
class Turret_stand():
def __init__(self, x, y, picture, picture_tag):
self.xpos = x
self.ypos = y
self.picture = picture
super().__init__()
self.picture = pygame.transform.scale(self.picture, (200, 200))
self.rect = self.picture.get_rect()
self.rect.x = self.xpos
self.rect.y = self.ypos
self.tag = picture_tag
def draw(self):
screen.blit(self.picture, (self.xpos, self.ypos))
turret_gun_pic = pygame.image.load("C:/knuckles_pictures/turret_gun.png").convert_alpha()
class Turret_gun():
def __init__(self, x, y, picture, picture_tag):
self.xpos = x
self.ypos = y
self.picture = picture
super().__init__()
self.picture = pygame.transform.scale(self.picture, (200, 80))
self.rect = self.picture.get_rect()
self.rect.x = self.xpos
self.rect.y = self.ypos
self.tag = picture_tag
self.previous_angle = 0
self.angle = -90
self.speed_x = 0
self.speed_y = 0
self.facing = "left"
def rotate(self, angle):
if angle != self.angle:
self.picture = pygame.transform.rotate(self.picture, angle)
"""self.angle += angle
self.previous_angle = self.angle"""
def draw(self):
if self.angle == 0:
screen.blit(self.picture, (self.xpos+70, self.ypos-70))
elif self.angle == -90:
screen.blit(self.picture, (self.xpos, self.ypos))
turret = Turret_gun(500, 370, turret_gun_pic, "turret")
turret_stand = Turret_stand(500, 400, turret_stand_pic, "turret stand")
while True:
[...]
if joystick:
move = 3
axis_x_two, axis_y_two = (joystick.get_axis(3), joystick.get_axis(2))
if abs(axis_x_two) > 0.1:
turret.speed_x = move * axis_x_two
turret.speed_y = move * axis_y_two
turret.speed_y = round(int(turret.speed_y))
turret.speed_x = round(int(turret.speed_x))
if turret.angle == -90 and turret.speed_y == -3 and turret.speed_x <= 1 and turret.speed_x >= -1:
turret.rotate(90)
if turret.angle == 0 and turret.speed_x == -3 and turret.speed_y <= 1 and turret.speed_y >= -1:
turret.rotate(-90)
turret.update()
turret.draw()
The actual results are that when you push the joystick upwards the machine gun points downwards. Heres what I mean:
[![enter image description here][1]][1]
In this case the turret ends up pointing downards:
[1]: https://i.stack.imgur.com/Aheix.jpg
[![enter image description here][2]][2]
What I should expect is that when I move the joystick upwards the turret points upwards.
[2]: https://i.stack.imgur.com/jFyg5.jpg
Sometimes the gun does not show when the joystick is pointing right:
Basically, you need to reverse the direction that you rotate the image. So, to flip it, rotate it an additional 180 degrees.
Change the line in your rotate method to
self.picture = pygame.transform.rotate(self.picture, angle+180)
I don't know your exact setup, but this might work only for the up/down case. The gun might still point right if you put the stick left. If this happens, change it to
self.picture = pygame.transform.rotate(self.picture, 180-angle)
This doesn't just flip it, the direction of rotation is reversed. Increasing the angle actually decreases the rotation.
Again, that might not work. It could point right if you move the stick up. If so, try changing the 180 to another number, like 90 or 270. This offsets the rotation by 90 degrees in one direction or the other.
self.picture = pygame.transform.rotate(self.picture, 90-angle)
or
self.picture = pygame.transform.rotate(self.picture, 270-angle)
I want to change an image of the object worker each time when it stops.
The class Worker is created based on the answer of #sloth in this thread.
class Worker(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the workers on top
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.transform.scale(pygame.image.load(image_file).convert_alpha(), (40, 40))
self.rect = self.image.get_rect(topleft=location)
# let's call this handy function to set a random direction for the worker
self.change_direction()
# speed is also random
self.speed = random.randint(1, 3)
def change_direction(self):
# let's create a random vector as direction, so we can move in every direction
self.direction = pygame.math.Vector2(random.randint(-1,1), random.randint(-1,1))
# we don't want a vector of length 0, because we want to actually move
# it's not enough to account for rounding errors, but let's ignore that for now
while self.direction.length() == 0:
self.direction = pygame.math.Vector2(random.randint(-1,1), random.randint(-1,1))
# always normalize the vector, so we always move at a constant speed at all directions
self.direction = self.direction.normalize()
def update(self, screen):
# there is a less than 1% chance every time that direction is changed
if random.uniform(0,1)<0.005:
self.change_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, move back and change direction
if not screen.get_rect().contains(self.rect):
self.change_direction()
self.rect.clamp_ip(screen.get_rect())
I try to create a cache of pre-loaded images
image_cache = {}
def get_image(key):
if not key in image_cache:
image_cache[key] = pygame.image.load(key)
return image_cache[key]
Then I assume that it is necessary to add the following code into def __init__:
images = ["worker.png", "worker_stopped.png"]
for i in range(0,len(images)):
self.images[i] = get_image(images[i])
and the following code into def update(self):
if self.direction.length() == 0:
self.image = self.images[1]
else:
self.image = self.images[0]
However, it does not seem to work properly. The old image worker.png does not disappear and the whole animation gets locked.
I think you should introduce some kind of state to indicate that the worker is running or not. Here's an example. Note the comments:
class Worker(pygame.sprite.Sprite):
# we introduce to possible states: RUNNING and IDLE
RUNNING = 0
IDLE = 1
def __init__(self, location, *groups):
# each state has it's own image
self.images = {
Worker.RUNNING: pygame.transform.scale(get_image("worker.png"), (40, 40)),
Worker.IDLE: pygame.transform.scale(get_image("worker_stopped.png"), (40, 40))
}
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
# let's keep track of the state and how long we are in this state already
self.state = Worker.IDLE
self.ticks_in_state = 0
self.image = self.images[self.state]
self.rect = self.image.get_rect(topleft=location)
self.direction = pygame.math.Vector2(0, 0)
self.speed = random.randint(2, 4)
self.set_random_direction()
def set_random_direction(self):
# random new direction or standing still
vec = pygame.math.Vector2(random.randint(-100,100), random.randint(-100,100)) if random.randint(0, 5) > 1 else pygame.math.Vector2(0, 0)
# check the new vector and decide if we are running or fooling around
length = vec.length()
speed = sum(abs(int(v)) for v in vec.normalize() * self.speed) if length > 0 else 0
if length == 0 or speed == 0:
new_state = Worker.IDLE
self.direction = pygame.math.Vector2(0, 0)
else:
new_state = Worker.RUNNING
self.direction = vec.normalize()
self.ticks_in_state = 0
self.state = new_state
# use the right image for the current state
self.image = self.images[self.state]
def update(self, screen):
self.ticks_in_state += 1
# the longer we are in a certain state, the more likely is we change direction
if random.randint(0, self.ticks_in_state) > 30:
self.set_random_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, change direction
if not screen.get_rect().contains(self.rect):
self.direction = self.direction * -1
self.rect.clamp_ip(screen.get_rect())
This question already has answers here:
How do I create animated sprites using Sprite Sheets in Pygame?
(1 answer)
Invalid destination position for blit error, not seeing how
(1 answer)
Closed 2 years ago.
Alright, so I got some extremely simple code going on here, any comments/suggestions are recommended. Keep in mind, SIMPLE, in other words short and concise. thnx.
My problem is loading an image from a png file. So for example i got a couple of images in the file, and i want only one row to be loaded when the user presses for example, the right arrow key. Basically i have 4 rows, 2 or 3 columns 4 rows for each arrow key respectively
#
import pygame, time, random
pygame.init()
HEIGHT = 700
WIDTH = 1350
GRIDSIZE = HEIGHT/28
BLACK = ( 0, 0, 0)
WHITE = (255,255,255)
RED = (255, 0, 0)
screen=pygame.display.set_mode((WIDTH,HEIGHT))
font = pygame.font.SysFont("arial", 36)
shift = 10
#---------------------------------------#
# classes #
#---------------------------------------#
class Sprite(pygame.sprite.Sprite):
""" (fileName)
Visible game object.
Inherits Sprite to use its Rect property.
"""
def __init__(self, picture=None):
pygame.sprite.Sprite.__init__(self)
self.x = 0
self.y = 0
self.visible = False
self.image = self.blend_image(picture)
self.rect = self.image.get_rect()
self.width, self.height = self.rect.width, self.rect.height
self.update()
def spawn(self, x, y):
""" Assign coordinates to the object and make it visible.
"""
self.x, self.y = x,y
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
self.visible = True
def draw(self, surface):
surface.blit(self.image, self.rect)
def update(self):
# after moving a sprite, the rect attribute must be updated
self.rect = self.image.get_rect()
self.rect = pygame.Rect(self.x,self.y,self.rect.width,self.rect.height)
def moveLeft(self):
self.x -= shift
self.update()
def moveRight(self):
self.x += shift
self.update()
def moveUp(self):
self.y -= shift
self.update()
def moveDown(self):
self.y += shift
self.update()
def blend_image(self, file_name):
""" Remove background colour of an image.
Need to use it when a picture is stored in BMP or JPG format, with opaque background.
"""
image_surface = pygame.image.load(file_name)
image_surface = image_surface.convert()
colorkey = image_surface.get_at((0,0))
image_surface.set_colorkey(colorkey)
return image_surface
#---------------------------------------#
# functions #
#---------------------------------------#
def redraw_screen():
screen.fill(BLACK)
world.draw(screen)
if player.visible:
player.draw(screen)
pygame.display.update()
#---------------------------------------#
# main program #
#---------------------------------------#
world = Sprite("town.png")
world.spawn(0,0)
player = Sprite("player.png")
player.spawn(100,400)
LEFT_BORDER = 0
RIGHT_BORDER = WIDTH-1100
TOP_BORDER = 0
BOTTOM_BORDER = HEIGHT-480
#---------------------------------------#
clock = pygame.time.Clock()
FPS = 10
inPlay = True
while inPlay:
clock.tick(FPS)
# keyboard handler
pygame.event.get()
keys = pygame.key.get_pressed()
if keys[pygame.K_ESCAPE]:
inPlay = False
# world moves opposite to the arrow
if keys[pygame.K_LEFT] and world.x < LEFT_BORDER:
world.moveRight()
if keys[pygame.K_RIGHT] and world.x > RIGHT_BORDER:
world.moveLeft()
if keys[pygame.K_UP] and world.y < TOP_BORDER:
world.moveDown()
if keys[pygame.K_DOWN] and world.y > -BOTTOM_BORDER:
world.moveUp()
redraw_screen()
#---------------------------------------#
pygame.quit()
Right, so this is using python 2.7 btw. Any help is appreciated thnx again.
And also, there are some things that are added that have no use, but thats for later code, and vice-versa. Some tweaks i could do, like naming, that will come later.
If the player presses right, i have an image of a guy facing right.
Left, up, and down same concept.
if he holds the button, the guy "runs"
Now i hav normal position, and running position.
4 rows, 2 columns in a png file, how to load 1 row for respective key?