Author SHA1 Message Date
ThomasCharester 0502308673 Assets added 2025-11-11 14:14:15 +03:00
1175 changed files with 2831 additions and 1026520 deletions

No files matched your search

-6
View File
@@ -1,6 +0,0 @@
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedGame"
]
}
@@ -13,5 +13,4 @@ MonoBehaviour:
m_Name: AddressableAssetGroupSortSettings m_Name: AddressableAssetGroupSortSettings
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.AddressableAssetGroupSortSettings m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.AddressableAssetGroupSortSettings
sortOrder: sortOrder:
- 7e9d70e73e500eb9287683466ebf646f
- d447fc60305c17a17bf20948ab295a45 - d447fc60305c17a17bf20948ab295a45
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: d447fc60305c17a17bf20948ab295a45 m_DefaultGroup: d447fc60305c17a17bf20948ab295a45
m_currentHash: m_currentHash:
serializedVersion: 2 serializedVersion: 2
Hash: 1ec1ee985689145b755c859b69691cb0 Hash: 90c715b85c1e0904b3dc61a9ed460206
m_OptimizeCatalogSize: 0 m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0 m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0 m_CatalogRequestsTimeout: 0
@@ -60,7 +60,6 @@ MonoBehaviour:
m_BuildAddressablesWithPlayerBuild: 0 m_BuildAddressablesWithPlayerBuild: 0
m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]' m_overridePlayerVersion: '[UnityEditor.PlayerSettings.bundleVersion]'
m_GroupAssets: m_GroupAssets:
- {fileID: 11400000, guid: ba51c0b85beab267d8f41a72df4fb657, type: 2}
- {fileID: 11400000, guid: 7c592a6445170e8379910601b962cb30, type: 2} - {fileID: 11400000, guid: 7c592a6445170e8379910601b962cb30, type: 2}
m_BuildSettings: m_BuildSettings:
m_LogResourceManagerExceptions: 1 m_LogResourceManagerExceptions: 1
@@ -1,21 +0,0 @@
%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: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
m_Name: Audio
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup
m_GroupName: Audio
m_GUID: 7e9d70e73e500eb9287683466ebf646f
m_SerializeEntries: []
m_ReadOnly: 0
m_Settings: {fileID: 11400000, guid: 2588e84eb64800d0781c574a37fa48d8, type: 2}
m_SchemaSet:
m_Schemas: []
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ba51c0b85beab267d8f41a72df4fb657
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -15,259 +15,31 @@ MonoBehaviour:
m_GroupName: Default Local Group m_GroupName: Default Local Group
m_GUID: d447fc60305c17a17bf20948ab295a45 m_GUID: d447fc60305c17a17bf20948ab295a45
m_SerializeEntries: m_SerializeEntries:
- m_GUID: 144ab5188f8ad7a0a92c75703bd12e60
m_Address: launch_upmenu1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 16562a5c87213914cad415f10c65a3b0
m_Address: "Assets/Resources_moved/Audio/Music/LASKX3I - \u041F\u0440\u043E\u0442\u0438\u0432
\u0446\u0430\u0440\u044F (\u041A\u0430\u0440\u0430\u043E\u043A\u0435).mp3"
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 170b1a70ddee4d948abea041b9b805e4
m_Address: Assets/Resources_moved/Audio/Music/Half Life Original Soundtrack -
Track 8 - Diabolical Adrenaline Guitar.mp3
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1cc66c0b13a946e1c9ec37026268e144
m_Address: Audio/Enemies/pl_metal2
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1d4767c722c05b5d6b96bb226d378c65
m_Address: Assets/Resources_moved/Prefabs/Player/ArmsMesh.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1d7110a045075b22fa27d62bd5f2784e
m_Address: 357_shot1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 1f79c7b1e492a4ea2a8d666cdead0c52
m_Address: Audio/Dispatcher/take_damage4
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 23e11d2c8a4f12f4ea9a7a43d1118464 - m_GUID: 23e11d2c8a4f12f4ea9a7a43d1118464
m_Address: Gameplay m_Address: Gameplay
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 26d4e4c556527924490b8018cd510eda
m_Address: Assets/Resources_moved/Audio/Enemies/bc_bite2.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 28a6c7a4124141341ab7c2b7dfd3e95b
m_Address: Assets/Resources_moved/Prefabs/Levels/Level1 2.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 29bff8011973e4e519906b9da8fca0fc
m_Address: Audio/Dispatcher/late3
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 29ee630aab7bbef8799dc76ed0c239f7
m_Address: Audio/Dispatcher/late4
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2c6a984e4a1b42647b48e6b7bc69b158
m_Address: Prefabs/Levels/Level6
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2ed2d93a9f39d4edba511cb7396c7bb6
m_Address: Assets/Resources_moved/Prefabs/Enemies/Cockroach.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 2f6134357dc2a5641a846a7478ce4c2c
m_Address: bravo!
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 31f16c866d3224ab8a7e0fabf3745ec5
m_Address: Audio/Dispatcher/late5
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3831a36e0c8e48f659517a6bb99378b6
m_Address: cbar_hit1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 3f8f2171cc252d953b82083cc16bd1ae
m_Address: Assets/Resources_moved/Prefabs/Levels/Level6_1.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 48aebfe8826dc0df79d047b5afbf8f5b - m_GUID: 48aebfe8826dc0df79d047b5afbf8f5b
m_Address: Canvas m_Address: Canvas
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4b279429428a13040b6d1713122cd0de
m_Address: Assets/Resources_moved/Audio/SFX/pl_pain5.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4bd64d1fcecc9c6b09d81e78b7dcfebe
m_Address: Audio/Dispatcher/take_damage1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 7067a5248027e74478f4f0f53ddf06a4
m_Address: Prefabs/Rooms/Save1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 735f38402ea347fa381408293503a24e
m_Address: Audio/Dispatcher/receiving_an_order1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 751890caa1647ec6689f24933fbcb770
m_Address: "Prefabs/Furniture/\u0442\u0440\u0443\u0431\u0430"
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 7c5caf3ceee44f71588860c5c1fbdbe0
m_Address: Audio/Dispatcher/take_damage3
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 803e40b883c823b89a91aa8b9d9fc483
m_Address: valve_sound
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 81844a99f5b786b41b831174f8fb6d3a
m_Address: Prefabs/Player/CharacterController
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8187ea542d6ed824d81230966a3b7499
m_Address: Assets/Resources_moved/Audio/Music/Bauhaus - Bela Lugosi's Dead (No
Vocals Remix).mp3
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 82c71411174bfa86ca1f676b9d389f94
m_Address: bastard!
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8c2be26d359d54063825fedfad7a493b
m_Address: Audio/Dispatcher/late2
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 8fa89a492fc3a3768a0f0ba1bb7080e0
m_Address: Audio/Dispatcher/late1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9293380a88bf6736889be29722b94269
m_Address: Audio/AudioSource
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 9d78f017876abed4c9cd8957f1e140cb
m_Address: Prefabs/Rooms/Level1_2
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a306df57f348ce7cf900d0b9431739ee
m_Address: launch_selectmenu
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: aa138b2e21adfbf4bbbfa5bf9d54b8f2
m_Address: Assets/Resources_moved/Audio/UI/buttonclickrelease.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ae6ab2c88ae248ce1b6efca892f17659
m_Address: Assets/Resources_moved/Audio/Music/TestDeath.ogg
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b2e8dc2aeb0311cd8a826b51cf8e94dc - m_GUID: b2e8dc2aeb0311cd8a826b51cf8e94dc
m_Address: GameOver m_Address: GameOver
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b33855c694de89386a25c56cbe463a0e
m_Address: Assets/Resources_moved/Prefabs/Enemies/DrunkenGrandpa.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b37ea8c61cc322afdad529d678612d22
m_Address: Assets/Resources_moved/Audio/Enemies/zo_attack2.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: bf87bc9ac43df86be8c818de81d44a54
m_Address: Assets/Resources_moved/Audio/SFX/pl_step1.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c566fc932edefbbee9e53271eac3c560 - m_GUID: c566fc932edefbbee9e53271eac3c560
m_Address: MainMenu m_Address: MainMenu
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c5a0ad8a615841547ac2ee613af526d8
m_Address: Assets/Resources_moved/Audio/SFX/pl_pain2.wav
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c938de3174f184741925c407a43a47f2
m_Address: Prefabs/Rooms/Level1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: cab4b1ff78dd6b1fba61aa165aa17b4d
m_Address: Audio/Dispatcher/receiving_an_order2
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e3a3840a7963bf841872cddb02e90151
m_Address: Prefabs/Levels/MainMenu 1
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: e84e3a3c26ccbd50d92f13627403f148
m_Address: Audio/Enemies/rch_walk
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: ebe18293a5ef86704b23dad3a1786476
m_Address: Half-Life01
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f23b9fce9594618e5b75845f1cae50de - m_GUID: f23b9fce9594618e5b75845f1cae50de
m_Address: LoadingScreen m_Address: LoadingScreen
m_ReadOnly: 0 m_ReadOnly: 0
m_SerializedLabels: [] m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0 FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f3d7dc64b34919ad7b50a3dd307a935b
m_Address: Assets/Resources_moved/Prefabs/Enemies/Fog.prefab
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: f74a409bf11b25148ad305acc05b14f5
m_Address: Audio/Dispatcher/take_damage2
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:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d36aab6707c518f5abfd60065165eb35
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,28 +0,0 @@
%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: 7e3976da977cb49238499ea3b4c237ae, type: 3}
m_Name: ProfileDataSourceSettings
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.ProfileDataSourceSettings
profileGroupTypes:
- m_GroupTypePrefix: Built-In
m_Variables:
- m_Suffix: BuildPath
m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]'
- m_Suffix: LoadPath
m_Value: '{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]'
environments: []
currentEnvironment:
id:
projectId:
projectGenesisId:
name:
isDefault: 0
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7e2b0e48c9649284a9541c4d1dad59c2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,85 +1,41 @@
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Audio;
using Code.Gameplay.Player;
using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.Scenes.SceneEnvironments;
using Code.StateMachine; using Code.StateMachine;
using Code.UI; using Code.UI;
using Cysharp.Threading.Tasks;
using Reflex.Core;
using Reflex.Extensions;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
namespace Code.App.AppStates namespace Code.App.AppStates
{ {
public class GameOverState : BaseState<AppStateMachineContext> public class GameOverState : BaseState<AppStateMachineContext>
{ {
public GameOverState(AppStateMachineContext context) : base(context) public GameOverState(AppStateMachineContext context) : base(context)
{ { }
}
private IPlayerScore _playerScore;
private GameOverPanel _gameOverPanel;
private float damnTimer = 3f; // TODO Удалить при первой возможности
public override void EnterState() public override void EnterState()
{ {
Container sceneContainer = SceneManager.GetActiveScene().GetSceneContainer(); Context.UIService.SwitchPanelTo(UIPanel.GameOver);
_playerScore = sceneContainer.Resolve<IPlayerScore>();
_scoreCounter = 0;
Context.InputService.EnableMap(ActionMappings.UI);
Context.InputService.DebugBindAction((int)ActionMappings.UI,(int)UIActions.Continue, TryToExit);
Context.AudioService.PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType.Death);
Context.AudioService.ApplyStun(5000,0.5f);
GameOver().Forget();
base.EnterState(); base.EnterState();
} }
private void TryToExit(InputAction.CallbackContext context)
{
ToMainMenu();
}
private async UniTaskVoid GameOver()
{
await Context.UIService.AsyncSwitchPanelTo(UIPanel.GameOver);
_gameOverPanel = (GameOverPanel)Context.UIService.CurrentPanel;
_gameOverPanel.Show();
}
private void ToMainMenu() private void ToMainMenu()
{ {
Context.StateMachine.SwitchState(AppState.MainMenu) Context.StateMachine.SwitchState(AppState.MainMenu)
.AllowStateTransition(AppState.Gameplay); .AllowStateTransition(AppState.Gameplay);
} }
private float _scoreCounter;
public override void Tick() public override void Tick()
{ {
if (!_gameOverPanel) return; damnTimer -= Time.deltaTime;
if (damnTimer <= float.Epsilon) ToMainMenu();
_scoreCounter += Time.deltaTime * 100;
// UnityEngine.Debug.Log($"Score: {_scoreCounter}");
if((int)_scoreCounter >= _playerScore.Score)
{
_gameOverPanel.UpdateScoreText(_playerScore.Score);
}
else _gameOverPanel.UpdateScoreText((int)_scoreCounter);
} }
public override void ExitState() public override void ExitState()
{ {
Context.InputService.DisableMap(ActionMappings.UI); damnTimer = 5f;
Context.InputService.DebugUnbindAction((int)ActionMappings.UI,(int)UIActions.Continue, TryToExit);
Context.AudioService.GladlyAskToStopMusic(MusicType.Death, callback: Context.AudioFactory.ReleaseDeathMusic); Context.UIService.LoadingScreen.Show();
Context.AudioService.TransitToSnapshot(AudioSnapshotState.None);
base.ExitState(); base.ExitState();
} }
} }
@@ -1,192 +1,95 @@
using Code.App.Loading;
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Audio;
using Code.Gameplay.Camera; using Code.Gameplay.Camera;
using Code.Gameplay.Camera.CameraStates;
using Code.Gameplay.Character.Core;
using Code.Gameplay.Orders.Order;
using Code.Gameplay.Player; using Code.Gameplay.Player;
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 Code.UI;
using Code.UI.Panels;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using Reflex.Core;
using Reflex.Extensions;
using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
namespace Code.App.AppStates namespace Code.App.AppStates
{ {
public class GameplayState : BaseState<AppStateMachineContext> public class GameplayState : BaseState<AppStateMachineContext>
{ {
private PlayerHealth _playerHealth; private PlayerHealth _playerHealth;
private IPlayerScore _playerScore; private PlayerMoney _playerMoney;
private IOrderService _orderService;
private CharacterProvider _playerCharacter; private bool _uiLoaded, _sceneLoaded;
public GameplayState(AppStateMachineContext context) : base(context) public GameplayState(AppStateMachineContext context) : base(context)
{ {
} }
public override void EnterState() public override void EnterState()
{ {
_uiLoaded = _sceneLoaded = false;
Context.UIService.LoadingScreen.Show(); Context.UIService.LoadingScreen.Show();
Context.LoadingManager
.RegisterLoading(LoadingWork.LoadScene)
.RegisterLoading(LoadingWork.LoadUI)
.RegisterLoading(LoadingWork.LoadModelResource)
.RegisterLoading(LoadingWork.LoadAudio);
Context.AudioService.TransitToSnapshot(AudioSnapshotState.None); _playerMoney = Context.PlayerFactory.CreatePlayerMoney();
_playerHealth = Context.PlayerFactory.CreatePlayerHealth();
Context.InputService.EnableMap(ActionMappings.Gameplay);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.Debug1, InstaKillDEBUG);
Context.InputService.DebugBindAction((int)ActionMappings.Debug, (int)DebugActions.Debug4, TakeGamageDEBUG);
Context.SceneLoaderService.LoadSceneAsyncAwaitable((int)SceneIds.DevGen);
Context.LoadingManager.OnLoadingEnded += AfterSceneLoaded;
WaitForLoad().Forget();
}
private async UniTask WaitForPlayerCharacterLoad() => _playerCharacter = await Context.PlayerFactory.CreatePlayer();
private async UniTaskVoid WaitForLoad()
{
await UniTask.WhenAll(
Context.UIService.AsyncSwitchPanelTo(UIPanel.Gameplay),
Context.AudioFactory.WarmGameplayMusic(),
Context.AudioFactory.WarmDeathMusic(),
Context.AudioFactory.WarmSFX(),
Context.AudioFactory.WarmDispatcher(),
Context.AudioFactory.WarmEnemies(),
Context.AudioFactory.WarmCourier()
);
Context.LoadingManager
.UnregisterLoading(LoadingWork.LoadUI)
.UnregisterLoading(LoadingWork.LoadAudio);
InputServiceInitialize();
MusicInitialize();
}
private async void AfterSceneLoaded()
{
Context.LoadingManager
.RegisterLoading(LoadingWork.LoadModelResource);
Context.LoadingManager.OnLoadingEnded -= AfterSceneLoaded;
Context.LoadingManager.OnLoadingEnded += AfterPlayerLoaded;
Context.PlayerProvider = SceneManager.GetActiveScene().GetSceneContainer().Resolve<IPlayerProvider>();
Context.PlayerFactory = SceneManager.GetActiveScene().GetSceneContainer().Resolve<IPlayerFactory>();
await UniTask.WhenAll(
WaitForPlayerArmsToInitialize(),
WaitForPlayerCharacterLoad()
);
_playerHealth = Context.PlayerProvider.Health;
_playerHealth.OnDamage += PlayerDamageTaken;
_playerHealth.OnDeath += EndGame; _playerHealth.OnDeath += EndGame;
Context.LoadingManager Context.SceneLoaderService.LoadSceneAsync((int)SceneIds.MovementDev, callBack: OnSceneLoaded); // TODO Фабрика игрока Nada
.UnregisterLoading(LoadingWork.LoadModelResource);
} Context.UIService.SwitchPanelTo(UIPanel.Gameplay, callback: PostUILoaded);
private void AfterPlayerLoaded() 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()
{ {
Container sceneContainer = SceneManager.GetActiveScene().GetSceneContainer(); _sceneLoaded = true;
CheckIfLoaded();
_playerScore = sceneContainer.Resolve<IPlayerScore>();
GameplaySceneEnvironment sceneEnvironment = SceneEnvironmentVariables(sceneContainer);
_volume = sceneEnvironment.volume;
PlayerInitialize(sceneEnvironment);
CameraInitialize();
GameplayServicesInitialize(sceneContainer);
UIInitialize();
Context.LoadingManager.OnLoadingEnded -= AfterPlayerLoaded;
_tickEnabled = true;
} }
private async UniTask WaitForPlayerArmsToInitialize() => _armsMesh = await Context.PlayerFactory.CreateArms(Context.CameraStateMachine.Camera.transform); private void PostUILoaded()
private void OrderGiven(float time)
{
_gameplayPanel.ShowTimer();
Context.AudioService.TransitToSnapshot(AudioSnapshotState.Action);
}
private void OrderFailure()
{ {
_playerHealth.InstaKill(); ((GameplayPanel)Context.UIService.CurrentPanel).Initialize(_playerMoney, _playerHealth);
_uiLoaded = true;
CheckIfLoaded();
} }
private void OrderRecieved(float time)
{
Context.AudioService.PlayDispatcher((DispatcherSounds)Random.Range((int)DispatcherSounds.Recieve1,(int)DispatcherSounds.Recieve2));
}
private void OrderDelivered(int score, float time)
{
_gameplayPanel.HideTimer();
if(time < 0f) Context.AudioService.PlayDispatcher((DispatcherSounds)Random.Range((int)DispatcherSounds.Late1,(int)DispatcherSounds.Late2)); private void CheckIfLoaded()
_playerScore.UpdateScore(score); {
if(!_uiLoaded || !_sceneLoaded) return;
Context.AudioService.TransitToSnapshot(AudioSnapshotState.Peaceful); Context.UIService.LoadingScreen.Hide();
}
private void PlayerDamageTaken(int damage, PlayerHealth health)
{
Context.AudioService.PlayDispatcher((DispatcherSounds)Random.Range((int)DispatcherSounds.TakeDamage1,(int)DispatcherSounds.TakeDamage4));
Context.AudioService.PlayCourier((CourierSounds)Random.Range((int)CourierSounds.Damage1,(int)CourierSounds.Damage2));
DamageEffectProcess().Forget();
}
// TODO В конфиг
private async UniTaskVoid DamageEffectProcess()
{
_damagePanel.Show();
Context.AudioService.ApplyStun(200, 0.5f);
DOTween.To(() => _volume.weight, x => _volume.weight = x, 1f, 0.5f); Context.CameraStateMachine.SwitchState(CameraMode.FirstPerson);
await UniTask.WaitForSeconds(3f); Context.InputService.EnableMap(ActionMappings.Gameplay);
DOTween.To(() => _volume.weight, x => _volume.weight = x, 0f, 1f);
Context.AudioService.ApplyStun(5000, 1f);
_damagePanel.Hide();
} }
// DEBUG
private void InstaKillDEBUG(InputAction.CallbackContext context)
// Debug
private void GiveHealth(InputAction.CallbackContext context)
{ {
_playerHealth.InstaKill(); _playerHealth.GetDamage(-1);
} }
private void TakeGamageDEBUG(InputAction.CallbackContext context)
private void LooseHealth(InputAction.CallbackContext context)
{ {
_playerHealth.GetDamage(1); _playerHealth.GetDamage(1);
} }
// DEBUG
private bool _tickEnabled; 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()
{ {
if (!_tickEnabled) return;
_orderService.DecreaseTimer(Time.deltaTime);
} }
private void EndGame() private void EndGame()
@@ -198,83 +101,16 @@ namespace Code.App.AppStates
public override void ExitState() public override void ExitState()
{ {
_playerHealth = null; _playerHealth = null;
_playerScore = null; _playerMoney = null;
_tickEnabled = false;
Context.PlayerFactory.ReleasePlayer(_playerCharacter);
_playerCharacter = null;
Context.PlayerFactory.ReleaseArms(_armsMesh);
_armsMesh = null;
_orderService = null;
Context.AudioService.OnDispatcherStarted -= _gameplayPanel.SummonDispatcher;
Context.AudioService.OnDispatcherEnded -= _gameplayPanel.CloseDispatcher;
Context.InputService.DisableMap(ActionMappings.Gameplay); Context.InputService.DisableMap(ActionMappings.Gameplay);
Context.AudioService.GladlyAskToStopMusic(MusicType.Action, time: 0f, callback: Context.AudioFactory.ReleaseActionMusic);
Context.AudioService.GladlyAskToStopMusic(MusicType.Peaceful, time: 0f, callback: Context.AudioFactory.ReleasePeacefulMusic);
Context.AudioFactory.ReleaseCourier();
Context.AudioFactory.ReleaseDispatcher();
Context.AudioFactory.ReleaseEnemies();
Context.AudioFactory.ReleaseSFX();
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.GiveHealth, GiveHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.Debug1, InstaKillDEBUG); Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.GiveMoney, GiveHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.Debug4, TakeGamageDEBUG); Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.LooseHealth, LooseHealth);
Context.InputService.DebugUnbindAction((int)ActionMappings.Debug, (int)DebugActions.LooseMoney, LooseMoney);
base.ExitState(); base.ExitState();
} }
private void InputServiceInitialize()
{
Context.InputService.EnableMap(ActionMappings.Gameplay);
}
private void CameraInitialize()
{
Context.CameraStateMachine.SwitchState(CameraMode.FirstPerson);
((BaseCameraState)Context.CameraStateMachine.CurrentBaseState).SetFollowTransform(_playerCharacter.GetFollowTransform());
}
private GameplayPanel _gameplayPanel;
private ArmsMeshProvider _armsMesh;
private DamagePanel _damagePanel;
private void UIInitialize()
{
_gameplayPanel = (GameplayPanel)Context.UIService.CurrentPanel;
_gameplayPanel.Initialize(_playerScore, _playerHealth, _orderService);
Context.AudioService.OnDispatcherStarted += _gameplayPanel.SummonDispatcher;
Context.AudioService.OnDispatcherEnded += _gameplayPanel.CloseDispatcher;
_damagePanel = _gameplayPanel.DamagePanel;
}
private void MusicInitialize()
{
Context.AudioService.PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType.Peaceful);
Context.AudioService.PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType.Action);
Context.AudioService.TransitToSnapshot(AudioSnapshotState.Peaceful);
}
private void PlayerInitialize(GameplaySceneEnvironment sceneEnvironment)
{
_playerCharacter.Teleport(sceneEnvironment.playerSpawnPoint.position, sceneEnvironment.playerSpawnPoint.rotation);
_playerCharacter.PlayerAnimator.SetAnimator(_armsMesh.ArmsAnimator);
}
private void GameplayServicesInitialize(Container sceneContainer)
{
_orderService = sceneContainer.Resolve<IOrderService>();
_orderService.OnOrderGiven += OrderGiven;
_orderService.OnOrderRecieved += OrderRecieved;
_orderService.OnOrderDelivered += OrderDelivered;
_orderService.OnOrderFailure += OrderFailure;
}
private Volume _volume;
private GameplaySceneEnvironment SceneEnvironmentVariables(Container sceneContainer)
{
var sceneEnvironment = (GameplaySceneEnvironment)sceneContainer.Resolve<CurrentSceneEnvironment>();
return sceneEnvironment;
}
} }
} }
@@ -1,16 +1,11 @@
using Code.App.Loading;
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Audio;
using Code.Gameplay.Camera; using Code.Gameplay.Camera;
using Code.Gameplay.Camera.CameraStates; using Code.Gameplay.Camera.CameraStates;
using Code.Gameplay.Door;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.Scenes.SceneEnvironments; using Code.Scenes.SceneEnvironments;
using Code.StateMachine; using Code.StateMachine;
using Code.UI; using Code.UI;
using Code.UI.Panels;
using Cysharp.Threading.Tasks;
using Reflex.Extensions; using Reflex.Extensions;
using UnityEngine; using UnityEngine;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
@@ -19,89 +14,61 @@ 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.LoadingManager _uiLoaded = _sceneLoaded = false;
.RegisterLoading(LoadingWork.LoadScene)
.RegisterLoading(LoadingWork.LoadUI)
.RegisterLoading(LoadingWork.LoadAudio);
// Context.UIService.LoadingScreen.Show(); Context.UIService.LoadingScreen.Show();
Context.CameraStateMachine.SwitchState(CameraMode.MainMenu);
Context.SceneLoaderService.LoadSceneAsyncAwaitable((int)SceneIds.MainMenu); Context.SceneLoaderService.LoadSceneAsync((int)SceneIds.MainMenu, callBack: SceneLoaded);
Context.LoadingManager.OnLoadingEnded += AfterSceneLoaded;
Context.UIService.SwitchPanelTo(UIPanel.MainMenu, PostUILoaded);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Context.CameraStateMachine.SwitchState(CameraMode.Static);
WaitForLoad();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
private async UniTaskVoid WaitForLoad() private void PostUILoaded()
{ {
await UniTask.WhenAll( ((MainMenuPanel)Context.UIService.CurrentPanel).startButton.onClick.AddListener(StartGame);
Context.UIService.AsyncSwitchPanelTo(UIPanel.MainMenu), ((MainMenuPanel)Context.UIService.CurrentPanel).exitButton.onClick.AddListener(ExitGame);
Context.AudioFactory.WarmMenuMusic(),
Context.AudioFactory.WarmAudioTestClips()
);
Context.LoadingManager
.UnregisterLoading(LoadingWork.LoadUI)
.UnregisterLoading(LoadingWork.LoadAudio);
InitializeAudio(); _uiLoaded = true;
InitializeInput();
}
private void AfterSceneLoaded()
{
var mainMenuEnvironment = (MainMenuEnvironment)SceneManager.GetActiveScene()
.GetSceneContainer().Resolve<CurrentSceneEnvironment>();
InitializeUI(mainMenuEnvironment);
InitializeCamera(mainMenuEnvironment);
Context.LoadingManager.OnLoadingEnded -= AfterSceneLoaded; CheckIfLoaded();
} }
private void InitializeCamera(MainMenuEnvironment mainMenuEnvironment) private void SceneLoaded()
{ {
((Gameplay.Camera.CameraStates.MainMenuState)Context.CameraStateMachine.CurrentBaseState).SetFollowTransform(mainMenuEnvironment var hui = ((MainMenuEnvironment)SceneManager.GetActiveScene()
.cameraPosition); .GetSceneContainer().Resolve<CurrentSceneEnvironment>());
((StaticState)Context.CameraStateMachine.CurrentBaseState).LookTarget =
hui.cameraTarget;
((BaseCameraState)Context.CameraStateMachine.CurrentBaseState).SetFollowTransform(hui.cameraPosition);
_sceneLoaded = true;
CheckIfLoaded();
} }
private void InitializeInput() private void CheckIfLoaded()
{ {
if(!_uiLoaded || !_sceneLoaded) return;
Context.UIService.LoadingScreen.Hide();
Context.InputService.EnableMap(ActionMappings.MainMenu); Context.InputService.EnableMap(ActionMappings.MainMenu);
base.EnterState();
} }
private void InitializeAudio()
{
Context.AudioService.TransitToSnapshot(AudioSnapshotState.Menu);
Context.AudioService.PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType.Menu);
}
private void InitializeUI(MainMenuEnvironment mainMenuEnvironment)
{
var menuPanel = (MainMenuPanel)Context.UIService.CurrentPanel;
menuPanel.Initialize(mainMenuEnvironment.uiVariables,
mainMenuEnvironment.startButton,
mainMenuEnvironment.settingButton,
mainMenuEnvironment.exitButton);
menuPanel.startButton.button.onClick.AddListener(StartGame);
menuPanel.exitButton.button.onClick.AddListener(ExitGame);
mainMenuEnvironment.menuButtonsCanvas.worldCamera = Context.CameraStateMachine.Camera;
mainMenuEnvironment.settingsCanvas.worldCamera = Context.CameraStateMachine.Camera;
}
public override void Tick() public override void Tick()
{ {
} }
@@ -120,9 +87,7 @@ namespace Code.App.AppStates
public override void ExitState() public override void ExitState()
{ {
Context.InputService.DisableMap(ActionMappings.MainMenu); Context.InputService.DisableMap(ActionMappings.MainMenu);
Context.AudioService.GladlyAskToStopMusic(MusicType.Menu, callback: Context.AudioFactory.ReleaseMenuMusic);
Context.AudioFactory.ReleaseAudioTestClips();
base.ExitState(); base.ExitState();
} }
} }
@@ -1,14 +1,9 @@
using System.Collections.Generic; using System.Threading.Tasks;
using Code.App.Loading;
using Code.App.StateMachine; using Code.App.StateMachine;
using Code.Audio;
using Code.Configs; using Code.Configs;
using Code.Constants; using Code.Constants;
using Code.Gameplay.Camera; using Code.Gameplay.Camera;
using Code.Gameplay.Enemies;
using Code.Gameplay.Orders.Order;
using Code.Gameplay.Player; using Code.Gameplay.Player;
using Code.Generator;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.StaticData; using Code.StaticData;
@@ -18,6 +13,7 @@ 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
@@ -30,18 +26,12 @@ namespace Code.App
InstallInfrastructureUnit(containerBuilder, staticDataService); InstallInfrastructureUnit(containerBuilder, staticDataService);
containerBuilder.AddSingleton(typeof(LoadingManager), typeof(ILoadingManager));
containerBuilder.AddSingleton(typeof(InputService), typeof(IInputService)); containerBuilder.AddSingleton(typeof(InputService), typeof(IInputService));
containerBuilder.AddSingleton(typeof(PlayerFactory), typeof(IPlayerFactory));
InitializeCamera(containerBuilder, staticDataService); InitializeCamera(containerBuilder, staticDataService);
containerBuilder.AddSingleton(typeof(UIFactory), typeof(IUIFactory)); containerBuilder.AddSingleton(typeof(UIFactory), typeof(IUIFactory));
containerBuilder.AddSingleton(typeof(UIService), typeof(IUIService)); containerBuilder.AddSingleton(typeof(UIService), typeof(IUIService));
containerBuilder.AddSingleton(typeof(AudioFactory), typeof(IAudioFactory));
containerBuilder.AddSingleton(typeof(AudioService), typeof(IAudioService));
containerBuilder.AddSingleton(typeof(SceneLoaderService), typeof(ISceneLoaderService)); containerBuilder.AddSingleton(typeof(SceneLoaderService), typeof(ISceneLoaderService));
containerBuilder.OnContainerBuilt += Bootstrap; containerBuilder.OnContainerBuilt += Bootstrap;
@@ -67,48 +57,24 @@ namespace Code.App
containerBuilder.AddSingleton(infrastructureUnit.AddComponent<CameraStateMachine>()); containerBuilder.AddSingleton(infrastructureUnit.AddComponent<CameraStateMachine>());
} }
// TODO Часть в сцены
private void InstallStaticData(ContainerBuilder containerBuilder, out IStaticDataService staticDataService) private void InstallStaticData(ContainerBuilder containerBuilder, out IStaticDataService staticDataService)
{ {
staticDataService = new StaticDataService(); staticDataService = new StaticDataService();
staticDataService.Load(); staticDataService.Load();
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.InputConfig, typeof(InputConfig));
containerBuilder.AddSingleton(staticDataService.CameraConfig, typeof(CameraConfig)); containerBuilder.AddSingleton(staticDataService.CameraConfig, typeof(CameraConfig));
containerBuilder.AddSingleton(staticDataService.CameraSettingsConfigs, typeof(Dictionary<CameraMode,CameraSettingsConfig>));
containerBuilder.AddSingleton(staticDataService.PlayerConfig, typeof(PlayerConfig)); containerBuilder.AddSingleton(staticDataService.PlayerConfig, typeof(PlayerConfig));
containerBuilder.AddSingleton(staticDataService.CharacterConfig, typeof(CharacterConfig)); containerBuilder.AddSingleton(staticDataService.CharacterConfig, typeof(CharacterConfig));
containerBuilder.AddSingleton(staticDataService.UIConfig, typeof(UIConfig)); containerBuilder.AddSingleton(staticDataService.UIConfig, typeof(UIConfig));
// containerBuilder.AddSingleton(staticDataService.UIConfig.canvasPrefab, typeof(Canvas));
containerBuilder.AddSingleton(staticDataService.DrunkenGrandpaConfig, typeof(DrunkenGrandpaConfig)); // containerBuilder.AddSingleton(staticDataService.UIConfig.uiElementsPrefabs, typeof(UIElement[]));
containerBuilder.AddSingleton(staticDataService.CockroachConfig, typeof(CockroachConfig));
containerBuilder.AddSingleton(staticDataService.FogConfig, typeof(FogConfig));
containerBuilder.AddSingleton(staticDataService.AudioMixerConfig, typeof(AudioMixerConfig));
containerBuilder.AddSingleton(staticDataService.SoundtrackListConfig, typeof(SoundtrackListConfig));
containerBuilder.AddSingleton(staticDataService.AudioTestClipsConfig, typeof(AudioTestClipsConfig));
containerBuilder.AddSingleton(staticDataService.SFXListConfig, typeof(SFXListConfig));
containerBuilder.AddSingleton(staticDataService.CourierSoundListConfig, typeof(CourierSoundListConfig));
containerBuilder.AddSingleton(staticDataService.DispatcherSoundListConfig, typeof(DispatcherSoundListConfig));
containerBuilder.AddSingleton(staticDataService.EnemiesSoundListConfig, typeof(EnemiesSoundListConfig));
containerBuilder.AddSingleton(staticDataService.AudioSourceSettingsConfigs, typeof(Dictionary<AudioSourceType, AudioSourceSettingsConfig>));
containerBuilder.AddSingleton(staticDataService.DefaultAudioSettingsConfig, typeof(DefaultAudioSettingsConfig));
containerBuilder.AddSingleton(staticDataService.OrderServiceConfig, typeof(OrderServiceConfig));
containerBuilder.AddSingleton(staticDataService.OrderBoxConfig, typeof(OrderBoxConfig));
containerBuilder.AddSingleton(staticDataService.GeneratorConfig, typeof(GeneratorConfig));
} }
private bool _onUiLoaded;
private AppStateMachine _appStateMachine; private AppStateMachine _appStateMachine;
private void Bootstrap(Container container) private void Bootstrap(Container container)
{ {
_appStateMachine = container.Resolve<AppStateMachine>(); _appStateMachine = container.Resolve<AppStateMachine>();
@@ -118,27 +84,20 @@ namespace Code.App
CameraStateMachine cameraStateMachine = container.Resolve<CameraStateMachine>(); CameraStateMachine cameraStateMachine = container.Resolve<CameraStateMachine>();
AttributeInjector.Inject(cameraStateMachine.StateMachineContext, container); AttributeInjector.Inject(cameraStateMachine.StateMachineContext, container);
cameraStateMachine.Initialize(); cameraStateMachine.Initialize();
BootstrapFinish(container).Forget(); var loadingScreen = GameObject.FindWithTag(Tags.LoadingScreen);
DontDestroyOnLoad(loadingScreen);
IUIService uiService = container.Resolve<IUIService>();
uiService.Initialize(loadingScreen.GetComponent<LoadingScreen>());
TryStartGame();
} }
private async UniTaskVoid BootstrapFinish(Container container)
private void TryStartGame()
{ {
var loadingScreen = GameObject.FindWithTag(Tags.LoadingScreen).GetComponent<LoadingScreen>();
DontDestroyOnLoad(loadingScreen.gameObject);
ILoadingManager loadingManager = container.Resolve<ILoadingManager>();
loadingManager.OnLoadingStarted += loadingScreen.Show;
loadingManager.OnLoadingEnded += loadingScreen.Hide;
loadingManager.RegisterLoading(LoadingWork.Bootstrap);
await UniTask.WhenAll(
container.Resolve<IUIService>().Initialize(loadingScreen),
container.Resolve<IAudioService>().Initialize()
);
loadingManager.UnregisterLoading(LoadingWork.Bootstrap);
// TODO: поменяно для теста
_appStateMachine _appStateMachine
.SwitchState(AppState.MainMenu) .SwitchState(AppState.MainMenu)
.AllowStateTransition(AppState.Gameplay); .AllowStateTransition(AppState.Gameplay);
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 08b54658296d4279b85a2bfc3cf10e62
timeCreated: 1763842256
@@ -1,16 +0,0 @@
using System.Collections.ObjectModel;
using Cysharp.Threading.Tasks;
namespace Code.App.Loading
{
public interface ILoadingManager
{
delegate void OnLoadingDelegate();
event OnLoadingDelegate OnLoadingStarted;
event OnLoadingDelegate OnLoadingEnded;
ReadOnlyCollection<LoadingWork> LoadingThings { get; }
LoadingManager RegisterLoading(LoadingWork loadingThingName);
LoadingManager UnregisterLoading(LoadingWork loadingThingName);
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: bfb3837590d04da3b04ca8b0f0aca437
timeCreated: 1763842985
@@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Cysharp.Threading.Tasks;
namespace Code.App.Loading
{
public class LoadingManager : ILoadingManager
{
public event ILoadingManager.OnLoadingDelegate OnLoadingStarted;
public event ILoadingManager.OnLoadingDelegate OnLoadingEnded;
private List<LoadingWork> _loadingThings = new();
public ReadOnlyCollection<LoadingWork> LoadingThings => _loadingThings.AsReadOnly();
public LoadingManager RegisterLoading(LoadingWork loadingThingName)
{
if(_loadingThings.Count == 0) OnLoadingStarted?.Invoke();
_loadingThings.Add(loadingThingName);
return this;
}
public LoadingManager UnregisterLoading(LoadingWork loadingThingName)
{
_loadingThings.Remove(loadingThingName);
if(_loadingThings.Count == 0) OnLoadingEnded?.Invoke();
return this;
}
}
// public static class LoadingManagerExtensions
// {
// public static LoadingManager AddLoading(this LoadingManager manager, LoadingWork loadingThingName)
// {
// manager.RegisterLoading(loadingThingName);
// return manager;
// }
// public static LoadingManager RemoveLoading(this LoadingManager manager, LoadingWork loadingThingName)
// {
// manager.UnregisterLoading(loadingThingName);
// return manager;
// }
// }
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 185988ac74a24a39864238cd7f5cb626
timeCreated: 1763842268
@@ -1,12 +0,0 @@
namespace Code.App.Loading
{
public enum LoadingWork
{
Bootstrap,
LoadUI,
LoadAudio,
LoadScene,
LoadModelResource,
WaitingForTime
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 318d1b29b41d4b96bc09334acbf36506
timeCreated: 1763843934
@@ -1,56 +0,0 @@
using Code.App.Loading;
using Code.Gameplay.Enemies;
using Code.Gameplay.Orders.Order;
using Code.Gameplay.Player;
using Code.Generator;
using Code.Scenes;
using Code.Scenes.SceneEnvironments;
using Cysharp.Threading.Tasks;
using Reflex.Core;
using UnityEngine;
using IPlayerScore = Code.Gameplay.Player.IPlayerScore;
namespace Code.App.SceneInstallers
{
public class GameplaySceneInstaller : MonoBehaviour, IInstaller
{
[SerializeField] private GameplaySceneEnvironment _gameplaySceneEnvironment;
public void InstallBindings(ContainerBuilder containerBuilder)
{
containerBuilder.AddScoped(typeof(GameplaySceneEnvironment), typeof(CurrentSceneEnvironment));
containerBuilder.AddScoped(typeof(PlayerScore), typeof(IPlayerScore));
containerBuilder.AddScoped(typeof(OrderService), typeof(IOrderService));
containerBuilder.AddScoped(typeof(RoomGeneratorFactory), typeof(IRoomGeneratorFactory));
containerBuilder.AddScoped(typeof(PlayerProvider), typeof(IPlayerProvider));
containerBuilder.AddScoped(typeof(PlayerFactory), typeof(IPlayerFactory));
containerBuilder.AddScoped(typeof(EnemyFactory), typeof(IEnemyFactory));
containerBuilder.AddScoped(typeof(RoomGenerator));
containerBuilder.OnContainerBuilt += Initialize;
}
private void Initialize(Container container)
{
ILoadingManager loadingManager = container.Resolve<ILoadingManager>();
GameplaySceneEnvironment env = (GameplaySceneEnvironment)container.Resolve<CurrentSceneEnvironment>();
env.TransitVariables(_gameplaySceneEnvironment);
loadingManager
.UnregisterLoading(LoadingWork.LoadScene);
LoadRooms(container,loadingManager).Forget();
}
private async UniTaskVoid LoadRooms(Container container, ILoadingManager loadingManager)
{
// await container.Resolve<IRoomGeneratorFactory>().PreloadRooms(container.Resolve<IOrderService>());
// container.Resolve<RoomGenerator>().Initialize();
loadingManager.UnregisterLoading(LoadingWork.LoadModelResource);
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 88584fd4361b46f6a7326440cd95ce69
timeCreated: 1763144580
@@ -1,8 +1,7 @@
using Code.App.Loading;
using Code.Scenes; using Code.Scenes;
using Code.Scenes.SceneEnvironments; using Code.Scenes.SceneEnvironments;
using Reflex.Core; using Reflex.Core;
using UnityEngine; using UnityEngine;
namespace Code.App.SceneInstallers namespace Code.App.SceneInstallers
{ {
@@ -22,11 +21,6 @@ namespace Code.App.SceneInstallers
MainMenuEnvironment env = (MainMenuEnvironment)container.Resolve<CurrentSceneEnvironment>(); MainMenuEnvironment env = (MainMenuEnvironment)container.Resolve<CurrentSceneEnvironment>();
env.TransitVariables(_mainMenuEnvironment); env.TransitVariables(_mainMenuEnvironment);
ILoadingManager loadingManager = container.Resolve<ILoadingManager>();
loadingManager
.UnregisterLoading(LoadingWork.LoadScene);
} }
} }
} }
@@ -1,9 +1,5 @@
using Code.App.Loading;
using Code.Audio;
using Code.Gameplay.Camera; using Code.Gameplay.Camera;
using Code.Gameplay.Enemies;
using Code.Gameplay.Player; using Code.Gameplay.Player;
using Code.Generator;
using Code.Input; using Code.Input;
using Code.Scenes; using Code.Scenes;
using Code.UI; using Code.UI;
@@ -13,16 +9,11 @@ namespace Code.App.StateMachine
{ {
public class AppStateMachineContext public class AppStateMachineContext
{ {
[Inject] public ILoadingManager LoadingManager { get; private set; }
[Inject] public ISceneLoaderService SceneLoaderService { get; private set; } [Inject] public ISceneLoaderService SceneLoaderService { get; private set; }
[Inject] public IInputService InputService { get; private set; } [Inject] public IInputService InputService { get; private set; }
[Inject] public AppStateMachine StateMachine { get; private set; } [Inject] public AppStateMachine StateMachine { get; private set; }
[Inject] public CameraStateMachine CameraStateMachine { get; private set; } [Inject] public CameraStateMachine CameraStateMachine { get; private set; }
[Inject] public IUIService UIService { get; private set; } [Inject] public IUIService UIService { get; private set; }
[Inject] public IAudioFactory AudioFactory { get; private set; } [Inject] public IPlayerFactory PlayerFactory { get; private set; }
[Inject] public IAudioService AudioService { get; private set; }
/*[Inject]*/public IPlayerFactory PlayerFactory { get; set; }
/*[Inject]*/public IPlayerProvider PlayerProvider { get; set; }
// [Inject] public IEnemyFactory EnemyFactory { get; private set; }
} }
} }
@@ -1,39 +0,0 @@
namespace Code.Audio
{
public enum SFXSounds
{
Crowbar,
Shot357
}
public enum UISounds
{
Crowbar
}
public enum EnemiesSounds
{
GrandpaAttack,
RoachAttack,
RoachStep,
GrandpaStep
}
public enum DispatcherSounds
{
Recieve1,
Recieve2,
TakeDamage1,
TakeDamage2,
TakeDamage3,
TakeDamage4,
Late1,
Late2,
Late3,
Late4,
Late5
}
public enum CourierSounds
{
Step,
Damage1,
Damage2
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 907b506ceac843ebb509a8e5298af540
timeCreated: 1762888870
@@ -1,224 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Code.Configs;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Audio;
using UnityEngine.ResourceManagement.AsyncOperations;
using Object = UnityEngine.Object;
namespace Code.Audio
{
public class AudioFactory : IAudioFactory
{
public AudioMixer AudioMixer { get; private set; }
private AssetReference _audioSourcePrefab;
private AssetReference[] _audioTestClipsReferences;
private AssetReference[] _peacefulSoundtrack;
private AssetReference[] _actionSoundtrack;
private AssetReference[] _deathSoundtrack;
private AssetReference[] _menuSoundtrack;
private AssetReference[] _SFX;
private AssetReference[] _enemies;
private AssetReference[] _dispatcher;
private AssetReference[] _courier;
private AsyncOperationHandle<IList<AudioClip>> _audioTestClipsHandle;
private AsyncOperationHandle<IList<AudioClip>> _menuMusicHandle;
private AsyncOperationHandle<IList<AudioClip>> _deathMusicHandle;
private AsyncOperationHandle<IList<AudioClip>> _actionMusicHandle;
private AsyncOperationHandle<IList<AudioClip>> _peacefulMusicHandle;
private AsyncOperationHandle<IList<AudioClip>> _SFXHandle;
private AsyncOperationHandle<IList<AudioClip>> _enemiesHandle;
private AsyncOperationHandle<IList<AudioClip>> _dispatcherHandle;
private AsyncOperationHandle<IList<AudioClip>> _courierHandle;
public Dictionary<AudioType, AudioClip> AudioTestClips { get; private set; } = new();
public Dictionary<MusicType, List<AudioClip>> MusicDictionary { get; private set; } = new();
public List<AudioClip> SFXAudios { get; private set; } = new();
public List<AudioClip> EnemiesAudios { get; private set; } = new();
public List<AudioClip> CourierAudios { get; private set; } = new();
public List<AudioClip> DispatcherAudios { get; private set; } = new();
public AudioFactory(AudioMixerConfig audioMixerConfig,
SoundtrackListConfig soundtrackListConfig,
SFXListConfig sfxListConfig,
EnemiesSoundListConfig enemiesSoundListConfig,
DispatcherSoundListConfig dispatcherSoundListConfig,
AudioTestClipsConfig audioTestClipsConfig,
CourierSoundListConfig courierSoundListConfig
)
{
AudioMixer = audioMixerConfig.audioMixerAsset;
_audioSourcePrefab = audioMixerConfig.audioSourcePrefab;
_peacefulSoundtrack = soundtrackListConfig.peacefulSoundtrack;
_actionSoundtrack = soundtrackListConfig.actionSoundtrack;
_deathSoundtrack = soundtrackListConfig.deathSoundtrack;
_menuSoundtrack = soundtrackListConfig.menuSoundtrack;
_SFX = sfxListConfig.SFX;
_enemies = enemiesSoundListConfig.enemies;
_dispatcher = dispatcherSoundListConfig.dispatcher;
_courier = courierSoundListConfig.courierSounds;
_audioTestClipsReferences = audioTestClipsConfig.audioTestClips;
MusicDictionary.Add(MusicType.Menu, new ());
MusicDictionary.Add(MusicType.Death, new ());
MusicDictionary.Add(MusicType.Action, new ());
MusicDictionary.Add(MusicType.Peaceful, new ());
}
public async UniTask LoadAudioSourcePrefab(Action<GameObject> callback) =>
callback?.Invoke(await Addressables.LoadAssetAsync<GameObject>(_audioSourcePrefab));
public async UniTask WarmGameplayMusic( )
{
_actionMusicHandle = Addressables.LoadAssetsAsync<AudioClip>(_actionSoundtrack, OnActionTrackLoaded, Addressables.MergeMode.Union);
_peacefulMusicHandle = Addressables.LoadAssetsAsync<AudioClip>(_peacefulSoundtrack, OnPeacefulTrackLoaded, Addressables.MergeMode.Union);
await UniTask.WhenAll(_actionMusicHandle.ToUniTask(), _peacefulMusicHandle.ToUniTask());
}
public void ReleaseActionMusic()
{
MusicDictionary[MusicType.Action].Clear();
_actionMusicHandle.Release();
}
public void ReleasePeacefulMusic()
{
MusicDictionary[MusicType.Peaceful].Clear();
_peacefulMusicHandle.Release();
}
public async UniTask WarmAudioTestClips( )
{
// _audioTestClipCounter = 0;
_audioTestClipsHandle = Addressables.LoadAssetsAsync<AudioClip>(_audioTestClipsReferences, OnAudioTestClipLoaded, Addressables.MergeMode.Union);
// foreach (var assetReference in _menuSoundtrack) MenuMusic.Add(await assetReference.LoadAssetAsync<AudioClip>());
var clips = (await _audioTestClipsHandle.ToUniTask()).ToList();
for (int i = 0; i < clips.Count; i++)
AudioTestClips[(AudioType)i] = clips[i];
}
public void ReleaseAudioTestClips()
{
AudioTestClips.Clear();
// foreach (var assetReference in _menuSoundtrack) assetReference.ReleaseAsset();
_audioTestClipsHandle.Release();
}
public async UniTask WarmMenuMusic( )
{
_menuMusicHandle = Addressables.LoadAssetsAsync<AudioClip>(_menuSoundtrack, OnMenuTrackLoaded, Addressables.MergeMode.Union);
// foreach (var assetReference in _menuSoundtrack) MenuMusic.Add(await assetReference.LoadAssetAsync<AudioClip>());
await _menuMusicHandle.ToUniTask();
}
public void ReleaseMenuMusic()
{
MusicDictionary[MusicType.Menu].Clear();
// foreach (var assetReference in _menuSoundtrack) assetReference.ReleaseAsset();
_menuMusicHandle.Release();
}
public async UniTask WarmDeathMusic()
{
_deathMusicHandle = Addressables.LoadAssetsAsync<AudioClip>(_deathSoundtrack, OnDeathTrackLoaded, Addressables.MergeMode.Union);
await _deathMusicHandle.ToUniTask();
}
public void ReleaseDeathMusic()
{
MusicDictionary[MusicType.Death].Clear();
_deathMusicHandle.Release();
}
public async UniTask WarmSFX( )
{
_SFXHandle = Addressables.LoadAssetsAsync<AudioClip>(_SFX, OnSFXLoaded, Addressables.MergeMode.Union);
await _SFXHandle.ToUniTask();
}
public void ReleaseSFX()
{
SFXAudios.Clear();
_SFXHandle.Release();
}
public async UniTask WarmEnemies( )
{
_enemiesHandle = Addressables.LoadAssetsAsync<AudioClip>(_enemies, OnEnemiesLoaded, Addressables.MergeMode.Union);
await _enemiesHandle.ToUniTask();
}
public void ReleaseEnemies()
{
EnemiesAudios.Clear();
_enemiesHandle.Release();
}
public async UniTask WarmCourier()
{
_courierHandle = Addressables.LoadAssetsAsync<AudioClip>(_courier, OnCourierLoaded, Addressables.MergeMode.Union);
await _courierHandle.ToUniTask();
}
public void ReleaseCourier()
{
CourierAudios.Clear();
_courierHandle.Release();
}
public async UniTask WarmDispatcher( )
{
_dispatcherHandle = Addressables.LoadAssetsAsync<AudioClip>(_dispatcher, OnDispatcherLoaded, Addressables.MergeMode.Union);
await _dispatcherHandle.ToUniTask();
}
public void ReleaseDispatcher()
{
DispatcherAudios.Clear();
_dispatcherHandle.Release();
}
private void OnMenuTrackLoaded(AudioClip audioClip)
{
MusicDictionary[MusicType.Menu].Add(audioClip);
}
private void OnDeathTrackLoaded(AudioClip audioClip)
{
MusicDictionary[MusicType.Death].Add(audioClip);
}
// private int _audioTestClipCounter;
private void OnAudioTestClipLoaded(AudioClip audioClip)
{
// AudioTestClips[(AudioType)_audioTestClipCounter] = audioClip;_audioTestClipCounter++;
}
private void OnActionTrackLoaded(AudioClip audioClip)
{
MusicDictionary[MusicType.Action].Add(audioClip);
}
private void OnPeacefulTrackLoaded(AudioClip audioClip)
{
MusicDictionary[MusicType.Peaceful].Add(audioClip);
}
private void OnSFXLoaded(AudioClip audioClip)
{
SFXAudios.Add(audioClip);
}
private void OnEnemiesLoaded(AudioClip audioClip)
{
EnemiesAudios.Add(audioClip);
}
private void OnDispatcherLoaded(AudioClip audioClip)
{
DispatcherAudios.Add(audioClip);
}
private void OnCourierLoaded(AudioClip audioClip)
{
CourierAudios.Add(audioClip);
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5a4b48740d194d4dafa1732f96d71687
timeCreated: 1762626915
@@ -1,260 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Code.Configs;
using Code.Constants;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using Reflex.Attributes;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Audio;
using Random = UnityEngine.Random;
namespace Code.Audio
{
public class AudioService : IAudioService
{
private IAudioFactory _audioFactory;
private AudioMixer _mixer;
private AudioSource _actionMusicSource;
private AudioSource _peacefulMusicSource;
private Dictionary<AudioType, AudioMixerGroup> _audioMixerGroups = new();
private readonly AudioSourcePool _audioSourcePool;
// Юнити не может - говнокод поможет.
private Dictionary<AudioSnapshotState, Dictionary<AudioType, float>> _snapshotsVolumes = new();
private Dictionary<AudioSourceType, AudioSourceSettingsConfig> _settings;
public AudioService(IAudioFactory audioFactory,
Dictionary<AudioSourceType, AudioSourceSettingsConfig> settings,
DefaultAudioSettingsConfig defaultAudioSettingsConfig)
{
_audioFactory = audioFactory;
_audioSourcePool = new(settings);
_settings = settings;
_snapshotsVolumes.Add(AudioSnapshotState.None,defaultAudioSettingsConfig.noneVolume);
_snapshotsVolumes.Add(AudioSnapshotState.Menu,defaultAudioSettingsConfig.menuVolume);
_snapshotsVolumes.Add(AudioSnapshotState.Action,defaultAudioSettingsConfig.actionVolume);
_snapshotsVolumes.Add(AudioSnapshotState.Peaceful,defaultAudioSettingsConfig.peacefulVolume);
}
public float GetSetting(AudioType type)
{
_mixer.GetFloat(_audioMixerGroups[type].name, out float value);
value = Mathf.Pow(10f, value / 20);
return value;
}
public void ChangeSetting(AudioType type, float value)
{
float volume = Mathf.Log10(value) * 20;
switch (type)
{
case AudioType.DeathMusic:
case AudioType.Master:
_snapshotsVolumes[AudioSnapshotState.Menu][type] = value;
_snapshotsVolumes[AudioSnapshotState.Action][type] = value;
_snapshotsVolumes[AudioSnapshotState.Peaceful][type] = value;
_mixer.SetFloat(_audioMixerGroups[type].name, volume);
break;
case AudioType.ActionMusic:
_snapshotsVolumes[AudioSnapshotState.Action][type] = value;
_mixer.SetFloat(_audioMixerGroups[type].name, volume);
break;
case AudioType.PeacefulMusic:
_snapshotsVolumes[AudioSnapshotState.Peaceful][type] = value;
_mixer.SetFloat(_audioMixerGroups[type].name, volume);
break;
case AudioType.MenuMusic:
_snapshotsVolumes[AudioSnapshotState.Menu][type] = value;
_mixer.SetFloat(_audioMixerGroups[type].name, volume);
break;
case AudioType.SFX:
case AudioType.Dispatcher:
case AudioType.Courier:
case AudioType.Enemies:
_snapshotsVolumes[AudioSnapshotState.Action][type] = value;
_snapshotsVolumes[AudioSnapshotState.Peaceful][type] = value;
_mixer.SetFloat(_audioMixerGroups[type].name, volume);
break;
}
}
public async UniTask Initialize()
{
_mixer = _audioFactory.AudioMixer;
_musicAudioGroups.Add(MusicType.Menu, _mixer.FindMatchingGroups(AudioGroups.MenuMusicGroup)[0]);
_musicAudioGroups.Add(MusicType.Death, _mixer.FindMatchingGroups(AudioGroups.DeathMusicGroup)[0]);
_musicAudioGroups.Add(MusicType.Action, _mixer.FindMatchingGroups(AudioGroups.ActionMusicGroup)[0]);
_musicAudioGroups.Add(MusicType.Peaceful, _mixer.FindMatchingGroups(AudioGroups.PeacefulMusicGroup)[0]);
_cancellationTokenSources.Add(MusicType.Menu, new());
_cancellationTokenSources.Add(MusicType.Death, new());
_cancellationTokenSources.Add(MusicType.Action, new());
_cancellationTokenSources.Add(MusicType.Peaceful, new());
_musicAudioSources.Add(MusicType.Menu, null);
_musicAudioSources.Add(MusicType.Death, null);
_musicAudioSources.Add(MusicType.Action, null);
_musicAudioSources.Add(MusicType.Peaceful, null);
_audioMixerGroups.Add(AudioType.Master, _mixer.FindMatchingGroups(AudioGroups.MasterGroup)[0]);
_audioMixerGroups.Add(AudioType.SFX, _mixer.FindMatchingGroups(AudioGroups.SFXGroup)[0]);
_audioMixerGroups.Add(AudioType.Dispatcher, _mixer.FindMatchingGroups(AudioGroups.DispatcherMusicGroup)[0]);
_audioMixerGroups.Add(AudioType.Courier, _mixer.FindMatchingGroups(AudioGroups.CourierGroup)[0]);
_audioMixerGroups.Add(AudioType.Enemies, _mixer.FindMatchingGroups(AudioGroups.EnemiesGroup)[0]);
_audioMixerGroups.Add(AudioType.MenuMusic, _musicAudioGroups[MusicType.Menu]);
_audioMixerGroups.Add(AudioType.DeathMusic, _musicAudioGroups[MusicType.Death]);
_audioMixerGroups.Add(AudioType.ActionMusic, _musicAudioGroups[MusicType.Action]);
_audioMixerGroups.Add(AudioType.PeacefulMusic, _musicAudioGroups[MusicType.Peaceful]);
await _audioFactory.LoadAudioSourcePrefab(_audioSourcePool.Initialize);
}
private Dictionary<MusicType, CancellationTokenSource> _cancellationTokenSources = new();
private Dictionary<MusicType, AudioSource> _musicAudioSources = new();
private Dictionary<MusicType, AudioMixerGroup> _musicAudioGroups = new();
public async UniTaskVoid PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType musicType)
{
if (!_musicAudioSources[musicType])
{
_musicAudioSources[musicType] = _audioSourcePool.GetSource();
_musicAudioSources[musicType].spatialBlend = _settings[AudioSourceType.NoBlend].spatialBlend;
_musicAudioSources[musicType].outputAudioMixerGroup = _musicAudioGroups[musicType];
}
_musicAudioSources[musicType].resource =
_audioFactory.MusicDictionary[musicType][
Random.Range(0, _audioFactory.MusicDictionary[musicType].Count)];
_musicAudioSources[musicType].Play();
await UniTask.NextFrame();
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(_musicAudioSources[musicType],
_ => !_musicAudioSources[musicType].isPlaying,
cancellationToken: _cancellationTokenSources[musicType].Token).SuppressCancellationThrow();
Debug.LogError("Music skipping");
if (isCanceled)
{
_cancellationTokenSources[musicType].Dispose();
_cancellationTokenSources[musicType] = new();
_audioSourcePool.ReleaseSource(_musicAudioSources[musicType]);
_musicAudioSources[musicType] = null;
return;
}
PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(musicType).Forget();
}
public async UniTaskVoid GladlyAskToStopMusic(MusicType musicType,
AudioSnapshotState audioSnapshotState = AudioSnapshotState.None, float time = 1f, Action callback = null)
{
//TransitToSnapshot(audioSnapshotState, time);
await UniTask.WaitForSeconds(time);
_cancellationTokenSources[musicType].Cancel();
callback?.Invoke();
}
public void StopMusic()
{
_peacefulMusicSource.Stop();
_actionMusicSource.Stop();
_cancellationTokenSources[MusicType.Peaceful].Cancel();
_cancellationTokenSources[MusicType.Action].Cancel();
}
public void GladlyAskToStopMusic(Action callback = null)
{
// TransitToSnapshot(AudioSnapshotState.None);
_peacefulMusicSource.Stop();
_actionMusicSource.Stop();
callback?.Invoke();
}
public void TransitToSnapshot(AudioSnapshotState state, float time = 1)
{
foreach (var key in _audioMixerGroups.Keys)
_mixer.DOSetFloat(_audioMixerGroups[key].name, Mathf.Log10(_snapshotsVolumes[state][key]) * 20, time);
}
public void PlaySFX(SFXSounds sfxId, Vector3 position)
{
_audioSourcePool
.PlayOneShot(_audioFactory.SFXAudios[(int)sfxId], position, _audioMixerGroups[AudioType.SFX]).Forget();
}
public void ApplyStun(float value, float time)
{
_mixer.DOSetFloat("Stun", value, time);
}
public void PlayEnemy(EnemiesSounds enemySoundId, Vector3 position)
{
_audioSourcePool.PlayOneShot(_audioFactory.EnemiesAudios[(int)enemySoundId], position,
_audioMixerGroups[AudioType.Enemies]).Forget();
}
public AudioSource GetSourceOfGroup(AudioType group)
{
AudioSource source = _audioSourcePool.GetSource();
source.outputAudioMixerGroup = _audioMixerGroups[group];
return source;
}
public void ReleaseSourceOfGroup(AudioSource source) => _audioSourcePool.ReleaseSource(source);
// Чинить долго и дорого пусть Артёмов будет много, ведь он наш друг
public event IAudioService.OnAudioPlayingDelegate OnDispatcherStarted;
public event IAudioService.OnAudioPlayingDelegate OnDispatcherEnded;
public void PlayDispatcher(DispatcherSounds dispatcherSoundId)
{
OnDispatcherStarted?.Invoke();
_audioSourcePool.PlayOneShot(_audioFactory.DispatcherAudios[(int)dispatcherSoundId],
_audioMixerGroups[AudioType.Dispatcher],
AudioSourceType.NoBlend, () => OnDispatcherEnded?.Invoke()).Forget();
}
public void PlayCourier(CourierSounds courierSounds)
{
_audioSourcePool.PlayOneShot(_audioFactory.CourierAudios[(int)courierSounds],
_audioMixerGroups[AudioType.Courier],
AudioSourceType.NoBlend).Forget();
}
private CancellationTokenSource _testClipCancellationTokenSource = new();
private void ResetCurrentTestAudioSource() => _currentTestClipAudioType = AudioType.Master;
private AudioType _currentTestClipAudioType;
// TODO Засунуть в словарь
public void PlayTestClip(AudioType audioType)
{
if (_currentTestClipAudioType == audioType) return;
_testClipCancellationTokenSource.Cancel();
_testClipCancellationTokenSource = new();
_currentTestClipAudioType = audioType;
_audioSourcePool.PlayOneShot(_audioFactory.AudioTestClips[audioType],
_audioMixerGroups[audioType], _testClipCancellationTokenSource,
AudioSourceType.NoBlend, ResetCurrentTestAudioSource).Forget();
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: bf44cb051308463494bd930f2a3d31e4
timeCreated: 1762625073
@@ -1,10 +0,0 @@
namespace Code.Audio
{
public enum AudioSnapshotState
{
None,
Peaceful,
Action,
Menu
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 29adaf8e912649d9b91c09735e36840a
timeCreated: 1762808301
@@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Code.Configs;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
namespace Code.Audio
{
public class AudioSourcePool
{
// The pool holds plain GameObjects (you can swap this for any component type).
private IObjectPool<AudioSource> _pool;
private GameObject _sourcePrefab;
private Dictionary<AudioSourceType, AudioSourceSettingsConfig> _settings;
public AudioSourcePool(Dictionary<AudioSourceType, AudioSourceSettingsConfig> settings) => _settings = settings;
public void Initialize(GameObject sourcePrefab)
{
_sourcePrefab = sourcePrefab;
// Create a pool with the four core callbacks.
_pool = new ObjectPool<AudioSource>(
createFunc: CreateItem,
// actionOnGet: OnGet,
actionOnRelease: OnRelease,
actionOnDestroy: OnDestroyItem,
collectionCheck: true, // helps catch double-release mistakes
defaultCapacity: 10,
maxSize: 20
);
}
// Creates a new pooled GameObject the first time (and whenever the pool needs more).
private AudioSource CreateItem()
{
AudioSource audioSource = Object.Instantiate(_sourcePrefab).GetComponent<AudioSource>();
audioSource.name = "PooledAudioSource";
Object.DontDestroyOnLoad(audioSource);
return audioSource;
}
// Called when an item is taken from the pool.
private void OnGet(AudioSource source)
{
//source.gameObject.SetActive(true);
}
// Called when an item is returned to the pool.
private void OnRelease(AudioSource source)
{
source.Stop();
//source.gameObject.SetActive(false);
}
// Called when the pool decides to destroy an item (e.g., above max size).
private void OnDestroyItem(AudioSource source)
{
Object.Destroy(source.gameObject);
}
public async UniTaskVoid PlayOneShot(AudioClip clip, Vector3 position, AudioMixerGroup audioMixerGroup, AudioSourceType audioSourceType = AudioSourceType.NoBlend, Action onComplete = null)
{
AudioSource source = _pool.Get();
source.PlayOneShot(clip);
source.spatialBlend = _settings[audioSourceType].spatialBlend;
source.transform.position = position;
source.outputAudioMixerGroup = audioMixerGroup;
await UniTask.WaitUntilValueChanged(source, x => x.isPlaying);
onComplete?.Invoke();
_pool.Release(source);
}
public async UniTaskVoid PlayOneShot(AudioClip clip, AudioMixerGroup audioMixerGroup, AudioSourceType audioSourceType = AudioSourceType.NoBlend, Action onComplete = null)
{
AudioSource source = _pool.Get();
source.PlayOneShot(clip);
source.spatialBlend = _settings[audioSourceType].spatialBlend;
source.outputAudioMixerGroup = audioMixerGroup;
await UniTask.WaitUntilValueChanged(source, x => x.isPlaying);
onComplete?.Invoke();
_pool.Release(source);
}
public async UniTaskVoid PlayOneShot(AudioSource audioSource, AudioClip clip, AudioMixerGroup audioMixerGroup, AudioSourceType audioSourceType = AudioSourceType.NoBlend, Action onComplete = null)
{
audioSource.PlayOneShot(clip);
audioSource.spatialBlend = _settings[audioSourceType].spatialBlend;
audioSource.outputAudioMixerGroup = audioMixerGroup;
// TODO Потом сделать. Можно с cancellation, но сейчас лень. Ну правда
await UniTask.WaitUntilValueChanged(audioSource, x => x.isPlaying);
if (!audioSource) return;
_pool.Release(audioSource);
onComplete?.Invoke();
}
public async UniTaskVoid PlayOneShot(AudioClip clip, AudioMixerGroup audioMixerGroup, CancellationTokenSource cancellationToken, AudioSourceType audioSourceType = AudioSourceType.NoBlend, Action onComplete = null)
{
AudioSource source = _pool.Get();
source.PlayOneShot(clip);
source.spatialBlend = _settings[audioSourceType].spatialBlend;
source.outputAudioMixerGroup = audioMixerGroup;
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(source, x => x.isPlaying, cancellationToken: cancellationToken.Token).SuppressCancellationThrow();
_pool.Release(source);
if(!isCanceled) onComplete?.Invoke();
else cancellationToken.Dispose();
}
public AudioSource GetSource() => _pool.Get();
public void ReleaseSource(AudioSource source) => _pool.Release(source);
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 370b6a43a74744ff9d90a99991b104f7
timeCreated: 1762852042
@@ -1,8 +0,0 @@
namespace Code.Audio
{
public enum AudioSourceType
{
Full3D,
NoBlend
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 260bbb19b55049928b9ab4d55b32fc3f
timeCreated: 1763849966
@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Audio;
namespace Code.Audio
{
public interface IAudioFactory
{
AudioMixer AudioMixer { get; }
Dictionary<AudioType, AudioClip> AudioTestClips { get; }
Dictionary<MusicType, List<AudioClip>> MusicDictionary { get; }
List<AudioClip> SFXAudios { get; }
List<AudioClip> EnemiesAudios { get; }
List<AudioClip> DispatcherAudios { get; }
List<AudioClip> CourierAudios { get; }
UniTask LoadAudioSourcePrefab(Action<GameObject> callback);
UniTask WarmGameplayMusic( );
void ReleaseActionMusic();
void ReleasePeacefulMusic();
UniTask WarmAudioTestClips( );
void ReleaseAudioTestClips();
UniTask WarmMenuMusic( );
void ReleaseMenuMusic();
UniTask WarmSFX( );
void ReleaseSFX();
UniTask WarmEnemies( );
void ReleaseEnemies();
UniTask WarmDispatcher( );
void ReleaseDispatcher();
UniTask WarmCourier();
void ReleaseCourier();
UniTask WarmDeathMusic( );
void ReleaseDeathMusic();
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ea837e1ca08a4846b7f4ce2df771bf65
timeCreated: 1762804793
@@ -1,31 +0,0 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace Code.Audio
{
public interface IAudioService
{
UniTask Initialize();
UniTaskVoid PlayRandomMusicUntilHumanityWillBeDestroyedForTheySins(MusicType musicType);
UniTaskVoid GladlyAskToStopMusic(MusicType musicType, AudioSnapshotState audioSnapshotState = AudioSnapshotState.None, float time = 1f, Action callback = null);
void StopMusic();
void GladlyAskToStopMusic(Action callback = null);
void TransitToSnapshot(AudioSnapshotState state, float time = 1f);
void PlaySFX(SFXSounds sfxId, Vector3 position);
void PlayEnemy(EnemiesSounds enemySoundId, Vector3 position);
delegate void OnAudioPlayingDelegate();
event OnAudioPlayingDelegate OnDispatcherStarted;
event OnAudioPlayingDelegate OnDispatcherEnded;
void PlayDispatcher(DispatcherSounds dispatcherSoundId);
void ChangeSetting(AudioType type, float value);
float GetSetting(AudioType type);
void PlayTestClip(AudioType audioType);
AudioSource GetSourceOfGroup(AudioType group);
void ReleaseSourceOfGroup(AudioSource source);
void PlayCourier(CourierSounds range);
void ApplyStun(float value, float time);
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 30521fadcce84848ae88363e915bceb4
timeCreated: 1762804728
@@ -1,22 +0,0 @@
namespace Code.Audio
{
public enum MusicType
{
Menu,
Peaceful,
Action,
Death
}
public enum AudioType
{
Master,
MenuMusic,
Dispatcher,
SFX,
Courier,
Enemies,
ActionMusic,
PeacefulMusic,
DeathMusic
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 74ff08d5f1b741f69e37fc06fb07ae8d
timeCreated: 1763151897
@@ -1,13 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Audio;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/Mixer", fileName = "AudioMixerConfig")]
public class AudioMixerConfig : ScriptableObject
{
public AudioMixer audioMixerAsset;
public AssetReference audioSourcePrefab;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 353cee8946ca43efbbcda905d7b32ae1
timeCreated: 1762624627
@@ -1,14 +0,0 @@
using Code.Audio;
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(fileName = "AudioSourceSettingsConfig", menuName = "Audio/AudioSourceSettingsConfig")]
public class AudioSourceSettingsConfig : ScriptableObject
{
// Дополнять по мере необходимости
public float spatialBlend;
public AudioSourceType sourceType;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e6406ab390b74d5ca336d7d4d0e1a732
timeCreated: 1763850008
@@ -1,11 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/AudioTestClips", fileName = "AudioTestClipsConfig")]
public class AudioTestClipsConfig : ScriptableObject
{
public AssetReference[] audioTestClips;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f2f1c08a20a54a708336caf8ca6499e1
timeCreated: 1764263243
@@ -1,14 +0,0 @@
using Code.Gameplay.Camera;
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(fileName = "CameraSettingsConfig", menuName = "Config/CameraSettingsConfig")]
public class CameraSettingsConfig : ScriptableObject
{
// Дополнять по мере необходимости
public float fov;
public CameraMode cameraMode;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6610b94e9823401c94533e1756a97195
timeCreated: 1763846744
@@ -5,42 +5,37 @@ namespace Code.Configs
[CreateAssetMenu(fileName = "CharacterConfig", menuName = "Config/CharacterConfig")] [CreateAssetMenu(fileName = "CharacterConfig", menuName = "Config/CharacterConfig")]
public class CharacterConfig : ScriptableObject public class CharacterConfig : ScriptableObject
{ {
// Основное движение по земле
public float public float
maxStableMoveSpeed = 12f, maxStableMoveSpeed = 8f, // Максимальная скорость движения по устойчивой поверхности
stableMovementSharpness = 15f, stableMovementSharpness = 15f, // Резкость изменения скорости движения (чем выше - тем резче)
orientationSharpness = 10f, orientationSharpness = 10f, // Резкость изменения вращения персонажа (чем выше - тем резче)
jumpSpeed = 10f; jumpSpeed = 10f; // Скорость прыжка
public float // Спринт
dashSpeed = 20f, public float
dashDuration = 0.35f, sprintMoveSpeed = 12f, // Максимальная скорость спринта
dashCooldown = 1.2f; sprintAngleThreshold = 90f; // Максимальный угол (в градусах) от направления вперёд, при котором разрешён спринт
public AnimationCurve slideVelocityCurve = new(
new Keyframe(0.0f, 1.0f),
new Keyframe(0.3f, 1.4f),
new Keyframe(1.0f, 0.2f)
);
public float
slideMinVelocityThreshold = 3f,
slideCooldown = 1.5f;
public float // Слайд
leanAngle = 25f, public float
leanThreshold = 0.3f, slideSpeed = 20f, // Скорость слайда
leanSmoothSpeed = 10f; slideDuration = 0.8f, // Длительность слайда в секундах
slideCooldown = 1.5f; // Время перезарядки слайда в секундах
public Vector3 gravity = new(0f, -30f, 0f); // Залазание на стены
public float public float
maxAirSpeed = 10f, wallClimbSpeedFactor = 0.8f, // Коэффициент преобразования горизонтальной скорости в вертикальную при залазании на стену
airControlSharpness = 8f; minWallClimbSpeed = 9f, // Минимальная горизонтальная скорость для начала залазания на стену
wallDetectionAngleThreshold = 80f; // Максимальный угол (в градусах) от вертикали, при котором поверхность считается "стеной"
public float // Наклон при залазании на стены
wallRunMaxTime = 1.75f, public float
wallRunSpeed = 15f, maxWallLeanAngle = 45f, // Максимальный угол наклона от стены (в градусах)
wallRunAcceleration = 25f, wallLeanSharpness = 50f, // Скорость наклона/выпрямления (чем выше — тем резче)
wallRunGravityScale = 0.25f, wallLeanHeightScale = 0.1f; // Множитель: на сколько метров подъёма = 1 единица "наклона"
wallDetachImpulse = 7f,
wallContactAngleThreshold = 45f; // Гравитация
public Vector3 gravity = new(0f, -30f, 0f); // Вектор, отвечающий за применение гравитации
} }
} }
@@ -1,29 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Enemies/Cockroach", fileName = "CockroachConfig")]
public class CockroachConfig : ScriptableObject
{
public AssetReference cockroachPrefab;
public int damage = 2;
public float attackCooldown = 1.5f;
public float detectionRange = 20f;
public float attackRange = 1.3f;
public float speed = 6f;
public bool isStunnable;
public float stunDuration;
public bool usesIdleMovement = true;
public float idleWanderRadius = 3f;
public float idleMoveDurationMin = 0.3f;
public float idleMoveDurationMax = 1f;
public float idlePauseDurationMin = 0.1f;
public float idlePauseDurationMax = 0.8f;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 7bc162d3127949aa950d0738beee99a4
timeCreated: 1763571220
@@ -1,11 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/CourierSoundListConfig", fileName = "CourierSoundListConfig")]
public class CourierSoundListConfig : ScriptableObject
{
public AssetReference[] courierSounds;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 83304a98bbdc4e7c9c7c643653ae1368
timeCreated: 1762625438
@@ -1,27 +0,0 @@
using System.Collections.Generic;
using AYellowpaper.SerializedCollections;
using UnityEngine;
using AudioType = Code.Audio.AudioType;
namespace Code.Configs
{
[CreateAssetMenu(fileName = "DefaultAudioSettingsConfig", menuName = "Audio/DefaultAudioSettings")]
public class DefaultAudioSettingsConfig : ScriptableObject
{
[Header("Menu")]
[SerializedDictionary("AudioType", "Volume")]
public SerializedDictionary<Audio.AudioType, float> menuVolume = new();
[Header("Action")]
[SerializedDictionary("AudioType", "Volume")]
public SerializedDictionary<Audio.AudioType, float> actionVolume = new();
[Header("Peaceful")]
[SerializedDictionary("AudioType", "Volume")]
public SerializedDictionary<Audio.AudioType, float> peacefulVolume = new();
[Header("None")]
[SerializedDictionary("AudioType", "Volume")]
public SerializedDictionary<Audio.AudioType, float> noneVolume = new();
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 36d35c52933d4d4ba836f75b05c4810f
timeCreated: 1764754933
@@ -1,12 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/DispatcherSoundList", fileName = "DispatcherSoundListConfig")]
public class DispatcherSoundListConfig : ScriptableObject
{
public AssetReference[] dispatcher;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8a6f6ffb8f5242899791719801791945
timeCreated: 1762625431
@@ -1,29 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Enemies/DrunkenGrandpa", fileName = "DrunkenGrandpaConfig")]
public class DrunkenGrandpaConfig : ScriptableObject
{
public AssetReference drunkenGrandpaPrefab;
public int damage = 2;
public float attackCooldown = 5f;
public float detectionRange = 10f;
public float attackRange = 1.5f;
public float speed = 3f;
public bool isStunnable = true;
public float stunDuration = 3f;
public bool usesIdleMovement = true;
public float idleWanderRadius = 5f;
public float idleMoveDurationMin = 1f;
public float idleMoveDurationMax = 5f;
public float idlePauseDurationMin = 3f;
public float idlePauseDurationMax = 5f;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4d157e8891074b90957a7434cff0ae71
timeCreated: 1763569096
@@ -1,11 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/EnemiesSoundList", fileName = "EnemiesSoundListConfig")]
public class EnemiesSoundListConfig : ScriptableObject
{
public AssetReference[] enemies;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e73ac7f7e3cd4db8ada95f63d73c2d1d
timeCreated: 1762625425
@@ -1,29 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Enemies/Fog", fileName = "FogConfig")]
public class FogConfig : ScriptableObject
{
public AssetReference fogPrefab;
public int damage = int.MaxValue;
public float attackCooldown;
public float detectionRange;
public float attackRange = 5f;
public float speed = 1.5f;
public bool isStunnable;
public float stunDuration;
public bool usesIdleMovement;
public float idleWanderRadius;
public float idleMoveDurationMin;
public float idleMoveDurationMax;
public float idlePauseDurationMin;
public float idlePauseDurationMax;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: aa4dd2a5cce24902a2918488b5751f2f
timeCreated: 1763581410
@@ -1,10 +0,0 @@
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/MusicPlayer", fileName = "MusicPlayerConfig")]
public class MusicPlayerConfig : ScriptableObject
{
public float threshold = 0.5f;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8708e3c129d241b08563f1a170bd3a44
timeCreated: 1764585060
@@ -1,12 +0,0 @@
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Gameplay/OrderBox", fileName = "OrderBoxConfig")]
public class OrderBoxConfig : ScriptableObject
{
public float activationTime;
public AnimationCurve floatingCurve;
public float curveLength;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6af4f53d425a41c78ef889fd7e5c3197
timeCreated: 1763735968
@@ -1,11 +0,0 @@
using UnityEngine;
namespace Code.Configs
{
[CreateAssetMenu(fileName = "OrderServiceConfig", menuName = "Config/Gameplay/OrderServiceConfig")]
public class OrderServiceConfig : ScriptableObject
{
public Vector2 timeBounds;
public float koefRooms, koefOvertime;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ee2550c3e4874575ac3aecadfcc42475
timeCreated: 1763559999
@@ -1,5 +1,4 @@
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs namespace Code.Configs
{ {
@@ -7,8 +6,5 @@ namespace Code.Configs
public class PlayerConfig : ScriptableObject public class PlayerConfig : ScriptableObject
{ {
public int maxHealth; public int maxHealth;
public AssetReference characterPrefab;
public AssetReference armsMeshPrefab;
} }
} }
@@ -1,11 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/SFXList", fileName = "SFXListConfig")]
public class SFXListConfig : ScriptableObject
{
public AssetReference[] SFX;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: be49de18297645009c1e2092f382a670
timeCreated: 1762625417
@@ -1,14 +0,0 @@
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace Code.Configs
{
[CreateAssetMenu(menuName = "Config/Audio/SoundtrackList", fileName = "SoundtrackListConfig")]
public class SoundtrackListConfig : ScriptableObject
{
public AssetReference[] peacefulSoundtrack;
public AssetReference[] actionSoundtrack;
public AssetReference[] deathSoundtrack;
public AssetReference[] menuSoundtrack;
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6d4b28e4552d456d99550b6d91467345
timeCreated: 1762624980
@@ -10,23 +10,4 @@ namespace Code.Constants
{ {
public const string LoadingScreen = "LoadingScreen"; public const string LoadingScreen = "LoadingScreen";
} }
public static class AudioGroups
{
public const string MasterGroup = "Master";
public const string SFXGroup = "SFX";
public const string CreaturesGroup = "Creatures";
public const string MusicGroup = "Music";
public const string PeacefulMusicGroup = "Peaceful";
public const string ActionMusicGroup = "Action";
public const string DispatcherMusicGroup = "Dispatcher";
public const string CourierGroup = "Courier";
public const string EnemiesGroup = "Enemies";
public const string PeacefulSnapshot = "Peaceful";
public const string ActionSnapshot = "Action";
public const string NoneSnapshot = "None";
public const string MenuSnapshot = "Menu";
public const string MenuMusicGroup = "Menu";
public const string DeathMusicGroup = "Death";
}
} }
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: bb5ad0cc3a72448a93ee2adb560c35ad
timeCreated: 1764345160
@@ -1,12 +0,0 @@
using UnityEngine;
namespace Code.DebugAndTest
{
public class TriggerTest : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
Debug.Log(gameObject.name + " found " + other.name);
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6bb3c649d82049079d00b843a76aced8
timeCreated: 1764345168
@@ -10,9 +10,8 @@ namespace Code.Gameplay.Camera
*/ */
public enum CameraMode public enum CameraMode
{ {
None, Bogdan,
Static, Static,
MainMenu,
FirstPerson, FirstPerson,
ThirdPerson, ThirdPerson,
Cinematic Cinematic
@@ -9,16 +9,13 @@ namespace Code.Gameplay.Camera
// Текущий режим работы камеры // Текущий режим работы камеры
public override void Initialize() public override void Initialize()
{ {
States.Add(CameraMode.None, new NoneState(StateMachineContext));
States.Add(CameraMode.Static, new StaticState(StateMachineContext)); States.Add(CameraMode.Static, new StaticState(StateMachineContext));
States.Add(CameraMode.MainMenu, new MainMenuState(StateMachineContext));
States.Add(CameraMode.FirstPerson, new FirstPersonState(StateMachineContext)); States.Add(CameraMode.FirstPerson, new FirstPersonState(StateMachineContext));
States.Add(CameraMode.ThirdPerson, new ThirdPersonState(StateMachineContext)); States.Add(CameraMode.ThirdPerson, new ThirdPersonState(StateMachineContext));
CurrentBaseState = States[CameraMode.None]; CurrentBaseState = States[CameraMode.Static];
} }
public UnityEngine.Camera Camera => StateMachineContext.Camera;
protected override bool CheckTransitionAllowed(CameraMode nextState) => true; protected override bool CheckTransitionAllowed(CameraMode nextState) => true;
@@ -1,4 +1,3 @@
using System.Collections.Generic;
using Code.Configs; using Code.Configs;
using Code.Input; using Code.Input;
using Reflex.Attributes; using Reflex.Attributes;
@@ -10,6 +9,5 @@ namespace Code.Gameplay.Camera
[Inject] public UnityEngine.Camera Camera; [Inject] public UnityEngine.Camera Camera;
[Inject] public CameraConfig Config; [Inject] public CameraConfig Config;
[Inject] public IInputService InputService; [Inject] public IInputService InputService;
[Inject] public Dictionary<CameraMode,CameraSettingsConfig> CameraSettingsConfigs;
} }
} }
@@ -117,7 +117,7 @@ namespace Code.Gameplay.Camera.CameraStates
protected virtual void HandleRotationInput(float deltaTime, Vector3 rotationInput, out Quaternion targetRotation) protected virtual void HandleRotationInput(float deltaTime, Vector3 rotationInput, out Quaternion targetRotation)
{ {
// Вращение вокруг вертикальной оси (горизонтальный поворот) // Вращение вокруг вертикальной оси (горизонтальный поворот)
var rotationFromInput = Quaternion.Euler(Vector3.up * (rotationInput.x * RotationSpeed)); var rotationFromInput = Quaternion.Euler(FollowTransform.up * (rotationInput.x * RotationSpeed));
PlanarDirection = rotationFromInput * PlanarDirection; PlanarDirection = rotationFromInput * PlanarDirection;
var planarRotation = Quaternion.LookRotation(PlanarDirection, FollowTransform.up); var planarRotation = Quaternion.LookRotation(PlanarDirection, FollowTransform.up);
@@ -14,18 +14,14 @@ namespace Code.Gameplay.Camera.CameraStates
public override void EnterState() public override void EnterState()
{ {
base.EnterState();
// В FirstPerson режиме дистанция всегда равна 0 // В FirstPerson режиме дистанция всегда равна 0
MinDistance = 0f; MinDistance = 0f;
MaxDistance = 0f; MaxDistance = 0f;
DefaultDistance = 0f; DefaultDistance = 0f;
TargetDistance = 0f; TargetDistance = 0f;
CurrentDistance = 0f; CurrentDistance = 0f;
Camera.fieldOfView = Context.CameraSettingsConfigs[CameraMode.FirstPerson].fov;
Cursor.lockState = CursorLockMode.Locked; base.EnterState();
} }
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation) protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
@@ -1,31 +0,0 @@
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
public class MainMenuState : BaseCameraState, ICameraState
{
public MainMenuState(CameraStateMachineContext context) : base(context) { }
public override void EnterState()
{
base.EnterState();
Camera.fieldOfView = Context.CameraSettingsConfigs[CameraMode.MainMenu].fov;
Cursor.lockState = CursorLockMode.None;
}
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
Camera.transform.position = FollowTransform.position;
}
public CameraMode GetCameraMode() => CameraMode.MainMenu;
protected override void HandleRotationInput(float deltaTime, Vector3 rotationInput,
out Quaternion targetRotation)
{
if(FollowTransform) Camera.transform.rotation = FollowTransform.rotation;
targetRotation = Camera.transform.rotation;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 8e4b2fa3af4b4ba78ab79ca154f05fb4
timeCreated: 1763547110
@@ -1,21 +0,0 @@
using UnityEngine;
namespace Code.Gameplay.Camera.CameraStates
{
public class NoneState : BaseCameraState, ICameraState
{
public NoneState(CameraStateMachineContext context) : base(context) { }
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
{
}
public CameraMode GetCameraMode() => CameraMode.None;
protected override void HandleRotationInput(float deltaTime, Vector3 rotationInput,
out Quaternion targetRotation)
{
targetRotation = Quaternion.identity;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: d8fa6119113f4f12a35365fd27387124
timeCreated: 1763847889
@@ -9,10 +9,8 @@ namespace Code.Gameplay.Camera.CameraStates
public override void EnterState() public override void EnterState()
{ {
Debug.Log("Static State Enter");
base.EnterState(); base.EnterState();
Camera.fieldOfView = Context.CameraSettingsConfigs[CameraMode.Static].fov;
Cursor.lockState = CursorLockMode.None;
} }
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation) protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
@@ -14,13 +14,13 @@ namespace Code.Gameplay.Camera.CameraStates
public override void EnterState() public override void EnterState()
{ {
base.EnterState();
// Восстанавливаем исходные значения дистанции // Восстанавливаем исходные значения дистанции
MinDistance = OriginalMinDistance; MinDistance = OriginalMinDistance;
MaxDistance = OriginalMaxDistance; MaxDistance = OriginalMaxDistance;
DefaultDistance = OriginalDefaultDistance; DefaultDistance = OriginalDefaultDistance;
TargetDistance = DefaultDistance; TargetDistance = DefaultDistance;
base.EnterState();
} }
protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation) protected override void HandlePosition(float deltaTime, float zoomInput, Quaternion targetRotation)
@@ -1,82 +0,0 @@
using UnityEngine;
using Code.Gameplay.Camera;
using KinematicCharacterController;
namespace Code.Gameplay.Character.Controller.Abilities
{
public class DashMechanic
{
private readonly float _dashSpeed;
private readonly float _dashDuration;
private readonly float _dashCooldown;
private float _dashTimer;
private float _dashCooldownTimer;
private Vector3 _dashDirection;
private bool _dashRequested;
public bool IsDashing { get; private set; }
public DashMechanic(float dashSpeed, float dashDuration, float dashCooldown)
{
_dashSpeed = dashSpeed;
_dashDuration = dashDuration;
_dashCooldown = dashCooldown;
}
public void TickCooldown(float deltaTime)
{
if (_dashCooldownTimer > 0f) _dashCooldownTimer -= deltaTime;
}
public void RequestDash(bool dashPressed)
{
if (dashPressed && !_dashRequested && _dashCooldownTimer <= 0f)
_dashRequested = true;
}
public void TryStartDash(KinematicCharacterMotor motor,
CameraMode currentCameraMode,
Quaternion firstPersonCameraRotation,
Vector3 lookInputVector)
{
if (!_dashRequested || !motor.GroundingStatus.IsStableOnGround || _dashCooldownTimer > 0f)
{
_dashRequested = false;
return;
}
IsDashing = true;
_dashTimer = _dashDuration;
var dir = currentCameraMode == CameraMode.FirstPerson
? firstPersonCameraRotation * Vector3.forward
: lookInputVector;
_dashDirection = Vector3.ProjectOnPlane(dir, motor.CharacterUp).normalized;
if (_dashDirection.sqrMagnitude < 0.01f) _dashDirection = motor.CharacterForward;
_dashCooldownTimer = _dashCooldown;
_dashRequested = false;
}
public void HandleDash(ref Vector3 currentVelocity, float deltaTime, KinematicCharacterMotor motor)
{
_dashTimer -= deltaTime;
if (_dashTimer <= 0f || !motor.GroundingStatus.IsStableOnGround)
{
EndDash();
return;
}
currentVelocity = _dashDirection * _dashSpeed;
currentVelocity += motor.CharacterUp * 1.5f;
}
public void EndDash()
{
IsDashing = false;
_dashTimer = 0f;
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6d62efb53e0c4d49be9fc658484e2575
timeCreated: 1764856437
@@ -1,91 +1,122 @@
using UnityEngine;
using Code.Gameplay.Camera; using Code.Gameplay.Camera;
using KinematicCharacterController; using KinematicCharacterController;
using UnityEngine;
namespace Code.Gameplay.Character.Controller.Abilities namespace Code.Gameplay.Character.Controller.Abilities
{ {
/*
* Механика слайда для персонажа
* Отвечает за управление состоянием, таймерами и скоростью слайда
*/
public class SlideMechanic public class SlideMechanic
{ {
private readonly AnimationCurve _velocityCurve; // Скорость слайда
private readonly float _minVelocityThreshold; private readonly float _slideSpeed;
private readonly float _cooldown; // Длительность слайда в секундах
private readonly float _slideDuration;
// Время перезарядки слайда
private readonly float _slideCooldown;
private float _slideTime; // Таймер слайда
private float _cooldownTimer; private float _slideTimer;
// Таймер перезарядки слайда
private float _slideCooldownTimer;
// Направление слайда
private Vector3 _slideDirection; private Vector3 _slideDirection;
// Флаг запроса слайда
private bool _slideRequested; private bool _slideRequested;
private float _initialSpeed;
// Свойство-флаг, активен ли слайд
public bool IsSliding { get; private set; } public bool IsSliding { get; private set; }
public SlideMechanic(AnimationCurve velocityCurve, float minVelocityThreshold, float cooldown) // Конструктор механики слайда
public SlideMechanic(float slideSpeed, float slideDuration, float slideCooldown)
{ {
_velocityCurve = velocityCurve; _slideSpeed = slideSpeed;
_minVelocityThreshold = minVelocityThreshold; _slideDuration = slideDuration;
_cooldown = cooldown; _slideCooldown = slideCooldown;
} }
// Обновляем таймер перезарядки слайда
public void TickCooldown(float deltaTime) public void TickCooldown(float deltaTime)
{ {
if (_cooldownTimer > 0f) _cooldownTimer -= deltaTime; if (_slideCooldownTimer > 0f)
{
_slideCooldownTimer -= deltaTime;
}
} }
// Обработка слайда
public void RequestSlide(bool slidePressed) public void RequestSlide(bool slidePressed)
{ {
if (slidePressed && !_slideRequested && _cooldownTimer <= 0f) if (slidePressed && !_slideRequested && _slideCooldownTimer <= 0f)
{
_slideRequested = true; _slideRequested = true;
}
} }
// Начало слайда
public void TryStartSlide(KinematicCharacterMotor motor, public void TryStartSlide(KinematicCharacterMotor motor,
CameraMode currentCameraMode, CameraMode currentCameraMode,
Quaternion firstPersonCameraRotation, Quaternion firstPersonCameraRotation,
Vector3 lookInputVector) Vector3 lookInputVector)
{ {
if (!_slideRequested || !motor.GroundingStatus.IsStableOnGround || _cooldownTimer > 0f) // Проверяем условия для старта слайда
if (!_slideRequested || !motor.GroundingStatus.IsStableOnGround || _slideCooldownTimer > 0f)
{ {
_slideRequested = false; _slideRequested = false;
return; return;
} }
IsSliding = true; IsSliding = true;
_slideTime = 0f; _slideTimer = _slideDuration;
var dir = currentCameraMode == CameraMode.FirstPerson
? firstPersonCameraRotation * Vector3.forward
: lookInputVector;
_slideDirection = Vector3.ProjectOnPlane(dir, motor.CharacterUp).normalized;
if (_slideDirection.sqrMagnitude < 0.01f) _slideDirection = motor.CharacterForward;
_initialSpeed = motor.Velocity.magnitude;
if (_initialSpeed < 12f) _initialSpeed = 12f; // fallback
_cooldownTimer = _cooldown; // НАПРАВЛЕНИЕ СЛАЙДА - ВСЕГДА ВПЕРЕД ПО КАМЕРЕ
// Используем направление камеры, но проецируем на горизонтальную плоскость
var slideDir = currentCameraMode == CameraMode.FirstPerson ?
firstPersonCameraRotation * Vector3.forward :
lookInputVector;
// Проецируем на горизонтальную плоскость и нормализуем
_slideDirection = Vector3.ProjectOnPlane(slideDir, motor.CharacterUp).normalized;
// Если направление нулевое (персонаж стоит на месте), используем направление вперед
if (_slideDirection.sqrMagnitude < 0.01f)
{
_slideDirection = motor.CharacterForward;
}
// Устанавливаем перезарядку
_slideCooldownTimer = _slideCooldown;
_slideRequested = false; _slideRequested = false;
} }
// Обработка слайда
public void HandleSlide(ref Vector3 currentVelocity, float deltaTime, KinematicCharacterMotor motor) public void HandleSlide(ref Vector3 currentVelocity, float deltaTime, KinematicCharacterMotor motor)
{ {
if (!IsSliding) return; // Уменьшаем таймер слайда
_slideTimer -= deltaTime;
_slideTime += deltaTime;
var t = Mathf.Clamp01(_slideTime / 2.5f); // Если время слайда вышло, персонаж в воздухе - заканчиваем слайд
var speedMult = _velocityCurve.Evaluate(t); if (_slideTimer <= 0f || !motor.GroundingStatus.IsStableOnGround)
{
var targetSpeed = _initialSpeed * speedMult;
currentVelocity = _slideDirection * targetSpeed;
currentVelocity += motor.CharacterUp * 1f;
if (targetSpeed < _minVelocityThreshold || !motor.GroundingStatus.IsStableOnGround)
EndSlide(); EndSlide();
return;
}
// Устанавливаем скорость слайда вперед
// Используем прямую установку скорости вместо сложных вычислений
currentVelocity = _slideDirection * _slideSpeed;
// Добавляем небольшую вертикальную компоненту для лучшего скольжения по неровностям
currentVelocity += motor.CharacterUp * 2f;
} }
// Завершение слайда
public void EndSlide() public void EndSlide()
{ {
IsSliding = false; IsSliding = false;
_slideTime = 0f; _slideTimer = 0f;
} }
} }
} }
@@ -15,12 +15,6 @@ namespace Code.Gameplay.Character.Controller.Abilities
private readonly float _minWallClimbSpeed; private readonly float _minWallClimbSpeed;
// Пороговый косинус стены // Пороговый косинус стены
public float WallNormalThreshold { get; } public float WallNormalThreshold { get; }
private bool _hasClimbedThisWallSession;
private float _wallClimbCooldownTimer;
private readonly float _wallClimbCooldownDuration = 1.5f;
public bool IsClimbingActive { get; private set; }
// Конструктор механики залазания на стену // Конструктор механики залазания на стену
public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold) public WallClimbMechanic(float wallClimbSpeedFactor, float minWallClimbSpeed, float wallDetectionAngleThreshold)
@@ -32,32 +26,21 @@ namespace Code.Gameplay.Character.Controller.Abilities
} }
// Обработка столкновения при движении (вызывается при ударе о препятствие) // Обработка столкновения при движении (вызывается при ударе о препятствие)
public bool TryClimbWall(KinematicCharacterMotor motor, Vector3 hitNormal) public void OnMovementHit(KinematicCharacterMotor motor, Vector3 hitNormal)
{ {
// Проверяем, что это вертикальная или почти вертикальная поверхность (стена) // Проверяем, что это вертикальная или почти вертикальная поверхность (стена)
var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp); var dotWithUp = Vector3.Dot(hitNormal, motor.CharacterUp);
if (Mathf.Abs(dotWithUp) > WallNormalThreshold) return; // Не стена — это пол или потолок
if (Mathf.Abs(dotWithUp) > WallNormalThreshold)
{
// Сбрасываем сессию, если коснулись пола/потолка → выход из "режима стены"
_hasClimbedThisWallSession = false;
return false;
}
// Запрещаем повторное залазание в рамках одной сессии
if (_hasClimbedThisWallSession || _wallClimbCooldownTimer > 0f)
return false;
// Получаем текущую скорость персонажа // Получаем текущую скорость персонажа
var currentVelocity = motor.Velocity; var currentVelocity = motor.Velocity;
// Проекция скорости на направление к стене (отрицательная, если летим в стену) // Проекция скорости на направление к стене (отрицательная, если летим в стену)
var velocityIntoWall = Vector3.Dot(currentVelocity, -hitNormal); var velocityIntoWall = Vector3.Dot(currentVelocity, -hitNormal);
// Если движемся в стену с достаточной скоростью // Если движемся в стену с достаточной скоростью
if (velocityIntoWall > _minWallClimbSpeed) if (velocityIntoWall > _minWallClimbSpeed)
{ {
_wallClimbCooldownTimer = _wallClimbCooldownDuration;
// Преобразуем часть горизонтальной скорости в вертикальную // Преобразуем часть горизонтальной скорости в вертикальную
var verticalBoost = velocityIntoWall * _wallClimbSpeedFactor; var verticalBoost = velocityIntoWall * _wallClimbSpeedFactor;
@@ -66,26 +49,6 @@ namespace Code.Gameplay.Character.Controller.Abilities
// Предотвращаем заземление и повторное применение в этом кадре // Предотвращаем заземление и повторное применение в этом кадре
motor.ForceUnground(); motor.ForceUnground();
_hasClimbedThisWallSession = true;
IsClimbingActive = true;
return true;
}
return false;
}
public void ResetWallClimbSession()
{
_hasClimbedThisWallSession = false;
IsClimbingActive = false;
}
public void TickCooldown(float deltaTime)
{
if (_wallClimbCooldownTimer > 0f)
{
_wallClimbCooldownTimer -= deltaTime;
} }
} }
} }
@@ -1,78 +1,79 @@
using UnityEngine; using UnityEngine;
using KinematicCharacterController;
namespace Code.Gameplay.Character.Controller.Abilities namespace Code.Gameplay.Character.Controller.Abilities
{ {
/*
* Механика наклона персонажа при контакте со стеной
* Управляет визуальным наклоном модели в зависимости от высоты подъёма по стене
*/
public class WallLeanMechanic public class WallLeanMechanic
{ {
private readonly float _leanAngle; private readonly float _maxLeanAngle;
private readonly float _threshold; private readonly float _leanSharpness;
private readonly float _smoothSpeed; private readonly float _heightScale;
private Transform _characterTransform;
private Vector3 _lastWallNormal;
private float _lastWallContactHeight;
private float _leanFactor;
private Transform _model; public WallLeanMechanic(float maxLeanAngle, float leanSharpness, float heightScale)
public WallLeanMechanic(
float leanAngle,
float threshold,
float smoothSpeed)
{ {
_leanAngle = leanAngle; _maxLeanAngle = maxLeanAngle;
_threshold = threshold; _leanSharpness = leanSharpness;
_smoothSpeed = smoothSpeed; _heightScale = heightScale;
} }
public void SetModel(Transform model) => _model = model; // Назначаем трансформ визуальной модели один раз
public void SetCharacterTransform(Transform transform)
public void Update(bool isWallRunning, Vector3 wallNormal, Vector3 characterForward)
{ {
if (!_model) _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)
{ {
_model.localRotation = Quaternion.identity; 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; return;
} }
var targetRotation = Quaternion.identity; var leanDirection = -_lastWallNormal;
leanDirection = Vector3.ProjectOnPlane(leanDirection, motor.CharacterUp).normalized;
if (isWallRunning) if (leanDirection.sqrMagnitude < 0.01f) return;
{
var n = new Vector3(wallNormal.x, 0f, wallNormal.z).normalized;
if (n.sqrMagnitude < 0.001f) n = Vector3.forward;
var bodyRight = Vector3.Cross(Vector3.up, characterForward).normalized; var leanAngle = _leanFactor * _maxLeanAngle;
var side = Vector3.Dot(n, bodyRight); var leanRotation = Quaternion.AngleAxis(leanAngle, Vector3.Cross(motor.CharacterUp, leanDirection));
var front = Vector3.Dot(n, characterForward); _characterTransform.localRotation = leanRotation;
var axis = Vector3.right;
var angle = 0f;
if (side > _threshold)
{
axis = Vector3.forward;
angle = -_leanAngle;
}
else if (side < -_threshold)
{
axis = Vector3.forward;
angle = _leanAngle;
}
else if (front > _threshold)
{
axis = Vector3.right;
angle = -_leanAngle;
}
else if (front < -_threshold)
{
axis = Vector3.right;
angle = _leanAngle;
}
targetRotation = Quaternion.AngleAxis(angle, axis);
}
_model.localRotation = Quaternion.Slerp(
_model.localRotation,
targetRotation,
1f - Mathf.Exp(-_smoothSpeed * Time.deltaTime)
);
} }
} }
} }
@@ -1,233 +0,0 @@
using Code.Gameplay.Character.Controller.Input;
using Code.Gameplay.Character.Core;
using KinematicCharacterController;
using UnityEngine;
namespace Code.Gameplay.Character.Controller.Abilities
{
public class WallRunningMechanic
{
private readonly float _maxWallRunTime;
private readonly float _wallRunSpeed;
private readonly float _wallRunAcceleration;
private readonly float _wallRunGravityScale;
private readonly float _wallDetachImpulse;
private readonly float _wallContactAngleThreshold;
private float _wallRunTimer;
private Vector3 _wallNormal;
private Vector3 _wallTangentRight;
private Vector3 _wallTangentUp;
private Vector3 _wallContactPoint;
private Vector3 _lastMoveInputInWallSpace;
private float _surfaceAngleCos;
private bool _canWallRun;
private KinematicCharacterMotor _motor;
private CharacterInputHandler _inputHandler;
public bool IsWallRunning { get; private set; }
public WallRunningMechanic(
float maxWallRunTime,
float wallRunSpeed,
float wallRunAcceleration,
float wallRunGravityScale,
float wallDetachImpulse,
float wallContactAngleThreshold)
{
_maxWallRunTime = maxWallRunTime;
_wallRunSpeed = wallRunSpeed;
_wallRunAcceleration = wallRunAcceleration;
_wallRunGravityScale = wallRunGravityScale;
_wallDetachImpulse = wallDetachImpulse;
_wallContactAngleThreshold = wallContactAngleThreshold;
}
public void Initialize(KinematicCharacterMotor motor, CharacterInputHandler inputHandler)
{
_motor = motor;
_inputHandler = inputHandler;
_surfaceAngleCos = Mathf.Cos(Mathf.Deg2Rad * _wallContactAngleThreshold);
}
public void Tick(float deltaTime)
{
if (!IsWallRunning) return;
_wallRunTimer += deltaTime;
if (_wallRunTimer > _maxWallRunTime)
{
DetachFromWall(false);
}
}
public bool TryStartWallRun(Vector3 hitNormal, Vector3 hitPoint)
{
if (IsWallRunning) return false;
var dotWithUp = Vector3.Dot(hitNormal, _motor.CharacterUp);
var isWall = Mathf.Abs(dotWithUp) < _surfaceAngleCos;
if (!isWall) return false;
if (_motor.GroundingStatus.IsStableOnGround) return false;
var velocityIntoWall = Vector3.Dot(_motor.Velocity, -hitNormal);
if (velocityIntoWall < 1f) return false;
StartWallRun(hitNormal, hitPoint);
return true;
}
private void StartWallRun(Vector3 wallNormal, Vector3 contactPoint)
{
IsWallRunning = true;
_wallRunTimer = 0f;
_wallNormal = wallNormal.normalized;
_wallContactPoint = contactPoint;
if (_wallTangentUp.sqrMagnitude < 0.01f) _wallTangentUp = Vector3.up;
_wallTangentRight = Vector3.Cross(_wallNormal, _wallTangentUp).normalized;
_wallTangentUp = Vector3.Cross(_wallTangentRight, _wallNormal).normalized;
if (Vector3.Dot(_wallTangentUp, Vector3.up) < 0)
_wallTangentUp = -_wallTangentUp;
_motor.ForceUnground();
}
public void HandleWallRun(ref Vector3 currentVelocity, CharacterInputs inputs, float deltaTime)
{
if (!IsWallRunning) return;
if (!IsWallStillPresent()) { DetachFromWall(false); return; }
var moveInputWorld = _inputHandler.MoveInputVector;
var moveInWallPlane = Vector3.ProjectOnPlane(moveInputWorld, _wallNormal);
var wsMove = Vector3.Project(moveInWallPlane, _wallTangentRight);
var ad = inputs.MoveAxisRight;
var sideDot = Vector3.Dot(_wallNormal, _motor.CharacterRight);
if (sideDot < 0)
ad = -ad;
var adMove = _wallTangentUp * (-ad);
var finalMove = wsMove + adMove;
finalMove = finalMove.normalized * moveInputWorld.magnitude;
var targetVelocity = finalMove * _wallRunSpeed;
currentVelocity = Vector3.Lerp(
currentVelocity,
targetVelocity,
1f - Mathf.Exp(-_wallRunAcceleration * deltaTime)
);
currentVelocity += Physics.gravity * (_wallRunGravityScale * deltaTime);
var distanceToWall = Vector3.Dot(_motor.TransientPosition - _wallContactPoint, _wallNormal);
if (Mathf.Abs(distanceToWall) > 0.8f)
{
DetachFromWall(false);
return;
}
if (inputs.JumpPressed)
{
DetachFromWall(true);
}
}
private bool IsWallStillPresent()
{
if (!_motor) return false;
var characterCenter = _motor.TransientPosition;
var characterRight = _motor.CharacterRight;
var characterUp = _motor.CharacterUp;
const float checkDistance = 0.45f;
var rayOrigin = characterCenter + characterUp * 0.2f;
var rayDir = -_wallNormal;
Vector3[] offsets = {
Vector3.zero,
characterUp * 0.3f,
-characterUp * 0.3f,
characterRight * 0.2f,
-characterRight * 0.2f
};
foreach (var offset in offsets)
{
if (!Physics.Raycast(rayOrigin + offset, rayDir, out var hit, checkDistance,
_motor.CollidableLayers, QueryTriggerInteraction.Ignore)) continue;
var dotWithUp = Mathf.Abs(Vector3.Dot(hit.normal, characterUp));
if (!(dotWithUp < _surfaceAngleCos)) continue;
_wallNormal = hit.normal.normalized;
_wallContactPoint = hit.point;
return true;
}
return false;
}
private void DetachFromWall(bool withImpulse)
{
if (!IsWallRunning) return;
IsWallRunning = false;
if (!withImpulse || !_motor) return;
var detachImpulse = -_wallNormal * _wallDetachImpulse;
_motor.BaseVelocity += detachImpulse;
}
public Vector3 GetWallNormal() => _wallNormal;
public void PerformWallJump(CharacterInputs inputs, Quaternion cameraRotation)
{
if (!IsWallRunning || !_motor) return;
IsWallRunning = false;
var wallAway = _wallNormal;
var wallUp = _wallTangentUp;
var playerForward = cameraRotation * Vector3.forward;
var forwardInWallPlane = Vector3.ProjectOnPlane(playerForward, _wallNormal).normalized;
if (forwardInWallPlane.sqrMagnitude < 0.01f) forwardInWallPlane = _wallTangentRight;
var jumpDirection =
forwardInWallPlane * 0.2f +
wallAway * 0.4f +
wallUp * 0.4f;
jumpDirection = jumpDirection.normalized;
var wallJumpForce = _wallDetachImpulse * 2f;
var jumpImpulse = jumpDirection * wallJumpForce;
_motor.BaseVelocity += jumpImpulse;
_motor.SetPosition(_motor.TransientPosition + wallAway * 0.07f);
if (_motor.CharacterController is CustomCharacterController controller)
{
controller.BlockAbilities(0.12f);
}
}
}
}
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4bef67aa9cf14753a63ea07eecc5ff67
timeCreated: 1764263348
@@ -15,7 +15,7 @@ namespace Code.Gameplay.Character.Controller
* Реализует физическое движение персонажа с использованием Kinematic Character Controller * Реализует физическое движение персонажа с использованием Kinematic Character Controller
* Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности * Обрабатывает ввод пользователя, применяет движение и вращение с учетом поверхности
* *
* Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM * Источник: Tvtig, https://www.youtube.com/watch?v=9ibpY64wBYM
*/ */
public class CustomCharacterController : MonoBehaviour, ICharacterController public class CustomCharacterController : MonoBehaviour, ICharacterController
{ {
@@ -27,251 +27,216 @@ namespace Code.Gameplay.Character.Controller
// Подсистемы // Подсистемы
private CharacterInputHandler _inputHandler; private CharacterInputHandler _inputHandler;
private DashMechanic _dashMechanic;
private SlideMechanic _slideMechanic; private SlideMechanic _slideMechanic;
// private WallClimbMechanic _wallClimbMechanic; private WallClimbMechanic _wallClimbMechanic;
private WallRunningMechanic _wallRunningMechanic;
private WallLeanMechanic _wallLeanMechanic; private WallLeanMechanic _wallLeanMechanic;
private GroundMovementSystem _groundMovement; private GroundMovementSystem _groundMovement;
private AirMovementSystem _airMovement; private AirMovementSystem _airMovement;
public bool IsSliding => _slideMechanic.IsSliding; // Инициализация контроллера
public bool IsDashing => _dashMechanic.IsDashing; private void Awake()
public bool IsWallRunning => _wallRunningMechanic.IsWallRunning;
private float abilityBlockTimer;
private bool isAbilitiesBlocked;
private CharacterInputs _lastInputs;
public void SetPosition(Vector3 position) => motor.SetPosition(position);
public void SetRotation(Quaternion rotation) => motor.SetRotation(rotation);
[Inject]
private void Construct(CharacterConfig characterConfig)
{ {
_characterConfig = characterConfig;
// Инициализация подсистем с параметрами из конфига // Инициализация подсистем с параметрами из конфига
_inputHandler = new CharacterInputHandler(); _inputHandler = new CharacterInputHandler(
_dashMechanic = new DashMechanic( _characterConfig.sprintAngleThreshold
_characterConfig.dashSpeed,
_characterConfig.dashDuration,
_characterConfig.dashCooldown
); );
_slideMechanic = new SlideMechanic( _slideMechanic = new SlideMechanic(
_characterConfig.slideVelocityCurve, _characterConfig.slideSpeed,
_characterConfig.slideMinVelocityThreshold, _characterConfig.slideDuration,
_characterConfig.slideCooldown _characterConfig.slideCooldown
); );
_wallRunningMechanic = new WallRunningMechanic( _wallClimbMechanic = new WallClimbMechanic(
_characterConfig.wallRunMaxTime, _characterConfig.wallClimbSpeedFactor,
_characterConfig.wallRunSpeed, _characterConfig.minWallClimbSpeed,
_characterConfig.wallRunAcceleration, _characterConfig.wallDetectionAngleThreshold
_characterConfig.wallRunGravityScale,
_characterConfig.wallDetachImpulse,
_characterConfig.wallContactAngleThreshold
); );
_wallLeanMechanic = new WallLeanMechanic( _wallLeanMechanic = new WallLeanMechanic(
_characterConfig.leanAngle, _characterConfig.maxWallLeanAngle,
_characterConfig.leanThreshold, _characterConfig.wallLeanSharpness,
_characterConfig.leanSmoothSpeed _characterConfig.wallLeanHeightScale
); );
_groundMovement = new GroundMovementSystem( _groundMovement = new GroundMovementSystem(
_characterConfig.maxStableMoveSpeed, _characterConfig.maxStableMoveSpeed,
_characterConfig.sprintMoveSpeed,
_characterConfig.stableMovementSharpness, _characterConfig.stableMovementSharpness,
_characterConfig.jumpSpeed _characterConfig.jumpSpeed
); );
_airMovement = new AirMovementSystem( _airMovement = new AirMovementSystem(_characterConfig.gravity);
_characterConfig.gravity,
_characterConfig.maxAirSpeed,
_characterConfig.airControlSharpness
);
motor.CharacterController = this; // Регистрация этого контроллера в движке
_inputHandler.FirstPersonCameraRotation =
motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
_wallLeanMechanic.SetModel(characterTransform);
_wallRunningMechanic.Initialize(motor, _inputHandler);
} }
// Текущий режим камеры для адаптации поведения // Текущий режим камеры для адаптации поведения
private CameraMode _currentCameraMode = CameraMode.FirstPerson; private CameraMode _currentCameraMode = CameraMode.FirstPerson;
// Инициализация контроллера
private void Start()
{
motor.CharacterController = this; // Регистрация этого контроллера в движке
_inputHandler.FirstPersonCameraRotation =
motor.Transform.rotation; // Начальное вращение совпадает с персонажемс
_wallLeanMechanic.SetCharacterTransform(characterTransform);
}
// Обработка и преобразование входных данных от персонажа
public void SetInputs(ref CharacterInputs inputs) public void SetInputs(ref CharacterInputs inputs)
{ {
_lastInputs = inputs; // Обновляем таймер перезарядки слайда
_dashMechanic.TickCooldown(Time.deltaTime);
_slideMechanic.TickCooldown(Time.deltaTime); _slideMechanic.TickCooldown(Time.deltaTime);
_wallRunningMechanic.Tick(Time.deltaTime);
// Если активен слайд - игнорируем весь остальной ввод // Если активен слайд - игнорируем весь остальной ввод
if (_dashMechanic.IsDashing || _slideMechanic.IsSliding) if (_slideMechanic.IsSliding)
{ {
// Во время слайда не обрабатываем движение, прыжки и спринт
_inputHandler.MoveInputVector = Vector3.zero; _inputHandler.MoveInputVector = Vector3.zero;
_inputHandler.LookInputVector = Vector3.one; _inputHandler.LookInputVector = Vector3.one;
_inputHandler.IsSprinting = false;
_groundMovement.RequestJump(inputs.JumpPressed); _groundMovement.RequestJump(inputs.JumpPressed);
return; return;
} }
// Сохраняем текущий режим камеры
_currentCameraMode = inputs.CameraMode; _currentCameraMode = inputs.CameraMode;
if (isAbilitiesBlocked)
{
abilityBlockTimer -= Time.deltaTime;
if (abilityBlockTimer <= 0)
{
isAbilitiesBlocked = false;
}
else
{
inputs.DashPressed = false;
inputs.SlidePressed = false;
}
}
_dashMechanic.RequestDash(inputs.DashPressed); // Обработка слайда
_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);
} }
// Обновление вращения персонажа на основе входных данных
public void UpdateRotation(ref Quaternion currentRotation, float deltaTime) public void UpdateRotation(ref Quaternion currentRotation, float deltaTime)
{ {
// ВО ВРЕМЯ СЛАЙДА НЕ ОБНОВЛЯЕМ ВРАЩЕНИЕ - ПЕРСОНАЖ СМОТРИТ ПРЯМО
if (_slideMechanic.IsSliding) return; if (_slideMechanic.IsSliding) return;
switch (_currentCameraMode) switch (_currentCameraMode)
{ {
// В FirstPerson режиме применяем вращение камеры напрямую к персонажу (только горизонталь)
case CameraMode.FirstPerson when _inputHandler.ShouldApplyCameraRotation: case CameraMode.FirstPerson when _inputHandler.ShouldApplyCameraRotation:
// Немедленно применяем горизонтальное вращение камеры к персонажу
currentRotation = _inputHandler.FirstPersonCameraRotation; currentRotation = _inputHandler.FirstPersonCameraRotation;
break; break;
// В ThirdPerson режиме плавно поворачиваем персонаж в направлении движения
case CameraMode.ThirdPerson: case CameraMode.ThirdPerson:
{ {
// Вращение применяется только если есть направление движения и разрешена резкость вращения
if (_inputHandler.LookInputVector.sqrMagnitude > 0 && _characterConfig.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(-_characterConfig.orientationSharpness * deltaTime) 1f - Mathf.Exp(-_characterConfig.orientationSharpness * deltaTime)
).normalized; ).normalized;
// Установка нового вращения смотря в сглаженном направлении
currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, motor.CharacterUp); currentRotation = Quaternion.LookRotation(smoothedLookInputDirection, motor.CharacterUp);
} }
break; break;
} }
// Заготовка на будущее (если понадобится)
case CameraMode.Static: case CameraMode.Static:
case CameraMode.Cinematic: case CameraMode.Cinematic:
case CameraMode.Bogdan: // Скорее всего юзлес, но пускай будет
default: default:
break; break;
} }
} }
// Обновление скорости движения персонажа
public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime) public void UpdateVelocity(ref Vector3 currentVelocity, float deltaTime)
{ {
if (_dashMechanic.IsDashing && _groundMovement.JumpRequested) // Если во время слайда запрошен прыжок — прерываем слайд
_dashMechanic.EndDash();
if (_slideMechanic.IsSliding && _groundMovement.JumpRequested) if (_slideMechanic.IsSliding && _groundMovement.JumpRequested)
_slideMechanic.EndSlide();
if (!_dashMechanic.IsDashing)
_dashMechanic.TryStartDash(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation, _inputHandler.LookInputVector);
else
{ {
_dashMechanic.HandleDash(ref currentVelocity, deltaTime, motor); _slideMechanic.EndSlide();
return;
} }
// Проверяем запрос слайда и запускаем его если можно
if (!_slideMechanic.IsSliding) if (!_slideMechanic.IsSliding)
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation, _inputHandler.LookInputVector); {
_slideMechanic.TryStartSlide(motor, _currentCameraMode, _inputHandler.FirstPersonCameraRotation,
_inputHandler.LookInputVector);
}
// Если активен слайд - обрабатываем отдельно
else else
{ {
_slideMechanic.HandleSlide(ref currentVelocity, deltaTime, motor); _slideMechanic.HandleSlide(ref currentVelocity, deltaTime, motor);
return; return;
} }
// === WALL RUNNING: обработка состояния ===
if (_wallRunningMechanic.IsWallRunning)
{
// 🔄 Передаём актуальные данные для walljump
if (_groundMovement.JumpRequested) // или _lastInputs.JumpPressed — зависит от логики
{
_wallRunningMechanic.PerformWallJump(_lastInputs, _inputHandler.FirstPersonCameraRotation);
_groundMovement.CancelJumpRequest(); // сброс, чтобы не сработало дважды
}
else
{
_wallRunningMechanic.HandleWallRun(ref currentVelocity, _lastInputs, deltaTime);
}
return;
}
// Если персонаж находится на земле // Если персонаж находится на земле
if (motor.GroundingStatus.IsStableOnGround) if (motor.GroundingStatus.IsStableOnGround)
{ {
_groundMovement.UpdateVelocity(ref currentVelocity, _inputHandler, motor, deltaTime); _groundMovement.UpdateVelocity(ref currentVelocity, _inputHandler, motor, deltaTime);
} }
// Если персонаж не на земле — стандартное воздушное движение // Если персонаж не находится на земле
else else
{ {
_airMovement.UpdateVelocity(ref currentVelocity, _inputHandler, motor, deltaTime); _airMovement.UpdateVelocity(ref currentVelocity, deltaTime);
// Отменяем прыжок (фикс проблемы с двойным прыжком после приземления)
_groundMovement.CancelJumpRequest(); _groundMovement.CancelJumpRequest();
} }
} }
public void BlockAbilities(float duration)
{
isAbilitiesBlocked = true;
abilityBlockTimer = duration;
}
// Метод вызывается перед обновлением персонажа (можно использовать для подготовки)
public void BeforeCharacterUpdate(float deltaTime) public void BeforeCharacterUpdate(float deltaTime)
{ {
} }
// Метод вызывается после обработки столкновения с землей
public void PostGroundingUpdate(float deltaTime) public void PostGroundingUpdate(float deltaTime)
{ {
if (_dashMechanic.IsDashing && !motor.GroundingStatus.IsStableOnGround) // Если во время слайда персонаж оторвался от земли - заканчиваем слайд
_dashMechanic.EndDash();
if (_slideMechanic.IsSliding && !motor.GroundingStatus.IsStableOnGround) if (_slideMechanic.IsSliding && !motor.GroundingStatus.IsStableOnGround)
{
_slideMechanic.EndSlide(); _slideMechanic.EndSlide();
}
} }
// Метод вызывается после обновления персонажа
public void AfterCharacterUpdate(float deltaTime) public void AfterCharacterUpdate(float deltaTime)
{ {
_wallLeanMechanic.Update( _wallLeanMechanic.UpdateLean(motor, deltaTime);
_wallRunningMechanic.IsWallRunning,
_wallRunningMechanic.GetWallNormal(),
motor.CharacterForward
);
} }
// Проверка валидности коллайдера для обработки столкновений
public bool IsColliderValidForCollisions(Collider coll) public bool IsColliderValidForCollisions(Collider coll)
{ {
return true; return true; // Все коллайдеры считаются валидными для столкновений
} }
// Обработка столкновения с землей (вызывается при приземлении)
public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, public void OnGroundHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
ref HitStabilityReport hitStabilityReport) ref HitStabilityReport hitStabilityReport)
{ {
} }
// Обработка столкновения при движении (вызывается при ударе о препятствие)
public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint, public void OnMovementHit(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
ref HitStabilityReport hitStabilityReport) ref HitStabilityReport hitStabilityReport)
{ {
if (_wallRunningMechanic.TryStartWallRun(hitNormal, hitPoint)) _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, public void ProcessHitStabilityReport(Collider hitCollider, Vector3 hitNormal, Vector3 hitPoint,
Vector3 atCharacterPosition, Vector3 atCharacterPosition,
Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport) Quaternion atCharacterRotation, ref HitStabilityReport hitStabilityReport)
{ {
} }
// Обработка дискретных столкновений (для триггеров и специальных коллайдеров)
public void OnDiscreteCollisionDetected(Collider hitCollider) public void OnDiscreteCollisionDetected(Collider hitCollider)
{ {
} }
@@ -11,23 +11,40 @@ 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; }
// Флаг-свойство указывающий, что активен режим спринта
public bool IsSprinting { get; set; }
// Вращение камеры в FirstPerson режиме для синхронизации (только горизонтальная компонента) // Вращение камеры в FirstPerson режиме для синхронизации (только горизонтальная компонента)
public Quaternion FirstPersonCameraRotation { get; set; } public Quaternion FirstPersonCameraRotation { get; set; }
// Флаг-свойство указывающий, что нужно применить вращение камеры к персонажу // Флаг-свойство указывающий, что нужно применить вращение камеры к персонажу
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)
{ {
// Сохраняем текущий режим камеры // Сохраняем текущий режим камеры
var currentCameraMode = inputs.CameraMode; var currentCameraMode = inputs.CameraMode;
// Обработка спринта
IsSprinting = inputs.SprintPressed && inputs.MoveAxisForward > _sprintAngleThresholdCos; // Спринт только при движении на заданный в конфиге угол
// Нормализация вектора движения и ограничение его величины до 1 // Нормализация вектора движения и ограничение его величины до 1
var moveInputVector = var moveInputVector =
Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0, inputs.MoveAxisForward), 1f); Vector3.ClampMagnitude(new Vector3(inputs.MoveAxisRight, 0, inputs.MoveAxisForward), 1f);
@@ -96,7 +113,7 @@ namespace Code.Gameplay.Character.Controller.Input
var cameraForward = cameraRotation * Vector3.forward; var cameraForward = cameraRotation * Vector3.forward;
// Проецируем на горизонтальную плоскость // Проецируем на горизонтальную плоскость
var horizontalForward = Vector3.ProjectOnPlane(cameraForward, Vector3.up).normalized; var horizontalForward = Vector3.ProjectOnPlane(cameraForward, motor.CharacterUp).normalized;
// Если проекция вырождена, используем текущее направление персонажа // Если проекция вырождена, используем текущее направление персонажа
if (horizontalForward.sqrMagnitude < 0.01f) if (horizontalForward.sqrMagnitude < 0.01f)
Loaded 100 of 1175 files, more files were not shown because too many files have changed in this diff. Show more