Rotating a sprite on pygame [duplicate] - python

This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
Closed last year.
I have created a class for the zombie I blit into the screen, but the rotation does not work at all, it rotates a lot and moves the image around, is there some way to rotate the image around its center? how could I change the rotate def so that it works properly?
class zombieObj:
def __init__(self, x, y, vel, angle):
tempZombie = pygame.image.load('zombieSprite.png')
self.zombieSpriteSurface = pygame.transform.scale(tempZombie, (64, 64))
self.x = x
self.y = y
self.vel = 1
self.angle = angle
def rotate(self, image, angle):
self.zombieSpriteSurface = pygame.transform.rotate(image, angle)
and this is how I called it in the loop:
zombieSprite.angle = zombieSprite.angle + 5
zombieSprite.rotate(zombieSprite.zombieSpriteSurface, zombieSprite.angle)

See How do I rotate an image around its center using PyGame?. The trick is to get the center of the image before rotation and set it after rotation. Also, you need to rotate the original image to avoid distortion:
class ZombieObj:
def __init__(self, x, y, vel, angle):
tempZombie = pygame.image.load('zombieSprite.png')
self.originalImage = pygame.transform.scale(tempZombie, (64, 64))
self.vel = 1
self.angle = 0
self.rect = self.originalImage.get_rect(topleft = (x, y))
self.roatate(angle)
def rotate(self, angle):
self.angle = angle
self.zombieSpriteSurface = pygame.transform.rotate(self.originalImage, self.angle)
self.rect = self.zombieSpriteSurface.get_rect(center = self.rect.center)
If you want to increase the rotation by a certain angle, you need to add the additional angle to the current angle:
class ZombieObj:
# [...]
def rotate(self, deltaAngle):
self.angle += deltaAngle
self.zombieSpriteSurface = pygame.transform.rotate(self.originalImage, self.angle)
self.rect = self.zombieSpriteSurface.get_rect(center = self.rect.center)

Related

Source object must be a surface

