[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
Re: [pygame] how to rotate a sprite correctly clockwise or counterclockwise
Hi Jens
well, I got it working after some debugging. The simplest solution would
be to use a cross product (see rot_dir3, line 233), but then you need to
convert facing into a vector.
I also managed to get it working using angles (line 245). But angles are
more complicated. I also would recommend to use atan2() (line 191).
I added some lines to actually see the facing and newfacing. It makes it
a lot easier to see what happens.
HTH
~DR0ID
Horst JENS schrieb:
dear masters of pygame,
i'm stuck at a little problem but too tired to find a solution:
want to rotate a little sprite (pirate1, class smallboat) in the correct
way (minimal rotating time) either clockwise or counterclockwise.
at the moment the sprite rotate itself half the time in the wrong
direction.
sprite rotate until it can mofe forward to the green cross.
i tryed to calculate the amount of degrees (Grad) to rotate and decide
to flip rotation direction if amount > 180; (line 202 and 203,
out-commented)
but somehow my sprite then goes either backwards instead forward or is
stuck in a never ending rotating loop.
the version attached rotate the sprite half the time in the wrong
direction.
(ignore the big sprite in the middle).
program need pygame>1.8.1 and python>2.5
nearly all comments in English, only docstring and todo in German
greetings & many thanks in advance for any hint,
-Horst
# -*- coding: utf-8 -*-
"""
test für schlachtschiff. soll sich drehen, langsam in richtung Mauszeiger fahren,
kanone soll auf Sprite oder auf Mauszeiger zeigen.
"""
import os
import pygame
import random
import math
# todo: pirat dreht manchmal in die falsche richtung. > 180 check ?
# todo: pirat "zittert" manchmal lange ... rotspeed senken ? toleranz erhöhen ?
# ---------------------------
os.environ["SDL_VIDEO_CENTERED"] = "1" # center pygame window on screen
pygame.init()
screen=pygame.display.set_mode((640,480))
pygame.display.set_caption("press Esc to exit")
# background
#from file:
#background = pygame.image.load("horst.jpg")
#or create on the fly:
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255,255,255)) #fill the background white
# make a more pretty background:
#blue line each 20 pixel
# black line each 100 pixel
xlist = range(0,screen.get_width(), 20)
ylist = range(0,screen.get_height(),20)
for x in xlist:
if x%100 == 0:
color = (0,0,64)
else:
color = (64,64,255)
pygame.draw.line(background, color, (x,0), (x,screen.get_height())) #light blue
for y in ylist:
if y%100 == 0:
color = (0,0,64)
else:
color = (64,64,255)
pygame.draw.line(background, color, (0, y), (screen.get_width(), y)) # dark blue
screen.blit(background, (0,0)) # blit the backgroundsurface on the screen
class BigBoat(pygame.sprite.Sprite):
"""a big battleship"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
#self.image_orig = pygame.image.load("pygame_tiny.gif").convert_alpha()
self.image = pygame.Surface((100,100)) #
self.image.fill((255,255,255)) # fill white
pygame.draw.rect(self.image, (64,64,63), (0,0,100,100)) # rectangle
pygame.draw.circle(self.image, (128,128,128), (50,25), 25) # north guntower
pygame.draw.circle(self.image, (128,128,128), (50,75), 25) # south guntower
pygame.draw.rect(self.image, (0,0,0), [25,0,50,5]) # north tip of boat
self.image.set_colorkey((255,255,255)) # make white transparent
self.image0 = self.image.copy()
self.rect = self.image.get_rect()
self.rect.center = (300,300)
self.oldcenter = self.rect.center
self.destination = pygame.mouse.get_pos()
self.facing = 0
self.oldfacing = 0
self.enemy = (0,0)
def learn(self,position):
#self.enemy = position
self.enemy = pygame.mouse.get_pos()
def update(self, seconds_passed):
self.oldfacing = self.facing #save old value
pressed_keys = pygame.key.get_pressed()
#if pressed_keys[pygame.K_a]:
# self.facing += 1
#elif pressed_keys[pygame.K_d]:
# self.facing -=1
#----rotation:
# tan(alpha) = y/x
# alpha = atan(y/x) # in radiant
# alpha = atan(y/x) / (math.pi*2) * 360 # alpha in Grad
#---- where is the enemy, where am i ?
dx = self.enemy[0] - self.rect.centerx * 1.0 #float
dy = self.enemy[1] - self.rect.centery * 1.0
try:
# catch possible division by zero or invalid atan
winkel = math.atan(abs(dy/dx)) / (math.pi*2) * 360 # in Grad
if (dx >= 0) and (dy >= 0):
#quadrant = 1 # lower right
self.facing = -winkel -90
elif (dx < 0) and (dy >=0):
#quadrant = 2 # lower left
self.facing = winkel + 90
elif (dx >=0) and (dy < 0):
#quadrant = 3 # upper right
self.facing = winkel -90
elif (dx <0) and (dy < 0):
#quadrant = 4
self.facing = - winkel +90
except:
pass # division by zero ?
#print dx, dy, self.facing
if self.oldfacing != self.facing:
self.oldcenter = self.rect.center
self.image = pygame.transform.rotate(self.image0, self.facing)
self.rect = self.image.get_rect()
self.rect.center = self.oldcenter
class Cross(pygame.sprite.Sprite):
""" a green cross indicating the destination of a boat """
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self)
self.boss = boss # the sprite i belong to
self.image = pygame.Surface((10,10))
self.image.fill((0,0,0)) #fill black
pygame.draw.line(self.image, (0,255,0), (0,0), (10,10),4)
pygame.draw.line(self.image, (0,255,0), (10,0), (0,10),4)
self.image.set_colorkey((0,0,0)) # make black transparent
self.image.convert_alpha() # necessary ?
self.rect = self.image.get_rect()
self.status = True
def update(self, seconds_passed):
#ignore seconds_passed
self.rect.center = self.boss.destination
class SmallBoat(pygame.sprite.Sprite):
"""a small independent moving Baby Tux"""
def __init__(self):
"a small boat that drives around"
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((40,40))
self.image.fill((255,255,255)) # fill white
pygame.draw.polygon(self.image, (0,0,0), [(20,0), (40,40), (20, 20), (0,40)], 3)
self.image.set_colorkey((255,255,255)) # make white tranparent
self.image.convert_alpha() # to make sure tranparency works
self.image0 = self.image.copy() # copy original image for rotation
self.rect = self.image.get_rect()
# TrueX,Y (float) because we want to be able to travel with less than 1 pixel /frame (very, very slow)
self.TrueX = 0.0
self.TrueY = 0.0
self.rect.center = (0,0)
self.oldcenter = (0,0)
self.facing = 0
self.oldfacing = 0
self.rotspeed = 90 # one half revolution per second
self.newfacing = 0
self.newDestination() #must be last line of init
def newDestination(self):
"calculate a random new Destination to travel and a random speed"
self.destination = (random.randint(0,screen.get_width()), random.randint(0,screen.get_height()))
self.speed = random.random() * 3 + 1.0
#---calculate the way
self.speedx = (self.destination[0] - self.rect.centerx ) / self.speed
self.speedy = (self.destination[1] - self.rect.centery ) / self.speed
#----rotation:
#--calculate correct facing
#---- where is the my destination, where am i ?
dx = self.destination[0] - self.rect.centerx * 1.0 #float
dy = self.destination[1] - self.rect.centery * 1.0
try:
## winkel = math.atan(abs(dy/dx)) / (math.pi*2) * 360 # in Grad
## if (dx >= 0) and (dy >= 0):
## #quadrant = 1 # lower right
## self.newfacing = -winkel -90
## elif (dx < 0) and (dy >=0):
## #quadrant = 2 # lower left
## self.newfacing = winkel + 90
## elif (dx >=0) and (dy < 0):
## #quadrant = 3 # upper right
## self.newfacing = winkel -90
## elif (dx <0) and (dy < 0):
## #quadrant = 4
## self.newfacing = -winkel +90
## #print winkel, self.facing
#===============
# calculate angel in range [0, 360)
winkel = math.degrees(math.atan2(dy, dx))
# atan2 returns angle in range [-180, 180], so add 360 if negative
if winkel < 0:
winkel = 360 + winkel
self.newfacing = winkel
#===============
except:
pass # catch possible division by zero or invalid atan
def update(self, seconds_passed):
#---rotate of move ? ---
#--- this is not exact, so give a tolerance
#if self.facing > 180:
#self.facing = 180 - self.facing
#if self.facing < -180:
#self.facing = -180 - self.facing
#if self.newfacing > 180:
#self.newfacing = 180 - self.facing
#if self.newfacing < -180:
#self.newfacing = -180 - self.facing
# move or rotate ?-----------
face_diff = self.newfacing - self.facing
#if face_diff < -180:
# face_diff = - 180 - face_diff
#elif face_diff > 180:
# face_diff = 180 - face_diff
#print face_diff
if abs(face_diff) < 2:
#move ------------
self.TrueX += self.speedx * seconds_passed
self.TrueY += self.speedy * seconds_passed
else:
#rotate-----
#===============
# use the cross product of the two vectors: facing and to destination
rot_dir = cmp(self.newfacing, self.facing) # return 0,-1 or 1
ddx = self.destination[0] - self.rect.centerx * 1.0 #float
ddy = self.destination[1] - self.rect.centery * 1.0
fdx = 1.0 * math.cos(math.radians(self.facing))
fdy = 1.0 * math.sin(math.radians(self.facing))
rot_dir3 = ddx * fdy - fdx * ddy
if rot_dir3 < 0:
rot_dir3 = 1
else:
rot_dir3 = -1
#===============
# calculating rotation direction using angles
if face_diff > 0:
if face_diff > 180:
rot_dir2 = -1
else:
rot_dir2 = 1
else:
if face_diff < -180:
rot_dir2 = 1
else:
rot_dir2 = -1
#===============
#if abs(face_diff) > 180:
# rot_dir *= -1
print '? facing: %f newfacing: %f facediff: %f rotdir: %i rotdir2: %i rot_dir3: %i' % (self.facing, self.newfacing, face_diff, rot_dir, rot_dir2, rot_dir3)
if rot_dir2 != rot_dir3:
print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
self.facing += self.rotspeed * rot_dir3 * seconds_passed
#===============
# make sure facing is in range [0, 360)
self.facing %= 360
#===============
#bounce --------------
if self.TrueX > screen.get_width():
self.TrueX = screen.get_width()
self.newDestination()
if self.TrueX < 0:
self.TrueX = 0
self.newDestination()
if self.TrueY > screen.get_height():
self.TrueY = screen.get_height()
self.newDestination()
if self.TrueY < 0:
self.TrueY = 0
self.newDestination()
# calculate new position
self.rect.centerx = round(self.TrueX,0) # make integer out of float
self.rect.centery = round(self.TrueY,0)
# destination reached ?
# this is never exact, so give a tolerance of 5 pixel
##print self.rect.center, self.destination
if abs(self.rect.centerx - self.destination[0]) < 5:
if abs(self.rect.centery - self.destination[1]) < 5:
#print "i reached my goal"
self.newDestination()
#rotate---------------
if self.oldfacing != self.facing:
self.oldcenter = self.rect.center
#===============
# looks like the image has some angle offset of 90°
# pygame rotates in the other direction as the facing, thats why mult with -1
self.image = pygame.transform.rotate(self.image0, -1 * self.facing - 90)
#===============
self.rect = self.image.get_rect()
self.rect.center = self.oldcenter
#save old rotate value---------------------
self.oldfacing = self.facing # save old facing value
#===============
# help visualize what the angles are
draw_line_angle(self.TrueX, self.TrueY, self.facing, (255, 0, 0))
draw_line(self.TrueX, self.TrueY, self.destination[0] - self.rect.centerx * 1.0, self.destination[1] - self.rect.centery * 1.0, (0, 255, 0))
def draw_line_angle(x, y, angle, color):
dx = 100 * math.cos(math.radians(angle))
dy = 100 * math.sin(math.radians(angle))
draw_line(x, y, dx, dy, color)
def draw_line(x, y, dx, dy, color):
screen = pygame.display.get_surface()
pygame.draw.line(screen, color, (x, y), (x+dx, y+dy))
#===============
#adding sprites to the game ------------------------
bigships = pygame.sprite.Group()
smallships = pygame.sprite.Group()
helper = pygame.sprite.Group() # a group for visual helpers like destination cross
royal1 = BigBoat() # the boat is so big, it get an individual name
bigships.add(royal1) # maybe it will some day get a sister ship, so it belongs to a group
pirate1 = SmallBoat()
smallships.add(pirate1) ####small pirate boat(s); not important enough to get a individual name
for ship in smallships:
helper.add(Cross(pirate1))
allsprites = pygame.sprite.LayeredUpdates(bigships, smallships, helper) # oder of drawing (groups)
# variables needed for snake
#showBorder = False
#a bit of text
#myFont = pygame.font.SysFont("None",20)
#textsurface = myFont.render("press b or n to toggle border rect/ color", True, (0,0,255))
#textsurface2 = myFont.render("press ESC or q to quit", True, (0,0,255))
#background.blit(textsurface, (100,screen.get_height()/2)) # blit the textsurface on the backgroundsurface
#background.blit(textsurface2, (100, screen.get_height()/2+20))
#screen.blit(background, (0,0)) # blit the backgroundsurface on the screen
#--- loop prepare ---
mainloop = True
#clock = pygame.time.Clock()
fps = 30 #frames per second
seconds_played = 0.0
#--- mainloop ------
while mainloop:
tick_time = pygame.time.Clock().tick(fps) # milliseconds since last frame
seconds_passed = tick_time / 1000.0 # decimal-seconds since last frame
seconds_played += seconds_passed # counter, will not be resetted
#change = False # any update
#event handler
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE or event.key == pygame.K_q:
mainloop = False
# more precise keyboard event handler
#pressed_keys = pygame.key.get_pressed()
#if pressed_keys[pygame.K_b]:
#BigBoat collide with Pirate ?
#crashGroup = pygame.sprite.spritecollide(Cobra, Babys, False)
#for penguin in crashGroup:
# penguin.neg = True
#Babytux crash into Babytux ?
#for penguin in babysprites:
# othergroup = babysprites.copy()
# othergroup.remove(penguin) # a copy of the group without the current penguin
# if pygame.sprite.spritecollideany(penguin, othergroup):
# penguin.neg = True
# penguin.newspeed()
# decorate pygame window
pygame.display.set_caption("press [Esc] to quit. Facing:%i" % royal1.facing)
allsprites.clear(screen, background)
allsprites.update(seconds_passed)
# royal1 has to learn the position of pirate1
royal1.learn(pirate1.rect.center)
allsprites.draw(screen)
#for everyone
pygame.display.flip()
#--end of loop
pygame.quit() #idle-friendly quit