Python lerp移动和键盘移动按钮-快速按下会导致角色卡在原位

Python lerp移动和键盘移动按钮-快速按下会导致角色卡在原位,python,pygame,Python,Pygame,所以我决定在pygame中为我的矩形角色添加线性插值。我几天前见过勒普,我不确定我是否错过了什么 问题是: 例如,当我向右移动时。速度插值以达到所需的最大速度-(7)或(-7表示左侧)。 当我释放关键点时,速度再次插值,从最大速度到0,非常平滑。 但是如果我按下左键,当我向右移动时,插值从速度7变为0,使我的角色停止 编辑:这是整个游戏的一部分,我跳过了跳跃和窗口边界碰撞检测等功能。但是这个代码仍然复制了我不想要的运动 import pygame import sys import math i

所以我决定在pygame中为我的矩形角色添加线性插值。我几天前见过勒普,我不确定我是否错过了什么

问题是: 例如,当我向右移动时。速度插值以达到所需的最大速度-(7)或(-7表示左侧)。 当我释放关键点时,速度再次插值,从最大速度到0,非常平滑。 但是如果我按下左键,当我向右移动时,插值从速度7变为0,使我的角色停止

编辑:这是整个游戏的一部分,我跳过了跳跃和窗口边界碰撞检测等功能。但是这个代码仍然复制了我不想要的运动

import pygame
import sys
import math
import datetime
from pygame.locals import *

class Vector(object):
    ''' Performs vector aritmetics
    '''
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self, v):
        x = self.x + v.x
        y = self.y + v.y
        return Vector(x, y)

class GroundData(object):
    ''' Ground data structure.
    Creates a ground data structure and her component's.
    '''
    def __init__(self):
        # Vectors
        self.position = Vector(0, WINDOWHEIGHT - WINDOWHEIGHT / 3)
        self.size = Vector(WINDOWWIDTH, WINDOWHEIGHT-self.position.y)

        # Ground data structure
        self.color = (128, 128, 128) # Gray
        self.rect = pygame.Rect((self.position.x, self.position.y),
                             (self.size.x, self.size.y))
        self.ground = {'shape': self.rect, 'color': self.color}

    def draw(self):
        ''' Draw's the ground shape and color using pygame.draw.rect(...).
        '''
        pygame.draw.rect(WINDOWSURFACE, self.ground['color'],
                                    self.ground['shape'])


class PlayerData(object):
    ''' Player data structure.
    Creates a player data structure and handles few actions.
    '''
    def __init__(self):
        self.ground = GroundData()

        # Vectors
        self.size = Vector(50, 70)
        self.position = Vector(
            15, self.ground.position.y - self.size.y + 1) # + 1 forced collision
        self.velocity = Vector(0, 0)
        self.velocity_goal = Vector(0, 0)
        self.gravity = Vector(0, 3)

        # Player data structure
        self.color = (0, 100, 0) # Dark Green
        self.rect = pygame.Rect((self.position.x, self.position.y),
                                (self.size.x, self.size.y))
        self.player = {'shape': self.rect, 'color': self.color}

    def is_colliding_ground(self):
        ''' Returns true if player shape is colliding with a ground.
        '''
        if self.position.y + self.size.y >= self.ground.position.y:
            return True
        return False

    def approach(self, vel_goal, vel_curr, dt):
        difference = vel_goal - vel_curr
        if difference > dt:
            return vel_curr + dt
        if difference < -dt:
            return vel_curr - dt
        return vel_goal

    def update(self, dt):
        self.velocity.x = self.approach(self.velocity_goal.x,
                                    self.velocity.x, dt * 95)
        # Update position and velocity
        # self.position = self.position.add(self.velocity) * dt
        # If I mult (x, y) by dt I get alot slower.
        self.position = self.position.add(self.velocity)
        self.player['shape'].top = self.position.y
        self.player['shape'].left = self.position.x

    def draw(self):
        ''' Draw's the player shape and color using pygame.draw.rect(...).
        '''
        pygame.draw.rect(WINDOWSURFACE, self.player['color'],
                                    self.player['shape'])