I am encountering a problem which says that the source object must be a surface. I asked this question before, and the answer worked, but now it's a different scenario. I looked at the previous question I had, and I couldn't figure out how to solve it.
Error Message:
Traceback (most recent call last):
File "C:\Users\Daniel\Desktop\Thing.py", line 22, in <module>
level.run()
File "C:\Users\Daniel\Desktop\level2.py", line 131, in run
self.clouds.draw(self.display_surface, self.world_shift)
File "C:\Users\Daniel\Desktop\decoration.py", line 67, in draw
self.cloud_sprites.draw(surface)
File "C:\Users\Daniel\AppData\Local\Programs\Python\Python310\lib\site-
packages\pygame\sprite.py", line 551, in draw
self.spritedict.update(zip(sprites, surface.blits((spr.image, spr.rect) for
spr in sprites)))
TypeError: Source objects must be a surface
Basically I am trying to blit things onto a surface, and the thing that confuses me the most is that it worked when I was trying to blit water onto the screen, it had the exact same code but instead of 'cloud' it had 'water', however the water blitting worked while the cloud did not.
Water Class:
class Water:
def __init__(self, top, level_width):
water_start = -width
water_tile_width = 192
tile_x_amount = int((level_width + width) / water_tile_width)
self.water_sprites = pygame.sprite.Group()
for tile in range(tile_x_amount):
x = tile * water_tile_width + water_start
y = top
sprite = AnimatedTile(192, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\water')
self.water_sprites.add(sprite)
def draw(self, surface, shift):
self.water_sprites.update(shift)
self.water_sprites.draw(surface)
Cloud Class:
class Clouds:
def __init__(self, horizon, level_width, cloud_number):
min_x = -width
max_x = level_width + width
min_y = 0
max_y = horizon
cloud_surface_list = 'C:\\Desktop\\Game\\decoration\\clouds'
self.cloud_sprites = pygame.sprite.Group()
for cloud in range(cloud_number):
cloud = choice(cloud_surface_list)
x = randint(min_x, max_x)
y = randint(min_y, max_y)
sprite = StaticTile(0, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\clouds')
self.cloud_sprites.add(sprite)
def draw(self, surface, shift):
self.cloud_sprites.update(shift)
self.cloud_sprites.draw(surface)
As you can see the draw method is the exact same code, pretty much. The only difference that I can spot that could be causing the problem would be the inheritance. Water inherits from a class called AnimatedTile while Clouds inherits from StaticTile. I will put the code for both of those here:
AnimatedTile:
class AnimatedTile(Tile):
def __init__(self, size, x, y, pos, path):
super().__init__(size, x, y, (x, y))
self.frames = import_folder(path)
self.frame_index = 0
self.image = self.frames[int(self.frame_index)]
def animate(self):
self.frame_index += 0.15
if self.frame_index >= 4:
self.frame_index = 0
self.image = self.frames[int(self.frame_index)]
def update(self, shift):
self.animate()
self.rect.x += shift
StaticTile:
class StaticTile(Tile):
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y, (x,y))
self.image = surface
self.surface = surface
Drawing and Updating:
self.clouds.draw(self.display_surface,
self.world_shift)
*I know these are surfaces because I drew them the same exact way to blit the water onto the screen (self.water.draw(self.display_surface, self.world_shift) and it worked.
Other code:
class Level:
def __init__(self, level_data, surface):
self.display_surface = surface
self.world_shift = -8
Tile:
class Tile(pygame.sprite.Sprite):
def __init__(self, size, x, y, pos):
super().__init__()
self.image = pygame.Surface((size, size))
self.rect = self.image.get_rect(topleft = pos)
def update(self, shift):
self.rect.x += shift
world_shift is basically how fast the level is moving (negative means right, positive means left)
I'm sorry if this sounds stupid and the answer is obvious.
You're not correctly assigning to the image attribute of your StaticTile sprite in Clouds. I'm not sure if it's the caller (Clouds) or the tile class that is getting it wrong though, you'll have to decide what the API should be.
The mismatch is between your call, here:
sprite = StaticTile(0, x, y, (x, y),
'C:\\Desktop\\Game\\decoration\\clouds')
And the StaticTile.__init__ method, which is declared like this:
def __init__(self, size, x, y, pos, surface):
super().__init__(size, x, y, (x,y))
self.image = surface
self.surface = surface
Note that the calling code is passing a string as the surface argument. That's similar to how AnimatedTile is initialized (with a path), but the StaticTile class expects a surface object to already be loaded.

Change color of Pygame Surface

I've made a Rectangle class:
class Rectangle(pygame.sprite.Sprite):
def __init__(self, len, x, y, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((len, len), flags=pygame.SRCALPHA)
# self.image.set_colorkey(Constants.WHITE)
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.center = (x + len / 2, y + len / 2)
self.is_blocked = False
self.x_pos = x
self.y_pos = y
self.x = math.floor((x - Constants.PADTOPBOTTOM) / Constants.CELL_SIZE)
self.y = math.floor((y - Constants.PADLEFTRIGHT) / Constants.CELL_SIZE)
def update(self):
if self.is_blocked:
print("is update working?") # it is working.
self.image.fill(Constants.GREEN)
And when update() is called I want to change the color of the rectangle to green but it doesn't work.
I'm new to pygame and I don't know what to do.
Your problem is that you keep creating new sprites every frame, so when you change the color of one Rectangle to green, it will be covered by a white one.
You do this by calling the fill function (which creates new Rectangle instances) inside the drawGrid function.

Pygame Gun Rotating Up sight Down On The Left Side Problem

so I have this gun here where it rotates towards my mouse x, and mouse y position but the problem (VIDEO) is the gun turns upside down. Is there a way I could make the image of my gun not upside down and do the same movement as my gun facing the right side? Like the gun for the left side is upside down for some reason and I don't have an idea on how to do this at all
Gun Image:
my gun draw(self)
def draw(self,drawX,drawY):
self.rect.topleft = (drawX,drawY)
# the gun's hitbox
# rotating the gun
dx = self.look_at_pos[0] - self.rect.centerx
dy = self.look_at_pos[1] - self.rect.centery
angle = (180/math.pi) * math.atan2(-dy, dx)
gun_size = self.image.get_size()
pivot = (8, gun_size[1]//2)
blitRotate(window, self.image, self.rect.center, pivot, angle)
def lookAt( self, coordinate ):
self.look_at_pos = coordinate
my full gun class
class handgun():
def __init__(self,x,y,height,width,color):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = color
self.rect = pygame.Rect(x,y,height,width)
# LOL THESE IS THE HAND
self.shootsright = pygame.image.load("hands.png")
self.image = self.shootsright
self.rect = self.image.get_rect(center = (self.x, self.y))
self.look_at_pos = (self.x, self.y)
self.isLookingAtPlayer = False
self.look_at_pos = (x,y)
self.hitbox = (self.x + -18, self.y, 46,60)
def draw(self,drawX,drawY):
self.rect.topleft = (drawX,drawY)
# the gun's hitbox
# rotating the gun
dx = self.look_at_pos[0] - self.rect.centerx
dy = self.look_at_pos[1] - self.rect.centery
angle = (180/math.pi) * math.atan2(-dy, dx)
gun_size = self.image.get_size()
pivot = (8, gun_size[1]//2)
blitRotate(window, self.image, self.rect.center, pivot, angle)
def lookAt( self, coordinate ):
self.look_at_pos = coordinate
this is where the rotation happens like how my gun will rotate:
def blitRotate(surf, image, pos, originPos, angle):
# calcaulate the axis aligned bounding box of the rotated image
w, h = image.get_size()
sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle))
min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
# calculate the translation of the pivot
pivot = pygame.math.Vector2(originPos[0], -originPos[1])
pivot_rotate = pivot.rotate(angle)
pivot_move = pivot_rotate - pivot
# calculate the upper left origin of the rotated image
origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
# get a rotated image
rotated_image = pygame.transform.rotate(image, angle)
# rotate and blit the image
surf.blit(rotated_image, origin)
my full code: script
andy help is appreciated thank you!
You need to mirror the gun image once it turns past 90 degrees anti-clockwise from the positive x-axis.
You can do this using pygame.transform.flip() which takes in three parameters:
The surface objects (In this case, your gun image)
Whether the image should be flipped horizontally (No)
Whether the image should be flipped vertically (Yes)
So you'll want to have a variable which indicates where the gun is facing:
gunDirection = "right"
And also code which will flip the image whenever the gun changes direction:
if(angle > 90 and gunDirection != "left"):
gunDirection = "left"
self.image = pygame.transform.flip(self.image, False, True)
if(angle < 90 and gunDirection != "right"):
gunDirection = "right"
self.image = pygame.transform.flip(self.image, False, True)
This should all be in the gun class, if you're using radians the angle would be 0.5pi

Sprite Is Not Rotating Towards The Player Properly How Do I Fix This? [duplicate]

This question already has answers here:
How do I rotate an image around its center using Pygame?
(6 answers)
How do I make my player rotate towards mouse position?
(1 answer)
Closed 2 years ago.
I am trying to make my sprites rotate towards the player, but the rotation is out of place. Here is a video of the behaviour.
I am not sure how to fix this,
Sprite class
#-------------------------------- enemy shoots left and right
shotsright = pygame.image.load("canss.png")
class enemyshoot:
def __init__(self,x,y,height,width,color):
self.x = x
self.y =y
self.height = height
self.width = width
self.color = color
self.rect = pygame.Rect(x,y,height,width)
self.health = 10
self.hitbox = (self.x + -20, self.y + 30, 31, 57)
#-------------------------------------------------------
# Make a Reference Copy of the bitmap for later rotation
self.shootsright = pygame.image.load("canss.png")
self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))
self.image = self.shootsright
self.rect = self.image.get_rect()
self.position = pygame.math.Vector2( (200, 180) )
self.isLookingAtPlayer = False
def draw(self):
self.rect.topleft = (self.x,self.y)
window.blit(self.image, self.rect)
self.hits = (self.x + 20, self.y, 28,60)
pygame.draw.rect(window, (255,0,0), (self.hitbox[0], self.hitbox[1] - 60, 100, 10)) # NEW
pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 100 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 200, self.y + 200, 51, 65)
def lookAt( self, coordinate ):
# Rotate image to point in the new direction
delta_vector = coordinate - self.position
radius, angle = delta_vector.as_polar()
self.image = pygame.transform.rotate(self.shootsright, -angle)
# Re-set the bounding rectangle and position since
# the dimensions and centroid will have (probably) changed.
current_pos = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = current_pos
black = (0,0,0)
enemyshooting = []
platformGroup = pygame.sprite.Group
platformList = []
level = [" p p p p p ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",]
for iy, row in enumerate(level):
for ix, col in enumerate(row):
if col == "p":
new_platforms = enemyshoot(ix*10, iy*50, 10,10,(255,255,255))
enemyshooting.append(new_platforms)
this is the rotating part
class enemyshoot:
def __init__(self,x,y,height,width,color):
# [................]
#-------------------------------------------------------
# Make a Reference Copy of the bitmap for later rotation
self.shootsright = pygame.image.load("canss.png")
self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))
self.image = self.shootsright
self.rect = self.image.get_rect()
self.position = pygame.math.Vector2( (200, 180) )
def lookAt( self, coordinate ):
# Rotate image to point in the new direction
delta_vector = coordinate - pygame.math.Vector2(self.rect.center)
radius, angle = delta_vector.as_polar()
self.image = pygame.transform.rotate(self.shootsright, -angle)
# Re-set the bounding rectangle and position since
# the dimensions and centroid will have (probably) changed.
current_pos = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = current_pos
This is where I call the LookAt function to face the player:
# so instead of this
for enemyshoot in enemyshooting:
if not enemyshoot.isLookingAtPlayer:
enemyshoot.lookAt((playerman.x, playerman.y))
The rotation is out of place, and I can't figure out how to fix it. I am trying to make the mouth of the cannon rotate towards the player because that's where the bullets will append from.
I concur with everything #Rabbid76 says in the above answer.
I suspect part of your problem may be that the "human readable" part of the bitmap is not centered around the bitmap's centroid. Thus when rotated, it "sweeps" through an arc, rather than being rotated "around itself". (The preserving of the centre co-ordinate is an important step here to maintain a smooth rotation about the centroid of the object).
Consider the two bitmaps (the image on the right has a large 3/4 transparent section top-left):
Both are rotated around their centroid, but since the visible part on the 2nd image is not centred, it rotates weirdly.
So ensure your actual bitmap is centred within itself.
Reference Code:
import pygame
import random
# Window size
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 400
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
DARK_BLUE = ( 3, 5, 54 )
class RotationSprite( pygame.sprite.Sprite ):
def __init__( self, image, x, y, ):
pygame.sprite.Sprite.__init__(self)
self.original = image
self.image = image
self.rect = self.image.get_rect()
self.rect.center = ( x, y )
# for maintaining trajectory
self.position = pygame.math.Vector2( ( x, y ) )
self.velocity = pygame.math.Vector2( ( 0, 0 ) )
def lookAt( self, co_ordinate ):
# Rotate image to point in the new direction
delta_vector = co_ordinate - self.position
radius, angle = delta_vector.as_polar()
self.image = pygame.transform.rotozoom( self.original, -angle, 1 )
# Re-set the bounding rectagle
current_pos = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = current_pos
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption( "Rotation Example" )
### Missiles!
rotation_sprites = pygame.sprite.Group()
rotation_image1 = pygame.image.load( 'rot_offcentre_1.png' )
rotation_image2 = pygame.image.load( 'rot_offcentre_2.png' )
rotation_sprites.add( RotationSprite( rotation_image1, WINDOW_WIDTH//3, WINDOW_HEIGHT//2 ) )
rotation_sprites.add( RotationSprite( rotation_image2, 2*( WINDOW_WIDTH//3 ), WINDOW_HEIGHT//2 ) )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.MOUSEBUTTONUP ):
# On mouse-click
pass
# Record mouse movements for positioning the paddle
mouse_pos = pygame.mouse.get_pos()
for m in rotation_sprites:
m.lookAt( mouse_pos )
rotation_sprites.update()
# Update the window, but not more than 60 FPS
window.fill( DARK_BLUE )
rotation_sprites.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
self.position is set in the constructor, but it is not updated. Use the self.rect.center to compute the direction vector:
delta_vector = coordinate - pygame.math.Vector2(self.rect.center)
Use math.atan2 to compute the angle of rotation:
(See How do I make my player rotate towards mouse position?)
angle = (180 / math.pi) * math.atan2(-delta_vector.x, -delta_vector.y)
Setting the rectangle position in draw struggles with rotating the sprite in lookAt. Note the size of the rotated rectangle is enlarged. See How do I rotate an image around its center using Pygame?.
I recommend to set the look-at position in lookAt and to compute the rotated image in draw:
class enemyshoot:
def __init__(self,x,y,height,width,color):
# [...]
self.look_at_pos = (x, y)
def draw(self):
self.rect = self.shootsright.get_rect(topleft = (self.x, self.y))
dx = self.look_at_pos[0] - self.rect.centerx
dy = self.look_at_pos[1] - self.rect.centery
angle = (180 / math.pi) * math.atan2(-dx, -dy)
self.image = pygame.transform.rotate(self.shootsright, angle)
self.rect = self.image.get_rect(center = self.rect.center)
window.blit(self.image, self.rect)
self.hits = (self.x + 20, self.y, 28,60)
pygame.draw.rect(window, (255,0,0), (self.hitbox[0], self.hitbox[1] - 60, 100, 10)) # NEW
pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 100 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 200, self.y + 200, 51, 65)
def lookAt(self, coordinate):
self.look_at_pos = coordinate

Pygame collision detection not working with rotated image

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!

Categories

Resources