NetworkPlayerMovementComponent.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  3. *
  4. * SPDX-License-Identifier: Apache-2.0 OR MIT
  5. *
  6. */
  7. #include <Source/Components/NetworkPlayerMovementComponent.h>
  8. #include <Source/Components/NetworkWeaponsComponent.h>
  9. #include <Source/Components/NetworkAiComponent.h>
  10. #include <Multiplayer/Components/NetworkCharacterComponent.h>
  11. #include <Source/Components/NetworkAnimationComponent.h>
  12. #include <Source/Components/NetworkMatchComponent.h>
  13. #include <Source/Components/NetworkSimplePlayerCameraComponent.h>
  14. #include <Multiplayer/Components/NetworkTransformComponent.h>
  15. #include <AzCore/Time/ITime.h>
  16. #include <AzFramework/Components/CameraBus.h>
  17. #include <AzFramework/Physics/SystemBus.h>
  18. #include <AzFramework/Physics/PhysicsScene.h>
  19. #include <AzFramework/Physics/CharacterBus.h>
  20. #include <AzFramework/Physics/Common/PhysicsTypes.h>
  21. #include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
  22. #include <PhysX/CharacterGameplayBus.h>
  23. #include <PhysX/CharacterControllerBus.h>
  24. namespace MultiplayerSample
  25. {
  26. AZ_CVAR(float, cl_WasdStickAccel, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The linear acceleration to apply to WASD inputs to simulate analog stick controls");
  27. AZ_CVAR(float, cl_AimStickScaleZ, 0.025f, nullptr, AZ::ConsoleFunctorFlags::Null, "The scaling to apply to aim and view adjustments");
  28. AZ_CVAR(float, cl_AimStickScaleX, 0.0125f, nullptr, AZ::ConsoleFunctorFlags::Null, "The scaling to apply to aim and view adjustments");
  29. /*
  30. * @cl_MaxMouseDelta should be large enough to contain the sum of mouse deltas across frames
  31. * between NetworkPlayerMovementComponentController::CreateInput() calls,
  32. * which happens at the frequency set by @cl_InputRateMs (33 times per second by default).
  33. *
  34. * The total value is then quantized using type @MouseAxis in "MultiplayerSampleTypes.h", for example:
  35. * using MouseAxis = AzNetworking::QuantizedValues<1, 2, -1, 1>;
  36. * (The four numbers within the template parameters above read as: for a single value, spend 2 bytes to quantize a value between -1 and 1.)
  37. *
  38. * Keep the ranges and the precision of QuantizedValues and float types in mind when modifying mouse input configuration values.
  39. */
  40. AZ_CVAR(float, cl_MaxMouseDelta, 128.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The sum of mouse deltas will be clamped to this maximum");
  41. #if AZ_TRAIT_CLIENT
  42. AZ_CVAR(bool, mps_botMode, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, enable bot (AI) mode for client.");
  43. AZ_CVAR(float, mps_botMinInterval, 500.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The minimum amount of time between bot control updates");
  44. AZ_CVAR(float, mps_botMaxInterval, 9500.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The maximum amount of time between bot control updates");
  45. #endif
  46. NetworkPlayerMovementComponentController::NetworkPlayerMovementComponentController(NetworkPlayerMovementComponent& parent)
  47. : NetworkPlayerMovementComponentControllerBase(parent)
  48. #if AZ_TRAIT_SERVER
  49. , m_updateAI{ [this] { UpdateAI(); }, AZ::Name{ "MovementControllerAi" } }
  50. #endif
  51. #if AZ_TRAIT_CLIENT
  52. , m_updateLocalBot{ [this] { UpdateLocalBot(); }, AZ::Name{ "MovementControllerLocalBot" } }
  53. #endif
  54. {
  55. ;
  56. }
  57. void NetworkPlayerMovementComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  58. {
  59. #if AZ_TRAIT_SERVER
  60. NetworkAiComponent* networkAiComponent = GetParent().GetNetworkAiComponent();
  61. m_aiEnabled = (networkAiComponent != nullptr) ? networkAiComponent->GetEnabled() : false;
  62. if (m_aiEnabled)
  63. {
  64. m_updateAI.Enqueue(AZ::TimeMs{ 0 }, true);
  65. m_networkAiComponentController = GetNetworkAiComponentController();
  66. }
  67. #endif
  68. #if AZ_TRAIT_CLIENT
  69. if (IsNetEntityRoleAutonomous())
  70. {
  71. if (mps_botMode)
  72. {
  73. m_updateLocalBot.Enqueue(AZ::TimeMs{ 0 }, true);
  74. }
  75. else
  76. {
  77. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(MoveFwdEventId);
  78. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(MoveBackEventId);
  79. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(MoveLeftEventId);
  80. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(MoveRightEventId);
  81. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(SprintEventId);
  82. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(JumpEventId);
  83. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(CrouchEventId);
  84. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(LookLeftRightEventId);
  85. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(LookUpDownEventId);
  86. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(ZoomInEventId);
  87. StartingPointInput::InputEventNotificationBus::MultiHandler::BusConnect(ZoomOutEventId);
  88. }
  89. }
  90. #endif
  91. // During activation the character controller is not created yet.
  92. // Connect to CharacterNotificationBus to listen when it's activated after creation.
  93. Physics::CharacterNotificationBus::Handler::BusConnect(GetEntityId());
  94. AzPhysics::SimulatedBody* worldBody = nullptr;
  95. AzPhysics::SimulatedBodyComponentRequestsBus::EventResult(worldBody, GetEntityId(), &AzPhysics::SimulatedBodyComponentRequests::GetSimulatedBody);
  96. if (worldBody)
  97. {
  98. if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
  99. {
  100. m_gravity = sceneInterface->GetGravity(worldBody->m_sceneOwner).GetZ();
  101. }
  102. }
  103. }
  104. void NetworkPlayerMovementComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
  105. {
  106. #if AZ_TRAIT_CLIENT
  107. if (IsNetEntityRoleAutonomous() && !mps_botMode)
  108. {
  109. StartingPointInput::InputEventNotificationBus::MultiHandler::BusDisconnect();
  110. }
  111. #endif
  112. }
  113. void NetworkPlayerMovementComponentController::OnCharacterActivated([[maybe_unused]] const AZ::EntityId& entityId)
  114. {
  115. // Wait until the character is activated before requesting its parameters.
  116. Physics::CharacterRequestBus::EventResult(m_stepHeight, GetEntityId(), &Physics::CharacterRequestBus::Events::GetStepHeight);
  117. PhysX::CharacterControllerRequestBus::EventResult(m_radius, GetEntityId(), &PhysX::CharacterControllerRequestBus::Events::GetRadius);
  118. PhysX::CharacterGameplayRequestBus::EventResult(
  119. m_gravityMultiplier, GetEntityId(), &PhysX::CharacterGameplayRequestBus::Events::GetGravityMultiplier);
  120. Physics::CharacterNotificationBus::Handler::BusDisconnect();
  121. }
  122. void NetworkPlayerMovementComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime)
  123. {
  124. // Inputs for your own component always exist
  125. NetworkPlayerMovementComponentNetworkInput* playerInput = input.FindComponentInput<NetworkPlayerMovementComponentNetworkInput>();
  126. // Check current game-play state
  127. INetworkMatch* networkMatchComponent = AZ::Interface<INetworkMatch>::Get();
  128. if (networkMatchComponent && (networkMatchComponent->PlayerActionsAllowed() != AllowedPlayerActions::None))
  129. {
  130. // View Axis are clamped and brought into the -1,1 range for transport across the network.
  131. // These are set if the player actions allow for rotation and/or all movement.
  132. playerInput->m_viewYaw = MouseAxis(AZStd::clamp<float>(m_viewYaw, -cl_MaxMouseDelta, cl_MaxMouseDelta) / cl_MaxMouseDelta);
  133. playerInput->m_viewPitch = MouseAxis(AZStd::clamp<float>(m_viewPitch, -cl_MaxMouseDelta, cl_MaxMouseDelta) / cl_MaxMouseDelta);
  134. }
  135. // reset accumulated amounts
  136. m_viewYaw = 0.f;
  137. m_viewPitch = 0.f;
  138. if (networkMatchComponent && (networkMatchComponent->PlayerActionsAllowed() == AllowedPlayerActions::All))
  139. {
  140. // Movement axis
  141. // Since we're on a keyboard, this adds a touch of an acceleration curve to the keyboard inputs
  142. // This is so that tapping the keyboard moves the virtual stick less than just holding it down
  143. m_forwardWeight = std::min<float>(m_forwardDown ? m_forwardWeight + cl_WasdStickAccel * deltaTime : 0.0f, 1.0f);
  144. m_leftWeight = std::min<float>(m_leftDown ? m_leftWeight + cl_WasdStickAccel * deltaTime : 0.0f, 1.0f);
  145. m_backwardWeight = std::min<float>(m_backwardDown ? m_backwardWeight + cl_WasdStickAccel * deltaTime : 0.0f, 1.0f);
  146. m_rightWeight = std::min<float>(m_rightDown ? m_rightWeight + cl_WasdStickAccel * deltaTime : 0.0f, 1.0f);
  147. playerInput->m_forwardAxis = StickAxis(m_forwardWeight - m_backwardWeight);
  148. playerInput->m_strafeAxis = StickAxis(m_leftWeight - m_rightWeight);
  149. playerInput->m_sprint = m_sprinting && (playerInput->m_forwardAxis > 0.0f); // Only sprint if we're moving forward
  150. playerInput->m_jump = m_jumping;
  151. playerInput->m_crouch = m_crouching;
  152. }
  153. else
  154. {
  155. // Don't set m_forward/left/right/backDown to 0, instead just 0 out the net-input by hand.
  156. // This way players can start the round hot out of the gate.
  157. // Keep their finger on the 'W' (forward) key, and instantly start
  158. // running when the round starts instead of having to release the 'W' key and pressing it again.
  159. playerInput->m_forwardAxis = StickAxis(0);
  160. playerInput->m_strafeAxis = StickAxis(0);
  161. playerInput->m_sprint = false;
  162. playerInput->m_jump = false;
  163. playerInput->m_crouch = false;
  164. }
  165. // reset jumping until next press. We only track when the jump is initially pressed, not that it's being held.
  166. m_jumping = false;
  167. // Just a note for anyone who is super confused by this, ResetCount is a predictable network property, it gets set on the client
  168. // through correction packets
  169. playerInput->m_resetCount = GetNetworkTransformComponentController()->GetResetCount();
  170. }
  171. void NetworkPlayerMovementComponentController::ProcessInput(Multiplayer::NetworkInput& input, float deltaTime)
  172. {
  173. // If the input reset count doesn't match the state's reset count it can mean two things:
  174. // 1) On the server: we were reset and we are now receiving inputs from the client for an old reset count
  175. // 2) On the client: we were reset and we are replaying old inputs after being corrected
  176. // In both cases we don't want to process these inputs
  177. NetworkPlayerMovementComponentNetworkInput* playerInput = input.FindComponentInput<NetworkPlayerMovementComponentNetworkInput>();
  178. if (playerInput->m_resetCount != GetNetworkTransformComponentController()->GetResetCount())
  179. {
  180. return;
  181. }
  182. NetworkWeaponsComponentNetworkInput* weaponInput = input.FindComponentInput<NetworkWeaponsComponentNetworkInput>();
  183. if ((weaponInput != nullptr) && weaponInput->m_firing.AnySet())
  184. {
  185. // Note that weaponInput is not guaranteed to exist, so we have to check for nullptr
  186. // Don't allow sprinting when the character is trying to shoot
  187. playerInput->m_sprint = false;
  188. }
  189. const bool wasOnGround = GetWasOnGround();
  190. bool onGround = GetOnGround();
  191. // Update the "on ground" state for the character.
  192. PhysX::CharacterGameplayRequestBus::EventResult(onGround, GetEntityId(), &PhysX::CharacterGameplayRequestBus::Events::IsOnGround);
  193. SetOnGround(onGround);
  194. // Track timers for how recently it's been since the player was on the ground and how recently they pressed the jump button.
  195. // These will be compared against "slop factors" to allow for a little bit of leniency in jumping to make it feel more reactive.
  196. SetSecondsSinceOnGround(onGround ? 0.0f : (GetSecondsSinceOnGround() + deltaTime));
  197. SetSecondsSinceJumpRequest(playerInput->m_jump ? 0.0f : (GetSecondsSinceJumpRequest() + deltaTime));
  198. // Update orientation
  199. AZ::Vector3 aimAngles = GetNetworkSimplePlayerCameraComponentController()->GetAimAngles();
  200. aimAngles.SetZ(NormalizeHeading(aimAngles.GetZ() - playerInput->m_viewYaw * cl_AimStickScaleZ * cl_MaxMouseDelta));
  201. aimAngles.SetX(NormalizeHeading(aimAngles.GetX() - playerInput->m_viewPitch * cl_AimStickScaleX * cl_MaxMouseDelta));
  202. aimAngles.SetX(
  203. NormalizeHeading(AZ::GetClamp(aimAngles.GetX(), -AZ::Constants::QuarterPi * 1.5f, AZ::Constants::QuarterPi * 1.5f)));
  204. GetNetworkSimplePlayerCameraComponentController()->SetAimAngles(aimAngles);
  205. const AZ::Quaternion newOrientation = AZ::Quaternion::CreateRotationZ(aimAngles.GetZ());
  206. GetEntity()->GetTransform()->SetLocalRotationQuaternion(newOrientation);
  207. // Update velocity
  208. bool jumpTriggered = false;
  209. bool movingDownward = false;
  210. UpdateVelocity(*playerInput, deltaTime, jumpTriggered, movingDownward);
  211. // absolute velocity is based on velocity generated by the player and other sources
  212. const AZ::Vector3 absoluteVelocity = GetVelocityFromExternalSources() + GetSelfGeneratedVelocity();
  213. // if we're not intentionally moving downward on a platform and have a negative velocity we're falling
  214. const bool isFalling = !movingDownward && absoluteVelocity.GetZ() < 0.0f;
  215. GetNetworkCharacterComponentController()->TryMoveWithVelocity(absoluteVelocity, deltaTime);
  216. // If a jump was triggered, reset our jump request time to our "slop threshold" so that we don't double-count the jump request
  217. // if we land too quickly.
  218. if (jumpTriggered)
  219. {
  220. SetSecondsSinceJumpRequest(GetJumpPressQueuedSeconds());
  221. }
  222. // Tell the camera whether or not we're sprinting
  223. GetNetworkSimplePlayerCameraComponentController()->SetSprintMode(playerInput->m_sprint);
  224. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(
  225. aznumeric_cast<uint32_t>(CharacterAnimState::Sprinting), playerInput->m_sprint);
  226. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(
  227. aznumeric_cast<uint32_t>(CharacterAnimState::Crouching), playerInput->m_crouch);
  228. // the Landing anim state will automatically turn off after it's triggered
  229. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(
  230. aznumeric_cast<uint32_t>(CharacterAnimState::Landing), onGround && !wasOnGround && !jumpTriggered);
  231. // Always set/clear the jump state every tick or you might get ghost jump animations.
  232. // We only set it on the tick where the jump is first triggered, not for the entire jump.
  233. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(
  234. aznumeric_cast<uint32_t>(CharacterAnimState::Jumping), jumpTriggered);
  235. // Set whether or not we're currently falling.
  236. GetNetworkAnimationComponentController()->ModifyActiveAnimStates().SetBit(
  237. aznumeric_cast<uint32_t>(CharacterAnimState::Falling), isFalling);
  238. // At the end, track whether or not we were on the ground for this input so we can compare states when processing the next input.
  239. SetWasOnGround(onGround);
  240. }
  241. void NetworkPlayerMovementComponentController::UpdateVelocity(const NetworkPlayerMovementComponentNetworkInput& playerInput, float deltaTime, bool& jumpTriggered, bool& movingDownward)
  242. {
  243. AZ::Vector3 velocityFromExternalSources = GetVelocityFromExternalSources(); // non-player generated (jump pads, explosions etc.)
  244. AZ::Vector3 selfGeneratedVelocity = GetSelfGeneratedVelocity(); // player generated
  245. const float secondsSinceOnGround = GetSecondsSinceOnGround();
  246. const float secondsSinceJumpRequest = GetSecondsSinceJumpRequest();
  247. const bool onGround = GetOnGround();
  248. if (onGround)
  249. {
  250. // Reset our jumping state if we're on the ground.
  251. if (GetIsJumping())
  252. {
  253. SetIsJumping(false);
  254. }
  255. // If we're on the ground, we should never have velocities pushing us into the ground.
  256. if (selfGeneratedVelocity.GetZ() <= 0.0f)
  257. {
  258. selfGeneratedVelocity.SetZ(0.0f);
  259. }
  260. }
  261. else
  262. {
  263. // If we're not on the ground, apply gravity.
  264. // NOTE: We do this *before* trying to trigger a jump so that the jump can overwrite the velocity and not have
  265. // gravity applied on the first tick of the jump.
  266. selfGeneratedVelocity.SetZ(selfGeneratedVelocity.GetZ() + m_gravity * m_gravityMultiplier * deltaTime);
  267. }
  268. // Ideally, the way velocities would work below is that there would be a single player velocity tracking the player's
  269. // current velocity, and pressing the jump button and getting external sources would just add an impulse into the player's
  270. // velocity, which then would get ticked back down by gravity over time.
  271. // However, the animation graph is expecting the player-generated velocity to be tracked separately from any external sources,
  272. // so at least for now, we'll keep them as separate velocities.
  273. // The one place where this is a problem is in how to make gravity work, as seen below. It's not actually solved correctly
  274. // right now, but until something uses the external sources velocity, it's hard to tell whether or not it needs to be fixed
  275. // in a better way.
  276. // If we're not currently jumping, see if we should trigger a jump.
  277. if (!GetIsJumping())
  278. {
  279. // We can trigger a jump as long as we aren't currently falling
  280. if (selfGeneratedVelocity.GetZ() <= 0.0f)
  281. {
  282. // We can trigger a jump as long as we *were* on the ground in the last "slop factor" fractions of a second
  283. if (secondsSinceOnGround <= GetJumpOnGroundQueuedSeconds())
  284. {
  285. // We can trigger a jump as long as we pressed the jump button in the last "slop factor" fractions of a second
  286. if (secondsSinceJumpRequest <= GetJumpPressQueuedSeconds())
  287. {
  288. // We're jumping, so set the upwards velocity necessary to reach our desired jump height.
  289. // Note that we're only setting Z velocity because the XY velocity components can be changed by the player
  290. // even while in midair.
  291. const float initialJumpVelocity = AZ::Sqrt(2.0f * (-m_gravity * m_gravityMultiplier) * GetMaxJumpHeight());
  292. selfGeneratedVelocity.SetZ(initialJumpVelocity);
  293. jumpTriggered = true;
  294. SetIsJumping(true);
  295. }
  296. }
  297. }
  298. }
  299. // External sources can add velocity, but get clamped to 0 so that they never add negative velocity, since
  300. // the selfGeneratedVelocity already fully accounts for gravity so that the player can fall.
  301. // If we don't clamp it, we'll get too much gravity influence on the player.
  302. // Note that because we're applying gravity to the external source as well as to the self-generated velocity,
  303. // we're double-counting gravity's influence. This will probably make the external source velocities a bit harder
  304. // to tune.
  305. velocityFromExternalSources.SetZ(
  306. AZStd::max(0.0f, velocityFromExternalSources.GetZ() + m_gravity * m_gravityMultiplier * deltaTime));
  307. // Now that we've got the vertical velocity from jumps / external sources / gravity accounted for, let's calculate
  308. // the player horizontal movement velocity.
  309. const float fwdBack = playerInput.m_forwardAxis;
  310. const float leftRight = playerInput.m_strafeAxis;
  311. // TODO break out into air/water/ground speeds
  312. float speed = 0.0f;
  313. if (playerInput.m_crouch)
  314. {
  315. speed = GetCrouchSpeed();
  316. }
  317. else if (fwdBack < 0.0f)
  318. {
  319. speed = GetReverseSpeed();
  320. }
  321. else
  322. {
  323. if (playerInput.m_sprint)
  324. {
  325. speed = GetSprintSpeed();
  326. }
  327. else
  328. {
  329. speed = GetWalkSpeed();
  330. }
  331. }
  332. // If the player isn't trying to move, set the self-generated XY velocity to 0.
  333. // If they *are* trying to move, set a velocity based on the XY movement direction requested and the Z direction based
  334. // on the slope of the ground directly ahead vs what's currently under the player.
  335. if (fwdBack != 0.0f || leftRight != 0.0f)
  336. {
  337. const float stickInputAngle = AZ::Atan2(leftRight, fwdBack);
  338. const float currentHeading = GetNetworkTransformComponentController()->GetRotation().GetEulerRadians().GetZ();
  339. const float targetHeadingAngleRadians = NormalizeHeading(currentHeading + stickInputAngle);
  340. // Using the unit vector from GetSlopeHeading that provides the direction of movement, multiply by speed
  341. // to get the moveVelocity.
  342. const AZ::Vector3 moveVelocity = GetSlopeHeading(targetHeadingAngleRadians) * speed;
  343. // Immediately switch to the newly-requested XY movement direction, even if in midair.
  344. // We've chosen not to apply acceleration / deceleration.
  345. selfGeneratedVelocity.SetX(moveVelocity.GetX());
  346. selfGeneratedVelocity.SetY(moveVelocity.GetY());
  347. // If we're jumping, use the jumping/falling Z velocity. If we're on the ground or falling not from a jump, then we'll
  348. // add in any movement velocity. This will be 0 if there's no ground near us, but it will be in the direction of the ground
  349. // if we're next to some ground that's within a step height up or down.
  350. // Note that we can't just check for "on ground" here, because in the case of moving down a ramp or small steps, we might
  351. // actually be off the ground a little bit and need to correct our movement downward to get back onto the ground.
  352. if (!GetIsJumping())
  353. {
  354. selfGeneratedVelocity.SetZ(selfGeneratedVelocity.GetZ() + moveVelocity.GetZ());
  355. // If we're not jumping and we have a downwards velocity, track that we're deliberately moving downward so that
  356. // we can distinguish this state from arbitrary falling.
  357. if (moveVelocity.GetZ() < 0.0f)
  358. {
  359. movingDownward = true;
  360. }
  361. }
  362. }
  363. else
  364. {
  365. // instant deceleration for now
  366. selfGeneratedVelocity.SetX(0.f);
  367. selfGeneratedVelocity.SetY(0.f);
  368. }
  369. SetVelocityFromExternalSources(velocityFromExternalSources);
  370. SetSelfGeneratedVelocity(selfGeneratedVelocity);
  371. }
  372. AZ::Vector3 NetworkPlayerMovementComponentController::GetSlopeHeading(float headingAngleRadians) const
  373. {
  374. // Returns a unit vector pointing in the direction that the player is moving.
  375. // Start with a direction vector in the XY plane.
  376. const AZ::Vector3 fwd = AZ::Quaternion::CreateRotationZ(headingAngleRadians).TransformVector(AZ::Vector3::CreateAxisY());
  377. // The origin is set to the bottom of the player, not the center.
  378. const AZ::Vector3 origin = GetEntity()->GetTransform()->GetWorldTranslation();
  379. constexpr float forwardEpsilon = 0.01f;
  380. constexpr float heightEpsilon = 0.01f;
  381. // Raycast straight down in front of the player by a tiny amount (forwardEpsilon) starting at the step height plus an epsilon
  382. // and ending at negative step height plus an epsilon. This will tell us if there's any surface directly in front of the player
  383. // within the step height up or down. If so, we'll use that to calculate the Z direction.
  384. const AZ::Vector3 start = origin + fwd * (m_radius + forwardEpsilon) + AZ::Vector3(0.f, 0.f, m_stepHeight + heightEpsilon);
  385. AzPhysics::RayCastRequest request;
  386. request.m_start = start;
  387. request.m_direction = AZ::Vector3::CreateAxisZ(-1.f);
  388. request.m_distance = (m_stepHeight + heightEpsilon) * 2.f;
  389. request.m_queryType = AzPhysics::SceneQuery::QueryType::Static;
  390. AzPhysics::SceneQueryHits result;
  391. if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
  392. {
  393. if (AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName);
  394. sceneHandle != AzPhysics::InvalidSceneHandle)
  395. {
  396. result = sceneInterface->QueryScene(sceneHandle, &request);
  397. }
  398. }
  399. // If we've found a surface in front of us that's within the step height in size in either direction, then we'll create a vector
  400. // from the current bottom of the player to that new location so that our heading direction accounts for the Z slope.
  401. if (result && result.m_hits[0].IsValid())
  402. {
  403. // we use epsilon here to avoid the case where we are pushing up against an object and become slightly
  404. // elevated
  405. if (result.m_hits[0].m_position.GetZ() < (origin.GetZ() - heightEpsilon))
  406. {
  407. const AZ::Vector3 delta = result.m_hits[0].m_position - origin;
  408. return delta.GetNormalized();
  409. }
  410. }
  411. return fwd;
  412. }
  413. float NetworkPlayerMovementComponentController::NormalizeHeading(float heading) const
  414. {
  415. // Ensure heading in range [-pi, +pi]
  416. if (heading > AZ::Constants::Pi)
  417. {
  418. return static_cast<float>(heading - AZ::Constants::TwoPi);
  419. }
  420. else if (heading < -AZ::Constants::Pi)
  421. {
  422. return static_cast<float>(heading + AZ::Constants::TwoPi);
  423. }
  424. return heading;
  425. }
  426. void NetworkPlayerMovementComponentController::OnPressed(float value)
  427. {
  428. const StartingPointInput::InputEventNotificationId* inputId = StartingPointInput::InputEventNotificationBus::GetCurrentBusId();
  429. if (inputId == nullptr)
  430. {
  431. return;
  432. }
  433. else if (*inputId == MoveFwdEventId)
  434. {
  435. m_forwardDown = true;
  436. }
  437. else if (*inputId == MoveBackEventId)
  438. {
  439. m_backwardDown = true;
  440. }
  441. else if (*inputId == MoveLeftEventId)
  442. {
  443. m_leftDown = true;
  444. }
  445. else if (*inputId == MoveRightEventId)
  446. {
  447. m_rightDown = true;
  448. }
  449. else if (*inputId == SprintEventId)
  450. {
  451. m_sprinting = true;
  452. }
  453. else if (*inputId == JumpEventId)
  454. {
  455. m_jumping = true;
  456. }
  457. else if (*inputId == CrouchEventId)
  458. {
  459. m_crouching = true;
  460. }
  461. else if (*inputId == LookLeftRightEventId)
  462. {
  463. // Accumulate input to be processed in CreateInput().
  464. // In-between two CreateInput() calls, multiple series of presses and holds can occur, accumulate all of them,
  465. // otherwise we will drop some of the input data by only including the last press and hold combination.
  466. m_viewYaw += value;
  467. }
  468. else if (*inputId == LookUpDownEventId)
  469. {
  470. m_viewPitch += value;
  471. }
  472. }
  473. void NetworkPlayerMovementComponentController::OnReleased([[maybe_unused]]float value)
  474. {
  475. const StartingPointInput::InputEventNotificationId* inputId = StartingPointInput::InputEventNotificationBus::GetCurrentBusId();
  476. if (inputId == nullptr)
  477. {
  478. return;
  479. }
  480. else if (*inputId == MoveFwdEventId)
  481. {
  482. m_forwardDown = false;
  483. }
  484. else if (*inputId == MoveBackEventId)
  485. {
  486. m_backwardDown = false;
  487. }
  488. else if (*inputId == MoveLeftEventId)
  489. {
  490. m_leftDown = false;
  491. }
  492. else if (*inputId == MoveRightEventId)
  493. {
  494. m_rightDown = false;
  495. }
  496. else if (*inputId == SprintEventId)
  497. {
  498. m_sprinting = false;
  499. }
  500. else if (*inputId == JumpEventId)
  501. {
  502. m_jumping = false;
  503. }
  504. else if (*inputId == CrouchEventId)
  505. {
  506. m_crouching = false;
  507. }
  508. }
  509. void NetworkPlayerMovementComponentController::OnHeld(float value)
  510. {
  511. const StartingPointInput::InputEventNotificationId* inputId = StartingPointInput::InputEventNotificationBus::GetCurrentBusId();
  512. if (inputId == nullptr)
  513. {
  514. return;
  515. }
  516. else if (*inputId == LookLeftRightEventId)
  517. {
  518. // accumulate input to be processed in CreateInput()
  519. m_viewYaw += value;
  520. }
  521. else if (*inputId == LookUpDownEventId)
  522. {
  523. // accumulate input to be processed in CreateInput()
  524. m_viewPitch += value;
  525. }
  526. }
  527. #if AZ_TRAIT_SERVER
  528. void NetworkPlayerMovementComponentController::UpdateAI()
  529. {
  530. float deltaTime = static_cast<float>(m_updateAI.TimeInQueueMs()) / 1000.f;
  531. if (m_networkAiComponentController != nullptr)
  532. {
  533. m_networkAiComponentController->TickMovement(*this, deltaTime);
  534. }
  535. }
  536. #endif
  537. #if AZ_TRAIT_CLIENT
  538. void NetworkPlayerMovementComponentController::UpdateLocalBot()
  539. {
  540. float deltaTimeMs = static_cast<float>(m_updateLocalBot.TimeInQueueMs());
  541. m_botRemainingTime -= deltaTimeMs;
  542. if (m_botRemainingTime <= 0)
  543. {
  544. // Determine a new directive after 500 to 9500 ms
  545. m_botRemainingTime = (m_botLcg.GetRandomFloat() * (mps_botMaxInterval - mps_botMinInterval) + mps_botMinInterval);
  546. m_botTurnRate = (1.f / m_botRemainingTime);
  547. // Randomize new target yaw and pitch and compute the delta from the current yaw and pitch respectively
  548. m_botTargetYawDelta = -m_viewYaw + (m_botLcg.GetRandomFloat() * 2.f - 1.f);
  549. m_botTargetPitchDelta = -m_viewPitch + (m_botLcg.GetRandomFloat() - 0.5f);
  550. // Randomize the action and strafe direction (used only if we decide to strafe)
  551. m_botAction = static_cast<Action>(m_botLcg.GetRandom() % static_cast<int>(Action::COUNT));
  552. m_botStrafingRight = static_cast<bool>(m_botLcg.GetRandom() % 2);
  553. }
  554. // Translate desired motion into inputs
  555. // Interpolate the current view yaw and pitch values towards the desired values
  556. m_viewYaw += m_botTurnRate * deltaTimeMs * m_botTargetPitchDelta;
  557. m_viewPitch += m_botTurnRate * deltaTimeMs * m_botTargetPitchDelta;
  558. // Reset keyboard movement inputs decided on the previous frame
  559. m_forwardDown = false;
  560. m_backwardDown = false;
  561. m_leftDown = false;
  562. m_rightDown = false;
  563. m_sprinting = false;
  564. m_jumping = false;
  565. m_crouching = false;
  566. switch (m_botAction)
  567. {
  568. case Action::Default:
  569. m_forwardDown = true;
  570. break;
  571. case Action::Sprinting:
  572. m_forwardDown = true;
  573. m_sprinting = true;
  574. break;
  575. case Action::Jumping:
  576. m_forwardDown = true;
  577. m_jumping = true;
  578. break;
  579. case Action::Crouching:
  580. m_forwardDown = true;
  581. m_crouching = true;
  582. break;
  583. case Action::Strafing:
  584. if (m_botStrafingRight)
  585. {
  586. m_rightDown = true;
  587. }
  588. else
  589. {
  590. m_leftDown = true;
  591. }
  592. break;
  593. default:
  594. break;
  595. }
  596. }
  597. #endif
  598. } // namespace MultiplayerSample