class EventManagement(object):
    ''' Handles keyboard event's.
    Toggles player variables according to the event's.
    '''
    def __init__(self, player):
        self.player = player

    def is_doneloop(self, flag):
        global is_doneloop
        is_doneloop = flag
        return is_doneloop

    def listen(self):
        ''' Toggles player variables according to keyboard/mouse input.
        '''
        for event in pygame.event.get():
            if event.type == QUIT:
                self.is_doneloop(True)

            if event.type == KEYDOWN:
                if event.key == ord('a'):
                    self.player.velocity_goal.x = -7
                if event.key == ord('d'):
                    self.player.velocity_goal.x = 7

            if event.type == KEYUP:
                if event.key == K_ESCAPE:
                    self.is_doneloop(True)
                if event.key == ord('a'):
                    self.player.velocity_goal.x = 0
                if event.key == ord('d'):
                    self.player.velocity_goal.x = 0

#-------------------------------------------------------------------------

WINDOWWIDTH = 900
WINDOWHEIGHT = 500
WINDOWSURFACE = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32)
is_doneloop = False
Clock = pygame.time.Clock()
FPS = 40

def mainloop():
    pygame.init()
    Ground = GroundData()
    Player = PlayerData()
    EventHandle = EventManagement(Player)

    prev_time = 0
    curr_time = datetime.datetime.now()

    while not is_doneloop:
        # Get deltaT
        dt = Clock.tick(FPS)
        dt = dt / 1000.0 # Convert milliseconds to seconds
        pygame.display.set_caption('FPS: %.2f' % Clock.get_fps())

        # Handle events
        EventHandle.listen()

        # Update game state
        Player.update(dt)

        # Draw
        WINDOWSURFACE.fill((0, 0, 0)) # Black
        Ground.draw()
        Player.draw()
        pygame.display.update()

    pygame.quit()
    sys.exit()

if __name__ == '__main__':
    mainloop()

我不确定矩形字符对每种可能的按键/按钮组合应该有什么反应,因为有些是不明确的,例如同时按下左键和右键时——因此下面的修改(仅适用于
EventManagement.Listen()
方法)可能不会产生完全受欢迎的行为

在修订版中,如果您在向右移动时按下左键,则在释放右键之前,视觉上不会发生任何变化,反之亦然。无论哪种方式,速度都会平稳变化

基本上,我所做的是实现对两个速度键的处理,这两个键可以处于四种状态之一,表示这两个键在向上或向下位置的所有可能组合。除此之外,还有4个不同的速度键“事件”,对应于按下或释放这些键

代码有点长,因为它必须处理每种可能状态下的每一个可能事件,但它不是我所说的复杂的。通过使其成为一种替代的实现方式,可以缩短它

更新

为了解决你在评论中提到的另一个问题,我认为你需要做几件事。首先,
update()
中注释掉的行:

不适用,因为它不是C++版本的正确翻译:

    box.vecPosition = box.vecPosition + box.vecVelocity * dt;
因为它首先将
位置
添加到
速度
,然后将结果乘以
dt
。因此,要解决它,只需使您的

    self.position.x = self.position.x + (self.velocity.x * dt)
这将允许首先通过标准计算数量
self.velocity.x*dt
,然后将其添加到
self.position.x

即使进行了这种修改,速度的变化也会很慢。我认为这是因为将
velocity\u goal.x
设置为太小的值(代码中为+/-7)。相反,使用更大的工具,如
200
。当你这样做的时候,用一个新的常数替换它们——比如说
MAX\u VELOCITY=200
,这样你只需要在一个地方更改这个值

奖金:

虽然这当然不是一个要求,但通过以下方式编写代码,您可以稍微简化/缩短和加速
Vector
类:

class Vector(object):
    ''' Performs vector arithmetic
    '''
    def __init__(self, x, y):
        self.x, self. y = x, y

    def add(self, v):
        return Vector(self.x + v.x, self.y + v.y)

    def mult(self, s):
        return Vector(s * self.x, s * self.y)
您可以更进一步,定义
向量、
向量、
向量、\uuuuuuuuuuuuuuuuuuuuuuuu()
等,然后能够更自然地使用它们

最后一个建议是替换
self.ground={'shape':self.rect,'color':self.color}
self.player={'shape':self.rect,'color':self.color}
只使用
self.shape
self.color
属性——因为将它们放在单独的字典中这样做没有什么好处,反而会降低访问它们值的速度和复杂性

更新2:

