rotating a rectangle in pygame - python

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

Related

pygame maintain a points position around a rotated image

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()

pygame screen not refreshing

So i am new to python, and im working on a code that draws a spirograph and moves it. When I ran the code, all I got was a black screen with no circles on it. Im sure that im drawing circles and the screen is refreshing because i printed out its coordinates. However, the screen is still black. Any help?
import pygame
import math
import sys
import time
#setting colors
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
ORANGE = (255, 127, 0)
YELLOW = (255, 255, 0)
PURPLE = (160, 32, 240)
#setting what order the colors go in
listCircleColor = (RED, BLUE, GREEN, ORANGE, YELLOW, PURPLE, WHITE)
#how many circles per color
intGroup = 5
#the space between each circle
turnangle = 360/35
#width of screen
width = 600
#height of screen
height = 600
#radius of circles
radius = 100
#making the screen
screen = pygame.display.set_mode((width, height))
#if the code is running, then continue
running = True
##.draw.circle(screen, BLUE, (0, 0), radius, width=2)
circles = []
#draw
alpha = turnangle
for i in range(intGroup):
for cl in listCircleColor:
surfacetemp = pygame.Surface((width, height))
##circlerect = pygame.rect
if alpha > 0 and alpha < 90:
circlerect = pygame.draw.circle(surfacetemp, cl, (300 + radius *
math.cos(math.radians(alpha)), 300 + radius * math.sin(math.radians(alpha))), radius, width=2)
# second quarter of circles
if alpha > 90 and alpha < 180:
circlerect = pygame.draw.circle(surfacetemp, cl, (300 - radius *
math.cos(math.radians(180 - alpha)), 300 + radius * math.sin(math.radians(180 - alpha))), radius, width=2)
# third quarter of circles
if alpha > 180 and alpha < 270:
circlerect = pygame.draw.circle(surfacetemp, cl, (300 - radius *
math.cos(math.radians(270 - alpha)), 300 - radius * math.sin(math.radians(270 - alpha))), radius, width=2)
# last quarter of circles
if alpha > 270 and alpha < 360:
circlerect = pygame.draw.circle(surfacetemp, cl, (300 + radius *
math.cos(math.radians(360 - alpha)), 300 - radius * math.sin(math.radians(360 - alpha))), radius, width=2)
alpha = alpha + turnangle
##circles.append(circlerect)
circles.append(surfacetemp)
#move"
#exit only when user clicks on exit button
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
for crect in circles:
ret = crect.get_rect()
ret.right += 5
ret.left += 5
screen.blit(screen, ret)
##screen.blit(crect,crect)
pygame.time.Clock().tick(20)
pygame.display.update()
##for center, color in circles:
## pygame.draw.circle(screen, color, center, radius, 2)
##pygame.display.flip()
You blit on correct screen but you blit wrong objects.
You should use crect instead of screen as first argument
screen.blit(crect, ret)
but you have
screen.blit(screen, ret)
so you blit black screen on black screen
Surface as default uses 24bit colors R,G,B and it has black background so your code shows only one circle because other circles are behind black backgrounds of other surfaces.
To make transparent background you have to convert surfaces to 32bit colors R,G,B,Alpha
surfacetemp = surfacetemp.convert_alpha()
and fill background with transparent color - last value (alpha) has to be 0, other values don't matter (if you don't need semi-transparent color)
surfacetemp.fill((0,0,0,0))
And now you can see all circles.
There is also other problem.
Surface can keep image but not position. surface.get_rect() gives always only (0, 0, width, height). If you want to animate then you need another list to keep positions - you can use pygame.Rect() for this.
At start I create circles_rect with all get_rect() and later I use this list to change position and blit it.
Animation need also to clear screen in every loop to remove circles in old positions. I use screen.fill((0,0,0)) to draw black background.
If you change rec.right += 5 then you don't have to change rec.left because it is changed automatically (the same with rec.centerx)
Running
rec.right += 5
rec.left += 5
gives the same result as
rec.right += 10
EDIT:
You don't have to make different calculations for different quarters.
import pygame
import math
# setting colors
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = (255, 0, 0)
ORANGE = (255, 127, 0)
YELLOW = (255, 255, 0)
PURPLE = (160, 32, 240)
# setting what order the colors go in
listCircleColor = (RED, BLUE, GREEN, ORANGE, YELLOW, PURPLE, WHITE)
# how many circles per color
intGroup = 5
# the space between each circle
turnangle = 360/35
# width of screen
width = 600
# height of screen
height = 600
# radius of circles
radius = 100
# making the screen
screen = pygame.display.set_mode((width, height))
# .draw.circle(screen, BLUE, (0, 0), radius, width=2)
circles = []
circles_rect = []
# draw
alpha = turnangle
for i in range(intGroup):
for cl in listCircleColor:
surfacetemp = pygame.Surface((width, height))
surfacetemp = surfacetemp.convert_alpha()
surfacetemp.fill((0,0,0,0))
x = 300 + radius * math.cos(math.radians(alpha))
y = 300 + radius * math.sin(math.radians(alpha))
circlerect = pygame.draw.circle(surfacetemp, cl, (x, y), radius, width=2)
alpha = alpha + turnangle
circles.append(surfacetemp)
circles_rect.append(surfacetemp.get_rect())
# move
# exit only when user clicks on exit button
# if the code is running, then continue
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
#running = False
screen.fill((0,0,0)) # remove previous circles
for crect, ret in zip(circles, circles_rect):
ret.right += 5
#ret.left += 5
screen.blit(crect, ret)
result = clock.tick(25)
fps = clock.get_fps()
text = f'FRAME TIME: {result:.02f} ms | FPS: {fps:.02f}'
pygame.display.set_caption(text)
#print(text)
#pygame.display.update()
pygame.display.flip()

