简体   繁体   中英

Rotating and moving a sprite in Pygame

I'm new to Python, Pygame, and programming in general. I'm trying to create an Asteroids clone, but I can't figure out how to make my player sprite move and rotate at the same time. I used the Vector2D class from this answer (tried to modify it for the keyboard instead of the mouse) so my sprite is kind of rotating (it won't rotate in a complete circle), but now it won't move. I'm trying to use the up and down keys to accelerate/decelerate and the left and right arrows to turn. I want the ship to face the direction in which it is moving.

Here's the code thus far:

# an asteroids clone

try:
    import sys
    import random
    import math
    import os
    import getopt
    import pygame
    from socket import *
    from pygame.locals import *
    from pygame.mixer import Sound
except ImportError, err:
    print "couldn't load module. %s" % (err)
    sys.exit(2)

# these are warnings if font or sound modules are not available.
if not pygame.font: print 'Warning, fonts disabled'
if not pygame.mixer: print 'Warning, sound disabled'


# VECTOR CLASS
class Vector2D(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        if isinstance(other, Vector2D):
            # Vector multiplication
            return self.x * other.x + self.y * other.y
        else:
            # Scalar multiplication
            return Vector2D(self.x * other, self.y * other)

    __radd__ = __add__
    __rsub__ = __sub__
    __rmul__ = __mul__

    def get_length(self):
        return (self.x ** 2 + self.y ** 2) ** (1/2)

    def get_angle(self, other, radians=False):
        """Will return the angle between this vector and the other vector."""
        if self.get_length() == 0 or other.get_length() == 0:
            return 0
        if not radians:
            return (360 / (2 * math.pi)) * (math.atan2(other.y, other.x) - math.atan2(self.y, self.x))
        else:
            return math.atan2(other.y, other.x) - math.atan2(self.y, self.x)

    def normalize(self):
        if self.get_length() == 0:
            return Vector2D(0, 0)
        return Vector2D(self.x / self.get_length(), self.y / self.get_length())

class Player(pygame.sprite.Sprite):
    """moves ship on screen"""
    def __init__(self, image_file, pos=(0, 0)):
        super(Player, self).__init__() # call Sprite initializer
        self.original_image = pygame.image.load(image_file).convert() # RemEMBER TO CONVERT
        self.image = self.original_image # this will reference our rotated copy
        self.rect = self.image.get_rect()
        self.position = Vector2D(*pos)

        self.moving = 0 # won't move at start of game
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.speed = 10
        self.state = "still"
        degree = 0
        self.reinit()

    def reinit(self):
        self.state = "still"
        self.movepos = [0,0]

    def update(self):
        newpos = self.rect.move(self.movepos)
        if self.area.contains(newpos):
            self.rect = newpos

        # Create a vector pointing at the key position
        key_pos = self.rect.move(self.movepos)

        # Create a vector pointing from the image towards the key direction
        rel_key_pos = key_pos - self.position

        # Calculate the angle between the y_axis and the vector pointing from the
        # image towards the mouse position
        y_axis = Vector2D(0, -1)
        angle = -y_axis.get_angle(rel_key_pos) # Negating bc pygame rotates counter-clockwise

        # Create the rotated copy
        self.image = pygame.transform.rotate(self.original_image, angle).convert() # Angle is absolute value!!

        # Make sure your rect represent the actual Surface
        self.rect = self.image.get_rect()

        # Since the dimension probably changed you should move its center back to where it was.
        self.rect.center = self.position.x, self.position.y
        pygame.event.pump()


    def accelerate(self):
        self.speed += 1
        self.state = "accelerate"


    def decelerate(self):
        self.speed -= 1
        self.state = "decelerate"

    def moveleft(self):
        self.movepos[0]-=(self.speed)
        self.state="moveleft"

    def moveright(self):
        self.movepos[0]+=(self.speed)
        self.state="moveright"

class Bullet():
    pass

class Asteroid():
    pass

class Background(pygame.sprite.Sprite):
    def __init__(self, image_file, location):
        pygame.sprite.Sprite.__init__(self)  #call Sprite initializer
        self.image = pygame.image.load(image_file)
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = location

# main event loop
def main():
    # initialize screen
    pygame.init()
    pygame.mixer.init()
    pygame.key.set_repeat(500,30)

    screen = pygame.display.set_mode((1280, 1024))
    pygame.display.set_caption('Asteroids')
    pygame.mouse.set_visible(0)

    # make background

    background = Background('data\stars1.jpg', [0,0])

    # prepare background music
    pygame.mixer.music.load('data\patakasmusic.wav')
    pygame.mixer.music.play(-1)

    # load player sprite
    global player
    player = Player('data\ship.png', [0,0])
    # initialize player sprite
    playersprite = pygame.sprite.RenderPlain((player))

    # initialize clock
    clock = pygame.time.Clock()

    # event loop
    while 1:
        clock.tick(60)
        # event loop
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            elif event.type == KEYDOWN:
                if event.key == K_UP:
                    player.accelerate()
                if event.key == K_DOWN:
                    player.decelerate()
                if event.key == K_LEFT:
                    player.moveleft()
                if event.key == K_RIGHT:
                    player.moveright()
            elif event.type == KEYUP:
                if event.key == K_UP or event.key == K_DOWN:
                    player.movepos = [0,0]




        screen.fill([255, 255, 255])
        screen.blit(background.image, background.rect)
        screen.blit(player.image, (500,500))
        playersprite.draw(screen)
        playersprite.update()

        pygame.display.update()
        playersprite.update()

        pygame.display.flip()

if __name__ == '__main__': main()

I'd use another vector to store the direction of the sprite and also add an angle_speed attribute. When the user wants to turn the ship, set the angle_speed to the desired value in degrees and in the update method rotate the direction vector by the angle speed. To move the sprite, multiply the direction by self.speed to get the velocity and add it to the self.position , then update the rect.

I recommend to use the pygame.math.Vector2 class, since it has more features and is also pretty fast because it's implemented in C.

import math
import pygame as pg
from pygame.math import Vector2


class Player(pg.sprite.Sprite):

    def __init__(self, pos=(420, 420)):
        super(Player, self).__init__()
        self.image = pg.Surface((70, 50), pg.SRCALPHA)
        pg.draw.polygon(self.image, (50, 120, 180), ((0, 0), (0, 50), (70, 25)))
        self.original_image = self.image
        self.rect = self.image.get_rect(center=pos)
        self.position = Vector2(pos)
        self.direction = Vector2(1, 0)  # A unit vector pointing rightward.
        self.speed = 2
        self.angle_speed = 0
        self.angle = 0

    def update(self):
        if self.angle_speed != 0:
            # Rotate the direction vector and then the image.
            self.direction.rotate_ip(self.angle_speed)
            self.angle += self.angle_speed
            self.image = pg.transform.rotate(self.original_image, -self.angle)
            self.rect = self.image.get_rect(center=self.rect.center)
        # Update the position vector and the rect.
        self.position += self.direction * self.speed
        self.rect.center = self.position


def main():
    pg.init()
    screen = pg.display.set_mode((1280, 720))
    player = Player((420, 420))
    playersprite = pg.sprite.RenderPlain((player))

    clock = pg.time.Clock()
    done = False
    while not done:
        clock.tick(60)
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.KEYDOWN:
                if event.key == pg.K_UP:
                    player.speed += 1
                elif event.key == pg.K_DOWN:
                    player.speed -= 1
                elif event.key == pg.K_LEFT:
                    player.angle_speed = -4
                elif event.key == pg.K_RIGHT:
                    player.angle_speed = 4
            elif event.type == pg.KEYUP:
                if event.key == pg.K_LEFT:
                    player.angle_speed = 0
                elif event.key == pg.K_RIGHT:
                    player.angle_speed = 0

        playersprite.update()

        screen.fill((30, 30, 30))
        playersprite.draw(screen)
        pg.display.flip()

if __name__ == '__main__':
    main()
    pg.quit()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM