28 Commits
Author SHA1 Message Date
ThomasCharester 7bc15cc03d Перекинул энтерстейт 2025-11-03 16:45:41 +03:00
ThomasCharester d861e06caf Фикс утечки памяти. Первой наверное... 2025-11-03 12:54:45 +03:00
ThomasCharester f940cbffe0 Теперь Addressables и UniTask в InstantiateAsync и прочих процессах 2025-11-03 12:31:26 +03:00
ThomasCharester 6b1f24b72e Addressables in process 2025-11-02 23:29:29 +03:00
ThomasCharester 1ad87e7432 Unitask добавлен 2025-11-02 18:02:45 +03:00
ThomasCharester 09219d9091 Этот интерфейс... Добавил инструменты и заготовки 2025-11-02 17:31:45 +03:00
ThomasCharester 37f5d15410 Диспетчер в процессе 2025-11-01 11:34:34 +03:00
ThomasCharester 019da8cfd3 Экран смерти, смерть, бабки, Интерфейс, Шлюхи 2025-11-01 01:45:46 +03:00
ThomasCharester edf3821c0d Межинтерфейс 2025-11-01 00:54:24 +03:00
ThomasCharester 45fe19be24 Интерфейс в процессе 2025-10-31 23:19:16 +03:00
ThomasCharester bea109175b Допил интерфейса, здоровья, денег 2025-10-30 21:13:42 +03:00
MrTorex b214bb0f63 Инишиал механика наклона при залазании на стены
Нуждается в доработке (как минимум чтобы вращалось в правильную сторону), но вроде-как выглядит прикольно
2025-10-29 20:09:39 +03:00
ThomasCharester 0ac5ba1e1c Интерфейс прокачан 2025-10-29 20:06:37 +03:00
ThomasCharester ab9d444072 Система здоровья 2025-10-29 16:02:05 +03:00
ThomasCharester 2335908a90 Какие штуки 2025-10-29 16:01:46 +03:00
ThomasCharester ae76edb0d0 Сделан сервис UI и заготовки панелек 2025-10-29 16:01:18 +03:00
ThomasCharester 6f2b063700 Небольшой фикс инфраструктуры 2025-10-29 15:57:27 +03:00
ThomasCharester c6d1c3bffd Static camera state, Scene environments 2025-10-29 14:13:09 +03:00
MrTorex 07ce27618c Перенос всех магических чисел в градусы 2025-10-29 12:11:03 +03:00
MrTorex fb85b22e96 Вынос настроек персонажа в конфиг
В который сейчас будет добавлено пару новых настроек
2025-10-29 11:17:27 +03:00
MrTorex 665114b7dc Соединение всё со всем что имеется, дописывание стейт-машины
Теперь мне удобно кастить и получать тип камеры, всё вроде работает нормисно
2025-10-29 01:02:02 +03:00
ThomasCharester 4549617275 Теперь камера - это стейт-машина 2025-10-28 22:11:46 +03:00
ThomasCharester db541fed8a Фикс hardcrash а при старте 2025-10-28 22:11:02 +03:00
ThomasCharester 085429d5ca Небольшая перепись камеры 2025-10-28 22:10:45 +03:00
ThomasCharester 981d081bed Переполох в камере, насрано 2025-10-28 22:10:25 +03:00
MrTorex 6148889fc1 Merge branch 'movement-dev' of https://github.com/ThomasCharester/CourierOld into movement-dev 2025-10-28 12:54:03 +03:00
MrTorex 7538ce81af Допил конфига, переделаны комментарии
Чисто для удобства, чтобы не было много грязюки
2025-10-28 12:42:17 +03:00
MrTorex 9a3743988d Завершение рефакторинга, фикс бага с запросом слайда
Теперь всё поделено по логическим классам, чтобы было помодульно, а также если выполняется запрос на слайд в момент, когда слайд не может быть выполнен, запрос отменяется
2025-10-27 18:16:24 +03:00
362 changed files with 84870 additions and 650 deletions

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/projectSettingsUpdater.xml
/modules.xml
/.idea.CourierOldRe.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dea69d41f90c6ea4fa55c27c1d60c145, type: 3}
m_Name: AddressableAssetGroupSortSettings
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.AddressableAssetGroupSortSettings
sortOrder:
- d447fc60305c17a17bf20948ab295a45
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6938984fc7d25285abb4922dd46d9272 guid: 88c15d5f41d582974a9b7218c493e4a7
NativeFormatImporter: NativeFormatImporter:
externalObjects: {} externalObjects: {}
mainObjectFileID: 11400000 mainObjectFileID: 11400000
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: d447fc60305c17a17bf20948ab295a45 m_DefaultGroup: d447fc60305c17a17bf20948ab295a45
m_currentHash: m_currentHash:
serializedVersion: 2 serializedVersion: 2
Hash: 44f191e9f235d5420c20a51f93ead7da Hash: 90c715b85c1e0904b3dc61a9ed460206
m_OptimizeCatalogSize: 0 m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0 m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0 m_CatalogRequestsTimeout: 0
@@ -54,7 +54,7 @@ MonoBehaviour:
m_Id: m_Id:
m_RemoteCatalogLoadPath: m_RemoteCatalogLoadPath:
m_Id: m_Id:
m_ContentStateBuildPathProfileVariableName: m_ContentStateBuildPathProfileVariableName: <default settings path>
m_CustomContentStateBuildPath: m_CustomContentStateBuildPath:
m_ContentStateBuildPath: m_ContentStateBuildPath:
m_BuildAddressablesWithPlayerBuild: 0 m_BuildAddressablesWithPlayerBuild: 0
@@ -14,7 +14,32 @@ MonoBehaviour:
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup
m_GroupName: Default Local Group m_GroupName: Default Local Group
m_GUID: d447fc60305c17a17bf20948ab295a45 m_GUID: d447fc60305c17a17bf20948ab295a45
m_SerializeEntries: [] m_SerializeEntries:
- m_GUID: 23e11d2c8a4f12f4ea9a7a43d1118464
m_Address: Gameplay
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 48aebfe8826dc0df79d047b5afbf8f5b
m_Address: Canvas
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b2e8dc2aeb0311cd8a826b51cf8e94dc
m_Address: GameOver
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c566fc932edefbbee9e53271eac3c560
m_Address: MainMenu
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f23b9fce9594618e5b75845f1cae50de
m_Address: LoadingScreen
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
m_ReadOnly: 0 m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 2588e84eb64800d0781c574a37fa48d8, type: 2} m_Settings: {fileID: 11400000, guid: 2588e84eb64800d0781c574a37fa48d8, type: 2}
m_SchemaSet: m_SchemaSet:
@@ -0,0 +1,12 @@
using Code.App.StateMachine;
using Code.StateMachine;
namespace Code.App.AppStates
{
public class BootstrapState : BaseState<AppStateMachineContext>
{
public BootstrapState(AppStateMachineContext context) : base(context)
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0183e73a08c64332babded3df3d06b95
timeCreated: 1762122962
@@ -1,5 +1,8 @@
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Scenes;
using Code.StateMachine; using Code.StateMachine;
using Code.UI;
using UnityEngine;
namespace Code.App.AppStates namespace Code.App.AppStates
{ {
@@ -7,17 +10,32 @@ namespace Code.App.AppStates
{ {
public GameOverState(AppStateMachineContext context) : base(context) public GameOverState(AppStateMachineContext context) : base(context)
{ } { }
private float damnTimer = 3f; // TODO Удалить при первой возможности
public override void EnterState() public override void EnterState()
{ {
Context.UIService.SwitchPanelTo(UIPanel.GameOver);
base.EnterState(); base.EnterState();
} }
private void ToMainMenu()
{
Context.StateMachine.SwitchState(AppState.MainMenu)
.AllowStateTransition(AppState.Gameplay);
}
public override void Tick() public override void Tick()
{ } {
damnTimer -= Time.deltaTime;
if (damnTimer <= float.Epsilon) ToMainMenu();
}
public override void ExitState() public override void ExitState()
{ {
damnTimer = 5f;
Context.UIService.LoadingScreen.Show();
base.ExitState(); base.ExitState();
} }
} }
@@ -1,37 +1,115 @@
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Gameplay.Camera;
using Code.Gameplay.Player;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.StateMachine; using Code.StateMachine;
using UnityEngine; using Code.UI;
using UnityEngine.InputSystem;
namespace Code.App.AppStates namespace Code.App.AppStates
{ {
public class GameplayState : BaseState<AppStateMachineContext> public class GameplayState : BaseState<AppStateMachineContext>
{ {
private PlayerHealth _playerHealth;
private PlayerMoney _playerMoney;
private bool _uiLoaded, _sceneLoaded;
public GameplayState(AppStateMachineContext context) : base(context) public GameplayState(AppStateMachineContext context) : base(context)
{ {
} }
public override void EnterState() public override void EnterState()
{ {
Context.SceneLoaderService.LoadScene((int)SceneIds.Gameplay, SceneLoaded); _uiLoaded = _sceneLoaded = false;
Context.UIService.LoadingScreen.Show();
_playerMoney = Context.PlayerFactory.CreatePlayerMoney();
_playerHealth = Context.PlayerFactory.CreatePlayerHealth();
_playerHealth.OnDeath += EndGame;
Context.SceneLoaderService.LoadSceneAsync((int)SceneIds.MovementDev, callBack: OnSceneLoaded); // TODO Фабрика игрока Nada
Context.UIService.SwitchPanelTo(UIPanel.Gameplay, callback: PostUILoaded);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.GiveHealth, GiveHealth);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.GiveMoney, GiveMoney);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.LooseHealth, LooseHealth);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.LooseMoney, LooseMoney);
}
private void OnSceneLoaded()
{
_sceneLoaded = true;
CheckIfLoaded();
}
private void PostUILoaded()
{
((GameplayPanel)Context.UIService.CurrentPanel).Initialize(_playerMoney, _playerHealth);
_uiLoaded = true;
CheckIfLoaded();
}
private void CheckIfLoaded()
{
if(!_uiLoaded || !_sceneLoaded) return;
Context.UIService.LoadingScreen.Hide();
Context.CameraStateMachine.SwitchState(CameraMode.FirstPerson);
Context.InputService.EnableMap(ActionMappings.Gameplay); Context.InputService.EnableMap(ActionMappings.Gameplay);
} }
private void SceneLoaded()
// Debug
private void GiveHealth(InputAction.CallbackContext context)
{ {
_playerHealth.GetDamage(-1);
base.EnterState();
} }
private void LooseHealth(InputAction.CallbackContext context)
{
_playerHealth.GetDamage(1);
}
private void GiveMoney(InputAction.CallbackContext context)
{
_playerMoney.GiveMoney(100);
((GameplayPanel)(Context.UIService.CurrentPanel)).SummonDispatcher();
}
private void LooseMoney(InputAction.CallbackContext context)
{
_playerMoney.LooseMoney(100);
((GameplayPanel)(Context.UIService.CurrentPanel)).CloseDispatcher();
}
public override void Tick() public override void Tick()
{ {
} }
private void EndGame()
{
Context.StateMachine.SwitchState(AppState.GameOver)
.AllowStateTransition(AppState.MainMenu);
}
public override void ExitState() public override void ExitState()
{ {
_playerHealth = null;
_playerMoney = null;
Context.InputService.DisableMap(ActionMappings.Gameplay); Context.InputService.DisableMap(ActionMappings.Gameplay);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.GiveHealth, GiveHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.GiveMoney, GiveHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.LooseHealth, LooseHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.LooseMoney, LooseMoney);
base.ExitState(); base.ExitState();
} }
} }
@@ -1,34 +1,93 @@
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Gameplay.Camera;
using Code.Gameplay.Camera.CameraStates;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.Scenes.SceneEnvironments;
using Code.StateMachine; using Code.StateMachine;
using Code.UI;
using Reflex.Extensions;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Code.App.AppStates namespace Code.App.AppStates
{ {
public class MainMenuState : BaseState<AppStateMachineContext> public class MainMenuState : BaseState<AppStateMachineContext>
{ {
private bool _uiLoaded, _sceneLoaded;
public MainMenuState(AppStateMachineContext context) : base(context) public MainMenuState(AppStateMachineContext context) : base(context)
{ } {
}
public override void EnterState() public override void EnterState()
{ {
Context.SceneLoaderService.LoadScene((int)SceneIds.MainMenu, SceneLoaded); _uiLoaded = _sceneLoaded = false;
Context.InputService.EnableMap(ActionMappings.MainMenu);
Context.UIService.LoadingScreen.Show();
Context.SceneLoaderService.LoadSceneAsync((int)SceneIds.MainMenu, callBack: SceneLoaded);
Context.UIService.SwitchPanelTo(UIPanel.MainMenu, PostUILoaded);
Context.CameraStateMachine.SwitchState(CameraMode.Static);
} }
private void PostUILoaded()
{
((MainMenuPanel)Context.UIService.CurrentPanel).startButton.onClick.AddListener(StartGame);
((MainMenuPanel)Context.UIService.CurrentPanel).exitButton.onClick.AddListener(ExitGame);
_uiLoaded = true;
CheckIfLoaded();
}
private void SceneLoaded() private void SceneLoaded()
{ {
var hui = ((MainMenuEnvironment)SceneManager.GetActiveScene()
.GetSceneContainer().Resolve<CurrentSceneEnvironment>());
((StaticState)Context.CameraStateMachine.CurrentBaseState).LookTarget =
hui.cameraTarget;
((BaseCameraState)Context.CameraStateMachine.CurrentBaseState).SetFollowTransform(hui.cameraPosition);
_sceneLoaded = true;
CheckIfLoaded();
}
private void CheckIfLoaded()
{
if(!_uiLoaded || !_sceneLoaded) return;
Context.UIService.LoadingScreen.Hide();
Context.InputService.EnableMap(ActionMappings.MainMenu);
base.EnterState(); base.EnterState();
} }
public override void Tick() public override void Tick()
{ } {
}
private void StartGame()
{
Context.StateMachine.SwitchState(AppState.Gameplay)
.AllowStateTransition(AppState.GameOver);
}
private void ExitGame()
{
Application.Quit();
}
public override void ExitState() public override void ExitState()
{ {
Context.InputService.DisableMap(ActionMappings.MainMenu); Context.InputService.DisableMap(ActionMappings.MainMenu);
base.ExitState(); base.ExitState();
} }
} }
@@ -1,4 +1,5 @@
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Gameplay.Camera;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.StateMachine; using Code.StateMachine;
@@ -14,8 +15,10 @@ namespace Code.App.AppStates
public override void EnterState() public override void EnterState()
{ {
Context.SceneLoaderService.LoadScene((int)SceneIds.MovementDev, SceneLoaded); // Context.SceneLoaderService.LoadScene((int)SceneIds.MovementDev, SceneLoaded);
Context.InputService.EnableMap(ActionMappings.Gameplay); Context.InputService.EnableMap(ActionMappings.Gameplay);
Context.CameraStateMachine.SwitchState(CameraMode.FirstPerson);
} }
private void SceneLoaded() private void SceneLoaded()
@@ -1,18 +1,25 @@
using System.Threading.Tasks;
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Configs; using Code.Configs;
using Code.Constants;
using Code.Gameplay.Camera;
using Code.Gameplay.Player;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.StaticData; using Code.StaticData;
using Code.UI;
using Code.UI.Panels;
using Cysharp.Threading.Tasks;
using Reflex.Core; using Reflex.Core;
using Reflex.Injectors; using Reflex.Injectors;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
namespace Code.App namespace Code.App
{ {
public class BootstrapInstaller : MonoBehaviour, IInstaller public class BootstrapInstaller : MonoBehaviour, IInstaller
{ {
public void InstallBindings(ContainerBuilder containerBuilder) public void InstallBindings(ContainerBuilder containerBuilder)
{ {
InstallStaticData(containerBuilder, out IStaticDataService staticDataService); InstallStaticData(containerBuilder, out IStaticDataService staticDataService);
@@ -20,20 +27,34 @@ namespace Code.App
InstallInfrastructureUnit(containerBuilder, staticDataService); InstallInfrastructureUnit(containerBuilder, staticDataService);
containerBuilder.AddSingleton(typeof(InputService), typeof(IInputService)); containerBuilder.AddSingleton(typeof(InputService), typeof(IInputService));
containerBuilder.AddSingleton(typeof(PlayerFactory), typeof(IPlayerFactory));
InitializeCamera(containerBuilder, staticDataService);
containerBuilder.AddSingleton(typeof(UIFactory), typeof(IUIFactory));
containerBuilder.AddSingleton(typeof(UIService), typeof(IUIService));
containerBuilder.AddSingleton(typeof(SceneLoaderService), typeof(ISceneLoaderService)); containerBuilder.AddSingleton(typeof(SceneLoaderService), typeof(ISceneLoaderService));
containerBuilder.OnContainerBuilt += Bootstrap; containerBuilder.OnContainerBuilt += Bootstrap;
} }
private void InitializeCamera(ContainerBuilder containerBuilder, IStaticDataService staticDataService)
{
Camera camera = Instantiate(staticDataService.CameraConfig.cameraPrefab);
DontDestroyOnLoad(camera);
containerBuilder.AddSingleton(camera);
}
private void InstallInfrastructureUnit(ContainerBuilder containerBuilder, IStaticDataService staticDataService) private void InstallInfrastructureUnit(ContainerBuilder containerBuilder, IStaticDataService staticDataService)
{ {
GameObject infrastructureUnit = Instantiate(staticDataService.AppConfig.infrastructureUnitPrefab); GameObject infrastructureUnit = Instantiate(staticDataService.AppConfig.infrastructureUnitPrefab);
DontDestroyOnLoad(infrastructureUnit); DontDestroyOnLoad(infrastructureUnit);
containerBuilder.AddSingleton(infrastructureUnit); containerBuilder.AddSingleton(infrastructureUnit);
containerBuilder.AddSingleton(infrastructureUnit.GetComponent<ICoroutineRunner>(), containerBuilder.AddSingleton(infrastructureUnit.AddComponent<CoroutineRunner>(),
typeof(ICoroutineRunner)); typeof(ICoroutineRunner));
containerBuilder.AddSingleton(infrastructureUnit.AddComponent<AppStateMachine>()); containerBuilder.AddSingleton(infrastructureUnit.AddComponent<AppStateMachine>());
containerBuilder.AddSingleton(infrastructureUnit.AddComponent<CameraStateMachine>());
} }
private void InstallStaticData(ContainerBuilder containerBuilder, out IStaticDataService staticDataService) private void InstallStaticData(ContainerBuilder containerBuilder, out IStaticDataService staticDataService)
@@ -43,18 +64,43 @@ namespace Code.App
containerBuilder.AddSingleton(staticDataService, typeof(IStaticDataService)); containerBuilder.AddSingleton(staticDataService, typeof(IStaticDataService));
containerBuilder.AddSingleton(staticDataService.InputConfig.inputActionAsset, typeof(InputActionAsset)); containerBuilder.AddSingleton(staticDataService.InputConfig.inputActionAsset, typeof(InputActionAsset));
containerBuilder.AddSingleton(staticDataService.InputConfig, typeof(InputConfig));
containerBuilder.AddSingleton(staticDataService.CameraConfig, typeof(CameraConfig));
containerBuilder.AddSingleton(staticDataService.PlayerConfig, typeof(PlayerConfig));
containerBuilder.AddSingleton(staticDataService.CharacterConfig, typeof(CharacterConfig));
containerBuilder.AddSingleton(staticDataService.UIConfig, typeof(UIConfig));
// containerBuilder.AddSingleton(staticDataService.UIConfig.canvasPrefab, typeof(Canvas));
// containerBuilder.AddSingleton(staticDataService.UIConfig.uiElementsPrefabs, typeof(UIElement[]));
} }
private bool _onUiLoaded;
private AppStateMachine _appStateMachine;
private void Bootstrap(Container container) private void Bootstrap(Container container)
{ {
AppStateMachine appStateMachine = container.Resolve<AppStateMachine>(); _appStateMachine = container.Resolve<AppStateMachine>();
appStateMachine.Initialize(); _appStateMachine.Initialize();
AttributeInjector.Inject(_appStateMachine.StateMachineContext, container);
CameraStateMachine cameraStateMachine = container.Resolve<CameraStateMachine>();
AttributeInjector.Inject(cameraStateMachine.StateMachineContext, container);
cameraStateMachine.Initialize();
var loadingScreen = GameObject.FindWithTag(Tags.LoadingScreen);
DontDestroyOnLoad(loadingScreen);
IUIService uiService = container.Resolve<IUIService>();
uiService.Initialize(loadingScreen.GetComponent<LoadingScreen>());
TryStartGame();
}
AttributeInjector.Inject(appStateMachine.StateMachineContext, container); private void TryStartGame()
{
// TODO: поменяно для теста // TODO: поменяно для теста
appStateMachine _appStateMachine
.SwitchState(AppState.MovementDev); .SwitchState(AppState.MainMenu)
.AllowStateTransition(AppState.Gameplay);
} }
} }
} }
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c7a271c6319540abb78d561f392e1396
timeCreated: 1761743570
@@ -0,0 +1,26 @@
using Code.Scenes;
using Code.Scenes.SceneEnvironments;
using Reflex.Core;
using UnityEngine;
namespace Code.App.SceneInstallers
{
public class MainMenuSceneInstaller : MonoBehaviour, IInstaller
{
[SerializeField] private MainMenuEnvironment _mainMenuEnvironment;
public void InstallBindings(ContainerBuilder containerBuilder)
{
containerBuilder.AddScoped(typeof(MainMenuEnvironment), typeof(CurrentSceneEnvironment));
containerBuilder.OnContainerBuilt += Initialize;
}
private void Initialize(Container container)
{
MainMenuEnvironment env = (MainMenuEnvironment)container.Resolve<CurrentSceneEnvironment>();
env.TransitVariables(_mainMenuEnvironment);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 04145a046c584b66818ca25745652e66
timeCreated: 1761743587
@@ -7,6 +7,7 @@ namespace Code.App.StateMachine
{ {
public override void Initialize() public override void Initialize()
{ {
States.Add(AppState.Bootstrap, new BootstrapState(StateMachineContext));
States.Add(AppState.MainMenu, new MainMenuState(StateMachineContext)); States.Add(AppState.MainMenu, new MainMenuState(StateMachineContext));
States.Add(AppState.Gameplay, new GameplayState(StateMachineContext)); States.Add(AppState.Gameplay, new GameplayState(StateMachineContext));
States.Add(AppState.GameOver, new GameOverState(StateMachineContext)); States.Add(AppState.GameOver, new GameOverState(StateMachineContext));
@@ -16,6 +17,8 @@ namespace Code.App.StateMachine
AllowedStates |= AppState.MainMenu; AllowedStates |= AppState.MainMenu;
AllowedStates |= AppState.Gameplay; AllowedStates |= AppState.Gameplay;
AllowedStates |= AppState.MovementDev; AllowedStates |= AppState.MovementDev;
CurrentBaseState = States[AppState.Bootstrap];
} }
protected override bool CheckTransitionAllowed(AppState nextState) => AllowedStates.HasFlag(nextState); protected override bool CheckTransitionAllowed(AppState nextState) => AllowedStates.HasFlag(nextState);
@@ -1,12 +1,19 @@
using Code.Gameplay.Camera;
using Code.Gameplay.Player;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.UI;
using Reflex.Attributes; using Reflex.Attributes;
namespace Code.App.StateMachine namespace Code.App.StateMachine
{ {
public class AppStateMachineContext public class AppStateMachineContext
{ {
[Inject] public ISceneLoaderService SceneLoaderService; [Inject] public ISceneLoaderService SceneLoaderService { get; private set; }
[Inject] public IInputService InputService; [Inject] public IInputService InputService { get; private set; }
[Inject] public AppStateMachine StateMachine { get; private set; }
[Inject] public CameraStateMachine CameraStateMachine { get; private set; }
[Inject] public IUIService UIService { get; private set; }
[Inject] public IPlayerFactory PlayerFactory { get; private set; }
} }
} }
@@ -2,7 +2,7 @@ using UnityEngine;
namespace Code.Configs namespace Code.Configs
{ {
[CreateAssetMenu(menuName = "Config/AppConfig", fileName = "AppConfig")] [CreateAssetMenu(menuName = "Config/App", fileName = "AppConfig")]
public class AppConfig : ScriptableObject public class AppConfig : ScriptableObject
{ {
public GameObject infrastructureUnitPrefab; public GameObject infrastructureUnitPrefab;
@@ -1,10 +1,10 @@
using Code.Gameplay.Character.Core;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs namespace Code.Configs
{ {
[CreateAssetMenu(fileName = "CharacterCameraConfig", menuName = "Config/CharacterCameraConfig")] [CreateAssetMenu(fileName = "CameraConfig", menuName = "Config/CameraConfig")]
public class CharacterCameraConfig : ScriptableObject public class CameraConfig : ScriptableObject
{ {
public float public float
// Расстояния от персонажа до камеры // Расстояния от персонажа до камеры
@@ -29,6 +29,10 @@ namespace Code.Configs
maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх) maxVerticalAngle = 70f, // Максимальный угол наклона камеры (вверх)
defaultVerticalAngle = 10f; // Стандартный угол наклона камеры defaultVerticalAngle = 10f; // Стандартный угол наклона камеры
public CameraMode defaultCameraMode = CameraMode.MainMenu; // Текущий режим камеры // Позиция следования по умолчанию
public Vector3 defaultFollowPosition = Vector3.zero;
// Префаб камеры
public Camera cameraPrefab;
} }
} }
@@ -0,0 +1,41 @@
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(fileName = "CharacterConfig", menuName = "Config/CharacterConfig")]
public class CharacterConfig : ScriptableObject
{
// Основное движение по земле
public float
maxStableMoveSpeed = 8f, // Максимальная скорость движения по устойчивой поверхности
stableMovementSharpness = 15f, // Резкость изменения скорости движения (чем выше - тем резче)
orientationSharpness = 10f, // Резкость изменения вращения персонажа (чем выше - тем резче)
jumpSpeed = 10f; // Скорость прыжка
// Спринт
public float
sprintMoveSpeed = 12f, // Максимальная скорость спринта
sprintAngleThreshold = 90f; // Максимальный угол (в градусах) от направления вперёд, при котором разрешён спринт
// Слайд
public float
slideSpeed = 20f, // Скорость слайда
slideDuration = 0.8f, // Длительность слайда в секундах
slideCooldown = 1.5f; // Время перезарядки слайда в секундах
// Залазание на стены
public float
wallClimbSpeedFactor = 0.8f, // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену
minWallClimbSpeed = 9f, // Минимальная горизонтальная скорость для начала залазания на стену
wallDetectionAngleThreshold = 80f; // Максимальный угол (в градусах) от вертикали, при котором поверхность считается "стеной"
// Наклон при залазании на стены
public float
maxWallLeanAngle = 45f, // Максимальный угол наклона от стены (в градусах)
wallLeanSharpness = 50f, // Скорость наклона/выпрямления (чем выше — тем резче)
wallLeanHeightScale = 0.1f; // Множитель: на сколько метров подъёма = 1 единица "наклона"
// Гравитация
public Vector3 gravity = new(0f, -30f, 0f); // Вектор, отвечающий за применение гравитации
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 10cfaf8e57f64eddb8c2eda147e4739a
timeCreated: 1761723608
@@ -0,0 +1,10 @@
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Player", fileName = "PlayerConfig")]
public class PlayerConfig : ScriptableObject
{
public int maxHealth;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 425c2ad28dbe4552acd1564f64372558
timeCreated: 1761591274
@@ -0,0 +1,12 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/UI", fileName = "UIConfig")]
public class UIConfig : ScriptableObject
{
public AssetReference[] uiPanelsAssetReferences;
public AssetReference canvasAssetReference;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: feaa36c74e094e0aa24b3983957a6e4e
timeCreated: 1761591380
@@ -6,4 +6,8 @@ namespace Code.Constants
public const string ConfigMultiple = "Configs/{0}"; public const string ConfigMultiple = "Configs/{0}";
} }
public static class Tags
{
public const string LoadingScreen = "LoadingScreen";
}
} }
+6 -1
View File
@@ -1,3 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: e4901b413afd4d7da914edf2a1e51f8f guid: e4901b413afd4d7da914edf2a1e51f8f
timeCreated: 1760627936 folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0fae52abe6b44ad5bdd6e67876fe940a
timeCreated: 1761671702
@@ -1,7 +1,8 @@
namespace Code.Gameplay.Character.Core namespace Code.Gameplay.Camera
{ {
/* /*
* Перечисление режимов работы камеры * Перечисление режимов работы камеры
* MainMenu - статичная камера, смотрит на заданную позицию (то что надо в главном меню)
* Static - статичная камера без следования за целью * Static - статичная камера без следования за целью
* FirstPerson - камера от первого лица (встроена в модель персонажа) * FirstPerson - камера от первого лица (встроена в модель персонажа)
* ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии) * ThirdPerson - камера от третьего лица (следует за персонажем на расстоянии)
@@ -9,7 +10,7 @@ namespace Code.Gameplay.Character.Core
*/ */
public enum CameraMode public enum CameraMode
{ {
MainMenu, Bogdan,
Static, Static,
FirstPerson, FirstPerson,
ThirdPerson, ThirdPerson,
@@ -0,0 +1,25 @@
using Code.Configs;
using Code.Gameplay.Camera.CameraStates;
using Code.StateMachine;
namespace Code.Gameplay.Camera
{
public class CameraStateMachine : AbstractStateMachine<CameraMode, CameraStateMachineContext>
{
// Текущий режим работы камеры
public override void Initialize()
{
States.Add(CameraMode.Static, new StaticState(StateMachineContext));
States.Add(CameraMode.FirstPerson, new FirstPersonState(StateMachineContext));
States.Add(CameraMode.ThirdPerson, new ThirdPersonState(StateMachineContext));
CurrentBaseState = States[CameraMode.Static];
}
protected override bool CheckTransitionAllowed(CameraMode nextState) => true;
protected override void ResetAllowedTransitions()
{ }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b220b02510c34ea08257cc951d949fd7
timeCreated: 1761671720
@@ -0,0 +1,13 @@
using Code.Configs;
using Code.Input;
using Reflex.Attributes;
namespace Code.Gameplay.Camera
{
public class CameraStateMachineContext
{
[Inject] public UnityEngine.Camera Camera;
[Inject] public CameraConfig Config;
[Inject] public IInputService InputService;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f18a74bdb8947bebec56115659e5af3
timeCreated: 1761671752
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d7399e433dff42a185f62d79b546b059
timeCreated: 1761671908
@@ -0,0 +1,152 @@
using Code.Configs;
using Code.Input;
using Code.StateMachine;
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
public abstract class BaseCameraState : BaseState<CameraStateMachineContext>
{
// DI сервиса ввода
private readonly IInputService _inputService;
// Конфиг камеры
private readonly CameraConfig _config;
// Используемая камера
protected UnityEngine.Camera Camera { get; private set; }
// Трансформ объекта, за которым следует камера
protected Transform FollowTransform;
// Позиция следования: текущая сглаженная позиция и горизонтальное направление камеры
protected Vector3 CurrentFollowPosition;
// Fallback-позиция для режимов без цели
private Vector3 _defaultFollowPosition;
// Целевой вертикальный угол камеры (наклон вверх/вниз)
private float _targetVerticalAngle;
// Текущая и целевая дистанция камеры от цели
protected float CurrentDistance, TargetDistance;
// Исходные значения дистанции для восстановления
protected float OriginalDefaultDistance, OriginalMinDistance, OriginalMaxDistance;
// Горизонтальное направление камеры (проекция forward на плоскость)
private Vector3 PlanarDirection { get; set; }
// Параметры загружаются из CameraConfig
protected float
DefaultDistance,
MinDistance,
MaxDistance,
DistanceMovementSpeed,
RotationSpeed,
RotationSharpness,
FollowSharpness,
MinVerticalAngle,
MaxVerticalAngle,
DefaultVerticalAngle;
// Свойства для доступа к важным данным камеры извне
public Vector3 GetPlanarDirection() => PlanarDirection; // Горизонтальное направление камеры
public Quaternion GetCameraRotation() => Camera.transform.rotation; // Текущее вращение камеры
// Конструктор базового состояния
protected BaseCameraState(CameraStateMachineContext context) : base(context)
{
_inputService = context.InputService;
_config = context.Config;
LoadConfig();
}
// Загрузка конфига для камеры
private void LoadConfig()
{
// Загрузка значений из конфигов
DefaultDistance = _config.defaultDistance;
MinDistance = _config.minDistance;
MaxDistance = _config.maxDistance;
DistanceMovementSpeed = _config.distanceMovementSpeed;
RotationSpeed = _config.rotationSpeed;
RotationSharpness = _config.rotationSharpness;
FollowSharpness = _config.followSharpness;
MinVerticalAngle = _config.minVerticalAngle;
MaxVerticalAngle = _config.maxVerticalAngle;
DefaultVerticalAngle = _config.defaultVerticalAngle;
_defaultFollowPosition = _config.defaultFollowPosition;
// Обеспечиваем корректные значения дистанции и угла
DefaultDistance = Mathf.Clamp(DefaultDistance, MinDistance, MaxDistance);
DefaultVerticalAngle = Mathf.Clamp(DefaultVerticalAngle, MinVerticalAngle, MaxVerticalAngle);
// Сохраняем оригинальные значения для восстановления
OriginalDefaultDistance = DefaultDistance;
OriginalMinDistance = MinDistance;
OriginalMaxDistance = MaxDistance;
CurrentDistance = DefaultDistance;
TargetDistance = CurrentDistance;
_targetVerticalAngle = DefaultVerticalAngle;
PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z
CurrentFollowPosition = _defaultFollowPosition;
}
// Переход в состояние
public override void EnterState()
{
Camera = Context.Camera;
}
public override void LateTick()
{
// Обновляем камеру только если есть цель для следования
if (FollowTransform)
{
UpdateWithInput(Time.deltaTime, _inputService.ZoomInput, _inputService.LookInput);
}
}
// Абстрактные методы для специфики режима
protected abstract void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation);
// Обработка вращения камеры на основе пользовательского ввода
// Теперь виртуальный, чтобы статический режим мог работать как хочет
protected virtual void HandleRotationInput(float deltaTime, Vector3 rotationInput, out Quaternion targetRotation)
{
// Вращение вокруг вертикальной оси (горизонтальный поворот)
var rotationFromInput = Quaternion.Euler(FollowTransform.up * (rotationInput.x * RotationSpeed));
PlanarDirection = rotationFromInput * PlanarDirection;
var planarRotation = Quaternion.LookRotation(PlanarDirection, FollowTransform.up);
// Вращение вокруг горизонтальной оси (вертикальный наклон)
_targetVerticalAngle -= rotationInput.y * RotationSpeed;
_targetVerticalAngle = Mathf.Clamp(_targetVerticalAngle, MinVerticalAngle, MaxVerticalAngle);
var verticalRotation = Quaternion.Euler(_targetVerticalAngle, 0, 0);
// Интерполяция к целевому вращению для плавности
targetRotation = Quaternion.Slerp(Camera.transform.rotation, planarRotation * verticalRotation, deltaTime * RotationSharpness);
// Применение финального вращения
Camera.transform.rotation = targetRotation;
}
// Основной метод обновления камеры, вызываемый извне каждый кадр
private void UpdateWithInput(float deltaTime, float zoomInput, Vector3 rotationInput)
{
// Обработка ввода и позиционирования
HandleRotationInput(deltaTime, rotationInput, out var targetRotation);
HandlePosition(deltaTime, zoomInput, targetRotation);
}
// Установка цели для следования камеры
public void SetFollowTransform(Transform t)
{
FollowTransform = t;
CurrentFollowPosition = t ? t.position : _defaultFollowPosition;
if (t) PlanarDirection = t.forward; // Начальное направление совпадает с направлением цели
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 92ff312b40a647b392af24877e699bd5
timeCreated: 1761685916
@@ -0,0 +1,43 @@
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
/*
* Состояние камеры от первого лица
* Камера размещается непосредственно на позиции цели (например, на уровне глаз персонажа)
* Дистанция до цели принудительно устанавливается в 0
* Вращение управляется мышью, позиция следует за целью без отступа
*/
public class FirstPersonState : BaseCameraState, ICameraState
{
public FirstPersonState(CameraStateMachineContext context) : base(context) { }
public override void EnterState()
{
// В FirstPerson режиме дистанция всегда равна 0
MinDistance = 0f;
MaxDistance = 0f;
DefaultDistance = 0f;
TargetDistance = 0f;
CurrentDistance = 0f;
base.EnterState();
}
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
// Плавное следование за целью с использованием экспоненциального сглаживания
CurrentFollowPosition = Vector3.Lerp(CurrentFollowPosition, FollowTransform.position, 1f - Mathf.Exp(-FollowSharpness * deltaTime));
// В FirstPerson режиме камера находится прямо на позиции цели
var targetPosition = CurrentFollowPosition;
// Установка финальной позиции камеры
Camera.transform.position = targetPosition;
}
public CameraMode GetCameraMode()
{
return CameraMode.FirstPerson;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8bd4b9c56b1e435d8699160bc950994c
timeCreated: 1761672032
@@ -0,0 +1,7 @@
namespace Code.Gameplay.Camera.CameraStates
{
public interface ICameraState
{
CameraMode GetCameraMode();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f25670e547242e8a91e19894cf6889c
timeCreated: 1761687204
@@ -0,0 +1,30 @@
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
public class StaticState : BaseCameraState, ICameraState
{
public Transform LookTarget;
public StaticState(CameraStateMachineContext context) : base(context) { }
public override void EnterState()
{
Debug.Log("Static State Enter");
base.EnterState();
}
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
Camera.transform.position = FollowTransform.position;
}
public CameraMode GetCameraMode() => CameraMode.Static;
protected override void HandleRotationInput(float deltaTime, Vector3 rotationInput,
out Quaternion targetRotation)
{
if(LookTarget) Camera.transform.LookAt(LookTarget);
targetRotation = Camera.transform.rotation;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ddbcc6a073b744a08ce31c0607c1e9e9
timeCreated: 1761671944
@@ -0,0 +1,58 @@
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
/*
* Состояние камеры от третьего лица
* Камера следует за персонажем на заданном расстоянии
* Поддерживает зум, плавное следование и вращение с учётом угла наклона
* Дистанция и углы настраиваются через конфигурацию
*/
public class ThirdPersonState : BaseCameraState, ICameraState
{
public ThirdPersonState(CameraStateMachineContext context) : base(context) { }
public override void EnterState()
{
// Восстанавливаем исходные значения дистанции
MinDistance = OriginalMinDistance;
MaxDistance = OriginalMaxDistance;
DefaultDistance = OriginalDefaultDistance;
TargetDistance = DefaultDistance;
base.EnterState();
}
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
// Обновление целевой дистанции на основе ввода зума
TargetDistance += zoomInput * DistanceMovementSpeed;
TargetDistance = Mathf.Clamp(TargetDistance, MinDistance, MaxDistance);
// Плавное следование за целью с использованием экспоненциального сглаживания
CurrentFollowPosition = Vector3.Lerp(
CurrentFollowPosition,
FollowTransform.position,
1f - Mathf.Exp(-FollowSharpness * deltaTime)
);
// В ThirdPerson режиме камера отодвинута на расстояние от цели
var targetPosition = CurrentFollowPosition - targetRotation * Vector3.forward * CurrentDistance;
// Плавное изменение текущей дистанции к целевой
CurrentDistance = Mathf.Lerp(
CurrentDistance,
TargetDistance,
1f - Mathf.Exp(-DistanceMovementSpeed * deltaTime)
);
// Установка финальной позиции камеры
Camera.transform.position = targetPosition;
}
public CameraMode GetCameraMode()
{
return CameraMode.ThirdPerson;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ec6778ceb9854ff8af7cba4f6285c370
timeCreated: 1761672071
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b36ec6a31fb74d608232394fd5df657c
timeCreated: 1761570832
@@ -1,223 +0,0 @@
using System;
using Code.Gameplay.Character.Core;
using Code.StaticData;
using Reflex.Attributes;
using UnityEngine;
namespace Code.Gameplay.Character.Camera
{
/*
* Скрипт, отвечающий за поведение камеры при передвижении
* Поддерживает как 3rd-person, так и 1st-person режимы камеры
* Реализует плавное следование за персонажем, вращение и зум камеры
*
* Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM
*/
public class CharacterCamera : MonoBehaviour
{
// Значения теперь берутся из конфига
private float
// Расстояния от персонажа до камеры
_defaultDistance, // Стандартное расстояние камеры от цели
_minDistance, // Минимальное расстояние при зуме
_maxDistance, // Максимальное расстояние при зуме
// Скорость зума камеры
_distanceMovementSpeed, // Множитель скорости изменения дистанции при зуме
// Скорость поворота камеры
_rotationSpeed, // Множитель скорости вращения камеры
// Скорость интерполяции текущее -> целевое вращение
_rotationSharpness, // Коэффициент плавности вращения камеры (чем выше - тем резче)
// Резкость следования камеры за целью
_followSharpness, // Коэффициент плавности следования за движущейся целью (чем выше - тем резче)
// Углы наклона камеры
_minVerticalAngle, // Минимальный угол наклона камеры (вниз)
_maxVerticalAngle, // Максимальный угол наклона камеры (вверх)
_defaultVerticalAngle; // Стандартный угол наклона камеры
// Трансформ объекта, за которым следует камера (для 1st-person: уровень глаз)
private Transform _followTransform;
// Позиция следования: текущая сглаженная позиция и горизонтальное направление камеры
private Vector3 _currentFollowPosition;
// Позиция следования по умолчанию (для меню)
private readonly Vector3 _defaultFollowPosition = Vector3.zero;
// Целевой вертикальный угол камеры (наклон вверх/вниз)
private float _targetVerticalAngle;
// Текущая и целевая дистанция камеры от цели
private float _currentDistance, _targetDistance;
// Исходные значения дистанции для восстановления после FirstPerson режима
private float _originalDefaultDistance, _originalMinDistance, _originalMaxDistance;
// Свойства для доступа к важным данным камеры извне
public Vector3 PlanarDirection { get; private set; } // Горизонтальное направление камеры
public CameraMode CurrentMode { get; private set; }
[Inject] private IStaticDataService _staticDataService;
// Загрузка конфига для камеры
private void LoadConfig()
{
var config = _staticDataService.CharacterCameraConfig;
// Загрузка значений из конфигов
_defaultDistance = config.defaultDistance;
_minDistance = config.minDistance;
_maxDistance = config.maxDistance;
_distanceMovementSpeed = config.distanceMovementSpeed;
_rotationSpeed = config.rotationSpeed;
_rotationSharpness = config.rotationSharpness;
_followSharpness = config.followSharpness;
_minVerticalAngle = config.minVerticalAngle;
_maxVerticalAngle = config.maxVerticalAngle;
_defaultVerticalAngle = config.defaultVerticalAngle;
CurrentMode = config.defaultCameraMode;
// Обеспечиваем корректные значения дистанции и угла
_defaultDistance = Mathf.Clamp(_defaultDistance, _minDistance, _maxDistance);
_defaultVerticalAngle = Mathf.Clamp(_defaultVerticalAngle, _minVerticalAngle, _maxVerticalAngle);
// Сохраняем оригинальные значения для восстановления
_originalDefaultDistance = _defaultDistance;
_originalMinDistance = _minDistance;
_originalMaxDistance = _maxDistance;
_currentDistance = _defaultDistance;
_targetDistance = _currentDistance;
_targetVerticalAngle = _defaultVerticalAngle;
PlanarDirection = Vector3.forward; // Начальное направление - вперед по оси Z
_currentFollowPosition = _defaultFollowPosition;
}
// Инициализация камеры при создании
private void Awake()
{
LoadConfig();
}
// Установка цели для следования камеры
public void SetFollowTransform(Transform t)
{
_followTransform = t;
_currentFollowPosition = t.position;
PlanarDirection = t.forward; // Начальное направление совпадает с направлением цели
}
// Обработка вращения камеры на основе пользовательского ввода
private void HandleRotationInput(float deltaTime, Vector3 rotationInput, out Quaternion targetRotation)
{
// Вращение вокруг вертикальной оси (горизонтальный поворот)
var rotationFromInput = Quaternion.Euler(_followTransform.up * (rotationInput.x * _rotationSpeed));
PlanarDirection = rotationFromInput * PlanarDirection;
var planarRotation = Quaternion.LookRotation(PlanarDirection, _followTransform.up);
// Вращение вокруг горизонтальной оси (вертикальный наклон)
_targetVerticalAngle -= rotationInput.y * _rotationSpeed;
_targetVerticalAngle = Math.Clamp(_targetVerticalAngle, _minVerticalAngle, _maxVerticalAngle);
var verticalRotation = Quaternion.Euler(_targetVerticalAngle, 0, 0);
// Интерполяция к целевому вращению для плавности
targetRotation = Quaternion.Slerp(transform.rotation, planarRotation * verticalRotation, deltaTime * _rotationSharpness);
// Применение финального вращения
transform.rotation = targetRotation;
}
// Обработка позиционирования камеры и зума с учетом текущего режима
private void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
// В FirstPerson режиме зум отключается
if (CurrentMode != CameraMode.FirstPerson)
{
// Обновление целевой дистанции на основе ввода зума
_targetDistance += zoomInput * _distanceMovementSpeed;
_targetDistance = Mathf.Clamp(_targetDistance, _minDistance, _maxDistance);
}
// Плавное следование за целью с использованием экспоненциального сглаживания
_currentFollowPosition = Vector3.Lerp(_currentFollowPosition, _followTransform.position, 1f - Mathf.Exp(-_followSharpness * deltaTime));
// Вычисление целевой позиции камеры с учетом текущего режима
Vector3 targetPosition;
if (CurrentMode == CameraMode.FirstPerson)
{
// В FirstPerson режиме камера находится прямо на позиции цели
targetPosition = _currentFollowPosition;
}
else
{
// В ThirdPerson режиме камера отодвинута на расстояние от цели
targetPosition = _currentFollowPosition - targetRotation * Vector3.forward * _currentDistance;
}
// Плавное изменение текущей дистанции к целевой (только для ThirdPerson)
if (CurrentMode != CameraMode.FirstPerson)
{
_currentDistance = Mathf.Lerp(_currentDistance, _targetDistance, 1f - Mathf.Exp(-_distanceMovementSpeed * deltaTime));
}
// Установка финальной позиции камеры
transform.position = targetPosition;
}
// Основной метод обновления камеры, вызываемый извне каждый кадр
public void UpdateWithInput(float deltaTime, float zoomInput, Vector3 rotationInput)
{
// Проверка наличия цели для следования и проверка на режим главного меню
if (!_followTransform || CurrentMode == CameraMode.MainMenu) return;
// Обработка ввода и позиционирования
HandleRotationInput(deltaTime, rotationInput, out var targetRotation);
HandlePosition(deltaTime, zoomInput, targetRotation);
}
// Смена режима камеры
public void SetCameraMode(CameraMode newMode)
{
if (CurrentMode == newMode) return;
CurrentMode = newMode;
// Применяем настройки специфичные для режима
ApplyModeSpecificSettings();
}
// Применение настроек специфичных для текущего режима
private void ApplyModeSpecificSettings()
{
switch (CurrentMode)
{
case CameraMode.FirstPerson:
// В FirstPerson режиме дистанция всегда равна 0
_minDistance = 0f;
_maxDistance = 0f;
_defaultDistance = 0f;
_targetDistance = 0f;
_currentDistance = 0f;
break;
case CameraMode.ThirdPerson:
// Восстанавливаем исходные значения дистанции
_minDistance = _originalMinDistance;
_maxDistance = _originalMaxDistance;
_defaultDistance = _originalDefaultDistance;
_targetDistance = _defaultDistance;
break;
// Заготовка на будущее (если понадобится)
case CameraMode.MainMenu:
case CameraMode.Static:
case CameraMode.Cinematic:
default:
break;
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f1d98e6c5419411098391c07861e627e
timeCreated: 1760627986
@@ -1,6 +1,6 @@
using Code.Gameplay.Camera;
using KinematicCharacterController; using KinematicCharacterController;
using UnityEngine; using UnityEngine;
using Code.Gameplay.Character.Core;
namespace Code.Gameplay.Character.Controller.Abilities namespace Code.Gameplay.Character.Controller.Abilities
{ {
@@ -13,12 +13,16 @@ namespace Code.Gameplay.Character.Controller.Abilities
private readonly float _wallClimbSpeedFactor; private readonly float _wallClimbSpeedFactor;
// Минимальная горизонтальная скорость для начала залазания на стену // Минимальная горизонтальная скорость для начала залазания на стену
private readonly float _minWallClimbSpeed; private readonly float _minWallClimbSpeed;
// Пороговый косинус стены
public float WallNormalThreshold { get; }
// Конструктор механики залазания на стену // Конструктор механики залазания на стену
public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed) public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold)
{ {
_wallClimbSpeedFactor = wallClimbSpeedFactor; _wallClimbSpeedFactor = wallClimbSpeedFactor;
_minWallClimbSpeed = minWallClimbSpeed; _minWallClimbSpeed = minWallClimbSpeed;
// Преобразуем угол в косинус порога: если |dot| > cos(threshold), то это НЕ стена
WallNormalThreshold = Mathf.Cos(Mathf.Deg2Rad * wallDetectionAngleThreshold);
} }
// Обработка столкновения при движении (вызывается при ударе о препятствие) // Обработка столкновения при движении (вызывается при ударе о препятствие)
@@ -26,7 +30,7 @@ namespace Code.Gameplay.Character.Controller.Abilities
{ {
// Проверяем, что это вертикальная или почти вертикальная поверхность (стена) // Проверяем, что это вертикальная или почти вертикальная поверхность (стена)
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp); var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
if (Mathf.Abs(dotWithUp) > 0.1f) return; // Не стена — это пол или потолок if (Mathf.Abs(dotWithUp) > WallNormalThreshold) return; // Не стена — это пол или потолок
// Получаем текущую скорость персонажа // Получаем текущую скорость персонажа
var currentVelocity = motor.Velocity; var currentVelocity = motor.Velocity;
@@ -0,0 +1,79 @@
using UnityEngine;
using KinematicCharacterController;
namespace Code.Gameplay.Character.Controller.Abilities
{
/*
* Механика наклона персонажа при контакте со стеной
* Управляет визуальным наклоном модели в зависимости от высоты подъёма по стене
*/
public class WallLeanMechanic
{
private readonly float _maxLeanAngle;
private readonly float _leanSharpness;
private readonly float _heightScale;
private Transform _characterTransform;
private Vector3 _lastWallNormal;
private float _lastWallContactHeight;
private float _leanFactor;
public WallLeanMechanic(float maxLeanAngle, float leanSharpness, float heightScale)
{
_maxLeanAngle = maxLeanAngle;
_leanSharpness = leanSharpness;
_heightScale = heightScale;
}
// Назначаем трансформ визуальной модели один раз
public void SetCharacterTransform(Transform transform)
{
_characterTransform = transform;
}
// Вызывается из OnMovementHit
public void OnWallContact(Vector3 wallNormal, float contactHeight)
{
_lastWallNormal = wallNormal;
_lastWallContactHeight = contactHeight; }
// Обновление каждый кадр
public void UpdateLean(KinematicCharacterMotor motor, float deltaTime)
{
if (!_characterTransform) return;
var isGrounded = motor.GroundingStatus.IsStableOnGround;
if (!isGrounded)
{
var heightDelta = motor.TransientPosition.y - _lastWallContactHeight;
var targetLeanFactor = Mathf.Clamp01(heightDelta * _heightScale);
_leanFactor = Mathf.Lerp(_leanFactor, targetLeanFactor, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
}
else
{
_leanFactor = Mathf.Lerp(_leanFactor, 0f, 1f - Mathf.Exp(-_leanSharpness * deltaTime));
}
ApplyLeanRotation(motor);
}
private void ApplyLeanRotation(KinematicCharacterMotor motor)
{
if (_leanFactor <= 0.01f)
{
_characterTransform.localRotation = Quaternion.identity;
return;
}
var leanDirection = -_lastWallNormal;
leanDirection = Vector3.ProjectOnPlane(leanDirection, motor.CharacterUp).normalized;
if (leanDirection.sqrMagnitude < 0.01f) return;
var leanAngle = _leanFactor * _maxLeanAngle;
var leanRotation = Quaternion.AngleAxis(leanAngle, Vector3.Cross(motor.CharacterUp, leanDirection));
_characterTransform.localRotation = leanRotation;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dfaefcb035124edb84b7874c5938defc
timeCreated: 1761738483
@@ -1,8 +1,11 @@
using Code.Configs;
using Code.Gameplay.Camera;
using Code.Gameplay.Character.Controller.Abilities; using Code.Gameplay.Character.Controller.Abilities;
using Code.Gameplay.Character.Controller.Input; using Code.Gameplay.Character.Controller.Input;
using Code.Gameplay.Character.Controller.Movement; using Code.Gameplay.Character.Controller.Movement;
using Code.Gameplay.Character.Core; using Code.Gameplay.Character.Core;
using KinematicCharacterController; using KinematicCharacterController;
using Reflex.Attributes;
using UnityEngine; using UnityEngine;
namespace Code.Gameplay.Character.Controller namespace Code.Gameplay.Character.Controller
@@ -16,50 +19,61 @@ namespace Code.Gameplay.Character.Controller
*/ */
public class CustomCharacterController : MonoBehaviour, ICharacterController public class CustomCharacterController : MonoBehaviour, ICharacterController
{ {
[SerializeField] [Inject] private CharacterConfig _characterConfig; // Конфиг персонажа через DI
private KinematicCharacterMotor motor; // Движок физического перемещения персонажа
[SerializeField] [SerializeField] private KinematicCharacterMotor motor; // Движок физического перемещения персонажа
private float
maxStableMoveSpeed = 8f, // Максимальная скорость движения по устойчивой поверхности [SerializeField] private Transform characterTransform; // Трансформ персонажа (с точкой отслеживания камеры)
sprintMoveSpeed = 12f, // Максимальная скорость спринта
slideSpeed = 20f, // Скорость слайда
slideDuration = 0.8f, // Длительность слайда в секундах
slideCooldown = 1.5f, // Время перезарядки слайда
stableMovementSharpness = 15f, // Резкость изменения скорости движения (чем выше - тем резче)
orientationSharpness = 10f, // Резкость изменения вращения персонажа (чем выше - тем резче)
jumpSpeed = 10f, // Скорость прыжка
wallClimbSpeedFactor = 0.8f, // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену
minWallClimbSpeed = 9f; // Минимальная горизонтальная скорость для начала залазания на стену
[SerializeField]
private Vector3 gravity = new(0, -30f, 0); // Вектор, отвечающий за применение гравитации
// Подсистемы // Подсистемы
private readonly CharacterInputHandler _inputHandler; private CharacterInputHandler _inputHandler;
private readonly SlideMechanic _slideMechanic; private SlideMechanic _slideMechanic;
private readonly WallClimbMechanic _wallClimbMechanic; private WallClimbMechanic _wallClimbMechanic;
private readonly GroundMovementSystem _groundMovement; private WallLeanMechanic _wallLeanMechanic;
private readonly AirMovementSystem _airMovement; private GroundMovementSystem _groundMovement;
private AirMovementSystem _airMovement;
// Конструктор без параметров, инициализирующий контроллер // Инициализация контроллера
public CustomCharacterController() private void Awake()
{ {
_inputHandler = new CharacterInputHandler(); // Инициализация подсистем с параметрами из конфига
_slideMechanic = new SlideMechanic(slideSpeed, slideDuration, slideCooldown); _inputHandler = new CharacterInputHandler(
_wallClimbMechanic = new WallClimbMechanic(wallClimbSpeedFactor, minWallClimbSpeed); _characterConfig.sprintAngleThreshold
_groundMovement = new GroundMovementSystem(maxStableMoveSpeed, sprintMoveSpeed, stableMovementSharpness, jumpSpeed); );
_airMovement = new AirMovementSystem(gravity); _slideMechanic = new SlideMechanic(
_characterConfig.slideSpeed,
_characterConfig.slideDuration,
_characterConfig.slideCooldown
);
_wallClimbMechanic = new WallClimbMechanic(
_characterConfig.wallClimbSpeedFactor,
_characterConfig.minWallClimbSpeed,
_characterConfig.wallDetectionAngleThreshold
);
_wallLeanMechanic = new WallLeanMechanic(
_characterConfig.maxWallLeanAngle,
_characterConfig.wallLeanSharpness,
_characterConfig.wallLeanHeightScale
);
_groundMovement = new GroundMovementSystem(
_characterConfig.maxStableMoveSpeed,
_characterConfig.sprintMoveSpeed,
_characterConfig.stableMovementSharpness,
_characterConfig.jumpSpeed
);
_airMovement = new AirMovementSystem(_characterConfig.gravity);
} }
// Текущий режим камеры для адаптации поведения // Текущий режим камеры для адаптации поведения
private CameraMode _currentCameraMode = CameraMode.ThirdPerson; private CameraMode _currentCameraMode = CameraMode.FirstPerson;
// Инициализация контроллера // Инициализация контроллера
private void Start() private void Start()
{ {
motor.CharacterController = this; // Регистрация этого контроллера в движке motor.CharacterController = this; // Регистрация этого контроллера в движке
_inputHandler.FirstPersonCameraRotation = motor.Transform.rotation; // Начальное вращение совпадает с персонажем _inputHandler.FirstPersonCameraRotation =
motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
_wallLeanMechanic.SetCharacterTransform(characterTransform);
} }
// Обработка и преобразование входных данных от персонажа // Обработка и преобразование входных данных от персонажа
@@ -67,7 +81,7 @@ namespace Code.Gameplay.Character.Controller
{ {
// Обновляем таймер перезарядки слайда // Обновляем таймер перезарядки слайда
_slideMechanic.TickCooldown(Time.deltaTime); _slideMechanic.TickCooldown(Time.deltaTime);
// Если активен слайд - игнорируем весь остальной ввод // Если активен слайд - игнорируем весь остальной ввод
if (_slideMechanic.IsSliding) if (_slideMechanic.IsSliding)
{ {
@@ -78,16 +92,16 @@ namespace Code.Gameplay.Character.Controller
_groundMovement.RequestJump(inputs.JumpPressed); _groundMovement.RequestJump(inputs.JumpPressed);
return; return;
} }
// Сохраняем текущий режим камеры // Сохраняем текущий режим камеры
_currentCameraMode = inputs.CameraMode; _currentCameraMode = inputs.CameraMode;
// Обработка слайда // Обработка слайда
_slideMechanic.RequestSlide(inputs.SlidePressed); _slideMechanic.RequestSlide(inputs.SlidePressed);
// Обработка остального ввода // Обработка остального ввода
_inputHandler.ProcessInputs(ref inputs, motor); _inputHandler.ProcessInputs(ref inputs, motor);
// Передаём запрос прыжка напрямую в систему движения по земле // Передаём запрос прыжка напрямую в систему движения по земле
_groundMovement.RequestJump(inputs.JumpPressed); _groundMovement.RequestJump(inputs.JumpPressed);
} }
@@ -109,13 +123,13 @@ namespace Code.Gameplay.Character.Controller
case CameraMode.ThirdPerson: case CameraMode.ThirdPerson:
{ {
// Вращение применяется только если есть направление движения и разрешена резкость вращения // Вращение применяется только если есть направление движения и разрешена резкость вращения
if (_inputHandler.LookInputVector.sqrMagnitude > 0 && orientationSharpness > 0) if (_inputHandler.LookInputVector.sqrMagnitude > 0 && _characterConfig.orientationSharpness > 0)
{ {
// Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием // Плавная интерполяция текущего направления к целевому с экспоненциальным сглаживанием
var smoothedLookInputDirection = Vector3.Slerp( var smoothedLookInputDirection = Vector3.Slerp(
motor.CharacterForward, motor.CharacterForward,
_inputHandler.LookInputVector, _inputHandler.LookInputVector,
1f - Mathf.Exp(-orientationSharpness * deltaTime) 1f - Mathf.Exp(-_characterConfig.orientationSharpness * deltaTime)
).normalized; ).normalized;
// Установка нового вращения смотря в сглаженном направлении // Установка нового вращения смотря в сглаженном направлении
@@ -127,6 +141,7 @@ namespace Code.Gameplay.Character.Controller
// Заготовка на будущее (если понадобится) // Заготовка на будущее (если понадобится)
case CameraMode.Static: case CameraMode.Static:
case CameraMode.Cinematic: case CameraMode.Cinematic:
case CameraMode.Bogdan: // Скорее всего юзлес, но пускай будет
default: default:
break; break;
} }
@@ -140,11 +155,12 @@ namespace Code.Gameplay.Character.Controller
{ {
_slideMechanic.EndSlide(); _slideMechanic.EndSlide();
} }
// Проверяем запрос слайда и запускаем его если можно // Проверяем запрос слайда и запускаем его если можно
if (!_slideMechanic.IsSliding) if (!_slideMechanic.IsSliding)
{ {
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation, _inputHandler.LookInputVector); _slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation,
_inputHandler.LookInputVector);
} }
// Если активен слайд - обрабатываем отдельно // Если активен слайд - обрабатываем отдельно
else else
@@ -152,7 +168,7 @@ namespace Code.Gameplay.Character.Controller
_slideMechanic.HandleSlide(ref currentVelocity, deltaTime, motor); _slideMechanic.HandleSlide(ref currentVelocity, deltaTime, motor);
return; return;
} }
// Если персонаж находится на земле // Если персонаж находится на земле
if (motor.GroundingStatus.IsStableOnGround) if (motor.GroundingStatus.IsStableOnGround)
{ {
@@ -182,9 +198,10 @@ namespace Code.Gameplay.Character.Controller
} }
} }
// Метод вызывается после обновления персонажа (можно использовать для пост-обработки) // Метод вызывается после обновления персонажа
public void AfterCharacterUpdate(float deltaTime) public void AfterCharacterUpdate(float deltaTime)
{ {
_wallLeanMechanic.UpdateLean(motor, deltaTime);
} }
// Проверка валидности коллайдера для обработки столкновений // Проверка валидности коллайдера для обработки столкновений
@@ -194,7 +211,8 @@ namespace Code.Gameplay.Character.Controller
} }
// Обработка столкновения с землей (вызывается при приземлении) // Обработка столкновения с землей (вызывается при приземлении)
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, ref HitStabilityReport hitStabilityReport) public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
ref HitStabilityReport hitStabilityReport)
{ {
} }
@@ -203,10 +221,17 @@ namespace Code.Gameplay.Character.Controller
ref HitStabilityReport hitStabilityReport) ref HitStabilityReport hitStabilityReport)
{ {
_wallClimbMechanic.OnMovementHit(motor, hitNormal); _wallClimbMechanic.OnMovementHit(motor, hitNormal);
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
if (Mathf.Abs(dotWithUp) <= _wallClimbMechanic.WallNormalThreshold)
{
_wallLeanMechanic.OnWallContact(hitNormal, motor.TransientPosition.y);
}
} }
// Обработка отчета о стабильности столкновения // Обработка отчета о стабильности столкновения
public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, Vector3 atCharacterPosition, public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
Vector3 atCharacterPosition,
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{ {
} }
@@ -1,3 +1,4 @@
using Code.Gameplay.Camera;
using UnityEngine; using UnityEngine;
using Code.Gameplay.Character.Core; using Code.Gameplay.Character.Core;
using KinematicCharacterController; using KinematicCharacterController;
@@ -10,6 +11,9 @@ namespace Code.Gameplay.Character.Controller.Input
*/ */
public class CharacterInputHandler public class CharacterInputHandler
{ {
// Косинус порогового угла для спринта (предвычислен)
private readonly float _sprintAngleThresholdCos;
// Векторы движения и направления взгляда // Векторы движения и направления взгляда
public Vector3 MoveInputVector { get; set; } public Vector3 MoveInputVector { get; set; }
public Vector3 LookInputVector { get; set; } public Vector3 LookInputVector { get; set; }
@@ -22,7 +26,16 @@ namespace Code.Gameplay.Character.Controller.Input
// Флаг-свойство указывающий, что нужно применить вращение камеры к персонажу // Флаг-свойство указывающий, что нужно применить вращение камеры к персонажу
public bool ShouldApplyCameraRotation { get; private set; } public bool ShouldApplyCameraRotation { get; private set; }
// Конструктор с пороговым углом
public CharacterInputHandler(float sprintAngleThreshold)
{
// Предвычисляем косинус порогового угла для эффективности
// Угол в градусах → радианы → косинус
// Чем меньше угол — тем строже условие
_sprintAngleThresholdCos = Mathf.Cos(Mathf.Deg2Rad * sprintAngleThreshold);
}
// Обработка и преобразование входных данных от персонажа // Обработка и преобразование входных данных от персонажа
public void ProcessInputs(ref CharacterInputs inputs, KinematicCharacterMotor motor) public void ProcessInputs(ref CharacterInputs inputs, KinematicCharacterMotor motor)
{ {
@@ -30,7 +43,7 @@ namespace Code.Gameplay.Character.Controller.Input
var currentCameraMode = inputs.CameraMode; var currentCameraMode = inputs.CameraMode;
// Обработка спринта // Обработка спринта
IsSprinting = inputs is { SprintPressed: true, MoveAxisForward: > 0.1f }; // Спринт только при движении вперёд IsSprinting = inputs.SprintPressed && inputs.MoveAxisForward > _sprintAngleThresholdCos; // Спринт только при движении на заданный в конфиге угол
// Нормализация вектора движения и ограничение его величины до 1 // Нормализация вектора движения и ограничение его величины до 1
var moveInputVector = var moveInputVector =
@@ -1,4 +1,5 @@
using Code.Gameplay.Character.Camera; using Code.Gameplay.Camera;
using Code.Gameplay.Camera.CameraStates;
using Code.Gameplay.Character.Controller; using Code.Gameplay.Character.Controller;
using Code.Input; using Code.Input;
using Reflex.Attributes; using Reflex.Attributes;
@@ -14,7 +15,7 @@ namespace Code.Gameplay.Character.Core
*/ */
public class Character : MonoBehaviour public class Character : MonoBehaviour
{ {
[SerializeField] private CharacterCamera characterCamera; // Компонент камеры персонажа [Inject] private CameraStateMachine _cameraStateMachine; // Стейт-машина камеры (нужна для транзакций и т.п.)
[SerializeField] private CustomCharacterController customCharacterController; // Компонент управления персонажем [SerializeField] private CustomCharacterController customCharacterController; // Компонент управления персонажем
[SerializeField] private Transform cameraFollowPoint; // Точка следования камеры (уровень глаз для FirstPerson) [SerializeField] private Transform cameraFollowPoint; // Точка следования камеры (уровень глаз для FirstPerson)
@@ -27,24 +28,12 @@ namespace Code.Gameplay.Character.Core
private void Start() private void Start()
{ {
// Настройка камеры для следования за точкой камеры // Настройка камеры для следования за точкой камеры
characterCamera.SetFollowTransform(cameraFollowPoint); ((BaseCameraState)_cameraStateMachine.CurrentBaseState).SetFollowTransform(cameraFollowPoint);
// Включение Gameplay мапа ввода // Включение Gameplay мапа ввода
_inputService.EnableMap(ActionMappings.Gameplay); _inputService.EnableMap(ActionMappings.Gameplay);
} }
// Обработка ввода для камеры (вращение и зум)
private void HandleCameraInput()
{
// Получаем данные ввода из Input System
var lookInput = _inputService.LookInput;
var scrollInput = _inputService.ZoomInput;
// Передаем данные в камеру
characterCamera.UpdateWithInput(Time.deltaTime, scrollInput,
new Vector3(lookInput.x, lookInput.y, 0));
}
// Обработка ввода для движения персонажа // Обработка ввода для движения персонажа
private void HandleCharacterInput() private void HandleCharacterInput()
{ {
@@ -58,11 +47,11 @@ namespace Code.Gameplay.Character.Core
inputs.JumpPressed = _inputService.JumpPressed; // Прыжки (Space) inputs.JumpPressed = _inputService.JumpPressed; // Прыжки (Space)
inputs.SprintPressed = _inputService.SprintPressed; // Спринт (Shift) inputs.SprintPressed = _inputService.SprintPressed; // Спринт (Shift)
inputs.SlidePressed = _inputService.SlidePressed; // Слайд (Ctrl) inputs.SlidePressed = _inputService.SlidePressed; // Слайд (Ctrl)
inputs.CameraMode = ((ICameraState)_cameraStateMachine.CurrentBaseState).GetCameraMode();
// Передаем информацию о камере для адаптивного поведения // Передаем информацию о камере для адаптивного поведения
inputs.CameraRotation = characterCamera.transform.rotation; inputs.CameraRotation = ((BaseCameraState)_cameraStateMachine.CurrentBaseState).GetCameraRotation();
inputs.CameraMode = characterCamera.CurrentMode; inputs.CameraPlanarDirection = ((BaseCameraState)_cameraStateMachine.CurrentBaseState).GetPlanarDirection();
inputs.CameraPlanarDirection = characterCamera.PlanarDirection;
// Передаем ввод в контроллер персонажа // Передаем ввод в контроллер персонажа
customCharacterController.SetInputs(ref inputs); customCharacterController.SetInputs(ref inputs);
@@ -87,16 +76,18 @@ namespace Code.Gameplay.Character.Core
break; break;
} }
} }
// Метод для переключения режимов камеры // Метод для переключения режимов камеры
private void ToggleCameraMode() private void ToggleCameraMode()
{ {
var currentMode = characterCamera.CurrentMode; var currentMode = ((ICameraState)_cameraStateMachine.CurrentBaseState).GetCameraMode();
var newMode = currentMode == CameraMode.FirstPerson ? CameraMode.ThirdPerson : CameraMode.FirstPerson; var newMode = currentMode == CameraMode.FirstPerson ? CameraMode.ThirdPerson : CameraMode.FirstPerson;
characterCamera.SetCameraMode(newMode); _cameraStateMachine.SwitchState(newMode);
((BaseCameraState)_cameraStateMachine.CurrentBaseState).SetFollowTransform(cameraFollowPoint);
} }
// Обновление логики каждый кадр // Обновление логики каждый кадр
private void Update() private void Update()
{ {
@@ -106,12 +97,5 @@ namespace Code.Gameplay.Character.Core
// Обработка переключения режима камеры // Обработка переключения режима камеры
HandleCameraModeInput(); HandleCameraModeInput();
} }
// Обновление после всех вычислений (идеально для камеры)
private void LateUpdate()
{
// Обработка ввода для камеры в LateUpdate (после движения персонажа)
HandleCameraInput();
}
} }
} }
@@ -1,3 +1,4 @@
using Code.Gameplay.Camera;
using UnityEngine; using UnityEngine;
namespace Code.Gameplay.Character.Core namespace Code.Gameplay.Character.Core
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58441fe5bc46071fab2546105689c70b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
namespace Code.Gameplay.Player
{
public interface IPlayerFactory
{
PlayerHealth CreatePlayerHealth(int count = -1);
PlayerMoney CreatePlayerMoney(int count = 0);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6d2689e8fb7e48a4a8a62a97d292e1e3
timeCreated: 1761761977
@@ -0,0 +1,19 @@
using UnityEngine;
using Code.Configs;
using Reflex.Attributes;
namespace Code.Gameplay.Player
{
public class PlayerFactory : IPlayerFactory
{
[Inject] PlayerConfig _playerConfig;
public PlayerHealth CreatePlayerHealth(int count = -1)
{
if (count < 0) count = _playerConfig.maxHealth;
return new(count);
}
public PlayerMoney CreatePlayerMoney(int count = 0) => new(count);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77dd77e7e8714974a7c8bc83772e52fa
timeCreated: 1761060390
@@ -0,0 +1,32 @@
namespace Code.Gameplay.Player
{
public class PlayerHealth
{
public delegate void OnDeathDelegate();
public event OnDeathDelegate OnDeath;
public delegate void OnDamageDelegate(int damage, PlayerHealth health);
public event OnDamageDelegate OnDamage;
private int _healthPoints;
public int HealthPoints => _healthPoints;
public int MaxHealthPoints { get; private set; }
public void GetDamage(int count)
{
_healthPoints -= count;
OnDamage?.Invoke(count, this);
if (_healthPoints <= 0) OnDeath?.Invoke();
}
public PlayerHealth(int maxHealthPoints = 1)
{
_healthPoints = maxHealthPoints;
MaxHealthPoints = maxHealthPoints;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3873d746bf1595618802b853c6920616
@@ -0,0 +1,26 @@
namespace Code.Gameplay.Player
{
public class PlayerMoney
{
public delegate void OnMoneyGetDelegate(int count, int money);
public event OnMoneyGetDelegate OnMoneyGet;
public delegate void OnMoneyLooseDelegate(int count, int money);
public event OnMoneyLooseDelegate OnMoneyLoose;
private int _money;
public int Money => _money;
public void GiveMoney(int amount)
{
_money += amount;
OnMoneyGet?.Invoke(amount, _money);
}
public void LooseMoney(int amount)
{
_money -= amount;
OnMoneyLoose?.Invoke(amount, _money);
}
public PlayerMoney(int money) => _money = money;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 455b08dd38eb4f4f8ab62b9493cd5e12
timeCreated: 1761760097
@@ -4,6 +4,7 @@ namespace Code.Input
{ {
MainMenu, MainMenu,
UI, UI,
Gameplay Gameplay,
Debug
} }
} }
@@ -6,4 +6,11 @@ namespace Code.Input
Look, Look,
Zoom Zoom
} }
public enum DebugActions
{
GiveHealth,
LooseHealth,
GiveMoney,
LooseMoney
}
} }
@@ -1,4 +1,6 @@
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem;
namespace Code.Input namespace Code.Input
{ {
@@ -19,5 +21,7 @@ namespace Code.Input
// Для переключения режима камеры // Для переключения режима камеры
bool ToggleCameraModePressed { get; } bool ToggleCameraModePressed { get; }
void DebugBindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action);
void DebugUnbindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action);
} }
} }
@@ -21,6 +21,8 @@ namespace Code.Input
{ {
_inputActionAsset = inputActionAsset; _inputActionAsset = inputActionAsset;
SetupInputCallbacks(); SetupInputCallbacks();
inputActionAsset.actionMaps[(int)ActionMappings.Debug].Enable();
} }
// Обработка всех карт ввода // Обработка всех карт ввода
@@ -165,10 +167,10 @@ namespace Code.Input
public void EnableMap(ActionMappings actionMap) => _inputActionAsset.actionMaps[(int)actionMap].Enable(); public void EnableMap(ActionMappings actionMap) => _inputActionAsset.actionMaps[(int)actionMap].Enable();
public void DisableMap(ActionMappings actionMap) => _inputActionAsset.actionMaps[(int)actionMap].Disable(); public void DisableMap(ActionMappings actionMap) => _inputActionAsset.actionMaps[(int)actionMap].Disable();
public void BindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action) => _inputActionAsset public void DebugBindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action) => _inputActionAsset
.actionMaps[mapId].actions[actionId].performed += action; .actionMaps[mapId].actions[actionId].performed += action;
public void UnbindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action) => public void DebugUnbindAction(int mapId, int actionId, Action<InputAction.CallbackContext> action) =>
_inputActionAsset _inputActionAsset
.actionMaps[mapId].actions[actionId].performed -= action; .actionMaps[mapId].actions[actionId].performed -= action;
@@ -0,0 +1,9 @@
using UnityEngine;
namespace Code.Scenes
{
public abstract class CurrentSceneEnvironment
{
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e67772183eee40b0bd3ca806dae98f20
timeCreated: 1761736090
@@ -1,10 +1,11 @@
using System; using System;
using Cysharp.Threading.Tasks;
namespace Code.Scenes namespace Code.Scenes
{ {
public interface ISceneLoaderService public interface ISceneLoaderService
{ {
void LoadScene(string sceneName, Action callback = null, float waitFor = 0f); UniTaskVoid LoadSceneAsync(string sceneName, float waitFor = 0f, Action callBack = null);
void LoadScene(int sceneId, Action callback = null, float waitFor = 0f); UniTaskVoid LoadSceneAsync(int sceneId, float waitFor = 0f, Action callBack = null);
} }
} }
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1364bdc4fb54b40843a614990de89ad
timeCreated: 1761736163
@@ -0,0 +1,18 @@
using UnityEngine;
using UnityEngine.Serialization;
namespace Code.Scenes.SceneEnvironments
{
[System.Serializable]
public class MainMenuEnvironment : CurrentSceneEnvironment
{
public Transform cameraTarget;
public Transform cameraPosition;
public void TransitVariables(MainMenuEnvironment env)
{
cameraTarget = env.cameraTarget;
cameraPosition = env.cameraPosition;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2e7be1a504f64b20ac431e6e231c1f70
timeCreated: 1761736177
@@ -6,6 +6,7 @@ namespace Code.Scenes
MainMenu, MainMenu,
Gameplay, Gameplay,
// TODO: поменяно для теста // TODO: поменяно для теста
MovementDev MovementDev,
DevGen
} }
} }
@@ -1,46 +1,33 @@
using System; using System;
using System.Collections; using Cysharp.Threading.Tasks;
using Code.App;
using Reflex.Attributes;
using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
namespace Code.Scenes namespace Code.Scenes
{ {
public class SceneLoaderService : ISceneLoaderService public class SceneLoaderService : ISceneLoaderService
{ {
[Inject] private ICoroutineRunner _coroutineRunner; public async UniTaskVoid LoadSceneAsync(string sceneName, float waitFor = 0f, Action callBack = null)
public void LoadScene(string sceneName, Action callback = null, float waitFor = 0f) =>
_coroutineRunner.StartCoroutine(LoadSceneAsync(sceneName, callback, waitFor));
public void LoadScene(int sceneId, Action callback = null, float waitFor = 0f) =>
_coroutineRunner.StartCoroutine(LoadSceneAsync(sceneId, callback, waitFor));
private IEnumerator LoadSceneAsync(string sceneName, Action callback = null, float waitFor = 0f)
{ {
if (waitFor > 0f) // if (waitFor > 0f)
yield return new WaitForSeconds(waitFor); // yield return new WaitForSeconds(waitFor);
AsyncOperation waitNextScene = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single); // AsyncOperation waitNextScene = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
while (!waitNextScene.isDone) // while (!waitNextScene.isDone)
yield return null; // yield return null;
callback?.Invoke(); // callback?.Invoke();
await UniTask.Delay(TimeSpan.FromSeconds(waitFor));
await SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single).ToUniTask();
callBack?.Invoke();
} }
private IEnumerator LoadSceneAsync(int sceneId, Action callback = null, float waitFor = 0f) public async UniTaskVoid LoadSceneAsync(int sceneId, float waitFor = 0f, Action callBack = null)
{ {
if (waitFor > 0f) await UniTask.WhenAll(
yield return new WaitForSeconds(waitFor); UniTask.Delay(TimeSpan.FromSeconds(waitFor)),
SceneManager.LoadSceneAsync(sceneId, LoadSceneMode.Single).ToUniTask());
AsyncOperation waitNextScene = SceneManager.LoadSceneAsync(sceneId, LoadSceneMode.Single); callBack?.Invoke();
while (!waitNextScene.isDone)
yield return null;
callback?.Invoke();
} }
} }
} }
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@@ -7,7 +8,8 @@ namespace Code.StateMachine
: MonoBehaviour : MonoBehaviour
where TStateMachineContext : new() where TStateMachineContext : new()
{ {
public BaseState<TStateMachineContext> CurrentBaseState { get; private set; } public TStateEnum CurrentState { get; protected set; }
public BaseState<TStateMachineContext> CurrentBaseState { get; protected set; }
private BaseState<TStateMachineContext> _previousBaseState; private BaseState<TStateMachineContext> _previousBaseState;
protected Dictionary<TStateEnum, BaseState<TStateMachineContext>> States = new(); protected Dictionary<TStateEnum, BaseState<TStateMachineContext>> States = new();
@@ -20,6 +22,10 @@ namespace Code.StateMachine
CurrentBaseState.Tick(); CurrentBaseState.Tick();
} }
protected void LateUpdate()
{
CurrentBaseState.LateTick();
}
public abstract void Initialize(); public abstract void Initialize();
// Костыли 2 шт. для флагового енама в шаблонах // Костыли 2 шт. для флагового енама в шаблонах
@@ -31,11 +37,9 @@ namespace Code.StateMachine
{ {
if (!CheckTransitionAllowed(nextState)) if (!CheckTransitionAllowed(nextState))
{ {
Debug.LogError("TransitionNotAllowed");
return null; return null;
} }
Debug.Log("Entered " + nextState);
ResetAllowedTransitions(); ResetAllowedTransitions();
_previousBaseState = CurrentBaseState; _previousBaseState = CurrentBaseState;
@@ -53,6 +57,8 @@ namespace Code.StateMachine
CurrentBaseState.EnterState(); CurrentBaseState.EnterState();
} }
CurrentState = nextState;
return this; return this;
} }
@@ -17,6 +17,7 @@ namespace Code.StateMachine
public virtual void EnterState() => OnEntered?.Invoke(); public virtual void EnterState() => OnEntered?.Invoke();
public virtual void Tick(){} public virtual void Tick(){}
public virtual void LateTick(){}
public virtual void ExitState() => OnExited?.Invoke(); public virtual void ExitState() => OnExited?.Invoke();
} }
@@ -1,5 +1,10 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Code.Configs; using Code.Configs;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.StaticData namespace Code.StaticData
{ {
@@ -8,6 +13,10 @@ namespace Code.StaticData
void Load(); void Load();
InputConfig InputConfig { get; } InputConfig InputConfig { get; }
AppConfig AppConfig { get; } AppConfig AppConfig { get; }
CharacterCameraConfig CharacterCameraConfig { get; } CameraConfig CameraConfig { get; }
CharacterConfig CharacterConfig { get; }
UIConfig UIConfig { get; }
PlayerConfig PlayerConfig { get; }
Task<T> InstantiateAsset<T>(AssetReference assetReference) where T : Behaviour;
} }
} }
@@ -1,6 +1,9 @@
using System.Threading.Tasks;
using Code.Configs; using Code.Configs;
using Code.Constants; using Code.Constants;
using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.StaticData namespace Code.StaticData
{ {
@@ -8,13 +11,24 @@ namespace Code.StaticData
{ {
public AppConfig AppConfig { get; private set; } public AppConfig AppConfig { get; private set; }
public InputConfig InputConfig { get; private set; } public InputConfig InputConfig { get; private set; }
public CharacterCameraConfig CharacterCameraConfig { get; private set; } public CameraConfig CameraConfig { get; private set; }
public CharacterConfig CharacterConfig { get; private set; }
public UIConfig UIConfig { get; private set; }
public PlayerConfig PlayerConfig { get; private set; }
public void Load() public void Load()
{ {
InputConfig = Resources.Load<InputConfig>(string.Format(StringPaths.ConfigSingle, "App", "InputConfig")); InputConfig = Resources.Load<InputConfig>(string.Format(StringPaths.ConfigSingle, "App", "InputConfig"));
AppConfig = Resources.Load<AppConfig>(string.Format(StringPaths.ConfigSingle, "App", "AppConfig")); AppConfig = Resources.Load<AppConfig>(string.Format(StringPaths.ConfigSingle, "App", "AppConfig"));
CharacterCameraConfig = Resources.Load<CharacterCameraConfig>(string.Format(StringPaths.ConfigSingle, "App", "CharacterCameraConfig")); CameraConfig = Resources.Load<CameraConfig>(string.Format(StringPaths.ConfigSingle, "App", "CameraConfig"));
CharacterConfig = Resources.Load<CharacterConfig>(string.Format(StringPaths.ConfigSingle, "App", "CharacterConfig"));
UIConfig = Resources.Load<UIConfig>(string.Format(StringPaths.ConfigSingle, "App", "UIConfig"));
PlayerConfig = Resources.Load<PlayerConfig>(string.Format(StringPaths.ConfigSingle, "App", "PlayerConfig"));
} }
public async Task<T> InstantiateAsset<T>(AssetReference assetReference) where T : Behaviour
{
return (await Addressables.InstantiateAsync(assetReference).ToUniTask()).GetComponent<T>();
}
} }
} }
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aca6d2360b0a4b35b124f6d6ffa10039
timeCreated: 1761577505
@@ -0,0 +1,11 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Code.UI
{
public interface IUIFactory
{
UniTask<Canvas> CreateCanvas();
UniTask<UIElement> CreatePanel(Canvas canvas, UIPanel uiPanel);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 61892c4ae3ab4b1498c3df219c5c75d6
timeCreated: 1761922548
@@ -0,0 +1,13 @@
using System;
using Code.UI.Panels;
namespace Code.UI
{
public interface IUIService
{
void SwitchPanelTo(UIPanel uiPanel, Action callback = null);
UIElement CurrentPanel { get; }
LoadingScreen LoadingScreen { get; }
void Initialize(LoadingScreen loadingScreen);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5d5cb971e8da41a5885f37d1334ab26a
timeCreated: 1761591248
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e8f289dfee4b4bd4988d217b988ae82d
timeCreated: 1761951389
@@ -0,0 +1,19 @@
using UnityEngine;
using UnityEngine.UI;
namespace Code.UI
{
public class GameOverPanel : UIElement
{
[SerializeField] private Image _background;
public override void Hide()
{
_background.enabled = false;
}
public override void Show()
{
_background.enabled = true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ecbe291069244405ac22e5470f3ba62b
timeCreated: 1761948692
@@ -0,0 +1,66 @@
using Code.Gameplay.Player;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
namespace Code.UI
{
public class GameplayPanel : UIElement
{
[Header("Dispatcher")]
[SerializeField] private Image dispatcher;
[SerializeField] private RectTransform dispatcherHiddenPosition;
[SerializeField] private RectTransform dispatcherShownPosition;
[SerializeField] private float dispatcherTransitionTime = 0.5f;
[Header("Timer")]
[SerializeField] private TMP_Text timer;
[Header("HealthBar")]
[SerializeField] private Image healthBar;
[Header("MoneyBar")]
[SerializeField] private TMP_Text moneyText;
private void MoneyUpdate(int count, int money)
{
// TODO Анимация получения\потери
moneyText.text = money.ToString();
}
private void HealthUpdate(int count, PlayerHealth health)
{
// TODO Анимация потери
float fillAmount = (float)health.HealthPoints / health.MaxHealthPoints;
healthBar.fillAmount = fillAmount;
}
public void Initialize(PlayerMoney money, PlayerHealth health)
{
money.OnMoneyGet += MoneyUpdate;
money.OnMoneyLoose += MoneyUpdate;
MoneyUpdate(0,money.Money);
health.OnDamage += HealthUpdate;
HealthUpdate(0,health);
}
private void Update()
{
timer.text = Time.deltaTime.ToString();
}
public void SummonDispatcher()
{
// TODO
dispatcher.transform.DOMoveX(dispatcherShownPosition.position.x, dispatcherTransitionTime);
}
public void CloseDispatcher()
{
dispatcher.transform.DOMoveX(dispatcherHiddenPosition.position.x, dispatcherTransitionTime);
// TODO
}
public override void Hide()
{
}
public override void Show()
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d79893d2e9024719924dc5c15d582e33
timeCreated: 1761760839
@@ -0,0 +1,22 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Code.UI.Panels
{
public class LoadingScreen : UIElement
{
[SerializeField] private Image background;
[SerializeField] private TMP_Text loadingTitle;
public override void Hide()
{
background.enabled = false;
loadingTitle.enabled = false;
}
public override void Show()
{
background.enabled = true;
loadingTitle.enabled = true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b4a0cb6cea947c6b085269973968c12
timeCreated: 1761594685
@@ -0,0 +1,18 @@
using UnityEngine;
using UnityEngine.UI;
namespace Code.UI
{
public class MainMenuPanel : UIElement
{
public Button startButton;
public Button exitButton;
public override void Show()
{
}
public override void Hide()
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3257edb60d20461a92f3e5dd2c4afee4
timeCreated: 1761596135
@@ -0,0 +1,10 @@
using UnityEngine;
namespace Code.UI
{
public abstract class UIElement : MonoBehaviour
{
public abstract void Show();
public abstract void Hide();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9af80648f8048e397791a856c682cce
timeCreated: 1761590528
Loaded 100 of 362 files, more files were not shown because too many files have changed in this diff. Show more