Implement activity SFX and decouple camera orbit from movement facing

This commit is contained in:
Kelsi 2026-02-03 19:49:56 -08:00
parent dfc29cad10
commit 8bc50818a9
9 changed files with 592 additions and 12 deletions

View file

@ -36,6 +36,7 @@ std::optional<float> selectReachableFloor(const std::optional<float>& terrainH,
CameraController::CameraController(Camera* cam) : camera(cam) {
yaw = defaultYaw;
facingYaw = defaultYaw;
pitch = defaultPitch;
reset();
}
@ -90,6 +91,7 @@ void CameraController::update(float deltaTime) {
}
if (nowTurnLeft || nowTurnRight) {
camera->setRotation(yaw, pitch);
facingYaw = yaw;
}
// Select physics constants based on mode
@ -129,12 +131,14 @@ void CameraController::update(float deltaTime) {
// Get camera axes — project forward onto XY plane for walking
glm::vec3 forward3D = camera->getForward();
glm::vec3 forward = glm::normalize(glm::vec3(forward3D.x, forward3D.y, 0.0f));
glm::vec3 right = camera->getRight();
right.z = 0.0f;
if (glm::length(right) > 0.001f) {
right = glm::normalize(right);
bool cameraDrivesFacing = rightMouseDown || mouseAutorun;
if (cameraDrivesFacing) {
facingYaw = yaw;
}
float moveYaw = cameraDrivesFacing ? yaw : facingYaw;
float moveYawRad = glm::radians(moveYaw);
glm::vec3 forward(std::cos(moveYawRad), std::sin(moveYawRad), 0.0f);
glm::vec3 right(-std::sin(moveYawRad), std::cos(moveYawRad), 0.0f);
// Toggle sit/crouch with X or C key (edge-triggered) — only when UI doesn't want keyboard
bool xDown = !uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_X) || input.isKeyPressed(SDL_SCANCODE_C));
@ -209,6 +213,27 @@ void CameraController::update(float deltaTime) {
if (verticalVelocity > 0.0f) verticalVelocity = 0.0f;
}
// Prevent sinking/clipping through world floor while swimming.
std::optional<float> floorH;
if (terrainManager) {
floorH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
}
if (wmoRenderer) {
auto wh = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, targetPos.z + 5.0f);
if (wh && (!floorH || *wh > *floorH)) floorH = wh;
}
if (m2Renderer) {
auto mh = m2Renderer->getFloorHeight(targetPos.x, targetPos.y, targetPos.z);
if (mh && (!floorH || *mh > *floorH)) floorH = mh;
}
if (floorH) {
float swimFloor = *floorH + 0.30f;
if (targetPos.z < swimFloor) {
targetPos.z = swimFloor;
if (verticalVelocity < 0.0f) verticalVelocity = 0.0f;
}
}
grounded = false;
} else {
swimming = false;
@ -815,6 +840,7 @@ void CameraController::reset() {
}
yaw = defaultYaw;
facingYaw = defaultYaw;
pitch = defaultPitch;
verticalVelocity = 0.0f;
grounded = true;

View file

@ -1185,6 +1185,19 @@ bool CharacterRenderer::hasAnimation(uint32_t instanceId, uint32_t animationId)
return false;
}
bool CharacterRenderer::getInstanceModelName(uint32_t instanceId, std::string& modelName) const {
auto it = instances.find(instanceId);
if (it == instances.end()) {
return false;
}
auto modelIt = models.find(it->second.modelId);
if (modelIt == models.end()) {
return false;
}
modelName = modelIt->second.data.name;
return !modelName.empty();
}
bool CharacterRenderer::attachWeapon(uint32_t charInstanceId, uint32_t attachmentId,
const pipeline::M2Model& weaponModel, uint32_t weaponModelId,
const std::string& texturePath) {

View file

@ -28,6 +28,7 @@
#include "game/zone_manager.hpp"
#include "audio/music_manager.hpp"
#include "audio/footstep_manager.hpp"
#include "audio/activity_sound_manager.hpp"
#include <GL/glew.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/euler_angles.hpp>
@ -190,6 +191,7 @@ bool Renderer::initialize(core::Window* win) {
// Create music manager (initialized later with asset manager)
musicManager = std::make_unique<audio::MusicManager>();
footstepManager = std::make_unique<audio::FootstepManager>();
activitySoundManager = std::make_unique<audio::ActivitySoundManager>();
LOG_INFO("Renderer initialized");
return true;
@ -266,6 +268,10 @@ void Renderer::shutdown() {
footstepManager->shutdown();
footstepManager.reset();
}
if (activitySoundManager) {
activitySoundManager->shutdown();
activitySoundManager.reset();
}
zoneManager.reset();
@ -647,13 +653,16 @@ void Renderer::update(float deltaTime) {
// Sync character model position/rotation and animation with follow target
if (characterInstanceId > 0 && characterRenderer && cameraController && cameraController->isThirdPerson()) {
characterRenderer->setInstancePosition(characterInstanceId, characterPosition);
if (activitySoundManager) {
std::string modelName;
if (characterRenderer->getInstanceModelName(characterInstanceId, modelName)) {
activitySoundManager->setCharacterVoiceProfile(modelName);
}
}
// Keep facing decoupled from lateral movement:
// face camera when RMB is held, or with forward/back intent.
if (cameraController->isRightMouseHeld() ||
cameraController->isMovingForward() ||
cameraController->isMovingBackward()) {
characterYaw = cameraController->getYaw();
// Movement-facing comes from camera controller and is decoupled from LMB orbit.
if (cameraController->isMoving() || cameraController->isRightMouseHeld()) {
characterYaw = cameraController->getFacingYaw();
} else if (targetPosition && !emoteActive && !cameraController->isMoving()) {
// Face target when idle
glm::vec3 toTarget = *targetPosition - characterPosition;
@ -737,6 +746,51 @@ void Renderer::update(float deltaTime) {
}
}
// Activity SFX: animation/state-driven jump, landing, and swim loops/splashes.
if (activitySoundManager) {
activitySoundManager->update(deltaTime);
if (cameraController && cameraController->isThirdPerson()) {
bool grounded = cameraController->isGrounded();
bool jumping = cameraController->isJumping();
bool falling = cameraController->isFalling();
bool swimming = cameraController->isSwimming();
bool moving = cameraController->isMoving();
if (!sfxStateInitialized) {
sfxPrevGrounded = grounded;
sfxPrevJumping = jumping;
sfxPrevFalling = falling;
sfxPrevSwimming = swimming;
sfxStateInitialized = true;
}
if (jumping && !sfxPrevJumping && !swimming) {
activitySoundManager->playJump();
}
if (grounded && !sfxPrevGrounded) {
bool hardLanding = sfxPrevFalling;
activitySoundManager->playLanding(resolveFootstepSurface(), hardLanding);
}
if (swimming && !sfxPrevSwimming) {
activitySoundManager->playWaterEnter();
} else if (!swimming && sfxPrevSwimming) {
activitySoundManager->playWaterExit();
}
activitySoundManager->setSwimmingState(swimming, moving);
sfxPrevGrounded = grounded;
sfxPrevJumping = jumping;
sfxPrevFalling = falling;
sfxPrevSwimming = swimming;
} else {
activitySoundManager->setSwimmingState(false, false);
sfxStateInitialized = false;
}
}
// Update M2 doodad animations
if (m2Renderer) {
m2Renderer->update(deltaTime);
@ -1011,6 +1065,9 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
if (footstepManager) {
footstepManager->initialize(assetManager);
}
if (activitySoundManager) {
activitySoundManager->initialize(assetManager);
}
cachedAssetManager = assetManager;
}
@ -1079,6 +1136,11 @@ bool Renderer::loadTerrainArea(const std::string& mapName, int centerX, int cent
footstepManager->initialize(cachedAssetManager);
}
}
if (activitySoundManager && cachedAssetManager) {
if (!activitySoundManager->isInitialized()) {
activitySoundManager->initialize(cachedAssetManager);
}
}
// Wire WMO, M2, and water renderer to camera controller
if (cameraController && wmoRenderer) {