Pygame resizing a surface and window based on a rect size

I have a display area and a surface that is blitted on the display. On the surface is an image, in this case a rect. In the future it may be multiple rects or lines drawn on the surface keep that in mind.
I am trying to enlarge (by pressing x) the Rect named Sprite that is on SpriteSurface and SpriteSurface as well as the whole display window. The SpriteSurface image should be centered despite the resize. Currently the window will enlarge and the image stays centered, but if you uncomment the spritesizeX and Y lines the image gets larger but too big too fast and the window doesn't seem to enlarge big enough. Lowering the values shows that the offset of centering gets thrown off after the first resize. I feel like the solution should be relatively easy but im stumped. Any help would be appreciated.
Settings.py
spriteSizeX = 30
spriteSizeY = 30
SpHalfX = int(round(spriteSizeX / 2))
SpHalfY = int(round(spriteSizeY / 2))
multiplyer = 3
windowSizeX = int(round(spriteSizeX * multiplyer))
windowSizeY = int(round(spriteSizeY * multiplyer))
HalfWinX = int(round((windowSizeX / 2) - SpHalfX))
HalfWinY = int(round((windowSizeY / 2) - SpHalfY))
Orange = (238,154,0)
Gold = (255,215,0)
Black = (0,0,0)
Blue = (0,0,255)
Gray = (128,128,128)
DarkGray = (100,100,100)
Green = (0,128,0)
Lime = (0,255,0)
Purple = (128,0,128)
Red = (255,0,0)
Teal = (0,200, 128)
Yellow = (255,255,0)
White = (255,255,255)
run = True
SpriteCapture.py
#!/usr/local/bin/python3.6
import sys, pygame
from pygame.locals import *
from settings import *
pygame.init()
pygame.display.set_caption("Sprite Capture")
Screen = pygame.display.set_mode((windowSizeX, windowSizeY),RESIZABLE)
SpriteSurface = pygame.Surface((spriteSizeX,spriteSizeY))
Sprite = Rect(0,0,spriteSizeX,spriteSizeY)
while run == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if pygame.key.get_pressed()[pygame.K_s]:
pygame.image.save(SpriteSurface, 'img1.png')
run = False
if pygame.key.get_pressed()[pygame.K_q]:
run = False
if pygame.key.get_pressed()[pygame.K_z]:
#spriteSizeX += 10
#spriteSizeY += 10
windowSizeX += -10
windowSizeY += -10
HalfWinX = int(round(windowSizeX / 2 - SpHalfX))
HalfWinY = int(round(windowSizeY / 2 - SpHalfY))
Screen = pygame.display.set_mode((windowSizeX, windowSizeY),RESIZABLE)
SpriteSurface = pygame.Surface((spriteSizeX,spriteSizeY))
if pygame.key.get_pressed()[pygame.K_x]:
#spriteSizeX += 10
#spriteSizeY += 10
windowSizeX += 10
windowSizeY += 10
HalfWinX = int(round(windowSizeX / 2 - SpHalfX))
HalfWinY = int(round(windowSizeY / 2 - SpHalfY))
Screen = pygame.display.set_mode((windowSizeX, windowSizeY),RESIZABLE)
SpriteSurface = pygame.Surface((spriteSizeX,spriteSizeY))
Sprite = Sprite = Rect(0,0,spriteSizeX,spriteSizeY)
Screen.fill(Black)
pygame.draw.rect(SpriteSurface,Orange,Sprite)
Screen.blit(SpriteSurface, (HalfWinX,HalfWinY))
pygame.display.flip()
If you want to scale your surfaces or rects according to the screen size, you can define a zoom_factor variable which you can just increase when a key gets pressed and then use it to scale the window and the surfaces. Multiply it by the original screen width and height to scale the window, and also scale your surfaces with pygame.transform.rotozoom and pass the zoom_factor as the scale argument.
import sys
import pygame
from pygame.locals import *
width = 30
height = 30
multiplyer = 3
window_width = round(width * multiplyer)
window_height = round(height * multiplyer)
zoom_factor = 1
ORANGE = (238,154,0)
BLACK = (0,0,0)
pygame.init()
screen = pygame.display.set_mode((window_width, window_height), RESIZABLE)
screen_rect = screen.get_rect() # A rect with the size of the screen.
clock = pygame.time.Clock()
# Keep a reference to the original image to preserve the quality.
ORIG_SURFACE = pygame.Surface((width, height))
ORIG_SURFACE.fill(ORANGE)
surface = ORIG_SURFACE
# Center the rect on the screen's center.
rect = surface.get_rect(center=screen_rect.center)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
run = False
elif event.key == pygame.K_z:
zoom_factor = round(zoom_factor-.1, 1)
# Scale the screen.
w, h = int(window_width*zoom_factor), int(window_height*zoom_factor)
screen = pygame.display.set_mode((w, h), RESIZABLE)
screen_rect = screen.get_rect() # Get a new rect.
# Scale the ORIG_SURFACE (the original won't be modified).
surface = pygame.transform.rotozoom(ORIG_SURFACE, 0, zoom_factor)
rect = surface.get_rect(center=screen_rect.center) # Get a new rect.
elif event.key == pygame.K_x:
zoom_factor = round(zoom_factor+.1, 1)
w, h = int(window_width*zoom_factor), int(window_height*zoom_factor)
screen = pygame.display.set_mode((w, h), RESIZABLE)
screen_rect = screen.get_rect()
surface = pygame.transform.rotozoom(ORIG_SURFACE, 0, zoom_factor)
rect = surface.get_rect(center=screen_rect.center)
# Note that the rect.w/screen_rect.w ratio is not perfectly constant.
print(zoom_factor, screen_rect.w, rect.w, rect.w/screen_rect.w)
screen.fill(BLACK)
screen.blit(surface, rect) # Blit the surface at the rect.topleft coords.
pygame.display.flip()
clock.tick(60)
Alternatively, you could just blit all of your surfaces onto a background surface, then scale this background with pygame.transform.rotozoom each frame and blit it onto the screen. However, scaling a big background surface each frame will be bad for the performance.

