diff --git a/Assets/01_Scenes/GameScene.unity b/Assets/01_Scenes/GameScene.unity index 307b1af..7d87ca9 100644 --- a/Assets/01_Scenes/GameScene.unity +++ b/Assets/01_Scenes/GameScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:167544ed5081cd415d112953f10ab5d47070c5a5c25b808ad4ca030e5e155e2b -size 51559 +oid sha256:c0753f43382b6471b985b9460794faa29aed9ac8aad2560fcab7b9bde8d08fd6 +size 52624 diff --git a/Assets/02_Scripts/Combat/ActionData.cs b/Assets/02_Scripts/Combat/ActionData.cs new file mode 100644 index 0000000..8b8cf7d --- /dev/null +++ b/Assets/02_Scripts/Combat/ActionData.cs @@ -0,0 +1,42 @@ +using UnityEngine; +using UnityEngine.Serialization; + +[CreateAssetMenu(fileName = "ActionData", menuName = "Combat/ActionData")] +public class ActionData : ScriptableObject +{ + [FormerlySerializedAs("AttackName")] + [FormerlySerializedAs("MotionName")] + public string ActionName; + public string AnimationState; + public float AnimationSpeed = 1f; + public AnimationCurve AnimationSpeedCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); + public bool ReturnToIdleOnAnimationComplete; + public float Cooldown = 0.3f; + public float ComboWindow = 0.25f; + + [Header("Motion")] + public bool HasMotion; + public Vector2 Velocity = Vector2.zero; + public AnimationCurve MotionSpeedCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); + [FormerlySerializedAs("Duration")] + public float MotionDuration = 0.3f; + public bool CanMoveDuringAction; + public bool CanTurnDuringAction; + public bool UseInputDirection = true; + public bool PreserveYVelocity = true; + public bool StopHorizontalVelocityOnEnd = true; + public bool IgnoreCollisionDuringAction; + public LayerMask IgnoredCollisionLayers; + + [Header("Hit")] + public bool HasHit = true; + public Vector2 Offset = new Vector2(0.5f, 0f); + public float Radius = 0.5f; + public int Damage = 10; + public float HitTiming = 0.15f; + public float HitDuration = 0f; + + [Header("Hit Reaction")] + public Vector2 HitVelocity = Vector2.zero; + public string HitReactionAnimationState; +} diff --git a/Assets/02_Scripts/Combat/ActionData.cs.meta b/Assets/02_Scripts/Combat/ActionData.cs.meta new file mode 100644 index 0000000..3e9cce0 --- /dev/null +++ b/Assets/02_Scripts/Combat/ActionData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 526ebe3dc7ff32e4faa2cfcdc1670638 diff --git a/Assets/02_Scripts/Combat/AttackData.cs b/Assets/02_Scripts/Combat/AttackData.cs deleted file mode 100644 index bc8c7ac..0000000 --- a/Assets/02_Scripts/Combat/AttackData.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEngine; - -[CreateAssetMenu(fileName = "AttackData", menuName = "Combat/AttackData")] -public class AttackData : ScriptableObject -{ - public string AttackName; - public string AnimationState; - public Vector2 Offset = new Vector2(0.5f, 0f); - public float Radius = 0.5f; - public int Damage = 10; - public float Cooldown = 0.3f; - - [Header("Timing")] - public float HitTiming = 0.15f; - public float HitDuration = 0f; - public float MotionDuration = 0.3f; -} diff --git a/Assets/02_Scripts/Combat/AttackData.cs.meta b/Assets/02_Scripts/Combat/AttackData.cs.meta deleted file mode 100644 index c282ab0..0000000 --- a/Assets/02_Scripts/Combat/AttackData.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 526ebe3dc7ff32e4faa2cfcdc1670638 \ No newline at end of file diff --git a/Assets/02_Scripts/Combat/ComboNode.cs b/Assets/02_Scripts/Combat/ComboNode.cs index 9389268..ad3caaa 100644 --- a/Assets/02_Scripts/Combat/ComboNode.cs +++ b/Assets/02_Scripts/Combat/ComboNode.cs @@ -1,10 +1,12 @@ using System; using UnityEngine; +using UnityEngine.Serialization; public enum ComboInputType { Punch, - Kick + Kick, + Motion } [Serializable] @@ -20,7 +22,8 @@ public class ComboTransition public class ComboNode : ScriptableObject { public string NodeName; - public AttackData Attack; + [FormerlySerializedAs("Attack")] + public ActionData Action; public float ComboWindow = 0.8f; public ComboTransition[] Transitions; } diff --git a/Assets/02_Scripts/Combat/IDamageable.cs b/Assets/02_Scripts/Combat/IDamageable.cs index b58de0a..ab7440e 100644 --- a/Assets/02_Scripts/Combat/IDamageable.cs +++ b/Assets/02_Scripts/Combat/IDamageable.cs @@ -1,4 +1,6 @@ +using UnityEngine; + public interface IDamageable { - void TakeDamage(int amount); + void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null); } diff --git a/Assets/02_Scripts/Enemy/Enemy.cs b/Assets/02_Scripts/Enemy/Enemy.cs index 36cc079..ff04a36 100644 --- a/Assets/02_Scripts/Enemy/Enemy.cs +++ b/Assets/02_Scripts/Enemy/Enemy.cs @@ -1,6 +1,7 @@ using UnityEngine; [RequireComponent(typeof(Collider2D))] +[RequireComponent(typeof(Rigidbody2D))] public class Enemy : MonoBehaviour, IDamageable { [Header("Stats")] @@ -10,14 +11,28 @@ public class Enemy : MonoBehaviour, IDamageable [SerializeField] private float _hitFlashDuration = 0.1f; [SerializeField] private Color _hitFlashColor = Color.red; + [Header("Hit Bounce")] + [SerializeField] private float _hitReactionDuration = 0.5f; + [SerializeField] private float _airborneHitYVelocity = 3f; + [SerializeField] private float _wallBounceVelocityMultiplier = 0.8f; + [SerializeField] private float _wallBounceMinXVelocity = 1f; + [SerializeField] private float _wallBounceUpwardVelocity = 1.5f; + private int _currentHealth; + private Rigidbody2D _rb; + private Animator _anim; private SpriteRenderer _spriteRenderer; private Color _originalColor; private float _flashTimer; + private float _hitReactionTimer; + private bool _isGrounded; + private Vector2 _lastVelocity; private void Awake() { _currentHealth = _maxHealth; + _rb = GetComponent(); + _anim = GetComponentInChildren(); _spriteRenderer = GetComponentInChildren(); if (_spriteRenderer != null) _originalColor = _spriteRenderer.color; @@ -31,9 +46,18 @@ private void Update() if (_flashTimer <= 0f && _spriteRenderer != null) _spriteRenderer.color = _originalColor; } + + if (_hitReactionTimer > 0f) + _hitReactionTimer -= Time.deltaTime; } - public void TakeDamage(int amount) + private void FixedUpdate() + { + if (_rb != null) + _lastVelocity = _rb.linearVelocity; + } + + public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null) { if (_currentHealth <= 0) return; @@ -46,10 +70,89 @@ public void TakeDamage(int amount) _flashTimer = _hitFlashDuration; } + if (_anim != null && !string.IsNullOrEmpty(hitReactionAnimationState)) + _anim.Play(hitReactionAnimationState); + + if (_rb != null) + { + Vector2 nextVelocity = GetHitReactionVelocity(hitVelocity); + if (nextVelocity != Vector2.zero) + { + _rb.linearVelocity = nextVelocity; + _hitReactionTimer = _hitReactionDuration; + } + } + if (_currentHealth <= 0) Die(); } + private void OnCollisionEnter2D(Collision2D collision) + { + UpdateGroundedState(collision); + + if (_hitReactionTimer <= 0f || _rb == null) return; + if (collision.collider.GetComponentInParent() != null) return; + + for (int i = 0; i < collision.contactCount; i++) + { + Vector2 normal = collision.GetContact(i).normal; + if (Mathf.Abs(normal.x) < 0.5f) continue; + + BounceOffWall(normal); + return; + } + } + + private void OnCollisionStay2D(Collision2D collision) + { + UpdateGroundedState(collision); + } + + private void OnCollisionExit2D(Collision2D collision) + { + _isGrounded = false; + } + + private Vector2 GetHitReactionVelocity(Vector2 hitVelocity) + { + if (_hitReactionTimer <= 0f || _isGrounded) + return hitVelocity; + + Vector2 currentVelocity = _rb.linearVelocity; + Vector2 nextVelocity = hitVelocity == Vector2.zero ? currentVelocity : hitVelocity; + nextVelocity.y = _airborneHitYVelocity; + return nextVelocity; + } + + private void UpdateGroundedState(Collision2D collision) + { + for (int i = 0; i < collision.contactCount; i++) + { + if (collision.GetContact(i).normal.y > 0.5f) + { + _isGrounded = true; + return; + } + } + } + + private void BounceOffWall(Vector2 wallNormal) + { + Vector2 incomingVelocity = _lastVelocity.sqrMagnitude > _rb.linearVelocity.sqrMagnitude + ? _lastVelocity + : _rb.linearVelocity; + + if (Mathf.Abs(incomingVelocity.x) < _wallBounceMinXVelocity) return; + + Vector2 bouncedVelocity = Vector2.Reflect(incomingVelocity, wallNormal) * _wallBounceVelocityMultiplier; + if (bouncedVelocity.y < _wallBounceUpwardVelocity) + bouncedVelocity.y = _wallBounceUpwardVelocity; + + _rb.linearVelocity = bouncedVelocity; + _hitReactionTimer = _hitReactionDuration; + } + private void Die() { Debug.Log($"{name} 사망"); diff --git a/Assets/02_Scripts/Input/GameInput.cs b/Assets/02_Scripts/Input/GameInput.cs index c022346..8221579 100644 --- a/Assets/02_Scripts/Input/GameInput.cs +++ b/Assets/02_Scripts/Input/GameInput.cs @@ -127,6 +127,24 @@ public @GameInput() ""processors"": """", ""interactions"": """", ""initialStateCheck"": false + }, + { + ""name"": ""Dash"", + ""type"": ""Button"", + ""id"": ""4245d8e3-7e61-4548-84af-75512958eb2f"", + ""expectedControlType"": """", + ""processors"": """", + ""interactions"": """", + ""initialStateCheck"": false + }, + { + ""name"": ""Roll"", + ""type"": ""Button"", + ""id"": ""7e00ae7c-ad0c-460d-be3d-0072054ceb9c"", + ""expectedControlType"": """", + ""processors"": """", + ""interactions"": """", + ""initialStateCheck"": false } ], ""bindings"": [ @@ -188,7 +206,7 @@ public @GameInput() { ""name"": """", ""id"": ""b9d8e2f3-4a5c-4b6d-8e7f-901234567890"", - ""path"": ""/space"", + ""path"": ""/c"", ""interactions"": """", ""processors"": """", ""groups"": """", @@ -217,6 +235,28 @@ public @GameInput() ""action"": ""Kick"", ""isComposite"": false, ""isPartOfComposite"": false + }, + { + ""name"": """", + ""id"": ""8bca2399-c8c8-41e8-a3b2-08267c8bc571"", + ""path"": ""/leftShift"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""Dash"", + ""isComposite"": false, + ""isPartOfComposite"": false + }, + { + ""name"": """", + ""id"": ""df4d72ec-012c-413a-862d-1a36d2c5b69a"", + ""path"": ""/space"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""Roll"", + ""isComposite"": false, + ""isPartOfComposite"": false } ] } @@ -229,6 +269,8 @@ public @GameInput() m_Player_Jump = m_Player.FindAction("Jump", throwIfNotFound: true); m_Player_Punch = m_Player.FindAction("Punch", throwIfNotFound: true); m_Player_Kick = m_Player.FindAction("Kick", throwIfNotFound: true); + m_Player_Dash = m_Player.FindAction("Dash", throwIfNotFound: true); + m_Player_Roll = m_Player.FindAction("Roll", throwIfNotFound: true); } ~@GameInput() @@ -313,6 +355,8 @@ public int FindBinding(InputBinding bindingMask, out InputAction action) private readonly InputAction m_Player_Jump; private readonly InputAction m_Player_Punch; private readonly InputAction m_Player_Kick; + private readonly InputAction m_Player_Dash; + private readonly InputAction m_Player_Roll; /// /// Provides access to input actions defined in input action map "Player". /// @@ -341,6 +385,14 @@ public struct PlayerActions /// public InputAction @Kick => m_Wrapper.m_Player_Kick; /// + /// Provides access to the underlying input action "Player/Dash". + /// + public InputAction @Dash => m_Wrapper.m_Player_Dash; + /// + /// Provides access to the underlying input action "Player/Roll". + /// + public InputAction @Roll => m_Wrapper.m_Player_Roll; + /// /// Provides access to the underlying input action map instance. /// public InputActionMap Get() { return m_Wrapper.m_Player; } @@ -378,6 +430,12 @@ public void AddCallbacks(IPlayerActions instance) @Kick.started += instance.OnKick; @Kick.performed += instance.OnKick; @Kick.canceled += instance.OnKick; + @Dash.started += instance.OnDash; + @Dash.performed += instance.OnDash; + @Dash.canceled += instance.OnDash; + @Roll.started += instance.OnRoll; + @Roll.performed += instance.OnRoll; + @Roll.canceled += instance.OnRoll; } /// @@ -401,6 +459,12 @@ private void UnregisterCallbacks(IPlayerActions instance) @Kick.started -= instance.OnKick; @Kick.performed -= instance.OnKick; @Kick.canceled -= instance.OnKick; + @Dash.started -= instance.OnDash; + @Dash.performed -= instance.OnDash; + @Dash.canceled -= instance.OnDash; + @Roll.started -= instance.OnRoll; + @Roll.performed -= instance.OnRoll; + @Roll.canceled -= instance.OnRoll; } /// @@ -469,5 +533,19 @@ public interface IPlayerActions /// /// void OnKick(InputAction.CallbackContext context); + /// + /// Method invoked when associated input action "Dash" is either , or . + /// + /// + /// + /// + void OnDash(InputAction.CallbackContext context); + /// + /// Method invoked when associated input action "Roll" is either , or . + /// + /// + /// + /// + void OnRoll(InputAction.CallbackContext context); } } diff --git a/Assets/02_Scripts/Managers/InputManager.cs b/Assets/02_Scripts/Managers/InputManager.cs index 1e3ad11..d1fe0dd 100644 --- a/Assets/02_Scripts/Managers/InputManager.cs +++ b/Assets/02_Scripts/Managers/InputManager.cs @@ -12,6 +12,8 @@ public class InputManager : MonoBehaviour, GameInput.IPlayerActions public event Action OnJump_Event; public event Action OnPunch_Event; public event Action OnKick_Event; + public event Action OnDash_Event; + public event Action OnRoll_Event; private void Awake() { @@ -27,7 +29,9 @@ private void Awake() } private void OnEnable() => _input?.Player.Enable(); + private void OnDisable() => _input?.Player.Disable(); + private void OnDestroy() => _input?.Dispose(); public void OnMove(InputAction.CallbackContext ctx) @@ -54,4 +58,15 @@ public void OnKick(InputAction.CallbackContext ctx) OnKick_Event?.Invoke(); } + public void OnDash(InputAction.CallbackContext ctx) + { + if (ctx.phase == InputActionPhase.Started) + OnDash_Event?.Invoke(); + } + + public void OnRoll(InputAction.CallbackContext ctx) + { + if (ctx.phase == InputActionPhase.Started) + OnRoll_Event?.Invoke(); + } } diff --git a/Assets/02_Scripts/Player/PlayerController.cs b/Assets/02_Scripts/Player/PlayerController.cs index a4b9a89..0cabf47 100644 --- a/Assets/02_Scripts/Player/PlayerController.cs +++ b/Assets/02_Scripts/Player/PlayerController.cs @@ -27,11 +27,31 @@ public class PlayerController : MonoBehaviour private bool IsTouchingWall => _isTouchingLeftWall || _isTouchingRightWall; private int _wallDirection; private float _inputLockTimer; + private float _facingLockTimer; + + [Header("Motion")] + [SerializeField] private ComboNode _dashRootNode; + [SerializeField] private ComboNode _rollRootNode; + private readonly Dictionary _motionCooldownTimers = new(); + private readonly List _motionCooldownKeys = new(); + private readonly List _ignoredLayerCollisions = new(); + private readonly List _overlapResults = new(); + private readonly List _castResults = new(); + private int _activeCollisionRecoveryLayerMask; + private Collider2D[] _bodyColliders; + private CancellationTokenSource _restoreCollisionCts; + private CancellationTokenSource _motionCts; + + [Header("Collision Recovery")] + [SerializeField] private float _overlapRecoverySpeed = 8f; + [SerializeField] private float _overlapRecoveryOtherBodyRatio = 0.5f; + [SerializeField] private float _bodyBlockCastDistance = 0.08f; [Header("Attack")] [SerializeField] private ComboNode _punchRootNode; [SerializeField] private ComboNode _kickRootNode; [SerializeField] private LayerMask _enemyLayer; + [SerializeField] private LayerMask _bodyCollisionIgnoredLayers; [SerializeField] private string _idleAnimationState = "Idle"; [SerializeField] private float _bufferOpenTime = 0.1f; [SerializeField] private float _bufferLifetime = 0.5f; @@ -41,10 +61,11 @@ public class PlayerController : MonoBehaviour private ComboNode _currentNode; private float _comboWindowTimer; private CancellationTokenSource _attackCts; - private AttackData _lastAttackGizmoData; + private CancellationTokenSource _animationSpeedCts; + private ActionData _lastAttackGizmoData; private float _lastAttackGizmoTime = -1f; [SerializeField] private float _hitGizmoFadeDuration = 0.5f; - private AttackData _lastHitData; + private ActionData _lastHitData; private Vector2 _lastHitCenter; private float _lastHitTime = -1f; @@ -62,6 +83,8 @@ private void Awake() _rb = GetComponent(); _anim = GetComponent(); _spriteRenderer = GetComponentInChildren(); + _bodyColliders = GetComponentsInChildren(); + IgnoreBodyCollisions(); } private void Start() @@ -71,6 +94,8 @@ private void Start() InputManager.Instance.OnPunch_Event += OnPunchInput; InputManager.Instance.OnKick_Event += OnKickInput; + InputManager.Instance.OnDash_Event += OnDashInput; + InputManager.Instance.OnRoll_Event += OnRollInput; } private void OnDestroy() @@ -81,10 +106,19 @@ private void OnDestroy() InputManager.Instance.OnJump_Event -= OnJumpInput; InputManager.Instance.OnPunch_Event -= OnPunchInput; InputManager.Instance.OnKick_Event -= OnKickInput; + InputManager.Instance.OnDash_Event -= OnDashInput; + InputManager.Instance.OnRoll_Event -= OnRollInput; } _attackCts?.Cancel(); _attackCts?.Dispose(); + _animationSpeedCts?.Cancel(); + _animationSpeedCts?.Dispose(); + _motionCts?.Cancel(); + _motionCts?.Dispose(); + _restoreCollisionCts?.Cancel(); + _restoreCollisionCts?.Dispose(); + RestoreIgnoredLayerCollisions(); } private void FixedUpdate() @@ -97,6 +131,8 @@ private void FixedUpdate() if (_attackCooldownTimer > 0f) _attackCooldownTimer -= Time.fixedDeltaTime; + UpdateMotionCooldowns(); + if (_attackCooldownTimer <= 0f && _pendingInput.HasValue) { ComboInputType buffered = _pendingInput.Value; @@ -115,7 +151,12 @@ private void FixedUpdate() if (_inputLockTimer > 0f) _inputLockTimer -= Time.fixedDeltaTime; else - _rb.linearVelocity = new Vector2(_moveInputX * _moveSpeed, _rb.linearVelocity.y); + _rb.linearVelocity = new Vector2(GetBodyBlockedXVelocity(_moveInputX * _moveSpeed), _rb.linearVelocity.y); + + if (_facingLockTimer > 0f) + _facingLockTimer -= Time.fixedDeltaTime; + else + UpdateFacingFromMoveInput(); if (IsTouchingWall && !_isGrounded && _rb.linearVelocity.y < -_wallSlideSpeed) _rb.linearVelocity = new Vector2(_rb.linearVelocity.x, -_wallSlideSpeed); @@ -125,6 +166,13 @@ private void OnMoveInput(Vector2 value) { _moveInputX = value.x == 0f ? 0f : Mathf.Sign(value.x); + if (_facingLockTimer > 0f) return; + + UpdateFacingFromMoveInput(); + } + + private void UpdateFacingFromMoveInput() + { if (_moveInputX != 0f && _spriteRenderer != null) _spriteRenderer.flipX = _moveInputX < 0f; } @@ -143,7 +191,9 @@ private void OnJumpInput() } private void OnPunchInput() => HandleComboInput(ComboInputType.Punch); - private void OnKickInput() => HandleComboInput(ComboInputType.Kick); + private void OnKickInput() => HandleComboInput(ComboInputType.Kick); + private void OnDashInput() => ExecuteMotionNode(_dashRootNode); + private void OnRollInput() => ExecuteMotionNode(_rollRootNode); private void HandleComboInput(ComboInputType input) { @@ -168,10 +218,10 @@ private void ExecuteComboInput(ComboInputType input) foreach (var transition in _currentNode.Transitions) { if (transition.Trigger != input) continue; - if (transition.Next == null || transition.Next.Attack == null) continue; + if (transition.Next == null || transition.Next.Action == null) continue; ApplyForwardStep(transition.ForwardStep, transition.ForwardStepDuration); - PerformAttack(transition.Next.Attack); + PerformAttack(transition.Next.Action, transition.ForwardStep > 0f); _currentNode = transition.Next; _comboWindowTimer = transition.Next.ComboWindow; return; @@ -184,14 +234,262 @@ private void ExecuteComboInput(ComboInputType input) ComboInputType.Kick => _kickRootNode, _ => null }; - if (root == null || root.Attack == null) return; + if (root == null || root.Action == null) return; - PerformAttack(root.Attack); + PerformAttack(root.Action); _currentNode = root; _comboWindowTimer = root.ComboWindow; } - private async void PerformAttack(AttackData data) + private void ExecuteMotionNode(ComboNode root) + { + if (root == null || root.Action == null) return; + if (IsMotionOnCooldown(root.Action)) return; + + PerformMotion(root.Action); + _currentNode = root; + _comboWindowTimer = root.ComboWindow; + } + + private void UpdateMotionCooldowns() + { + if (_motionCooldownTimers.Count == 0) return; + + _motionCooldownKeys.Clear(); + foreach (var pair in _motionCooldownTimers) + _motionCooldownKeys.Add(pair.Key); + + foreach (var action in _motionCooldownKeys) + { + float remaining = _motionCooldownTimers[action] - Time.fixedDeltaTime; + if (remaining <= 0f) + _motionCooldownTimers.Remove(action); + else + _motionCooldownTimers[action] = remaining; + } + } + + private bool IsMotionOnCooldown(ActionData data) + { + return data != null && _motionCooldownTimers.ContainsKey(data); + } + + private void SetMotionCooldown(ActionData data) + { + if (data == null || data.Cooldown <= 0f) return; + + _motionCooldownTimers[data] = data.Cooldown; + } + + private void IgnoreBodyCollisions() + { + int ignoredLayers = _bodyCollisionIgnoredLayers.value; + if (ignoredLayers == 0) return; + + int playerLayer = gameObject.layer; + for (int layer = 0; layer < 32; layer++) + { + if ((ignoredLayers & (1 << layer)) == 0) continue; + Physics2D.IgnoreLayerCollision(playerLayer, layer, true); + } + } + + private float GetBodyBlockedXVelocity(float xVelocity, ActionData action = null) + { + if (Mathf.Approximately(xVelocity, 0f)) return 0f; + if (_bodyCollisionIgnoredLayers.value == 0) return xVelocity; + if (CanPassBodyCollision(action)) return xVelocity; + if (!IsBodyBlocked(Mathf.Sign(xVelocity))) return xVelocity; + + return 0f; + } + + private bool CanPassBodyCollision(ActionData action) + { + return action != null + && action.IgnoreCollisionDuringAction + && (action.IgnoredCollisionLayers.value & _bodyCollisionIgnoredLayers.value) != 0; + } + + private bool IsBodyBlocked(float direction) + { + if (_bodyColliders == null || _bodyColliders.Length == 0) + _bodyColliders = GetComponentsInChildren(); + + ContactFilter2D filter = new ContactFilter2D + { + useLayerMask = true, + layerMask = _bodyCollisionIgnoredLayers, + useTriggers = false + }; + + Vector2 castDirection = new Vector2(direction, 0f); + for (int i = 0; i < _bodyColliders.Length; i++) + { + Collider2D bodyCollider = _bodyColliders[i]; + if (bodyCollider == null || bodyCollider.isTrigger) continue; + + _castResults.Clear(); + if (bodyCollider.Cast(castDirection, filter, _castResults, _bodyBlockCastDistance) > 0) + return true; + } + + return false; + } + + private void IgnoreCollisionsIfNeeded(ActionData data) + { + _restoreCollisionCts?.Cancel(); + if (!data.IgnoreCollisionDuringAction) + { + RestoreIgnoredLayerCollisionsWhenClear(); + return; + } + + int playerLayer = gameObject.layer; + int ignoredLayers = data.IgnoredCollisionLayers.value; + _activeCollisionRecoveryLayerMask = ignoredLayers; + for (int layer = 0; layer < 32; layer++) + { + if ((ignoredLayers & (1 << layer)) == 0) continue; + if (Physics2D.GetIgnoreLayerCollision(playerLayer, layer)) continue; + + Physics2D.IgnoreLayerCollision(playerLayer, layer, true); + _ignoredLayerCollisions.Add(new IgnoredLayerCollision(playerLayer, layer)); + } + } + + private void RestoreIgnoredLayerCollisions() + { + for (int i = 0; i < _ignoredLayerCollisions.Count; i++) + { + IgnoredLayerCollision ignored = _ignoredLayerCollisions[i]; + Physics2D.IgnoreLayerCollision(ignored.LayerA, ignored.LayerB, false); + } + + _ignoredLayerCollisions.Clear(); + _activeCollisionRecoveryLayerMask = 0; + } + + private void RestoreIgnoredLayerCollisionsWhenClear(LayerMask ignoredLayers = default) + { + int recoveryLayerMask = ignoredLayers.value != 0 ? ignoredLayers.value : _activeCollisionRecoveryLayerMask; + if (_ignoredLayerCollisions.Count == 0 && recoveryLayerMask == 0) return; + + _restoreCollisionCts?.Cancel(); + _restoreCollisionCts?.Dispose(); + _restoreCollisionCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); + WaitUntilClearThenRestoreCollisions(recoveryLayerMask, _restoreCollisionCts.Token); + } + + private async void WaitUntilClearThenRestoreCollisions(int recoveryLayerMask, CancellationToken token) + { + try + { + while (IsOverlappingIgnoredLayers(recoveryLayerMask)) + { + ResolveIgnoredLayerOverlap(recoveryLayerMask); + await Awaitable.NextFrameAsync(token); + } + } + catch (System.OperationCanceledException) + { + return; + } + + RestoreIgnoredLayerCollisions(); + } + + private void ResolveIgnoredLayerOverlap(int layerMask) + { + if (layerMask == 0) return; + + if (_bodyColliders == null || _bodyColliders.Length == 0) + _bodyColliders = GetComponentsInChildren(); + + ContactFilter2D filter = new ContactFilter2D + { + useLayerMask = true, + layerMask = layerMask, + useTriggers = false + }; + + Vector2 selfCenter = _rb.position; + float distance = _overlapRecoverySpeed * Time.deltaTime; + + for (int i = 0; i < _bodyColliders.Length; i++) + { + Collider2D bodyCollider = _bodyColliders[i]; + if (bodyCollider == null || bodyCollider.isTrigger) continue; + + _overlapResults.Clear(); + bodyCollider.Overlap(filter, _overlapResults); + + for (int j = 0; j < _overlapResults.Count; j++) + { + Collider2D other = _overlapResults[j]; + if (other == null) continue; + + Vector2 pushDirection = selfCenter - (Vector2)other.bounds.center; + if (pushDirection.sqrMagnitude < 0.0001f) + pushDirection = GetFacingDirection(); + pushDirection.Normalize(); + + Rigidbody2D otherRb = other.attachedRigidbody; + bool canMoveOther = otherRb != null && otherRb != _rb && otherRb.bodyType != RigidbodyType2D.Static; + float otherDistance = canMoveOther ? distance * _overlapRecoveryOtherBodyRatio : 0f; + float selfDistance = distance - otherDistance; + + _rb.position += pushDirection * selfDistance; + if (canMoveOther) + otherRb.position -= pushDirection * otherDistance; + } + } + } + + private Vector2 GetFacingDirection() + { + float facing = _spriteRenderer != null && _spriteRenderer.flipX ? -1f : 1f; + return new Vector2(facing, 0f); + } + + private bool IsOverlappingIgnoredLayers(int layerMask) + { + if (layerMask == 0) return false; + + if (_bodyColliders == null || _bodyColliders.Length == 0) + _bodyColliders = GetComponentsInChildren(); + + ContactFilter2D filter = new ContactFilter2D + { + useLayerMask = true, + layerMask = layerMask, + useTriggers = false + }; + + for (int i = 0; i < _bodyColliders.Length; i++) + { + Collider2D bodyCollider = _bodyColliders[i]; + if (bodyCollider == null || bodyCollider.isTrigger) continue; + + _overlapResults.Clear(); + if (bodyCollider.Overlap(filter, _overlapResults) > 0) + return true; + } + + return false; + } + + private int GetCurrentIgnoredLayerMask() + { + int layerMask = 0; + for (int i = 0; i < _ignoredLayerCollisions.Count; i++) + layerMask |= 1 << _ignoredLayerCollisions[i].LayerB; + + return layerMask; + } + + private async void PerformAttack(ActionData data, bool preserveHorizontalVelocity = false) { _attackCts?.Cancel(); _attackCts?.Dispose(); @@ -203,20 +501,217 @@ private async void PerformAttack(AttackData data) _attackStartTime = Time.time; _hitFired = false; - if (_anim != null && !string.IsNullOrEmpty(data.AnimationState)) - _anim.Play(data.AnimationState); + PlayActionAnimation(data); + + if (data.HasMotion) + { + ApplyActionVelocity(data); + } + LockMovementIfNeeded(data, preserveHorizontalVelocity); + LockFacingIfNeeded(data); + IgnoreCollisionsIfNeeded(data); try { await HitRoutine(data, token); } catch (System.OperationCanceledException) { } + RestoreIgnoredLayerCollisionsWhenClear(data.IgnoredCollisionLayers); } - private async Awaitable HitRoutine(AttackData data, CancellationToken token) + private async void PerformMotion(ActionData data) + { + if (data == null || IsMotionOnCooldown(data)) return; + + CancelAttack(); + _motionCts?.Cancel(); + _motionCts?.Dispose(); + _motionCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); + CancellationToken token = _motionCts.Token; + + SetMotionCooldown(data); + + FaceMotionDirection(data); + PlayActionAnimation(data); + + LockMovementIfNeeded(data); + LockFacingIfNeeded(data); + IgnoreCollisionsIfNeeded(data); + + bool completed = false; + try + { + await MotionRoutine(data, token); + completed = true; + } + catch (System.OperationCanceledException) { } + + if (completed) + { + StopActionVelocity(data); + PlayIdleAnimation(); + } + RestoreIgnoredLayerCollisionsWhenClear(data.IgnoredCollisionLayers); + } + + private async Awaitable MotionRoutine(ActionData data, CancellationToken token) + { + float elapsed = 0f; + float duration = Mathf.Max(data.MotionDuration, 0.01f); + + while (elapsed < duration) + { + token.ThrowIfCancellationRequested(); + + float normalizedTime = Mathf.Clamp01(elapsed / duration); + ApplyActionVelocity(data, normalizedTime); + + if (data.ReturnToIdleOnAnimationComplete && IsActionAnimationComplete(data)) + return; + + await Awaitable.NextFrameAsync(token); + elapsed += Time.deltaTime; + } + } + + private void CancelAttack() + { + _attackCts?.Cancel(); + _attackCooldownTimer = 0f; + _pendingInput = null; + } + + private void ApplyActionVelocity(ActionData data, float normalizedTime = 0f) + { + float direction = GetMotionDirection(data); + float speedMultiplier = data.MotionSpeedCurve != null + ? data.MotionSpeedCurve.Evaluate(normalizedTime) + : 1f; + Vector2 velocity = data.Velocity * speedMultiplier; + velocity.x *= direction; + velocity.x = GetBodyBlockedXVelocity(velocity.x, data); + + if (data.PreserveYVelocity) + velocity.y = _rb.linearVelocity.y; + + _rb.linearVelocity = velocity; + } + + private void StopActionVelocity(ActionData data) + { + if (!data.StopHorizontalVelocityOnEnd) return; + + _rb.linearVelocity = new Vector2(0f, _rb.linearVelocity.y); + } + + private bool IsActionAnimationComplete(ActionData data) + { + if (_anim == null || string.IsNullOrEmpty(data.AnimationState)) return false; + + AnimatorStateInfo stateInfo = _anim.GetCurrentAnimatorStateInfo(0); + return stateInfo.IsName(data.AnimationState) && stateInfo.normalizedTime >= 1f; + } + + private void LockMovementIfNeeded(ActionData data, bool preserveHorizontalVelocity = false) + { + if (data.CanMoveDuringAction) return; + + if (!preserveHorizontalVelocity && !data.HasMotion) + _rb.linearVelocity = new Vector2(0f, _rb.linearVelocity.y); + + _inputLockTimer = Mathf.Max(data.MotionDuration, 0.02f); + } + + private void LockFacingIfNeeded(ActionData data) + { + if (data.CanTurnDuringAction) return; + + _facingLockTimer = Mathf.Max(data.MotionDuration, 0.02f); + } + + private void PlayIdleAnimation() + { + if (_anim == null) return; + + _animationSpeedCts?.Cancel(); + _anim.speed = 1f; + if (!string.IsNullOrEmpty(_idleAnimationState)) + _anim.Play(_idleAnimationState); + } + + private void PlayActionAnimation(ActionData data) + { + if (_anim == null) return; + + _animationSpeedCts?.Cancel(); + _animationSpeedCts?.Dispose(); + _animationSpeedCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken); + + _anim.speed = GetAnimationSpeed(data, 0f); + if (!string.IsNullOrEmpty(data.AnimationState)) + _anim.Play(data.AnimationState); + + ApplyAnimationSpeedCurve(data, _animationSpeedCts.Token); + } + + private async void ApplyAnimationSpeedCurve(ActionData data, CancellationToken token) + { + float duration = Mathf.Max(data.MotionDuration, data.HitTiming + data.HitDuration, 0.01f); + float elapsed = 0f; + + try + { + while (elapsed < duration) + { + token.ThrowIfCancellationRequested(); + + float normalizedTime = Mathf.Clamp01(elapsed / duration); + _anim.speed = GetAnimationSpeed(data, normalizedTime); + + await Awaitable.NextFrameAsync(token); + elapsed += Time.deltaTime; + } + } + catch (System.OperationCanceledException) { } + } + + private float GetAnimationSpeed(ActionData data, float normalizedTime) + { + float curveMultiplier = data.AnimationSpeedCurve != null + ? data.AnimationSpeedCurve.Evaluate(normalizedTime) + : 1f; + + return Mathf.Max(data.AnimationSpeed * curveMultiplier, 0.01f); + } + + private float GetMotionDirection(ActionData data) + { + if (data.UseInputDirection && _moveInputX != 0f) + return _moveInputX; + + return _spriteRenderer != null && _spriteRenderer.flipX ? -1f : 1f; + } + + private void FaceMotionDirection(ActionData data) + { + if (_spriteRenderer == null) return; + if (!data.UseInputDirection || _moveInputX == 0f) return; + + _spriteRenderer.flipX = _moveInputX < 0f; + _facingLockTimer = 0f; + } + + private async Awaitable HitRoutine(ActionData data, CancellationToken token) { float attackStartTime = Time.time; + if (!data.HasHit) + { + if (data.MotionDuration > 0f) + await Awaitable.WaitForSecondsAsync(data.MotionDuration, token); + return; + } + if (data.HitTiming > 0f) await Awaitable.WaitForSecondsAsync(data.HitTiming, token); @@ -245,11 +740,10 @@ private async Awaitable HitRoutine(AttackData data, CancellationToken token) if (remaining > 0f) await Awaitable.WaitForSecondsAsync(remaining, token); - if (_anim != null && !string.IsNullOrEmpty(_idleAnimationState)) - _anim.Play(_idleAnimationState); + PlayIdleAnimation(); } - private void ApplyDamageInArea(AttackData data, HashSet alreadyHit) + private void ApplyDamageInArea(ActionData data, HashSet alreadyHit) { Vector2 center = GetAttackCenter(data.Offset); @@ -268,10 +762,23 @@ private void ApplyDamageInArea(AttackData data, HashSet alreadyHit) alreadyHit.Add(target); } - target.TakeDamage(data.Damage); + target.TakeDamage(data.Damage, GetHitVelocity(data.HitVelocity), data.HitReactionAnimationState); } } + private Vector2 GetHitVelocity(Vector2 hitVelocity) + { + float facing = _spriteRenderer != null && _spriteRenderer.flipX ? -1f : 1f; + hitVelocity.x *= facing; + return hitVelocity; + } + + private string GetActionName(ActionData data) + { + if (data == null) return string.Empty; + return string.IsNullOrEmpty(data.ActionName) ? data.name : data.ActionName; + } + private Vector2 GetAttackCenter(Vector2 offset) { float facing = _spriteRenderer != null && _spriteRenderer.flipX ? -1f : 1f; @@ -338,7 +845,7 @@ private void DrawLastAttackGizmo() float elapsed = Time.time - _lastAttackGizmoTime; if (elapsed < 0f) return; - AttackData data = _lastAttackGizmoData; + ActionData data = _lastAttackGizmoData; float activeDuration = Mathf.Max(data.HitDuration, 0.05f); float fadeDuration = 0.4f; float total = activeDuration + fadeDuration; @@ -360,7 +867,7 @@ private void OnGUI() { if (!_showAttackDebug || _lastAttackGizmoData == null) return; - AttackData data = _lastAttackGizmoData; + ActionData data = _lastAttackGizmoData; float elapsed = Time.time - _attackStartTime; string status = _hitFired @@ -375,7 +882,7 @@ private void OnGUI() : (elapsed >= _bufferOpenTime && _attackCooldownTimer > 0f ? "ready to buffer" : "-"); string info = - $"{(string.IsNullOrEmpty(data.AttackName) ? data.name : data.AttackName)} [{status}]\n" + + $"{GetActionName(data)} [{status}]\n" + $"Elapsed : {elapsed:F3} s\n" + $"HitTiming : {data.HitTiming:F3} s\n" + $"HitDuration : {data.HitDuration:F3} s\n" + @@ -397,7 +904,7 @@ private void OnGUI() DrawTimelineBar(data, elapsed); } - private void DrawTimelineBar(AttackData data, float elapsed) + private void DrawTimelineBar(ActionData data, float elapsed) { float barX = 10f; float barY = 302f; @@ -431,4 +938,16 @@ private void DrawTimelineBar(AttackData data, float elapsed) GUI.color = Color.white; } + + private readonly struct IgnoredLayerCollision + { + public readonly int LayerA; + public readonly int LayerB; + + public IgnoredLayerCollision(int layerA, int layerB) + { + LayerA = layerA; + LayerB = layerB; + } + } } diff --git a/Assets/05_Data/Attack/Kick_A.asset b/Assets/05_Data/Attack/Kick_A.asset index e59cbb3..bddc55e 100644 --- a/Assets/05_Data/Attack/Kick_A.asset +++ b/Assets/05_Data/Attack/Kick_A.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72dbd6f9fd5f4dc9ca77940765f38b40a7adea50d27ad13e90bf727f3e9069f6 -size 595 +oid sha256:85865e7243cbf924a44378c57d46bb4d7bc620a1115148958b3deeb4cdfd99c5 +size 1397 diff --git a/Assets/05_Data/Attack/Kick_B.asset b/Assets/05_Data/Attack/Kick_B.asset index 57d57ce..9535507 100644 --- a/Assets/05_Data/Attack/Kick_B.asset +++ b/Assets/05_Data/Attack/Kick_B.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b526368afdd58cbbd4dcc02e8e9508ac87ff65b3c1443c69e55cdf95e0234061 -size 595 +oid sha256:8fb17a64df97fd6ff3d7fac22ba257b48f0a41d5fb9680c8f92c024237bc7d5a +size 1397 diff --git a/Assets/05_Data/Attack/Kick_C.asset b/Assets/05_Data/Attack/Kick_C.asset index a792047..6f89e66 100644 --- a/Assets/05_Data/Attack/Kick_C.asset +++ b/Assets/05_Data/Attack/Kick_C.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:271ca01e668560c7ee35da6cd07bbb44e5b49c2133bd18fc152356f847942309 -size 594 +oid sha256:77ba009af9cd1cd1d4e4d1c6709e21fc949d3f6864e376b68685d399a1f25c82 +size 1454 diff --git a/Assets/05_Data/Attack/Punch_A.asset b/Assets/05_Data/Attack/Punch_A.asset index 31f5bfb..4231957 100644 --- a/Assets/05_Data/Attack/Punch_A.asset +++ b/Assets/05_Data/Attack/Punch_A.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ea339da71d3097a389461bc60fd69f952a348e58134c712a012d908ebad858b -size 597 +oid sha256:fd1fcd2a69cccb16a6c1116fd36d8bdbf7c5e38738cb29f5e2534aa33bb33a60 +size 1399 diff --git a/Assets/05_Data/Attack/Punch_B.asset b/Assets/05_Data/Attack/Punch_B.asset index 47fb8c0..318875e 100644 --- a/Assets/05_Data/Attack/Punch_B.asset +++ b/Assets/05_Data/Attack/Punch_B.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:940ff46b36ef1fc51609c093504e46c8b08d7400edc5be42341aaddc3ab86df1 -size 597 +oid sha256:e5dcd26ba980948c6dcff5d7c726f304d3fcedfd43c17e3bcbb63de27117a80b +size 1399 diff --git a/Assets/05_Data/Attack/Punch_C.asset b/Assets/05_Data/Attack/Punch_C.asset index 23c09ec..8829500 100644 --- a/Assets/05_Data/Attack/Punch_C.asset +++ b/Assets/05_Data/Attack/Punch_C.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:458b420ea8c651c508121438e640eada16c7664a99b7b1fd9e65221e5ac45083 -size 597 +oid sha256:366fd5748ee99cd90fb000d43cd52136c903f7ec6e6887784bf820f9daa234d7 +size 1459 diff --git a/Assets/05_Data/Combo/Combo_Dash.asset b/Assets/05_Data/Combo/Combo_Dash.asset new file mode 100644 index 0000000..e850682 --- /dev/null +++ b/Assets/05_Data/Combo/Combo_Dash.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f73d3b349118476c5ae70fdffbe8226f2b8721b8f251ee4187da9e100a57042 +size 700 diff --git a/Assets/05_Data/Combo/Combo_Dash.asset.meta b/Assets/05_Data/Combo/Combo_Dash.asset.meta new file mode 100644 index 0000000..bc8595c --- /dev/null +++ b/Assets/05_Data/Combo/Combo_Dash.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c2c47ad8605b4f23a77d8f77bcebe312 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/05_Data/Combo/Combo_Kick_A.asset b/Assets/05_Data/Combo/Combo_Kick_A.asset index 16d6814..8b3a4d5 100644 --- a/Assets/05_Data/Combo/Combo_Kick_A.asset +++ b/Assets/05_Data/Combo/Combo_Kick_A.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b71f8555c5216a9405f13b9e1649a3256a13af9ea0982cbf1bb622ec468b9e7a +oid sha256:0696ba9d04427b140caa19122c76b971abcdc62d29fcf5dcdb97506e83d6f194 size 705 diff --git a/Assets/05_Data/Combo/Combo_Kick_B.asset b/Assets/05_Data/Combo/Combo_Kick_B.asset index b83c1a0..675e185 100644 --- a/Assets/05_Data/Combo/Combo_Kick_B.asset +++ b/Assets/05_Data/Combo/Combo_Kick_B.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:434c2fb5c2eda30d50b943d761e95ba642fb028548ff119e3d2a8e39a9b0137b +oid sha256:d3c19086ed48c7d48c3cb9e2354882ba6ac4677bb57c7785fbe75f3d4977d672 size 708 diff --git a/Assets/05_Data/Combo/Combo_Kick_C.asset b/Assets/05_Data/Combo/Combo_Kick_C.asset index db7a69e..b6e8287 100644 --- a/Assets/05_Data/Combo/Combo_Kick_C.asset +++ b/Assets/05_Data/Combo/Combo_Kick_C.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:825e76e7b78d8f3e6575db6a20cb61e0b1daa51a1824e1204163ca9cb11ba8fb +oid sha256:294db8eab933b3f36b4699716fddd1cb60f6476d77289fef3f9e5bcd1ff472b2 size 567 diff --git a/Assets/05_Data/Combo/Combo_Punch_A.asset b/Assets/05_Data/Combo/Combo_Punch_A.asset index c7ae42c..7a9ee6a 100644 --- a/Assets/05_Data/Combo/Combo_Punch_A.asset +++ b/Assets/05_Data/Combo/Combo_Punch_A.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d0941defedb480bf1ff9ee11be2d0d25c8ac000ffdf8ab392980a6cd89032fe +oid sha256:61a0ae9dc452434b1327453e7c70d95a5d83fb2df692877e927e3b203b426651 size 709 diff --git a/Assets/05_Data/Combo/Combo_Punch_B.asset b/Assets/05_Data/Combo/Combo_Punch_B.asset index 65f39be..e44f598 100644 --- a/Assets/05_Data/Combo/Combo_Punch_B.asset +++ b/Assets/05_Data/Combo/Combo_Punch_B.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad0e94dcd0572a96d6503ae7a98a28ff25a1ff1eb7d15c3bf0522ad846828785 +oid sha256:76379b7dcebadbf175dd7c593a86ce196244ad0383bddaee1bf2065a1f65bf32 size 710 diff --git a/Assets/05_Data/Combo/Combo_Punch_C.asset b/Assets/05_Data/Combo/Combo_Punch_C.asset index 047a960..78bc4ac 100644 --- a/Assets/05_Data/Combo/Combo_Punch_C.asset +++ b/Assets/05_Data/Combo/Combo_Punch_C.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd4e3e455128278ecbb1a127abcd52cb08a36a89a664722a244aa2642b15934f +oid sha256:c311ab650560ff057a24babdbf6e0b7cee227b7aa97016189245c4ce1c86108e size 569 diff --git a/Assets/05_Data/Combo/Combo_Roll.asset b/Assets/05_Data/Combo/Combo_Roll.asset new file mode 100644 index 0000000..42e7b4e --- /dev/null +++ b/Assets/05_Data/Combo/Combo_Roll.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:281b39a4ecb4e0e17b7872f293379b87aea41aa33b9a26936f52cfaf3621fcc3 +size 564 diff --git a/Assets/05_Data/Combo/Combo_Roll.asset.meta b/Assets/05_Data/Combo/Combo_Roll.asset.meta new file mode 100644 index 0000000..eed52f9 --- /dev/null +++ b/Assets/05_Data/Combo/Combo_Roll.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 25cae4aa2eed34745ae44f422fe7db0b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/05_Data/Motion.meta b/Assets/05_Data/Motion.meta new file mode 100644 index 0000000..c28338f --- /dev/null +++ b/Assets/05_Data/Motion.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c9e5f4a7b3649ce8f2a53d91b0c7e6a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/05_Data/Motion/Dash.asset b/Assets/05_Data/Motion/Dash.asset new file mode 100644 index 0000000..a27c4cf --- /dev/null +++ b/Assets/05_Data/Motion/Dash.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22d4fe2447d78255aa7b8ed8aae62475b8b01b1b759d953a4f98e4a526262e13 +size 3117 diff --git a/Assets/05_Data/Motion/Dash.asset.meta b/Assets/05_Data/Motion/Dash.asset.meta new file mode 100644 index 0000000..7ad64a1 --- /dev/null +++ b/Assets/05_Data/Motion/Dash.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b31f0b5b733f4f1a8c9cb9bb96a5334e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/05_Data/Motion/Roll.asset b/Assets/05_Data/Motion/Roll.asset new file mode 100644 index 0000000..676e365 --- /dev/null +++ b/Assets/05_Data/Motion/Roll.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931bb8aa580fecf101b41a450fba014a0012d61f3d6c0dd0924f945b4f3b8b0a +size 2462 diff --git a/Assets/05_Data/Motion/Roll.asset.meta b/Assets/05_Data/Motion/Roll.asset.meta new file mode 100644 index 0000000..2cb282e --- /dev/null +++ b/Assets/05_Data/Motion/Roll.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8604a664935ecec4b82c162eca1ba4b6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/11_Input/GameInput.inputactions b/Assets/11_Input/GameInput.inputactions index 38da9e3..b3e12c4 100644 --- a/Assets/11_Input/GameInput.inputactions +++ b/Assets/11_Input/GameInput.inputactions @@ -41,6 +41,24 @@ "processors": "", "interactions": "", "initialStateCheck": false + }, + { + "name": "Dash", + "type": "Button", + "id": "4245d8e3-7e61-4548-84af-75512958eb2f", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Roll", + "type": "Button", + "id": "7e00ae7c-ad0c-460d-be3d-0072054ceb9c", + "expectedControlType": "", + "processors": "", + "interactions": "", + "initialStateCheck": false } ], "bindings": [ @@ -102,7 +120,7 @@ { "name": "", "id": "b9d8e2f3-4a5c-4b6d-8e7f-901234567890", - "path": "/space", + "path": "/c", "interactions": "", "processors": "", "groups": "", @@ -131,6 +149,28 @@ "action": "Kick", "isComposite": false, "isPartOfComposite": false + }, + { + "name": "", + "id": "8bca2399-c8c8-41e8-a3b2-08267c8bc571", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "", + "action": "Dash", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "df4d72ec-012c-413a-862d-1a36d2c5b69a", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "", + "action": "Roll", + "isComposite": false, + "isPartOfComposite": false } ] }