Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,221 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <LmbrCentral/Audio/AudioSystemComponentBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game options that can be modified via the options menu and saved to persistent storage.
class GameOptions final
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! Name of the game options save data file.
static constexpr const char* SaveDataBufferName = "GameOptions";
////////////////////////////////////////////////////////////////////////////////////////////
//! Default value for the specified game option.
///@{
static constexpr float DefaultAmbientVolume = 100.0f;
static constexpr float DefaultEffectsVolume = 100.0f;
static constexpr float DefaultMainVolume = 100.0f;
static constexpr float DefaultMusicVolume = 100.0f;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameOptions, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameOptions, "{DC3C8011-7E2B-458F-8C95-FC1A06C9D8F4}");
////////////////////////////////////////////////////////////////////////////////////////////
// Reflection
static void Reflect(AZ::SerializeContext& sc);
////////////////////////////////////////////////////////////////////////////////////////////
//! Called when loaded from persistent data.
void OnLoadedFromPersistentData();
////////////////////////////////////////////////////////////////////////////////////////////
//! Effects volume accessor function.
///@{
float GetAmbientVolume() const;
void SetAmbientVolume(float ambientVolume);
void ApplyAmbientVolume();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Effects volume accessor function.
///@{
float GetEffectsVolume() const;
void SetEffectsVolume(float effectsVolume);
void ApplyEffectsVolume();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Main volume accessor function.
///@{
float GetMainVolume() const;
void SetMainVolume(float mainVolume);
void ApplyMainVolume();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Music volume accessor function.
///@{
float GetMusicVolume() const;
void SetMusicVolume(float musicVolume);
void ApplyMusicVolume();
///@}
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
float m_ambientVolume = DefaultAmbientVolume; //!< The current ambient volume.
float m_effectsVolume = DefaultEffectsVolume; //!< The current effects volume.
float m_mainVolume = DefaultMainVolume; //!< The current main volume.
float m_musicVolume = DefaultMusicVolume; //!< The current music volume.
};
////////////////////////////////////////////////////////////////////////////////////////////////
//! EBus interface used to submit requests related to game options.
class GameOptionRequests : public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
//! EBus Trait: requests can only be sent to and addressed by a single instance (singleton)
///@{
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Retrieve the game options.
virtual AZStd::shared_ptr<GameOptions> GetGameOptions() = 0;
};
using GameOptionRequestBus = AZ::EBus<GameOptionRequests>;
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::Reflect(AZ::SerializeContext& sc)
{
sc.Class<GameOptions>()
->Version(1)
->Field("ambientVolume", &GameOptions::m_ambientVolume)
->Field("effectsVolume", &GameOptions::m_effectsVolume)
->Field("mainVolume", &GameOptions::m_mainVolume)
->Field("musicVolume", &GameOptions::m_musicVolume)
;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::OnLoadedFromPersistentData()
{
ApplyAmbientVolume();
ApplyEffectsVolume();
ApplyMainVolume();
ApplyMusicVolume();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline float GameOptions::GetAmbientVolume() const
{
return m_ambientVolume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::SetAmbientVolume(float ambientVolume)
{
m_ambientVolume = ambientVolume;
ApplyAmbientVolume();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::ApplyAmbientVolume()
{
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequests::GlobalSetAudioRtpc,
"AmbientVolume",
m_ambientVolume);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline float GameOptions::GetEffectsVolume() const
{
return m_effectsVolume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::SetEffectsVolume(float effectsVolume)
{
m_effectsVolume = effectsVolume;
ApplyEffectsVolume();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::ApplyEffectsVolume()
{
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequests::GlobalSetAudioRtpc,
"EffectsVolume",
m_effectsVolume);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline float GameOptions::GetMainVolume() const
{
return m_mainVolume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::SetMainVolume(float mainVolume)
{
m_mainVolume = mainVolume;
ApplyMainVolume();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::ApplyMainVolume()
{
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequests::GlobalSetAudioRtpc,
"MainVolume",
m_mainVolume);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline float GameOptions::GetMusicVolume() const
{
return m_musicVolume;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::SetMusicVolume(float musicVolume)
{
m_musicVolume = musicVolume;
ApplyMusicVolume();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameOptions::ApplyMusicVolume()
{
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequests::GlobalSetAudioRtpc,
"MusicVolume",
m_musicVolume);
}
} // namespace GameState
@@ -0,0 +1,59 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <ISystem.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while a level is loading.
class GameStateLevelLoading : public GameState::IGameState
, public ISystemEventListener
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateLevelLoading, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateLevelLoading, "{3ABD903B-4E9D-4BFB-A080-4795253F420C}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateLevelLoading() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateLevelLoading() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref ISystemEventListener::OnSystemEvent
void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override;
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateLevelLoading.inl>
@@ -0,0 +1,62 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateLevelLoading.h>
#include <GameStateSamples/GameStateLevelRunning.h>
#include <IConsole.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelLoading::OnEnter()
{
ISystem* iSystem = GetISystem();
if (iSystem)
{
iSystem->GetISystemEventDispatcher()->RegisterListener(this);
}
IConsole* iConsole = iSystem ? iSystem->GetIConsole() : nullptr;
if (iConsole)
{
iConsole->GetCVar("level_load_screen_uicanvas_path")->Set("@assets@/ui/canvases/defaultlevelloadingscreen.uicanvas");
iConsole->GetCVar("level_load_screen_sequence_to_auto_play")->Set("DefaultLevelLoadingAnimatedSequence");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelLoading::OnExit()
{
if (ISystem* iSystem = GetISystem())
{
iSystem->GetISystemEventDispatcher()->RemoveListener(this);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelLoading::OnSystemEvent(ESystemEvent event, UINT_PTR, UINT_PTR)
{
if (event == ESYSTEM_EVENT_LEVEL_LOAD_END)
{
// Replace the level loading game state with the level running game state
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelLoading>(),
"The active game state is not of type GameStateLevelLoading");
AZ_Assert(!GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelRunning>(),
"The game state stack already contains an instance of GameStateLevelRunning");
AZStd::shared_ptr<GameState::IGameState> gameStateLevelRunning = GameState::GameStateRequests::CreateNewOverridableGameStateOfType<GameStateLevelRunning>();
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::ReplaceActiveGameState, gameStateLevelRunning);
}
}
}
@@ -0,0 +1,92 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while gameplay is paused.
class GameStateLevelPaused : public GameState::IGameState
, public AzFramework::InputChannelEventListener
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateLevelPaused, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateLevelPaused, "{6CAA4810-AA67-4A96-BB23-3EFA4BCCBF12}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateLevelPaused() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateLevelPaused() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load and unload the pause menu UI canvas.
///@{
virtual void LoadPauseMenuCanvas();
virtual void UnloadPauseMenuCanvas();
virtual void SetPauseMenuCanvasDrawOrder();
virtual const char* GetPauseMenuCanvasAssetPath();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to pause and unpause the game.
///@{
virtual void PauseGame();
virtual void UnpauseGame();
///@}
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::EntityId m_pauseMenuCanvasEntityId; //!< Id of the UI canvas being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateLevelPaused.inl>
@@ -0,0 +1,203 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateLevelPaused.h>
#include <GameStateSamples/GameStateMainMenu.h>
#include <GameStateSamples/GameStateOptionsMenu.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <LyShine/Bus/UiButtonBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiCursorBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::OnPushed()
{
LoadPauseMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::OnPopped()
{
UnloadPauseMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::OnEnter()
{
InputChannelEventListener::Connect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::OnExit()
{
InputChannelEventListener::Disconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStateLevelPaused::GetPriority() const
{
// Make unpausing the game precedence over any UI that might be showing
return AzFramework::InputChannelEventListener::GetPriorityUI() + 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStateLevelPaused::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateEnded() &&
(inputChannel.GetInputChannelId() == AzFramework::InputDeviceGamepad::Button::Start ||
inputChannel.GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::Escape))
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelPaused>(),
"The active game state is not an instance of GameStateLevelPaused");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
return true; // Consume this input
}
return false; // Don't consume other input
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::LoadPauseMenuCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetPauseMenuCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_pauseMenuCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_pauseMenuCanvasEntityId.IsValid())
{
AZ_Warning("GameStateLevelPaused", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the pause menu
UiCanvasBus::Event(m_pauseMenuCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
SetPauseMenuCanvasDrawOrder();
// Display the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::IncrementVisibleCounter);
// Setup the 'Resume' button to return to the level running state
AZ::EntityId resumeButtonElementId;
UiCanvasBus::EventResult(resumeButtonElementId,
m_pauseMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"ResumeButton");
UiButtonBus::Event(resumeButtonElementId,
&UiButtonInterface::SetOnClickCallback,
[]([[maybe_unused]] AZ::EntityId clickedEntityId, [[maybe_unused]] AZ::Vector2 point)
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelPaused>(),
"The active game state is not an instance of GameStateLevelPaused");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
});
// Setup the 'Options' button to open the options menu
AZ::EntityId optionsButtonElementId;
UiCanvasBus::EventResult(optionsButtonElementId,
m_pauseMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"OptionsButton");
UiButtonBus::Event(optionsButtonElementId,
&UiButtonInterface::SetOnClickCallback,
[]([[maybe_unused]] AZ::EntityId clickedEntityId, [[maybe_unused]] AZ::Vector2 point)
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelPaused>(),
"The active game state is not an instance of GameStateLevelPaused");
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateOptionsMenu>();
});
// Setup the 'Return to Main Menu' button to return to the main menu state
AZ::EntityId returnToMainMenuButtonElementId;
UiCanvasBus::EventResult(returnToMainMenuButtonElementId,
m_pauseMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"ReturnToMainMenuButton");
const bool enableReturnToMainMenuButton = GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateMainMenu>();
UiElementBus::Event(returnToMainMenuButtonElementId, &UiElementInterface::SetIsEnabled, enableReturnToMainMenuButton);
if (enableReturnToMainMenuButton)
{
UiButtonBus::Event(returnToMainMenuButtonElementId,
&UiButtonInterface::SetOnClickCallback,
[]([[maybe_unused]] AZ::EntityId clickedEntityId, [[maybe_unused]] AZ::Vector2 point)
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelPaused>(),
"The active game state is not an instance of GameStateLevelPaused");
GameState::GameStateRequests::PopActiveGameStateUntilOfType<GameStateMainMenu>();
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::UnloadPauseMenuCanvas()
{
if (m_pauseMenuCanvasEntityId.IsValid())
{
// Hide the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::DecrementVisibleCounter);
// Unload the pause menu
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_pauseMenuCanvasEntityId);
m_pauseMenuCanvasEntityId.SetInvalid();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::SetPauseMenuCanvasDrawOrder()
{
// Loaded canvases are already stored sorted by draw order...
UiCanvasManagerInterface::CanvasEntityList canvases;
UiCanvasManagerBus::BroadcastResult(canvases, &UiCanvasManagerInterface::GetLoadedCanvases);
// ...so get the draw order of the top-most displayed UI canvas...
int highestDrawOrder = 0;
UiCanvasBus::EventResult(highestDrawOrder, canvases.back(), &UiCanvasInterface::GetDrawOrder);
// ...and increment it by one unless it's already set to int max...
if (highestDrawOrder != std::numeric_limits<int>::max())
{
++highestDrawOrder;
}
// ...ensuring the pause menu gets displayed on top of all other loaded canvases,
// with the exception of 'special' ones like message popups or the loading screen
// that use a draw order of int max.
UiCanvasBus::Event(m_pauseMenuCanvasEntityId, &UiCanvasInterface::SetDrawOrder, highestDrawOrder);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStateLevelPaused::GetPauseMenuCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultpausemenuscreen.uicanvas";
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::PauseGame()
{
// We need a way to pause the game.
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelPaused::UnpauseGame()
{
// We need a way to un-pause the game.
}
}
@@ -0,0 +1,102 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
#include <ISystem.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while the game is running.
class GameStateLevelRunning : public GameState::IGameState
, public AzFramework::InputChannelEventListener
, public AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public ISystemEventListener
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateLevelRunning, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateLevelRunning, "{93501205-D39D-4E91-B93C-1E16EFAEEB43}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateLevelRunning() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateLevelRunning() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::ApplicationLifecycleEvents::OnApplicationConstrained
void OnApplicationConstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref ISystemEventListener::OnSystemEvent
void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default level paused game state.
//! Override if you wish to push a different level paused game state.
virtual void PushLevelPausedGameState();
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load and unload the pause button UI canvas.
///@{
virtual void LoadPauseButtonCanvas();
virtual void UnloadPauseButtonCanvas();
virtual const char* GetPauseButtonCanvasAssetPath();
///@}
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::EntityId m_pauseButtonCanvasEntityId; //!< Id of the UI canvas being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateLevelRunning.inl>
@@ -0,0 +1,220 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateLevelRunning.h>
#include <GameStateSamples/GameStateLevelLoading.h>
#include <GameStateSamples/GameStateLevelPaused.h>
#include <GameStateSamples/GameStateSamples_Traits_Platform.h>
#include <AzFramework/Input/Buses/Requests/InputTextEntryRequestBus.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
#include <AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h>
#include <LyShine/Bus/UiButtonBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <ILevelSystem.h>
#include <I3DEngine.h>
#include <ISystem.h>
#include <IConsole.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnPushed()
{
// Load the pause button if there's a touch input device connected
const AzFramework::InputDevice* inputDeviceTouch = nullptr;
AzFramework::InputDeviceRequestBus::EventResult(inputDeviceTouch,
AzFramework::InputDeviceTouch::Id,
&AzFramework::InputDeviceRequests::GetInputDevice);
if (inputDeviceTouch && inputDeviceTouch->IsConnected())
{
LoadPauseButtonCanvas();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnPopped()
{
UnloadPauseButtonCanvas();
ISystem* iSystem = GetISystem();
ILevelSystem* levelSystem = iSystem ? iSystem->GetILevelSystem() : nullptr;
if (levelSystem && !iSystem->GetGlobalEnvironment()->IsEditor())
{
// Unload the currently loaded level
levelSystem->UnLoadLevel();
if (iSystem->GetI3DEngine())
{
iSystem->GetI3DEngine()->LoadEmptyLevel();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnEnter()
{
InputChannelEventListener::Connect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
if (ISystem* iSystem = GetISystem())
{
iSystem->GetISystemEventDispatcher()->RegisterListener(this);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnExit()
{
if (ISystem* iSystem = GetISystem())
{
iSystem->GetISystemEventDispatcher()->RemoveListener(this);
}
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
InputChannelEventListener::Disconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStateLevelRunning::GetPriority() const
{
// Make pausing the game precedence over any UI that might be showing
return AzFramework::InputChannelEventListener::GetPriorityUI() + 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStateLevelRunning::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateEnded() &&
(inputChannel.GetInputChannelId() == AzFramework::InputDeviceGamepad::Button::Start ||
inputChannel.GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::Escape))
{
PushLevelPausedGameState();
return true; // Consume this input
}
return false; // Don't consume other input
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnApplicationConstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/)
{
bool pauseOnApplicationConstrained = false;
#if AZ_TRAIT_GAMESTATESAMPLES_PAUSE_ON_APPLICATION_CONSTRAINED
pauseOnApplicationConstrained = true;
#else
pauseOnApplicationConstrained = false;
#endif // AZ_TRAIT_GAMESTATESAMPLES_PAUSE_ON_APPLICATION_CONSTRAINED
ISystem* iSystem = GetISystem();
IConsole* iConsole = iSystem ? iSystem->GetIConsole() : nullptr;
if (iConsole && iConsole->GetCVar("sys_pauseOnApplicationConstrained"))
{
switch (iConsole->GetCVar("sys_pauseOnApplicationConstrained")->GetIVal())
{
case 0: { pauseOnApplicationConstrained = false; } break;
case 1: { pauseOnApplicationConstrained = true; } break;
default: break; // Use the default value that was set above
}
}
// Do not pause if the application was constrained because the virtual keyboard was shown
bool hasTextEntryStarted = false;
AzFramework::InputTextEntryRequestBus::EventResult(hasTextEntryStarted,
AzFramework::InputDeviceVirtualKeyboard::Id,
&AzFramework::InputTextEntryRequests::HasTextEntryStarted);
if (pauseOnApplicationConstrained && !hasTextEntryStarted)
{
PushLevelPausedGameState();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::OnSystemEvent(ESystemEvent event, UINT_PTR, UINT_PTR)
{
// If the user happens to initiate a level load outside the context of these game states,
// for example via executing the 'map' command from the debug console or in autoexec.cfg,
// this will also be detected by checking for the ESYSTEM_EVENT_LEVEL_LOAD_PREPARE event.
if (event == ESYSTEM_EVENT_LEVEL_LOAD_PREPARE)
{
// Replace the level running game state with the level loading game state
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelRunning>(),
"The active game state is not of type GameStateLevelRunning");
AZ_Assert(!GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelLoading>(),
"The game state stack already contains an instance of GameStateLevelLoading");
AZStd::shared_ptr<GameState::IGameState> gameStateLevelLoading = GameState::GameStateRequests::CreateNewOverridableGameStateOfType<GameStateLevelLoading>();
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::ReplaceActiveGameState, gameStateLevelLoading);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::PushLevelPausedGameState()
{
AZ_Assert(!GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelPaused>(),
"The game state stack already contains an instance of GameStateLevelPaused");
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateLevelPaused>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::LoadPauseButtonCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetPauseButtonCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_pauseButtonCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_pauseButtonCanvasEntityId.IsValid())
{
AZ_Warning("GameStateLevelRunning", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the pause HUD
UiCanvasBus::Event(m_pauseButtonCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
// Setup the 'Pause' button to push the level paused state
AZ::EntityId pauseButtonElementId;
UiCanvasBus::EventResult(pauseButtonElementId,
m_pauseButtonCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"PauseButton");
auto OnPauseButtonClicked = [this]([[maybe_unused]] AZ::EntityId clickedEntityId, [[maybe_unused]] AZ::Vector2 point)
{
PushLevelPausedGameState();
};
UiButtonBus::Event(pauseButtonElementId, &UiButtonInterface::SetOnClickCallback, OnPauseButtonClicked);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLevelRunning::UnloadPauseButtonCanvas()
{
if (m_pauseButtonCanvasEntityId.IsValid())
{
// Unload the pause menu
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_pauseButtonCanvasEntityId);
m_pauseButtonCanvasEntityId.SetInvalid();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStateLevelRunning::GetPauseButtonCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultpausebuttonfortouchscreens.uicanvas";
}
}
@@ -0,0 +1,138 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <LocalUser/LocalUserNotificationBus.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Input/Buses/Notifications/InputDeviceNotificationBus.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that acts a lobby by assigning local user ids into local player slots as needed.
class GameStateLocalUserLobby : public GameState::IGameState
, public AzFramework::InputChannelEventListener
, public AzFramework::InputDeviceNotificationBus::Handler
, public AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public LocalUser::LocalUserNotificationBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateLocalUserLobby, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateLocalUserLobby, "{E6D54EAF-F826-4EEE-91CD-60A052DA55E4}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateLocalUserLobby() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateLocalUserLobby() override = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnUpdate
void OnUpdate() override;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceNotifications::OnInputDeviceConnectedEvent
void OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceNotifications::OnInputDeviceDisconnectedEvent
void OnInputDeviceDisconnectedEvent(const AzFramework::InputDevice& inputDevice) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::ApplicationLifecycleEvents::OnApplicationUnconstrained
void OnApplicationUnconstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref LocalUser::LocalUserNotifications::OnLocalUserIdAssignedToLocalPlayerSlot
void OnLocalUserIdAssignedToLocalPlayerSlot(AzFramework::LocalUserId localUserId,
AZ::u32 newLocalPlayerSlot,
AZ::u32 previousLocalPlayerSlot) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref LocalUser::LocalUserNotifications::OnLocalUserIdRemovedFromLocalPlayerSlot
void OnLocalUserIdRemovedFromLocalPlayerSlot(AzFramework::LocalUserId localUserId,
AZ::u32 localPlayerSlot) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load and unload the signed in user overlay UI canvas.
///@{
virtual void LoadSignedInUserOverlayCanvas();
virtual void UnloadSignedInUserOverlayCanvas();
virtual const char* GetSignedInUserOverlayCanvasAssetPath();
///@}
protected:
void RefreshLocalPlayerSlotAssignments();
bool IsLocalUserSignedIn(AzFramework::LocalUserId localUserId) const;
bool IsLocalUserAssociatedWithConnectedInputDevice(AzFramework::LocalUserId localUserId) const;
void RefreshAllGamepadLightBarColors();
void RefreshGamepadLightBarColor(AzFramework::LocalUserId localUserId, AZ::u32 localPlayerSlot);
void RefreshGamepadLightBarColor(const AzFramework::InputDeviceId& inputDeviceId, AZ::u32 localPlayerSlot);
void RefreshAllSignedInUserOverlays();
void RefreshSignedInUserOverlay(AZ::u32 localPlayerSlot);
void SetSignedInUserOverlayEnabled(AZ::u32 localPlayerSlot,
bool enabled);
void SetSignedInUserOverlayNameText(AZ::u32 localPlayerSlot,
const AZStd::string& localUserName);
AZ::EntityId GetUiElementIdForLocalPlayerSlot(const char* elementName,
AZ::u32 localPlayerSlot) const;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::EntityId m_signedInUsersOverlayCanvasEntityId;
bool m_shouldRefreshLevelListDisplay = false;
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateLocalUserLobby.inl>
@@ -0,0 +1,519 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateLocalUserLobby.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiTextBus.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h>
#include <AzFramework/Input/Buses/Requests/InputLightBarRequestBus.h>
#include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
#include <AzFramework/Input/Utils/IsAnyKeyOrButton.h>
#include <AzCore/std/sort.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnPushed()
{
// We could load the UI canvas here and keep it cached until OnPopped is called in order to
// speed up re-entering this game state, but doing so would consume memory for the lifetime
// of the process that is only needed while this state is active (which is not very often).
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnPopped()
{
// See comment above in OnPushed
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnEnter()
{
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
AzFramework::InputDeviceNotificationBus::Handler::BusConnect();
LocalUser::LocalUserNotificationBus::Handler::BusConnect();
InputChannelEventListener::Connect();
RefreshLocalPlayerSlotAssignments();
LoadSignedInUserOverlayCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnExit()
{
UnloadSignedInUserOverlayCanvas();
InputChannelEventListener::Disconnect();
LocalUser::LocalUserNotificationBus::Handler::BusDisconnect();
AzFramework::InputDeviceNotificationBus::Handler::BusDisconnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnUpdate()
{
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStateLocalUserLobby::GetPriority() const
{
return AzFramework::InputChannelEventListener::GetPriorityFirst();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStateLocalUserLobby::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateBegan() && AzFramework::IsAnyKeyOrButton(inputChannel))
{
const AzFramework::LocalUserId localUserId = inputChannel.GetInputDevice().GetAssignedLocalUserId();
if (localUserId == AzFramework::LocalUserIdAny ||
localUserId == AzFramework::LocalUserIdNone)
{
// No local user is associated with this input device yet, so prompt for user sign-in
inputChannel.GetInputDevice().PromptLocalUserSignIn();
}
else
{
// Assign the local user to the first available slot (if they haven't already been assigned one)
AZ::u32 assignedSlot = LocalUser::LocalPlayerSlotNone;
LocalUser::LocalUserRequestBus::BroadcastResult(assignedSlot,
&LocalUser::LocalUserRequests::GetLocalPlayerSlotOccupiedByLocalUserId,
localUserId);
if (assignedSlot == LocalUser::LocalPlayerSlotNone)
{
// This call to AssignLocalUserIdToLocalPlayerSlot will trigger another to
// OnLocalUserIdAssignedToLocalPlayerSlot, which is where we update the UI.
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::AssignLocalUserIdToLocalPlayerSlot,
localUserId,
LocalUser::LocalPlayerSlotAny);
}
}
}
// Don't consume the input
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice)
{
const AzFramework::LocalUserId localUserId = inputDevice.GetAssignedLocalUserId();
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (localUserId == AzFramework::LocalUserIdNone ||
localUserId == primaryLocalUserId)
{
// The connected controller does not belong to any user, or it belongs to the primary
// user which is handled in GameStatePrimaryControllerDisconnected
return;
}
// A secondary controller was connected, so assign the associated local
// user id to a free local player slot. Note that we only do this while
// in GameStateLocalUserLobby, not during gameplay, as we do not want to
// assign a local user id to a local player slot mid-game. To account
// for the case where a controller/user connects during gameplay (and
// does not disconnect by the time we return to the main menu) we call
// RefreshLocalPlayerSlotAssignments from GameStateLocalUserLobby::OnEnter.
//
// This call to AssignLocalUserIdToLocalPlayerSlot will trigger another to
// OnLocalUserIdAssignedToLocalPlayerSlot, which is where we update the UI.
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::AssignLocalUserIdToLocalPlayerSlot,
localUserId,
LocalUser::LocalPlayerSlotAny);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnInputDeviceDisconnectedEvent(const AzFramework::InputDevice& inputDevice)
{
const AzFramework::LocalUserId localUserId = inputDevice.GetAssignedLocalUserId();
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (localUserId == AzFramework::LocalUserIdNone ||
localUserId == primaryLocalUserId)
{
// The disconnected controller does not belong to any user, or it belongs to the primary
// user which is handled in GameStatePrimaryUserMonitor::OnInputDeviceDisconnectedEvent
return;
}
// A secondary controller was disconnected, so remove the associated local
// user id from their local player slot. Note that we only do this while in
// GameStateLocalUserLobby, not during gameplay, as we don't want to remove a
// local user id from a local player slot mid-game. To account for the case
// where a user disconnects during gameplay (and does not re-connect by the
// time we exit to the main menu) we call RefreshLocalPlayerSlotAssignments
// from GameStateLocalUserLobby::OnEnter
//
// This call to RemoveLocalUserIdFromLocalPlayerSlot will trigger another to
// OnLocalUserIdRemovedFromLocalPlayerSlot, which is where we update the UI.
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::RemoveLocalUserIdFromLocalPlayerSlot,
localUserId);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnApplicationUnconstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/)
{
RefreshLocalPlayerSlotAssignments();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnLocalUserIdAssignedToLocalPlayerSlot(AzFramework::LocalUserId localUserId,
AZ::u32 newLocalPlayerSlot,
AZ::u32 previousLocalPlayerSlot)
{
RefreshSignedInUserOverlay(newLocalPlayerSlot);
RefreshSignedInUserOverlay(previousLocalPlayerSlot);
RefreshGamepadLightBarColor(localUserId, newLocalPlayerSlot);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::OnLocalUserIdRemovedFromLocalPlayerSlot(AzFramework::LocalUserId localUserId,
AZ::u32 localPlayerSlot)
{
RefreshSignedInUserOverlay(localPlayerSlot);
RefreshGamepadLightBarColor(localUserId, LocalUser::LocalPlayerSlotNone);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::LoadSignedInUserOverlayCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetSignedInUserOverlayCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_signedInUsersOverlayCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_signedInUsersOverlayCanvasEntityId.IsValid())
{
AZ_Warning("GameStateLocalUserLobby", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the overlay, set it to draw over the top of the main menu,
// and set it to stay loaded when a level unloads
UiCanvasBus::Event(m_signedInUsersOverlayCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
UiCanvasBus::Event(m_signedInUsersOverlayCanvasEntityId, &UiCanvasInterface::SetDrawOrder, 10);
UiCanvasBus::Event(m_signedInUsersOverlayCanvasEntityId, &UiCanvasInterface::SetKeepLoadedOnLevelUnload, true);
RefreshAllSignedInUserOverlays();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::UnloadSignedInUserOverlayCanvas()
{
if (m_signedInUsersOverlayCanvasEntityId.IsValid())
{
// Unload the overlay
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_signedInUsersOverlayCanvasEntityId);
m_signedInUsersOverlayCanvasEntityId.SetInvalid();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStateLocalUserLobby::GetSignedInUserOverlayCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultsignedinusersoverlay.uicanvas";
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshLocalPlayerSlotAssignments()
{
// Check whether local users that have been assigned to a local player slot
// are still signed in and associated with a connected input device. If not,
// remove them from the local player slot so that it becomes free for other
// players to join. Note that we ignore the primary user here, as that is a
// speacial case we handle at all times in LyPlatformServicesSystemComponent
for (AZ::u32 i = LocalUser::LocalPlayerSlotPrimary + 1; i < LocalUser::LocalPlayerSlotMax; ++i)
{
const AzFramework::LocalUserId localUserId = LocalUser::LocalUserRequests::GetLocalUserIdAt(i);
if (localUserId == AzFramework::LocalUserIdNone)
{
// No local user assigned to this slot
continue;
}
const bool isLocalUserSignedIn = IsLocalUserSignedIn(localUserId);
if (!isLocalUserSignedIn)
{
// The local user is no longer signed in, remove them from their local player slot
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::RemoveLocalUserIdFromLocalPlayerSlot,
localUserId);
continue;
}
const bool isAssignedToConnectedInputDevice = IsLocalUserAssociatedWithConnectedInputDevice(localUserId);
if (!isAssignedToConnectedInputDevice)
{
// The local user is no longer associated with a connected input device, remove them from their local player slot
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::RemoveLocalUserIdFromLocalPlayerSlot,
localUserId);
continue;
}
// The local user is still signed in and associated with a connected input device
}
// Get all connected controllers...
AZStd::vector<const AzFramework::InputDevice*> gamepadInputDevices;
AzFramework::InputDeviceRequests::InputDeviceByIdMap inputDevicesById;
AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDevicesById,
inputDevicesById);
for (const auto& inputDeviceById : inputDevicesById)
{
const AzFramework::InputDevice* inputDevice = inputDeviceById.second;
if (inputDevice &&
inputDevice->IsConnected() &&
AzFramework::InputDeviceGamepad::IsGamepadDevice(inputDeviceById.first))
{
// The input device is a connected gamepad
gamepadInputDevices.push_back(inputDevice);
}
}
// ...sort them by index and then go through to check whether they have been
// assigned a local user id. If so, auto-assign their local user id into the
// first available local player slot (unless they've already been assigned).
AZStd::sort(gamepadInputDevices.begin(), gamepadInputDevices.end(),
[](const AzFramework::InputDevice* lhs, const AzFramework::InputDevice* rhs)
{
return lhs->GetInputDeviceId() < rhs->GetInputDeviceId();
});
for (const AzFramework::InputDevice* gamepadDevice : gamepadInputDevices)
{
const AzFramework::LocalUserId localUserId = gamepadDevice->GetAssignedLocalUserId();
if (localUserId == AzFramework::LocalUserIdAny ||
localUserId == AzFramework::LocalUserIdNone ||
!IsLocalUserSignedIn(localUserId))
{
// The input device has no associated local user id,
// or is associated with a user that's not signed in.
continue;
}
// Assign the local user id to a local player slot.
// If it is already assigned this will do nothing.
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::AssignLocalUserIdToLocalPlayerSlot,
localUserId,
LocalUser::LocalPlayerSlotAny);
}
// Lastly, iterate over all the local player slots again and 'collapse' them
// so that we aren't left with any gaps. For example, if after all the above
// we are left with user A in local player slot 0 and user B in local player
// slot 3, the following will result in user B being moved down into slot 1.
// Note again that we ignore the primary user here, which is a speacial case
// handled in LyPlatformServicesSystemComponent so that it'll never be empty.
for (AZ::u32 i = LocalUser::LocalPlayerSlotPrimary + 1; i < LocalUser::LocalPlayerSlotMax; ++i)
{
AzFramework::LocalUserId localUserId = LocalUser::LocalUserRequests::GetLocalUserIdAt(i);
if (localUserId == AzFramework::LocalUserIdNone)
{
// No local user assigned to this slot, look for the next occupied slot...
AZ::u32 j = i + 1;
for (; j < LocalUser::LocalPlayerSlotMax; ++j)
{
localUserId = LocalUser::LocalUserRequests::GetLocalUserIdAt(j);
if (localUserId != AzFramework::LocalUserIdNone)
{
// ...and move that local user down into the unnocupied slot.
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::AssignLocalUserIdToLocalPlayerSlot,
localUserId,
i);
break;
}
}
if (j == LocalUser::LocalPlayerSlotMax)
{
// There are no more occupied slots, no need to continue.
break;
}
}
}
// After all this, refresh the gamepad light bar colors (if they exist)
RefreshAllGamepadLightBarColors();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStateLocalUserLobby::IsLocalUserSignedIn(AzFramework::LocalUserId localUserId) const
{
bool isLocalUserSignedIn = false;
LocalUser::LocalUserRequestBus::BroadcastResult(isLocalUserSignedIn,
&LocalUser::LocalUserRequests::IsLocalUserSignedIn,
localUserId);
return isLocalUserSignedIn;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStateLocalUserLobby::IsLocalUserAssociatedWithConnectedInputDevice(AzFramework::LocalUserId localUserId) const
{
AzFramework::InputDeviceRequests::InputDeviceByIdMap inputDevicesById;
AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDevicesByIdWithAssignedLocalUserId,
inputDevicesById,
localUserId);
for (const auto& inputDeviceById : inputDevicesById)
{
if (inputDeviceById.second && inputDeviceById.second->IsConnected())
{
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshAllGamepadLightBarColors()
{
AzFramework::InputDeviceRequests::InputDeviceByIdMap inputDevicesById;
AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDevicesById,
inputDevicesById);
for (const auto& inputDeviceById : inputDevicesById)
{
const AzFramework::InputDevice* inputDevice = inputDeviceById.second;
if (inputDevice && AzFramework::InputDeviceGamepad::IsGamepadDevice(inputDeviceById.first))
{
AZ::u32 localPlayerSlot = LocalUser::LocalPlayerSlotNone;
LocalUser::LocalUserRequestBus::BroadcastResult(localPlayerSlot,
&LocalUser::LocalUserRequests::GetLocalPlayerSlotOccupiedByLocalUserId,
inputDevice->GetAssignedLocalUserId());
RefreshGamepadLightBarColor(inputDeviceById.first, localPlayerSlot);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshGamepadLightBarColor(AzFramework::LocalUserId localUserId,
AZ::u32 localPlayerSlot)
{
AzFramework::InputDeviceRequests::InputDeviceByIdMap inputDevicesById;
AzFramework::InputDeviceRequestBus::Broadcast(&AzFramework::InputDeviceRequests::GetInputDevicesByIdWithAssignedLocalUserId,
inputDevicesById,
localUserId);
for (const auto& inputDeviceById : inputDevicesById)
{
if (inputDeviceById.second && AzFramework::InputDeviceGamepad::IsGamepadDevice(inputDeviceById.first))
{
RefreshGamepadLightBarColor(inputDeviceById.first, localPlayerSlot);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshGamepadLightBarColor(const AzFramework::InputDeviceId& inputDeviceId,
AZ::u32 localPlayerSlot)
{
// Colors with a low saturation (< 75%) tend to get washed out and look mostly white.
static const AZ::Color s_gamepadLightBarColorsByLocalPlayerSlot[LocalUser::LocalPlayerSlotMax]
{
AZ::Color((AZ::u8)0, (AZ::u8)0, (AZ::u8)255, (AZ::u8)255), // Blue
AZ::Color((AZ::u8)255, (AZ::u8)0, (AZ::u8)0, (AZ::u8)255), // Red
AZ::Color((AZ::u8)0, (AZ::u8)255, (AZ::u8)0, (AZ::u8)255), // Green
#if defined(AZ_PLATFORM_PROVO)
AZ::Color((AZ::u8)255, (AZ::u8)0, (AZ::u8)127, (AZ::u8)255)// Pink
#else
AZ::Color((AZ::u8)127, (AZ::u8)255, (AZ::u8)0, (AZ::u8)255)// Yellow
#endif
};
static const AZ::Color s_gamepadLightBarColorNoLocalPlayerSlot = AZ::Color::CreateOne(); // White
const AZ::Color& lightBarColor = localPlayerSlot < LocalUser::LocalPlayerSlotMax ?
s_gamepadLightBarColorsByLocalPlayerSlot[localPlayerSlot] :
s_gamepadLightBarColorNoLocalPlayerSlot;
AzFramework::InputLightBarRequestBus::Event(inputDeviceId,
&AzFramework::InputLightBarRequests::SetLightBarColor,
lightBarColor);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshAllSignedInUserOverlays()
{
for (AZ::u32 localPlayerSlot = 0; localPlayerSlot < LocalUser::LocalPlayerSlotMax; ++localPlayerSlot)
{
RefreshSignedInUserOverlay(localPlayerSlot);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::RefreshSignedInUserOverlay(AZ::u32 localPlayerSlot)
{
if (!m_signedInUsersOverlayCanvasEntityId.IsValid())
{
return;
}
const AzFramework::LocalUserId localUserId = LocalUser::LocalUserRequests::GetLocalUserIdAt(localPlayerSlot);
if (localUserId == AzFramework::LocalUserIdNone)
{
SetSignedInUserOverlayEnabled(localPlayerSlot, false);
return;
}
const bool isLocalUserSignedIn = IsLocalUserSignedIn(localUserId);
if (!isLocalUserSignedIn)
{
SetSignedInUserOverlayEnabled(localPlayerSlot, false);
return;
}
AZStd::string localUserName;
LocalUser::LocalUserRequestBus::BroadcastResult(localUserName,
&LocalUser::LocalUserRequests::GetLocalUserName,
localUserId);
SetSignedInUserOverlayEnabled(localPlayerSlot, true);
SetSignedInUserOverlayNameText(localPlayerSlot, localUserName);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::SetSignedInUserOverlayEnabled(AZ::u32 localPlayerSlot,
bool enabled)
{
AZ::EntityId userElementName = GetUiElementIdForLocalPlayerSlot("User", localPlayerSlot);
if (userElementName.IsValid())
{
UiElementBus::Event(userElementName, &UiElementInterface::SetIsEnabled, enabled);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateLocalUserLobby::SetSignedInUserOverlayNameText(AZ::u32 localPlayerSlot,
const AZStd::string& localUserName)
{
AZ::EntityId userNameTextElementName = GetUiElementIdForLocalPlayerSlot("UserName", localPlayerSlot);
if (userNameTextElementName.IsValid())
{
UiTextBus::Event(userNameTextElementName, &UiTextInterface::SetText, localUserName);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::EntityId GameStateLocalUserLobby::GetUiElementIdForLocalPlayerSlot(const char* elementName,
AZ::u32 localPlayerSlot) const
{
char elementNameForSlot[16];
azsnprintf(elementNameForSlot, 16, "%s%d", elementName, localPlayerSlot);
AZ::EntityId elementId;
UiCanvasBus::EventResult(elementId,
m_signedInUsersOverlayCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
elementNameForSlot);
return elementId;
}
}
@@ -0,0 +1,97 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <GameStateSamples/GameStateLocalUserLobby.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <ISystem.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while displaying the main game menu (or another front-end menu).
class GameStateMainMenu : public GameState::IGameState
, public ISystemEventListener
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateMainMenu, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateMainMenu, "{53EB59EC-77F1-4C8E-AC5F-B2A94F15AF31}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateMainMenu() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateMainMenu() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnUpdate
void OnUpdate() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref ISystemEventListener::OnSystemEvent
void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load and unload the main game menu UI canvas.
///@{
virtual void LoadMainMenuCanvas();
virtual void UnloadMainMenuCanvas();
virtual const char* GetMainMenuCanvasAssetPath();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Refresh the list of levels displayed in the main menu.
virtual void RefreshLevelListDisplay();
////////////////////////////////////////////////////////////////////////////////////////////
//! Load options from persistent storage, which is done upon first entry into the main menu.
virtual void LoadGameOptionsFromPersistentStorage();
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::unique_ptr<GameStateLocalUserLobby> m_localUserLobbySubState;
AZ::EntityId m_mainMenuCanvasEntityId;
bool m_shouldRefreshLevelListDisplay = false;
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateMainMenu.inl>
@@ -0,0 +1,333 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateMainMenu.h>
#include <GameStateSamples/GameStateOptionsMenu.h>
#include <GameStateSamples/GameStateLevelLoading.h>
#include <GameStateSamples/GameStatePrimaryUserSelection.h>
#include <GameStateSamples/GameStateSamples_Traits_Platform.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <LyShine/Bus/UiButtonBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiCursorBus.h>
#include <LyShine/Bus/UiDynamicLayoutBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <SaveData/SaveDataRequestBus.h>
#include <ILevelSystem.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnPushed()
{
// We could load the UI canvas here and keep it cached until OnPopped is called in order to
// speed up re-entering this game state, but doing so would consume memory for the lifetime
// of the process that is only needed while this state is active (which is not very often).
bool createLocalUserLobbySubState = false;
#if AZ_TRAIT_GAMESTATESAMPLES_LOCAL_USER_LOBBY_ENABLED
createLocalUserLobbySubState = true;
#else
createLocalUserLobbySubState = false;
#endif // AZ_TRAIT_GAMESTATESAMPLES_LOCAL_USER_LOBBY_ENABLED
ISystem* iSystem = GetISystem();
IConsole* iConsole = iSystem ? iSystem->GetIConsole() : nullptr;
if (iConsole && iConsole->GetCVar("sys_localUserLobbyEnabled"))
{
switch (iConsole->GetCVar("sys_localUserLobbyEnabled")->GetIVal())
{
case 0: { createLocalUserLobbySubState = false; } break;
case 1: { createLocalUserLobbySubState = true; } break;
default: break; // Use the default value that was set above
}
}
if (createLocalUserLobbySubState)
{
m_localUserLobbySubState.reset(aznew GameStateLocalUserLobby());
m_localUserLobbySubState->OnPushed();
}
LoadGameOptionsFromPersistentStorage();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnPopped()
{
// See comment above in OnPushed
if (m_localUserLobbySubState)
{
m_localUserLobbySubState->OnPopped();
m_localUserLobbySubState.reset();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnEnter()
{
LoadMainMenuCanvas();
if (m_localUserLobbySubState)
{
m_localUserLobbySubState->OnEnter();
}
if (ISystem* iSystem = GetISystem())
{
iSystem->GetISystemEventDispatcher()->RegisterListener(this);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnExit()
{
if (ISystem* iSystem = GetISystem())
{
iSystem->GetISystemEventDispatcher()->RemoveListener(this);
}
if (m_localUserLobbySubState)
{
m_localUserLobbySubState->OnExit();
}
UnloadMainMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnUpdate()
{
// This should be called directly from LoadMainMenuCanvas (or right after it from OnEnter),
// but at that point in our convoluted startup sequence the level system doesn't exist yet.
if (m_shouldRefreshLevelListDisplay)
{
m_shouldRefreshLevelListDisplay = false;
RefreshLevelListDisplay();
}
if (m_localUserLobbySubState)
{
m_localUserLobbySubState->OnUpdate();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void OnLevelButtonClicked(AZ::EntityId entityId, AZ::Vector2)
{
// Load the selected level
AZStd::string levelName;
UiButtonBus::EventResult(levelName, entityId, &UiButtonInterface::GetOnClickActionName);
ISystem* iSystem = GetISystem();
IConsole* iConsole = iSystem ? iSystem->GetIConsole() : nullptr;
ILevelSystem* levelSystem = iSystem ? iSystem->GetILevelSystem() : nullptr;
if (levelSystem && !levelName.empty() && iConsole)
{
// This command gets delayed by one frame, so we check for the
// actual level load start in GameStateMainMenu::OnSystemEvent
AZStd::string mapCommand = "map " + levelName;
iConsole->ExecuteString(mapCommand.c_str());
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void OnOptionsButtonClicked([[maybe_unused]] AZ::EntityId entityId, AZ::Vector2)
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateMainMenu>(),
"The active game state is not an instance of GameStateMainMenu");
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateOptionsMenu>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void OnBackButtonClicked([[maybe_unused]] AZ::EntityId entityId, AZ::Vector2)
{
AZ_Assert(GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryUserSelection>(),
"The game state stack doesn't contain an instance of GameStatePrimaryUserSelection");
GameState::GameStateRequests::PopActiveGameStateUntilOfType<GameStatePrimaryUserSelection>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::OnSystemEvent(ESystemEvent event, UINT_PTR, UINT_PTR)
{
// If the user happens to initiate a level load outside the context of these game states,
// for example via executing the 'map' command from the debug console or in autoexec.cfg,
// this will also be detected by checking for the ESYSTEM_EVENT_LEVEL_LOAD_PREPARE event.
if (event == ESYSTEM_EVENT_LEVEL_LOAD_PREPARE)
{
// Push the level loading game state
AZ_Assert(!GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelLoading>(),
"The game state stack already contains an instance of GameStateLevelLoading");
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateLevelLoading>();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::LoadMainMenuCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetMainMenuCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_mainMenuCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_mainMenuCanvasEntityId.IsValid())
{
AZ_Warning("GameStateMainMenu", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the main menu and set it to stay loaded when a level unloads
UiCanvasBus::Event(m_mainMenuCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
UiCanvasBus::Event(m_mainMenuCanvasEntityId, &UiCanvasInterface::SetKeepLoadedOnLevelUnload, true);
// Display the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::IncrementVisibleCounter);
// Setup the 'Options' button to open the options menu
AZ::EntityId optionsButtonElementId;
UiCanvasBus::EventResult(optionsButtonElementId,
m_mainMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"OptionsButton");
UiButtonBus::Event(optionsButtonElementId, &UiButtonInterface::SetOnClickCallback, OnOptionsButtonClicked);
// Setup the 'Back' button to return to the primary user selection screen
AZ::EntityId backButtonElementId;
UiCanvasBus::EventResult(backButtonElementId,
m_mainMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"BackButton");
const bool enableBackButton = GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryUserSelection>();
UiElementBus::Event(backButtonElementId, &UiElementInterface::SetIsEnabled, enableBackButton);
if (enableBackButton)
{
UiButtonBus::Event(backButtonElementId, &UiButtonInterface::SetOnClickCallback, OnBackButtonClicked);
}
else
{
// Use the wide version of the 'Options' button.
UiElementBus::Event(optionsButtonElementId, &UiElementInterface::SetIsEnabled, false);
UiCanvasBus::EventResult(optionsButtonElementId,
m_mainMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"OptionsButtonWide");
UiElementBus::Event(optionsButtonElementId, &UiElementInterface::SetIsEnabled, true);
UiButtonBus::Event(optionsButtonElementId, &UiButtonInterface::SetOnClickCallback, OnOptionsButtonClicked);
}
// RefreshLevelListDisplay() should be called directly here (or right after it from OnEnter),
// but at this point in our messed up startup sequence the level system doesn't exist yet.
m_shouldRefreshLevelListDisplay = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::UnloadMainMenuCanvas()
{
m_shouldRefreshLevelListDisplay = false;
if (m_mainMenuCanvasEntityId.IsValid())
{
// Hide the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::DecrementVisibleCounter);
// Unload the main menu
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_mainMenuCanvasEntityId);
m_mainMenuCanvasEntityId.SetInvalid();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStateMainMenu::GetMainMenuCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultmainmenuscreen.uicanvas";
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::RefreshLevelListDisplay()
{
// Get the dynamic layout UI element
AZ::EntityId dynamicLayoutElementId;
UiCanvasBus::EventResult(dynamicLayoutElementId,
m_mainMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"DynamicColumn");
if (dynamicLayoutElementId.IsValid())
{
// Refresh the dynamic layout UI element with the list of levels in the project
ISystem* iSystem = GetISystem();
ILevelSystem* levelSystem = iSystem ? iSystem->GetILevelSystem() : nullptr;
const int numLevels = levelSystem ? levelSystem->GetLevelCount() : 0;
UiDynamicLayoutBus::Event(dynamicLayoutElementId, &UiDynamicLayoutInterface::SetNumChildElements, numLevels);
for (int i = 0; i < numLevels; ++i)
{
// Get the level name (strip folder names from the path)
const char* levelPath = levelSystem->GetLevelInfo(i)->GetName();
const int levelPathLength = strlen(levelPath);
const char* levelName = levelPath;
for (int j = 0; j < levelPathLength; ++j)
{
if ((levelPath[j] == '\\' || levelPath[j] == '/') && j + 1 < levelPathLength)
{
levelName = levelPath + j + 1;
}
}
// Get the button element id
AZ::EntityId buttonElementId;
UiElementBus::EventResult(buttonElementId,
dynamicLayoutElementId,
&UiElementInterface::GetChildEntityId,
i);
// Get the text element id
AZ::EntityId textElementId;
UiElementBus::EventResult(textElementId,
buttonElementId,
&UiElementInterface::FindChildEntityIdByName,
"Text");
// Set the name, on-click callback, and on-click action name for each button
UiTextBus::Event(textElementId, &UiTextInterface::SetText, levelName);
UiButtonBus::Event(buttonElementId, &UiButtonInterface::SetOnClickCallback, OnLevelButtonClicked);
UiButtonBus::Event(buttonElementId, &UiButtonInterface::SetOnClickActionName, levelName);
if (i == 0)
{
// Force the first level to be selected
UiCanvasBus::Event(m_mainMenuCanvasEntityId, &UiCanvasInterface::ForceHoverInteractable, buttonElementId);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateMainMenu::LoadGameOptionsFromPersistentStorage()
{
SaveData::SaveDataRequests::SaveOrLoadObjectParams<GameOptions> loadObjectParams;
GameOptionRequestBus::BroadcastResult(loadObjectParams.serializableObject,
&GameOptionRequests::GetGameOptions);
loadObjectParams.dataBufferName = GameOptions::SaveDataBufferName;
loadObjectParams.localUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
loadObjectParams.callback = [](const SaveData::SaveDataRequests::SaveOrLoadObjectParams<GameOptions>& params,
[[maybe_unused]] SaveData::SaveDataNotifications::Result result)
{
params.serializableObject->OnLoadedFromPersistentData();
};
SaveData::SaveDataRequests::LoadObject(loadObjectParams);
}
}
@@ -0,0 +1,82 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <GameStateSamples/GameOptionRequestBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while displaying the game's options menu.
class GameStateOptionsMenu : public GameState::IGameState
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStateOptionsMenu, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStateOptionsMenu, "{2441BA71-8AD2-47A1-92BB-478ED74ACE63}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStateOptionsMenu() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStateOptionsMenu() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load, unload, and refresh the options menu UI canvas.
///@{
virtual void LoadOptionsMenuCanvas();
virtual void UnloadOptionsMenuCanvas();
virtual void RefreshOptionsMenuCanvas();
virtual void SetOptionsMenuCanvasDrawOrder();
virtual const char* GetOptionsMenuCanvasAssetPath();
///@}
////////////////////////////////////////////////////////////////////////////////////////////
//! Save game options to persistent storage, which is done upon exiting the options menu.
virtual void SaveGameOptionsToPersistentStorage();
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZStd::shared_ptr<GameOptions> m_gameOptions; //!< The game options object
AZ::EntityId m_optionsMenuCanvasEntityId; //!< Id of the UI canvas being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStateOptionsMenu.inl>
@@ -0,0 +1,234 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateOptionsMenu.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <SaveData/SaveDataRequestBus.h>
#include <LyShine/Bus/UiButtonBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LyShine/Bus/UiCursorBus.h>
#include <LyShine/Bus/UiSliderBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::OnPushed()
{
LoadOptionsMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::OnPopped()
{
UnloadOptionsMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::OnEnter()
{
GameOptionRequestBus::BroadcastResult(m_gameOptions, &GameOptionRequests::GetGameOptions);
RefreshOptionsMenuCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::OnExit()
{
SaveGameOptionsToPersistentStorage();
m_gameOptions.reset();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::LoadOptionsMenuCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetOptionsMenuCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_optionsMenuCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_optionsMenuCanvasEntityId.IsValid())
{
AZ_Warning("GameStateOptionsMenu", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the options menu
UiCanvasBus::Event(m_optionsMenuCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
SetOptionsMenuCanvasDrawOrder();
// Display the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::IncrementVisibleCounter);
// Setup the 'Back' button to return to the previous menu (either the main menu or the pause menu)
AZ::EntityId backButtonElementId;
UiCanvasBus::EventResult(backButtonElementId,
m_optionsMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"BackButton");
UiButtonBus::Event(backButtonElementId,
&UiButtonInterface::SetOnClickCallback,
[]([[maybe_unused]] AZ::EntityId clickedEntityId, [[maybe_unused]] AZ::Vector2 point)
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStateOptionsMenu>(),
"The active game state is not an instance of GameStateOptionsMenu");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
});
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::UnloadOptionsMenuCanvas()
{
if (m_optionsMenuCanvasEntityId.IsValid())
{
// Hide the UI cursor
UiCursorBus::Broadcast(&UiCursorInterface::DecrementVisibleCounter);
// Unload the pause menu
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_optionsMenuCanvasEntityId);
m_optionsMenuCanvasEntityId.SetInvalid();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::RefreshOptionsMenuCanvas()
{
if (!m_gameOptions)
{
return;
}
// Refresh the ambient volume slider
AZ::EntityId ambientVolumeSliderElementId;
UiCanvasBus::EventResult(ambientVolumeSliderElementId,
m_optionsMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"AmbientVolumeSlider");
UiSliderBus::Event(ambientVolumeSliderElementId,
&UiSliderInterface::SetValue,
m_gameOptions->GetAmbientVolume());
auto setAmbientVolume = [gameOptions = m_gameOptions]([[maybe_unused]] AZ::EntityId entityId, float value)
{
gameOptions->SetAmbientVolume(value);
};
UiSliderBus::Event(ambientVolumeSliderElementId,
&UiSliderInterface::SetValueChangingCallback,
setAmbientVolume);
UiSliderBus::Event(ambientVolumeSliderElementId,
&UiSliderInterface::SetValueChangedCallback,
setAmbientVolume);
// Refresh the effects volume slider
AZ::EntityId effectsVolumeSliderElementId;
UiCanvasBus::EventResult(effectsVolumeSliderElementId,
m_optionsMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"EffectsVolumeSlider");
UiSliderBus::Event(effectsVolumeSliderElementId,
&UiSliderInterface::SetValue,
m_gameOptions->GetEffectsVolume());
auto setEffectsVolume = [gameOptions = m_gameOptions]([[maybe_unused]] AZ::EntityId entityId, float value)
{
gameOptions->SetEffectsVolume(value);
};
UiSliderBus::Event(effectsVolumeSliderElementId,
&UiSliderInterface::SetValueChangingCallback,
setEffectsVolume);
UiSliderBus::Event(effectsVolumeSliderElementId,
&UiSliderInterface::SetValueChangedCallback,
setEffectsVolume);
// Refresh the main volume slider
AZ::EntityId mainVolumeSliderElementId;
UiCanvasBus::EventResult(mainVolumeSliderElementId,
m_optionsMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"MainVolumeSlider");
UiSliderBus::Event(mainVolumeSliderElementId,
&UiSliderInterface::SetValue,
m_gameOptions->GetMainVolume());
auto setMainVolume = [gameOptions = m_gameOptions]([[maybe_unused]] AZ::EntityId entityId, float value)
{
gameOptions->SetMainVolume(value);
};
UiSliderBus::Event(mainVolumeSliderElementId,
&UiSliderInterface::SetValueChangingCallback,
setMainVolume);
UiSliderBus::Event(mainVolumeSliderElementId,
&UiSliderInterface::SetValueChangedCallback,
setMainVolume);
// Refresh the music volume slider
AZ::EntityId musicVolumeSliderElementId;
UiCanvasBus::EventResult(musicVolumeSliderElementId,
m_optionsMenuCanvasEntityId,
&UiCanvasInterface::FindElementEntityIdByName,
"MusicVolumeSlider");
UiSliderBus::Event(musicVolumeSliderElementId,
&UiSliderInterface::SetValue,
m_gameOptions->GetMusicVolume());
auto setMusicVolume = [gameOptions = m_gameOptions]([[maybe_unused]] AZ::EntityId entityId, float value)
{
gameOptions->SetMusicVolume(value);
};
UiSliderBus::Event(musicVolumeSliderElementId,
&UiSliderInterface::SetValueChangingCallback,
setMusicVolume);
UiSliderBus::Event(musicVolumeSliderElementId,
&UiSliderInterface::SetValueChangedCallback,
setMusicVolume);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::SetOptionsMenuCanvasDrawOrder()
{
// Loaded canvases are already stored sorted by draw order...
UiCanvasManagerInterface::CanvasEntityList canvases;
UiCanvasManagerBus::BroadcastResult(canvases, &UiCanvasManagerInterface::GetLoadedCanvases);
// ...so get the draw order of the top-most displayed UI canvas...
int highestDrawOrder = 0;
UiCanvasBus::EventResult(highestDrawOrder, canvases.back(), &UiCanvasInterface::GetDrawOrder);
// ...and increment it by one unless it's already set to int max...
if (highestDrawOrder != std::numeric_limits<int>::max())
{
++highestDrawOrder;
}
// ...ensuring the pause menu gets displayed on top of all other loaded canvases,
// with the exception of 'special' ones like message popups or the loading screen
// that use a draw order of int max.
UiCanvasBus::Event(m_optionsMenuCanvasEntityId, &UiCanvasInterface::SetDrawOrder, highestDrawOrder);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStateOptionsMenu::GetOptionsMenuCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultoptionsmenuscreen.uicanvas";
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStateOptionsMenu::SaveGameOptionsToPersistentStorage()
{
SaveData::SaveDataRequests::SaveOrLoadObjectParams<GameOptions> saveObjectParams;
saveObjectParams.serializableObject = m_gameOptions;
saveObjectParams.dataBufferName = GameOptions::SaveDataBufferName;
saveObjectParams.localUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
SaveData::SaveDataRequests::SaveObject(saveObjectParams);
}
}
@@ -0,0 +1,81 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
#include <AzFramework/Input/Buses/Notifications/InputDeviceNotificationBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while the primary user's controller is disconnected.
class GameStatePrimaryControllerDisconnected : public GameState::IGameState
, public AzFramework::InputChannelEventListener
, public AzFramework::InputDeviceNotificationBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStatePrimaryControllerDisconnected, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStatePrimaryControllerDisconnected, "{47FCBC7A-49CB-4FEB-842A-C730CCB19940}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStatePrimaryControllerDisconnected() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStatePrimaryControllerDisconnected() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceNotifications::OnInputDeviceConnectedEvent
void OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to show/hide the primary controller disconnected popup.
///@{
virtual void ShowPrimaryControllerDisconnectedPopup();
virtual void HidePrimaryControllerDisconnectedPopup();
///@}
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::u32 m_primaryControllerDisconnectedPopupId = 0; //!< Id of the popup being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStatePrimaryControllerDisconnected.inl>
@@ -0,0 +1,118 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStatePrimaryControllerDisconnected.h>
#include <GameStateSamples/GameStateSamples_Traits_Platform.h>
#include <MessagePopup/MessagePopupBus.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <AzFramework/Input/Utils/IsAnyKeyOrButton.h>
#include <ILocalizationManager.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryControllerDisconnected::OnEnter()
{
ShowPrimaryControllerDisconnectedPopup();
AzFramework::InputChannelEventListener::Connect();
AzFramework::InputDeviceNotificationBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryControllerDisconnected::OnExit()
{
AzFramework::InputDeviceNotificationBus::Handler::BusDisconnect();
AzFramework::InputChannelEventListener::Disconnect();
HidePrimaryControllerDisconnectedPopup();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStatePrimaryControllerDisconnected::GetPriority() const
{
// Re-connecting the primary user's controller takes precedence over everything else
return AzFramework::InputChannelEventListener::GetPriorityFirst();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStatePrimaryControllerDisconnected::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateEnded() &&
AzFramework::IsAnyKeyOrButton(inputChannel))
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId == inputChannel.GetInputDevice().GetAssignedLocalUserId())
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryControllerDisconnected>(),
"The active game state is not an instance of GameStatePrimaryControllerDisconnected");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
}
}
return true; // Consume all input while this game state is active
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryControllerDisconnected::OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId == inputDevice.GetAssignedLocalUserId())
{
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryControllerDisconnected>(),
"The active game state is not an instance of GameStatePrimaryControllerDisconnected");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryControllerDisconnected::ShowPrimaryControllerDisconnectedPopup()
{
if (m_primaryControllerDisconnectedPopupId != 0)
{
// We're already displaying the message popup
return;
}
string localizedMessage;
const char* localizationKey = AZ_TRAIT_GAMESTATESAMPLES_PRIMARY_CONTROLLER_DISCONNECTED_LOC_KEY;
bool wasLocalized = false;
LocalizationManagerRequestBus::BroadcastResult(wasLocalized,
&LocalizationManagerRequestBus::Events::LocalizeString_ch,
localizationKey,
localizedMessage,
false);
const char* popupMessage = wasLocalized && localizedMessage != localizationKey ?
localizedMessage.c_str() :
AZ_TRAIT_GAMESTATESAMPLES_PRIMARY_CONTROLLER_DISCONNECTED_DEFAULT_MESSAGE;
MessagePopup::MessagePopupRequestBus::BroadcastResult(m_primaryControllerDisconnectedPopupId,
&MessagePopup::MessagePopupRequests::ShowPopup,
popupMessage,
MessagePopup::EPopupButtons_NoButtons);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryControllerDisconnected::HidePrimaryControllerDisconnectedPopup()
{
if (m_primaryControllerDisconnectedPopupId != 0)
{
MessagePopup::MessagePopupRequestBus::Broadcast(&MessagePopup::MessagePopupRequests::HidePopup,
m_primaryControllerDisconnectedPopupId, 0);
m_primaryControllerDisconnectedPopupId = 0;
}
}
}
@@ -0,0 +1,111 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <GameState/GameStateNotificationBus.h>
#include <LocalUser/LocalUserNotificationBus.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Input/Buses/Notifications/InputDeviceNotificationBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is pushed after determining the primary user (GameStatePrimaryUserSelection)
//! that monitors for events related to the primary user we must respond to (eg. user sign-out).
//! This state will almost never be active, so it won't receive updates, but will rather sit in
//! the stack monitoring for events and respond them by pushing (or popping) other game states.
class GameStatePrimaryUserMonitor : public GameState::IGameState
, public GameState::GameStateNotificationBus::Handler
, public AzFramework::InputDeviceNotificationBus::Handler
, public AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public LocalUser::LocalUserNotificationBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStatePrimaryUserMonitor, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStatePrimaryUserMonitor, "{2B7DB914-DEEC-4A2F-B178-9AD953D70FE0}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStatePrimaryUserMonitor() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStatePrimaryUserMonitor() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameStateNotifications::OnActiveGameStateChanged
void OnActiveGameStateChanged(AZStd::shared_ptr<IGameState> oldGameState,
AZStd::shared_ptr<IGameState> newGameState) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceNotifications::OnInputDeviceConnectedEvent
void OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceNotifications::OnInputDeviceDisconnectedEvent
void OnInputDeviceDisconnectedEvent(const AzFramework::InputDevice& inputDevice) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::ApplicationLifecycleEvents::OnApplicationUnconstrained
void OnApplicationUnconstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref LocalUser::LocalUserNotifications::OnLocalUserSignedIn
void OnLocalUserSignedIn(AzFramework::LocalUserId localUserId) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref LocalUser::LocalUserNotifications::OnLocalUserSignedOut
void OnLocalUserSignedOut(AzFramework::LocalUserId localUserId) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default primary controller disconnected game state.
//! Override if you wish to push a different primary controller disconnected game state.
virtual void PushPrimaryControllerDisconnectedGameState();
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default primary user signed out game state.
//! Override if you wish to push a different primary user signed out game state.
virtual void PushPrimaryUserSignedOutGameState();
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default level paused game state (if it is needed).
//! Override if you wish to push a different level paused game state.
virtual void TryPushLevelPausedGameState();
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
bool m_primaryControllerDisconnectedWhileLevelLoading = false;
bool m_primaryUserSignedOutWhileLevelLoading = false;
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStatePrimaryUserMonitor.inl>
@@ -0,0 +1,228 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateLevelLoading.h>
#include <GameStateSamples/GameStateLevelPaused.h>
#include <GameStateSamples/GameStateLevelRunning.h>
#include <GameStateSamples/GameStatePrimaryControllerDisconnected.h>
#include <GameStateSamples/GameStatePrimaryUserMonitor.h>
#include <GameStateSamples/GameStatePrimaryUserSignedOut.h>
#include <LocalUser/LocalUserRequestBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnPushed()
{
m_primaryControllerDisconnectedWhileLevelLoading = false;
m_primaryUserSignedOutWhileLevelLoading = false;
GameState::GameStateNotificationBus::Handler::BusConnect();
AzFramework::InputDeviceNotificationBus::Handler::BusConnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
LocalUser::LocalUserNotificationBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnPopped()
{
LocalUser::LocalUserNotificationBus::Handler::BusDisconnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzFramework::InputDeviceNotificationBus::Handler::BusDisconnect();
GameState::GameStateNotificationBus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnActiveGameStateChanged(AZStd::shared_ptr<IGameState> oldGameState,
AZStd::shared_ptr<IGameState> newGameState)
{
if (m_primaryUserSignedOutWhileLevelLoading &&
azrtti_istypeof<GameStateLevelLoading>(oldGameState.get()))
{
// The primary user signed out while a level was loading,
// we had to wait until the level finished loading before
// transitioning to the primary user signed out game state.
m_primaryUserSignedOutWhileLevelLoading = false;
m_primaryControllerDisconnectedWhileLevelLoading = false;
PushPrimaryUserSignedOutGameState();
return;
}
if (m_primaryControllerDisconnectedWhileLevelLoading &&
azrtti_istypeof<GameStateLevelLoading>(oldGameState.get()))
{
// The controller disconnected while a level was loading,
// we had to wait until the level finished loading before
// transitioning to the controller disconnected game state.
m_primaryControllerDisconnectedWhileLevelLoading = false;
PushPrimaryControllerDisconnectedGameState();
return;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnInputDeviceConnectedEvent(const AzFramework::InputDevice& inputDevice)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (m_primaryControllerDisconnectedWhileLevelLoading &&
primaryLocalUserId == inputDevice.GetAssignedLocalUserId())
{
// The controller disconnected while a level was loading,
// but was reconnected before the level finished loading.
m_primaryControllerDisconnectedWhileLevelLoading = false;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnInputDeviceDisconnectedEvent(const AzFramework::InputDevice& inputDevice)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId != inputDevice.GetAssignedLocalUserId() ||
primaryLocalUserId == AzFramework::LocalUserIdNone)
{
// The disconnected controller does not belong to the primary user,
// or the primary user has not yet been set.
return;
}
if (GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelLoading>())
{
// The controller disconnected while a level is loading;
// we have to wait until the level has finished loading.
m_primaryControllerDisconnectedWhileLevelLoading = true;
return;
}
PushPrimaryControllerDisconnectedGameState();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnApplicationUnconstrained(AzFramework::ApplicationLifecycleEvents::Event /*lastEvent*/)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId == AzFramework::LocalUserIdNone)
{
// The primary user has yet to be set
m_primaryUserSignedOutWhileLevelLoading = false;
return;
}
bool isPrimaryLocalUserSignedIn = false;
LocalUser::LocalUserRequestBus::BroadcastResult(isPrimaryLocalUserSignedIn,
&LocalUser::LocalUserRequests::IsLocalUserSignedIn,
primaryLocalUserId);
if (isPrimaryLocalUserSignedIn)
{
// The primary user is still signed in
m_primaryUserSignedOutWhileLevelLoading = false;
return;
}
if (GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelLoading>())
{
// The primary user signed out while a level is loading;
// we have to wait until the level has finished loading.
m_primaryUserSignedOutWhileLevelLoading = true;
return;
}
PushPrimaryUserSignedOutGameState();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnLocalUserSignedIn(AzFramework::LocalUserId localUserId)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (m_primaryUserSignedOutWhileLevelLoading &&
primaryLocalUserId == localUserId)
{
// The primary user signed out while a level was loading,
// but signed in again before the level finished loading.
m_primaryUserSignedOutWhileLevelLoading = false;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::OnLocalUserSignedOut(AzFramework::LocalUserId localUserId)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId != localUserId ||
primaryLocalUserId == AzFramework::LocalUserIdNone)
{
// The user that signed out is not the primary user,
// or the primary user has not yet been set.
return;
}
if (GameState::GameStateRequests::IsActiveGameStateOfType<GameStateLevelLoading>())
{
// The primary user signed out while a level is loading;
// we have to wait until the level has finished loading.
m_primaryUserSignedOutWhileLevelLoading = true;
return;
}
PushPrimaryUserSignedOutGameState();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::PushPrimaryControllerDisconnectedGameState()
{
if (GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryControllerDisconnected>() ||
GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryUserSignedOut>())
{
// The controller disconnection has already been detected,
// or the primary user signed out (which takes precedence).
return;
}
// Ensure the game is paused if needed before pushing the controller disconnected game state
TryPushLevelPausedGameState();
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStatePrimaryControllerDisconnected>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::PushPrimaryUserSignedOutGameState()
{
if (GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryUserSignedOut>())
{
// The primary user sign out has already been detected
return;
}
if (GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryControllerDisconnected>())
{
// The primary user signing out takes precedence over their controller being disconnected
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
}
// Ensure the game is paused if needed before pushing the primary user signed out game state
TryPushLevelPausedGameState();
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStatePrimaryUserSignedOut>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserMonitor::TryPushLevelPausedGameState()
{
if (GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelPaused>() ||
!GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateLevelRunning>())
{
// The game has already been paused or is not actively running yet
return;
}
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateLevelPaused>();
}
}
@@ -0,0 +1,102 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while waiting to determine who the primary user is.
class GameStatePrimaryUserSelection : public GameState::IGameState
, public AzFramework::InputChannelEventListener
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStatePrimaryUserSelection, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStatePrimaryUserSelection, "{953A3CBD-92BD-4B9A-9FD2-C1DC6E9A8BF8}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStatePrimaryUserSelection() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStatePrimaryUserSelection() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPushed
void OnPushed() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnPopped
void OnPopped() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
//! Used to detect the press of a button / key that will identify the primary user. If you
//! override this class and wish to detect the primary user through another means, or want
//! the UI displayed to process input, you should override this function to do nothing and
//! handle setting the primary user yourself, before transitioning to the next game state.
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to set the primary local user and transition to the next game state
//! \param[in] localUserId The local user id to set as the primary user
virtual void SetPrimaryLocalUser(AzFramework::LocalUserId localUserId);
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default primary user monitor game state.
//! Override if you wish to push a different primary user monitor game state.
virtual void PushPrimaryUserMonitorGameState();
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience function to push the default main menu game state.
//! Override if you wish to push a different main menu game state.
virtual void PushMainMenuGameState();
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to load and unload the primary user selection UI canvas.
///@{
virtual void LoadPrimaryUserSelectionCanvas();
virtual void UnloadPrimaryUserSelectionCanvas();
virtual const char* GetPrimaryUserSelectionCanvasAssetPath();
protected:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::EntityId m_primaryUserSelectionCanvasEntityId; //!< Id of the UI canvas being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStatePrimaryUserSelection.inl>
@@ -0,0 +1,177 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStateMainMenu.h>
#include <GameStateSamples/GameStatePrimaryUserSelection.h>
#include <GameStateSamples/GameStatePrimaryUserMonitor.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiCanvasManagerBus.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <AzFramework/Input/Utils/IsAnyKeyOrButton.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::OnPushed()
{
// We could load the UI canvas here and keep it cached until OnPopped is called in order to
// speed up re-entering this game state, but doing so would consume memory for the lifetime
// of the process that is only needed while this state is active (which is not very often).
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::OnPopped()
{
// See comment above in OnPushed
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::OnEnter()
{
// In case we are returning to this game state from another (rather than for the first time)
LocalUser::LocalUserRequestBus::Broadcast(&LocalUser::LocalUserRequests::ClearAllLocalUserIdToLocalPlayerSlotAssignments);
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::SetLocalUserIdInputFilterForAllCanvases, AzFramework::LocalUserIdAny);
// Load and display the UI canvas
LoadPrimaryUserSelectionCanvas();
// Start listening for input in order to determine the primary user
InputChannelEventListener::Connect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::OnExit()
{
// Stop listening for input
InputChannelEventListener::Disconnect();
// Hide and unload the UI canvas
UnloadPrimaryUserSelectionCanvas();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStatePrimaryUserSelection::GetPriority() const
{
// Take precedence over all other input in order to detect the press of a button or key that
// will identify the primary user. If you override this class and wish to detect the primary
// user through another means or want the UI displayed to process input, you should override
// GameStatePrimaryUserSelection::OnInputChannelEventFiltered to do nothing.
return AzFramework::InputChannelEventListener::GetPriorityFirst();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStatePrimaryUserSelection::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateEnded() && AzFramework::IsAnyKeyOrButton(inputChannel))
{
const AzFramework::LocalUserId localUserId = inputChannel.GetInputDevice().GetAssignedLocalUserId();
if (localUserId == AzFramework::LocalUserIdAny ||
localUserId == AzFramework::LocalUserIdNone)
{
// No local user is associated with this input device yet, so prompt for user sign-in
inputChannel.GetInputDevice().PromptLocalUserSignIn();
}
else
{
SetPrimaryLocalUser(localUserId);
PushPrimaryUserMonitorGameState();
PushMainMenuGameState();
}
}
// Consume the input regardless because nothing else should be able to
// process it while we're waiting to determine who the primary user is.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::SetPrimaryLocalUser(AzFramework::LocalUserId localUserId)
{
AZ::u32 assignedSlot = LocalUser::LocalPlayerSlotNone;
LocalUser::LocalUserRequestBus::BroadcastResult(assignedSlot,
&LocalUser::LocalUserRequests::AssignLocalUserIdToLocalPlayerSlot,
localUserId,
LocalUser::LocalPlayerSlotPrimary);
AZ_Assert(assignedSlot == LocalUser::LocalPlayerSlotPrimary,
"Could not assign local user id %u to the primary local player slot", localUserId);
// Make it so only the primary user can interact with the UI
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::SetLocalUserIdInputFilterForAllCanvases, localUserId);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::PushPrimaryUserMonitorGameState()
{
// Push the game state that monitors for events related to the primary user we must respond to
if (GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStatePrimaryUserMonitor>())
{
AZ_Assert(false, "The game state stack already contains an instance of GameStatePrimaryUserMonitor");
return;
}
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStatePrimaryUserMonitor>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::PushMainMenuGameState()
{
// Push the game menu game state
if (GameState::GameStateRequests::DoesStackContainGameStateOfType<GameStateMainMenu>())
{
AZ_Assert(false, "The game state stack already contains an instance of GameStateMainMenu");
return;
}
GameState::GameStateRequests::CreateAndPushNewOverridableGameStateOfType<GameStateMainMenu>();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::LoadPrimaryUserSelectionCanvas()
{
// Load the UI canvas
const char* uiCanvasAssetPath = GetPrimaryUserSelectionCanvasAssetPath();
UiCanvasManagerBus::BroadcastResult(m_primaryUserSelectionCanvasEntityId,
&UiCanvasManagerInterface::LoadCanvas,
uiCanvasAssetPath);
if (!m_primaryUserSelectionCanvasEntityId.IsValid())
{
AZ_Warning("GameStatePrimaryUserSelection", false, "Could not load %s", uiCanvasAssetPath);
return;
}
// Display the canvas and set it to stay loaded when a level unloads
UiCanvasBus::Event(m_primaryUserSelectionCanvasEntityId, &UiCanvasInterface::SetEnabled, true);
UiCanvasBus::Event(m_primaryUserSelectionCanvasEntityId, &UiCanvasInterface::SetKeepLoadedOnLevelUnload, true);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSelection::UnloadPrimaryUserSelectionCanvas()
{
if (m_primaryUserSelectionCanvasEntityId.IsValid())
{
// Unload the main menu
UiCanvasManagerBus::Broadcast(&UiCanvasManagerInterface::UnloadCanvas,
m_primaryUserSelectionCanvasEntityId);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline const char* GameStatePrimaryUserSelection::GetPrimaryUserSelectionCanvasAssetPath()
{
return "@assets@/ui/canvases/defaultprimaryuserselectionscreen.uicanvas";
}
}
@@ -0,0 +1,82 @@
/*
* 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.
*
*/
#pragma once
#include <GameState/GameState.h>
#include <LocalUser/LocalUserNotificationBus.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
//! Game state that is active while the primary user is signed out.
class GameStatePrimaryUserSignedOut : public GameState::IGameState
, public AzFramework::InputChannelEventListener
, public LocalUser::LocalUserNotificationBus::Handler
{
public:
////////////////////////////////////////////////////////////////////////////////////////////
// Allocator
AZ_CLASS_ALLOCATOR(GameStatePrimaryUserSignedOut, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
// Type Info
AZ_RTTI(GameStatePrimaryUserSignedOut, "{5750DA57-349F-4401-B133-977C68ED70A3}", IGameState);
////////////////////////////////////////////////////////////////////////////////////////////
//! Default constructor
GameStatePrimaryUserSignedOut() = default;
////////////////////////////////////////////////////////////////////////////////////////////
//! Default destructor
~GameStatePrimaryUserSignedOut() override = default;
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnEnter
void OnEnter() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref GameState::GameState::OnExit
void OnExit() override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::GetPriority
AZ::s32 GetPriority() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputChannelEventListener::OnInputChannelEventFiltered
bool OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref LocalUser::LocalUserNotifications::OnLocalUserSignedIn
void OnLocalUserSignedIn(AzFramework::LocalUserId localUserId) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! Convenience functions to show/hide the primary user signed out popup.
///@{
virtual void ShowPrimaryUserSignedOutPopup();
virtual void HidePrimaryUserSignedOutPopup();
///@}
private:
////////////////////////////////////////////////////////////////////////////////////////////
// Variables
AZ::u32 m_primaryUserSignedOutPopupId = 0; //!< Id of the popup being displayed
};
} // namespace GameStateSamples
// Include the implementation inline so the class can be instantiated outside of the gem.
#include <GameStateSamples/GameStatePrimaryUserSignedOut.inl>
@@ -0,0 +1,133 @@
/*
* 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 <GameState/GameStateRequestBus.h>
#include <GameStateSamples/GameStatePrimaryUserSignedOut.h>
#include <GameStateSamples/GameStatePrimaryUserSelection.h>
#include <MessagePopup/MessagePopupBus.h>
#include <LocalUser/LocalUserRequestBus.h>
#include <AzFramework/Input/Utils/IsAnyKeyOrButton.h>
#include <ILocalizationManager.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace GameStateSamples
{
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSignedOut::OnEnter()
{
ShowPrimaryUserSignedOutPopup();
AzFramework::InputChannelEventListener::Connect();
LocalUser::LocalUserNotificationBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSignedOut::OnExit()
{
LocalUser::LocalUserNotificationBus::Handler::BusDisconnect();
AzFramework::InputChannelEventListener::Disconnect();
HidePrimaryUserSignedOutPopup();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline AZ::s32 GameStatePrimaryUserSignedOut::GetPriority() const
{
// Re-establishing a primary user takes precedence over everything else
return AzFramework::InputChannelEventListener::GetPriorityFirst();
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline bool GameStatePrimaryUserSignedOut::OnInputChannelEventFiltered(const AzFramework::InputChannel & inputChannel)
{
if (inputChannel.IsStateEnded() &&
AzFramework::IsAnyKeyOrButton(inputChannel))
{
const AzFramework::LocalUserId assignedLocalUserId = inputChannel.GetInputDevice().GetAssignedLocalUserId();
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (assignedLocalUserId == primaryLocalUserId)
{
// We received input from the primary user, so just pop this state
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryUserSignedOut>(),
"The active game state is not an instance of GameStatePrimaryUserSignedOut");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
}
else if (assignedLocalUserId == AzFramework::LocalUserIdAny ||
assignedLocalUserId == AzFramework::LocalUserIdNone)
{
// We received input from a device that is not associated with a user, so prompt for user sign-in
inputChannel.GetInputDevice().PromptLocalUserSignIn();
}
else
{
// We received input from a different user confirming we want to select a new one
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryUserSignedOut>(),
"The active game state is not an instance of GameStatePrimaryUserSignedOut");
GameState::GameStateRequests::PopActiveGameStateUntilOfType<GameStatePrimaryUserSelection>();
}
}
return true; // Consume all input while this game state is active
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSignedOut::OnLocalUserSignedIn(AzFramework::LocalUserId localUserId)
{
const AzFramework::LocalUserId primaryLocalUserId = LocalUser::LocalUserRequests::GetPrimaryLocalUserId();
if (primaryLocalUserId == localUserId)
{
// The primary user signed back in
AZ_Assert(GameState::GameStateRequests::IsActiveGameStateOfType<GameStatePrimaryUserSignedOut>(),
"The active game state is not an instance of GameStatePrimaryUserSignedOut");
GameState::GameStateRequestBus::Broadcast(&GameState::GameStateRequests::PopActiveGameState);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSignedOut::ShowPrimaryUserSignedOutPopup()
{
if (m_primaryUserSignedOutPopupId != 0)
{
// We're already displaying the message popup
return;
}
string localizedMessage;
const char* localizationKey = "@PRIMARY_CONTROLLER_DISCONNECTED_LOC_KEY";
bool wasLocalized = false;
LocalizationManagerRequestBus::BroadcastResult(wasLocalized,
&LocalizationManagerRequestBus::Events::LocalizeString_ch,
localizationKey,
localizedMessage,
false);
const char* popupMessage = wasLocalized && localizedMessage != localizationKey ?
localizedMessage.c_str() :
"Primary profile signed out.\n\nEither sign in again with the same profile, or press any button while signed into a different profile to return to the main menu.\n\n(any unsaved progress will be lost)";
MessagePopup::MessagePopupRequestBus::BroadcastResult(m_primaryUserSignedOutPopupId,
&MessagePopup::MessagePopupRequests::ShowPopup,
popupMessage,
MessagePopup::EPopupButtons_NoButtons);
}
////////////////////////////////////////////////////////////////////////////////////////////////
inline void GameStatePrimaryUserSignedOut::HidePrimaryUserSignedOutPopup()
{
if (m_primaryUserSignedOutPopupId != 0)
{
MessagePopup::MessagePopupRequestBus::Broadcast(&MessagePopup::MessagePopupRequests::HidePopup,
m_primaryUserSignedOutPopupId, 0);
m_primaryUserSignedOutPopupId = 0;
}
}
}