Adding gradient to moving objects in pygame

I want to add gradient to the ball in this program & also possibly the waves drawn to fade into the colour of the background (as if glowing) instead of one colour fills.
I've looked at tons of tutorials however none of them are making much sense to my syntax, the general idea to me is confusing as I have moving objects that draw the space I want to add gradient to quite slowly. Can anyone give an insight into how I can do this?
code:
import sys, pygame, math
from pygame.locals import *
# set up of constants
WHITE = (255, 255, 255)
DARKRED = (128, 0, 0)
RED = (255, 0, 0)
BLACK = ( 0, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
BGCOLOR = WHITE
screen = pygame.display.set_mode()
WINDOWWIDTH = 800 # width of the program's window, in pixels
WINDOWHEIGHT = 800 # height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
screen = pygame.display.get_surface()
FPS = 160 # frames per second to run at
AMPLITUDE = 80 # how many pixels tall the waves with rise/fall.
# standard pygame setup code
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.RESIZABLE)
pygame.display.set_caption('Window title')
fontObj = pygame.font.Font('freesansbold.ttf', 16)
# variables that track visibility modes
showSine = True
showSquare = True
pause = False
xPos = 0
step = 0 # the current input f
posRecord = {'sin': [], 'square': []} # keeps track of the ball positions for drawing the waves
yPosSquare = AMPLITUDE # starting position
# main application loop
while True:
# event handling loop for quit events
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
# fill the screen to draw from a blank state
DISPLAYSURF.fill(BGCOLOR)
# sine wave
yPos = -1 * math.sin(step) * AMPLITUDE
posRecord['sin'].append((int(xPos), int(yPos) + WIN_CENTERY))
if showSine:
# draw the sine ball and label
pygame.draw.circle(DISPLAYSURF, RED, (int(xPos), int(yPos) + WIN_CENTERY), 10)
sinLabelRect.center = (int(xPos), int(yPos) + WIN_CENTERY + 20)
DISPLAYSURF.blit(sinLabelSurf, sinLabelRect)
# draw the waves from the previously recorded ball positions
if showSine:
for x, y in posRecord['sin']:
pygame.draw.circle(DISPLAYSURF, DARKRED, (x,y), 4)
#drawing horizontal lines
# square
posRecord['square'].append((int(xPos), int(yPosSquare) + WIN_CENTERY))
if showSquare:
# draw the sine ball and label
pygame.draw.circle(DISPLAYSURF, GREEN, (int(xPos), int(yPosSquare) + WIN_CENTERY), 10)
squareLabelRect.center = (int(xPos), int(yPosSquare) + WIN_CENTERY + 20)
DISPLAYSURF.blit(squareLabelSurf, squareLabelRect)
# draw the waves from the previously recorded ball positions
if showSquare:
for x, y in posRecord['square']:
pygame.draw.circle(DISPLAYSURF, BLUE, (x, y), 4)
# draw the border
pygame.draw.rect(DISPLAYSURF, BLACK, (0, 0, WINDOWWIDTH, WINDOWHEIGHT), 1)
pygame.display.update()
FPSCLOCK.tick(FPS)
if not pause:
xPos += 1
#wave movement
if xPos > WINDOWWIDTH:
#sine
xPos = 0
posRecord['sin'] = []
step = 0
# square
yPosSquare = AMPLITUDE
posRecord['square'] = []
else:
#sine
step += 0.008
#step %= 2 * math.pi
# square
# jump top and bottom every 100 pixels
if xPos % 100 == 0:
yPosSquare *= -1
# add vertical line
for x in range(-AMPLITUDE, AMPLITUDE):
posRecord['square'].append((int(xPos), int(x) + WIN_CENTERY))
Use SPACE to change background color.
First line use only transparency - and has no problem with different background color.
Second line changes only circles color - and depends on background color.
Third and fourth line (it is the same line with different starting color) change circles color and transparency - and depends on background color.
Second and last line look good on one color background and need more work to find good-looking fading.
import pygame
pygame.init()
screen = pygame.display.set_mode((600,200))
#--------------------------------------
# circles positions and transparency (x,y, alpha)
circles = []
for x in range(100):
circles.append( [100+x*3, 200, x*2] )
#--------------------------------------
white = True # background color
#--------------------------------------
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
white = not white
#--------------------------------------
if white:
screen.fill((255,255,255))
else:
screen.fill((0,0,0))
#--------------------------------------
# first
circle_img = pygame.Surface((20,20))
pygame.draw.circle(circle_img, (255,0,0), (10,10), 10)
circle_img.set_colorkey(0)
for x in circles:
circle_img.set_alpha(x[2])
screen.blit(circle_img, (x[0],40))
#--------------------------------------
# second
circle_img = pygame.Surface((20,20))
for x in circles:
pygame.draw.circle(circle_img, (255,255-x[2],255-x[2]), (10,10), 10)
circle_img.set_colorkey(0)
screen.blit(circle_img, (x[0],90))
#--------------------------------------
# last
circle_img = pygame.Surface((20,20))
for x in circles:
pygame.draw.circle(circle_img, (255,255-x[2],255-x[2]), (10,10), 10)
circle_img.set_colorkey(0)
circle_img.set_alpha(x[2])
screen.blit(circle_img, (x[0],140))
#--------------------------------------
pygame.display.flip()
pygame.quit()

How to set the pivot point (center of rotation) for pygame.transform.rotate()?

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.

Categories

Resources