diff --git a/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs b/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs new file mode 100644 index 00000000..4ccf952f --- /dev/null +++ b/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs @@ -0,0 +1,19 @@ +using NUnit.Framework; +using XGame.ActorBehavior; + +public sealed class ActorBehaviorAnimatorControllerPathTests +{ + [Test] + public void GetControllerPath_Fighter_ReturnsGeneratedFighterController() + { + Assert.That( + ActorBehaviorAnimatorControllerPaths.GetControllerPath("fighter"), + Is.EqualTo("Assets/Game/Art/Actor/Controller/fighter.controller")); + } + + [Test] + public void GetControllerPath_UnknownType_ReturnsNull() + { + Assert.That(ActorBehaviorAnimatorControllerPaths.GetControllerPath("unknown"), Is.Null); + } +} diff --git a/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs.meta b/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs.meta new file mode 100644 index 00000000..e045d900 --- /dev/null +++ b/Client/Assets/Script/Tests/EditMode/ActorBehaviorAnimatorControllerPathTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b0d0c33caa94dbc94a83cf2c7e1cba0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs b/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs new file mode 100644 index 00000000..12e96695 --- /dev/null +++ b/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using UnityEngine; +using UObject = UnityEngine.Object; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace XGame.ActorBehavior +{ + public static class ActorBehaviorAnimatorControllerPaths + { + public static string GetControllerPath(string catalogType) + { + return string.Equals(catalogType, LobbyActorBehaviorRuntime.DefaultCatalogType, StringComparison.OrdinalIgnoreCase) + ? "Assets/Game/Art/Actor/Controller/fighter.controller" + : null; + } + } + + public static class ActorBehaviorAnimatorControllerLoader + { + public static IEnumerator ApplyController(string catalogType, Animator animator, Action onApplied = null) + { + if (animator == null) + { + onApplied?.Invoke(false); + yield break; + } + + string controllerPath = ActorBehaviorAnimatorControllerPaths.GetControllerPath(catalogType); + if (string.IsNullOrEmpty(controllerPath)) + { + onApplied?.Invoke(false); + yield break; + } + + RuntimeAnimatorController controller = LoadControllerInEditor(controllerPath); + if (controller == null) + { + UObject loaded = null; + yield return XGame.XResLoader.coLoadRes(controllerPath, typeof(RuntimeAnimatorController), obj => loaded = obj); + controller = loaded as RuntimeAnimatorController; + } + + if (controller == null) + { + Debug.LogWarning("[ActorBehaviorAnimatorControllerLoader] controller load failed: " + controllerPath); + onApplied?.Invoke(false); + yield break; + } + + if (animator.runtimeAnimatorController != controller) + { + animator.runtimeAnimatorController = controller; + animator.Rebind(); + animator.Update(0f); + } + onApplied?.Invoke(true); + } + + private static RuntimeAnimatorController LoadControllerInEditor(string controllerPath) + { +#if UNITY_EDITOR + return AssetDatabase.LoadAssetAtPath(controllerPath); +#else + return null; +#endif + } + } +} diff --git a/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs.meta b/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs.meta new file mode 100644 index 00000000..4b3e10e9 --- /dev/null +++ b/Client/Assets/Script/xmain/ActorBehavior/ActorBehaviorAnimatorControllerLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a9d2d021b3b4de0a1e9437e62a08f6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Script/xmain/Client/LobbyWorldController.cs b/Client/Assets/Script/xmain/Client/LobbyWorldController.cs index 61a84491..a2f99c2e 100644 --- a/Client/Assets/Script/xmain/Client/LobbyWorldController.cs +++ b/Client/Assets/Script/xmain/Client/LobbyWorldController.cs @@ -56,6 +56,10 @@ namespace XGame private const float CameraMouseZoomSensitivity = 1.4f; private const float CameraPinchZoomSensitivity = 0.015f; private const float CameraFollowSharpness = 12f; + private const string FighterStandState = "140_stand01_qt"; + private const string FighterWalkState = "140_walk01_qt"; + private const string LegacyStandState = "Stand"; + private const string LegacyWalkState = "Walk"; private readonly Dictionary avatars = new Dictionary(); private readonly Dictionary prefabCache = new Dictionary(); @@ -805,7 +809,11 @@ namespace XGame { return; } - string state = moving ? "Walk" : "Stand"; + string state = ResolveMoveState(avatar.Animator, moving); + if (string.IsNullOrEmpty(state)) + { + return; + } if (avatar.AnimState == state) { return; @@ -814,6 +822,25 @@ namespace XGame avatar.Animator.CrossFade(state, 0.1f); } + private static string ResolveMoveState(Animator animator, bool moving) + { + string fighterState = moving ? FighterWalkState : FighterStandState; + if (HasAnimatorState(animator, fighterState)) + { + return fighterState; + } + + string legacyState = moving ? LegacyWalkState : LegacyStandState; + return HasAnimatorState(animator, legacyState) ? legacyState : null; + } + + private static bool HasAnimatorState(Animator animator, string state) + { + return animator != null + && animator.runtimeAnimatorController != null + && animator.HasState(0, Animator.StringToHash(state)); + } + private void UpdateJoystick() { joystick = Vector2.zero; @@ -994,6 +1021,43 @@ namespace XGame actorBehaviorRuntime.RegisterOrUpdateActor(avatar.PlayerId, avatar.Obj.transform.position, avatar.Obj.transform.forward); actorBehaviorRuntime.SwitchActorCatalog(avatar.PlayerId, LobbyActorBehaviorRuntime.DefaultCatalogType); actorBehaviorPresentation.RegisterActor(avatar.PlayerId, avatar.Animator, avatar.Obj.transform, avatar.Obj.transform); + ApplyActorBehaviorAnimatorController(avatar); + } + + private void ApplyActorBehaviorAnimatorController(Avatar avatar) + { + if (avatar == null || avatar.Obj == null || avatar.Animator == null) + { + return; + } + + StartCoroutine(ApplyActorBehaviorAnimatorControllerAsync( + avatar.PlayerId, + avatar.Obj, + avatar.Animator, + LobbyActorBehaviorRuntime.DefaultCatalogType)); + } + + private IEnumerator ApplyActorBehaviorAnimatorControllerAsync( + int playerId, + GameObject actorObject, + Animator animator, + string catalogType) + { + bool applied = false; + yield return ActorBehaviorAnimatorControllerLoader.ApplyController(catalogType, animator, value => applied = value); + if (!applied || actorObject == null || animator == null) + { + yield break; + } + + if (!avatars.TryGetValue(playerId, out Avatar avatar) || avatar.Obj != actorObject || avatar.Animator != animator) + { + yield break; + } + + avatar.AnimState = null; + SetMoveState(avatar, avatar.Moving); } private void UpdateActorBehaviorActor(Avatar avatar)