问题
Below is what I have so far. At the moment, the player moves, and all I want it to do now is face the mouse cursor at all times (think Hotline Miami or other top-down games). I've used some code involving atan2 that I found online, but I barely understand what it does (finds the distance between the player and mouse somehow...) and my character just spins wildly offscreen until I get an 'Out of memory' error. Any help would be much appreciated, I've looked all over the internet and can't find anything that helps me.
import pygame, sys, math
from pygame.locals import *
pygame.init()
#setup the window with a 720p resolution and a title
winX, winY = 1280, 720
display = pygame.display.set_mode((winX, winY))
winTitle = pygame.display.set_caption("Game name")
#Colours
cBlack = (0, 0, 0)
#setup the clock and FPS limit
FPS = 70
clock = pygame.time.Clock()
#sprite groups
allSprites = pygame.sprite.Group()
#background image
bg = pygame.image.load("bg.jpg")
class Player(pygame.sprite.Sprite):
def __init__(self, speed):
#import the __init__ from the Sprite class
pygame.sprite.Sprite.__init__(self)
#sets the objects speed to the speed argument
self.speed = speed
#set the soldiers image
self.image = pygame.image.load("soldier_torso_DW.png")
#get the rectangle of the image (used for movement later)
self.rect = self.image.get_rect()
#sets starting position
self.rect.x, self.rect.y = winX/2, winY/2
def checkPlayerMovement(self):
"""Check if user has pressed any movement buttons
and sets the characters position accordingly."""
pressed = pygame.key.get_pressed()
if pressed[K_w]:
self.rect.y -= self.speed
if pressed[K_s]:
self.rect.y += self.speed
if pressed[K_a]:
self.rect.x -= self.speed
if pressed[K_d]:
self.rect.x += self.speed
def checkMouseMovement(self):
"""Check if user has moved the mouse and rotates the
character accordingly."""
mX, mY = pygame.mouse.get_pos()
angleRad = math.atan2(mY, mX) - math.atan2(self.rect.y, self.rect.x)
angleDeg = math.degrees(angleRad)
print angleDeg
self.image = pygame.transform.rotate(self.image, angleDeg)
def update(self):
"""Performs all movement and drawing functions and shit
for the object"""
self.checkPlayerMovement()
self.checkMouseMovement()
#draw the image at the given coordinate
display.blit(self.image, (self.rect.x, self.rect.y))
def updateAll():
#fill the window with black
display.blit(bg, (0, 0))
#update all sprites
allSprites.update()
#keeps FPS at 60
clock.tick(FPS)
#updates the display
pygame.display.flip()
#creates a player with a speed of 5
mainChar = Player(5)
#add the player character to main sprite group
allSprites.add(mainChar)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
updateAll()
回答1:
(Full disclosure, I'm not familiar with the pygame package, but I have done this sort of programming in other languages)
Discussion
When you want a player to determine how much it should rotate to face an object, the player should consider itself to be the world center. However, its coordinates are not always going to be (0,0)
. We first need to transform our coordinates into egocentric ones by subtracting the players' pos from the object it wishes to face.
Next, it looks like you're only using the player's position to compute the rotation. Unless the player always faces a direction equivalent to the direction of a vector from its current position to the origin, this is not a correct calculation. You must include additional information to track the player's rotation, and this is often done through a unit direction vector with (x, y) components.
Method
- Compute a relative vector from the object's position to the player's
- Call this vector Vrel
- Compute atan2(Vrel) - atan2(playerDiry, playerDirx)
- The result is now the amount your player needs to rotate, relative itself
- Translate the sprite to the origin, perform rotation, translate back (I'm sure pygame has functionality to just rotate in place)
The atan2() function
The atan2 function will give you a signed rotation from the positive X axis.
So, given the Cartesian X-Y plane and a position (x, y), if you drew a vector from (0, 0) to (x, y), then atan2(y, x) will give you the angle between the vector (1, 0) and (x, y). For example, if your (x, y) was (0, 1) (along the y axis), then atan2(1, 0) will give you π/2, or 90°, and if your (x, y) went along the negative y axis (0, -1), then atan2(0, -1) will give you -π/2, or -90°.
A positive angle output from atan2 is always a counter-clockwise rotation, and a negative angle is a clockwise rotation. Most calculators do require the form atan2(y, x), but not all, so be careful. In Python's math library, it is atan2(y, x)
Example
Forgive the not-to-scale, bad color choice, kindergarten quality pictures and try to focus on the concepts.
Object at position (5, 5) Player at position (-3, -1). Player facing direction (-0.8, 0.6)
- Relative vector is (Objectx - Playerx, Objecty - Playery) = (5, 5) - (-3, -1) = (5 - -3, 5 - -1) = (5+3, 5+1) = (8, 6)
atan2(Vrel) = atan2(6, 8) = 0.9272952180016122 rad
- atan2(playerDiry, playerDirx) = atan2(-0.8, 0.6) = 2.498091544796509 rad
- atan2(playerDiry, playerDirx) = atan2(-0.8, 0.6) = 2.498091544796509 rad
- atan2(Vrel) - atan2(player) = 0.9272952180016122 - 2.498091544796509 = -1.5707963267948966 rad
So the player must rotate -1.57 rad about its own center (nearly 90 degrees clockwise) to face the object.
回答2:
You're computing the difference between the angle between the screen and the mouse and the angle between the sprite and its position. That's not what you want.
I'd propose:
angleRad = math.atan2(self.rect.y-mY, mX-self.rect.x) # y is reversed due to top-left coordinate system
That's the absolute angle that the sprite should have to look at the mouse pointer.
However, you probably have a second issue: you're rotating the sprite by this angle at each step. But you should rotate only by the difference between the current orientation of the sprite and the desired (if the sprite is at 90º and you want it to be facing 90º, then you shouldn't rotate it).
You should either keep the former angle in order to rotate only by the difference (but each rotation might change slightly the sprite), or keep a non-rotated copy of the sprite to rotate it by this angle each time:
self.image = pygame.transform.rotate(self.unrotated_image, angleDeg)
来源:https://stackoverflow.com/questions/29085110/cant-understand-how-to-make-character-face-mouse-in-pygame