1
\$\begingroup\$

I am trying to create 2d side scrolling platform similar to Megaman. Here is the progress so far: project Red box is the spriteholder while blue box is the hitbox of the character. The sprite I use is random idle sprite I found on the internet.

My question will revolve around the quick movement, where the character will accelerate faster than their usual speed for certain distance / time / sprite index. This is my take currently on the problem:

def dash_left(self):
    self.sprite.index = 0
    self.sprite.rect.midbottom = self.rect.midbottom
    if self.dash_current_use > 0:
        self.vx = -1.5 * self.speed
        self.ax = (self.speed * 0.5)
        self.status = -3
        self.dash_current_use -= 1

on the def main():

if event.type == g.KEYDOWN:
    if event.key == g.K_q:
        player.dash_left()

if event.type == g.KEYUP:
    if event.key == g.K_q and player.vx < 0:
        player.stop_left()

The problem is that in my take, the dash will go continuously as long as the player pressed Q, or upon collision. My aim is for the dash to be able to stop itself, preferred when a certain distance has been passed or when ax (velocity variable from outside force) is greater or lesser - depending on the direction - than the vx (velocity variable from input.)

\$\endgroup\$
1
\$\begingroup\$

I think it would be a good idea to implement the dashing with a finite state machine, because that will make the transitions between the different states cleaner and the program will be easier to extend. There's a great chapter about state machines in Game Programming Patterns and I highly recommend that you read it before or while you're checking out my example.

Here's the explanation of the example:

I define some states (the MoveState, DashState, IdleState classes) and assign the current state instance to the self.state attribute of the Player class. The player object (its state machine) can only be in one state.

These states have their own methods for event handling and updating and are able to switch the state of the player object. To switch the state, I create a new state instance in the handle_event method when a specific key gets pressed and return it to the player, otherwise if None gets returned the state will not be changed.

The player object is responisble for passing the events to the current state and for calling its update method.

Also, in the DashState I don't return the new state from the handle_event method but change the player's state in the update method, because we need to wait for the timer which is updated there.

For the dash timer you need to pass the delta time (dt = time difference since last clock.tick call) to the update method where it is used to decrement the self.dash_timer attribute. When the timer is done the next state will be assigned to the player.state attribute.

import pygame as pg
from pygame.math import Vector2


PLAYER_IMAGE = pg.Surface((30, 50))
PLAYER_IMAGE.fill(pg.Color('dodgerblue1'))


# Classes for the three states (dash, idle, and move).

class DashState:

    def __init__(self, velocity_x, next_state):
        self.dash_timer = .5  # The dash will last .5 seconds.
        self.velocity_x = velocity_x
        self.next_state = next_state

    def handle_event(self, player, event):
        # Can queue the Move- or IdleState as the
        # next state after the dashing.
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_a:
                self.next_state = MoveState(-4)
            elif event.key == pg.K_d:
                self.next_state = MoveState(4)
        elif event.type == pg.KEYUP:
            if event.key == pg.K_a:
                self.next_state = IdleState()
            elif event.key == pg.K_d:
                self.next_state = IdleState()

    def update(self, player, dt):
        # Decrement the timer each frame.
        self.dash_timer -= dt
        # Update the position of the player.
        player.pos.x += self.velocity_x
        player.rect.center = player.pos

        if self.dash_timer <= 0:  # Once the timer is done...
            # switch to the queued state.
            player.state = self.next_state


class MoveState:

    def __init__(self, velocity_x):
        self.velocity_x = velocity_x

    def handle_event(self, player, event):
        # Can switch to Dash or Idle or change the direction.
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_q:
                return DashState(-8, player.state)
            elif event.key == pg.K_e:
                return DashState(8, player.state)
            elif event.key == pg.K_a:
                self.velocity_x = -4
            elif event.key == pg.K_d:
                self.velocity_x = 4
        elif event.type == pg.KEYUP:
            if event.key == pg.K_a and self.velocity_x < 0:
                return IdleState()
            elif event.key == pg.K_d and self.velocity_x > 0:
                return IdleState()

    def update(self, player, dt):
        player.pos.x += self.velocity_x
        player.rect.center = player.pos


class IdleState:

    def handle_event(self, player, event):
        # Can switch to Move or Dash.
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_a:
                return MoveState(-4)
            elif event.key == pg.K_d:
                return MoveState(4)
            elif event.key == pg.K_q:
                return DashState(-8, player.state)
            elif event.key == pg.K_e:
                return DashState(8, player.state)

    def update(self, player, dt):
        pass


class Player(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.image = PLAYER_IMAGE
        self.rect = self.image.get_rect(center=pos)
        self.vel = Vector2(0, 0)
        self.pos = Vector2(pos)
        # Start in the IdleState.
        self.state = IdleState()

    def handle_event(self, event):
        new_state = self.state.handle_event(self, event)
        # Check if the state's handle_event method returned a new
        # state object, if yes, assign it to self.state.
        self.state = new_state if new_state is not None else self.state

    def update(self, dt):
        # Update the current state.
        self.state.update(self, dt)


def main():
    pg.init()
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    player = Player((600, 300), all_sprites)
    dt = 0
    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            player.handle_event(event)

        all_sprites.update(dt)

        screen.fill((30, 30, 30))
        all_sprites.draw(screen)

        pg.display.flip()
        dt = clock.tick(30) / 1000  # delta time in milliseconds.


if __name__ == '__main__':
    main()
    pg.quit()
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.