【问题标题】:Puzzled by my sprite's unequal +/ - velocity对我的精灵不相等的 +/ - 速度感到困惑
【发布时间】:2021-05-07 02:41:26
【问题描述】:

我的游戏中有两个精灵。僵尸精灵完美运行,以 1.0 的速度向各个方向移动。然而,尽管所有方向上的所有值都是 2.25,但我的玩家精灵在 x/y 正方向上移动得更慢。

对于我的生活,我似乎看不出这里有什么问题。

完整的工作代码:

import pygame
import random
import sys
import itertools
import math
import time
from datetime import datetime
from librarymodified import *
from pygame.locals import *

# prints text using the supplied font
def print_text(font, x, y, text, color=(255,255,255)):
    imgText = font.render(text, True, color)
    DISPLAYSURF.blit(imgText, (x,y))

# MySprite class extends pygame.sprite.Sprite
class MySprite(pygame.sprite.Sprite):

    def __init__(self, target):
        pygame.sprite.Sprite.__init__(self) #extend the base Sprite class
        self.master_image = None
        self.frame = 0
        self.old_frame = -1
        self.frame_width = 1
        self.frame_height = 1
        self.first_frame = 0
        self.last_frame = 0
        self.columns = 1
        self.last_time = 0
        self.direction = 0
        self.times_hit = 0
        self.direction = 0
        self.velocity = Point(0.0,0.0)

    # times_hit property
    def _get_times_hit(self): return self.times_hit
    def _set_times_hit(self, hits): self.times_hit += hits
    number_hits_taken = property(_get_times_hit, _set_times_hit)

    #X property
    def _getx(self): return self.rect.x
    def _setx(self,value): self.rect.x = value
    X = property(_getx,_setx)

    #Y property
    def _gety(self): return self.rect.y
    def _sety(self,value): self.rect.y = value
    Y = property(_gety,_sety)

    #position property
    def _getpos(self): return self.rect.topleft
    def _setpos(self,pos): self.rect.topleft = pos
    position = property(_getpos,_setpos)


    def load(self, filename, width, height, columns):
        self.master_image = pygame.image.load(filename).convert_alpha()
        self.frame_width = width
        self.frame_height = height
        self.rect = Rect(0,0,width,height)
        self.columns = columns
        #try to auto-calculate total frames
        rect = self.master_image.get_rect()
        self.last_frame = (rect.width // width) * (rect.height // height) - 1

    def update(self, current_time, rate=30):
        #update animation frame number
        if current_time > self.last_time + rate:
            self.frame += 1
            if self.frame > self.last_frame:
                self.frame = self.first_frame
            self.last_time = current_time

        #build current frame only if it changed
        if self.frame != self.old_frame:
            frame_x = (self.frame % self.columns) * self.frame_width
            frame_y = (self.frame // self.columns) * self.frame_height
            rect = Rect(frame_x, frame_y, self.frame_width, self.frame_height)
            self.image = self.master_image.subsurface(rect)
            self.old_frame = self.frame

    def __str__(self):
        return str(self.frame) + "," + str(self.first_frame) + \
               "," + str(self.last_frame) + "," + str(self.frame_width) + \
               "," + str(self.frame_height) + "," + str(self.columns) + \
               "," + str(self.rect)

#Point class
class Point(object):
    def __init__(self, x, y):
        self.__x = x
        self.__y = y

    #X property
    def getx(self): return self.__x
    def setx(self, x): self.__x = x
    x = property(getx, setx)

    #Y property
    def gety(self): return self.__y
    def sety(self, y): self.__y = y
    y = property(gety, sety)

    def __str__(self):
        return "{X:" + "{:.0f}".format(self.__x) + \
            ",Y:" + "{:.0f}".format(self.__y) + "}"


def calc_velocity(direction, vel = 1.0):
    velocity = Point(0, 0)
    if direction == 0: # North
        velocity.y = -vel
    elif direction == 2: # East
        velocity.x = vel
    elif direction == 4: # south
        velocity.y = vel
    elif direction == 6: # west
        velocity.x = -vel
    return velocity

def reverse_direction(sprite):
    if sprite.direction == 0:
        sprite.direction = 4
    elif sprite.direction == 2:
        sprite.direction = 6
    elif sprite.direction == 4:
        sprite.direction = 0
    elif sprite.direction == 6:
        sprite.direction = 2




# main
pygame.init()

DISPLAYSURF = pygame.display.set_mode((800,600))
pygame.display.set_caption("Collision Detection")
font = pygame.font.SysFont(None, 36)
fpsclock = pygame.time.Clock()
fps = 30

# create sprite groups
zombie_group = pygame.sprite.Group()
player_group = pygame.sprite.Group()
health_group = pygame.sprite.Group()

# create player sprite
player = MySprite(DISPLAYSURF)
player.load("farmer walk.png", 96, 96, 8)
player.position = (80,80)
player.direction = 4
player_group.add(player)

# create zombie sprite
zombie_image = pygame.image.load("zombie walk.png").convert_alpha()
for i in range(1):
    zombie = MySprite(DISPLAYSURF)
    zombie.load("zombie walk.png", 96, 96, 8)
    zombie.position = (random.randint(0, 700), random.randint(0, 500))
    zombie.direction = random.randint(0,3) * 2
    zombie_group.add(zombie)

# create health sprite
health = MySprite(DISPLAYSURF)
health.load("health.png", 32, 32, 1)
health.position = (400, 300)
health_group.add(health)

game_over = False
player_moving = False
player_health = 100

# colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 0)
YELLOW = (255, 255, 0)

##DISPLAYSURF.fill(BLACK)
##pygame.mouse.set_visible(True)


# event loop
while True:
    ticks = pygame.time.get_ticks() # ms since pygame.init() called
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        if event.type == MOUSEMOTION:
            mousex, mousey = event.pos

    # keyboard polling
    keys = pygame.key.get_pressed()
    if keys[K_ESCAPE]:
        pygame.quit()
        sys.exit()
    elif keys[K_UP] or keys[K_w]:
        player.direction = 0
        player_moving = True
    elif keys[K_RIGHT] or keys[K_d]:
        player.direction = 2
        player_moving = True
    elif keys[K_LEFT] or keys[K_a]:
        player.direction = 6
        player_moving = True
    elif keys[K_DOWN] or keys[K_s]:
        player.direction = 4
        player_moving = True
    else:
        player_moving = False

    # these things should not happen if game is over
    if not game_over:
        # update player sprite
        player_group.update(ticks, 50)
        # use player direction to calculate frame range
        player.first_frame = player.direction * player.columns
        player.last_frame = player.first_frame + player.columns-1
        if player.frame < player.first_frame:
            player.frame = player.first_frame

        if not player_moving:
            # stop animating when player is not moving
            player.frame = player.first_frame = player.last_frame
        else:
            # move player in that direction
            player.velocity = calc_velocity(player.direction, 1.5)
            player.velocity.x *= 1.5
            player.velocity.y *= 1.5

        # manually move player
        if player_moving:
            player.X += player.velocity.x
            player.Y += player.velocity.y
            if player.X <0: player.X = 0
            elif player.X > 700: player.X = 700
            if player.Y <0: player.Y = 0
            elif player.Y > 500: player.Y = 500


        # update zombie sprites
        zombie_group.update(ticks, 50)

        # manually update zombies
        for z in zombie_group:
            # set zombie animation range
            z.first_frame = z.direction * z.columns
            z.last_frame = z.first_frame + z.columns-1
            if z.frame < z.first_frame:
                z.frame = z.first_frame
            z.velocity = calc_velocity(z.direction)

            # keep zombie on screen
            z.X += z.velocity.x
            z.Y += z.velocity.y
            if z.X < 0 or z.X > 700 or z.Y < 0 or z.Y > 500:
                reverse_direction(z)

        # check for sprite collision
        attacker = 0
        attacker = pygame.sprite.spritecollideany(player, zombie_group)
        if attacker != None:
            # more precise check
            if pygame.sprite.collide_rect_ratio(0.5)(player, attacker):
                player_health -= 10
                if attacker.X < player.X: attacker.X -= 10
                elif attacker.X > player.X: attacker.X += 10
            else:
                attacker = None

        # update health drop
        health_group.update(ticks, 50)

        # check for collision with health
        if pygame.sprite.collide_rect_ratio(0.5)(player, health):
            player_health += 30
            if player_health >100: player_health = 100
            health.X = random.randint(0, 700)
            health.Y = random.randint(0, 500)

        # is player dead?
        if player_health <= 0:
            game_over = True

        # clear screen
        DISPLAYSURF.fill((50,50,100))

        # draw sprites
        player_group.draw(DISPLAYSURF)
        zombie_group.draw(DISPLAYSURF)
        health_group.draw(DISPLAYSURF)

        # draw energy bar
        pygame.draw.rect(DISPLAYSURF, WHITE, (299, 555, 203, 31), 2)
        pygame.draw.rect(DISPLAYSURF, GREEN, (301, 557, player_health * 2, 28))

        # print zombie and player velocities for purpose of testing
        print_text(font, 350, 460, "Zombie X vel: " +\
                   str(zombie.velocity.x) + "\nY vel: " +\
                   str(zombie.velocity.y))
        print_text(font, 350, 500, "Player X vel: " +\
                   str(player.velocity.x) + "\nY vel: " +\
                   str(player.velocity.y))

    if game_over:
        print_text(font, 300, 200, "G A M E   O V E R")

    pygame.display.update()
    fpsclock.tick(fps)

【问题讨论】:

    标签: pygame sprite


    【解决方案1】:

    问题

    精灵组正在使用精灵的 rect 属性绘制您的精灵。 pygame Rect 对象只能保存整数,因此它会截断所有浮点数。

    假设您有一个x = 5

    • 如果添加1.1x += 1.1 x = x + 1.1 x = 5 + 1.1 x = 6.1 将被截断为x = 6。它增加了 1。
    • 如果你减去1.1x -= 1.1 x = x - 1.1 x = 5 - 1.1 x = 3.9 将被截断为x = 3。它减少了 2 个。

    换句话说:你向左移动的速度比向右移动的快(同样的原理也适用于负数)。这是一个演示它的示例:

    import pygame
    pygame.init()
    
    
    class Player(pygame.sprite.Sprite):
        def __init__(self, group):
            super(Player, self).__init__(group)
            self.image = pygame.Surface((32, 32))
            self.rect = self.image.get_rect()
    
    
    screen = pygame.display.set_mode((100, 100))
    group = pygame.sprite.Group()
    player = Player(group)
    clock = pygame.time.Clock()
    
    while True:
        clock.tick(10)
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    x = player.rect.x + 1.1
                    print("Actual x:", x)
                    player.rect.x = player.rect.x + 1.1
                    print("Truncated x:", player.rect.x)
                elif event.key == pygame.K_LEFT:
                    x = player.rect.x - 1.1
                    print("Actual x:", x)
                    player.rect.x = player.rect.x - 1.1
                    print("Truncated x:", player.rect.x)
    
        screen.fill((255, 255, 255))
        group.draw(screen)
        pygame.display.update()
    

    解决方案

    使用浮点数表示位置很棒;它可以使精灵每帧移动少于一个像素(如果您的游戏每秒更新 120 次并且您希望您的精灵每秒仅移动 30 像素)。

    但是,您必须补偿 rect 对象无法容纳它们的事实。最直接的解决方案是拥有一个属性position,它以浮点精度跟踪精灵的位置。然后在每次更新时将矩形更改为属性的位置。像这样:

    import pygame
    pygame.init()
    
    
    class Player(pygame.sprite.Sprite):
        def __init__(self, group):
            super(Player, self).__init__(group)
            self.image = pygame.Surface((32, 32))
            self.rect = self.image.get_rect()
            self.position = self.rect.x  # Or whatever point of the rect you want the position to be.
    
    
    screen = pygame.display.set_mode((100, 100))
    group = pygame.sprite.Group()
    player = Player(group)
    clock = pygame.time.Clock()
    
    while True:
        clock.tick(10)
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    player.position += 1.1
                    player.rect.x = player.position
                elif event.key == pygame.K_LEFT:
                    player.position -= 1.1
                    player.rect.x = player.position
    
        screen.fill((255, 255, 255))
        group.draw(screen)
        pygame.display.update()
    

    我只展示了这种运动在 x 轴上的工作原理,但在 y 轴上完全一样。

    【讨论】:

    • 太棒了!现在完全明白了。非常感谢!
    【解决方案2】:

    好的。我认为问题在于每次更新移动精灵的像素数向下舍入,因此 2.25 变为 2 像素,-2.25 变为 -3 像素。我认为移动一小部分像素没有意义。 如果您将第 229 - 233 行更改为

    else:
        # move player in that direction
        player.velocity = calc_velocity(player.direction, 2.0)
        player.velocity.x *= 2.0
        player.velocity.y *= 2.0
    

    速度现在是一个整数,不会有舍入问题。虽然它更快。是否有某些原因为什么您不只是将速度作为整数而不是浮点平方?

    【讨论】:

    • 谢谢。欣赏!
    猜你喜欢
    • 2019-11-21
    • 1970-01-01
    • 1970-01-01
    • 2021-09-05
    • 2018-10-28
    • 2016-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多