2

そこで、pygame で長方形のキャラクターに線形補間を追加することにしました。数日前に lerp を見ましたが、何かが欠けているかどうかわかりません。

問題は、たとえば右に移動するときです。目的の最大速度に到達するための速度補間 - (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()

私の Event クラスの UPDATE 2

class EventManager(object):
''' Event management.
Listens and handles keyboard and mouse events.
'''
def __init__(self, player):
    self.player = player

    # Player movement flags, according to keyboard/mouse state
    self.is_move_left, self.is_move_right = False, False
    self.is_jump = False

def exit_game(self):
    ''' Closes pygame and sys modules.
    A break statement follows this method to break the mainloop.
    '''
    pygame.quit()
    sys.exit()

def listener(self):
    ''' Toggles Player movement flags, according to keyboard/mouse state.
    '''
    for event in pygame.event.get():
        if event.type == QUIT:
            self.exit_game()
            break

        if event.type == KEYDOWN:
            if event.key == K_a:
                self.is_move_left = True
            elif event.key == K_d:
                self.is_move_right = True

        if event.type == KEYUP:
            if event.key == K_ESCAPE:
                self.exit_game()
                break
            if event.key == K_a:
                self.is_move_left = False
            elif event.key == K_d:
               self.is_move_right = False

def handler(self):
    ''' Set Player velocity_goal according to movement flags.
    '''
    if self.is_move_left and not self.is_move_right:
        self.player.velocity_goal.x = -self.player.MAX_VELOCITY
    elif self.is_move_right and not self.is_move_left:
        self.player.velocity_goal.x = self.player.MAX_VELOCITY
    elif not self.is_move_left and not self.is_move_right:
        self.player.velocity_goal.x = 0
4

1 に答える 1

3

左キーと右キーの両方が同時に押されている場合など、いくつかはあいまいであるため、キー/ボタンの押下のすべての可能な組み合わせに対して長方形のキャラクターがどのような反応を示すべきか正確にはわかりません-したがって、次の変更(これはメソッドに対してのみEventManagement.Listen())、求められている動作を正確に生成しない場合があります。

改訂版では、右に移動中に左ボタンを押すと、右ボタンを離すまで何も表示されず、逆の場合はその逆になります。いずれにせよ、速度はスムーズに変化します。

基本的に私が行ったことは、2 つのベロシティ キーの処理を、イベント駆動型の有限ステート マシンとして実装することです。これは、これら 2 つのキーのすべての可能な組み合わせを表す 4 つの状態のいずれかになり、上または下の位置になります。それに加えて、これらのキーが押されたり離されたりすることに対応する 4 つの異なるベロシティ キー「イベント」があります。

コードは、考えられるすべての状態で考えられるすべてのイベントを処理する必要があるため、少し長くなっていますが、複雑とは言えません。テーブル駆動型にすることで短縮できる場合があります。これは、有限状態オートマトンを実装する別の方法です。

# 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}  # all possible velocity key events
MAX_VELOCITY = 200

class EventManagement(object):
    ''' Handles keyboard events.
        Toggles player variables according to the event.
    '''
    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

            if event.type == KEYDOWN:
                if event.key == LEFT_KEY:
                    vel_event = LD
                elif event.key == RIGHT_KEY:
                    vel_event = RD

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

            if vel_event in VEL_EVENTS:
                if self.state == LURU:
                    if vel_event == LD:
                        self.player.velocity_goal.x = -MAX_VELOCITY
                        self.state = LDRU
                    elif vel_event == RD:
                        self.player.velocity_goal.x = MAX_VELOCITY
                        self.state = LURD
                elif self.state == LDRU:
                    if vel_event == RD:
                        self.state = LDRD
                    elif vel_event == LU:
                        self.state = LURU
                        self.player.velocity_goal.x = 0
                elif self.state == LURD:
                    if vel_event == LD:
                        self.state = LDRD
                    elif vel_event == RU:
                        self.state = LURU
                        self.player.velocity_goal.x = 0
                elif self.state == LDRD:
                    if vel_event == LU:
                        self.state = LURD
                        self.player.velocity_goal.x = MAX_VELOCITY
                    elif vel_event == RU:
                        self.state = LDRU
                        self.player.velocity_goal.x = -MAX_VELOCITY

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

アップデート

コメントでほのめかした他の問題に対処するには、いくつかのことを行う必要があると思います。まず、 のコメントアウトされた行update():

    # self.position = self.position.add(self.velocity) * dt

C++ バージョンの適切な翻訳ではないため、機能しません。

    box.vecPosition = box.vecPosition + box.vecVelocity * dt;

最初に加算positionvelocity、次に結果を で乗算するためdtです。それを修正するには、あなたのものを同じにしてください:

    self.position.x = self.position.x + (self.velocity.x * dt)

self.velocity.x * dtこれにより、最初に標準の演算子の優先順位を使用して数量を計算し、次にそれを に追加できself.position.xます。

この改造をしても速度の変化が鈍くなります。velocity_goal.xこれは、値が小さすぎる(コードで+/- 7)に設定したことが原因だと思います。代わりに、 のようなもっと大きなものを使用して200ください。あなたがそれをしている間に、それらを新しい定数に置き換えてください。たとえばMAX_VELOCITY = 200、値を 1 か所で変更するだけで済みます。

ボーナス:

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)

さらに進んVector.__add__()Vector.__mul__()、 などを定義すると、それらをより自然に使用できるようになります。

最後の提案は、self.ground = {'shape': self.rect, 'color': self.color}andself.player = {'shape': self.rect, 'color': self.color}をちょうどself.shapeandself.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

このメソッドに関連するMath for Game Developersシリーズのビデオをいくつか見た後、PlayerData.approach()メソッドが何をするのか (そしてなぜそのコードが私を困惑させたのか) を理解したと思います。

私の混乱の主な原因は、そのdt引数が時差の値ではないためです。そのため、PlayerData.update()呼び出す前にさらに別のマジックナンバーを掛ける必要があります。基本的には、デルタ時間あたりのデルタ速度です (加速度とも呼ばれます)。この値は、最大速度、1 秒あたりのフレーム数 (FPS)、およびプレイヤーがゼロからその速度に到達するまでの時間 (平均加速度) に関連しています。

たとえば、毎秒 40 フレームの dt value passed toPlayerData.update()` は 0.025 秒になるため、現在の速度にそれを追加しても、最大速度が 80 または 1000 の場合はほとんど効果がありません。毎秒40回。

それがどうあるべきかを理解するには、最初にオブジェクトの平均加速度を、立った状態から最大速度に達するまでにかかる時間で定義する必要があります。これは、最大速度/加速するまでの時間です。時間が秒単位で測定される場合、これは 1 秒あたりの速度アップの量です。1 フレームあたりの値を調べるには、1 秒あたりのフレーム数で割ります。

これdtは、特定の FPS に対してかなり一定である必要があるため、フレームあたりの加速度を事前に計算し、別の名前付き定数として保存することができます。

これを実装するには、次の変更と追加が必要になります。

# 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
于 2013-11-07T15:12:32.280 に答える