Merge feat/controls — sticks al menú, accept/cancel remappable, facing real
This commit is contained in:
commit
fe916d263f
3 files changed
+135
-22
No files matched your search
@@ -330,15 +330,18 @@ void Player::updateFacingAndTurn(float delta_time, float vx, float vy) {
|
||||
intermediate_cardinal_ = intermediateFor(INPUT_CARD);
|
||||
turning_remaining_ms_ = static_cast<float>(turn_config.duration_ms);
|
||||
applyCardinal(intermediate_cardinal_);
|
||||
} else if (vx != 0.0F) {
|
||||
// Tria de facing: prevalença lateral, EAST/WEST segons el signe
|
||||
// de vx; verticals purs van a NORTH/SOUTH. En aturar-se
|
||||
// conservem `facing_` per a l'animació stand.
|
||||
facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
|
||||
} else if (vy < 0.0F) {
|
||||
// Tria de facing amb prevalença vertical: si hi ha component
|
||||
// vertical, el sprite mira NORTH/SOUTH encara que també hi
|
||||
// haja horitzontal. Així una diagonal (down+right, up+left,
|
||||
// etc.) mostra el sprite mirant amunt o avall, no de costat.
|
||||
// Només quan el moviment és purament horitzontal anem a
|
||||
// EAST/WEST. En aturar-se conservem `facing_` per a stand.
|
||||
facing_ = Facing::NORTH;
|
||||
} else if (vy > 0.0F) {
|
||||
facing_ = Facing::SOUTH;
|
||||
} else if (vx != 0.0F) {
|
||||
facing_ = (vx < 0.0F) ? Facing::WEST : Facing::EAST;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,6 +1100,31 @@ void Player::applyMovement(float delta_time) {
|
||||
|
||||
clampToPlayfield();
|
||||
|
||||
// Refinem el `facing_` amb el desplaçament REAL del frame (post
|
||||
// col·lisió/snap), no amb la velocitat d'entrada: si l'usuari prem
|
||||
// diagonal en un pasillo, `tryMove` ja haurà anul·lat l'eix tapat
|
||||
// per la paret, així que el sprite mira en la direcció realment
|
||||
// efectuada (la d'input cru no ho sap). En AIRBORNE el facing es
|
||||
// preserva des del despegue (vegeu el comentari de `integrateJump`),
|
||||
// i durant un gir actiu el cardinal intermedi mana, així que els
|
||||
// saltem. Si DX==DY==0 (parat o totalment bloquejat) preservem el
|
||||
// facing previ per a l'animació stand. La prioritat vertical en
|
||||
// diagonals lliures és intranscendent en pasillos (un dels dos eixos
|
||||
// serà sempre 0); manté coherència amb la tria d'`updateFacingAndTurn`.
|
||||
if (!AIRBORNE && turning_remaining_ms_ <= 0.0F) {
|
||||
const float DX = getPosX() - FRAME_START_X;
|
||||
const float DY = getPosY() - FRAME_START_Y;
|
||||
if (DY < 0.0F) {
|
||||
facing_ = Facing::NORTH;
|
||||
} else if (DY > 0.0F) {
|
||||
facing_ = Facing::SOUTH;
|
||||
} else if (DX < 0.0F) {
|
||||
facing_ = Facing::WEST;
|
||||
} else if (DX > 0.0F) {
|
||||
facing_ = Facing::EAST;
|
||||
}
|
||||
}
|
||||
|
||||
const bool MOVED = (getPosX() != FRAME_START_X) || (getPosY() != FRAME_START_Y);
|
||||
// `being_pushed_` força `stand_*`: el jugador es desplaça sense input,
|
||||
// animar-lo com a caminant faria creure que està caminant quan de
|
||||
|
||||
@@ -445,14 +445,14 @@ void ServiceMenu::activateCurrent(int direction) {
|
||||
|
||||
namespace {
|
||||
|
||||
// Resol el botó SERVICE configurat per al mando que ha emès aquest
|
||||
// event. -1 si no troba cap match (mando desconegut o desconnectat
|
||||
// mentrestant).
|
||||
auto serviceButtonForEvent(SDL_JoystickID jid) -> int {
|
||||
// Resol el botó configurat per a una acció concreta (SERVICE / ATTACK
|
||||
// / JUMP) al mando que ha emès aquest event. -1 si no troba cap match
|
||||
// (mando desconegut o desconnectat mentrestant).
|
||||
auto actionButtonForEvent(SDL_JoystickID jid, GamepadBindings::Action action) -> int {
|
||||
for (const auto& info : GamepadManager::get().getAll()) {
|
||||
if (info.pad == nullptr) { continue; }
|
||||
if (SDL_GetJoystickID(SDL_GetGamepadJoystick(info.pad)) != jid) { continue; }
|
||||
return GamepadBindings::get().getButton(info.name, info.path, GamepadBindings::Action::SERVICE);
|
||||
return GamepadBindings::get().getButton(info.name, info.path, action);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -467,16 +467,89 @@ void ServiceMenu::playAcceptSound() {
|
||||
Audio::get()->playSound(UiConfig::get().menuAcceptSound(), Audio::Group::INTERFACE);
|
||||
}
|
||||
|
||||
void ServiceMenu::handleGamepadAxisMotion(const SDL_GamepadAxisEvent& ev) {
|
||||
// Llindars amb histèresi: cal travessar STICK_THRESHOLD per "entrar" a
|
||||
// un extrem, però només es torna a la zona neutra quan baixa de
|
||||
// STICK_RELEASE. Així evitem que petits tremolors al voltant del
|
||||
// llindar facin spam d'events. Valors triats per donar la mateixa
|
||||
// sensació que el stick del jugador (deadzone=12000) però una mica
|
||||
// més estrictes per a la navegació discreta del menú.
|
||||
constexpr Sint16 STICK_THRESHOLD = 16000;
|
||||
constexpr Sint16 STICK_RELEASE = 8000;
|
||||
|
||||
std::int8_t* zone = nullptr;
|
||||
bool horizontal = false;
|
||||
switch (ev.axis) {
|
||||
case SDL_GAMEPAD_AXIS_LEFTX:
|
||||
zone = &lx_zone_;
|
||||
horizontal = true;
|
||||
break;
|
||||
case SDL_GAMEPAD_AXIS_LEFTY:
|
||||
zone = &ly_zone_;
|
||||
horizontal = false;
|
||||
break;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTX:
|
||||
zone = &rx_zone_;
|
||||
horizontal = true;
|
||||
break;
|
||||
case SDL_GAMEPAD_AXIS_RIGHTY:
|
||||
zone = &ry_zone_;
|
||||
horizontal = false;
|
||||
break;
|
||||
default:
|
||||
return; // Ignorem L2/R2 i la resta.
|
||||
}
|
||||
|
||||
// Càlcul de la zona nova: si el valor està més enllà del llindar fort,
|
||||
// entra a l'extrem; si està dins de la zona morta, torna a 0; altrament
|
||||
// manté la zona prèvia (banda d'histèresi).
|
||||
std::int8_t new_zone = *zone;
|
||||
if (ev.value > STICK_THRESHOLD) {
|
||||
new_zone = +1;
|
||||
} else if (ev.value < -STICK_THRESHOLD) {
|
||||
new_zone = -1;
|
||||
} else if (std::abs(ev.value) < STICK_RELEASE) {
|
||||
new_zone = 0;
|
||||
}
|
||||
if (new_zone == *zone) { return; }
|
||||
*zone = new_zone;
|
||||
if (new_zone == 0) { return; } // Soltar el stick no dispara res.
|
||||
|
||||
if (horizontal) {
|
||||
activateCurrent(new_zone);
|
||||
playAcceptSound();
|
||||
} else {
|
||||
moveCursor(new_zone);
|
||||
playSelectSound();
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceMenu::handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev) {
|
||||
// El botó SERVICE (per defecte BACK; remapejable per dispositiu via
|
||||
// `GamepadBindings`) tanca el menú sencer. Cada mando pot tenir el
|
||||
// seu propi mapeig — resolem-lo a partir del joystick instance ID.
|
||||
const int SERVICE_BTN = serviceButtonForEvent(ev.which);
|
||||
if (SERVICE_BTN >= 0 && static_cast<int>(ev.button) == SERVICE_BTN) {
|
||||
// Tots tres botons rellevants (SERVICE/ATTACK/JUMP) són remapejables per
|
||||
// dispositiu via `GamepadBindings`. Resolem-los una sola vegada a partir
|
||||
// del joystick instance ID i comparem; els defaults (WEST=ATTACK,
|
||||
// SOUTH=JUMP, BACK=SERVICE) preserven el comportament històric.
|
||||
const int SERVICE_BTN = actionButtonForEvent(ev.which, GamepadBindings::Action::SERVICE);
|
||||
const int ACCEPT_BTN = actionButtonForEvent(ev.which, GamepadBindings::Action::ATTACK);
|
||||
const int CANCEL_BTN = actionButtonForEvent(ev.which, GamepadBindings::Action::JUMP);
|
||||
const int BTN = static_cast<int>(ev.button);
|
||||
|
||||
if (SERVICE_BTN >= 0 && BTN == SERVICE_BTN) {
|
||||
close();
|
||||
playSelectSound();
|
||||
return;
|
||||
}
|
||||
if (ACCEPT_BTN >= 0 && BTN == ACCEPT_BTN) {
|
||||
activateCurrent(+1);
|
||||
playAcceptSound();
|
||||
return;
|
||||
}
|
||||
if (CANCEL_BTN >= 0 && BTN == CANCEL_BTN) {
|
||||
popPage();
|
||||
playSelectSound();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ev.button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
moveCursor(-1);
|
||||
@@ -491,16 +564,9 @@ void ServiceMenu::handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev) {
|
||||
playAcceptSound();
|
||||
break;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
case SDL_GAMEPAD_BUTTON_EAST:
|
||||
case SDL_GAMEPAD_BUTTON_NORTH:
|
||||
activateCurrent(+1);
|
||||
playAcceptSound();
|
||||
break;
|
||||
case SDL_GAMEPAD_BUTTON_WEST:
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
popPage();
|
||||
playSelectSound();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -570,6 +636,11 @@ auto ServiceMenu::handleEvent(const SDL_Event& ev) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ev.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
|
||||
handleGamepadAxisMotion(ev.gaxis);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ev.type != SDL_EVENT_KEY_DOWN) { return false; }
|
||||
|
||||
const auto SC = ev.key.scancode;
|
||||
|
||||
@@ -208,6 +208,12 @@ class ServiceMenu {
|
||||
// sota el llindar de clang-tidy.
|
||||
void handleGamepadButtonDown(const SDL_GamepadButtonEvent& ev);
|
||||
|
||||
// Navegació via sticks analògics. Treballa per edge-detection: cada
|
||||
// eix recorda en quina "zona" està (negativa / neutra / positiva) i
|
||||
// sols dispara una acció quan creua de neutra a un extrem, igual que
|
||||
// un botó del DPAD. Aplicada tant al stick esquerre com al dret.
|
||||
void handleGamepadAxisMotion(const SDL_GamepadAxisEvent& ev);
|
||||
|
||||
// Sons d'UI: SELECT acompanya navegació (amunt/avall) i back; ACCEPT
|
||||
// acompanya modificació d'opció (esquerra/dreta/retorn). Els noms de
|
||||
// recurs venen d'`UiConfig` i el preload el fa el Director.
|
||||
@@ -270,6 +276,14 @@ class ServiceMenu {
|
||||
float transition_progress_{0.0F}; // 0 → 1 durant la transició de pàgina.
|
||||
bool capturing_{false}; // True mentre esperem una tecla per a un KEY_BIND.
|
||||
|
||||
// Estat per eix del stick per a edge-detection: -1, 0, +1 segons la
|
||||
// zona on viu el valor actual. Un canvi de zona dispara una acció
|
||||
// (només quan entra a un extrem); reset a 0 quan torna al centre.
|
||||
std::int8_t lx_zone_{0};
|
||||
std::int8_t ly_zone_{0};
|
||||
std::int8_t rx_zone_{0};
|
||||
std::int8_t ry_zone_{0};
|
||||
|
||||
// Sub-mòdul de captura de botons del mando. Quan està actiu absorbeix
|
||||
// tots els events de mando (BUTTON_DOWN/UP, AXIS_MOTION) i, en acabar,
|
||||
// persisteix la nova configuració via `GamepadBindings`. Replica el
|
||||
|
||||
Reference in new issue
Block a user