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/LmbrCentral/Code/Source/Scripting/SimpleStateComponent.cpp

472 lines
19 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include "SimpleStateComponent.h"
namespace
{
const char * NullStateName = "<None>";
const char * NewStateName = "New State";
}
namespace LmbrCentral
{
// BehaviorContext SimpleStateComponentNotificationBus forwarder
class BehaviorSimpleStateComponentNotificationBusHandler : public SimpleStateComponentNotificationBus::Handler, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(BehaviorSimpleStateComponentNotificationBusHandler, "{F935125C-AE4E-48C1-BB60-24A0559BC4D2}", AZ::SystemAllocator,
OnStateChanged);
void OnStateChanged(const char* oldState, const char* newState)
{
Call(FN_OnStateChanged, oldState, newState);
}
};
//=========================================================================
// ForEachEntity
//=========================================================================
template <class Func>
void ForEachEntity(AZStd::vector<AZ::EntityId>& entitiyIds, Func entityFunction)
{
for (const AZ::EntityId& currId : entitiyIds)
{
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, currId);
if (entity)
{
entityFunction(entity);
}
}
}
//=========================================================================
// State::State
//=========================================================================
State::State()
: m_name(NewStateName)
{
}
//=========================================================================
// State::Init
//=========================================================================
void State::Init()
{
UpdateNameCrc();
for (const AZ::EntityId& currId : m_entityIds)
{
// Listen for the entity's initialization so we can adjust initial activation state.
AZ::EntityBus::MultiHandler::BusConnect(currId);
}
}
//=========================================================================
// State::Activate
//=========================================================================
void State::Activate()
{
ForEachEntity(m_entityIds,
[](AZ::Entity* entity)
{
if (entity->GetState() != AZ::Entity::State::Active)
{
entity->Activate();
}
}
);
}
//=========================================================================
// State::Deactivate
//=========================================================================
void State::Deactivate()
{
ForEachEntity(m_entityIds,
[](AZ::Entity* entity)
{
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
}
}
);
}
//=========================================================================
// State::UpdateNameCrc
//=========================================================================
void State::UpdateNameCrc()
{
m_nameCrc = AZ::Crc32(m_name.c_str());
}
//=========================================================================
// GetStateFromName
//=========================================================================
State* State::FindWithName(AZStd::vector<State>& states, const char* stateName)
{
if (stateName)
{
const AZ::Crc32 stateNameCrc(stateName);
for (State& currState : states)
{
if ((currState.m_nameCrc == stateNameCrc) && (currState.m_name == stateName))
{
return &currState;
}
}
AZ_Error("SimpleStateComponent", false, "StateName '%s' does not map to any existing states", stateName);
}
return nullptr;
}
//=========================================================================
// OnEntityExists
//=========================================================================
void State::OnEntityExists(const AZ::EntityId& entityId)
{
AZ::EntityBus::MultiHandler::BusDisconnect(entityId);
// Mark the entity to not be activated by default.
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
if (entity && entity->GetState() <= AZ::Entity::State::Init)
{
entity->SetRuntimeActiveByDefault(false);
}
}
//=========================================================================
// SimpleStateComponent::Reflect
//=========================================================================
void State::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<State>()
->Version(1)
->Field("Name", &State::m_name)
->Field("EntityIds", &State::m_entityIds);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<State>("State", "A state includes a name and set of entities that will be activated when the state is entered and deactivated when the state is left.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &State::m_name, "Name", "The name of this state")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c))
->DataElement(0, &State::m_entityIds, "Entities", "The list of entities referenced by this state")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c));
}
}
}
//=========================================================================
// SimpleStateComponent::Reflect
//=========================================================================
void SimpleStateComponent::Reflect(AZ::ReflectContext* context)
{
State::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SimpleStateComponent, AZ::Component>()
->Version(1)
->Field("InitialStateName", &SimpleStateComponent::m_initialStateName)
->Field("ResetOnActivate", &SimpleStateComponent::m_resetStateOnActivate)
->Field("States", &SimpleStateComponent::m_states);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<SimpleStateComponent>("Simple State", "The Simple State component provides a simple state machine allowing activation and deactivation of associated entities")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Gameplay")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/SimpleState.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/SimpleState.svg")
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/simple-state/")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SimpleStateComponent::m_initialStateName, "Initial state", "The initial active state")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c))
->Attribute(AZ::Edit::Attributes::StringList, &SimpleStateComponent::GetStateNames)
->DataElement(0, &SimpleStateComponent::m_resetStateOnActivate, "Reset on activate", "If set, SimpleState will return to the configured initial state when activated, and not the state held prior to being deactivated.")
->DataElement(0, &SimpleStateComponent::m_states, "States", "The list of states")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c));
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<SimpleStateComponentRequestBus>("SimpleStateComponentRequestBus")
->Event("SetState", &SimpleStateComponentRequestBus::Events::SetState)
->Event("SetStateByIndex", &SimpleStateComponentRequestBus::Events::SetStateByIndex)
->Event("SetToNextState", &SimpleStateComponentRequestBus::Events::SetToNextState)
->Event("SetToPreviousState", &SimpleStateComponentRequestBus::Events::SetToPreviousState)
->Event("SetToFirstState", &SimpleStateComponentRequestBus::Events::SetToFirstState)
->Event("SetToLastState", &SimpleStateComponentRequestBus::Events::SetToLastState)
->Event("GetNumStates", &SimpleStateComponentRequestBus::Events::GetNumStates)
->Event("GetCurrentState", &SimpleStateComponentRequestBus::Events::GetCurrentState)
;
behaviorContext->EBus<SimpleStateComponentNotificationBus>("SimpleStateComponentNotificationBus")
->Handler<BehaviorSimpleStateComponentNotificationBusHandler>()
;
}
}
//=========================================================================
// SimpleStateComponent::SimpleStateComponent
//=========================================================================
SimpleStateComponent::SimpleStateComponent()
: m_initialStateName(NullStateName)
{
}
//=========================================================================
// SimpleStateComponent::Init
//=========================================================================
void SimpleStateComponent::Init()
{
for (State& currState : m_states)
{
currState.Init();
}
// Prior revisions used the empty string as null state
const char * initialStateName = (m_initialStateName.empty() || (m_initialStateName == NullStateName)) ? nullptr : m_initialStateName.c_str();
m_initialState = State::FindWithName(m_states, initialStateName);
m_currentState = m_initialState;
}
//=========================================================================
// SimpleStateComponent::Activate
//=========================================================================
void SimpleStateComponent::Activate()
{
if (m_resetStateOnActivate)
{
m_currentState = m_initialState;
}
for (State& currState : m_states)
{
if (&currState != m_currentState)
{
currState.Deactivate();
}
}
if (m_currentState)
{
m_currentState->Activate();
}
SimpleStateComponentRequestBus::Handler::BusConnect(GetEntityId());
if (m_currentState)
{
// Notify newly activated State
// Note: Even if !m_resetStateOnActivate, the prior state to Activation is NullState
EBUS_EVENT_ID(GetEntityId(), SimpleStateComponentNotificationBus, OnStateChanged, nullptr, m_currentState->GetNameCStr());
}
}
//=========================================================================
// SimpleStateComponent::Deactivate
//=========================================================================
void SimpleStateComponent::Deactivate()
{
SimpleStateComponentRequestBus::Handler::BusDisconnect();
if (m_currentState)
{
m_currentState->Deactivate();
}
}
//=========================================================================
// SimpleStateComponent::SetStateInternal
//=========================================================================
void SimpleStateComponent::SetStateInternal(State* newState)
{
// Out with the old
const char* oldName = nullptr;
if (m_currentState)
{
oldName = m_currentState->GetNameCStr();
m_currentState->Deactivate();
}
// In with the new
const char* newName = nullptr;
if (newState)
{
newName = newState->GetNameCStr();
newState->Activate();
}
if (m_currentState != newState)
{
m_currentState = newState;
EBUS_EVENT_ID(GetEntityId(), SimpleStateComponentNotificationBus, OnStateChanged, oldName, newName);
}
}
//=========================================================================
// SimpleStateComponent::SetState
//=========================================================================
void SimpleStateComponent::SetState(const char* stateName)
{
State* newState = State::FindWithName(m_states, stateName);
SetStateInternal(newState);
}
//=========================================================================
// SimpleStateComponent::SetStateByIndex
//=========================================================================
void SimpleStateComponent::SetStateByIndex(AZ::u32 stateIndex)
{
State* newState;
if (stateIndex < m_states.size())
{
newState = &m_states[stateIndex];
}
else
{
newState = nullptr;
AZ_Error("SimpleStateComponent", false, "StateName index '%d' is invalid (currently %d states)", stateIndex, static_cast<AZ::u32>(m_states.size()));
}
SetStateInternal(newState);
}
//=========================================================================
// SimpleStateComponent::SetToNextState
//=========================================================================
void SimpleStateComponent::SetToNextState()
{
if (!m_states.empty())
{
SetStateToOffset(1, m_states.front());
}
}
//=========================================================================
// SimpleStateComponent::SetToPreviousState
//=========================================================================
void SimpleStateComponent::SetToPreviousState()
{
if (!m_states.empty())
{
SetStateToOffset(-1, m_states.back());
}
}
//=========================================================================
// SimpleStateComponent::SetStateToOffset
//=========================================================================
void SimpleStateComponent::SetStateToOffset(AZ::s32 offset, State& fromNullState)
{
AZ_Assert(!m_states.empty(), "violated precondition - must be non-empty");
State* newState;
if (m_currentState)
{
const size_t currentStateIndex = m_currentState - &m_states[0];
AZ_Assert(currentStateIndex < m_states.size(), "Invalid current state pointer");
const size_t numStates = m_states.size();
const size_t newStateIndex = (currentStateIndex + numStates + offset) % numStates;
AZ_Assert(newStateIndex < numStates, "Invalid negative offset");
newState = &m_states[newStateIndex];
}
else
{
// "Advance" to the provided state from NullState
newState = &fromNullState;
}
SetStateInternal(newState);
}
//=========================================================================
// SimpleStateComponent::SetToFirstState
//=========================================================================
void SimpleStateComponent::SetToFirstState()
{
if (!m_states.empty())
{
SetStateInternal(&m_states.front());
}
}
//=========================================================================
// SimpleStateComponent::SetToLastState
//=========================================================================
void SimpleStateComponent::SetToLastState()
{
if (!m_states.empty())
{
SetStateInternal(&m_states.back());
}
}
//=========================================================================
// SimpleStateComponent::GetNumStates
//=========================================================================
AZ::u32 SimpleStateComponent::GetNumStates()
{
return static_cast<AZ::u32>(m_states.size());
}
//=========================================================================
// SimpleStateComponent::GetState
//=========================================================================
const char* SimpleStateComponent::GetCurrentState()
{
return m_currentState ? m_currentState->GetNameCStr() : nullptr;
}
//=========================================================================
// SimpleStateComponent::GetStateNames
//=========================================================================
AZStd::vector<AZStd::string> SimpleStateComponent::GetStateNames() const
{
AZStd::vector<AZStd::string> names;
names.reserve(m_states.size() + 1);
// Provide "no initial state" as an option
names.emplace_back(NullStateName);
for (const State& currState : m_states)
{
names.emplace_back(currState.GetName());
}
return names;
}
} // namespace LmbrCentral