From a1dc9cadd8fa536dbfdc7000a87b9a03b8c9a9c1 Mon Sep 17 00:00:00 2001 From: wraitii Date: Tue, 9 Jul 2019 19:56:28 +0000 Subject: [PATCH] UnitMotion / AI - remove the special 'move' animation, make UnitMotion inform the visual actor directly. Use UpdateMovementState to inform the visual actor of the unit's speed, which cill update the movement animation accordingly. The removes the need for UnitAI to handle movement animation using the special "move" state. Differential Revision: https://code.wildfiregames.com/D1901 This was SVN commit r22446. --- .../public/simulation/components/UnitAI.js | 54 +++++------------ .../simulation2/components/CCmpUnitMotion.cpp | 9 +++ .../components/CCmpVisualActor.cpp | 58 ++----------------- source/simulation2/components/ICmpVisual.cpp | 1 - source/simulation2/components/ICmpVisual.h | 14 +++-- 5 files changed, 38 insertions(+), 98 deletions(-) diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 70050ccdfb..952fd3ffd8 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1274,8 +1274,6 @@ UnitAI.prototype.UnitFsmSpec = { "enter": function() { let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z); - - this.SelectAnimation("move"); }, // Occurs when the unit has reached its destination and the controller @@ -1300,12 +1298,12 @@ UnitAI.prototype.UnitFsmSpec = { var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); if (cmpFormation) cmpFormation.UnsetInPosition(this.entity); + if (!this.MoveTo(this.order.data)) { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "MovementUpdate": function() { @@ -1465,10 +1463,6 @@ UnitAI.prototype.UnitFsmSpec = { this.RespondToHealableEntities(msg.data.added); }, - "MoveCompleted": function() { - this.SelectAnimation("idle"); - }, - "Timer": function(msg) { if (!this.isIdle) { @@ -1485,11 +1479,9 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function () { - this.SelectAnimation("idle"); this.StopMoving(); }, @@ -1510,7 +1502,6 @@ UnitAI.prototype.UnitFsmSpec = { this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); }, "Timer": function(msg) { @@ -1549,7 +1540,6 @@ UnitAI.prototype.UnitFsmSpec = { this.StartTimer(0, 1000); this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); }, "leave": function() { @@ -1593,7 +1583,6 @@ UnitAI.prototype.UnitFsmSpec = { this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); this.SetHeldPositionOnEntity(this.isGuardOf); return false; }, @@ -1641,7 +1630,6 @@ UnitAI.prototype.UnitFsmSpec = { this.StartTimer(1000, 1000); this.SetHeldPositionOnEntity(this.entity); this.SetAnimationVariant("combat"); - this.SelectAnimation("idle"); return false; }, @@ -1700,7 +1688,6 @@ UnitAI.prototype.UnitFsmSpec = { this.PlaySound("panic"); // Run quickly - this.SelectAnimation("move"); this.SetSpeedMultiplier(this.GetRunMultiplier()); }, @@ -1746,7 +1733,6 @@ UnitAI.prototype.UnitFsmSpec = { // Show weapons rather than carried resources. this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -1882,6 +1868,7 @@ UnitAI.prototype.UnitFsmSpec = { cmpBuildingAI.SetUnitAITarget(0); this.StopTimer(); this.SetDefaultAnimationVariant(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -2001,7 +1988,6 @@ UnitAI.prototype.UnitFsmSpec = { // Show weapons rather than carried resources. this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); if (cmpUnitAI && cmpUnitAI.IsFleeing()) { @@ -2055,8 +2041,6 @@ UnitAI.prototype.UnitFsmSpec = { this.SetNextState("GATHERING"); return true; } - - this.SelectAnimation("move"); return false; }, @@ -2088,7 +2072,6 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2175,6 +2158,7 @@ UnitAI.prototype.UnitFsmSpec = { delete this.gatheringTarget; // Show the carried resource, if we've gathered anything. + this.ResetAnimation(); this.SetDefaultAnimationVariant(); }, @@ -2347,7 +2331,6 @@ UnitAI.prototype.UnitFsmSpec = { return true; } - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -2398,6 +2381,7 @@ UnitAI.prototype.UnitFsmSpec = { }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -2458,13 +2442,10 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { - // Switch back to idle animation to guarantee we won't - // get stuck with the carry animation after stopping moving - this.SelectAnimation("idle"); + this.StopMoving(); }, "MovementUpdate": function() { @@ -2523,7 +2504,6 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2563,7 +2543,6 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2625,6 +2604,7 @@ UnitAI.prototype.UnitFsmSpec = { cmpBuilderList.RemoveBuilder(this.entity); delete this.repairTarget; this.StopTimer(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -2769,7 +2749,6 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2892,6 +2871,7 @@ UnitAI.prototype.UnitFsmSpec = { "leave": function() { this.StopTimer(); + this.ResetAnimation(); var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver); cmpDamageReceiver.SetInvulnerability(false); }, @@ -2949,7 +2929,6 @@ UnitAI.prototype.UnitFsmSpec = { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2969,7 +2948,6 @@ UnitAI.prototype.UnitFsmSpec = { "LOADING": { "enter": function() { this.StopMoving(); - this.SelectAnimation("idle"); var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) { @@ -3030,7 +3008,6 @@ UnitAI.prototype.UnitFsmSpec = { "ROAMING": { "enter": function() { // Walk in a random direction - this.SelectAnimation("move", false, 1); this.SetFacePointAfterMove(false); this.MoveRandomly(+this.template.RoamDistance); // Set a random timer to switch to feeding state @@ -3083,6 +3060,7 @@ UnitAI.prototype.UnitFsmSpec = { }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -4106,20 +4084,20 @@ UnitAI.prototype.SetDefaultAnimationVariant = function() this.SetAnimationVariant(""); }; -UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) +UnitAI.prototype.ResetAnimation = function() { let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (!cmpVisual) return; - // Special case: the "move" animation gets turned into a special - // movement mode that deals with speeds and walk/run automatically - if (name == "move") - { - // Speed to switch from walking to running animations - cmpVisual.SelectMovementAnimation(this.GetWalkSpeed()); + cmpVisual.SelectAnimation("idle", false, 1.0); +}; + +UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) +{ + let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) return; - } cmpVisual.SelectAnimation(name, once, speed); }; diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index 3f82f5b458..9cd7f41fd8 100644 --- a/source/simulation2/components/CCmpUnitMotion.cpp +++ b/source/simulation2/components/CCmpUnitMotion.cpp @@ -27,6 +27,7 @@ #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpValueModificationManager.h" +#include "simulation2/components/ICmpVisual.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Render.h" #include "simulation2/MessageTypes.h" @@ -517,18 +518,26 @@ private: void UpdateMovementState(entity_pos_t speed) { CmpPtr cmpObstruction(GetEntityHandle()); + CmpPtr cmpVisual(GetEntityHandle()); // Moved last turn, didn't this turn. if (speed == fixed::Zero() && m_CurSpeed > fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(false); + if (cmpVisual) + cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1)); } // Moved this turn, didn't last turn else if (speed > fixed::Zero() && m_CurSpeed == fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(true); + if (cmpVisual) + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); } + // Speed change, update the visual actor if necessary. + else if (speed != m_CurSpeed && cmpVisual) + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); m_CurSpeed = speed; } diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 2f37f0fab9..d09815a8eb 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -55,7 +55,6 @@ class CCmpVisualActor : public ICmpVisual public: static void ClassInit(CComponentManager& componentManager) { - componentManager.SubscribeToMessageType(MT_Update_Final); componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged); componentManager.SubscribeToMessageType(MT_OwnershipChanged); componentManager.SubscribeToMessageType(MT_ValueModification); @@ -75,7 +74,6 @@ private: fixed m_R, m_G, m_B; // shading color // Current animation state - fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode std::string m_AnimName; bool m_AnimOnce; fixed m_AnimSpeed; @@ -226,7 +224,6 @@ public: serialize.NumberFixed_Unbounded("g", m_G); serialize.NumberFixed_Unbounded("b", m_B); - serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold); serialize.StringASCII("anim name", m_AnimName, 0, 256); serialize.Bool("anim once", m_AnimOnce); serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed); @@ -281,12 +278,6 @@ public: { switch (msg.GetType()) { - case MT_Update_Final: - { - const CMessageUpdate_Final& msgData = static_cast (msg); - Update(msgData.turnLength); - break; - } case MT_OwnershipChanged: { if (!m_Unit) @@ -437,7 +428,6 @@ public: virtual void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1)) { - m_AnimRunThreshold = fixed::Zero(); m_AnimName = name; m_AnimOnce = once; m_AnimSpeed = speed; @@ -458,9 +448,12 @@ public: m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); } - virtual void SelectMovementAnimation(fixed runThreshold) + virtual void SelectMovementAnimation(const std::string& name, fixed speed) { - m_AnimRunThreshold = runThreshold; + ENSURE(name == "idle" || name == "walk" || name == "run"); + if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run") + return; + SelectAnimation(name, false, speed); } virtual void SetAnimationSyncRepeat(fixed repeattime) @@ -541,8 +534,6 @@ private: // ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical. // It is also used by ReloadActor. void ReloadUnitAnimation(); - - void Update(fixed turnLength); }; REGISTER_COMPONENT_TYPE(VisualActor) @@ -747,42 +738,3 @@ void CCmpVisualActor::ReloadUnitAnimation() if (!m_AnimSyncOffsetTime.IsZero()) m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat()); } - -void CCmpVisualActor::Update(fixed UNUSED(turnLength)) -{ - // This function is currently only used to update the animation if the speed in - // CCmpUnitMotion changes. This also only happens in the "special movement mode" - // triggered by SelectMovementAnimation. - - // TODO: This should become event based, in order to save performance and to make the code - // far less hacky. We should also take into account the speed when the animation is different - // from the "special movement mode" walking animation. - - // If we're not in the special movement mode, nothing to do. - if (m_AnimRunThreshold.IsZero()) - return; - - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition || !cmpPosition->IsInWorld()) - return; - - CmpPtr cmpUnitMotion(GetEntityHandle()); - if (!cmpUnitMotion) - return; - - fixed speed = cmpUnitMotion->GetCurrentSpeed(); - std::string name; - - if (speed.IsZero()) - { - speed = fixed::FromFloat(1.f); - name = "idle"; - } - else - name = speed < m_AnimRunThreshold ? "walk" : "run"; - - // Selecting the animation is going to reset the anim run threshold, so save it - fixed runThreshold = m_AnimRunThreshold; - SelectAnimation(name, false, speed); - m_AnimRunThreshold = runThreshold; -} diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp index e104dd14d8..e9658d5ff7 100644 --- a/source/simulation2/components/ICmpVisual.cpp +++ b/source/simulation2/components/ICmpVisual.cpp @@ -27,7 +27,6 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetAnimationName", std::string, ICmpVisual, Get DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileActor", std::wstring, ICmpVisual, GetProjectileActor) DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileLaunchPoint", CFixedVector3D, ICmpVisual, GetProjectileLaunchPoint) DEFINE_INTERFACE_METHOD_3("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed) -DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed) DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed) diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index ceea277475..9c90197f9a 100644 --- a/source/simulation2/components/ICmpVisual.h +++ b/source/simulation2/components/ICmpVisual.h @@ -96,20 +96,22 @@ public: /** * Start playing the given animation. If there are multiple possible animations then it will * pick one at random (not network-synchronised). - * If @p soundgroup is specified, then the sound will be played at each 'event' point in the - * animation cycle. * @param name animation name (e.g. "idle", "walk", "melee"; the names are determined by actor XML files) * @param once if true then the animation will play once and freeze at the final frame, else it will loop * @param speed animation speed multiplier (typically 1.0 for the default speed) - * @param soundgroup VFS path of sound group .xml, relative to audio/, or empty string for none */ virtual void SelectAnimation(const std::string& name, bool once, fixed speed) = 0; /** - * Start playing the walk/run animations, scaled to the unit's movement speed. - * @param runThreshold movement speed at which to switch to the run animation + * Start playing the given movement animation unless we are currently playing a non-movement animation. + * This is necessary so UnitMotion can set the movement animations without overwriting specific animations + * that might have been set by other components. + * TODO: Non-movement animations should probably be made into variants, defining "idle" (really "default"), "walk" and "run" as appropriate, + * and this would no longer be necessary. + * @param name animation name (i.e. one of "idle", "walk", "run"). + * @param speed animation speed multiplier (typically 1.0 for the default speed) */ - virtual void SelectMovementAnimation(fixed runThreshold) = 0; + virtual void SelectMovementAnimation(const std::string& name, fixed speed) = 0; /** * Adjust the speed of the current animation, so it can match simulation events.