You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/EMotionFX/Code/Source/Integration/Components/SimpleMotionComponent.cpp

469 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)
->Event("GetMotion", &SimpleMotionComponentRequestBus::Events::GetMotion)
->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