Pygame rotation sprite pivot on moving displayed surface [duplicate] - python

This question already has answers here:
How can you rotate an image around an off center pivot in Pygame
(1 answer)
Still Having Problems With Rotating Cannon's Properly Towards The Player Pygame
(1 answer)
How do I make image rotate with mouse python [duplicate]
(1 answer)
Closed 1 year ago.
so I tried to add the weapon to my game and rotate it on a pivot point looking to the mouse, I achieved to do it but I have an issue I can't resolve, when I walk with my player, after I walk down around the middle of the map the rotate angle looks to become wrong.
I link you a gif for a better understanding.
Here is some part of the working rotation code :
import pygame, os, math
from weapon_constants import *
from level_constants import *
from pygame.math import Vector2
class Weapon(pygame.sprite.Sprite):
def __init__(self, game, name, label, x, y):
self._layer = WEAPON_LAYER
self.groups = game.all_sprites, game.weapons
pygame.sprite.Sprite.__init__(self, self.groups)
self.options = WEAPONS[name]
self.label = label
self.game = game
self.file = os.path.join(os.getcwd(), DEFAULT_WEAPON_IMAGE_PATH, self.get_option('image'))
self.image = pygame.transform.scale(pygame.image.load(self.file).convert_alpha(), DEFAULT_WEAPON_SIZE)
self.original_image = self.image
self.rect = self.image.get_rect(center=(x, y))
self.offset = Vector2(25, 0)
self.pos = Vector2(self.rect.x, self.rect.y)
self.angle = 0
def get_option(self, option):
if option in self.options:
return self.options[option]
return False
def update(self):
self.pos = Vector2(self.game.player.rect.center)
rel_x, rel_y = pygame.mouse.get_pos() - self.pos
self.angle = (180 / math.pi) * math.atan2(rel_y, rel_x)
if self.pos.y > pygame.display.get_surface().get_width() // 2:
print('bad rotation happen \n pos_y : {}, \n angle : {}, \n mouse_pos : {}'.format(self.pos.y, self.angle, pygame.mouse.get_pos()))
pass
self.rotate()
def rotate(self):
self.image = pygame.transform.rotozoom(self.original_image, -self.angle, 1)
rotated_offset = self.offset.rotate(self.angle)
self.rect = self.image.get_rect(center= self.pos + rotated_offset)
Here is the first output (I saw it was around half width of the map) :
bad rotation happen
pos_y : 452.0,
angle : 23.962488974578186,
mouse_pos : (450, 468)

Related

How can I blit a random image at a random coordinate everytime collision occurs? [duplicate]

This question already has answers here:
How can i controll collision from sprites?
(1 answer)
How can I show explosion image when collision happens?
(2 answers)
Adding a particle effect to my clicker game
(1 answer)
Closed 4 months ago.
with my code right now, whenever my sprite collides with a Pokemon, the pokemon blits at random locations 3 - 5 times then just doesn't let me collide with it anymore. Sometimes the pokemon that blits is random sometimes its not.
Here is my pokemon code and my collision code:
class Pokemon:
def __init__(self, parent_screen):
self.pokemon = [
pygame.image.load('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/pokemon/bulbasaur.png').convert(),
pygame.image.load('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/pokemon/caterpie.png').convert(),
pygame.image.load('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/pokemon/charmander.png').convert(),
pygame.image.load('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/pokemon/pidgey.png').convert(),
pygame.image.load('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/pokemon/squirtle.png').convert()
]
self.parent_screen = parent_screen
self.x = SIZE
self.y = SIZE
self.random_pokemon = random.choice(self.pokemon)
self.set_colorkey()
def set_colorkey(self):
for i in range(len(self.pokemon)):
image = self.pokemon[i]
image.set_colorkey((0, 0, 0))
self.pokemon[i] = pygame.transform.scale(image, (150, 150))
def move(self):
self.parent_screen.blit(random.choice(self.pokemon), (self.x, self.y))
self.x = random.randint(1,25)*SIZE
self.y = random.randint(1,25)*SIZE
def draw_pokemon(self):
self.parent_screen.blit(self.random_pokemon, (self.x, self.y))
#MY COLLISION CODE:
score = 0
class Game:
def __init__(self):
pygame.init()
pygame.display.set_caption('Pokemon Catcher')
pygame.mixer.init()
self.surface = pygame.display.set_mode((700, 677))
self.background_music()
self.snake = Snake(self.surface)
self.snake.draw()
self.random_pokemon = Pokemon(self.surface)
self.pokemon = Pokemon(self.surface)
self.pokemon.draw_pokemon()
def collide(self,x1,y1,x2,y2):
distance = math.sqrt((math.pow(x2 - x1,2)) + (math.pow(y2-y1,2)))
if distance < 40:
return True
else:
return False
def play(self):
global score
self.render_background()
self.snake.walk()
self.pokemon.draw_pokemon()
self.display_score()
pygame.display.flip()
if self.collide(self.snake.x, self.snake.y, self.random_pokemon.x, self.random_pokemon.y):
score += 1
self.pokemon.move()
sound = pygame.mixer.Sound('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/music/cartoon eat.wav')
pygame.mixer.Sound.play(sound)
pygame.display.flip()
# collide with boundary
if self.snake.x <= -40 or self.snake.y <= -40 or self.snake.x >= 650 or self.snake.y >= 600:
sound = pygame.mixer.Sound('/Users/gersh/PycharmProjects/snakeeo/venv/lib/resources/music/bump.wav')
pygame.mixer.Sound.play(sound)
raise 'Hit the boundry error'

