Problem with rounding in pygame, using rotate_ip function - python

i have made a simple car game in pygame, w for acceleration, a and d for steering. However, Ive noticed that after some rotational motion, my sprite is going sideways while the image displays as if its going straight.
By printing velocity vector I have noticed that after some rotation and translation, value is no longer 0, but e-13/14. Could this be the problem? And if yes, how do I round it up, so the image actually follows the vector?
My code so far:
import pygame
import os
import sys
pygame.init()
screen_height = 750
screen_width = 1500
screen = pygame.display.set_mode((screen_width,screen_height))
robot = pygame.Surface((100,81), pygame.SRCALPHA)
robot.fill((255, 0, 0))
class Robot(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.robot_image = robot
self.image = self.robot_image
self.rect = self.image.get_rect(center=(100,355))
self.velocity = pygame.math.Vector2(1,0)
self.angle = 0
self.rotation = 5
self.state = 0
self.direction = 0
def update(self):
self.drive()
self.rotate()
def drive(self):
if self.state == 1:
self.rect.center += self.velocity * 6
if self.state == -1:
self.rect.center -= self.velocity * 6
def rotate(self):
if self.direction == 1:
self.angle -= self.rotation
self.velocity.rotate_ip(self.rotation)
if self.direction == -1:
self.angle += self.rotation
self.velocity.rotate_ip(-self.rotation)
self.rect = self.image.get_rect(center=self.rect.center)
self.image = pygame.transform.rotozoom(self.robot_image,self.angle,1)
rob = pygame.sprite.GroupSingle(Robot())
def main():
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
input = pygame.key.get_pressed()
if sum(pygame.key.get_pressed()) <= 1:
rob.sprite.state = 0
rob.sprite.direction = 0
if input[pygame.K_w]:
rob.sprite.state = 1
if input[pygame.K_s]:
rob.sprite.state = -1
if input[pygame.K_d]:
rob.sprite.direction = 1
if input[pygame.K_a]:
rob.sprite.direction = -1
screen.fill(0)
rob.draw(screen)
rob.update()
pygame.display.update()
main()

As you accumulate the rotation of the vector, a floating point precision error accumulates over time. I suggest calculating the velocity vector from the angle.
self.velocity = pygame.math.Vector2(1,0)
self.velocity.rotate_ip(-self.angle)
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data. The fraction part of the coordinates gets lost when the velocity vector is added to the position of the rectangle. If you want to store object positions with floating point accuracy, you have to store the location of the object in separate attribute and to synchronize the pygame.Rect object.
if self.state == 1:
self.position += self.velocity * 6
if self.state == -1:
self.position -= self.velocity * 6
self.rect.center = self.position
Complete class Robot:
class Robot(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.robot_image = robot
self.image = self.robot_image
self.rect = self.image.get_rect(center=(100,355))
self.position = self.rect.center
self.velocity = pygame.math.Vector2(1,0)
self.angle = 0
self.rotation = 5
self.state = 0
self.direction = 0
def update(self):
self.drive()
self.rotate()
def drive(self):
if self.state == 1:
self.position += self.velocity * 6
if self.state == -1:
self.position -= self.velocity * 6
self.rect.center = self.position
def rotate(self):
if self.direction == 1:
self.angle -= self.rotation
if self.direction == -1:
self.angle += self.rotation
self.velocity = pygame.math.Vector2(1,0)
self.velocity.rotate_ip(-self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
self.image = pygame.transform.rotozoom(self.robot_image,self.angle,1)

Related

Starting point for bullet including player rotation in Pygame

I'm making a simple topdown shooter game. The problem is with bullet starting point. I have player image where rifle barrel is between topmid and topright of rectangle. How to make bullet start always from the rifle barrel regardless image rotation?
# key input
def get_input(self, dt):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.direction.rotate_ip(dt * -360)
if keys[pygame.K_RIGHT]:
self.direction.rotate_ip(dt * 360)
self.angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
if keys[pygame.K_UP]:
self.movement = 1
self.status = 'move'
else:
self.movement = 0
self.status = 'idle'
if keys[pygame.K_DOWN]:
self.movement = -1
self.status = 'move'
if keys[pygame.K_SPACE] and not self.bullet.sprites():
self.create_bullet(dt)
shoot = pygame.mixer.Sound('audio/rumble.flac')
shoot.play()
# movement
def move(self, speed, dt):
movement_v = self.direction * self.movement
if movement_v.length() > 0:
movement_v.normalize_ip()
self.pos += movement_v * dt * speed
self.rect = self.image.get_rect(center=self.pos)
# boundary
if self.rect.x <= 0:
self.rect.x = 0
if self.rect.x >= 1216:
self.rect.x = 1216
if self.rect.y <= 0:
self.rect.y = 0
if self.rect.y >= 704:
self.rect.y = 704
# create bullet instance
def create_bullet(self, dt):
self.bullet.add(Bullet(self.rect.center, self.direction.normalize(), self.bullet_speed, self.angle))
You have rotate the offset of the bullet starting point with pygame.math.Vector2.rotate. e.g.:
offset = pygame.math.Vecotr2(15, 10) # (15, 10) is just an example
rotated_offset = offset.rotate(-self.angle)
pos = pygame.math.Vector2(self.rect.center) + rotated_offset
bullet = Bullet((pos.x, pos.y), ...)

Why my object doesn't rotate continuously?

Error : So, I'm having a logic error.
I want my object called 'Claw' rotate (range : 10-170 degrees) back and forth.
But, somehow when Claw reached the angle of 170, the value of angle didn't decrease. It sticks at the angle of 170.
Here is my Claw class
class Claw(pygame.sprite.Sprite):
def __init__(self,image,position):
super().__init__()
self.original_image = image #saved original image(unchangeable)
self.image = image
self.rect = image.get_rect(center = position)
self.offset = pygame.math.Vector2(default_offset_claw_x, 0)
self.position = position
self.direction = LEFT
self.angle = 10 #the first angle
self.angle_speed = 2.5
def update(self):
# rect_center = self.position + self.offset #mid point
# self.rect = self.image.get_rect(center = rect_center)
if self.direction == LEFT: #to the left
self.angle += self.angle_speed
elif self.direction == RIGHT:
self.angle -= self.angle_speed
#when it reaches an edge
if self.angle > 170:
self.angle = 170
self.directon = RIGHT
elif self.angle < 10:
self.angle = 10
self.direction = LEFT
print(self.angle, self.direction)
**Here is my game main variables**
default_offset_claw_x = 40
LEFT = -1
RIGHT = 1
Here is my main
#claw initialize
claw_image = pygame.image.load(os.path.join(current_path, "img\\claw.png"))
claw = Claw(claw_image, (screen_width//2, 110))
#game loop
running = True
while running:
clock.tick(30) # FPS = 30
# Look at every event in the queue
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.blit(background,(0,0))
gemstone_group.draw(screen) #draw all sprites in gemstone_group
claw.update()
claw.draw(screen)
pygame.display.update() #display update
pygame.quit()
There is missing an i in directon. It has to be self.direction = RIGHT. Anyway you can simplify the code:
class Claw(pygame.sprite.Sprite):
# [...]
def update(self):
self.angle += self.angle_speed * self.direction
if self.angle > 170 or self.angle < 10:
self.anlge = max(10, min(170, self.angle))
self.direction *= -1

Pygame diagonal movement not at correct angle

I would like my player to move diagonally at the same speed as it does when only moving horizontally or vertically. This does work when moving in a line with a negative gradient however, when moving along a positive gradient, the player doesn't move at a perfect 45 degree angle.
This is my code for moving the player:
def move(self, speed):
if self.direction.magnitude() != 0:
self.direction = self.direction.normalize()
self.rect.x += self.direction.x * speed
self.rect.y += self.direction.y * speed
As I said earlier, all I want is for the player to move diagonally at the same speed as it does in only the x or y direction.
Here is the full player class in case it is needed:
class Player:
def __init__(self, x, y):
self.image = pygame.Surface((30, 30))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect(center = (x, y))
self.direction = pygame.math.Vector2()
self.speed = 5
def input(self):
keys = pygame.key.get_pressed()
if keys[K_w]:
self.direction.y = -1
elif keys[K_s]:
self.direction.y = 1
else:
self.direction.y = 0
if keys[K_a]:
self.direction.x = -1
elif keys[K_d]:
self.direction.x = 1
else:
self.direction.x = 0
def move(self, speed):
if self.direction.magnitude() != 0:
self.direction = self.direction.normalize()
self.rect.x += self.direction.x * speed
self.rect.y += self.direction.y * speed
def update(self):
self.input()
self.move(self.speed)
def draw(self, screen):
screen.blit(self.image, self.rect.center)
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If you want to move the object with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:
class Player:
def __init__(self, x, y):
self.image = pygame.Surface((30, 30))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect(center = (x, y))
self.direction = pygame.math.Vector2()
self.speed = 5
self.position = pygame.math.Vector2(x, y)
def input(self):
keys = pygame.key.get_pressed()
dx = keys[K_d] - keys[K_a]
dy = keys[K_s] - keys[K_w]
self.direction = pygame.math.Vector2(dx, dy)
if dx != 0 and dy != 0:
self.direction /= 1.41421
def move(self, speed):
self.position += self.direction * speed
self.rect.x = round(self.position.x)
self.rect.y = round(self.position.y)
def update(self):
self.input()
self.move(self.speed)
def draw(self, screen):
screen.blit(self.image, self.rect.center)
See also Pygame doesn't let me use float for rect.move, but I need it and moving with a normalized vector in pygame inconsistent?.
Note, that pygame.math.Vector2.magnitude and math.Vector2.normalize are expensive for performance. Try to avoid this operations. Normalization of the vector is only required when moving along both axes. Since the absolute value of the vector is √2 in this case, the normalization can be replaced by dividing by √2.
dx = keys[K_d] - keys[K_a] # dx is -1, 0 or 1
dy = keys[K_s] - keys[K_w] # dy is -1, 0 or 1
self.direction = pygame.math.Vector2(dx, dy)
if dx != 0 and dy != 0:
self.direction /= 1.41421 # sqrt(2) is ~1.41421

How to make my sprite bounce off the boundaries in pygame

So basically I want to try to make the Sprite bounce off the boundaries of my pygame screen (1920, 1020) but it doesn't work along with the player movement. For me, either have the player movement or the bounce. The code below includes the player movement but not the bounce. But I want bottth... Any ideas? Thanks! Credits to Rabbid76 and Ann Zen.
Code:
import pygame
import os
import random
import math
import winsound
# winsound.PlaySound("explosion.wav", winsound.SND_ALIAS)
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (0, 30)
wn = pygame.display.set_mode((1920, 1020))
clock = pygame.time.Clock()
icon = pygame.image.load('Icon.png')
pygame.image.load('Sprite0.png')
pygame.image.load('Sprite0.png')
pygame.display.set_icon(icon)
pygame.display.set_caption('DeMass.io')
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
z = random.randint(1, 2)
if z == 2:
self.original_image = pygame.image.load('Sprite0.png')
else:
self.original_image = pygame.image.load('Sprite3.png')
self.image = self.original_image
self.rect = self.image.get_rect(center=(x, y))
self.direction = pygame.math.Vector2((0, -1))
self.velocity = 5
self.position = pygame.math.Vector2(x, y)
def point_at(self, x, y):
self.direction = pygame.math.Vector2(x, y) - self.rect.center
if self.direction.length() > 0:
self.direction = self.direction.normalize()
angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
def move(self, x, y):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def reflect(self, NV):
self.direction = self.direction.reflect(pygame.math.Vector2(NV))
def update(self):
self.position += self.direction * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def hit(self, player):
distance = math.sqrt(math.pow(self.xcor() - player.xcor(), 2) + math.pow(self.ycor() - player.ycor(), 2))
if distance < 20:
return True
else:
return False
player = Player(200, 200)
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
elif event.type == pygame.MOUSEMOTION:
player.point_at(*event.pos)
keys = pygame.key.get_pressed()
if keys[pygame.K_w] or keys[pygame.K_UP]:
player.move(0, -1)
wn.fill((255, 255, 255))
all_sprites.draw(wn)
pygame.display.update()
If you want to restrict the player to a rectangular area, you must restrict the player's position and rect attributes. Add an additional argument clamp_rect to the method move. If the player's position exceeds the boundaries of the rectangle, move the player inside the rectangle:
class Player(pygame.sprite.Sprite):
# [...]
def move(self, x, y, clamp_rect):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
if self.rect.left < clamp_rect.left:
self.rect.left = clamp_rect.left
self.position.x = self.rect.centerx
if self.rect.right > clamp_rect.right:
self.rect.right = clamp_rect.right
self.position.x = self.rect.centerx
if self.rect.top < clamp_rect.top:
self.rect.top = clamp_rect.top
self.position.y = self.rect.centery
if self.rect.bottom > clamp_rect.bottom:
self.rect.bottom = clamp_rect.bottom
self.position.y = self.rect.centery
Pass the window rectangle (wn.get_rect()) to move:
while True:
# [...]
keys = pygame.key.get_pressed()
if keys[pygame.K_w] or keys[pygame.K_UP]:
player.move(0, -1, wn.get_rect())
# [...]
make a bounce animation. define your boundaries and when you hit a wall make the animation run. (for this example you have to predefine hit_wall and have an if statement to check if it is on your boundaries)
Example:
if hit_wall:
animate(direction)
def animate(direction):
if direction == (0, 1):
/code to make it bounce the opposite direction
elif direction == (0, -1):
/code to make it bounce the opposite direction
elif direction == (1, 0):
/code to make it bounce the opposite direction
elif direction == (-1, 0):
/code to make it bounce the opposite direction

How to animate scale and rotate in pygame using rotozoom [duplicate]

This question already has answers here:
How to rotate an image around its center while its scale is getting larger(in Pygame)
(2 answers)
Closed 2 years ago.
Okay so Im just trying to get it working with rotations first before scaling then once I nail that the rotozoom should be easy. For the life of me I cant seem to get it working. Heres a simple class that I want the object to rotate over time as well a transform in the x direction. The transform is working fine but i just cant get it to rotate.
class Puff(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
try:
self.imagePuff = pygame.image.load(os.path.join("Images","puff.tga")).convert()
except:
raise UserWarning, "Unable to find Puff image"
self.imagePuff.set_colorkey((0,0,255))
self.image = self.imagePuff
self.rect = self.image.get_rect()
self.x = self.screen.get_width()/3
self.y = self.screen.get_height()/3+10
self.rect.center = (self.x, self.y)
self.lifespan = 60
self.speed = 1
self.count = 0
self.angle = 0
def update(self):
self.calcPos()
self.rect.center = self.x, self.y
self.transform()
if self.count > self.lifespan:
self.kill()
def calcPos(self):
self.x -= 5
self.y = self.y
def transform(self):
self.count += 1.1
self.angle += self.count*25
self.newTrans = pygame.transform.scale(self.copyImage, (400,400))
self.newRect = self.newTrans.get_rect()
self.newRect.center = self.rect.center
This is how I've made it so you can rotate your puff class:
import pygame
class Puff(pygame.sprite.Sprite):
def __init__(self, screen):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
try:
self.imagePuff = pygame.image.load("puff.jpg").convert()
except:
raise UserWarning, "Unable to find Puff image"
self.image = self.imagePuff
self.imagePuff.set_colorkey((0,0,255))
self.rect = self.image.get_rect()
self.x = self.screen.get_width()/3
self.y = self.screen.get_height()/3+10
self.rect.center = (self.x, self.y)
self.lifespan = 60
self.speed = 1
self.count = 0
self.angle = 0
def update(self):
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imagePuff, self.angle)
self.rect = self.image.get_rect()
self.rect.center = oldCenter
def calcPos(self):
self.x -= 5
self.y = self.y
def turnLeft(self):
self.angle = (self.angle + 45) % 360
def turnRight(self):
self.angle = (self.angle - 45) % 360
if __name__ == "__main__":
pygame.init()
screen = pygame.display.set_mode((400,300))
clock = pygame.time.Clock()
background = pygame.Surface(screen.get_size())
background.fill((0, 0, 0))
pygame.display.set_caption("Spinning sprite!!!")
ball = Puff(screen)
sprites = pygame.sprite.Group()
sprites.add(ball)
keepGoing = True
while keepGoing:
for event in pygame.event.get():
if event.type == pygame.QUIT:
keepGoing = False
ball.turnRight()
sprites.clear(screen, background)
sprites.update()
sprites.draw(screen)
pygame.display.flip()
clock.tick(30)
Essentially the key points are:
self.image = pygame.transform.rotate(self.imagePuff, self.angle)
In the update method, and the two methods for changing the angle:
def turnLeft(self):
self.angle = (self.angle + 45) % 360
def turnRight(self):
self.angle = (self.angle - 45) % 360
In the code above every clock tick the sprite is rotated to the right:
ball.turnRight()
As you omitted your transform method, I had to edit the code to show how to do just rotations.
Hope this helps and if you need any more help please leave a comment.
Edit:
if you want to rotate and move to the left then you only really need to add this:
def update(self):
self.calcPos() # This moves the position - 5 in the x direction
self.rect.center = self.x, self.y
self.rotate() # This calls the bit to change the angle
if self.count > self.lifespan:
self.kill()
oldCenter = self.rect.center
self.image = pygame.transform.rotate(self.imagePuff, self.angle) # This rotates
self.rect = self.image.get_rect()
self.rect.center = oldCenter
def calcPos(self):
self.x -= 5
def rotate(self):
self.angle = (self.angle + 45) % 360
You should check out:
http://www.gamedev.net/topic/444490-pygame-easy-as-py/
If you search for "rotate.py" you'll see the a section of code on rotating sprites
EDIT:
Try changing you transform method to:
def transform(self):
self.count += 1.1
self.angle += (self.count*25) % 360
self.copyImage = pygame.transform.rotate(self.imagePuff, self.angle)
self.newTrans = pygame.transform.scale(self.copyImage, (400,400))
self.image = self.newTrans

Categories