Attack animation, colliders, triggering
This commit is contained in:
1 parent
3e631899ea
commit
e61fcb78cf
94 files changed
+142319
-25861
No files matched your search
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89ff9bb140304946a76896dd4b841601
|
||||
timeCreated: 1792938702
|
||||
@@ -0,0 +1,27 @@
|
||||
using Code.Infrastructure.Generators;
|
||||
using Code.OfficeGame.BattleMode.HitBoxes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions.Attacks
|
||||
{
|
||||
[Config("Configs/AttackAction", true)]
|
||||
[CreateAssetMenu(fileName = "AttackActionConfig", menuName = "Configs/AttackAction")]
|
||||
public class AttackActionConfig : ScriptableObject
|
||||
{
|
||||
public BattleActionType ActionType;
|
||||
|
||||
public AnimationClip AnimationClip;
|
||||
|
||||
public int StateHash => Animator.StringToHash(AnimationClip.name);
|
||||
|
||||
public int TotalFrames;
|
||||
|
||||
public int HitboxStartFrame;
|
||||
|
||||
public int HitboxEndFrame;
|
||||
|
||||
public HitBoxPartType AttackingBodyPart;
|
||||
|
||||
public int Damage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d47fdf0c6f444c087bcc6f2492746af
|
||||
timeCreated: 1792938807
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Linq;
|
||||
using Code.Infrastructure.Utils;
|
||||
using Code.OfficeGame.BattleMode.HitBoxes;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Reflex.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions.Attacks
|
||||
{
|
||||
public class BattleActionPlayerAttackMMA : BattleAction
|
||||
{
|
||||
private Animator _animator;
|
||||
private EntityHitBoxes _entityHitBoxes;
|
||||
private AttackActionConfig _attackData;
|
||||
public int EntryStateHash => Animator.StringToHash("WalkBlend");
|
||||
|
||||
private int _currentFrame;
|
||||
|
||||
[Inject]
|
||||
public void Construct(MultipleConfig<AttackActionConfig> attackData)
|
||||
{
|
||||
_animator = GetComponentInChildren<Animator>();
|
||||
_entityHitBoxes = GetComponent<EntityHitBoxes>();
|
||||
_attackData = attackData.Configs.SingleOrDefault(x=>x.ActionType == ActionType);
|
||||
}
|
||||
|
||||
public override async UniTask<BattleActionDecisionResultCode> DecideAction()
|
||||
{
|
||||
_currentFrame = 0;
|
||||
Finished = false;
|
||||
|
||||
return BattleActionDecisionResultCode.Decided;
|
||||
}
|
||||
|
||||
public override BattleActionType ActionType => BattleActionType.PlayerAttackMMA;
|
||||
|
||||
public override void Tick(float tickDelta)
|
||||
{
|
||||
if (Finished) return;
|
||||
|
||||
if (_currentFrame == 0)
|
||||
{
|
||||
_animator.Play(_attackData.StateHash, 0, 0f);
|
||||
}
|
||||
|
||||
_currentFrame++;
|
||||
|
||||
if (_currentFrame >= _attackData.HitboxStartFrame && _currentFrame <= _attackData.HitboxEndFrame)
|
||||
{
|
||||
_entityHitBoxes.EnableHitbox(_attackData.AttackingBodyPart);
|
||||
_entityHitBoxes.CheckHitsForCurrentFrame(OnEnemyHit);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entityHitBoxes.DisableAllHitboxes();
|
||||
}
|
||||
|
||||
if (_currentFrame >= _attackData.TotalFrames)
|
||||
{
|
||||
CompleteOrInterruptAction();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnemyHit(Collider enemyCollider)
|
||||
{
|
||||
Debug.Log($"Попадание в {enemyCollider.name} на {_currentFrame}-м кадре! Урон: {_attackData.Damage}");
|
||||
}
|
||||
|
||||
public void CompleteOrInterruptAction()
|
||||
{
|
||||
_animator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
|
||||
_entityHitBoxes.DisableAllHitboxes();
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1803413768b4cf990d25732721300ea
|
||||
timeCreated: 1792938407
|
||||
@@ -5,6 +5,6 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
None,
|
||||
Wait,
|
||||
Move,
|
||||
Attack
|
||||
PlayerAttackMMA
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
@@ -12,12 +13,14 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
|
||||
public abstract UniTask DecideAction(CancellationToken ctx);
|
||||
|
||||
public Animator animator;
|
||||
[DoNotSerialize] public Animator animator;
|
||||
|
||||
protected readonly Dictionary<BattleActionType, BattleAction> BattleActions = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
|
||||
foreach (var action in GetComponents<BattleAction>())
|
||||
{
|
||||
BattleActions.Add(action.ActionType, action);
|
||||
|
||||
@@ -12,8 +12,6 @@ namespace Code.OfficeGame.BattleMode.Actions.Player
|
||||
|
||||
public override async UniTask DecideAction(CancellationToken ctx)
|
||||
{
|
||||
BattleActionDecisionResultCode decision = BattleActionDecisionResultCode.Deciding;
|
||||
|
||||
_battleActionDecisionPanel.SetEntityInfo(BattleActions);
|
||||
|
||||
BattleActionType decidedAction = BattleActionType.None;
|
||||
@@ -25,7 +23,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Player
|
||||
await _battleActionDecisionPanel.Open(ctx);
|
||||
|
||||
decidedAction = await _battleActionDecisionPanel.GetDecision(ctx);
|
||||
|
||||
var decision = BattleActionDecisionResultCode.Deciding;
|
||||
|
||||
while (decidedAction != BattleActionType.None
|
||||
&& decision == BattleActionDecisionResultCode.Deciding
|
||||
&& !ctx.IsCancellationRequested)
|
||||
@@ -40,7 +39,6 @@ namespace Code.OfficeGame.BattleMode.Actions.Player
|
||||
decidedAction = BattleActionType.None;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentAction = BattleActions[decidedAction];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0c80eaa7bc9433f919752a88a0e364f
|
||||
timeCreated: 1792933353
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.HitBoxes
|
||||
{
|
||||
public class EntityHitBoxes : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public struct HitBoxPartCollider
|
||||
{
|
||||
public HitBoxPartType PartType;
|
||||
public BoxCollider Collider;
|
||||
}
|
||||
|
||||
[SerializeField] private HitBoxPartCollider[] bodyParts;
|
||||
private LayerMask _hitBoxLayerMask;
|
||||
|
||||
private readonly Dictionary<HitBoxPartType, BoxCollider> _colliderMap = new();
|
||||
private readonly Collider[] _hitResults = new Collider[8];
|
||||
|
||||
// Список уже поражённых целей за один взмах, чтобы один удар не наносил урон 8 раз за 8 активных кадров
|
||||
private readonly HashSet<GameObject> _alreadyHitTargets = new();
|
||||
|
||||
private HitBoxPartType? _activePart;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
foreach (var part in bodyParts)
|
||||
{
|
||||
part.Collider.enabled = false;
|
||||
_colliderMap[part.PartType] = part.Collider;
|
||||
}
|
||||
_hitBoxLayerMask = LayerMask.GetMask("HitBox");
|
||||
}
|
||||
|
||||
public void EnableHitbox(HitBoxPartType part)
|
||||
{
|
||||
if (_activePart == part) return;
|
||||
|
||||
_alreadyHitTargets.Clear();
|
||||
_activePart = part;
|
||||
|
||||
if (_colliderMap.TryGetValue(part, out var col))
|
||||
{
|
||||
col.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableAllHitboxes()
|
||||
{
|
||||
if (_activePart.HasValue && _colliderMap.TryGetValue(_activePart.Value, out var col))
|
||||
{
|
||||
col.enabled = false;
|
||||
}
|
||||
|
||||
_activePart = null;
|
||||
_alreadyHitTargets.Clear();
|
||||
}
|
||||
|
||||
public void CheckHitsForCurrentFrame(Action<Collider> onNewTargetHit)
|
||||
{
|
||||
if (!_activePart.HasValue) return;
|
||||
|
||||
BoxCollider box = _colliderMap[_activePart.Value];
|
||||
Transform boxTransform = box.transform;
|
||||
|
||||
// Вычисляем точный мировой центр и размер коллайдера на кости в текущем кадре анимации
|
||||
Vector3 worldCenter = boxTransform.TransformPoint(box.center);
|
||||
Vector3 worldHalfExtents = Vector3.Scale(box.size * 0.5f, boxTransform.lossyScale);
|
||||
|
||||
int hitsCount = Physics.OverlapBoxNonAlloc(
|
||||
worldCenter,
|
||||
worldHalfExtents,
|
||||
_hitResults,
|
||||
boxTransform.rotation,
|
||||
_hitBoxLayerMask,
|
||||
QueryTriggerInteraction.Collide);
|
||||
|
||||
for (int i = 0; i < hitsCount; i++)
|
||||
{
|
||||
Collider hitCol = _hitResults[i];
|
||||
|
||||
// Игнорируем самого себя
|
||||
if (hitCol.transform.root == transform.root) continue;
|
||||
|
||||
// Если за этот взмах мы ещё не били этого врага — засчитываем попадание!
|
||||
if (_alreadyHitTargets.Add(hitCol.gameObject))
|
||||
{
|
||||
onNewTargetHit?.Invoke(hitCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum HitBoxPartType
|
||||
{
|
||||
Head,
|
||||
Chest,
|
||||
Arm,
|
||||
Leg
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7145242b58614696a59d16b96e3b694e
|
||||
timeCreated: 1792933436
|
||||
@@ -66,7 +66,7 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
private void SimulateSingleFrame(List<EntityTimelineActor> outReadyEntities)
|
||||
{
|
||||
CurrentBattleFrame++;
|
||||
Debug.Log($"Current frame: {CurrentBattleFrame}");
|
||||
|
||||
foreach (var entity in _entities)
|
||||
{
|
||||
entity.CurrentAction.Tick(TickDelta);
|
||||
|
||||
@@ -7,7 +7,6 @@ using Cysharp.Threading.Tasks;
|
||||
using LitMotion;
|
||||
using LitMotion.Extensions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.UI
|
||||
{
|
||||
@@ -23,8 +22,6 @@ namespace Code.OfficeGame.BattleMode.UI
|
||||
|
||||
public void SetEntityInfo(Dictionary<BattleActionType, BattleAction> battleActions)
|
||||
{
|
||||
_decisionCompletionSource = new();
|
||||
|
||||
foreach (var button in _actionButtons) Destroy(button.gameObject);
|
||||
|
||||
_actionButtons.Clear();
|
||||
@@ -45,6 +42,8 @@ namespace Code.OfficeGame.BattleMode.UI
|
||||
{
|
||||
try
|
||||
{
|
||||
_decisionCompletionSource = new();
|
||||
|
||||
var result = await _decisionCompletionSource.Task
|
||||
.AttachExternalCancellation(cts);
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using Code.OfficeGame.BattleMode.Actions;
|
||||
using KinematicCharacterController;
|
||||
using Reflex.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.Player
|
||||
@@ -10,18 +7,15 @@ namespace Code.OfficeGame.Player
|
||||
{
|
||||
private static readonly int VelocityForward = Animator.StringToHash("VelocityForward");
|
||||
private static readonly int VelocityRight = Animator.StringToHash("VelocityRight");
|
||||
private static readonly int Attack = Animator.StringToHash("Attack");
|
||||
|
||||
|
||||
private KinematicCharacterMotor _motor;
|
||||
public Animator Animator { get; private set; }
|
||||
public EntityTimelineActor EntityTimelineActor { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_motor = GetComponent<KinematicCharacterMotor>();
|
||||
Animator = GetComponentInChildren<Animator>();
|
||||
EntityTimelineActor = GetComponent<EntityTimelineActor>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -33,16 +27,6 @@ namespace Code.OfficeGame.Player
|
||||
var rightVelocity =
|
||||
Vector3.Project(_motor.Velocity, transform.forward).magnitude;
|
||||
Animator.SetFloat(VelocityRight, rightVelocity);
|
||||
|
||||
if(EntityTimelineActor.CurrentAction)
|
||||
switch (EntityTimelineActor.CurrentAction.ActionType)
|
||||
{
|
||||
case BattleActionType.Wait:
|
||||
break;
|
||||
case BattleActionType.Attack:
|
||||
Animator.SetTrigger(Attack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user