#include "stdafx.h" #include "Character.h" Character::Character(const CharacterDesc& characterDesc) : m_CharacterDesc{ characterDesc }, m_MoveAcceleration(characterDesc.maxMoveSpeed / characterDesc.moveAccelerationTime), m_FallAcceleration(characterDesc.maxFallSpeed / characterDesc.fallAccelerationTime) {} void Character::Initialize(const SceneContext& /*sceneContext*/) { //Controller m_pControllerComponent = AddComponent(new ControllerComponent(m_CharacterDesc.controller)); //Camera const auto pCamera = AddChild(new FixedCamera()); m_pCameraComponent = pCamera->GetComponent(); m_pCameraComponent->SetActive(true); //Uncomment to make this camera the active camera pCamera->GetTransform()->Translate(0.f, m_CharacterDesc.controller.height * .5f, 0.f); } void Character::Update(const SceneContext& sceneContext) { if (m_pCameraComponent->IsActive()) { //constexpr float epsilon{ 0.01f }; //Constant that can be used to compare if a float is near zero //*************** //HANDLE INPUT //## Input Gathering (move) XMFLOAT2 move{0.f, 0.f}; //move.y should contain a 1 (Forward) or -1 (Backward) based on the active input (check corresponding actionId in m_CharacterDesc) if (sceneContext.pInput->IsActionTriggered(m_CharacterDesc.actionId_MoveForward)) { move.y = 1; } else if (sceneContext.pInput->IsActionTriggered(m_CharacterDesc.actionId_MoveBackward)) { move.y = -1; } //Optional: if move.y is near zero (abs(move.y) < epsilon), you could use the Left ThumbStickPosition.y for movement if (abs(move.y) < FLT_EPSILON) { move.y = InputManager::GetThumbstickPosition().y; } //move.x should contain a 1 (Right) or -1 (Left) based on the active input (check corresponding actionId in m_CharacterDesc) if (sceneContext.pInput->IsActionTriggered(m_CharacterDesc.actionId_MoveRight)) { move.x = 1; } else if (sceneContext.pInput->IsActionTriggered(m_CharacterDesc.actionId_MoveLeft)) { move.x = -1; } //Optional: if move.x is near zero (abs(move.x) < epsilon), you could use the Left ThumbStickPosition.x for movement if (abs(move.x) < FLT_EPSILON) { move.x = InputManager::GetThumbstickPosition().x; } //## Input Gathering (look) XMFLOAT2 look{ 0.f, 0.f }; bool mouseMoved{ false }; //Only if the Left Mouse Button is Down > if (InputManager::IsMouseButton(InputState::down, VK_LBUTTON)) { // Store the MouseMovement in the local 'look' variable (cast is required) const auto& mouseMove = InputManager::GetMouseMovement(); look.x = static_cast(mouseMove.x); look.y = static_cast(mouseMove.y); mouseMoved = mouseMove.x != 0 || mouseMove.y != 0; //Optional: in case look.x AND look.y are near zero, you could use the Right ThumbStickPosition for look if (!mouseMoved) { look = InputManager::GetThumbstickPosition(false); } } //************************ //GATHERING TRANSFORM INFO //Retrieve the TransformComponent //Retrieve the forward & right vector (as XMVECTOR) from the TransformComponent TransformComponent* transformComp = GetTransform(); XMVECTOR forward = XMLoadFloat3(&transformComp->GetForward()); XMVECTOR right = XMLoadFloat3(&transformComp->GetRight()); //*************** //CAMERA ROTATION //Adjust the TotalYaw (m_TotalYaw) & TotalPitch (m_TotalPitch) based on the local 'look' variable //Make sure this calculated on a framerate independent way and uses CharacterDesc::rotationSpeed. const float elapsedSec = sceneContext.pGameTime->GetElapsed(); m_TotalYaw += look.x * m_CharacterDesc.rotationSpeed * elapsedSec; m_TotalPitch += look.y * m_CharacterDesc.rotationSpeed * elapsedSec; //Rotate this character based on the TotalPitch (X) and TotalYaw (Y) GetTransform()->Rotate(m_TotalPitch, m_TotalYaw, 0); //******** //MOVEMENT //## Horizontal Velocity (Forward/Backward/Right/Left) //Calculate the current move acceleration for this frame (m_MoveAcceleration * ElapsedTime) float deltaMoveAcceleration = m_MoveAcceleration * elapsedSec; //If the character is moving (= input is pressed) if (abs(move.y) > FLT_EPSILON || abs(move.x) > FLT_EPSILON) { //Calculate & Store the current direction (m_CurrentDirection) >> based on the forward/right vectors and the pressed input XMVECTOR currentDir = XMVectorZero(); forward = XMVectorScale(forward, move.y); right = XMVectorScale(right, move.x); currentDir = XMVectorAdd(currentDir, forward); currentDir = XMVectorAdd(currentDir, right); XMStoreFloat3(&m_CurrentDirection, currentDir); //Increase the current MoveSpeed with the current Acceleration (m_MoveSpeed) m_MoveSpeed += deltaMoveAcceleration; //Make sure the current MoveSpeed stays below the maximum MoveSpeed (CharacterDesc::maxMoveSpeed) if (m_MoveSpeed > m_CharacterDesc.maxMoveSpeed) m_MoveSpeed = m_CharacterDesc.maxMoveSpeed; } else //Else (character is not moving, or stopped moving) { //Decrease the current MoveSpeed with the current Acceleration (m_MoveSpeed) m_MoveSpeed -= deltaMoveAcceleration; //Make sure the current MoveSpeed doesn't get smaller than zero if (m_MoveSpeed < 0) m_MoveSpeed = 0; } //Now we can calculate the Horizontal Velocity which should be stored in m_TotalVelocity.xz //Calculate the horizontal velocity (m_CurrentDirection * MoveSpeed) XMVECTOR horizontalVelocity = XMVectorScale(XMLoadFloat3(&m_CurrentDirection), m_MoveSpeed); XMFLOAT3 result; XMStoreFloat3(&result, horizontalVelocity); //Set the x/z component of m_TotalVelocity (horizontal_velocity x/z) //It's important that you don't overwrite the y component of m_TotalVelocity (contains the vertical velocity) m_TotalVelocity.x = result.x; m_TotalVelocity.z = result.z; //## Vertical Movement (Jump/Fall) //If the Controller Component is NOT grounded (= freefall) if (!m_pControllerComponent->GetCollisionFlags().isSet(PxControllerCollisionFlag::eCOLLISION_DOWN) /* || raycast for more responsiveness*/) { //Decrease the y component of m_TotalVelocity with a fraction (ElapsedTime) of the Fall Acceleration (m_FallAcceleration) m_TotalVelocity.y -= m_FallAcceleration * elapsedSec; //Make sure that the minimum speed stays above -CharacterDesc::maxFallSpeed (negative!) if (m_TotalVelocity.y < -m_CharacterDesc.maxFallSpeed) m_TotalVelocity.y = -m_CharacterDesc.maxFallSpeed; } else if (sceneContext.pInput->IsActionTriggered(m_CharacterDesc.actionId_Jump)) //Else If the jump action is triggered { //Set m_TotalVelocity.y equal to CharacterDesc::JumpSpeed m_TotalVelocity.y = m_CharacterDesc.JumpSpeed; } else //Else (=Character is grounded, no input pressed) { //m_TotalVelocity.y is zero m_TotalVelocity.y = 0; } //************ //DISPLACEMENT //The displacement required to move the Character Controller (ControllerComponent::Move) can be calculated using our TotalVelocity (m/s) //Calculate the displacement (m) for the current frame and move the ControllerComponent XMVECTOR deltaHorizontalVelocity = XMVectorScale(XMLoadFloat3(&m_TotalVelocity), elapsedSec); XMFLOAT3 displacement; XMStoreFloat3(&displacement, deltaHorizontalVelocity); m_pControllerComponent->Move(displacement); //The above is a simple implementation of Movement Dynamics, adjust the code to further improve the movement logic and behaviour. //Also, it can be usefull to use a seperate RayCast to check if the character is grounded (more responsive) } } void Character::DrawImGui() { if (ImGui::CollapsingHeader("Character")) { ImGui::Text(std::format("Move Speed: {:0.1f} m/s", m_MoveSpeed).c_str()); ImGui::Text(std::format("Fall Speed: {:0.1f} m/s", m_TotalVelocity.y).c_str()); ImGui::Text(std::format("Move Acceleration: {:0.1f} m/s2", m_MoveAcceleration).c_str()); ImGui::Text(std::format("Fall Acceleration: {:0.1f} m/s2", m_FallAcceleration).c_str()); const float jumpMaxTime = m_CharacterDesc.JumpSpeed / m_FallAcceleration; const float jumpMaxHeight = (m_CharacterDesc.JumpSpeed * jumpMaxTime) - (0.5f * (m_FallAcceleration * powf(jumpMaxTime, 2))); ImGui::Text(std::format("Jump Height: {:0.1f} m", jumpMaxHeight).c_str()); ImGui::Dummy({ 0.f,5.f }); if (ImGui::DragFloat("Max Move Speed (m/s)", &m_CharacterDesc.maxMoveSpeed, 0.1f, 0.f, 0.f, "%.1f") || ImGui::DragFloat("Move Acceleration Time (s)", &m_CharacterDesc.moveAccelerationTime, 0.1f, 0.f, 0.f, "%.1f")) { m_MoveAcceleration = m_CharacterDesc.maxMoveSpeed / m_CharacterDesc.moveAccelerationTime; } ImGui::Dummy({ 0.f,5.f }); if (ImGui::DragFloat("Max Fall Speed (m/s)", &m_CharacterDesc.maxFallSpeed, 0.1f, 0.f, 0.f, "%.1f") || ImGui::DragFloat("Fall Acceleration Time (s)", &m_CharacterDesc.fallAccelerationTime, 0.1f, 0.f, 0.f, "%.1f")) { m_FallAcceleration = m_CharacterDesc.maxFallSpeed / m_CharacterDesc.fallAccelerationTime; } ImGui::Dummy({ 0.f,5.f }); ImGui::DragFloat("Jump Speed", &m_CharacterDesc.JumpSpeed, 0.1f, 0.f, 0.f, "%.1f"); ImGui::DragFloat("Rotation Speed (deg/s)", &m_CharacterDesc.rotationSpeed, 0.1f, 0.f, 0.f, "%.1f"); bool isActive = m_pCameraComponent->IsActive(); if(ImGui::Checkbox("Character Camera", &isActive)) { m_pCameraComponent->SetActive(isActive); } } }