diff --git a/docs/general_config.md b/docs/general_config.md index 25f01608..ed370521 100644 --- a/docs/general_config.md +++ b/docs/general_config.md @@ -272,6 +272,10 @@ Note: The sound played when `referenceTargetPlayFireSound` is set to `true` is t |--------------------|------|------------------------------------------------------------------------------------| |`moveRate` |m/s | The rate of player motion, set this parameter to `0` to disable player motion | |`moveScale` |`Vector2`| A scaler for X/Y player-space motion (set to 0 to lock forward/back, strafe motion)| +|`accelerationEnabled` |`bool`| Whether or not acceleration is enabled. | +|`movementAcceleration` |m/s^2 | Player acceleration during movement (defaults to 3.0) | +|`movementDeceleration` |m/s^2 | Player deceleration while stopping (defaults to 7.0) | +|`sprintMultiplier` |`float`| Multiplies with players move rate when player is sprinting (set 1 to turn off) | |`playerAxisLock` |`Array`| Axis aligned motion lock for player | |`turnScale` |`Vector2`| A scaler for horizontal/vertical player mouse motion (set to 0 to disable) | |`playerHeight` |m | The height of the player above the ground when "standing" | @@ -282,6 +286,9 @@ Note: The sound played when `referenceTargetPlayFireSound` is set to `true` is t |`playerGravity` |m/s^2| The graivty vector that impacts the player | |`disablePlayerMotionBetweenTrials`|`bool`|Don't allow the player to move when not in a trial? | |`resetPlayerPositionBetweenTrials`|`bool`|Respawn the player to their original position between trials? | +|`headBobEnabled` |`bool` | Whether or not the headbob effect is enabled. | +|`headBobAmplitude` |m | This is additive to the crouch/playerHeight, Camera Y will bob within headBobAmplitude/2 and -headBobAmplitude/2.| +|`headBobFrequency` |`float`| How fast the headBob will happen. scales up with movement speed. | ``` "moveRate": 0.0, // Player move rate (0 for no motion) @@ -294,6 +301,13 @@ Note: The sound played when `referenceTargetPlayFireSound` is set to `true` is t "jumpInterval": 0.5, // Minimum jump interval "jumpTouch": true, // Require touch for jump "playerGravity": Vector3(0.0, -10.0, 0.0), // Player gravity +"sprintMultiplier": 1, // Player spring is disabled by default. (1 for no multiplied movement speed) +"accelerationEnabled": false, // Acceleration is disabled by default +"movementAcceleration": 3.0f // Acceleration during movement +"movementDeceleration": 7.0f // Deceleration while stopping +"headBobEnabled": false, // HeadBob is disabled by default +"headBobAmplitude": 0.1f, // HeadBob Amplitude +"headBobFrequency": 1.0f, // HeadBob Frequency "disablePlayerMotionBetweenTrials": false, // Don't allow the player to move in between trials "resetPlayerPositionBetweenTrials": false, // Respawn the player in the starting location between trials ``` diff --git a/source/FPSciApp.cpp b/source/FPSciApp.cpp index a6152d5e..dbbb466b 100644 --- a/source/FPSciApp.cpp +++ b/source/FPSciApp.cpp @@ -616,6 +616,13 @@ void FPSciApp::initPlayer(bool setSpawnPosition) { // Set player values from session config player->moveRate = &sessConfig->player.moveRate; + player->sprintMultiplier = &sessConfig->player.sprintMultiplier; + player->headBobEnabled = &sessConfig->player.headBobEnabled; + player->headBobAmplitude = &sessConfig->player.headBobAmplitude; + player->headBobFrequency = &sessConfig->player.headBobFrequency; + player->accelerationEnabled = &sessConfig->player.accelerationEnabled; + player->movementAcceleration = &sessConfig->player.movementAcceleration; + player->movementDeceleration = &sessConfig->player.movementDeceleration; player->moveScale = &sessConfig->player.moveScale; player->axisLock = &sessConfig->player.axisLock; player->jumpVelocity = &sessConfig->player.jumpVelocity; @@ -1137,6 +1144,10 @@ bool FPSciApp::onEvent(const GEvent& event) { scene()->typedEntity("player")->setJumpPressed(true); foundKey = true; } + else if (keyMap.map["sprint"].contains(ksym)) { + scene()->typedEntity("player")->setSprintPressed(true); + foundKey = true; + } } } else if ((event.type == GEventType::KEY_UP)) { @@ -1145,6 +1156,10 @@ bool FPSciApp::onEvent(const GEvent& event) { scene()->typedEntity("player")->setCrouched(false); foundKey = true; } + else if (keyMap.map["sprint"].contains(ksym)) { + scene()->typedEntity("player")->setSprintPressed(false); + foundKey = true; + } } } if (foundKey) { diff --git a/source/FpsConfig.cpp b/source/FpsConfig.cpp index 3d103415..64014b98 100644 --- a/source/FpsConfig.cpp +++ b/source/FpsConfig.cpp @@ -178,6 +178,13 @@ void PlayerConfig::load(FPSciAnyTableReader reader, int settingsVersion) { case 1: reader.getIfPresent("moveRate", moveRate); reader.getIfPresent("moveScale", moveScale); + reader.getIfPresent("sprintMultiplier", sprintMultiplier); + reader.getIfPresent("headBobEnabled", headBobEnabled); + reader.getIfPresent("headBobAmplitude", headBobAmplitude); + reader.getIfPresent("headBobFrequency", headBobFrequency); + reader.getIfPresent("accelerationEnabled", accelerationEnabled); + reader.getIfPresent("movementAcceleration", movementAcceleration); + reader.getIfPresent("movementDeceleration", movementDeceleration); reader.getIfPresent("turnScale", turnScale); reader.getIfPresent("playerHeight", height); reader.getIfPresent("crouchHeight", crouchHeight); diff --git a/source/FpsConfig.h b/source/FpsConfig.h index dbe1f84a..df676d47 100644 --- a/source/FpsConfig.h +++ b/source/FpsConfig.h @@ -72,6 +72,13 @@ class PlayerConfig { Array axisLock = { false, false, false }; ///< World-space player motion axis lock bool stillBetweenTrials = false; ///< Disable player motion between trials? bool resetPositionPerTrial = false; ///< Reset the player's position on a per trial basis (to scene default) + float sprintMultiplier = 1.0f; ///< Multiplies with players move rate when player is sprinting (defaults to 1x (off)) + bool headBobEnabled = false; ///< Enables/Disables the HeadBob functionality (default is false (disabled)) + float headBobAmplitude = 0.17f; ///< Determines how high/low players head will oscillate during movement (defaults to 0.17) + float headBobFrequency = 0.7f; ///< Determines how fast players head will oscillate during movement (defaults to 0.7) + bool accelerationEnabled = false; ///< Enables/Disables the acceleration/deceleration functionality (default is false (disabled)) + float movementAcceleration = 12.0f; ///< Player acceleration during movement (defaults to 12.0) + float movementDeceleration = 22.0f; ///< Player deceleration while stopping (defaults to 12.0) void load(FPSciAnyTableReader reader, int settingsVersion = 1); Any addToAny(Any a, bool forceAll = false) const; diff --git a/source/GuiElements.cpp b/source/GuiElements.cpp index 89d073f9..ceb13795 100644 --- a/source/GuiElements.cpp +++ b/source/GuiElements.cpp @@ -174,6 +174,41 @@ PlayerControls::PlayerControls(SessionConfig& config, std::function expo c->setCaptionWidth(width / 2); c->setWidth(width*0.95f); }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addCheckBox("Use Acceleration?", &(config.player.accelerationEnabled)); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addNumberBox("Movement Acceleration", &(config.player.movementAcceleration), "m/s^2", GuiTheme::LINEAR_SLIDER, 0.001f, 100.0f); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addNumberBox("Movement Deceleration", &(config.player.movementDeceleration), "m/s^2", GuiTheme::LINEAR_SLIDER, 0.001f, 100.0f); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addNumberBox("Sprint Multiplier", &(config.player.sprintMultiplier), "x", GuiTheme::LINEAR_SLIDER, 1.0f, 10.0f); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addCheckBox("Use Headbob?", &(config.player.headBobEnabled)); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addNumberBox("Headbob Amplitude", &(config.player.headBobAmplitude), "m", GuiTheme::LINEAR_SLIDER, 0.0f, 5.0f); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); + movePane->beginRow(); { + auto c = movePane->addNumberBox("Headbob Frequency", &(config.player.headBobFrequency), "x", GuiTheme::LINEAR_SLIDER, 0.0f, 10.0f); + c->setCaptionWidth(width / 2); + c->setWidth(width * 0.95f); + }movePane->endRow(); movePane->beginRow(); { auto c = movePane->addNumberBox("Jump Velocity", &(config.player.jumpVelocity), "m/s", GuiTheme::LINEAR_SLIDER, 0.0f, 50.0f, 0.1f); c->setCaptionWidth(width / 2); diff --git a/source/KeyMapping.cpp b/source/KeyMapping.cpp index e156bdc6..80f04b4a 100644 --- a/source/KeyMapping.cpp +++ b/source/KeyMapping.cpp @@ -7,6 +7,7 @@ KeyMapping::KeyMapping() { map.set("strafeLeft", Array{ (GKey)'a', GKey::LEFT }); map.set("moveBackward", Array{ (GKey)'s', GKey::DOWN }); map.set("strafeRight", Array{ (GKey)'d', GKey::RIGHT }); + map.set("sprint", Array{ GKey::LSHIFT }); map.set("openMenu", Array{ GKey::ESCAPE }); map.set("quit", Array{ GKey::KP_MINUS, GKey::PAUSE }); map.set("crouch", Array{ GKey::LCTRL }); diff --git a/source/PlayerEntity.cpp b/source/PlayerEntity.cpp index 6210138b..515ecb0e 100644 --- a/source/PlayerEntity.cpp +++ b/source/PlayerEntity.cpp @@ -90,21 +90,30 @@ void PlayerEntity::onPose(Array >& surfaceArray) { } void PlayerEntity::updateFromInput(UserInput* ui) { + + m_walkSpeed = 0; - const float walkSpeed = *moveRate * units::meters() / units::seconds(); + // Check if player is sprinting or not + if (!m_sprinting) { + m_walkSpeed = *moveRate * units::meters() / units::seconds(); + } + else { + m_walkSpeed = *moveRate * *sprintMultiplier * units::meters() / units::seconds(); + } // Get walking speed here (and normalize if necessary) - Vector3 linear = Vector3(ui->getX()*moveScale->x, 0, -ui->getY()*moveScale->y); - if (linear.magnitude() > 0) { - linear = linear.direction() * walkSpeed; + m_linearVector = Vector3(ui->getX()*moveScale->x, 0, -ui->getY()*moveScale->y); + if (m_linearVector.magnitude() > 0) { + m_gettingMovementInput = true; } + // Add jump here (if needed) RealTime timeSinceLastJump = System::time() - m_lastJumpTime; if (m_jumpPressed && timeSinceLastJump > *jumpInterval) { // Allow jumping if jumpTouch = False or if jumpTouch = True and the player is in contact w/ the map if (!(*jumpTouch) || m_inContact) { const Vector3 jv(0, *jumpVelocity * units::meters() / units::seconds(), 0); - linear += jv; + m_linearVector += jv; m_lastJumpTime = System::time(); } } @@ -115,8 +124,7 @@ void PlayerEntity::updateFromInput(UserInput* ui) { float yaw = mouseRotate.x; float pitch = mouseRotate.y; - // Set the player translation/view velocities - setDesiredOSVelocity(linear); + // Set the player view velocity setDesiredAngularVelocity(yaw, pitch); } @@ -147,6 +155,68 @@ void PlayerEntity::onSimulation(SimTime absoluteTime, SimTime deltaTime) { respawn(); } } + + if (!m_gettingMovementInput) { + + if (accelerationEnabled!= nullptr && *accelerationEnabled) { + m_acceleratedVelocity = max(m_acceleratedVelocity - *movementDeceleration * deltaTime, 0.0f); + } + else { + m_acceleratedVelocity = 0; + } + + if (m_acceleratedVelocity > 0) { + m_linearVector = m_acceleratedVelocity * m_lastDirection; + } + } + else { + + if (*accelerationEnabled) { + m_acceleratedVelocity = min(m_acceleratedVelocity + *movementAcceleration * deltaTime, m_walkSpeed); + } + else { + m_acceleratedVelocity = m_walkSpeed; + } + + m_linearVector = m_linearVector.direction() * m_acceleratedVelocity; + m_lastDirection = m_linearVector.direction(); + } + /** The HeadBob uses lerp to achieve the "Bobbing" effect. The lerp function tries to reach + the designated amplitude, but as Lerp can never reach final value, we change the polarity + when it reaches half of the designated amplitude. The lerp function will then try to reach + to the negative(-) of amplitude value. The polarity will again be changed when it reaches + half of (-amplitude), thus achieving the effect. + + The frequency (how fast the HeadBob will happen) is controlled by the headBobFrequency value + as well as the players current movement speed. So the faster the player moves, faster the + effect will be. Its also tied to deltaTime, so FPS will not effect how fast/slow HeadBob + happens. + */ + if (headBobEnabled != nullptr && *headBobEnabled && m_acceleratedVelocity > 0) { + if (!m_headBobPolarity) { + m_headBobCurrentHeight = lerp(m_headBobCurrentHeight, *headBobAmplitude, *headBobFrequency * m_acceleratedVelocity * deltaTime); + + if (m_headBobCurrentHeight >= *headBobAmplitude / 2) { + m_headBobPolarity = !m_headBobPolarity; + } + } + else { + m_headBobCurrentHeight = lerp(m_headBobCurrentHeight, -*headBobAmplitude, *headBobFrequency * m_acceleratedVelocity * deltaTime); + + if (m_headBobCurrentHeight <= -*headBobAmplitude / 2) { + m_headBobPolarity = !m_headBobPolarity; + } + } + } + // Height of the camera will reset to the original position once the player has stopped moving + else if(headBobEnabled != nullptr && *headBobEnabled && m_acceleratedVelocity <= 0){ + m_headBobCurrentHeight = lerp(m_headBobCurrentHeight, 0, *headBobFrequency * deltaTime); + } + + //Set Players Translation velocity + setDesiredOSVelocity(m_linearVector); + + m_gettingMovementInput = false; } void PlayerEntity::getConservativeCollisionTris(Array& triArray, const Vector3& velocity, float deltaTime) const { diff --git a/source/PlayerEntity.h b/source/PlayerEntity.h index 980549ce..e0a7e4e4 100644 --- a/source/PlayerEntity.h +++ b/source/PlayerEntity.h @@ -27,10 +27,22 @@ class PlayerEntity : public VisibleEntity { float m_lastJumpVelocity; float m_health = 1.0f; ///< Player health storage + float m_walkSpeed; ///< Players movement speed + + bool m_gettingMovementInput; ///< Is getting movement input from user? + bool m_headBobPolarity; ///< Is head moving up/down? + float m_headBobCurrentHeight; ///< Headbob current that gets added to camera y + bool m_inContact = false; ///< Is the player in contact w/ anything? bool m_motionEnable = true; ///< Flag to disable player motion bool m_jumpPressed = false; ///< Indicates whether jump buton was pressed + bool m_sprinting = false; ///< Is the player sprinting? + + Vector3 m_linearVector; ///< Vector for movement + Vector3 m_lastDirection; ///< Holds players last heading + float m_acceleratedVelocity; ///< Velocity after adding acceleration + PlayerEntity() {} #ifdef G3D_OSX @@ -50,14 +62,24 @@ class PlayerEntity : public VisibleEntity { float* moveRate = nullptr; ///< Player movement rate (m/s) Vector2* moveScale = nullptr; ///< Player X/Y movement scale vector (interpreted as unit vector) Array* axisLock = nullptr; ///< World-space axis lock + + bool* accelerationEnabled = nullptr; ///< Checks if acceleration/deceleration is enabled or not + float* movementAcceleration = nullptr; ///< Players rate of acceletion during movement + float* movementDeceleration = nullptr; ///< Players rate of deceleration while stopping movement + + float* sprintMultiplier = nullptr; ///< Sprint speed multiplier float* jumpVelocity = nullptr; ///< Player vertical (+Y) jump velocity float* jumpInterval = nullptr; ///< Player minimum jump interval limit - bool* jumpTouch = nullptr; ///< Require contact for jump? + bool* jumpTouch = nullptr; ///< Require contact for jump? float* height = nullptr; ///< Player height when standing float* crouchHeight = nullptr; ///< Player height when crouched + bool* headBobEnabled = nullptr; ///< Checks if headbob is enabled or not + float* headBobAmplitude = nullptr; ///< Players headbob motion amplitude + float* headBobFrequency = nullptr; ///< Players headbob motion frequency + /** \brief Computes all triangles that could be hit during a slideMove with the current \a velocity, allowing that the velocity may be decreased along some axes during movement. @@ -98,7 +120,12 @@ class PlayerEntity : public VisibleEntity { const CFrame getCameraFrame() const { CFrame f = frame(); if (notNull(height)) { - f.translation += Point3(0.0f, heightOffset(m_crouched ? *crouchHeight : *height), 0.0f); + if (*headBobEnabled) { + f.translation += Point3(0.0f, heightOffset(m_crouched ? *crouchHeight : *height) + m_headBobCurrentHeight, 0.0f); + } + else { + f.translation += Point3(0.0f, heightOffset(m_crouched ? *crouchHeight : *height), 0.0f); + } } return f; } @@ -106,7 +133,7 @@ class PlayerEntity : public VisibleEntity { void setCrouched(bool crouched) { m_crouched = crouched; }; void setJumpPressed(bool pressed=true) { m_jumpPressed = pressed; } void setMoveEnable(bool enabled) { m_motionEnable = enabled; } - + void setSprintPressed(bool sprinting) { m_sprinting = sprinting; }; void setRespawnPosition(Point3 pos) { m_respawnPosition = pos; } void setRespawnHeadingDegrees(float headingDeg) { m_spawnHeadingRadians = pif() / 180.f * headingDeg; } void setRespawnHeight(float height) { m_respawnHeight = height; }