How Can I Rotate My Sprite Towards The Player Even If Player Moves Position

so I have a sprite that shoots projectiles the projectiles shoot at the player I was wondering how I could make the sprite rotate to the player? VIDEO the player bullets attack the player what ever position he is at but how could I make the cannon sprite do the same?
my cannon class
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.shootsright = pygame.image.load("canss.png")
self.shootsright = pygame.transform.scale(self.shootsright,(self.shootsright.get_width()-150,self.shootsright.get_height()-150))
self.rect = pygame.Rect(x,y,height,width)
self.health = 10
self.hitbox = (self.x + -20, self.y + 30, 31, 57)
def draw(self):
self.rect.topleft = (self.x,self.y)
window.blit(self.shootsright,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, 50, 10)) # NEW
pygame.draw.rect(window, (0,255,0), (self.hitbox[0], self.hitbox[1] - 60, 50 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 100, self.y + 200, 81, 87)
black = (0,0,0)
enemyshoots1 = enemyshoot(1100,10,100,100,black)
enemyshooting = [enemyshoots1]
my full code: script
Basically you can rotate the image to point to some co-ordinate quite simply. You create a vector of the distance between your cannon and the player. This is then converted to an angle with the Vector.as_polar() function. The angle is used to rotate an original copy of the bitmap to the desired angle. Image rotation can be fairly CPU-consuming.
class enemyshoot:
def __init__(self, x, y, height, width, color):
[...]
# Make a Reference Copy of the bitmap for later rotation
self.original = pygame.image.load("canss.png")
self.image = self.original
self.rect = self.image.get_rect()
self.position = pygame.math.Vector2( ( x, y ) )
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.rotozoom( self.original, -angle, 1 )
# 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
So the idea is you take the original bitmap, and rotate from that each time. If you keep rotating the same bitmap the slight differences will compound and the image will loose definition.
The other thing of note is that we're rotating around the bitmap's centre-point. But the rotation changes the bitmap's dimensions, also changing the centre-point. So this needs to be re-calculated and preserved.
You may find it useful to cache the rotated images once they have been rotated to save CPU. Maybe round the angle to the nearest 10 degrees, and then see if it's already been rotated. This would also allow you to pre-rotate all images and store them in a look-up table.
Edit: Calling lookAt() the mouse position:

pygame bullet physics messed up by scrolling

the code below is the bullet class for my shooter game in pygame. as you can see if you run the full game (https://github.com/hailfire006/economy_game/blob/master/shooter_game.py) the code works great to fire bullets at the cursor as long as the player isn't moving. However, I recently added scrolling, where I change a global offsetx and offsety every time the player gets close to an edge. These offsets are then used to draw each object in their respective draw functions.
unfortunately, my bullet physics in the bullet's init function no longer work as soon as the player scrolls and the offsets are added. Why are the offsets messing up my math and how can I change the code to get the bullets to fire in the right direction?
class Bullet:
def __init__(self,mouse,player):
self.exists = True
centerx = (player.x + player.width/2)
centery = (player.y + player.height/2)
self.x = centerx
self.y = centery
self.launch_point = (self.x,self.y)
self.width = 20
self.height = 20
self.name = "bullet"
self.speed = 5
self.rect = None
self.mouse = mouse
self.dx,self.dy = self.mouse
distance = [self.dx - self.x, self.dy - self.y]
norm = math.sqrt(distance[0] ** 2 + distance[1] ** 2)
direction = [distance[0] / norm, distance[1] / norm]
self.bullet_vector = [direction[0] * self.speed, direction[1] * self.speed]
def move(self):
self.x += self.bullet_vector[0]
self.y += self.bullet_vector[1]
def draw(self):
make_bullet_trail(self,self.launch_point)
self.rect = pygame.Rect((self.x + offsetx,self.y + offsety),(self.width,self.height))
pygame.draw.rect(screen,(255,0,40),self.rect)
You don't take the offset into account when calculating the angle between the player and the mouse. You can fix this by changing the distance like this:
distance = [self.dx - self.x - offsetx, self.dy - self.y - offsety]

Why pygame.draw.circle doesn't work in this code?

I have a ball object that waits one second in the middle of the screen before moving. This is the update method:
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self._draw_ball(rgb)
self._frame += 1
That one second happens below the else clause. My goal is to have the ball image go from black (8, 8, 8) to white (255, 255, 255) in that time but as it is _draw_ball doesn't do anything.
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
The funny things is, it works the first time when it's called in __init__. I've tried taking lines out of update and testing this code on its own in another module but can't figure out what's the problem. Why is pygame.draw.circle not drawing the the circles in the colors passed by the update method?
Here is the whole class:
#!python3
class Ball(pygame.sprite.Sprite):
def __init__(self, game, velocity):
super(Ball, self).__init__()
self.image = pygame.Surface((BALL_RADIUS*2, BALL_RADIUS*2))
self.image.fill(BLACK)
self.image.set_colorkey(BLACK, RLEACCEL)
self.rect = self.image.get_rect()
screen = pygame.display.get_surface()
self.area = screen.get_rect().inflate(-GAP*2, 0)
self.velocity = velocity
self.game = game
self.start_to_the = random.choice(['left', 'right'])
self._draw_ball(BALL_COLOR)
self.reinit()
def _draw_ball(self, rgb):
pygame.draw.circle(self.image, rgb, self.rect.center, BALL_RADIUS)
def _hit_topbottom(self):
return self.rect.top < self.area.top or self.rect.bottom > self.area.bottom
def _hit_leftright(self):
if self.rect.left < self.area.left: return 'left'
elif self.rect.right > self.area.right: return 'right'
else: return 0
def reinit(self):
self._spawn_time = pygame.time.get_ticks() / 1000
self._frame = 1
if self.start_to_the == 'left':
self.velocity = Vec2D(-BALL_SPEED, 0)
else:
self.velocity = Vec2D(BALL_SPEED, 0)
self.rect.center = self.area.center
def update(self, dt):
now = pygame.time.get_ticks() / 1000
if now - self._spawn_time >= BALL_WAIT_TIME:
self.rect = self.calcnewpos(dt)
self.handle_collision()
else:
step = 255 / FPS
value = int(self._frame * step)
rgb = (value, value, value)
self.image.fill(rgb)
self._frame += 1
def calcnewpos(self, dt):
(dx, dy) = self.velocity.x, self.velocity.y
return self.rect.move(dx, dy)
def handle_collision(self):
(dx, dy) = self.velocity.x, self.velocity.y
if self._hit_topbottom():
dy = -dy
elif self._hit_leftright():
side = self._hit_leftright()
self.game.enemy.update_hitpos()
self.game.increase_score(side)
if side == 'left': self.start_to_the = 'right'
elif side == 'right': self.start_to_the = 'left'
self.reinit()
return
else:
if self.hit_paddle():
paddle = self.hit_paddle()
paddle.handle_collision()
if paddle == self.game.paddles['left']:
self.rect.left = GAP + PADDLE_WIDTH
elif paddle == self.game.paddles['right']:
self.rect.right = SCREEN_WIDTH - (GAP + PADDLE_WIDTH)
dx = -dx
dy = (self.rect.centery - paddle.rect.centery)
dy = (math.copysign(min(abs(dy) // 16 * 16, 32), dy)) / 4
paddle.handle_collision()
self.velocity = Vec2D(dx, dy)
def hit_paddle(self):
paddles = self.game.paddles.values()
for paddle in paddles:
if self.rect.colliderect(paddle.rect): return paddle
I don't see any calls to pygame.display.flip. This is the function responsible for updating the screen with the current state of your display surface. It also doesn't look like you are redrawing your ball on the display surface. Somewhere, probably in update or _draw_ball there should be calls like the following:
self.screen.draw(self.image, self.rect)
pygame.display.flip()
The first line draws the image of the ball to the surface representing the screen, and the second call updates the screen to reflect the new surface.
My second theory is that you are drawing new frames of the ball outside of the bounds of self.image. This theory comes from seeing that are moving the ball's rect according to velocity, but always drawing a circle on self.image at self.rect's center. The size of self.image is only BALL_RADIUS*2, which makes it easy to draw outside of it if self.rect's topleft becomes something that's not (0,0). Even if this isn't your problem now, it will be later.
in pygame the draw circle statement is :
pygame.draw.circle (SURFACE, COLOUR, (X, Y), SIZE, 0)
if you put your screen.fill statement after the circle statement then it will draw the circle and immediately cover it up with the colour of the screen, making your circle disappear a 10000th of a second after its drawn.

Software Design and Development Major: Pygame Smudge Trails

First off, i have searched online and this website for solutions and the ones i have tried are not working so i decided to post my individual question and code. This program was created using Python 3.2.2 and the corresponding compatible version of pygame. I also realize a more efficient method would be to use sprites, sprite groups and 'dirty rect' updating but i unable to convert the program and so i will continue without the added benefits of such functions.
Problem: Smudge trails where the 'asteroids' are moving are left behind.
Hypothesis: Background is blitted onto the screen however the asteroids are blitted onto the Background.
Please Reply - btw i'm a highschooler from AUS :D
import pygame
import random
import math
pygame.init()
height = 550
width = 750
screen = pygame.display.set_mode((width, height))
background = pygame.image.load("Planet.jpg")
Clock = pygame.time.Clock()
class asteroid(pygame.sprite.Sprite):
def __init__(self, x, y, size):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.size = 15
self.speed = 0.0
self.angle = 0
self.colour = (171, 130, 255)
self.thickness = 0
def display(self):
pygame.draw.circle(background, self.colour, (int(self.x),int(self.y)), self.size, self.thickness)
pygame.draw.circle(background, (255, 255, 255), (int(self.x),int(self.y)), self.size, 1)
def move(self):
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
def boundaries(self):
if self.x > width - self.size:
self.x = 0 + self.size
elif self.x < self.size:
self.x = width - self.size
if self.y > height - self.size:
self.y = 0 + self.size
elif self.y <self.size:
self.y = height - self.size
num_target = 5
my_particles = []
num_particles = len(my_particles)
while num_particles < 5:
for n in range(num_target):
size = 20
x = random.randint(size, height - size)
y = random.randint(size, width - size)
target = asteroid(x, y, size)
target.speed = random.uniform(1.0, 1.0)
target.angle = random.uniform(0, math.pi*2)
my_particles.append(target)
num_particles = num_particles + 1
def main():
pygame.display.set_caption("Anyu's Game")
screen.blit(background, (0,0))
pygame.display.update()
score = (pygame.time.get_ticks()/1000)
print (score)
while True:
pygame.display.update()
screen.blit(background, (0,0))
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
target.display()
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit();
if __name__=='__main__':
main()
Basically, you are right! The circles are drawn directly onto the background, and everytime new circles are drawn, the old circles remain. Resulting in the smudges/trails.
You can just change background to screen in your draw method. This will fix it.
But it is really worth using the Sprite classes as intended. I've made a few changes to your code to switch it over for you. With these changes it runs without trails :)
Here are the changes and explainations:
Add this near the top:
#Create a new `pygame.Surface`, and draw a circle on it, then set transparency:
circle = pygame.Surface((30,30))
circle = circle.convert()
pygame.draw.circle(circle, (171, 130, 255), (int(15),int(15)), 15, 0)
circle.set_colorkey(circle.get_at((0, 0)), pygame.RLEACCEL)
Add this to the asteroid, __init__ method:
#Sets the asteroid image, and then the asteroids co-ords (these are in `rect`)
self.image = circle
self.rect = self.image.get_rect()
Add this to the end of def move(self):
self.rect[0] = self.x
self.rect[1] = self.y
change:
my_particles = []
to:
#This is a special pygame container class, it has a draw() method that tracks changed areas of the screen.
my_particles = pygame.sprite.RenderUpdates()
change:
my_particles.append(target)
to:
my_particles.add(target)
change:
while True:
pygame.display.update()
screen.blit(background, (0,0))
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
target.display()
pygame.display.update()
to:
#initial screen draw:
screen.blit(background, (0,0))
pygame.display.update()
while True:
#remove previous drawn sprites and replaces with background:
my_particles.clear(screen, background)
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
#draws changed sprites to the screen:
pygame.display.update(my_particles.draw(screen))
Remove the display method as it is no longer needed.
This will also run a lot faster than the your earlier code, as the time taken to draw something is proportional to the size of the drawing area, and previously it was drawing the whole background everytime - now it only draws the sprites and changes to the background!
Hope this helps :)
This already has an answer but this can be useful instead of other methods.
Make sure when you blit the images onto the screen, flip the display after blitting everything.
I would consider making a draw() function
Like this:
def draw(self):
# Blit images
self.screen.blit(image)
# Flip display
pygame.display.flip()
This will flip the display every frame and then draw the next frame without a trail.
Also quick notes, remember to do image = pygame.image.load(image).convert or .convert_alpha() else after adding more images the game will slow down.
Also, if you do import pygame as pg you don't have to type out pygame each time, instead you can just type pg.

Categories