├── LICENSE ├── README.md └── RimeFramework ├── #Generate └── Input │ ├── InputSettings.cs │ └── InputSettings.inputactions ├── Core ├── Animators │ └── Animators.cs ├── Audios │ └── Audios.cs ├── Consoles │ └── Consoles.cs ├── Controls │ └── Controls.cs ├── Cycles │ ├── Cycles.cs │ └── IRimeCycle │ │ └── ICycles.cs ├── Navigations │ ├── Navigations.cs │ └── Panels │ │ ├── Panel.cs │ │ └── PanelGroup.cs ├── Observers │ ├── ObsValue.cs │ └── Observers.cs ├── Pools │ ├── IRimePool │ │ └── IPool.cs │ └── Pools.cs ├── RimeManager.cs ├── Scenes │ └── Scenes.cs └── States │ ├── State │ └── State.cs │ └── States.cs ├── Tool └── Singleton.cs └── Utility ├── ColorUtility.cs ├── NumberUtility.cs ├── ObjectUtility.cs ├── StringUtility.cs └── VectorUtility.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 AstoraGray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rime Framework v0.0.1 2 |
3 | 4 |
5 | 「Rime Framework」很轻,就像雾凇上的白霜一般,覆之于上,将其包裹着的物体装饰的冰洁、神圣。 6 | 7 | https://github.com/AstoraGray/Unity-RimeFramework 8 | 9 | 「Rime Framework」是Unity的一个超轻量级框架,目的是提供简洁的接口,使用起来不会破坏原代码的结构,就像没有一样。目前目前主要有九个模块:Consoles、Controls、States、Pools、Cycles、Animators、Scenes、Navigations、Audios。RimeManager负责启动模块。 10 | 11 | Consoles 💻 12 | 13 | Rime Framework的控制台,负责打印RimeFramework的Log,支持打印实例和类型 14 | 15 | Controls 🎮 16 | 17 | Rime Framework的控制器,基于InputSystem开发,用事件驱动控制输入事件,支持设备热插拔,蓄力长按,并且能很好的扩展改建 18 | 19 | States 🗡️ 20 | 21 | Rime Framework的状态机,非常简洁易用,可以和Controls配合完成角色操纵的开发,并且能很好的完成状态切换、动画切换的操作 22 | 23 | Pools 💧 24 | 25 | Rime Framework的对象池,支持Object、Component、GameObject的三重管理,并且能够配合Cycles实现非Mono完整的Unity周期 26 | 27 | Cycles 🕙 28 | 29 | Rime Framework的生命周期控制,配合Pools优化内存,能够很简单地实现非Mono类加入到Unity循环的操作 30 | 31 | Animators ✍️ 32 | 33 | Rime Framework的动画师,注册播放动画,动画融合以及动画组、支持成功失败回调,可以脱离Animator连线 34 | 35 | Scenes 🎬 36 | 37 | Rime Framework的布景员,负责异步加载和卸载场景,支持完成回调、取消回调 38 | 39 | Navigations ➡️ 40 | 41 | Rime Framework的导航组,对游戏中的所有面板进行导航,并且有防循环机制 42 | 43 | Audios 🔊 44 | 45 | Rime Framework的音效师,管理游戏中的所有声音,并且与Pools联动 46 | 47 | Obsersers 📷 48 | 49 | Rime Framework的观察者,管理游戏中的有参无参事件,并且有协同的ObsValue观察值 50 | 51 | ------ 52 | 53 | The "Rime Framework" is lightweight, like the white frost on rime ice, enveloping objects and adorning them with purity and sanctity. 54 | 55 | https://github.com/AstoraGray/Unity-RimeFramework 56 | 57 | The "Rime Framework" is an ultra-lightweight framework for Unity, designed to provide a concise interface that does not disrupt the structure of the original code, making it feel almost invisible. Currently, it features nine main modules: Consoles, Controls, States, Pools, Cycles, Animators, Scenes, Navigations, and Audios. The RimeManager is responsible for initializing these modules. 58 | 59 | **Consoles 💻** 60 | 61 | The console of the Rime Framework, responsible for printing out the logs of RimeFramework. It supports logging for instances and types. 62 | 63 | **Controls 🎮** 64 | 65 | The controller of the Rime Framework, developed based on the InputSystem. It uses event-driven mechanisms to manage input events, supports hot-swapping of devices, and allows for long press actions. It is also easily extensible and customizable. 66 | 67 | **States 🗡️** 68 | 69 | The state machine of the Rime Framework, which is simple and easy to use, can work in conjunction with Controls to facilitate the development of character manipulation, and effectively handle state and animation transitions. 70 | 71 | **Pools 💧** 72 | 73 | The object pool of the Rime Framework, which supports triple management of Objects, Components, and GameObjects, and integrates well with Cycles to achieve a complete Unity lifecycle without using Mono. 74 | 75 | **Cycles 🕙** 76 | 77 | The lifecycle management of the Rime Framework, which optimizes memory in conjunction with Pools and makes it easy to add non-Mono classes into the Unity loop. 78 | 79 | **Animators ✍️** 80 | 81 | The animator of the Rime Framework, which registers animations for playback, blending animations, and supports callback mechanisms for success and failure, allowing it to operate independently of Animator connections. 82 | 83 | **Scenes 🎬** 84 | 85 | The scene manager of the Rime Framework, responsible for asynchronously loading and unloading scenes, supporting completion callbacks and cancellation callbacks. 86 | 87 | **Navigations ➡️** 88 | 89 | The navigation group of the Rime Framework, which manages navigation for all panels in the game and includes a mechanism to prevent loops. 90 | 91 | **Audios 🔊** 92 | 93 | The sound engineer of the Rime Framework, which manages all audio in the game and interacts with Pools. 94 | 95 | Obsersers 📷 96 | 97 | The observers of the Rime Framework manage both parameterized and non-parameterized events in the game, and have collaborative ObsValue observation values. 98 | -------------------------------------------------------------------------------- /RimeFramework/#Generate/Input/InputSettings.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator 4 | // version 1.7.0 5 | // from Assets/Settings/Input/InputSettings.inputactions 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | //------------------------------------------------------------------------------ 11 | 12 | using System; 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | using UnityEngine.InputSystem; 16 | using UnityEngine.InputSystem.Utilities; 17 | 18 | public partial class @InputSettings: IInputActionCollection2, IDisposable 19 | { 20 | public InputActionAsset asset { get; } 21 | public @InputSettings() 22 | { 23 | asset = InputActionAsset.FromJson(@"{ 24 | ""name"": ""InputSettings"", 25 | ""maps"": [ 26 | { 27 | ""name"": ""Player"", 28 | ""id"": ""22f1b114-6e5e-4f78-8694-b0c88b1d2fc1"", 29 | ""actions"": [ 30 | { 31 | ""name"": ""Move"", 32 | ""type"": ""PassThrough"", 33 | ""id"": ""90af6768-a9a9-45e8-bc84-a403d87e7cb0"", 34 | ""expectedControlType"": ""Vector2"", 35 | ""processors"": """", 36 | ""interactions"": """", 37 | ""initialStateCheck"": true 38 | }, 39 | { 40 | ""name"": ""Jump"", 41 | ""type"": ""Button"", 42 | ""id"": ""71d50bfa-a691-417e-8484-ab915a4af472"", 43 | ""expectedControlType"": ""Button"", 44 | ""processors"": """", 45 | ""interactions"": ""Press"", 46 | ""initialStateCheck"": false 47 | } 48 | ], 49 | ""bindings"": [ 50 | { 51 | ""name"": ""WASD"", 52 | ""id"": ""b6be6501-d8e3-4e4f-865f-c42285068746"", 53 | ""path"": ""2DVector"", 54 | ""interactions"": """", 55 | ""processors"": """", 56 | ""groups"": """", 57 | ""action"": ""Move"", 58 | ""isComposite"": true, 59 | ""isPartOfComposite"": false 60 | }, 61 | { 62 | ""name"": ""up"", 63 | ""id"": ""5f277161-e709-47a3-8d2d-afdb5062da15"", 64 | ""path"": ""/w"", 65 | ""interactions"": """", 66 | ""processors"": """", 67 | ""groups"": ""Keyboard Mouse"", 68 | ""action"": ""Move"", 69 | ""isComposite"": false, 70 | ""isPartOfComposite"": true 71 | }, 72 | { 73 | ""name"": ""down"", 74 | ""id"": ""19d812f5-ce19-4cf2-acca-de5ae92599f5"", 75 | ""path"": ""/s"", 76 | ""interactions"": """", 77 | ""processors"": """", 78 | ""groups"": ""Keyboard Mouse"", 79 | ""action"": ""Move"", 80 | ""isComposite"": false, 81 | ""isPartOfComposite"": true 82 | }, 83 | { 84 | ""name"": ""left"", 85 | ""id"": ""e1f20037-8df4-45b7-b4de-5a6a579d403b"", 86 | ""path"": ""/a"", 87 | ""interactions"": """", 88 | ""processors"": """", 89 | ""groups"": ""Keyboard Mouse"", 90 | ""action"": ""Move"", 91 | ""isComposite"": false, 92 | ""isPartOfComposite"": true 93 | }, 94 | { 95 | ""name"": ""right"", 96 | ""id"": ""1b9e2560-d735-4975-bb9b-4127be318379"", 97 | ""path"": ""/d"", 98 | ""interactions"": """", 99 | ""processors"": """", 100 | ""groups"": ""Keyboard Mouse"", 101 | ""action"": ""Move"", 102 | ""isComposite"": false, 103 | ""isPartOfComposite"": true 104 | }, 105 | { 106 | ""name"": """", 107 | ""id"": ""6fb6c7ec-8c3b-4e74-bdae-321dca219223"", 108 | ""path"": ""/leftStick"", 109 | ""interactions"": """", 110 | ""processors"": """", 111 | ""groups"": ""Controller"", 112 | ""action"": ""Move"", 113 | ""isComposite"": false, 114 | ""isPartOfComposite"": false 115 | }, 116 | { 117 | ""name"": """", 118 | ""id"": ""5f7c8403-8819-41d9-a8ef-ea25fbf6034c"", 119 | ""path"": ""/space"", 120 | ""interactions"": """", 121 | ""processors"": """", 122 | ""groups"": ""Keyboard Mouse"", 123 | ""action"": ""Jump"", 124 | ""isComposite"": false, 125 | ""isPartOfComposite"": false 126 | }, 127 | { 128 | ""name"": """", 129 | ""id"": ""e69a09f8-f362-4bf5-9cfe-085f6d3a8ec8"", 130 | ""path"": ""/buttonSouth"", 131 | ""interactions"": """", 132 | ""processors"": """", 133 | ""groups"": """", 134 | ""action"": ""Jump"", 135 | ""isComposite"": false, 136 | ""isPartOfComposite"": false 137 | } 138 | ] 139 | } 140 | ], 141 | ""controlSchemes"": [ 142 | { 143 | ""name"": ""Controller"", 144 | ""bindingGroup"": ""Controller"", 145 | ""devices"": [ 146 | { 147 | ""devicePath"": """", 148 | ""isOptional"": false, 149 | ""isOR"": false 150 | } 151 | ] 152 | }, 153 | { 154 | ""name"": ""Keyboard Mouse"", 155 | ""bindingGroup"": ""Keyboard Mouse"", 156 | ""devices"": [ 157 | { 158 | ""devicePath"": """", 159 | ""isOptional"": false, 160 | ""isOR"": false 161 | }, 162 | { 163 | ""devicePath"": """", 164 | ""isOptional"": false, 165 | ""isOR"": false 166 | } 167 | ] 168 | } 169 | ] 170 | }"); 171 | // Player 172 | m_Player = asset.FindActionMap("Player", throwIfNotFound: true); 173 | m_Player_Move = m_Player.FindAction("Move", throwIfNotFound: true); 174 | m_Player_Jump = m_Player.FindAction("Jump", throwIfNotFound: true); 175 | } 176 | 177 | public void Dispose() 178 | { 179 | UnityEngine.Object.Destroy(asset); 180 | } 181 | 182 | public InputBinding? bindingMask 183 | { 184 | get => asset.bindingMask; 185 | set => asset.bindingMask = value; 186 | } 187 | 188 | public ReadOnlyArray? devices 189 | { 190 | get => asset.devices; 191 | set => asset.devices = value; 192 | } 193 | 194 | public ReadOnlyArray controlSchemes => asset.controlSchemes; 195 | 196 | public bool Contains(InputAction action) 197 | { 198 | return asset.Contains(action); 199 | } 200 | 201 | public IEnumerator GetEnumerator() 202 | { 203 | return asset.GetEnumerator(); 204 | } 205 | 206 | IEnumerator IEnumerable.GetEnumerator() 207 | { 208 | return GetEnumerator(); 209 | } 210 | 211 | public void Enable() 212 | { 213 | asset.Enable(); 214 | } 215 | 216 | public void Disable() 217 | { 218 | asset.Disable(); 219 | } 220 | 221 | public IEnumerable bindings => asset.bindings; 222 | 223 | public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false) 224 | { 225 | return asset.FindAction(actionNameOrId, throwIfNotFound); 226 | } 227 | 228 | public int FindBinding(InputBinding bindingMask, out InputAction action) 229 | { 230 | return asset.FindBinding(bindingMask, out action); 231 | } 232 | 233 | // Player 234 | private readonly InputActionMap m_Player; 235 | private List m_PlayerActionsCallbackInterfaces = new List(); 236 | private readonly InputAction m_Player_Move; 237 | private readonly InputAction m_Player_Jump; 238 | public struct PlayerActions 239 | { 240 | private @InputSettings m_Wrapper; 241 | public PlayerActions(@InputSettings wrapper) { m_Wrapper = wrapper; } 242 | public InputAction @Move => m_Wrapper.m_Player_Move; 243 | public InputAction @Jump => m_Wrapper.m_Player_Jump; 244 | public InputActionMap Get() { return m_Wrapper.m_Player; } 245 | public void Enable() { Get().Enable(); } 246 | public void Disable() { Get().Disable(); } 247 | public bool enabled => Get().enabled; 248 | public static implicit operator InputActionMap(PlayerActions set) { return set.Get(); } 249 | public void AddCallbacks(IPlayerActions instance) 250 | { 251 | if (instance == null || m_Wrapper.m_PlayerActionsCallbackInterfaces.Contains(instance)) return; 252 | m_Wrapper.m_PlayerActionsCallbackInterfaces.Add(instance); 253 | @Move.started += instance.OnMove; 254 | @Move.performed += instance.OnMove; 255 | @Move.canceled += instance.OnMove; 256 | @Jump.started += instance.OnJump; 257 | @Jump.performed += instance.OnJump; 258 | @Jump.canceled += instance.OnJump; 259 | } 260 | 261 | private void UnregisterCallbacks(IPlayerActions instance) 262 | { 263 | @Move.started -= instance.OnMove; 264 | @Move.performed -= instance.OnMove; 265 | @Move.canceled -= instance.OnMove; 266 | @Jump.started -= instance.OnJump; 267 | @Jump.performed -= instance.OnJump; 268 | @Jump.canceled -= instance.OnJump; 269 | } 270 | 271 | public void RemoveCallbacks(IPlayerActions instance) 272 | { 273 | if (m_Wrapper.m_PlayerActionsCallbackInterfaces.Remove(instance)) 274 | UnregisterCallbacks(instance); 275 | } 276 | 277 | public void SetCallbacks(IPlayerActions instance) 278 | { 279 | foreach (var item in m_Wrapper.m_PlayerActionsCallbackInterfaces) 280 | UnregisterCallbacks(item); 281 | m_Wrapper.m_PlayerActionsCallbackInterfaces.Clear(); 282 | AddCallbacks(instance); 283 | } 284 | } 285 | public PlayerActions @Player => new PlayerActions(this); 286 | private int m_ControllerSchemeIndex = -1; 287 | public InputControlScheme ControllerScheme 288 | { 289 | get 290 | { 291 | if (m_ControllerSchemeIndex == -1) m_ControllerSchemeIndex = asset.FindControlSchemeIndex("Controller"); 292 | return asset.controlSchemes[m_ControllerSchemeIndex]; 293 | } 294 | } 295 | private int m_KeyboardMouseSchemeIndex = -1; 296 | public InputControlScheme KeyboardMouseScheme 297 | { 298 | get 299 | { 300 | if (m_KeyboardMouseSchemeIndex == -1) m_KeyboardMouseSchemeIndex = asset.FindControlSchemeIndex("Keyboard Mouse"); 301 | return asset.controlSchemes[m_KeyboardMouseSchemeIndex]; 302 | } 303 | } 304 | public interface IPlayerActions 305 | { 306 | void OnMove(InputAction.CallbackContext context); 307 | void OnJump(InputAction.CallbackContext context); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /RimeFramework/#Generate/Input/InputSettings.inputactions: -------------------------------------------------------------------------------- 1 | { 2 | "name": "InputSettings", 3 | "maps": [ 4 | { 5 | "name": "Player", 6 | "id": "22f1b114-6e5e-4f78-8694-b0c88b1d2fc1", 7 | "actions": [ 8 | { 9 | "name": "Move", 10 | "type": "PassThrough", 11 | "id": "90af6768-a9a9-45e8-bc84-a403d87e7cb0", 12 | "expectedControlType": "Vector2", 13 | "processors": "", 14 | "interactions": "", 15 | "initialStateCheck": true 16 | }, 17 | { 18 | "name": "Jump", 19 | "type": "Button", 20 | "id": "71d50bfa-a691-417e-8484-ab915a4af472", 21 | "expectedControlType": "Button", 22 | "processors": "", 23 | "interactions": "Press", 24 | "initialStateCheck": false 25 | } 26 | ], 27 | "bindings": [ 28 | { 29 | "name": "WASD", 30 | "id": "b6be6501-d8e3-4e4f-865f-c42285068746", 31 | "path": "2DVector", 32 | "interactions": "", 33 | "processors": "", 34 | "groups": "", 35 | "action": "Move", 36 | "isComposite": true, 37 | "isPartOfComposite": false 38 | }, 39 | { 40 | "name": "up", 41 | "id": "5f277161-e709-47a3-8d2d-afdb5062da15", 42 | "path": "/w", 43 | "interactions": "", 44 | "processors": "", 45 | "groups": "Keyboard Mouse", 46 | "action": "Move", 47 | "isComposite": false, 48 | "isPartOfComposite": true 49 | }, 50 | { 51 | "name": "down", 52 | "id": "19d812f5-ce19-4cf2-acca-de5ae92599f5", 53 | "path": "/s", 54 | "interactions": "", 55 | "processors": "", 56 | "groups": "Keyboard Mouse", 57 | "action": "Move", 58 | "isComposite": false, 59 | "isPartOfComposite": true 60 | }, 61 | { 62 | "name": "left", 63 | "id": "e1f20037-8df4-45b7-b4de-5a6a579d403b", 64 | "path": "/a", 65 | "interactions": "", 66 | "processors": "", 67 | "groups": "Keyboard Mouse", 68 | "action": "Move", 69 | "isComposite": false, 70 | "isPartOfComposite": true 71 | }, 72 | { 73 | "name": "right", 74 | "id": "1b9e2560-d735-4975-bb9b-4127be318379", 75 | "path": "/d", 76 | "interactions": "", 77 | "processors": "", 78 | "groups": "Keyboard Mouse", 79 | "action": "Move", 80 | "isComposite": false, 81 | "isPartOfComposite": true 82 | }, 83 | { 84 | "name": "", 85 | "id": "6fb6c7ec-8c3b-4e74-bdae-321dca219223", 86 | "path": "/leftStick", 87 | "interactions": "", 88 | "processors": "", 89 | "groups": "Controller", 90 | "action": "Move", 91 | "isComposite": false, 92 | "isPartOfComposite": false 93 | }, 94 | { 95 | "name": "", 96 | "id": "5f7c8403-8819-41d9-a8ef-ea25fbf6034c", 97 | "path": "/space", 98 | "interactions": "", 99 | "processors": "", 100 | "groups": "Keyboard Mouse", 101 | "action": "Jump", 102 | "isComposite": false, 103 | "isPartOfComposite": false 104 | }, 105 | { 106 | "name": "", 107 | "id": "e69a09f8-f362-4bf5-9cfe-085f6d3a8ec8", 108 | "path": "/buttonSouth", 109 | "interactions": "", 110 | "processors": "", 111 | "groups": "", 112 | "action": "Jump", 113 | "isComposite": false, 114 | "isPartOfComposite": false 115 | } 116 | ] 117 | } 118 | ], 119 | "controlSchemes": [ 120 | { 121 | "name": "Controller", 122 | "bindingGroup": "Controller", 123 | "devices": [ 124 | { 125 | "devicePath": "", 126 | "isOptional": false, 127 | "isOR": false 128 | } 129 | ] 130 | }, 131 | { 132 | "name": "Keyboard Mouse", 133 | "bindingGroup": "Keyboard Mouse", 134 | "devices": [ 135 | { 136 | "devicePath": "", 137 | "isOptional": false, 138 | "isOR": false 139 | }, 140 | { 141 | "devicePath": "", 142 | "isOptional": false, 143 | "isOR": false 144 | } 145 | ] 146 | } 147 | ] 148 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Animators/Animators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using RimeFramework.Tool; 6 | using UnityEngine; 7 | 8 | namespace RimeFramework.Core 9 | { 10 | /// 11 | /// 霜 · 动画师 ✍️ 12 | /// 13 | /// Note: 注册播放动画以及动画组、支持成功失败回调,可以脱离Animator连线 14 | /// 注册动画组 15 | /// 播放动画 16 | /// Author: AstoraGray 17 | public class Animators : Singleton 18 | { 19 | private static readonly Dictionary _dicGroups = new(); // 动画组 20 | 21 | private static readonly Dictionary _dicPlays = new (); // 播放组 22 | 23 | /// 24 | /// 注册组 25 | /// 26 | /// 组名 27 | /// 序列动画名 28 | public static void Register(string groupName,params string[] animations) => _dicGroups[groupName] = animations; 29 | 30 | /// 31 | /// 播放 动画/动画组,组优先级 > 动画 32 | /// 33 | /// 动画 34 | /// 动画组名 / 动画名 35 | /// 渐变时间 36 | /// 成功回调 37 | /// 失败回调 38 | public static void Play(Animator animator,string name,float fadeTime = 0.3f,Action onComplete = null,Action onCancel = null) 39 | { 40 | if (!_dicGroups.TryGetValue(name, out string[] animations)) 41 | { 42 | _dicPlays[animator] = Instance.StartCoroutine(Playing(animator,name,fadeTime, onComplete, onCancel)); 43 | return; 44 | } 45 | Play(animator,animations,fadeTime,onComplete,onCancel); 46 | } 47 | /// 48 | /// 播放动画组 49 | /// 50 | /// 动画 51 | /// 动画序列 52 | /// 渐变时间 53 | /// 成功回调 54 | /// 失败回调 55 | private static void Play(Animator animator,string[] animations,float fadeTime = 0.3f,Action onComplete = null,Action onCancel = null) 56 | { 57 | _dicPlays[animator] = Instance.StartCoroutine(Playing(animator,animations[0], fadeTime, () => 58 | { 59 | if (animations.Length == 1) 60 | { 61 | onComplete?.Invoke(); 62 | return; 63 | } 64 | 65 | animations = animations.Skip(1).ToArray(); 66 | Play(animator,animations,fadeTime,onComplete,onCancel); 67 | },onCancel)); 68 | } 69 | /// 70 | /// 播放协程 71 | /// 72 | /// 动画 73 | /// 动画序列 74 | /// 渐变时间 75 | /// 成功回调 76 | /// 失败回调 77 | /// 78 | private static IEnumerator Playing(Animator animator,string name,float fadeTime,Action onComplete,Action onCancel) 79 | { 80 | yield return null; 81 | Coroutine coroutine = _dicPlays[animator]; 82 | animator.CrossFadeInFixedTime(name,fadeTime); 83 | float length = animator.GetCurrentAnimatorClipInfo(0).Length - fadeTime; 84 | float startTime = Time.time; 85 | while (Time.time < startTime + length) 86 | { 87 | if (_dicPlays[animator] != coroutine) 88 | { 89 | onCancel?.Invoke(); 90 | yield break; 91 | } 92 | yield return Time.deltaTime; 93 | } 94 | onComplete?.Invoke(); 95 | if (_dicPlays[animator] == coroutine) 96 | { 97 | _dicPlays[animator] = null; 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Audios/Audios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using RimeFramework.Tool; 5 | using UnityEngine; 6 | 7 | namespace RimeFramework.Core 8 | { 9 | /// 10 | /// 霜 · 音效师 🔊 11 | /// 12 | /// 支持多通道播放声音、BGM淡入淡出、3D音效、循环播放、提供最简单的调用接口 13 | /// 播放3D声音,需要一个拥有者 14 | /// 播放2D声音 15 | /// 播放UI声音 16 | /// 播放BGM,淡入淡出 17 | /// Author: AstoraGray 18 | [RequireComponent(typeof(AudioSource))] 19 | public class Audios : Singleton 20 | { 21 | private static AudioSource _audioMusic; // BGM音轨 22 | 23 | public static readonly float musicVolume = 1; // BGM音量 24 | 25 | public static readonly float s2dVolume = 1; // 2D音量 26 | 27 | public static readonly float s3dVolume = 1; // 3D音量 28 | 29 | public static readonly float suiVolume = 1; // UI音量 30 | 31 | private static readonly Dictionary _dicClips = new(); // 缓存音频 32 | 33 | private static Coroutine _coroutineMusicFade; // 淡入淡出协程 34 | 35 | private static AudioClip _clipCurMusic; // 当前BGM 36 | 37 | private const float FADE_TIME = 2f; // 淡入淡出时间(真实时间) 38 | 39 | private const string AUDIO_PATH = "Audios/"; // 音频文件路径 40 | 41 | private enum AudioType 42 | { 43 | S2D, // 2D声音(无距离、可多个) 44 | S3D, // 3D声音(有距离、可多个) 45 | SUI, // UI声音(无距离、可多个) 46 | Music // BGM(无距离、单一、淡入淡出) 47 | } 48 | 49 | private enum FadeType 50 | { 51 | In, // 淡入 52 | Out // 淡出 53 | } 54 | /// 55 | /// 初始化 56 | /// 57 | protected override void Awake() 58 | { 59 | base.Awake(); 60 | _audioMusic = GetComponent(); 61 | _audioMusic.loop = true; 62 | _audioMusic.volume = 0f; 63 | } 64 | 65 | /// 66 | /// 播放3D声音 67 | /// 68 | public static void PlayS3D(MonoBehaviour own, string name,PlayingCall call = null,bool loop = false) => Play(own, name, AudioType.S3D,call,loop); 69 | /// 70 | /// 播放2D声音 71 | /// 72 | public static void PlayS2D(string name,PlayingCall call = null,bool loop = false) => Play(null, name, AudioType.S2D,call,loop); 73 | /// 74 | /// 播放UI声音 75 | /// 76 | public static void PlaySUI(string name,PlayingCall call = null,bool loop = false) => Play(null, name, AudioType.SUI,call,loop); 77 | /// 78 | /// 播放BGM 79 | /// 80 | public static void PlayMusic(string name) => Play(null, name, AudioType.Music,null,true); 81 | /// 82 | /// 播放声音 83 | /// 84 | /// 拥有者 85 | /// 音频名称 86 | /// 音频类型 87 | /// 播放回调 88 | /// 是否循环 89 | private static void Play(MonoBehaviour own, string name, AudioType type,PlayingCall call,bool loop) 90 | { 91 | AudioClip clip; 92 | if (!_dicClips.ContainsKey(name)) 93 | { 94 | string path = $"{AUDIO_PATH}{name}"; 95 | clip = Resources.Load(path); 96 | if (clip == null) 97 | { 98 | Consoles.Print(nameof(Audios),$"找不到音频文件{path}"); 99 | return; 100 | } 101 | _dicClips[name] = clip; 102 | } 103 | 104 | clip = _dicClips[name]; 105 | 106 | AudioSource audio = null; 107 | switch (type) 108 | { 109 | case AudioType.S2D: 110 | audio = Pools.Take(); 111 | audio.clip = clip; 112 | audio.volume = s2dVolume; 113 | audio.spatialBlend = 0; 114 | audio.loop = loop; 115 | Instance.StartCoroutine(PlaySound(null, audio,call)); 116 | break; 117 | case AudioType.S3D: 118 | if (own == null) 119 | { 120 | Consoles.Print(nameof(Audios),$"播放{name}声音失败,3D声音需要指定一个拥有者"); 121 | return; 122 | } 123 | audio = Pools.Take(); 124 | audio.clip = clip; 125 | audio.volume = s3dVolume; 126 | audio.spatialBlend = 1; 127 | audio.loop = loop; 128 | Instance.StartCoroutine(PlaySound(own, audio,call)); 129 | break; 130 | case AudioType.SUI: 131 | audio = Pools.Take(); 132 | audio.clip = clip; 133 | audio.volume = suiVolume; 134 | audio.spatialBlend = 0; 135 | audio.loop = loop; 136 | Instance.StartCoroutine(PlaySound(null, audio,call)); 137 | break; 138 | case AudioType.Music: 139 | if (clip == _clipCurMusic) 140 | { 141 | return; 142 | } 143 | 144 | _clipCurMusic = clip; 145 | 146 | if (_audioMusic.isPlaying) 147 | { 148 | Consoles.Print(nameof(Audios),$"开始淡出BGM {_audioMusic.name}"); 149 | _coroutineMusicFade = Instance.StartCoroutine(FadeMusic(_audioMusic.volume,0,FADE_TIME,FadeType.Out, () => 150 | { 151 | _audioMusic.clip = clip; 152 | Consoles.Print(nameof(Audios),$"开始淡入BGM {name}"); 153 | _coroutineMusicFade = Instance.StartCoroutine(FadeMusic(_audioMusic.volume,musicVolume,FADE_TIME,FadeType.In)); 154 | })); 155 | } 156 | else 157 | { 158 | _audioMusic.clip = clip; 159 | Consoles.Print(nameof(Audios),$"开始淡入BGM {name}"); 160 | _coroutineMusicFade = Instance.StartCoroutine(FadeMusic(_audioMusic.volume,musicVolume,FADE_TIME,FadeType.In)); 161 | } 162 | break; 163 | } 164 | } 165 | /// 166 | /// 淡入淡出BGM 167 | /// 168 | /// 开始音量 169 | /// 结束音量 170 | /// 持续时间 171 | /// 淡入/淡出 172 | /// 结束回调 173 | /// 174 | private static IEnumerator FadeMusic(float startVolume, float endVolume, float duration,FadeType fadeType, Action onComplete = null) 175 | { 176 | yield return null; 177 | Coroutine coroutine = _coroutineMusicFade; 178 | float startTime = Time.time; 179 | _audioMusic.volume = startVolume; 180 | 181 | if (fadeType == FadeType.In) 182 | { 183 | _audioMusic.Play(); 184 | } 185 | 186 | while (Time.time < startTime + duration) 187 | { 188 | if (coroutine != _coroutineMusicFade) 189 | { 190 | yield break; 191 | } 192 | _audioMusic.volume = Mathf.Lerp(startVolume, endVolume, 1 - (startTime + duration - Time.time) / duration); 193 | yield return null; 194 | } 195 | 196 | _audioMusic.volume = endVolume; 197 | 198 | if (fadeType == FadeType.Out) 199 | { 200 | _audioMusic.Stop(); 201 | } 202 | 203 | onComplete?.Invoke(); 204 | } 205 | /// 206 | /// 播放条件(可空) 207 | /// 208 | public delegate bool PlayingCall(MonoBehaviour own, string name); 209 | 210 | /// 211 | /// 播放声音协程 212 | /// 213 | /// 拥有者 214 | /// 音频 215 | /// 播放条件回调 216 | /// 217 | private static IEnumerator PlaySound(MonoBehaviour own, AudioSource audio, PlayingCall call) 218 | { 219 | audio.Play(); 220 | float clipLength = audio.clip.length; 221 | float endTime = Time.time + clipLength; 222 | 223 | while (call?.Invoke(own,audio.clip.name) ?? (audio.loop || Time.time < endTime)) 224 | { 225 | if (own != null) 226 | { 227 | audio.transform.position = own.transform.position; 228 | } 229 | 230 | if (!audio.isPlaying) 231 | { 232 | break; 233 | } 234 | 235 | yield return null; 236 | } 237 | 238 | audio.Stop(); 239 | Pools.Put(audio); 240 | } 241 | } 242 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Consoles/Consoles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RimeFramework.Tool; 5 | using RimeFramework.Utility; 6 | using UnityEngine; 7 | using Object = System.Object; 8 | 9 | namespace RimeFramework.Core 10 | { 11 | /// 12 | /// 霜 · 控制台 💻 13 | /// 14 | /// Note: 类打印传Type,实例传Object,目前可以配置特定的颜色 15 | /// 打印类 16 | /// 打印实例 17 | /// Author: AstoraGray 18 | public class Consoles : Singleton 19 | { 20 | private static readonly Dictionary _dicMapping = new() 21 | { 22 | { nameof(RimeManager), Color.cyan }, 23 | { nameof(States), Color.cyan }, 24 | { nameof(Controls), Color.cyan }, 25 | { nameof(Cycles), Color.cyan }, 26 | { nameof(Pools), Color.cyan }, 27 | { nameof(Navigations),Color.cyan}, 28 | { nameof(Scenes), Color.cyan }, 29 | { nameof(Animators),Color.cyan}, 30 | { nameof(Observers),Color.cyan} 31 | }; 32 | 33 | public static void Print(string name,string content) 34 | { 35 | if (_dicMapping.ContainsKey(name)) 36 | { 37 | name = $"{name}"; 38 | } 39 | Debug.Log($"{name}: {content}"); 40 | } 41 | 42 | public static void Print(Object obj, string content) 43 | { 44 | string objName; 45 | MonoBehaviour own = obj as MonoBehaviour; 46 | if (own != null) 47 | { 48 | objName = own.name; 49 | } 50 | else 51 | { 52 | objName = obj.GetType().Name; 53 | } 54 | Debug.Log($"{objName}: {content}"); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Controls/Controls.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using RimeFramework.Tool; 5 | using UnityEngine; 6 | using UnityEngine.InputSystem; 7 | 8 | namespace RimeFramework.Core 9 | { 10 | /// 11 | /// 霜 · 控制器 🎮 12 | /// 13 | /// Note: 后续将在Enable中初始化,读取保存的按键配置文件,以及管理协程 14 | /// 注册输入事件 15 | /// Author: AstoraGray 16 | public class Controls : Singleton 17 | { 18 | private static InputSettings _bindings; 19 | 20 | public static InputSettings Bindings 21 | { 22 | get 23 | { 24 | if (_bindings == null) 25 | { 26 | _bindings = new InputSettings(); 27 | _bindings.Enable(); 28 | } 29 | return _bindings; 30 | } 31 | } 32 | 33 | public static Action onControlSchemeChanged; // 输入切换事件 34 | 35 | public static Action onBehaviorUnregistered; // 输入注销事件,用来结束长按协程 36 | public static string CurrentControlScheme { get; private set; } = CONTROL_SCHEME_KEYBOARD_MOUSE; 37 | 38 | 39 | public const string CONTROL_SCHEME_KEYBOARD_MOUSE = "Keyboard Mouse"; // 键鼠 40 | public const string CONTROL_SCHEME_CONTROLLER = "Controller"; // 手柄 41 | 42 | 43 | public static bool UsingController() 44 | { 45 | return CurrentControlScheme == CONTROL_SCHEME_CONTROLLER; 46 | } 47 | 48 | public static bool UsingKeyboardMouse() 49 | { 50 | return CurrentControlScheme == CONTROL_SCHEME_KEYBOARD_MOUSE; 51 | } 52 | 53 | public static void OnLastInputDeviceChanged(string lastInputDevice) 54 | { 55 | UpdateCurrentControlScheme(lastInputDevice); 56 | } 57 | 58 | private static void UpdateCurrentControlScheme(string lastInputDevice) 59 | { 60 | switch (lastInputDevice) 61 | { 62 | case "Keyboard Mouse": 63 | SetCurrentControlScheme(CONTROL_SCHEME_KEYBOARD_MOUSE); 64 | break; 65 | case "Controller": 66 | SetCurrentControlScheme(CONTROL_SCHEME_CONTROLLER); 67 | break; 68 | default: 69 | Debug.LogWarning("上一个输入设备是未知的控制方案" + lastInputDevice); 70 | break; 71 | } 72 | } 73 | /// 74 | /// 输入设备切换 75 | /// 76 | /// 输入设备/风格 77 | private static void SetCurrentControlScheme(string newControlScheme) 78 | { 79 | if (CurrentControlScheme == newControlScheme) return; 80 | 81 | Consoles.Print(nameof(Controls),"[Input] 输入设备切换 [FROM: " + CurrentControlScheme + "] [TO: " + newControlScheme + 82 | "]"); 83 | 84 | CurrentControlScheme = newControlScheme; 85 | 86 | onControlSchemeChanged?.Invoke(); 87 | } 88 | 89 | private void OnEnable() 90 | { 91 | Bindings.Enable(); 92 | } 93 | 94 | private void OnDisable() 95 | { 96 | Bindings.Disable(); 97 | } 98 | 99 | public static BindingBehaviour Register(MonoBehaviour own,InputAction binding,Action behaviour) 100 | { 101 | BindingMonoBehaviour newBehaviour = new BindingMonoBehaviour(own, binding, behaviour); 102 | InputAction action = Bindings.FindAction(binding.name); 103 | action.performed += newBehaviour.Invoke; 104 | return newBehaviour; 105 | } 106 | 107 | public static BindingBehaviour Register(BindingBehaviour bindingBehavior) 108 | { 109 | InputAction action = Bindings.FindAction(bindingBehavior.binding.name); 110 | action.performed += bindingBehavior.Invoke; 111 | return bindingBehavior; 112 | } 113 | 114 | public static void UnRegister(BindingBehaviour behaviour) 115 | { 116 | InputAction action = Bindings.FindAction(behaviour.binding.name); 117 | if (action == null) 118 | { 119 | Consoles.Print(nameof(Controls),"找不到状态,是退出播放模式了吗?"); 120 | return; 121 | } 122 | action.performed -= behaviour.Invoke; 123 | onBehaviorUnregistered?.Invoke(behaviour); 124 | } 125 | 126 | public static Coroutine StartCoroutineOnInstance(IEnumerator coroutine) 127 | { 128 | return Instance.StartCoroutine(coroutine); 129 | } 130 | 131 | public static void StopCoroutineOnInstance(Coroutine coroutine) 132 | { 133 | if (coroutine != null && Instance != null) 134 | Instance.StopCoroutine(coroutine); 135 | } 136 | } 137 | 138 | /// 139 | /// 基础行为 140 | /// 141 | public class BindingBehaviour 142 | { 143 | public InputAction binding; 144 | public Action behaviour; 145 | public bool isEnabled; 146 | 147 | public BindingBehaviour(InputAction binding,Action behaviour,bool isEnabled = true) 148 | { 149 | this.binding = binding; 150 | this.behaviour = behaviour; 151 | this.isEnabled = isEnabled; 152 | } 153 | 154 | public virtual void Invoke(InputAction.CallbackContext context) 155 | { 156 | if (isEnabled) 157 | { 158 | behaviour?.Invoke(context); 159 | } 160 | } 161 | } 162 | /// 163 | /// 按键行为,绑定生命周期 164 | /// 165 | public class BindingMonoBehaviour : BindingBehaviour 166 | { 167 | public MonoBehaviour own; 168 | 169 | public BindingMonoBehaviour(MonoBehaviour own,InputAction binding, 170 | Action behaviour, 171 | bool isEnabled = true) 172 | : base(binding, behaviour, isEnabled) 173 | { 174 | this.own = own; 175 | } 176 | 177 | public override void Invoke(InputAction.CallbackContext context) 178 | { 179 | if (own == null || !own.isActiveAndEnabled) 180 | { 181 | Controls.UnRegister(this); 182 | return; 183 | } 184 | base.Invoke(context); 185 | } 186 | } 187 | /// 188 | /// 长按行为 189 | /// 190 | public class BindingHeldBehavior : BindingMonoBehaviour 191 | { 192 | public float holdDuration { get; private set; } // 长按时间 193 | public event Action onHoldStarted; // 开始长按 194 | public event Action onHoldCompleted; // 长按结束 195 | public event Action onButtonReleasedEarly; // 提前结束 196 | public event Action onEachFrameWhileButtonHeld; // 逐帧更新 197 | 198 | private Coroutine _activeBindingHeldCoroutine; 199 | 200 | public BindingHeldBehavior(MonoBehaviour own,InputAction binding, 201 | float holdDuration, 202 | Action onHoldStarted = null, 203 | Action onHoldCompleted = null, 204 | Action onButtonReleasedEarly = null, 205 | Action onEachFrameWhileButtonHeld = null, 206 | bool isEnabled = true) 207 | : base(own,binding, onHoldCompleted, isEnabled) 208 | { 209 | this.holdDuration = holdDuration; 210 | this.onHoldStarted = onHoldStarted; 211 | this.onHoldCompleted = onHoldCompleted; 212 | this.onButtonReleasedEarly = onButtonReleasedEarly; 213 | this.onEachFrameWhileButtonHeld = onEachFrameWhileButtonHeld; 214 | 215 | // 提前取消时,结束协程 216 | Controls.onBehaviorUnregistered += (BindingBehaviour behaviorThatWasUnregistered) => 217 | { 218 | if (behaviorThatWasUnregistered == this) 219 | { 220 | Controls.StopCoroutineOnInstance(_activeBindingHeldCoroutine); 221 | } 222 | }; 223 | } 224 | 225 | public override void Invoke(InputAction.CallbackContext context) 226 | { 227 | if (own == null || !own.isActiveAndEnabled) 228 | { 229 | Controls.UnRegister(this); 230 | return; 231 | } 232 | 233 | if (_activeBindingHeldCoroutine != null) 234 | { 235 | return; 236 | } 237 | _activeBindingHeldCoroutine = Controls.StartCoroutineOnInstance(ICheckForBindingHeld(context)); 238 | } 239 | 240 | private IEnumerator ICheckForBindingHeld(InputAction.CallbackContext context) 241 | { 242 | if (binding.controls[0] == null) 243 | { 244 | Consoles.Print(this,"绑定输入的按键为空"); 245 | yield return null; 246 | } 247 | 248 | onHoldStarted?.Invoke(context); 249 | 250 | float timeSinceButtonPressed = 0; 251 | while (AnyBindingControlIsHeld() || timeSinceButtonPressed < holdDuration) 252 | { 253 | timeSinceButtonPressed += Time.deltaTime; 254 | onEachFrameWhileButtonHeld?.Invoke(timeSinceButtonPressed); 255 | if (!AnyBindingControlIsHeld()) 256 | { 257 | onButtonReleasedEarly?.Invoke(timeSinceButtonPressed); 258 | _activeBindingHeldCoroutine = null; 259 | break; 260 | } 261 | 262 | if (timeSinceButtonPressed >= holdDuration) 263 | { 264 | onHoldCompleted?.Invoke(context); 265 | _activeBindingHeldCoroutine = null; 266 | break; 267 | } 268 | 269 | yield return new WaitForEndOfFrame(); 270 | } 271 | } 272 | 273 | private bool AnyBindingControlIsHeld() 274 | { 275 | return binding.controls.ToList().Any(control => control.IsPressed()); 276 | } 277 | } 278 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Cycles/Cycles.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | using RimeFramework.Tool; 4 | using RimeFramework.Utility; 5 | using UnityEngine; 6 | using Object = System.Object; 7 | 8 | namespace RimeFramework.Core 9 | { 10 | /// 11 | /// 霜 · 周期 🕙 12 | /// 13 | /// Note: 控制所有非Mono类的生命进程 14 | /// 挂载对象 15 | /// Author: AstoraGray 16 | public class Cycles : Singleton 17 | { 18 | private static readonly Dictionary _dicCells = new (); // 工作的细胞们 19 | 20 | private static readonly Dictionary _dicCellsReady = new(); // 就绪的细胞们 21 | 22 | private static readonly Queue _queueRecycle = new (); // 要回收的细胞列表 23 | 24 | private static readonly Queue _queueRecycleReady = new (); // 准备回收的细胞列表 25 | /// 26 | /// 细胞(ps:就像细胞一般的周期) 27 | /// 28 | private struct Cell 29 | { 30 | [CanBeNull] public IAwake awake; 31 | 32 | [CanBeNull] public IOnEnable onEnable; 33 | 34 | [CanBeNull] public IStart start; 35 | 36 | [CanBeNull] public IUpdate update; 37 | 38 | [CanBeNull] public IFixedUpdate fixedUpdate; 39 | 40 | [CanBeNull] public ILateUpdate lateUpdate; 41 | 42 | [CanBeNull] public IOnDisable onDisable; 43 | 44 | [CanBeNull] public IOnDestroy onDestroy; 45 | } 46 | /// 47 | /// 注册周期 48 | /// 49 | /// 绑定目标 50 | /// 周期类类型 51 | /// 52 | public static T Register(GameObject obj) where T : class,IBind, new() 53 | { 54 | return Register(Pools.Take(),obj); 55 | } 56 | /// 57 | /// 注册周期 58 | /// 59 | /// 自身实例 60 | /// 绑定目标 61 | /// 周期类类型 62 | /// 63 | private static T Register(T own,GameObject obj) where T : class,IBind, new() 64 | { 65 | if (own as MonoBehaviour != null) 66 | { 67 | Consoles.Print(nameof(Cycles),$"不允许Mono派生类加入周期"); 68 | return null; 69 | } 70 | 71 | Cell cell = new Cell(); 72 | 73 | if (own is IAwake awake) 74 | { 75 | cell.awake = awake; 76 | } 77 | 78 | if (own is IOnEnable onEnable) 79 | { 80 | cell.onEnable = onEnable; 81 | } 82 | 83 | if (own is IStart start) 84 | { 85 | cell.start = start; 86 | } 87 | 88 | if (own is IUpdate update) 89 | { 90 | cell.update = update; 91 | } 92 | 93 | if (own is IFixedUpdate fixedUpdate) 94 | { 95 | cell.fixedUpdate = fixedUpdate; 96 | } 97 | 98 | if (own is ILateUpdate lateUpdate) 99 | { 100 | cell.lateUpdate = lateUpdate; 101 | } 102 | 103 | if (own is IOnDisable onDisable) 104 | { 105 | cell.onDisable = onDisable; 106 | } 107 | 108 | if (own is IOnDestroy onDestroy) 109 | { 110 | cell.onDestroy = onDestroy; 111 | } 112 | 113 | _dicCellsReady.Add(own.GetKey(),cell); 114 | 115 | cell.onEnable?.OnEnable(obj); 116 | 117 | return own; 118 | } 119 | /// 120 | /// 注销周期 121 | /// 122 | /// 自身实例 123 | /// 周期类类型 124 | /// 125 | public static bool UnRegister(T own) where T : class,IBind, new() 126 | { 127 | if (own == null) 128 | { 129 | Consoles.Print(nameof(Cycles),$"注销物体为空"); 130 | return false; 131 | } 132 | 133 | string key = own.GetKey(); 134 | 135 | if (_dicCells.ContainsKey(key)) 136 | { 137 | _queueRecycle.Enqueue(key); 138 | return Pools.Put(own); 139 | } 140 | 141 | if (_dicCellsReady.ContainsKey(key)) 142 | { 143 | _queueRecycleReady.Enqueue(key); 144 | return Pools.Put(own);; 145 | } 146 | 147 | return false; 148 | } 149 | /// 150 | /// Unity - Update() 151 | /// 152 | private void Update() 153 | { 154 | Recycle(); 155 | 156 | foreach (var dicCell in _dicCells) 157 | { 158 | dicCell.Value.update?.Update(); 159 | } 160 | } 161 | /// 162 | /// Unity - FixedUpdate() 163 | /// 164 | private void FixedUpdate() 165 | { 166 | Recycle(); 167 | 168 | foreach (var dicCell in _dicCells) 169 | { 170 | dicCell.Value.fixedUpdate?.FixedUpdate(); 171 | } 172 | } 173 | /// 174 | /// Unity - LateUpdate() 175 | /// 176 | private void LateUpdate() 177 | { 178 | Recycle(); 179 | 180 | foreach (var dicCell in _dicCells) 181 | { 182 | dicCell.Value.lateUpdate?.LateUpdate(); 183 | } 184 | 185 | foreach (var pair in _dicCellsReady) 186 | { 187 | _dicCells[pair.Key] = pair.Value; 188 | } 189 | 190 | _dicCellsReady.Clear(); 191 | } 192 | /// 193 | /// 回收函数 194 | /// 195 | private void Recycle() 196 | { 197 | while (_queueRecycle.Count > 0) 198 | { 199 | string key = _queueRecycle.Dequeue(); 200 | _dicCells[key].onDisable?.OnDisable(); 201 | _dicCells.Remove(key); 202 | } 203 | 204 | while (_queueRecycleReady.Count > 0) 205 | { 206 | string key = _queueRecycleReady.Dequeue(); 207 | _dicCellsReady[key].onDisable?.OnDisable(); 208 | _dicCellsReady.Remove(key); 209 | } 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Cycles/IRimeCycle/ICycles.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RimeFramework.Core 4 | { 5 | /// 6 | /// 绑定接口 7 | /// 8 | /// Author: AstoraGray 9 | public interface IBind 10 | { 11 | 12 | } 13 | /// 14 | /// Unity · Awake 15 | /// 16 | public interface IAwake : IBind 17 | { 18 | public void Awake(); 19 | } 20 | /// 21 | /// Unity · OnEnable 22 | /// 23 | public interface IOnEnable : IBind 24 | { 25 | public void OnEnable(GameObject obj); 26 | } 27 | /// 28 | /// Unity · Start 29 | /// 30 | public interface IStart : IBind 31 | { 32 | public void Start(); 33 | } 34 | /// 35 | /// Unity · Update 36 | /// 37 | public interface IUpdate : IBind 38 | { 39 | public void Update(); 40 | } 41 | /// 42 | /// Unity · FixedUpdate 43 | /// 44 | public interface IFixedUpdate : IBind 45 | { 46 | public void FixedUpdate(); 47 | } 48 | /// 49 | /// Unity · LateUpdate 50 | /// 51 | public interface ILateUpdate : IBind 52 | { 53 | public void LateUpdate(); 54 | } 55 | /// 56 | /// Unity · OnDisable 57 | /// 58 | public interface IOnDisable : IBind 59 | { 60 | public void OnDisable(); 61 | } 62 | /// 63 | /// Unity · OnDestroy 64 | /// 65 | public interface IOnDestroy : IBind 66 | { 67 | public void OnDestroy(); 68 | } 69 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Navigations/Navigations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RimeFramework.Tool; 5 | using UnityEngine; 6 | using UnityEngine.UI; 7 | 8 | namespace RimeFramework.Core 9 | { 10 | /// 11 | /// 霜 · 导航组 ➡️ 12 | /// 13 | /// Note: 对游戏中的所有面板进行导航,并且有防循环机制 14 | /// 打开某类型面板,调度导航组 15 | /// 返回上一级面板,若当前组无面板则进入上个导航组 16 | /// 销毁所有的面板 17 | /// Author: AstoraGray 18 | [RequireComponent(typeof(Canvas),typeof(CanvasScaler),typeof(GraphicRaycaster))] 19 | public class Navigations : Singleton 20 | { 21 | private static readonly List _groups = new(); 22 | 23 | private static readonly Dictionary _dicPanels = new(); 24 | 25 | public static string CurGroupName => _groups.LastOrDefault()?.Name; 26 | /// 27 | /// 初始化Canvas 28 | /// 29 | protected override void Awake() 30 | { 31 | base.Awake(); 32 | gameObject.layer = LayerMask.NameToLayer("UI"); 33 | Canvas canvas = GetComponent(); 34 | canvas.renderMode = RenderMode.ScreenSpaceOverlay; 35 | canvas.vertexColorAlwaysGammaSpace = true; 36 | CanvasScaler scale = GetComponent(); 37 | scale.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; 38 | scale.referenceResolution = new Vector2(Screen.width, Screen.height); 39 | } 40 | /// 41 | /// 打开面板 42 | /// 43 | /// 面板类型 44 | /// 45 | public static Panel Open() where T : Panel,new() 46 | { 47 | Type type = typeof(T); 48 | Panel panel; 49 | if (!_dicPanels.ContainsKey(type)) 50 | { 51 | _dicPanels[type] = Pools.Take(); 52 | panel = _dicPanels[type]; 53 | panel.transform.SetParent(Instance.transform); 54 | RectTransform rect = panel.transform as RectTransform; 55 | rect.offsetMax = Vector2.zero; 56 | } 57 | panel = _dicPanels[type]; 58 | PanelGroup group = _groups.LastOrDefault(); 59 | 60 | if (group?.Name == panel.Group) 61 | { 62 | group.Open(panel); 63 | Consoles.Print(nameof(Navigations),$"打开面板{group.DebugPanels()},当前导航组为{DebugGroups()}"); 64 | return panel; 65 | } 66 | 67 | _groups.LastOrDefault()?.Close(); 68 | 69 | for (var i = 0; i < _groups.Count; i++) 70 | { 71 | if (_groups[i].Name == panel.Group) 72 | { 73 | group = _groups[i]; 74 | break; 75 | } 76 | } 77 | 78 | if (group?.Name != panel.Group) 79 | { 80 | group = new PanelGroup(panel.Group); 81 | } 82 | 83 | group.Clear(); 84 | 85 | _groups.Remove(group); 86 | _groups.Add(group); 87 | 88 | group.Open(panel); 89 | Consoles.Print(nameof(Navigations),$"打开面板{group.DebugPanels()},当前导航组为{DebugGroups()}"); 90 | return panel; 91 | } 92 | /// 93 | /// 返回上一级 94 | /// 95 | /// 96 | public static Panel Back() 97 | { 98 | PanelGroup group = _groups.LastOrDefault(); 99 | if (group == null) 100 | { 101 | Consoles.Print(nameof(Navigations),$"当前无导航组,无法返回"); 102 | return null; 103 | } 104 | 105 | bool isBack = group.Back(); 106 | Panel panel = null; 107 | 108 | if (!isBack) 109 | { 110 | _groups.Remove(_groups.LastOrDefault()); 111 | group = _groups.LastOrDefault(); 112 | panel = group?.Open(); 113 | if (panel == null) 114 | { 115 | Consoles.Print(nameof(Navigations),$"当前已无面板"); 116 | return panel; 117 | } 118 | } 119 | 120 | Consoles.Print(nameof(Navigations),$"打开面板{group?.DebugPanels()},当前导航组为{DebugGroups()}"); 121 | 122 | return panel; 123 | } 124 | 125 | public static void Clear() 126 | { 127 | Pools.Clear(); 128 | _groups.Clear(); 129 | _dicPanels.Clear(); 130 | Consoles.Print(nameof(Navigations),$"销毁所有面板"); 131 | } 132 | /// 133 | /// 输出导航组Debug信息 134 | /// 135 | /// 136 | private static string DebugGroups() 137 | { 138 | string groups = ""; 139 | for (var i = _groups.Count - 1; i >= 0; i--) 140 | { 141 | groups += _groups[i].Name; 142 | if (i > 0) 143 | { 144 | groups += "-->"; 145 | } 146 | } 147 | 148 | return groups; 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Navigations/Panels/Panel.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RimeFramework.Core 4 | { 5 | /// 6 | /// 面板 7 | /// 8 | /// Note: 面板属于IPool也就是池对象,配合Pools使用 9 | /// Author: AstoraGray 10 | public abstract class Panel : MonoBehaviour,IPool 11 | { 12 | public abstract string Group { get; } // 所属导航组 13 | /// 14 | /// 显示面板 15 | /// 16 | public virtual void Show() 17 | { 18 | gameObject.transform.SetAsLastSibling(); 19 | gameObject.SetActive(true); 20 | } 21 | /// 22 | /// 隐藏面板 23 | /// 24 | public virtual void Hide() 25 | { 26 | gameObject.SetActive(false); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Navigations/Panels/PanelGroup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace RimeFramework.Core 5 | { 6 | /// 7 | /// 导航组 8 | /// 9 | public class PanelGroup 10 | { 11 | public string Name { get; private set; } // 导航组名 12 | 13 | private readonly List _panels = new (); // 面板列表 14 | /// 15 | /// 初始化导航组 16 | /// 17 | /// 组名 18 | public PanelGroup(string name) => Name = name; 19 | /// 20 | /// 打开当前面板 21 | /// 22 | /// 23 | public Panel Open() 24 | { 25 | return Open(_panels.LastOrDefault()); 26 | } 27 | /// 28 | /// 打开面板 29 | /// 30 | /// 面板实例 31 | /// 32 | public Panel Open(Panel panel) 33 | { 34 | if (panel == _panels.LastOrDefault()) 35 | { 36 | panel?.Show(); 37 | return panel; 38 | } 39 | _panels.LastOrDefault()?.Hide(); 40 | 41 | _panels.Remove(panel); 42 | _panels.Add(panel); 43 | 44 | panel.Show(); 45 | 46 | return panel; 47 | } 48 | /// 49 | /// 返回上一级 50 | /// 51 | /// 52 | public bool Back() 53 | { 54 | Panel panel = _panels.LastOrDefault(); 55 | panel?.Hide(); 56 | _panels.Remove(panel); 57 | if (_panels.Count == 0) 58 | { 59 | return false; 60 | } 61 | _panels.LastOrDefault()?.Show(); 62 | return true; 63 | } 64 | /// 65 | /// 关闭当前面板 66 | /// 67 | public void Close() 68 | { 69 | _panels.LastOrDefault()?.Hide(); 70 | } 71 | /// 72 | /// 清理面板列表(不是销毁) 73 | /// 74 | public void Clear() 75 | { 76 | _panels.Clear(); 77 | } 78 | /// 79 | /// 打印面板Debug信息 80 | /// 81 | /// 82 | public string DebugPanels() 83 | { 84 | string log = ""; 85 | for (var i = 0; i < _panels.Count; i++) 86 | { 87 | log += _panels[i].GetType().Name; 88 | if (i < _panels.Count - 1) 89 | { 90 | log += "-->"; 91 | } 92 | } 93 | 94 | return log; 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Observers/ObsValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RimeFramework.Core 4 | { 5 | /// 6 | /// 监控值 7 | /// 8 | /// 值类型 9 | /// Author: AstoraGray 10 | public struct ObsValue where T : struct 11 | { 12 | public T Value 13 | { 14 | set 15 | { 16 | bool isChanged = !_value.Equals(value); 17 | _value = value; 18 | if (isChanged) 19 | { 20 | OnValueChanged?.Invoke(value); 21 | } 22 | } 23 | get => _value; 24 | } 25 | 26 | public event Action OnValueChanged; 27 | 28 | private T _value; 29 | } 30 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Observers/Observers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RimeFramework.Tool; 4 | 5 | namespace RimeFramework.Core 6 | { 7 | /// 8 | /// 霜 · 观察者 📷 9 | /// 10 | /// Note: 支持有参无参事件的管理 11 | /// Author: AstoraGray 12 | public class Observers : Singleton 13 | { 14 | private static readonly Dictionary> _dicEventObserversParams = new(); // 事件观察者 15 | 16 | private static readonly Dictionary _dicEventObservers = new(); // 事件观察者(无参) 17 | 18 | /// 19 | /// 注册事件 20 | /// 21 | /// 事件名 22 | /// 事件 23 | public static void Register(string name,Action onComplete) 24 | { 25 | if (!_dicEventObserversParams.ContainsKey(name)) 26 | { 27 | _dicEventObserversParams[name] = onComplete; 28 | return; 29 | } 30 | _dicEventObserversParams[name] += onComplete; 31 | } 32 | 33 | /// 34 | /// 注册事件(无参) 35 | /// 36 | /// 事件名 37 | /// 事件 38 | public static void Register(string name,Action onComplete) 39 | { 40 | if (!_dicEventObservers.ContainsKey(name)) 41 | { 42 | _dicEventObservers[name] = onComplete; 43 | return; 44 | } 45 | _dicEventObservers[name] += onComplete; 46 | } 47 | 48 | /// 49 | /// 注销事件 50 | /// 51 | /// 52 | /// 53 | public static void UnRegister(string name, Action onComplete) 54 | { 55 | if (!_dicEventObserversParams.ContainsKey(name)) 56 | { 57 | Consoles.Print(nameof(Observers),$"事件{name}不存在,因为它没有被注册"); 58 | return; 59 | } 60 | 61 | _dicEventObserversParams[name] -= onComplete; 62 | } 63 | 64 | /// 65 | /// 注销事件(无参) 66 | /// 67 | /// 68 | /// 69 | public static void UnRegister(string name, Action onComplete) 70 | { 71 | if (!_dicEventObservers.ContainsKey(name)) 72 | { 73 | Consoles.Print(nameof(Observers),$"事件{name}不存在,因为它没有被注册"); 74 | return; 75 | } 76 | 77 | _dicEventObservers[name] -= onComplete; 78 | } 79 | 80 | /// 81 | /// 广播事件 82 | /// 83 | /// 84 | /// 85 | public static void Dispatch(string name,params object[] parameters) 86 | { 87 | if (_dicEventObserversParams.TryGetValue(name, out Action onComplete1)) 88 | { 89 | onComplete1?.Invoke(parameters); 90 | } 91 | if (_dicEventObservers.TryGetValue(name, out Action onComplete2)) 92 | { 93 | onComplete2?.Invoke(); 94 | } 95 | } 96 | 97 | /// 98 | /// 清空 99 | /// 100 | public static void Clear() 101 | { 102 | _dicEventObserversParams.Clear(); 103 | _dicEventObservers.Clear(); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Pools/IRimePool/IPool.cs: -------------------------------------------------------------------------------- 1 | namespace RimeFramework.Core 2 | { 3 | /// 4 | /// Resource接口 5 | /// 6 | /// Author: AstoraGray 7 | interface IPool 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Pools/Pools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RimeFramework.Tool; 5 | using RimeFramework.Utility; 6 | using UnityEngine; 7 | 8 | namespace RimeFramework.Core 9 | { 10 | /// 11 | /// 霜 · 池 💧 12 | /// 13 | /// Note: 存取所有堆类型数据,包括Component派生类、以及提线木偶、纯GameObject 14 | /// 索取类型的资源 15 | /// 索取某一名称的Obj 16 | /// 归放类型的资源 17 | /// 归放某一名称的Obj 18 | /// 销毁名称的资源,支持里氏替换 19 | /// 销毁某一名称的资源 20 | /// Author: AstoraGray 21 | public class Pools : Singleton 22 | { 23 | private static readonly Dictionary> _dicDrips = new (); // Type水滴 24 | 25 | private static readonly Dictionary> _dicOuterDrips = new(); // Type外界水滴 26 | 27 | private static readonly Dictionary _dicWells = new(); // Type井 28 | 29 | private static readonly Dictionary _dicWares = new(); // Type仓库 30 | 31 | private static readonly Dictionary> _dicObjDrips = new(); // Name水滴 32 | 33 | private static readonly Dictionary> _dicObjOuterDrips = new(); // Name外界水滴 34 | 35 | private static readonly Dictionary _dicObjWells = new(); // Name井 36 | 37 | private static readonly Dictionary _dicObjWares = new(); // Name仓库 38 | 39 | private static readonly Queue _queueDestroy = new(); // 清理队列 40 | 41 | private static GameObject _objWell; // Type井根结点 42 | 43 | private static GameObject _objObjWell; // Name井根结点 44 | 45 | private const string WELL = "Well"; // Type井名称 46 | 47 | private const string OBJ_WELL = "ObjWell"; // Name井名称 48 | 49 | private const string RIME = "|RIME|"; // |烙印| 50 | 51 | /// 52 | /// 索要 - TYPE 53 | /// 54 | /// 水滴类型 55 | /// 水滴实例 56 | public static T Take() where T : class,new () 57 | { 58 | Type type = typeof(T); 59 | if (!_dicDrips.ContainsKey(type)) 60 | { 61 | _dicDrips[type] = new Queue(); 62 | } 63 | 64 | Queue queue = _dicDrips[type]; 65 | 66 | if (queue.Count == 0) 67 | { 68 | return TakeComponent(type); 69 | } 70 | 71 | return TakeComponent((T)queue.Dequeue(),type); 72 | } 73 | /// 74 | /// 索要 - NAME 75 | /// 76 | /// 水滴名称 77 | /// 水滴实例 - 特化GameObject 78 | public static GameObject Take(string name) 79 | { 80 | if (!_dicObjDrips.ContainsKey(name)) 81 | { 82 | _dicObjDrips[name] = new Queue(); 83 | } 84 | 85 | Queue queue = _dicObjDrips[name]; 86 | 87 | if (queue.Count == 0) 88 | { 89 | return TakeObj(name); 90 | } 91 | 92 | return TakeObj(name,queue.Dequeue()); 93 | } 94 | 95 | /// 96 | /// 归放 - TYPE 97 | /// 98 | /// 水滴实例 99 | /// 水滴类型 100 | public static bool Put(T drip) where T : class 101 | { 102 | Type type = typeof(T); 103 | if (!_dicDrips.ContainsKey(type)) 104 | { 105 | Consoles.Print(nameof(Pools),$"归放过程未发现池 {type}"); 106 | return false; 107 | } 108 | 109 | Queue queue = _dicDrips[type]; 110 | 111 | _dicOuterDrips[type].Remove(drip); 112 | queue.Enqueue(drip); 113 | 114 | return PutComponent(drip,type); 115 | } 116 | /// 117 | /// 归放 - GAMEOBJECT 118 | /// 119 | /// 水滴实例 - 特化GameObject 120 | /// 121 | public static bool Put(GameObject obj) 122 | { 123 | string name = obj.name.Extract(RIME); 124 | if (!_dicObjDrips.ContainsKey(name)) 125 | { 126 | Consoles.Print(nameof(Pools),$"归放过程未发现池 {name}"); 127 | return false; 128 | } 129 | 130 | Queue queue = _dicObjDrips[name]; 131 | 132 | _dicObjOuterDrips[name].Remove(obj); 133 | queue.Enqueue(obj); 134 | 135 | return PutObj(obj,name); 136 | } 137 | 138 | /// 139 | /// 清洗 - TYPE 140 | /// 141 | /// 水滴类型 142 | /// 是否成功 143 | public static bool Clear() where T : class 144 | { 145 | Type type = typeof(T); 146 | int clearCount = 0; 147 | foreach (var key in _dicDrips.Keys.ToList()) 148 | { 149 | if (key == typeof(T) || key.IsSubclassOf(typeof(T))) 150 | { 151 | ClearComponent(key); 152 | clearCount++; 153 | } 154 | } 155 | 156 | if (clearCount > 0) 157 | { 158 | return true; 159 | } 160 | Consoles.Print(nameof(Pools),$"清洗过程在{WELL}未发现池 {type}"); 161 | return false; 162 | } 163 | 164 | /// 165 | /// 清洗 - NAME 166 | /// 167 | /// 水滴名字 - 特化GameObject 168 | /// 169 | public static bool Clear(string name) 170 | { 171 | if (!_dicObjDrips.ContainsKey(name)) 172 | { 173 | Consoles.Print(nameof(Pools),$"清洗过程在{OBJ_WELL}中未发现池 {name}"); 174 | return false; 175 | } 176 | 177 | return ClearObj(name); 178 | } 179 | /// 180 | /// 索要Component 181 | /// 182 | /// 水滴类型 183 | /// 184 | /// 185 | private static T TakeComponent(Type type) where T : class,new () 186 | { 187 | if (!type.IsSubclassOf(typeof(Component))) 188 | { 189 | T t = new T(); 190 | (t as IAwake)?.Awake(); 191 | (t as IStart)?.Start(); 192 | return TakeComponent(new T(), type); 193 | } 194 | 195 | bool inWares = _dicWares.ContainsKey(type); 196 | bool haveIPools = type.GetInterface(typeof(IPool).ToString()) != null; 197 | 198 | if (!inWares && !haveIPools) 199 | { 200 | GameObject obj1 = new GameObject(type.Name); 201 | T Component1 = obj1.AddComponent(type) as T; 202 | return TakeComponent(Component1,type); 203 | } 204 | 205 | if (!inWares && haveIPools) 206 | { 207 | _dicWares[type] = Resources.Load($"Prefabs/{WELL}/{type.Name}"); 208 | if (_dicWares[type] == null) 209 | { 210 | Consoles.Print(nameof(Pools),$"Prefabs/{WELL}中未发现预制体{type.Name}"); 211 | return null; 212 | } 213 | } 214 | 215 | GameObject obj2 = Instantiate(_dicWares[type]); 216 | T Component2 = obj2.GetComponent(type) as T ?? obj2.AddComponent(type) as T; 217 | 218 | return TakeComponent(Component2,type); 219 | } 220 | /// 221 | /// 索要Component 222 | /// 223 | /// 水滴实例 224 | /// 水滴类型 225 | /// 226 | /// 227 | private static T TakeComponent(T drip,Type type) where T : class,new () 228 | { 229 | if (!_dicOuterDrips.ContainsKey(type)) 230 | { 231 | _dicOuterDrips[type] = new HashSet(); 232 | } 233 | 234 | HashSet hashSet = _dicOuterDrips[type]; 235 | hashSet.Add(drip); 236 | 237 | Component Component = drip as Component; 238 | if (Component == null) 239 | { 240 | return drip; 241 | } 242 | 243 | if (_objWell == null) 244 | { 245 | _objWell = new GameObject(WELL); 246 | _objWell.transform.SetParent(Instance.transform); 247 | } 248 | 249 | if (!_dicWells.ContainsKey(type)) 250 | { 251 | _dicWells[type] = new GameObject(type.ToString()); 252 | _dicWells[type].transform.SetParent(_objWell.transform); 253 | } 254 | 255 | Component.gameObject.name = Component.GetKey(); 256 | Component.transform.SetParent(_dicWells[type].transform); 257 | Component.gameObject.SetActive(true); 258 | return drip; 259 | } 260 | /// 261 | /// 索要Obj 262 | /// 263 | /// Obj名字 264 | /// 265 | private static GameObject TakeObj(string name) 266 | { 267 | _dicObjWares[name] = Resources.Load($"Prefabs/{OBJ_WELL}/{name}"); 268 | if (_dicObjWares[name] == null) 269 | { 270 | Consoles.Print(nameof(Pools),$"Prefabs/{OBJ_WELL}中未发现预制体{name}"); 271 | return null; 272 | } 273 | GameObject obj = Instantiate(_dicObjWares[name]); 274 | return TakeObj(name,obj); 275 | } 276 | /// 277 | /// 索要Obj 278 | /// 279 | /// Obj名字 280 | /// Obj实例 281 | /// 282 | private static GameObject TakeObj(string name,GameObject obj) 283 | { 284 | if (!_dicObjOuterDrips.ContainsKey(name)) 285 | { 286 | _dicObjOuterDrips[name] = new HashSet(); 287 | } 288 | 289 | HashSet hashSet = _dicObjOuterDrips[name]; 290 | hashSet.Add(obj); 291 | 292 | if (_objObjWell == null) 293 | { 294 | _objObjWell = new GameObject(OBJ_WELL); 295 | _objObjWell.transform.SetParent(Instance.transform); 296 | } 297 | 298 | if (!_dicObjWells.ContainsKey(name)) 299 | { 300 | _dicObjWells[name] = new GameObject(name); 301 | _dicObjWells[name].transform.SetParent(_objObjWell.transform); 302 | } 303 | 304 | obj.name = $"{obj.GetKey()}{RIME}{name}"; 305 | obj.transform.SetParent(_dicObjWells[name].transform); 306 | obj.SetActive(true); 307 | return obj; 308 | } 309 | 310 | /// 311 | /// 归放Component 312 | /// 313 | /// 水滴实例 314 | /// 水滴类型 315 | /// 316 | /// 317 | private static bool PutComponent(T drip,Type type) where T : class 318 | { 319 | Component component = drip as Component; 320 | if (component == null) 321 | { 322 | return true; 323 | } 324 | 325 | if (component.transform.parent != _dicWells[type].transform) 326 | { 327 | component.transform.SetParent(_dicWells[type].transform); 328 | } 329 | 330 | component.gameObject.SetActive(false); 331 | 332 | return true; 333 | } 334 | 335 | /// 336 | /// 归放Obj 337 | /// 338 | /// Obj实例 339 | /// Obj名字 340 | /// 341 | private static bool PutObj(GameObject obj, string name) 342 | { 343 | if (obj.transform.parent != _dicObjWells[name].transform) 344 | { 345 | obj.transform.SetParent(_dicObjWells[name].transform); 346 | } 347 | 348 | obj.gameObject.SetActive(false); 349 | 350 | return true; 351 | } 352 | 353 | /// 354 | /// 清洗Component - TYPE 355 | /// 356 | /// 水滴类型 357 | /// 清洗 358 | /// 359 | private static bool ClearComponent(Type type) where T : class 360 | { 361 | Consoles.Print(nameof(Pools),$"清洗类型 {type}"); 362 | Queue queueDrips = _dicDrips[type]; 363 | HashSet hashsetOuterDrips = _dicOuterDrips[type]; 364 | 365 | foreach (var outerDrip in hashsetOuterDrips.ToList()) 366 | { 367 | PutComponent(outerDrip,type); 368 | } 369 | 370 | if (type.GetInterface(typeof(IOnDestroy).ToString()) != null) 371 | { 372 | while (queueDrips.Count > 0) 373 | { 374 | (queueDrips.Dequeue() as IOnDestroy)?.OnDestroy(); 375 | } 376 | } 377 | 378 | _dicOuterDrips.Remove(type); 379 | _dicDrips.Remove(type); 380 | 381 | if (!_dicWells.ContainsKey(type)) 382 | { 383 | return true; 384 | } 385 | _queueDestroy.Enqueue(null); 386 | _queueDestroy.Enqueue(_dicWells[type]); 387 | _dicWells.Remove(type); 388 | return true; 389 | } 390 | 391 | /// 392 | /// 清洗Obj - NAME 393 | /// 394 | /// 水滴名字 - 特化GameObject 395 | /// 396 | private static bool ClearObj(string name) 397 | { 398 | Consoles.Print(nameof(Pools),$"清洗GameObject {name}"); 399 | HashSet hashsetOuterDrips = _dicObjOuterDrips[name]; 400 | 401 | foreach (var outerObjDrip in hashsetOuterDrips.ToList()) 402 | { 403 | Put(outerObjDrip); 404 | } 405 | 406 | _queueDestroy.Enqueue(null); 407 | _queueDestroy.Enqueue(_dicObjWells[name]); 408 | _dicObjWells.Remove(name); 409 | return true; 410 | } 411 | /// 412 | /// 延迟清洗水滴 413 | /// 414 | private void LateUpdate() 415 | { 416 | if (_queueDestroy.Count > 0) 417 | { 418 | GameObject obj = _queueDestroy.Dequeue(); 419 | Destroy(obj); 420 | } 421 | } 422 | } 423 | } -------------------------------------------------------------------------------- /RimeFramework/Core/RimeManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RimeFramework.Tool; 3 | 4 | namespace RimeFramework.Core 5 | { 6 | /// 7 | /// 最上层,游戏管理器 8 | /// 9 | /// Author: AstoraGray 10 | public class RimeManager : Singleton 11 | { 12 | public bool consoles = true; 13 | public bool controls = true; 14 | public bool states = true; 15 | public bool cycles = true; 16 | public bool pools = true; 17 | public bool navigations = true; 18 | public bool scenes = true; 19 | public bool animators = true; 20 | public bool audios = true; 21 | public bool observers = true; 22 | 23 | protected override void Awake() 24 | { 25 | base.Awake(); 26 | Init(); 27 | } 28 | 29 | private void Init() 30 | { 31 | Consoles.Print(nameof(RimeManager),$"{Environment.UserName}, 欢迎回来!"); 32 | if(consoles) Consoles.Instance.transform.SetParent(transform); 33 | if(controls) Controls.Instance.transform.SetParent(transform); 34 | if(states) States.Instance.transform.SetParent(transform); 35 | if(cycles) Cycles.Instance.transform.SetParent(transform); 36 | if(pools) Pools.Instance.transform.SetParent(transform); 37 | if(navigations) Navigations.Instance.transform.SetParent(transform); 38 | if(scenes) Scenes.Instance.transform.SetParent(transform); 39 | if(animators) Animators.Instance.transform.SetParent(transform); 40 | if(audios) Audios.Instance.transform.SetParent(transform); 41 | if(observers) Observers.Instance.transform.SetParent(transform); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /RimeFramework/Core/Scenes/Scenes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using RimeFramework.Tool; 6 | using UnityEngine; 7 | using UnityEngine.SceneManagement; 8 | 9 | namespace RimeFramework.Core 10 | { 11 | /// 12 | /// 霜 · 布景员 🎬 13 | /// 14 | /// Note: 负责异步加载和卸载场景,支持完成回调、取消回调 15 | /// 加载场景 16 | /// 卸载场景 17 | /// Author: AstoraGray 18 | public class Scenes : Singleton 19 | { 20 | private static readonly List _scenes = new (); // 已加载场景 21 | 22 | private static readonly Dictionary _dicCoroutines = new (); // 进行中协程 23 | 24 | protected override void Awake() 25 | { 26 | base.Awake(); 27 | _scenes.Add(SceneManager.GetActiveScene().name); 28 | } 29 | 30 | /// 31 | /// 加载场景 32 | /// 33 | /// 场景名 34 | /// 加载模式 35 | /// 更新回调 36 | /// 完成回调 37 | public static void Load(string name,LoadSceneMode mode,Action onUpdate = null,Action onComplete = null) 38 | { 39 | if (_scenes.Contains(name)) 40 | { 41 | Consoles.Print(nameof(Scenes),$"场景列表中已包含场景 {name},不可再次读取"); 42 | return; 43 | } 44 | if (mode == LoadSceneMode.Single) 45 | { 46 | _scenes.Clear(); 47 | } 48 | Consoles.Print(nameof(Scenes),$"正在加载场景 {name}"); 49 | _scenes.Add(name); 50 | 51 | _dicCoroutines[name] = Instance.StartCoroutine(Loading(name,mode,onUpdate,onComplete)); 52 | } 53 | /// 54 | /// 卸载场景 55 | /// 56 | /// 场景名 57 | /// 更新回调 58 | /// 完成回调 59 | public static void Unload(string name = null,Action onUpdate = null,Action onComplete = null) 60 | { 61 | if (_scenes.Count == 1) 62 | { 63 | Consoles.Print(nameof(Scenes), "唯一场景,不可卸载"); 64 | return; 65 | } 66 | if (name == null) 67 | { 68 | name = _scenes.Last(); 69 | } 70 | if (_scenes.Remove(name)) 71 | { 72 | Consoles.Print(nameof(Scenes),$"正在卸载场景{name}"); 73 | } 74 | else 75 | { 76 | Consoles.Print(nameof(Scenes), $"场景列表中不包含场景 {name},不可卸载"); 77 | return; 78 | } 79 | _dicCoroutines[name] = Instance.StartCoroutine(Unloading(name, onUpdate, onComplete)); 80 | } 81 | /// 82 | /// 加载协程 83 | /// 84 | /// 场景名 85 | /// 加载模式 86 | /// 更新回调 87 | /// 完成回调 88 | /// 89 | private static IEnumerator Loading(string name,LoadSceneMode mode,Action onUpdate,Action onComplete) 90 | { 91 | yield return null; 92 | Coroutine coroutine = _dicCoroutines[name]; 93 | AsyncOperation scene = SceneManager.LoadSceneAsync(name, mode); 94 | 95 | while (!scene.isDone) 96 | { 97 | if (_dicCoroutines[name] != coroutine) 98 | { 99 | Consoles.Print(nameof(Scenes),$"加载场景 {name} 被取消"); 100 | yield break; 101 | } 102 | onUpdate?.Invoke(scene.progress); 103 | yield return null; 104 | } 105 | 106 | if (_dicCoroutines[name] == coroutine) 107 | { 108 | _dicCoroutines[name] = null; 109 | } 110 | Consoles.Print(nameof(Scenes),$"成功加载场景 {name}"); 111 | onComplete?.Invoke(); 112 | } 113 | /// 114 | /// 卸载协程 115 | /// 116 | /// 场景名 117 | /// 更新回调 118 | /// 完成回调 119 | /// 120 | private static IEnumerator Unloading(string name,Action onUpdate,Action onComplete) 121 | { 122 | yield return null; 123 | Coroutine coroutine = _dicCoroutines[name]; 124 | AsyncOperation scene = SceneManager.UnloadSceneAsync(name); 125 | 126 | while (!scene.isDone) 127 | { 128 | if (_dicCoroutines[name] != coroutine) 129 | { 130 | Consoles.Print(nameof(Scenes),$"卸载场景 {name} 被取消"); 131 | yield break; 132 | } 133 | onUpdate?.Invoke(scene.progress); 134 | yield return null; 135 | } 136 | 137 | if (_dicCoroutines[name] == coroutine) 138 | { 139 | _dicCoroutines[name] = null; 140 | } 141 | Consoles.Print(nameof(Scenes),$"成功卸载场景 {name}"); 142 | onComplete?.Invoke(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /RimeFramework/Core/States/State/State.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RimeFramework.Core 5 | { 6 | /// 7 | /// 霜 · 状态类 8 | /// WakeUp(): 请将初始化状态要做的事写入这个方法 9 | /// On(): 将进入状态,或者说激活状态要做的事写入这个方法 10 | /// Off(): 将离开状态,或者说取消激活状态要做的事写入这个方法 11 | /// 生命周期:WakeUp->On->Update->Off 12 | /// 建议使用直接访问的方式监听连续输入,eg: InputDir 13 | /// 建议使用Action来监听离散输入,eg: Jump 14 | /// 15 | public abstract class State : MonoBehaviour 16 | { 17 | public MonoBehaviour own; 18 | 19 | /// 20 | /// 首次唤醒,代替Awake 21 | /// 22 | public virtual void WakeUp(){} 23 | 24 | /// 25 | /// 进入此状态,代替OnEnable 26 | /// 27 | public virtual void On(){} 28 | 29 | /// 30 | /// 离开此状态,代替OnDisAble 31 | /// 32 | public virtual void Off(){} 33 | 34 | /// 35 | /// 进入状态 36 | /// 37 | /// 是否离开本状态 38 | /// 是否激活目标状态 39 | /// 状态类型 40 | /// 41 | protected virtual T Enter(bool exit = true,bool enabled = true) where T : State 42 | { 43 | if (exit) 44 | { 45 | Exit(); 46 | } 47 | return States.Register(own, enabled); 48 | } 49 | /// 50 | /// 离开状态 51 | /// 52 | protected virtual void Exit() 53 | { 54 | enabled = false; 55 | } 56 | /// 57 | /// 隐藏Awake 58 | /// 59 | private void Awake(){} 60 | 61 | /// 62 | /// 隐藏Start 63 | /// 64 | private void Start(){} 65 | 66 | /// 67 | /// 隐藏OnEnable 68 | /// 69 | private void OnEnable() { } 70 | 71 | /// 72 | /// 取消激活 73 | /// 74 | private void OnDisable() 75 | { 76 | Off(); 77 | } 78 | /// 79 | /// 销毁时取消注册 80 | /// 81 | protected virtual void OnDestroy() 82 | { 83 | States.UnRegister(this); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /RimeFramework/Core/States/States.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RimeFramework.Tool; 4 | using UnityEngine; 5 | 6 | namespace RimeFramework.Core 7 | { 8 | /// 9 | /// 霜 · 状态机 🗡️ 10 | /// Note: 为什么使用静态方法,但又是单例? 11 | /// 这样可以在融入Unity生命周期的同时,保持一些配置不受生命周期的影响,比如后续可以给States设置一些Awake()初始化方法 12 | /// 注册状态 13 | /// 14 | /// Author: AstoraGray 15 | public class States : Singleton 16 | { 17 | private static Dictionary> _dicStates = new (); // 字典 18 | 19 | private const string STATES_NAME = "States"; // 状态节点名字 20 | 21 | public Dictionary this[MonoBehaviour monoBehaviour] 22 | { 23 | get 24 | { 25 | if (_dicStates.TryGetValue(monoBehaviour, out var states)) 26 | { 27 | return states; 28 | } 29 | Consoles.Print(nameof(States),"状态不存在"); 30 | return null; // 或者抛出异常,根据需求 31 | } 32 | } 33 | 34 | /// 35 | /// 注册状态行为 36 | /// 37 | /// 持有者 38 | /// 激活状态 39 | /// 类型 40 | /// 41 | public static T Register(MonoBehaviour own,bool enabled = true) where T : State 42 | { 43 | Transform actions = null; 44 | // 查找是否存在key 45 | if (!_dicStates.ContainsKey(own)) 46 | { 47 | // 查找是否已有节点 48 | if (!own.transform.Find(STATES_NAME)) 49 | { 50 | actions = new GameObject(STATES_NAME).transform; 51 | actions.SetParent(own.transform); 52 | } 53 | _dicStates.Add(own,new Dictionary()); 54 | } 55 | State newState = null; 56 | bool isWake = false; 57 | if (!_dicStates[own].ContainsKey(typeof(T))) 58 | { 59 | actions ??= own.transform.Find(STATES_NAME); 60 | newState = actions.GetComponent() ?? actions.gameObject.AddComponent(); 61 | _dicStates[own].Add(typeof(T),newState); 62 | isWake = true; 63 | } 64 | newState ??= _dicStates[own][typeof(T)]; 65 | newState.enabled = enabled; 66 | newState.own = own; 67 | if (isWake) 68 | { 69 | newState.WakeUp(); 70 | } 71 | newState.On(); 72 | return newState as T; 73 | } 74 | 75 | /// 76 | /// 注销状态行为 77 | /// 78 | /// 状态行为 79 | public static void UnRegister(State state) 80 | { 81 | if (state == null) 82 | { 83 | Consoles.Print(nameof(States),"注销目标不存在"); 84 | return; 85 | } 86 | _dicStates[state.own].Remove(state.GetType()); 87 | if (_dicStates[state.own].Count <= 0) 88 | { 89 | _dicStates.Remove(state.own); 90 | } 91 | Destroy(state); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /RimeFramework/Tool/Singleton.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RimeFramework.Tool 4 | { 5 | /// 6 | /// 单例类 7 | /// 8 | /// 类型 9 | /// Author: AstoraGray 10 | public abstract class Singleton : MonoBehaviour where T : Singleton 11 | { 12 | private static T _instance; 13 | 14 | protected virtual void Awake() 15 | { 16 | if (_instance != null && this != null) 17 | { 18 | Destroy(this); 19 | return; 20 | } 21 | 22 | _instance = this as T; 23 | DontDestroyOnLoad(_instance); 24 | } 25 | 26 | public static T Instance 27 | { 28 | get 29 | { 30 | if (_instance == null) 31 | { 32 | _instance = FindObjectOfType(); 33 | if (_instance == null) 34 | { 35 | _instance = new GameObject(typeof(T).Name).AddComponent(); 36 | } 37 | } 38 | return _instance; 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /RimeFramework/Utility/ColorUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RimeFramework.Utility 4 | { 5 | public static class ColorUtility 6 | { 7 | public static string ToHex(this Color color) 8 | { 9 | 10 | int r = Mathf.RoundToInt(color.r * 255); 11 | int g = Mathf.RoundToInt(color.g * 255); 12 | int b = Mathf.RoundToInt(color.b * 255); 13 | int a = Mathf.RoundToInt(color.a * 255); 14 | 15 | return $"#{r:X2}{g:X2}{b:X2}{a:X2}"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /RimeFramework/Utility/NumberUtility.cs: -------------------------------------------------------------------------------- 1 | namespace RimeFramework.Utility 2 | { 3 | public static class NumberUtility 4 | { 5 | public static int GetSign(this float y) 6 | { 7 | if (y > 0) 8 | { 9 | return 1; 10 | } 11 | if (y < 0) 12 | { 13 | return -1; 14 | } 15 | return 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /RimeFramework/Utility/ObjectUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RimeFramework.Utility 4 | { 5 | public static class ObjectUtility 6 | { 7 | public static string GetKey(this Object obj) 8 | { 9 | return $"{obj.GetType().Name}-{obj.GetHashCode()}"; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /RimeFramework/Utility/StringUtility.cs: -------------------------------------------------------------------------------- 1 | namespace RimeFramework.Utility 2 | { 3 | public static class StringUtility 4 | { 5 | public static string Extract(this string str,string strExt) 6 | { 7 | int index = str.IndexOf(strExt); 8 | 9 | if (index >= 0) 10 | { 11 | return str.Substring(index + strExt.Length); 12 | } 13 | else 14 | { 15 | return "未找到指定标记"; 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /RimeFramework/Utility/VectorUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RimeFramework.Utility 4 | { 5 | public static class VectorUtility 6 | { 7 | public static Vector2 SetX(this Vector2 v,float x) 8 | { 9 | return new Vector2(x,v.y); 10 | } 11 | 12 | public static Vector2 SetY(this Vector2 v,float y) 13 | { 14 | return new Vector2(v.x,y); 15 | } 16 | 17 | public static Vector3 SetX(this Vector3 v,float x) 18 | { 19 | return new Vector3(x,v.y,v.z); 20 | } 21 | 22 | public static Vector3 SetY(this Vector3 v,float y) 23 | { 24 | return new Vector3(v.x,y,v.z); 25 | } 26 | 27 | public static Vector3 SetZ(this Vector3 v,float z) 28 | { 29 | return new Vector3(v.x,v.y,z); 30 | } 31 | } 32 | } --------------------------------------------------------------------------------