/* * 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 #include #include #include #include #include #include #include #include #include namespace EMotionFX { namespace Integration { void EditorSimpleMotionComponent::Reflect(AZ::ReflectContext* context) { auto* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(3) ->Field("PreviewInEditor", &EditorSimpleMotionComponent::m_previewInEditor) ->Field("Configuration", &EditorSimpleMotionComponent::m_configuration) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { editContext->Class( "Simple Motion", "The Simple Motion component assigns a single motion to the associated Actor in lieu of an Anim Graph component") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Animation") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/SimpleMotion.svg") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, azrtti_typeid()) ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Mannequin.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(0, &EditorSimpleMotionComponent::m_previewInEditor, "Preview In Editor", "Plays motion in Editor") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged) ->DataElement(0, &EditorSimpleMotionComponent::m_configuration, "Configuration", "Settings for this Simple Motion") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-simple-motion.html") ; } } AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (behaviorContext) { behaviorContext->EBus("EditorSimpleMotionComponentRequestBus") ->Event("SetPreviewInEditor", &EditorSimpleMotionComponentRequestBus::Events::SetPreviewInEditor) ->Event("GetPreviewInEditor", &EditorSimpleMotionComponentRequestBus::Events::GetPreviewInEditor) ->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden) ->VirtualProperty("PreviewInEditor", "GetPreviewInEditor", "SetPreviewInEditor") ->Event("GetAssetDuration", &EditorSimpleMotionComponentRequestBus::Events::GetAssetDuration) ->Event("LoopMotion", &EditorSimpleMotionComponentRequestBus::Events::LoopMotion) ->Event("GetLoopMotion", &EditorSimpleMotionComponentRequestBus::Events::GetLoopMotion) ->VirtualProperty("LoopMotion", "GetLoopMotion", "LoopMotion") ->Event("RetargetMotion", &EditorSimpleMotionComponentRequestBus::Events::RetargetMotion) ->Event("GetRetargetMotion", &EditorSimpleMotionComponentRequestBus::Events::GetRetargetMotion) ->VirtualProperty("RetargetMotion", "GetRetargetMotion", "RetargetMotion") ->Event("ReverseMotion", &EditorSimpleMotionComponentRequestBus::Events::ReverseMotion) ->Event("GetReverseMotion", &EditorSimpleMotionComponentRequestBus::Events::GetReverseMotion) ->VirtualProperty("ReverseMotion", "GetReverseMotion", "ReverseMotion") ->Event("MirrorMotion", &EditorSimpleMotionComponentRequestBus::Events::MirrorMotion) ->Event("GetMirrorMotion", &EditorSimpleMotionComponentRequestBus::Events::GetMirrorMotion) ->VirtualProperty("MirrorMotion", "GetMirrorMotion", "MirrorMotion") ->Event("SetPlaySpeed", &EditorSimpleMotionComponentRequestBus::Events::SetPlaySpeed) ->Event("GetPlaySpeed", &EditorSimpleMotionComponentRequestBus::Events::GetPlaySpeed) ->VirtualProperty("PlaySpeed", "GetPlaySpeed", "SetPlaySpeed") ->Event("PlayTime", &EditorSimpleMotionComponentRequestBus::Events::PlayTime) ->Event("GetPlayTime", &EditorSimpleMotionComponentRequestBus::Events::GetPlayTime) ->VirtualProperty("PlayTime", "GetPlayTime", "PlayTime") ->Event("Motion", &EditorSimpleMotionComponentRequestBus::Events::Motion) ->Event("GetMotion", &EditorSimpleMotionComponentRequestBus::Events::GetMotion) ->Attribute(AZ::Script::Attributes::Ignore, true) ->VirtualProperty("Motion", "GetMotion", "Motion") ->Event("BlendInTime", &EditorSimpleMotionComponentRequestBus::Events::BlendInTime) ->Event("GetBlendInTime", &EditorSimpleMotionComponentRequestBus::Events::GetBlendInTime) ->Event("BlendOutTime", &EditorSimpleMotionComponentRequestBus::Events::BlendOutTime) ->Event("GetBlendOutTime", &EditorSimpleMotionComponentRequestBus::Events::GetBlendOutTime) ; behaviorContext->Class() ->RequestBus("SimpleMotionComponentRequestBus") ->RequestBus("EditorSimpleMotionComponentRequestBus") ; } } EditorSimpleMotionComponent::EditorSimpleMotionComponent() : m_previewInEditor(false) , m_configuration() , m_actorInstance(nullptr) , m_motionInstance(nullptr) , m_lastMotionInstance(nullptr) { } EditorSimpleMotionComponent::~EditorSimpleMotionComponent() { } void EditorSimpleMotionComponent::Activate() { AZ::Data::AssetBus::MultiHandler::BusDisconnect(); SimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId()); EditorSimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId()); //check if our motion has changed VerifyMotionAssetState(); ActorComponentNotificationBus::Handler::BusConnect(GetEntityId()); } void EditorSimpleMotionComponent::Deactivate() { ActorComponentNotificationBus::Handler::BusDisconnect(); EditorSimpleMotionComponentRequestBus::Handler::BusDisconnect(); SimpleMotionComponentRequestBus::Handler::BusDisconnect(); AZ::Data::AssetBus::MultiHandler::BusDisconnect(); RemoveMotionInstanceFromActor(m_lastMotionInstance); m_lastMotionInstance = nullptr; RemoveMotionInstanceFromActor(m_motionInstance); m_motionInstance = nullptr; m_configuration.m_motionAsset.Release(); m_lastMotionAsset.Release(); m_actorInstance = nullptr; } void EditorSimpleMotionComponent::VerifyMotionAssetState() { AZ::Data::AssetBus::MultiHandler::BusDisconnect(); if (m_configuration.m_motionAsset.GetId().IsValid()) { AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId()); m_configuration.m_motionAsset.QueueLoad(); } } void EditorSimpleMotionComponent::OnAssetReady(AZ::Data::Asset asset) { if (asset == m_configuration.m_motionAsset) { m_configuration.m_motionAsset = asset; PlayMotion(); } } void EditorSimpleMotionComponent::OnAssetReloaded(AZ::Data::Asset asset) { OnAssetReady(asset); } void EditorSimpleMotionComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) { m_actorInstance = actorInstance; PlayMotion(); } void EditorSimpleMotionComponent::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) { RemoveMotionInstanceFromActor(m_lastMotionInstance); m_lastMotionInstance = nullptr; RemoveMotionInstanceFromActor(m_motionInstance); m_motionInstance = nullptr; m_actorInstance = nullptr; } void EditorSimpleMotionComponent::PlayMotion() { if (m_previewInEditor) { // The Editor allows scrubbing back and forth on animation blending transitions, so don't delete // motion instances if it's blend weight is zero. // The Editor preview should preview the motion in place to prevent off center movement. m_motionInstance = SimpleMotionComponent::PlayMotionInternal(m_actorInstance, m_configuration, /*deleteOnZeroWeight*/false, /*inPlace*/true); } } void EditorSimpleMotionComponent::RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance) { if (motionInstance) { if (m_actorInstance && m_actorInstance->GetMotionSystem()) { m_actorInstance->GetMotionSystem()->RemoveMotionInstance(motionInstance); } } } void EditorSimpleMotionComponent::BuildGameEntity(AZ::Entity* gameEntity) { gameEntity->AddComponent(aznew SimpleMotionComponent(&m_configuration)); } void EditorSimpleMotionComponent::LoopMotion(bool enable) { m_configuration.m_loop = enable; if (m_motionInstance) { m_motionInstance->SetMaxLoops(enable ? EMFX_LOOPFOREVER : 1); } } bool EditorSimpleMotionComponent::GetLoopMotion() const { return m_configuration.m_loop; } void EditorSimpleMotionComponent::RetargetMotion(bool enable) { m_configuration.m_retarget = enable; if (m_motionInstance) { m_motionInstance->SetRetargetingEnabled(enable); } } bool EditorSimpleMotionComponent::GetRetargetMotion() const { return m_configuration.m_retarget; } void EditorSimpleMotionComponent::ReverseMotion(bool enable) { m_configuration.m_reverse = enable; if (m_motionInstance) { m_motionInstance->SetPlayMode(enable ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD); } } bool EditorSimpleMotionComponent::GetReverseMotion() const { return m_configuration.m_reverse; } void EditorSimpleMotionComponent::MirrorMotion(bool enable) { m_configuration.m_mirror = enable; if (m_motionInstance) { m_motionInstance->SetMirrorMotion(enable); } } bool EditorSimpleMotionComponent::GetMirrorMotion() const { return m_configuration.m_mirror; } void EditorSimpleMotionComponent::SetPlaySpeed(float speed) { m_configuration.m_playspeed = speed; if (m_motionInstance) { m_motionInstance->SetPlaySpeed(speed); } } float EditorSimpleMotionComponent::GetPlaySpeed() const { return m_configuration.m_playspeed; } float EditorSimpleMotionComponent::GetAssetDuration(const AZ::Data::AssetId& assetId) { float result = 1.0f; // Do a blocking load of the asset. AZ::Data::Asset motionAsset = AZ::Data::AssetManager::Instance().GetAsset(assetId, true, nullptr, true); if (motionAsset && motionAsset.Get()->m_emfxMotion) { result = motionAsset.Get()->m_emfxMotion.get()->GetMaxTime(); } motionAsset.Release(); return result; } void EditorSimpleMotionComponent::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 EditorSimpleMotionComponent::GetPlayTime() const { float result = 0.0f; if (m_motionInstance) { result = m_motionInstance->GetCurrentTimeNormalized(); } return result; } void EditorSimpleMotionComponent::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 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(assetId); } } // Connect the bus if the asset is valid. if (m_configuration.m_motionAsset.GetId().IsValid()) { AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId()); } } } AZ::Data::AssetId EditorSimpleMotionComponent::GetMotion() const { return m_configuration.m_motionAsset.GetId(); } void EditorSimpleMotionComponent::SetPreviewInEditor(bool enable) { if (m_previewInEditor != enable) { m_previewInEditor = enable; OnEditorPropertyChanged(); } } bool EditorSimpleMotionComponent::GetPreviewInEditor() const { return m_previewInEditor; } void EditorSimpleMotionComponent::BlendInTime(float time) { m_configuration.m_blendInTime = time; } float EditorSimpleMotionComponent::GetBlendInTime() const { return m_configuration.m_blendInTime; } void EditorSimpleMotionComponent::BlendOutTime(float time) { m_configuration.m_blendOutTime = time; } float EditorSimpleMotionComponent::GetBlendOutTime() const { return m_configuration.m_blendOutTime; } AZ::Crc32 EditorSimpleMotionComponent::OnEditorPropertyChanged() { RemoveMotionInstanceFromActor(m_lastMotionInstance); m_lastMotionInstance = nullptr; RemoveMotionInstanceFromActor(m_motionInstance); m_motionInstance = nullptr; m_configuration.m_motionAsset.Release(); VerifyMotionAssetState(); return AZ::Edit::PropertyRefreshLevels::None; } } }