正如我所提到的,FSM逻辑可以通过表驱动来消除冗余,从而变得更加紧凑——尽管这样做也会使它更加抽象,并且可能更难以最初理解。我的意思是:

# added constants
LEFT_KEY = K_a
RIGHT_KEY = K_d
LURU, LDRU, LURD, LDRD = range(4)  # velocity states
LD, RD, LU, RU = range(4)  # velocity key events
VEL_EVENTS = {LD, RD, LU, RU}  # every velocity key event value
NEW_VELOCITY_GOAL, NEW_STATE = range(2)  # indices of EVENT_DECISION_TABLE entries
MAX_VELOCITY = 200

# non-None entries represent new velocity_goal and state value for each event for each state
EVENT_DECISION_TABLE = [
# event       LD                     RD                    LU                    RU             # cur state
    [[-MAX_VELOCITY, LDRU], [MAX_VELOCITY, LURD], [None,         None], [None,          None]], # LURU
    [[None,          None], [None,         LDRD], [0,            LURU], [None,          None]], # LDRU
    [[None,          LDRD], [None,         None], [None,         None], [0,             LURU]], # LURD
    [[None,          None], [None,         None], [MAX_VELOCITY, LURD], [-MAX_VELOCITY, LDRU]], # LDRD
]

class EventManagement(object):
    ''' Handles keyboard event's.
    Toggles player variables according to the event's.
    '''
    def __init__(self, player):
        self.player = player
        self.state = LURU

    def is_doneloop(self, flag):
        global is_doneloop
        is_doneloop = flag
        return is_doneloop

    def listen(self):
        ''' Toggles player variables according to keyboard/mouse input.
        '''
        for event in pygame.event.get():
            vel_event = None

            if event.type == QUIT:
                self.is_doneloop(True)
                break
            elif event.type == KEYDOWN:
                if event.key == LEFT_KEY:
                    vel_event = LD
                elif event.key == RIGHT_KEY:
                    vel_event = RD
            elif event.type == KEYUP:
                if event.key == K_ESCAPE:
                    self.is_doneloop(True)
                    break
                elif event.key == LEFT_KEY:
                    vel_event = LU
                elif event.key == RIGHT_KEY:
                    vel_event = RU

            if vel_event in VEL_EVENTS:
                entry = EVENT_DECISION_TABLE[self.state][vel_event]
                if entry[NEW_VELOCITY_GOAL] is not None:
                    self.player.velocity_goal.x = entry[NEW_VELOCITY_GOAL]
                if entry[NEW_STATE] is not None:
                    self.state = entry[NEW_STATE]

    #-------------------------------------------------------------------------
更新3

在观看了两个与
PlayerData.approach()
方法相关的系列视频后,我想我现在明白了它的功能(以及它的代码让我困惑的原因)

造成我困惑的主要原因是,它的
dt
参数不是一个时差值,这就是为什么在
PlayerData.update()
调用它之前,它需要乘以另一个幻数。基本上是每增量时间的增量速度(也称为加速度)。它的值与最大速度、每秒帧数(FPS)以及您希望玩家从零开始达到该速度所需的时间(平均加速度)有关

