-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Energy Ball Fixes #363
Merged
mbalfour-amzn
merged 6 commits into
o3de:stabilization/2305
from
aws-lumberyard-dev:mbalfour/energy_ball_fixes
Apr 18, 2023
Merged
Energy Ball Fixes #363
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
06f2a30
Added guards to downgrade a sporadic Editor crash to an assert.
mbalfour-amzn 57c462e
Fixes the disappearing energy balls.
mbalfour-amzn e740c00
Merge branch 'stabilization/2305' into mbalfour/energy_ball_fixes
mbalfour-amzn 5a496ef
Switched from tick bus to event handlers
mbalfour-amzn 38a191a
Merge branch 'stabilization/2305' into mbalfour/energy_ball_fixes
mbalfour-amzn 5c3bc05
Merge branch 'stabilization/2305' into mbalfour/energy_ball_fixes
mbalfour-amzn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,16 +11,19 @@ | |
#include <Multiplayer/Components/NetworkRigidBodyComponent.h> | ||
#include <MultiplayerSampleTypes.h> | ||
#include <AzCore/Component/TransformBus.h> | ||
#include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h> | ||
#include <AzFramework/Physics/RigidBodyBus.h> | ||
#include <WeaponNotificationBus.h> | ||
|
||
#if AZ_TRAIT_CLIENT | ||
# include <PopcornFX/PopcornFXBus.h> | ||
# include <DebugDraw/DebugDrawBus.h> | ||
#endif | ||
|
||
namespace MultiplayerSample | ||
{ | ||
AZ_CVAR(float, sv_EnergyBallImpulseScalar, 500.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A fudge factor for imparting impulses on rigid bodies due to weapon hits"); | ||
AZ_CVAR(bool, cl_EnergyBallDebugDraw, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "When turned on this will draw the current energy ball location"); | ||
|
||
void EnergyBallComponent::Reflect(AZ::ReflectContext* context) | ||
{ | ||
|
@@ -37,39 +40,121 @@ namespace MultiplayerSample | |
{ | ||
m_effect = GetExplosionEffect(); | ||
m_effect.Initialize(); | ||
|
||
#if AZ_TRAIT_CLIENT | ||
// The energy ball particle effect defaults to active in the component, so initialize wasActive to true for the first state. | ||
m_wasActive = true; | ||
AZ::TickBus::Handler::BusConnect(); | ||
#endif | ||
} | ||
|
||
void EnergyBallComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) | ||
{ | ||
#if AZ_TRAIT_CLIENT | ||
AZ::TickBus::Handler::BusDisconnect(); | ||
#endif | ||
} | ||
|
||
#if AZ_TRAIT_CLIENT | ||
void EnergyBallComponent::HandleRPC_BallLaunched([[maybe_unused]] AzNetworking::IConnection* invokingConnection, [[maybe_unused]] const AZ::Vector3& location) | ||
void EnergyBallComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) | ||
{ | ||
PopcornFX::PopcornFXEmitterComponentRequests* emitterRequests = PopcornFX::PopcornFXEmitterComponentRequestBus::FindFirstHandler(GetEntity()->GetId()); | ||
if (emitterRequests != nullptr) | ||
#if AZ_TRAIT_CLIENT | ||
bool active = GetBallActive(); | ||
|
||
// Turn on the energy ball VFX. Physics on the server side will cause the energy ball to move. | ||
if (active != m_wasActive) | ||
{ | ||
emitterRequests->Start(); | ||
if (active) | ||
{ | ||
bool startSuccess = false; | ||
|
||
// Set to true to call "Kill" which is deferred, or false to call "Terminate" which is immediate. | ||
constexpr bool KillOnRestart = true; | ||
|
||
PopcornFX::PopcornFXEmitterComponentRequestBus::EventResult(startSuccess, | ||
GetEntity()->GetId(), &PopcornFX::PopcornFXEmitterComponentRequestBus::Events::Restart, KillOnRestart); | ||
|
||
AZ_Error("EnergyBall", startSuccess, "Restart call for Energy Ball was unsuccessful."); | ||
} | ||
else | ||
{ | ||
bool killSuccess = false; | ||
|
||
// This would ideally use Kill instead of Terminate, but there is a bug in PopcornFX 2.15.4 that if Kill is | ||
// called on the first tick (which can happen), then the effect will get stuck in a permanent waiting-to-die state, | ||
// and no amount of Restart calls will ever make it show up again. | ||
PopcornFX::PopcornFXEmitterComponentRequestBus::EventResult(killSuccess, | ||
GetEntity()->GetId(), &PopcornFX::PopcornFXEmitterComponentRequestBus::Events::Terminate); | ||
|
||
AZ_Error("EnergyBall", killSuccess, "Kill call for Energy Ball was unsuccessful."); | ||
} | ||
m_wasActive = active; | ||
} | ||
|
||
if (active && cl_EnergyBallDebugDraw) | ||
{ | ||
DebugDraw(); | ||
} | ||
#endif | ||
} | ||
|
||
#if AZ_TRAIT_CLIENT | ||
void EnergyBallComponent::HandleRPC_BallExplosion([[maybe_unused]] AzNetworking::IConnection* invokingConnection, const HitEvent& hitEvent) | ||
{ | ||
// Crate an explosion effect wherever the ball was last at. | ||
AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateIdentity(), hitEvent.m_target); | ||
m_effect.TriggerEffect(transform); | ||
|
||
// Notify every entity that was hit that they've received a weapon impact. | ||
for (const HitEntity& hitEntity : hitEvent.m_hitEntities) | ||
{ | ||
const AZ::Transform hitTransform = AZ::Transform::CreateLookAt(hitEntity.m_hitPosition, hitEntity.m_hitPosition + hitEntity.m_hitNormal, AZ::Transform::Axis::ZPositive); | ||
const Multiplayer::ConstNetworkEntityHandle handle = Multiplayer::GetNetworkEntityManager()->GetEntity(hitEntity.m_hitNetEntityId); | ||
const AZ::EntityId hitEntityId = handle.Exists() ? handle.GetEntity()->GetId() : AZ::EntityId(); | ||
WeaponNotificationBus::Broadcast(&WeaponNotificationBus::Events::OnWeaponImpact, GetEntity()->GetId(), hitTransform, hitEntityId); | ||
} | ||
} | ||
|
||
PopcornFX::PopcornFXEmitterComponentRequests* emitterRequests = PopcornFX::PopcornFXEmitterComponentRequestBus::FindFirstHandler(GetEntity()->GetId()); | ||
if (emitterRequests != nullptr) | ||
void EnergyBallComponent::DebugDraw() | ||
{ | ||
if (cl_EnergyBallDebugDraw) | ||
{ | ||
emitterRequests->Kill(); | ||
// Each draw only lasts one frame. | ||
constexpr float DrawDuration = 0.0f; | ||
|
||
auto* shapeConfig = GetGatherParams().GetCurrentShapeConfiguration(); | ||
if (shapeConfig->GetShapeType() == Physics::ShapeType::Sphere) | ||
{ | ||
const Physics::SphereShapeConfiguration* sphere = static_cast<const Physics::SphereShapeConfiguration*>(shapeConfig); | ||
float debugRadius = sphere->m_radius; | ||
|
||
DebugDraw::DebugDrawRequestBus::Broadcast( | ||
&DebugDraw::DebugDrawRequestBus::Events::DrawSphereAtLocation, | ||
GetEntity()->GetTransform()->GetWorldTM().GetTranslation(), | ||
debugRadius, | ||
AZ::Colors::Green, | ||
DrawDuration | ||
); | ||
} | ||
else if (shapeConfig->GetShapeType() == Physics::ShapeType::Box) | ||
{ | ||
const Physics::BoxShapeConfiguration* box = static_cast<const Physics::BoxShapeConfiguration*>(shapeConfig); | ||
AZ::Obb obb = AZ::Obb::CreateFromPositionRotationAndHalfLengths( | ||
GetEntity()->GetTransform()->GetWorldTM().GetTranslation(), | ||
GetEntity()->GetTransform()->GetWorldTM().GetRotation(), | ||
box->m_dimensions / 2.0f | ||
); | ||
|
||
DebugDraw::DebugDrawRequestBus::Broadcast( | ||
&DebugDraw::DebugDrawRequestBus::Events::DrawObb, | ||
obb, | ||
AZ::Colors::Green, | ||
DrawDuration | ||
); | ||
} | ||
else if (shapeConfig->GetShapeType() == Physics::ShapeType::Capsule) | ||
{ | ||
AZ_Error("EnergyBall", false, "Capsule shape type not currently supported with energy ball debug visualization."); | ||
} | ||
} | ||
} | ||
#endif | ||
|
@@ -83,20 +168,29 @@ namespace MultiplayerSample | |
void EnergyBallComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) | ||
{ | ||
#if AZ_TRAIT_SERVER | ||
m_collisionCheckEvent.Enqueue(AZ::TimeMs{ 10 }, true); | ||
SetBallActive(false); | ||
#endif | ||
} | ||
|
||
void EnergyBallComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) | ||
{ | ||
#if AZ_TRAIT_SERVER | ||
m_collisionCheckEvent.RemoveFromQueue(); | ||
SetBallActive(false); | ||
#endif | ||
} | ||
|
||
#if AZ_TRAIT_SERVER | ||
void EnergyBallComponentController::HandleRPC_LaunchBall([[maybe_unused]] AzNetworking::IConnection* invokingConnection, const AZ::Vector3& startingPosition, const AZ::Vector3& direction, const Multiplayer::NetEntityId& owningNetEntityId) | ||
void EnergyBallComponentController::HandleRPC_LaunchBall(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& startingPosition, const AZ::Vector3& direction, const Multiplayer::NetEntityId& owningNetEntityId) | ||
{ | ||
if (GetBallActive()) | ||
{ | ||
return; | ||
} | ||
|
||
m_collisionCheckEvent.Enqueue(AZ::TimeMs{ 10 }, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, that makes a lot more sense.. |
||
|
||
SetBallActive(true); | ||
|
||
m_shooterNetEntityId = owningNetEntityId; | ||
m_hitEvent.m_hitEntities.clear(); | ||
|
||
|
@@ -106,15 +200,14 @@ namespace MultiplayerSample | |
m_direction = direction; | ||
|
||
// Move the entity to the start position | ||
GetEntity()->GetTransform()->SetWorldTranslation(startingPosition); | ||
GetNetworkTransformComponentController()->HandleMultiplayerTeleport(invokingConnection, startingPosition); | ||
|
||
// We want to sweep our transform during intersect tests to avoid the ball tunneling through targets | ||
m_lastSweepTransform = GetEntity()->GetTransform()->GetWorldTM(); | ||
|
||
Physics::RigidBodyRequestBus::Event(GetEntityId(), &Physics::RigidBodyRequestBus::Events::EnablePhysics); | ||
AzPhysics::SimulatedBodyComponentRequestsBus::Event(GetEntityId(), &AzPhysics::SimulatedBodyComponentRequestsBus::Events::EnablePhysics); | ||
Physics::RigidBodyRequestBus::Event(GetEntityId(), &Physics::RigidBodyRequestBus::Events::SetLinearVelocity, direction * GetGatherParams().m_travelSpeed); | ||
|
||
RPC_BallLaunched(startingPosition); | ||
} | ||
|
||
void EnergyBallComponentController::HandleRPC_KillBall([[maybe_unused]] AzNetworking::IConnection* invokingConnection) | ||
|
@@ -124,6 +217,11 @@ namespace MultiplayerSample | |
|
||
void EnergyBallComponentController::CheckForCollisions() | ||
{ | ||
if (!GetBallActive()) | ||
{ | ||
return; | ||
} | ||
|
||
const AZ::Vector3& position = GetEntity()->GetTransform()->GetWorldTM().GetTranslation(); | ||
const HitEffect& effect = GetHitEffect(); | ||
|
||
|
@@ -174,17 +272,22 @@ namespace MultiplayerSample | |
|
||
void EnergyBallComponentController::HideEnergyBall() | ||
{ | ||
if (!GetBallActive()) | ||
{ | ||
return; | ||
} | ||
|
||
SetBallActive(false); | ||
m_collisionCheckEvent.RemoveFromQueue(); | ||
|
||
m_hitEvent.m_target = GetEntity()->GetTransform()->GetWorldTM().GetTranslation(); | ||
m_hitEvent.m_shooterNetEntityId = m_shooterNetEntityId; | ||
m_hitEvent.m_projectileNetEntityId = GetNetEntityId(); | ||
RPC_BallExplosion(m_hitEvent); | ||
|
||
Physics::RigidBodyRequestBus::Event(GetEntityId(), &Physics::RigidBodyRequestBus::Events::DisablePhysics); | ||
AzPhysics::SimulatedBodyComponentRequestsBus::Event(GetEntityId(), &AzPhysics::SimulatedBodyComponentRequestsBus::Events::DisablePhysics); | ||
Physics::RigidBodyRequestBus::Event(GetEntityId(), &Physics::RigidBodyRequestBus::Events::SetLinearVelocity, AZ::Vector3::CreateZero()); | ||
|
||
// move self and increment resetCount to prevent transform interpolation | ||
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3::CreateAxisZ(-1000.f)); | ||
GetNetworkTransformComponentController()->ModifyResetCount()++; | ||
RPC_BallExplosion(m_hitEvent); | ||
} | ||
#endif | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could actually, if you wanted, attach an event handler to the BallActive network property so that when the value changes your callback gets invoked. Since it's just an on/off thing I think it's safe to just say
if (active) startfx else killfx
although you've clearly run into some hair pulling bugs with pkfx.Only benefit is if somebody goes crazy and adds 100,000 energy balls to a level the game doesn't have to go through and tick all 100,000 every frame on the client and the server, you'd only process effects changes when the state of BallActive changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ooh, neat, i didn't know the "on changed" events existed! i'll switch over to using that.