818c2526c9
* Removed discrepancy between editor and game simple motion components which led to an animation being played in the game one while the editor component looked broken for animations with root joints animated only. * Sharing a new in-place attribute between the game and editor components that lets users control whether positional and rotational changes shall be applied onto root joints or not.
471 lines
20 KiB
C++
471 lines
20 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "EMotionFX_precompiled.h"
|
|
|
|
#include <AzCore/Component/Entity.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Script/ScriptContext.h>
|
|
|
|
#include <Integration/Components/SimpleMotionComponent.h>
|
|
#include <MCore/Source/AttributeString.h>
|
|
|
|
namespace EMotionFX
|
|
{
|
|
namespace Integration
|
|
{
|
|
|
|
void SimpleMotionComponent::Configuration::Reflect(AZ::ReflectContext *context)
|
|
{
|
|
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<Configuration>()
|
|
->Version(2)
|
|
->Field("MotionAsset", &Configuration::m_motionAsset)
|
|
->Field("Loop", &Configuration::m_loop)
|
|
->Field("Retarget", &Configuration::m_retarget)
|
|
->Field("Reverse", &Configuration::m_reverse)
|
|
->Field("Mirror", &Configuration::m_mirror)
|
|
->Field("PlaySpeed", &Configuration::m_playspeed)
|
|
->Field("BlendIn", &Configuration::m_blendInTime)
|
|
->Field("BlendOut", &Configuration::m_blendOutTime)
|
|
->Field("PlayOnActivation", &Configuration::m_playOnActivation)
|
|
->Field("InPlace", &Configuration::m_inPlace)
|
|
;
|
|
|
|
AZ::EditContext* editContext = serializeContext->GetEditContext();
|
|
if (editContext)
|
|
{
|
|
editContext->Class<Configuration>( "Configuration", "Settings for this Simple Motion")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_motionAsset, "Motion", "EMotion FX motion to be loaded for this actor")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_loop, "Loop motion", "Toggles looping of the animation")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_retarget, "Retarget motion", "Toggles retargeting of the animation")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_reverse, "Reverse motion", "Toggles reversing of the animation")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_mirror, "Mirror motion", "Toggles mirroring of the animation")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_playspeed, "Play speed", "Determines the rate at which the motion is played")
|
|
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_blendInTime, "Blend In Time", "Determines the blend in time in seconds")
|
|
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_blendOutTime, "Blend Out Time", "Determines the blend out time in seconds")
|
|
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_playOnActivation, "Play on active", "Playing animation immediately after activation.")
|
|
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_inPlace, "In-place",
|
|
"Plays the animation in-place and removes any positional and rotational changes from root joints.")
|
|
;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
Configuration::Reflect(context);
|
|
|
|
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<SimpleMotionComponent, AZ::Component>()
|
|
->Version(1)
|
|
->Field("Configuration", &SimpleMotionComponent::m_configuration)
|
|
;
|
|
}
|
|
|
|
auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
|
|
if (behaviorContext)
|
|
{
|
|
behaviorContext->EBus<SimpleMotionComponentRequestBus>("SimpleMotionComponentRequestBus")
|
|
->Event("LoopMotion", &SimpleMotionComponentRequestBus::Events::LoopMotion)
|
|
->Event("GetLoopMotion", &SimpleMotionComponentRequestBus::Events::GetLoopMotion)
|
|
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
|
|
->VirtualProperty("LoopMotion", "GetLoopMotion", "LoopMotion")
|
|
->Event("RetargetMotion", &SimpleMotionComponentRequestBus::Events::RetargetMotion)
|
|
->Event("ReverseMotion", &SimpleMotionComponentRequestBus::Events::ReverseMotion)
|
|
->Event("MirrorMotion", &SimpleMotionComponentRequestBus::Events::MirrorMotion)
|
|
->Event("SetPlaySpeed", &SimpleMotionComponentRequestBus::Events::SetPlaySpeed)
|
|
->Event("GetPlaySpeed", &SimpleMotionComponentRequestBus::Events::GetPlaySpeed)
|
|
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
|
|
->VirtualProperty("PlaySpeed", "GetPlaySpeed", "SetPlaySpeed")
|
|
->Event("PlayTime", &SimpleMotionComponentRequestBus::Events::PlayTime)
|
|
->Event("GetPlayTime", &SimpleMotionComponentRequestBus::Events::GetPlayTime)
|
|
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
|
|
->VirtualProperty("PlayTime", "GetPlayTime", "PlayTime")
|
|
->Event("Motion", &SimpleMotionComponentRequestBus::Events::Motion)
|
|
->Attribute(AZ::Script::Attributes::Ignore, true)
|
|
->Event("GetMotion", &SimpleMotionComponentRequestBus::Events::GetMotion)
|
|
->Attribute(AZ::Script::Attributes::Ignore, true)
|
|
->VirtualProperty("Motion", "GetMotion", "Motion")
|
|
->Event("BlendInTime", &SimpleMotionComponentRequestBus::Events::BlendInTime)
|
|
->Event("GetBlendInTime", &SimpleMotionComponentRequestBus::Events::GetBlendInTime)
|
|
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
|
|
->VirtualProperty("BlendInTime", "GetBlendInTime", "BlendInTime")
|
|
->Event("BlendOutTime", &SimpleMotionComponentRequestBus::Events::BlendOutTime)
|
|
->Event("GetBlendOutTime", &SimpleMotionComponentRequestBus::Events::GetBlendOutTime)
|
|
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
|
|
->VirtualProperty("BlendOutTime", "GetBlendOutTime", "BlendOutTime")
|
|
->Event("PlayMotion", &SimpleMotionComponentRequestBus::Events::PlayMotion)
|
|
;
|
|
|
|
behaviorContext->Class<SimpleMotionComponent>()->RequestBus("SimpleMotionComponentRequestBus");
|
|
}
|
|
}
|
|
|
|
SimpleMotionComponent::Configuration::Configuration()
|
|
: m_loop(false)
|
|
, m_retarget(false)
|
|
, m_reverse(false)
|
|
, m_mirror(false)
|
|
, m_playspeed(1.f)
|
|
, m_blendInTime(0.0f)
|
|
, m_blendOutTime(0.0f)
|
|
, m_playOnActivation(true)
|
|
, m_inPlace(false)
|
|
{
|
|
}
|
|
|
|
SimpleMotionComponent::SimpleMotionComponent(const Configuration* config)
|
|
: m_actorInstance(nullptr)
|
|
, m_motionInstance(nullptr)
|
|
, m_lastMotionInstance(nullptr)
|
|
{
|
|
if (config)
|
|
{
|
|
m_configuration = *config;
|
|
}
|
|
}
|
|
|
|
SimpleMotionComponent::~SimpleMotionComponent()
|
|
{
|
|
}
|
|
|
|
void SimpleMotionComponent::Init()
|
|
{
|
|
}
|
|
|
|
void SimpleMotionComponent::Activate()
|
|
{
|
|
m_motionInstance = nullptr;
|
|
|
|
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
|
|
|
|
SimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId());
|
|
|
|
auto& cfg = m_configuration;
|
|
|
|
if (cfg.m_motionAsset.GetId().IsValid())
|
|
{
|
|
AZ::Data::AssetBus::MultiHandler::BusConnect(cfg.m_motionAsset.GetId());
|
|
cfg.m_motionAsset.QueueLoad();
|
|
}
|
|
|
|
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
|
|
}
|
|
|
|
void SimpleMotionComponent::Deactivate()
|
|
{
|
|
SimpleMotionComponentRequestBus::Handler::BusDisconnect();
|
|
ActorComponentNotificationBus::Handler::BusDisconnect();
|
|
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
|
|
|
|
RemoveMotionInstanceFromActor(m_motionInstance);
|
|
m_motionInstance = nullptr;
|
|
RemoveMotionInstanceFromActor(m_lastMotionInstance);
|
|
m_lastMotionInstance = nullptr;
|
|
m_configuration.m_motionAsset.Release();
|
|
m_lastMotionAsset.Release();
|
|
m_actorInstance.reset();
|
|
}
|
|
|
|
const MotionInstance* SimpleMotionComponent::GetMotionInstance()
|
|
{
|
|
return m_motionInstance;
|
|
}
|
|
|
|
void SimpleMotionComponent::SetMotionAssetId(const AZ::Data::AssetId& assetId)
|
|
{
|
|
m_configuration.m_motionAsset = AZ::Data::Asset<MotionAsset>(assetId, azrtti_typeid<MotionAsset>());
|
|
}
|
|
|
|
void SimpleMotionComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
|
{
|
|
auto& cfg = m_configuration;
|
|
|
|
if (asset.GetId() == cfg.m_motionAsset.GetId())
|
|
{
|
|
cfg.m_motionAsset = asset;
|
|
if (m_configuration.m_playOnActivation)
|
|
{
|
|
PlayMotion();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
|
|
{
|
|
OnAssetReady(asset);
|
|
}
|
|
|
|
void SimpleMotionComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
|
|
{
|
|
m_actorInstance = actorInstance;
|
|
if (m_configuration.m_playOnActivation)
|
|
{
|
|
PlayMotion();
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
|
|
{
|
|
AZ_UNUSED(actorInstance);
|
|
RemoveMotionInstanceFromActor(m_motionInstance);
|
|
m_motionInstance = nullptr;
|
|
RemoveMotionInstanceFromActor(m_lastMotionInstance);
|
|
m_lastMotionInstance = nullptr;
|
|
|
|
m_actorInstance.reset();
|
|
}
|
|
|
|
void SimpleMotionComponent::PlayMotion()
|
|
{
|
|
m_motionInstance = PlayMotionInternal(m_actorInstance.get(), m_configuration, /*deleteOnZeroWeight*/true);
|
|
}
|
|
|
|
void SimpleMotionComponent::RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance)
|
|
{
|
|
if (motionInstance)
|
|
{
|
|
if (m_actorInstance && m_actorInstance->GetMotionSystem())
|
|
{
|
|
m_actorInstance->GetMotionSystem()->RemoveMotionInstance(motionInstance);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::LoopMotion(bool enable)
|
|
{
|
|
m_configuration.m_loop = enable;
|
|
if (m_motionInstance)
|
|
{
|
|
m_motionInstance->SetMaxLoops(enable ? EMFX_LOOPFOREVER : 1);
|
|
}
|
|
}
|
|
|
|
bool SimpleMotionComponent::GetLoopMotion() const
|
|
{
|
|
return m_configuration.m_loop;
|
|
}
|
|
|
|
void SimpleMotionComponent::RetargetMotion(bool enable)
|
|
{
|
|
m_configuration.m_retarget = enable;
|
|
if (m_motionInstance)
|
|
{
|
|
m_motionInstance->SetRetargetingEnabled(enable);
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::ReverseMotion(bool enable)
|
|
{
|
|
m_configuration.m_reverse = enable;
|
|
if (m_motionInstance)
|
|
{
|
|
m_motionInstance->SetPlayMode(enable ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD);
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::MirrorMotion(bool enable)
|
|
{
|
|
m_configuration.m_mirror = enable;
|
|
if (m_motionInstance)
|
|
{
|
|
m_motionInstance->SetMirrorMotion(enable);
|
|
}
|
|
}
|
|
|
|
void SimpleMotionComponent::SetPlaySpeed(float speed)
|
|
{
|
|
m_configuration.m_playspeed = speed;
|
|
if (m_motionInstance)
|
|
{
|
|
m_motionInstance->SetPlaySpeed(speed);
|
|
}
|
|
}
|
|
|
|
float SimpleMotionComponent::GetPlaySpeed() const
|
|
{
|
|
return m_configuration.m_playspeed;
|
|
}
|
|
|
|
void SimpleMotionComponent::PlayTime(float time)
|
|
{
|
|
if (m_motionInstance)
|
|
{
|
|
float delta = time - m_motionInstance->GetLastCurrentTime();
|
|
m_motionInstance->SetCurrentTime(time, false);
|
|
|
|
// Apply the same time step to the last animation
|
|
// so blend out will be good. Otherwise we are just blending
|
|
// from the last frame played of the last animation.
|
|
if (m_lastMotionInstance && m_lastMotionInstance->GetIsBlending())
|
|
{
|
|
m_lastMotionInstance->SetCurrentTime(m_lastMotionInstance->GetLastCurrentTime() + delta, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
float SimpleMotionComponent::GetPlayTime() const
|
|
{
|
|
float result = 0.0f;
|
|
if (m_motionInstance)
|
|
{
|
|
result = m_motionInstance->GetCurrentTimeNormalized();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SimpleMotionComponent::Motion(AZ::Data::AssetId assetId)
|
|
{
|
|
if (m_configuration.m_motionAsset.GetId() != assetId)
|
|
{
|
|
// Disconnect the old asset bus
|
|
if (AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(m_configuration.m_motionAsset.GetId()))
|
|
{
|
|
AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_configuration.m_motionAsset.GetId());
|
|
}
|
|
|
|
// Save the motion asset that we are about to be remove in case it can be reused.
|
|
AZ::Data::Asset<MotionAsset> oldLastMotionAsset = m_lastMotionAsset;
|
|
|
|
if (m_lastMotionInstance)
|
|
{
|
|
RemoveMotionInstanceFromActor(m_lastMotionInstance);
|
|
}
|
|
|
|
// Store the current motion asset as the last one for possible blending.
|
|
// If we don't keep a reference to the motion asset, the motion instance will be
|
|
// automatically released.
|
|
if (m_configuration.m_motionAsset.GetId().IsValid())
|
|
{
|
|
m_lastMotionAsset = m_configuration.m_motionAsset;
|
|
}
|
|
|
|
// Set the current motion instance as the last motion instance. The new current motion
|
|
// instance will then be set when the load is complete.
|
|
m_lastMotionInstance = m_motionInstance;
|
|
m_motionInstance = nullptr;
|
|
|
|
// Start the fade out if there is a blend out time. Otherwise just leave the
|
|
// m_lastMotionInstance where it is at so the next anim can blend from that frame.
|
|
if (m_lastMotionInstance && m_configuration.m_blendOutTime > 0.0f)
|
|
{
|
|
m_lastMotionInstance->Stop(m_configuration.m_blendOutTime);
|
|
}
|
|
|
|
// Reuse the old, last motion asset if possible. Otherwise, request a load.
|
|
if (assetId.IsValid() && oldLastMotionAsset.GetData() && assetId == oldLastMotionAsset.GetId())
|
|
{
|
|
// Even though we are not calling GetAsset here, OnAssetReady
|
|
// will be fired when the bus is connected because this asset is already loaded.
|
|
m_configuration.m_motionAsset = oldLastMotionAsset;
|
|
}
|
|
else
|
|
{
|
|
// Won't be able to reuse oldLastMotionAsset, release it.
|
|
oldLastMotionAsset.Release();
|
|
|
|
// Clear the old asset.
|
|
m_configuration.m_motionAsset.Release();
|
|
|
|
// Create a new asset
|
|
if (assetId.IsValid())
|
|
{
|
|
m_configuration.m_motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(assetId, m_configuration.m_motionAsset.GetAutoLoadBehavior());
|
|
}
|
|
}
|
|
|
|
// Connect the bus if the asset is is valid.
|
|
if (m_configuration.m_motionAsset.GetId().IsValid())
|
|
{
|
|
AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId());
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
AZ::Data::AssetId SimpleMotionComponent::GetMotion() const
|
|
{
|
|
return m_configuration.m_motionAsset.GetId();
|
|
}
|
|
|
|
void SimpleMotionComponent::BlendInTime(float time)
|
|
{
|
|
m_configuration.m_blendInTime = time;
|
|
}
|
|
|
|
float SimpleMotionComponent::GetBlendInTime() const
|
|
{
|
|
return m_configuration.m_blendInTime;
|
|
}
|
|
|
|
void SimpleMotionComponent::BlendOutTime(float time)
|
|
{
|
|
m_configuration.m_blendOutTime = time;
|
|
}
|
|
|
|
float SimpleMotionComponent::GetBlendOutTime() const
|
|
{
|
|
return m_configuration.m_blendOutTime;
|
|
}
|
|
|
|
EMotionFX::MotionInstance* SimpleMotionComponent::PlayMotionInternal(const EMotionFX::ActorInstance* actorInstance, const SimpleMotionComponent::Configuration& cfg, bool deleteOnZeroWeight)
|
|
{
|
|
if (!actorInstance || !cfg.m_motionAsset.IsReady())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (!actorInstance->GetMotionSystem())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
auto* motionAsset = cfg.m_motionAsset.GetAs<MotionAsset>();
|
|
if (!motionAsset)
|
|
|
|
{
|
|
AZ_Error("EMotionFX", motionAsset, "Motion asset is not valid.");
|
|
return nullptr;
|
|
}
|
|
//init the PlaybackInfo based on our config
|
|
EMotionFX::PlayBackInfo info;
|
|
info.mNumLoops = cfg.m_loop ? EMFX_LOOPFOREVER : 1;
|
|
info.mRetarget = cfg.m_retarget;
|
|
info.mPlayMode = cfg.m_reverse ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD;
|
|
info.mFreezeAtLastFrame = info.mNumLoops == 1;
|
|
info.mMirrorMotion = cfg.m_mirror;
|
|
info.mPlaySpeed = cfg.m_playspeed;
|
|
info.mPlayNow = true;
|
|
info.mDeleteOnZeroWeight = deleteOnZeroWeight;
|
|
info.mCanOverwrite = false;
|
|
info.mBlendInTime = cfg.m_blendInTime;
|
|
info.mBlendOutTime = cfg.m_blendOutTime;
|
|
info.mInPlace = cfg.m_inPlace;
|
|
return actorInstance->GetMotionSystem()->PlayMotion(motionAsset->m_emfxMotion.get(), &info);
|
|
}
|
|
|
|
} // namespace integration
|
|
} // namespace EMotionFX
|
|
|