例如,每秒40帧,传递给
PlayerData.update()`的dt
值将为0.025秒,因此,如果最大速度为80或1000,则将其添加到当前速度将几乎没有效果,即使每秒执行40次

要理解它应该是什么,首先需要定义物体的平均加速度,即从站立开始达到最大速度需要多长时间。这只是它加速的最大速度/时间。如果以秒为单位测量时间,则为每秒的加速量。要知道每帧应该是多少,只需将其除以每秒帧数即可

由于对于给定的FPS,
dt
is应该是一个相当恒定的值,因此可以提前计算每帧的加速度,并将其存储为另一个命名常数

实现这一点需要进行以下更改和添加:

# more constants
MAX_VELOCITY = 200
ACCEL_TIME = 1  # secs to accelerate to max velocity (or slow down from it)
AVG_ACCEL = MAX_VELOCITY / float(ACCEL_TIME)  # per sec
ACCEL_PER_FRAME = AVG_ACCEL / FPS

class PlayerData(object):

    ### showing updated methods only

    def approach(self, vel_goal, vel_curr, accel):
        difference = vel_goal - vel_curr
        if difference > accel:
            return vel_curr + accel
        if difference < -accel:
            return vel_curr - accel
        return vel_goal

    def update(self, dt):
        self.velocity.x = self.approach(self.velocity_goal.x,
                                        self.velocity.x, ACCEL_PER_FRAME)
        self.position.x = self.position.x + (self.velocity.x * dt)

        self.player['shape'].top = self.position.y
        self.player['shape'].left = self.position.x
#更多常量
最大速度=200
加速时间=1秒以加速至最大速度(或从中减速)
平均加速度=每秒最大速度/浮动(加速度时间)
每帧加速度=平均加速度/FPS
类PlayerData(对象):
###仅显示更新的方法
def进近(自我、水平目标、水平当前、加速):
差异=水平目标-水平当前
如果差异>加速:
class Vector(object):
    ''' Performs vector arithmetic
    '''
    def __init__(self, x, y):
        self.x, self. y = x, y

    def add(self, v):
        return Vector(self.x + v.x, self.y + v.y)

    def mult(self, s):
        return Vector(s * self.x, s * self.y)
# added constants
LEFT_KEY = K_a
RIGHT_KEY = K_d
LURU, LDRU, LURD, LDRD = range(4)  # velocity states
LD, RD, LU, RU = range(4)  # velocity key events
VEL_EVENTS = {LD, RD, LU, RU}  # every velocity key event value
NEW_VELOCITY_GOAL, NEW_STATE = range(2)  # indices of EVENT_DECISION_TABLE entries
MAX_VELOCITY = 200

# non-None entries represent new velocity_goal and state value for each event for each state
EVENT_DECISION_TABLE = [
# event       LD                     RD                    LU                    RU             # cur state
    [[-MAX_VELOCITY, LDRU], [MAX_VELOCITY, LURD], [None,         None], [None,          None]], # LURU
    [[None,          None], [None,         LDRD], [0,            LURU], [None,          None]], # LDRU
    [[None,          LDRD], [None,         None], [None,         None], [0,             LURU]], # LURD
    [[None,          None], [None,         None], [MAX_VELOCITY, LURD], [-MAX_VELOCITY, LDRU]], # LDRD
]

class EventManagement(object):
    ''' Handles keyboard event's.
    Toggles player variables according to the event's.
    '''
    def __init__(self, player):
        self.player = player
        self.state = LURU

    def is_doneloop(self, flag):
        global is_doneloop
        is_doneloop = flag
        return is_doneloop

    def listen(self):
        ''' Toggles player variables according to keyboard/mouse input.
        '''
        for event in pygame.event.get():
            vel_event = None

            if event.type == QUIT:
                self.is_doneloop(True)
                break
            elif event.type == KEYDOWN:
                if event.key == LEFT_KEY:
                    vel_event = LD
                elif event.key == RIGHT_KEY:
                    vel_event = RD
            elif event.type == KEYUP:
                if event.key == K_ESCAPE:
                    self.is_doneloop(True)
                    break
                elif event.key == LEFT_KEY:
                    vel_event = LU
                elif event.key == RIGHT_KEY:
                    vel_event = RU

            if vel_event in VEL_EVENTS:
                entry = EVENT_DECISION_TABLE[self.state][vel_event]
                if entry[NEW_VELOCITY_GOAL] is not None:
                    self.player.velocity_goal.x = entry[NEW_VELOCITY_GOAL]
                if entry[NEW_STATE] is not None:
                    self.state = entry[NEW_STATE]

    #-------------------------------------------------------------------------
# more constants
MAX_VELOCITY = 200
ACCEL_TIME = 1  # secs to accelerate to max velocity (or slow down from it)
AVG_ACCEL = MAX_VELOCITY / float(ACCEL_TIME)  # per sec
ACCEL_PER_FRAME = AVG_ACCEL / FPS

class PlayerData(object):

    ### showing updated methods only

    def approach(self, vel_goal, vel_curr, accel):
        difference = vel_goal - vel_curr
        if difference > accel:
            return vel_curr + accel
        if difference < -accel:
            return vel_curr - accel
        return vel_goal

    def update(self, dt):
        self.velocity.x = self.approach(self.velocity_goal.x,
                                        self.velocity.x, ACCEL_PER_FRAME)
        self.position.x = self.position.x + (self.velocity.x * dt)

        self.player['shape'].top = self.position.y
        self.player['shape'].left = self.position.x