In pygame I have an image object of a frigate at 0 degrees of rotation. On it i have turrets, i need to calculate their new position if the frigate rotates by say 90 degrees.
After rotating the image like so,
rotatedFrigate = pygame.transform.rotate(gui.frigate[0], facingAngle)
I have tried various ways such as rotating the point,
point = pygame.math.Vector2(turretx, turretY)
rotated_point = point.rotate(facingAngle)
Even adding on the original x,y coords still has it far off
t1x,t1y = rotated_point[0]+point[0], rotated_point[1]+point[1]
I have also tried rotation matrix approach using midpoint and adding new adjusted dims.
xm,ym = self.x + 0.5*self.w,self.y + 0.5*self.h
a = math.radians(facingAngle) # Convert to radians
xr = (x - xm) * math.cos(a) - (y - ym) * math.sin(a) + xm
yr = (x - xm) * math.sin(a) + (y - ym) * math.cos(a) + ym
rotatedFrigate = pygame.transform.rotate(gui.frigate[0], facingAngle)
t1x,t1y = xr + 0.5*rotatedFrigate.get_width(),yr+ 0.5*rotatedFrigate.get_height()
For the turret :
turretx, turretY = self.x,self.y+0.05*self.h
Self refers to the frigate coords prior to rotation
Frigate image center coordinates are calculated using
xm,ym = self.x + 0.5*self.w,self.y + 0.5*self.h
Where w & h are used on the frigate image get_width() get_height() methods.
Again prior to rotation.
Both approaches don't seem to work, sometimes they are close but most of the times they are far out.
Additional Info
Picture is if i use rotated_point = (point - pivot).rotate(-facingAngle) + pivot
I suggest to use pygame.math.Vector2.rotate(). The following works if the image is rotated around a pivot. See How do I rotate an image around its center using PyGame? and How to set the pivot point (center of rotation) for pygame.transform.rotate()? .
Calculate the vector from the pivot to the point
Rotate the vector with pygame.math.Vector2.rotate()
Add the pivot to the rotated vector
point = pygame.math.Vector2(turretx, turretY)
pivot = pygame.math.Vector2(self.x + 0.5*self.w, self.y + 0.5*self.h)
rotated_point = (point - pivot).rotate(-facingAngle) + pivot
t1x, t1y = rotated_point.x, rotated_point.y
Note that you need to rotate the vector by the negative angle. While pygame.transform.rotate works clockwise, pygame.math.Vector2.rotate() works counterclockwise.
In the following minimal example the pivot point is marked with the blue cross and the rotating point with the green cross. The rotated vector is the blue line between the blue cross and the green cross.
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
image_size = (160, 80)
point_on_image = (130, 40)
image = pygame.Surface(image_size, pygame.SRCALPHA)
pygame.draw.ellipse(image, "gray", (0, 0, *image_size))
pygame.draw.circle(image, "red", point_on_image, 10)
angle = 0
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window_center = window.get_rect().center
rotated_image = pygame.transform.rotate(image, angle)
px = window_center[0] - image_size[0]//2 + point_on_image[0]
py = window_center[1] - image_size[1]//2 + point_on_image[1]
point = pygame.math.Vector2(px, py)
pivot = pygame.math.Vector2(window_center)
rotated_point = (point - pivot).rotate(-angle) + pivot
window.fill("white")
window.blit(rotated_image, rotated_image.get_rect(center = window_center))
pygame.draw.line(window, "blue", (window_center[0]-15, window_center[1]), (window_center[0]+15, window_center[1]), 3)
pygame.draw.line(window, "blue", (window_center[0], window_center[1]-15), (window_center[0], window_center[1]+15), 3)
pygame.draw.line(window, "green", (rotated_point[0]-15, rotated_point[1]), (rotated_point[0]+15, rotated_point[1]), 3)
pygame.draw.line(window, "green", (rotated_point[0], rotated_point[1]-15), (rotated_point[0], rotated_point[1]+15), 3)
pygame.draw.line(window, "blue", window_center, rotated_point, 3)
pygame.display.flip()
angle +=1
pygame.quit()
exit()
If you do not rotate the image around its center, but only keep the position at the top left, you must:
Calculate the vector from the center of the original image to the point
Rotate the vector with pygame.math.Vector2.rotate()
Add the center of the rotated image to the rotated vector
point = pygame.math.Vector2(turretx, turretY)
pivot = pygame.math.Vector2(self.x + 0.5*self.w, self.y + 0.5*self.h)
rotatedFrigate = pygame.transform.rotate(gui.frigate[0], facingAngle)
new_pivot = pygame.math.Vector2(
self.x + 0.5 * rotatedFrigate.get_width(),
self.y + 0.5 * rotatedFrigate.get_height())
rotated_point = (point - pivot).rotate(-facingAngle) + new_pivot
t1x, t1y = rotated_point.x, rotated_point.y
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
image_size = (160, 80)
point_on_image = (130, 40)
image = pygame.Surface(image_size, pygame.SRCALPHA)
pygame.draw.ellipse(image, "gray", (0, 0, *image_size))
pygame.draw.circle(image, "red", point_on_image, 10)
angle = 0
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window_center = window.get_rect().center
image_rect = image.get_rect(center = window_center)
rotated_image = pygame.transform.rotate(image, angle)
px = window_center[0] - image_size[0]//2 + point_on_image[0]
py = window_center[1] - image_size[1]//2 + point_on_image[1]
point = pygame.math.Vector2(px, py)
pivot = pygame.math.Vector2(window_center)
rotated_image_rect = rotated_image.get_rect(topleft = image_rect.topleft)
rotated_image_center = rotated_image_rect.center
rotated_point = (point - pivot).rotate(-angle) + rotated_image_center
window.fill("white")
window.blit(rotated_image, rotated_image_rect)
pygame.draw.rect(window, "black", rotated_image_rect, 3)
pygame.draw.line(window, "blue", (rotated_image_center[0]-15, rotated_image_center[1]), (rotated_image_center[0]+15, rotated_image_center[1]), 3)
pygame.draw.line(window, "blue", (rotated_image_center[0], rotated_image_center[1]-15), (rotated_image_center[0], rotated_image_center[1]+15), 3)
pygame.draw.line(window, "green", (rotated_point[0]-15, rotated_point[1]), (rotated_point[0]+15, rotated_point[1]), 3)
pygame.draw.line(window, "green", (rotated_point[0], rotated_point[1]-15), (rotated_point[0], rotated_point[1]+15), 3)
pygame.draw.line(window, "blue", rotated_image_center, rotated_point, 3)
pygame.display.flip()
angle +=1
pygame.quit()
exit()
Related
I'm doing a pygame project for practice and I need a sprite to move to some point on screen and I did it, but it moves in a straight line and I would like to learn how to make it move to the same point in a curve.
def move_to_point(self, dest_rect, speed, delta_time):
# Calculates relative rect of dest
rel_x = self.rect.x - dest_rect[0]
rel_y = self.rect.y - dest_rect[1]
# Calculates diagonal distance and angle from entity rect to destination rect
dist = math.sqrt(rel_x**2 + rel_y**2)
angle = math.atan2( - rel_y, - rel_x)
# Divides distance to value that later gives apropriate delta x and y for the given speed
# there needs to be at least +2 at the end for it to work with all speeds
delta_dist = dist / (speed * delta_time) + 5
print(speed * delta_time)
# If delta_dist is greater than dist entety movement is jittery
if delta_dist > dist:
delta_dist = dist
# Calculates delta x and y
delta_x = math.cos(angle) * (delta_dist)
delta_y = math.sin(angle) * (delta_dist)
if dist > 0:
self.rect.x += delta_x
self.rect.y += delta_y
This movement looks like
and I would like it to be like
There are many many ways to achieve what you want. One possibility is a Bézier curve:
def bezier(p0, p1, p2, t):
px = p0[0]*(1-t)**2 + 2*(1-t)*t*p1[0] + p2[0]*t**2
py = p0[1]*(1-t)**2 + 2*(1-t)*t*p1[1] + p2[1]*t**2
return px, py
p0, p1 and p2 are the control points and t is a value in the range [0,0, 1,0] indicating the position along the curve. p0 is the start of the curve and p2 is the end of the curve. If t = 0, the point returned by the bezier function is equal to p0. If t=1, the point returned is equal to p2.
Also see PyGameExamplesAndAnswers - Shape and contour - Bezier
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
def bezier(p0, p1, p2, t):
px = p0[0]*(1-t)**2 + 2*(1-t)*t*p1[0] + p2[0]*t**2
py = p0[1]*(1-t)**2 + 2*(1-t)*t*p1[1] + p2[1]*t**2
return px, py
dx = 0
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pts = [(100, 100), (100, 400), (400, 400)]
window.fill(0)
for p in pts:
pygame.draw.circle(window, (255, 255, 255), p, 5)
for i in range(101):
x, y = bezier(*pts, i / 100)
pygame.draw.rect(window, (255, 255, 0), (x, y, 1, 1))
p = bezier(*pts, dx / 100)
dx = (dx + 1) % 101
pygame.draw.circle(window, (255, 0, 0), p, 5)
pygame.display.update()
pygame.quit()
exit()
I'm trying to make a white rectangle rotate like a the hands of a clock in pygame using this code,
import random, pygame, math, sys
from pygame.locals import *
Blue = (0,0,255)
Black = (0, 0, 0)
Green = (0,255,0)
White = (255,255,255)
pygame.init()
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Sailing!')
FPS = 30
fpsClock = pygame.time.Clock()
Sail = pygame.Surface([100,10])
Sail.set_colorkey (Black)
Sail.fill(White)
degrees = 0
hyp = 100
x = 200
y = 150
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
adj = 10 * math.cos(degrees)
opp = 10 * math.sin(degrees)
dx = adj + 200
dy = opp + 150
rotatedSail = pygame.transform.rotate(Sail, degrees)
Sail_rect = Sail.get_rect(topleft = (dx, dy))
DISPLAYSURF.fill(Blue)
DISPLAYSURF.blit(rotatedSail, Sail_rect)
pygame.display.flip()
fpsClock.tick(FPS)
degrees += 1
but the rectangle rotates in a weird way. I would appreciate it if you could keep the suggestion as simple and as close to my code as possible, because I'm just starting to learn. Plus i know it's easier to do it using an image of a rectangle, but I'm trying to use a surface.
can anyone help?
You need to get the bounding rectangle of the rotated rectangle and set the center of the rectangle by (x, y) (see also How do I rotate an image around its center using PyGame?):
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
rotatedSail = pygame.transform.rotate(Sail, degrees)
rotatedSail_rect = rotatedSail.get_rect(center = (x, y))
DISPLAYSURF.fill(Blue)
DISPLAYSURF.blit(rotatedSail, rotatedSail_rect)
pygame.display.flip()
fpsClock.tick(FPS)
degrees += 1
To rotate the object around another point than the center point is much more complicate. A general solution is described in the answer to How can you rotate an image around an off center pivot in PyGame.
Complete Example:
import pygame, math, sys
from pygame.locals import *
Blue = (0,0,255)
Black = (0, 0, 0)
Green = (0,255,0)
White = (255,255,255)
pygame.init()
DISPLAYSURF = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Sailing!')
FPS = 30
fpsClock = pygame.time.Clock()
Sail = pygame.Surface([100,10])
Sail.set_colorkey (Black)
Sail.fill(White)
degrees = 0
hyp = 100
x = 200
y = 150
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)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAYSURF.fill(Blue)
blitRotate(DISPLAYSURF, Sail, (x, y), (0, 5), degrees)
pygame.display.flip()
fpsClock.tick(FPS)
degrees += 1
If a sprite is in the centre of a circle at the point 250,250 in pygame what is the equation to find the edge of the circle in any direction relative to the original point. Could the equation have the angle (as X) in the equation?
The general formula is (x, y) = (cx + r * cos(a), cy + r * sin(a)).
However, in your case ° is at the top and the angle increases clockwise. Therefore the formula is:
angle_rad = math.radians(angle)
pt_x = cpt[0] + radius * math.sin(angle_rad)
pt_y = cpt[1] - radius * math.cos(angle_rad)
Alternatively, you can use the pygame.math module and pygame.math.Vector2.rotate:
vec = pygame.math.Vector2(0, -radius).rotate(angle)
pt_x, pt_y = cpt[0] + vec.x, cpt[1] + vec.y
Minimal example:
import pygame
import math
pygame.init()
window = pygame.display.set_mode((500, 500))
font = pygame.font.SysFont(None, 40)
clock = pygame.time.Clock()
cpt = window.get_rect().center
angle = 0
radius = 100
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# solution 1
#angle_rad = math.radians(angle)
#pt_x = cpt[0] + radius * math.sin(angle_rad)
#pt_y = cpt[1] - radius * math.cos(angle_rad)
# solution 2
vec = pygame.math.Vector2(0, -radius).rotate(angle)
pt_x, pt_y = cpt[0] + vec.x, cpt[1] + vec.y
angle += 1
if angle >= 360:
angle = 0
window.fill((255, 255, 255))
pygame.draw.circle(window, (0, 0, 0), cpt, radius, 2)
pygame.draw.line(window, (0, 0, 255), cpt, (pt_x, pt_y), 2)
pygame.draw.line(window, (0, 0, 255), cpt, (cpt[0], cpt[1]-radius), 2)
text = font.render(str(angle), True, (255, 0, 0))
window.blit(text, text.get_rect(center = cpt))
pygame.display.flip()
pygame.quit()
exit()
Below is the code I wrote.
The object moves along a circular path when I constantly calculate its position and give the coordinates to the obj.rect.x and object.rect.y.
What I need to know is how to rotate the object by something like below.
obj.rect.x += incrementx
obj.rect.y += incrementy
I implemented this in my code bu then the motion becomes anything but circluar.
Please help.
The two images used are here.
http://s5.postimg.org/fs4adqqib/crate_B.png
http://s5.postimg.org/vevjr44ab/plt0.png
import sys, os, pygame
from math import sin,cos,pi, radians
from pygame.locals import *
from standard_object_creator import *
SCREENW = 800
SCREENH = 700
BLUE = (0, 50, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
PURPLE = (145, 0, 100)
YELLOW = (220,220, 0)
pygame.init()
FPSCLOCK = pygame.time.Clock()
FONT1= "data\Cookie-Regular.ttf"
if sys.platform == 'win32' or sys.platform == 'win64':
#os.environ['SDL_VIDEO_CENTERED'] = '2'# center of screen
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (10,30)#top left corner
SCREEN = pygame.display.set_mode((SCREENW, SCREENH))
## self, imagelist, posx, posy, speedx = 0, speedy = 0, value = 0
plat = pygame.image.load("grfx\plt0.png").convert_alpha()
box = pygame.image.load("grfx\crateB.png").convert_alpha()
FPS = 160 # frames per second
platforms = pygame.sprite.Group()
boxes = pygame.sprite.Group()
def maketext(msg,fontsize, colour = YELLOW, font = FONT1):
mafont = pygame.font.Font(font, fontsize)
matext = mafont.render(msg, True, colour)
matext = matext.convert_alpha()
return matext
box = object_factory ([box], 340, 50, 0, 1)
boxes.add(box)
center_x = 450 # x pos in relation to screen width
center_y = 400 # y pos in relation to screen height
radius = 200
angle = -90 #pi / 4 # starting angle 45 degrees
omega = .001 #Angular velocity
for x in xrange(6):
xpos = radius * cos(angle) #+ center_x #Starting position x
ypos = radius * sin(angle) #+ center_x #Startinh position y
obj = object_factory([plat], xpos, ypos)
obj.angle = angle
obj.omega = omega #angula velocity
obj.radius = radius
platforms.add(obj)
angle += 60
mouseposlist = []
all2gether = [platforms, boxes]
while True:
SCREEN.fill(BLACK)
## MOVE THE SPRITE IN A CIRCLE. Each object is placed by varying the step)
for obj in platforms:
obj.angle = obj.angle + obj.omega
## THE CODE BELOW WORKS
obj.rect.x = center_x + (cos(obj.angle) * obj.radius)
obj.rect.y = center_y + (sin(obj.angle) * obj.radius)
## How can I get the same thing to work in this way? by adding the rate of change to the box objects rect.x and rec.t? Why does this not work?
#obj.rect.x += obj.radius * obj.omega * cos(obj.angle)
#obj.rect.y -= obj.radius * obj.omega * sin(obj.angle)
pygame.draw.line(SCREEN, BLUE, (center_x, center_y), (obj.rect.x, obj.rect.y), 2)
for hp in boxes:
hp.rect.x += hp.speedx
hp.rect.y += hp.speedy
hp.move()
hp.collide(platforms)
for thing in all2gether:
thing.update()
thing.draw(SCREEN)
pygame.draw.line(SCREEN, BLUE, (0, SCREENH / 2), (SCREENW, SCREENH / 2), 2)
pygame.draw.line(SCREEN, BLUE, (SCREENW / 2, 0), (SCREENW / 2, SCREENH), 2)
pygame.display.update()
FPSCLOCK.tick(FPS)
##--------------------------------------------------------------
pygame.event.pump()
keys = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
if event.button == 1:
pos = pygame.mouse.get_pos()
val = [pos[0], pos[1], 0, 0]
print val
mouseposlist.append(val)
elif event.button == 3 and mouseposlist != []:
mouseposlist.pop(-1)
if event.type == KEYDOWN and event.key == K_ESCAPE:
print mouseposlist
pygame.quit()
sys.exit()
pygame.time.wait(0)
Your solution for moving the sprite in a circle is the time evaluation of the positional equation. You need to calculate the angle as a function of time. x = r * cos (omega * time). your first solution is a loop on time, incrementing omega by the fractional angle that is provided by the angular velocity. To evaluate a position take the amount of time multiplied by the angular velocity....
I manged to solve my problem and would like to share it. The new code is given below.
This works with Python / Pygame
center_of_rotation_x = SCREENW/2
center_of_rotation_y = SCREENH/2
radius = 200
angle = radians(45) #pi/4 # starting angle 45 degrees
omega = 0.1 #Angular velocity
x = center_of_rotation_x + radius * cos(angle) #Starting position x
y = center_of_rotation_y - radius * sin(angle) #Starting position y
SCREEN.blit(star, (x, y)) # Draw current x,y
angle = angle + omega # New angle, we add angular velocity
x = x + radius * omega * cos(angle + pi / 2) # New x
y = y - radius * omega * sin(angle + pi / 2) # New y
The above code works as it is. But when applied as a class it works differently. I will ask that in another question
I want to rotate a rectangle about a point other than the center. My code so far is:
import pygame
pygame.init()
w = 640
h = 480
degree = 45
screen = pygame.display.set_mode((w, h))
surf = pygame.Surface((25, 100))
surf.fill((255, 255, 255))
surf.set_colorkey((255, 0, 0))
bigger = pygame.Rect(0, 0, 25, 100)
pygame.draw.rect(surf, (100, 0, 0), bigger)
rotatedSurf = pygame.transform.rotate(surf, degree)
screen.blit(rotatedSurf, (400, 300))
running = True
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
I can change the degree to get different rotation but the rotation is about the center. I want to set a point other than the center of the rectangle as the rotation point.
To rotate a surface around its center, we first rotate the image and then get a new rect to which we pass the center coordinates of the previous rect to keep it centered. To rotate around an arbitrary point, we can do pretty much the same, but we also have to add an offset vector to the center position (the pivot point) to shift the rect. This vector needs to be rotated each time we rotate the image.
So we have to store the pivot point (the original center of the image or sprite) - in a tuple, list, vector or a rect - and the offset vector (the amount by which we shift the rect) and pass them to the rotate function. Then we rotate the image and offset vector, get a new rect, pass the pivot + offset as the center argument and finally return the rotated image and the new rect.
import pygame as pg
def rotate(surface, angle, pivot, offset):
"""Rotate the surface around the pivot point.
Args:
surface (pygame.Surface): The surface that is to be rotated.
angle (float): Rotate by this angle.
pivot (tuple, list, pygame.math.Vector2): The pivot point.
offset (pygame.math.Vector2): This vector is added to the pivot.
"""
rotated_image = pg.transform.rotozoom(surface, -angle, 1) # Rotate the image.
rotated_offset = offset.rotate(angle) # Rotate the offset vector.
# Add the offset vector to the center/pivot point to shift the rect.
rect = rotated_image.get_rect(center=pivot+rotated_offset)
return rotated_image, rect # Return the rotated image and shifted rect.
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
# The original image will never be modified.
IMAGE = pg.Surface((140, 60), pg.SRCALPHA)
pg.draw.polygon(IMAGE, pg.Color('dodgerblue3'), ((0, 0), (140, 30), (0, 60)))
# Store the original center position of the surface.
pivot = [200, 250]
# This offset vector will be added to the pivot point, so the
# resulting rect will be blitted at `rect.topleft + offset`.
offset = pg.math.Vector2(50, 0)
angle = 0
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
keys = pg.key.get_pressed()
if keys[pg.K_d] or keys[pg.K_RIGHT]:
angle += 1
elif keys[pg.K_a] or keys[pg.K_LEFT]:
angle -= 1
if keys[pg.K_f]:
pivot[0] += 2
# Rotated version of the image and the shifted rect.
rotated_image, rect = rotate(IMAGE, angle, pivot, offset)
# Drawing.
screen.fill(BG_COLOR)
screen.blit(rotated_image, rect) # Blit the rotated image.
pg.draw.circle(screen, (30, 250, 70), pivot, 3) # Pivot point.
pg.draw.rect(screen, (30, 250, 70), rect, 1) # The rect.
pg.display.set_caption('Angle: {}'.format(angle))
pg.display.flip()
clock.tick(30)
pg.quit()
Here's a version with a pygame.sprite.Sprite:
import pygame as pg
from pygame.math import Vector2
class Entity(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pg.Surface((122, 70), pg.SRCALPHA)
pg.draw.polygon(self.image, pg.Color('dodgerblue1'),
((1, 0), (120, 35), (1, 70)))
# A reference to the original image to preserve the quality.
self.orig_image = self.image
self.rect = self.image.get_rect(center=pos)
self.pos = Vector2(pos) # The original center position/pivot point.
self.offset = Vector2(50, 0) # We shift the sprite 50 px to the right.
self.angle = 0
def update(self):
self.angle += 2
self.rotate()
def rotate(self):
"""Rotate the image of the sprite around a pivot point."""
# Rotate the image.
self.image = pg.transform.rotozoom(self.orig_image, -self.angle, 1)
# Rotate the offset vector.
offset_rotated = self.offset.rotate(self.angle)
# Create a new rect with the center of the sprite + the offset.
self.rect = self.image.get_rect(center=self.pos+offset_rotated)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
entity = Entity((320, 240))
all_sprites = pg.sprite.Group(entity)
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
keys = pg.key.get_pressed()
if keys[pg.K_d]:
entity.pos.x += 5
elif keys[pg.K_a]:
entity.pos.x -= 5
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.draw.circle(screen, (255, 128, 0), [int(i) for i in entity.pos], 3)
pg.draw.rect(screen, (255, 128, 0), entity.rect, 2)
pg.draw.line(screen, (100, 200, 255), (0, 240), (640, 240), 1)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
I also had this problem and found an easy solution:
You can just create a bigger surface (doubled length and doubled height) and blit the smaller surface into the bigger so that the rotation point of is the center of the bigger one. Now you can just rotate the bigger one around the center.
def rotate(img, pos, angle):
w, h = img.get_size()
img2 = pygame.Surface((w*2, h*2), pygame.SRCALPHA)
img2.blit(img, (w-pos[0], h-pos[1]))
return pygame.transform.rotate(img2, angle)
(If you would make sketch, it would make much more sense, but trust me: It works and is in my opinion easy to use and to understand than the other solutions.)
I agree with MegaIng and skrx. But I also have to admit that I could not truly grasp the concept after reading their answers. Then I found this game-tutorial regarding a rotating canon over its edge.
After running it, I still had questions in mind, but then I found out that the image that they have used for cannon was a part of the trick.
The image was not centered around its cannon's center, it was centered around the pivot point and the other half of the image was transparent. After that epiphany I applied the same to the legs of my bugs and they all work just fine now. Here is my rotation code:
def rotatePivoted(im, angle, pivot):
# rotate the leg image around the pivot
image = pygame.transform.rotate(im, angle)
rect = image.get_rect()
rect.center = pivot
return image, rect
Hope this helps!
Rotating an image around a pivot on the image, which is anchored to a point in the world (origin), can be achieved with the following function:
def blitRotate(surf, image, origin, pivot, angle):
image_rect = image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center
rotated_offset = offset_center_to_pivot.rotate(-angle)
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
surf.blit(rotated_image, rotated_image_rect)
Explanation:
A vector can be represented by pygame.math.Vector2 and can be rotated with pygame.math.Vector2.rotate. Notice that pygame.math.Vector2.rotate rotates in the opposite direction than pygame.transform.rotate. Therefore the angle has to be inverted:
Compute the offset vector from the center of the image to the pivot on the image:
image_rect = image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center
Rotate the offset vector the same angle you want to rotate the image:
rotated_offset = offset_center_to_pivot.rotate(-angle)
Calculate the new center point of the rotated image by subtracting the rotated offset vector from the pivot point in the world:
rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y)
Rotate the image and set the center point of the rectangle enclosing the rotated image. Finally blit the image :
rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
surf.blit(rotated_image, rotated_image_rect)
See also Rotate surface
Minimal example: repl.it/#Rabbid76/PyGame-RotateSpriteAroundOffCenterPivotCannon
import pygame
class SpriteRotate(pygame.sprite.Sprite):
def __init__(self, imageName, origin, pivot):
super().__init__()
self.image = pygame.image.load(imageName)
self.original_image = self.image
self.rect = self.image.get_rect(topleft = (origin[0]-pivot[0], origin[1]-pivot[1]))
self.origin = origin
self.pivot = pivot
self.angle = 0
def update(self):
image_rect = self.original_image.get_rect(topleft = (self.origin[0] - self.pivot[0], self.origin[1]-self.pivot[1]))
offset_center_to_pivot = pygame.math.Vector2(self.origin) - image_rect.center
rotated_offset = offset_center_to_pivot.rotate(-self.angle)
rotated_image_center = (self.origin[0] - rotated_offset.x, self.origin[1] - rotated_offset.y)
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center = rotated_image_center)
pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
cannon = SpriteRotate('cannon.png', (200, 200), (33.5, 120))
cannon_mount = SpriteRotate('cannon_mount.png', (200, 200), (43, 16))
all_sprites = pygame.sprite.Group([cannon, cannon_mount])
angle_range = [-90, 0]
angle_step = -1
frame = 0
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
all_sprites.update()
screen.fill((64, 128, 255))
pygame.draw.rect(screen, (127, 127, 127), (0, 250, 400, 150))
all_sprites.draw(screen)
pygame.display.flip()
frame += 1
cannon.angle += angle_step
if not angle_range[0] < cannon.angle < angle_range[1]:
angle_step *= -1
pygame.quit()
exit()
See alos
Rotating and scaling an image around a pivot, while scaling width and height separately in Pygame
I think you have to make a function of your own for that.
If you make a Vector class it's much easier.
Maybe something like:
def rotate(surf, angle, pos):
pygame.transform.rotate(surf, angle)
rel_pos = surf.blit_pos.sub(pos)
new_rel_pos = rel_pos.set_angle(rel_pos.get_angle() + angle)
surf.blit_pos = pos.add(new_rel_pos)
So there you have it.The only thing you have to do is the Vector class with the methods 'add()', 'sub()', 'get_angle()' and 'set_angle()'. If you are strugling just google for help.
In the end you'll end up with a nice Vector class that you can expand and use in other projects.