Can't understand how to make character face mouse in PyGame

烈酒焚心 提交于 2020-12-23 12:10:53

问题


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

  1. Compute a relative vector from the object's position to the player's
    • Call this vector Vrel
  2. Compute atan2(Vrel) - atan2(playerDiry, playerDirx)
    • The result is now the amount your player needs to rotate, relative itself
  3. 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)

Initial setup

  1. Relative vector is (Objectx - Playerx, Objecty - Playery) = (5, 5) - (-3, -1) = (5 - -3, 5 - -1) = (5+3, 5+1) = (8, 6)

computing relative vector

  1. atan2(Vrel) = atan2(6, 8) = 0.9272952180016122 rad

    • atan2(playerDiry, playerDirx) = atan2(-0.8, 0.6) = 2.498091544796509 rad

Setup with only vectorscomputing individual angles

  • atan2(Vrel) - atan2(player) = 0.9272952180016122 - 2.498091544796509 = -1.5707963267948966 rad

computing angle between direction vector and relative vector

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!