feat(player): estat ATTACKING amb edge-detect, animacions i spawn del whip via bus
This commit is contained in:
1 parent
5db3bb0bd0
commit
d208ba02df
7 files changed
+288
-1
No files matched your search
@@ -68,6 +68,20 @@ animations:
|
||||
frames: [8, 9, 10, 11]
|
||||
frame_ms: 100
|
||||
|
||||
# Placeholder: igual que sam.yaml, reaprofitem `walk_*` fins que
|
||||
# arribe l'art específic del braç llançant el látigo.
|
||||
attack_down:
|
||||
frames: [0, 1, 2, 3]
|
||||
frame_ms: 50
|
||||
|
||||
attack_up:
|
||||
frames: [4, 5, 6, 7]
|
||||
frame_ms: 50
|
||||
|
||||
attack:
|
||||
frames: [8, 9, 10, 11]
|
||||
frame_ms: 50
|
||||
|
||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
||||
@@ -127,3 +141,11 @@ jump:
|
||||
hurt:
|
||||
speed: 120
|
||||
brake_time_ms: 500
|
||||
|
||||
# Configuració del látigo. Mateixos valors que Sam de moment;
|
||||
# diferenciar quan equilibrem cada personatge.
|
||||
whip:
|
||||
range_tiles: 3
|
||||
swing_ms: 200
|
||||
cooldown_ms: 200
|
||||
damage: 1
|
||||
@@ -68,6 +68,22 @@ animations:
|
||||
frames: [8, 9, 10, 11]
|
||||
frame_ms: 100
|
||||
|
||||
# Animacions del braç llançant el látigo. Placeholder: reaprofiten els
|
||||
# frames de `walk_*` perquè els sprites reals encara no existeixen
|
||||
# (TODO: dibuixar-ne d'específics quan arribe l'art). El sprite del
|
||||
# látigo viu al `WhipAttack` i es renderitza superposat per z-order.
|
||||
attack_down:
|
||||
frames: [0, 1, 2, 3]
|
||||
frame_ms: 50
|
||||
|
||||
attack_up:
|
||||
frames: [4, 5, 6, 7]
|
||||
frame_ms: 50
|
||||
|
||||
attack:
|
||||
frames: [8, 9, 10, 11]
|
||||
frame_ms: 50
|
||||
|
||||
# Inèrcia només de frenada. L'acceleració és sempre instantània (soltar
|
||||
# el teclat + prémer una direcció = velocitat plena); en canvi, quan es
|
||||
# solta la direcció el jugador decau linealment a 0 en `brake_time_ms`.
|
||||
@@ -127,3 +143,16 @@ jump:
|
||||
hurt:
|
||||
speed: 120
|
||||
brake_time_ms: 500
|
||||
|
||||
# Configuració del látigo. `range_tiles` és la longitud de la hitbox
|
||||
# abans de retallar contra murs/sarcs (3 tiles ≈ 48 px). `swing_ms` és
|
||||
# la durada de l'animació de l'atac, durant la qual la hitbox del whip
|
||||
# està viva. `cooldown_ms` allarga ATTACKING amb una recuperació en què
|
||||
# el jugador segueix bloquejat però la hitbox ja ha desaparegut: castiga
|
||||
# l'ús abusiu del látigo. `damage` queda llegit per a futur (els
|
||||
# objectius reben `enterHurt` directament; cap entitat té HP encara).
|
||||
whip:
|
||||
range_tiles: 3
|
||||
swing_ms: 200
|
||||
cooldown_ms: 200
|
||||
damage: 1
|
||||
@@ -38,7 +38,7 @@ class MovingSprite : public Sprite {
|
||||
void setAccelY(float value) { ay_ = value; }
|
||||
|
||||
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
|
||||
auto getFlip() -> SDL_FlipMode { return flip_; }
|
||||
[[nodiscard]] auto getFlip() const -> SDL_FlipMode { return flip_; }
|
||||
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
|
||||
|
||||
// Moviment eix-separat amb col·lisió: aplica `dx` (i si la nova
|
||||
|
||||
@@ -68,6 +68,7 @@ Player::TurnConfig Player::turn_config;
|
||||
Player::InertiaConfig Player::inertia_config;
|
||||
Player::JumpConfig Player::jump_config;
|
||||
Player::HurtConfig Player::hurt_config;
|
||||
Player::WhipConfig Player::whip_config;
|
||||
|
||||
void Player::loadFrom(const std::string& yaml_path) {
|
||||
auto bytes = Resource::Helper::loadFile(yaml_path);
|
||||
@@ -114,6 +115,11 @@ void Player::loadFrom(const std::string& yaml_path) {
|
||||
// Bloc opcional `hurt:` — estat post-impacte. Si manca o el speed
|
||||
// és 0, l'estat HURT mai s'activa (cas degenerat).
|
||||
readHurtBlock(yaml);
|
||||
|
||||
// Bloc opcional `whip:` — paràmetres del látigo. Si manca, el
|
||||
// jugador no atacarà efectivament (ATTACKING dura 0 ms i el flanc
|
||||
// d'input es perd).
|
||||
readWhipBlock(yaml);
|
||||
}
|
||||
|
||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||
@@ -175,6 +181,16 @@ void Player::readHurtBlock(const fkyaml::node& yaml) {
|
||||
if (h.contains("brake_time_ms")) { hurt_config.brake_time_ms = h["brake_time_ms"].get_value<int>(); }
|
||||
}
|
||||
|
||||
void Player::readWhipBlock(const fkyaml::node& yaml) {
|
||||
whip_config = WhipConfig{};
|
||||
if (!yaml.contains("whip")) { return; }
|
||||
const auto& w = yaml["whip"];
|
||||
if (w.contains("range_tiles")) { whip_config.range_tiles = w["range_tiles"].get_value<float>(); }
|
||||
if (w.contains("swing_ms")) { whip_config.swing_ms = w["swing_ms"].get_value<int>(); }
|
||||
if (w.contains("cooldown_ms")) { whip_config.cooldown_ms = w["cooldown_ms"].get_value<int>(); }
|
||||
if (w.contains("damage")) { whip_config.damage = w["damage"].get_value<int>(); }
|
||||
}
|
||||
|
||||
void Player::applyCardinal(Cardinal c) {
|
||||
switch (c) {
|
||||
case Cardinal::NORTH:
|
||||
@@ -440,6 +456,7 @@ void Player::tick(float delta_time) {
|
||||
// durant el stun: així si l'usuari l'ha estat prement abans, no
|
||||
// se'l consumeix com a salt nou en sortir del stun.
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -449,11 +466,36 @@ void Player::tick(float delta_time) {
|
||||
// pulsació quan torne a terra es detecte com a flanc nou.
|
||||
if (state_ == State::AIRBORNE) {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
setVelX(intended_vx_);
|
||||
setVelY(intended_vy_);
|
||||
return;
|
||||
}
|
||||
|
||||
// En ATTACKING el jugador queda bloquejat tot el `swing_ms +
|
||||
// cooldown_ms`: ni input direccional, ni salt, ni atac, ni canvi
|
||||
// de facing. El timer descompta amb `delta_time * 1000` per
|
||||
// mantenir la convenció de delta_ms de la resta d'estats temporals.
|
||||
// Mantenim els flancs dels botons actualitzats perquè una
|
||||
// tecla mantinguda no es comporte com a flanc nou en sortir.
|
||||
if (state_ == State::ATTACKING) {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
attacking_remaining_ms_ -= delta_time * 1000.0F;
|
||||
if (attacking_remaining_ms_ <= 0.0F) {
|
||||
attacking_remaining_ms_ = 0.0F;
|
||||
state_ = State::GROUNDED;
|
||||
}
|
||||
input_vx_ = 0.0F;
|
||||
input_vy_ = 0.0F;
|
||||
intended_vx_ = 0.0F;
|
||||
intended_vy_ = 0.0F;
|
||||
motion_time_ms_ = 0.0F;
|
||||
setVelX(0.0F);
|
||||
setVelY(0.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
// En HURT el jugador està projectat sense control: descomptem
|
||||
// velocitat per inèrcia (decaiment lineal fins a 0). Quan arriba a
|
||||
// zero, tornem a GROUNDED i el frame següent ja torna a llegir input.
|
||||
@@ -461,6 +503,7 @@ void Player::tick(float delta_time) {
|
||||
// estats), que també gestiona el xoc contra murs.
|
||||
if (state_ == State::HURT) {
|
||||
(void)consumeJumpEdge();
|
||||
(void)consumeAttackEdge();
|
||||
const float DECEL = (hurt_config.brake_time_ms > 0)
|
||||
? hurt_config.initial_speed_px_per_s / (static_cast<float>(hurt_config.brake_time_ms) / 1000.0F)
|
||||
: 0.0F;
|
||||
@@ -535,6 +578,18 @@ void Player::tick(float delta_time) {
|
||||
// només disparen amb intenció explícita del jugador.
|
||||
updateFacingAndTurn(delta_time, input_vx_, input_vy_);
|
||||
|
||||
// Detecció de flanc del botó d'atac. Es processa ABANS del salt
|
||||
// perquè ATTACKING i AIRBORNE són mútuament excloents i, davant un
|
||||
// mateix-frame, l'atac té prioritat (millor castigar la pulsació
|
||||
// doble per excés que perdre l'atac per un toc accidental al salt).
|
||||
// En entrar a ATTACKING, descartem el flanc del salt per no que
|
||||
// queda penjat per a quan es desbloque.
|
||||
if (consumeAttackEdge() && whip_config.swing_ms > 0) {
|
||||
enterAttacking(currentFacingCardinal());
|
||||
(void)consumeJumpEdge();
|
||||
return;
|
||||
}
|
||||
|
||||
// Detecció de flanc del botó de salt. Es processa DESPRÉS d'aplicar
|
||||
// el facing perquè el sprite del salt usa el facing del moment del
|
||||
// despegue. `being_pushed_` no ho pot bloquejar ací (encara no s'ha
|
||||
@@ -565,6 +620,92 @@ auto Player::consumeJumpEdge() -> bool {
|
||||
return edge;
|
||||
}
|
||||
|
||||
auto Player::consumeAttackEdge() -> bool {
|
||||
// Mateix patró que `consumeJumpEdge` però per al botó d'atac. Mando:
|
||||
// SDL_GAMEPAD_BUTTON_WEST (X a Xbox / Quadrat a PlayStation), oposat
|
||||
// a SOUTH per evitar topades semàntiques.
|
||||
const bool* keys = InputState::getKeyboardState();
|
||||
const bool KEY_NOW = keys[controls_.attack];
|
||||
bool edge = (KEY_NOW && !attack_held_keyboard_);
|
||||
attack_held_keyboard_ = KEY_NOW;
|
||||
|
||||
SDL_Gamepad* pad = GamepadManager::get().getForPlayer(id_);
|
||||
const bool PAD_NOW = (pad != nullptr) && InputState::getGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST);
|
||||
if (PAD_NOW && !attack_held_gamepad_) { edge = true; }
|
||||
attack_held_gamepad_ = PAD_NOW;
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
auto Player::currentFacingCardinal() const -> Cardinal {
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return Cardinal::NORTH;
|
||||
case Facing::DOWN:
|
||||
return Cardinal::SOUTH;
|
||||
case Facing::SIDE:
|
||||
break;
|
||||
}
|
||||
// Lateral: el flip discrimina E/W. Sense flip = mira a la dreta
|
||||
// (convenció estable a tots els assets actuals).
|
||||
return (getFlip() == SDL_FLIP_HORIZONTAL) ? Cardinal::WEST : Cardinal::EAST;
|
||||
}
|
||||
|
||||
void Player::enterAttacking(Cardinal facing) {
|
||||
state_ = State::ATTACKING;
|
||||
attacking_remaining_ms_ = static_cast<float>(whip_config.swing_ms + whip_config.cooldown_ms);
|
||||
|
||||
// Garantim que facing/flip i cardinal queden coherents amb la
|
||||
// direcció triada (per si l'atac s'ha disparat amb facing residual,
|
||||
// p.ex. just després d'un canvi d'input).
|
||||
applyCardinal(facing);
|
||||
|
||||
// Geometria de spawn de la hitbox: alineada al rectangle de
|
||||
// col·lisió del jugador, no al sprite (que té perspectiva falsa amb
|
||||
// el cap dalt). La hitbox és `range_px × tile_size` (E/W) o
|
||||
// `tile_size × range_px` (N/S); el GameScene la consumeix per
|
||||
// crear el WhipAttack amb la mida adequada per direcció.
|
||||
const auto BOX = getBaseBox();
|
||||
const float RANGE_PX = whip_config.range_tiles * static_cast<float>(Map::tile_size);
|
||||
float spawn_x = 0.0F;
|
||||
float spawn_y = 0.0F;
|
||||
int dir_int = 0;
|
||||
switch (facing) {
|
||||
case Cardinal::EAST:
|
||||
spawn_x = BOX.x + BOX.w;
|
||||
spawn_y = BOX.y;
|
||||
dir_int = 3;
|
||||
break;
|
||||
case Cardinal::WEST:
|
||||
spawn_x = BOX.x - RANGE_PX;
|
||||
spawn_y = BOX.y;
|
||||
dir_int = 4;
|
||||
break;
|
||||
case Cardinal::NORTH:
|
||||
spawn_x = BOX.x;
|
||||
spawn_y = BOX.y - RANGE_PX;
|
||||
dir_int = 1;
|
||||
break;
|
||||
case Cardinal::SOUTH:
|
||||
spawn_x = BOX.x;
|
||||
spawn_y = BOX.y + BOX.h;
|
||||
dir_int = 2;
|
||||
break;
|
||||
case Cardinal::NONE:
|
||||
return;
|
||||
}
|
||||
|
||||
if (bus_ != nullptr) {
|
||||
bus_->publish("whip_spawn", WhipSpawnPayload{
|
||||
.owner = this,
|
||||
.direction = dir_int,
|
||||
.spawn_x = spawn_x,
|
||||
.spawn_y = spawn_y,
|
||||
.range_px = RANGE_PX,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Player::startJump() {
|
||||
// No saltem si l'estat no ho permet. `being_pushed_` no és segur de
|
||||
// consultar ací (es pot haver marcat o no, segons l'ordre del frame),
|
||||
@@ -788,6 +929,15 @@ void Player::applyMovement(float delta_time) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state_ == State::ATTACKING) {
|
||||
// Cap moviment durant ATTACKING. Animem (perquè avance la
|
||||
// seqüència `attack_*`) i actualitzem la clau d'animació segons
|
||||
// la sub-fase (swing vs cooldown).
|
||||
animate(delta_time);
|
||||
setCurrentAnimation(animationName(false));
|
||||
return;
|
||||
}
|
||||
|
||||
// El resolver del GameScene pot haver escalat `intended_vx_/vy_`
|
||||
// (cas push: velocitat * push_factor) o haver-les anul·lat (cas
|
||||
// head-on). Consumim-les aquí com a velocitat definitiva del frame.
|
||||
@@ -989,6 +1139,33 @@ auto Player::animationName(bool moving) const -> const char* {
|
||||
}
|
||||
return "hurt";
|
||||
}
|
||||
// ATTACKING té dues sub-fases: durant `swing_ms` el sprite és
|
||||
// `attack_*` (animació del braç llançant el látigo); durant
|
||||
// `cooldown_ms` posterior tornem al `stand_*` de la direcció. Fem
|
||||
// la decisió comparant el remaining_ms_ amb el cooldown configurat.
|
||||
if (state_ == State::ATTACKING) {
|
||||
const bool IN_SWING = (attacking_remaining_ms_ > static_cast<float>(whip_config.cooldown_ms));
|
||||
if (IN_SWING) {
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return "attack_up";
|
||||
case Facing::DOWN:
|
||||
return "attack_down";
|
||||
case Facing::SIDE:
|
||||
break;
|
||||
}
|
||||
return "attack";
|
||||
}
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return "stand_up";
|
||||
case Facing::DOWN:
|
||||
return "stand_down";
|
||||
case Facing::SIDE:
|
||||
break;
|
||||
}
|
||||
return "stand";
|
||||
}
|
||||
switch (facing_) {
|
||||
case Facing::UP:
|
||||
return moving ? "walk_up" : "stand_up";
|
||||
|
||||
@@ -24,6 +24,7 @@ class Player : public AnimatedSprite {
|
||||
SDL_Scancode left;
|
||||
SDL_Scancode right;
|
||||
SDL_Scancode jump;
|
||||
SDL_Scancode attack;
|
||||
};
|
||||
|
||||
// Estat d'alt nivell del jugador. Ortogonal a `Facing`/`Cardinal`
|
||||
@@ -44,6 +45,15 @@ class Player : public AnimatedSprite {
|
||||
AIRBORNE,
|
||||
LANDING_STUN,
|
||||
HURT,
|
||||
// Atacant amb látigo. Estat mútuament exclusiu amb AIRBORNE: no
|
||||
// es pot atacar saltant, ni saltar atacant. Cobreix swing +
|
||||
// cooldown internament: durant `whip_swing_ms` es spawneja
|
||||
// l'entitat WhipAttack i corre l'animació `attack_*`; durant
|
||||
// `whip_cooldown_ms` posterior, el jugador segueix bloquejat
|
||||
// (no input, no salt, no atac) però visualment torna a
|
||||
// `stand_*`. En total, ATTACKING dura `swing_ms + cooldown_ms`;
|
||||
// en acabar, torna a GROUNDED.
|
||||
ATTACKING,
|
||||
// Jugador que ha eixit per la porta de la sala. Despawn lògic:
|
||||
// no es renderitza, no processa input, no es mou, no col·lisiona
|
||||
// amb res ni rep contacte d'entitats. Idempotent: les crides
|
||||
@@ -503,6 +513,37 @@ class Player : public AnimatedSprite {
|
||||
// Llig el bloc `hurt:` del YAML i n'omple `hurt_config`.
|
||||
static void readHurtBlock(const fkyaml::node& yaml);
|
||||
|
||||
// Configuració del látigo del jugador llegida del bloc `whip:` del
|
||||
// YAML. `range_tiles` defineix la longitud de la hitbox abans de
|
||||
// retallar; `swing_ms` és la durada de l'animació d'atac (des
|
||||
// d'`enterAttacking` fins a desbloquejar el cooldown); `cooldown_ms`
|
||||
// afegeix una fase posterior durant la qual el jugador segueix
|
||||
// bloquejat però la hitbox ja no fa dany. `damage` queda llegit per
|
||||
// a futur; per ara els objectius reben sempre `enterHurt`.
|
||||
struct WhipConfig {
|
||||
float range_tiles{0.0F};
|
||||
int swing_ms{0};
|
||||
int cooldown_ms{0};
|
||||
int damage{0};
|
||||
};
|
||||
static WhipConfig whip_config;
|
||||
|
||||
// Llig el bloc `whip:` del YAML i n'omple `whip_config`. Si manca el
|
||||
// bloc, els camps queden a 0 i el jugador no pot atacar (el flanc
|
||||
// d'input es perd silenciosament).
|
||||
static void readWhipBlock(const fkyaml::node& yaml);
|
||||
|
||||
// Activa l'estat ATTACKING en la direcció cardinal indicada (la
|
||||
// direcció cap on mira el jugador) i publica `whip_spawn` al bus
|
||||
// perquè el GameScene cree el `WhipAttack` corresponent. Posa el
|
||||
// timer `attacking_remaining_ms_` a `swing_ms + cooldown_ms`.
|
||||
void enterAttacking(Cardinal facing);
|
||||
|
||||
// Cardinal que correspon al `(facing_, flip_)` actual del sprite —
|
||||
// la direcció cap on mira el jugador. Si `facing_ == SIDE` el
|
||||
// resultat depèn del flip; UP/DOWN són trivials. Mai retorna NONE.
|
||||
[[nodiscard]] auto currentFacingCardinal() const -> Cardinal;
|
||||
|
||||
// Activa l'estat HURT: el jugador queda projectat amb velocitat
|
||||
// inicial `hurt_config.initial_speed_px_per_s`. La direcció es tria
|
||||
// així:
|
||||
@@ -549,10 +590,24 @@ class Player : public AnimatedSprite {
|
||||
bool jump_held_keyboard_{false};
|
||||
bool jump_held_gamepad_{false};
|
||||
|
||||
// Mateixa idea per al botó d'atac: edge-detection independent
|
||||
// teclat/mando, perquè mantenir la tecla no genere atacs en cadena.
|
||||
bool attack_held_keyboard_{false};
|
||||
bool attack_held_gamepad_{false};
|
||||
|
||||
// Temps que queda dins de l'estat ATTACKING (swing + cooldown). Quan
|
||||
// arriba a 0, transició a GROUNDED. Mentre dura, el jugador queda
|
||||
// bloquejat i no rep input direccional ni de salt ni d'atac.
|
||||
float attacking_remaining_ms_{0.0F};
|
||||
|
||||
// Llig el botó de salt aquest frame (teclat + mando assignat) i,
|
||||
// amb edge-detection, retorna true si s'ha de despegar el salt.
|
||||
[[nodiscard]] auto consumeJumpEdge() -> bool;
|
||||
|
||||
// Anàleg per a l'atac: edge-detection del botó d'atac (teclat
|
||||
// `controls_.attack` + `SDL_GAMEPAD_BUTTON_WEST`).
|
||||
[[nodiscard]] auto consumeAttackEdge() -> bool;
|
||||
|
||||
// Inicia un salt: copia `intended_v*` com a velocitat fixada del salt,
|
||||
// posa `jump_vz_ = impulse`, transiciona a AIRBORNE.
|
||||
void startJump();
|
||||
|
||||
@@ -335,6 +335,7 @@ GameScene::GameScene(Config config)
|
||||
.left = Defaults::KeysGame::LEFT,
|
||||
.right = Defaults::KeysGame::RIGHT,
|
||||
.jump = Defaults::KeysGame::JUMP,
|
||||
.attack = Defaults::KeysGame::ATTACK,
|
||||
};
|
||||
const Player::Controls CONTROLS_P2{
|
||||
.up = Defaults::KeysGameP2::UP,
|
||||
@@ -342,6 +343,7 @@ GameScene::GameScene(Config config)
|
||||
.left = Defaults::KeysGameP2::LEFT,
|
||||
.right = Defaults::KeysGameP2::RIGHT,
|
||||
.jump = Defaults::KeysGameP2::JUMP,
|
||||
.attack = Defaults::KeysGameP2::ATTACK,
|
||||
};
|
||||
// Spawn lligat a la porta: tots els jugadors apareixen davant del
|
||||
// passadís de sortida. `computeSpawn` deriva les coordenades de
|
||||
|
||||
@@ -78,6 +78,7 @@ namespace Defaults {
|
||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_LEFT;
|
||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_RIGHT;
|
||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_M;
|
||||
constexpr SDL_Scancode ATTACK = SDL_SCANCODE_N;
|
||||
constexpr SDL_Scancode EXIT = SDL_SCANCODE_ESCAPE;
|
||||
} // namespace KeysGame
|
||||
|
||||
@@ -91,6 +92,7 @@ namespace Defaults {
|
||||
constexpr SDL_Scancode LEFT = SDL_SCANCODE_A;
|
||||
constexpr SDL_Scancode RIGHT = SDL_SCANCODE_D;
|
||||
constexpr SDL_Scancode JUMP = SDL_SCANCODE_V;
|
||||
constexpr SDL_Scancode ATTACK = SDL_SCANCODE_C;
|
||||
} // namespace KeysGameP2
|
||||
|
||||
namespace Player {
|
||||
|
||||
Reference in new issue
Block a user