Exaction timelinetion
This commit is contained in:
1 parent
921c4125b7
commit
0288afb00a
722 files changed
+144715
-152451
No files matched your search
@@ -1,9 +1,29 @@
|
||||
using Code.OfficeGame.BattleMode.Actions;
|
||||
using KinematicCharacterController;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.Game.Entities
|
||||
{
|
||||
public static class EntitySnapshotExtensions
|
||||
public static class EntityExtensions
|
||||
{
|
||||
// WC
|
||||
public static void SyncRootMotionToMotor(this EntityProperties entity, bool syncRotation = true)
|
||||
{
|
||||
Transform animTransform = entity.animator.transform;
|
||||
KinematicCharacterMotor motor = entity.KinematicCharacterMotor;
|
||||
|
||||
Vector3 targetWorldPos = animTransform.position;
|
||||
Quaternion targetWorldRot = syncRotation
|
||||
? animTransform.rotation
|
||||
: motor.TransientRotation;
|
||||
|
||||
animTransform.localPosition = Vector3.zero;
|
||||
animTransform.localRotation = Quaternion.identity;
|
||||
|
||||
motor.SetPositionAndRotation(targetWorldPos, targetWorldRot);
|
||||
motor.BaseVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
public static EntitySnapshot GetSnapshot(this EntityProperties entityProperties)
|
||||
{
|
||||
return EntitySnapshot.GetSnapshot(entityProperties);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Code.Infrastructure.StateMachine
|
||||
|
||||
private readonly SemaphoreSlim _stateLock = new (1, 1);
|
||||
|
||||
public virtual async UniTask SwitchState(TStateEnum nextState)
|
||||
public async UniTask SwitchState(TStateEnum nextState)
|
||||
{
|
||||
await _stateLock.WaitAsync();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
public class AttackBattleActionData : BattleActionData
|
||||
{
|
||||
public int CurrentFrame;
|
||||
public int TransitionFrames;
|
||||
public override BattleActionData GetSnapshot()
|
||||
{
|
||||
var snapshot = new AttackBattleActionData()
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
public abstract class BattleActionData
|
||||
{
|
||||
public int TotalFrames;
|
||||
public bool Finished = false;
|
||||
|
||||
public abstract BattleActionData GetSnapshot();
|
||||
public abstract void RestoreSnapshot(BattleActionData snapshot);
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public class MovementBattleActionData : BattleActionData
|
||||
{
|
||||
public bool WasStarted = false;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public NavMeshPath Path;
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
public class StuggerActionData : BattleActionData
|
||||
{
|
||||
public int CurrentFrame;
|
||||
public int TransitionFrames = 60;
|
||||
public override BattleActionData GetSnapshot()
|
||||
{
|
||||
var snapshot = new AttackBattleActionData()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Linq;
|
||||
using Code.Game.Entities;
|
||||
using Code.Infrastructure.Particles;
|
||||
using Code.Infrastructure.Utils;
|
||||
using Code.OfficeGame.BattleMode.Actions.Deciders;
|
||||
using Code.OfficeGame.BattleMode.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions.Attacks
|
||||
@@ -11,61 +13,77 @@ namespace Code.OfficeGame.BattleMode.Actions.Attacks
|
||||
private AttackActionConfig _attackData;
|
||||
private IParticleService _particleService;
|
||||
public int EntryStateHash => Animator.StringToHash("WalkBlend");
|
||||
|
||||
public BattleActionPlayerAttackMMA(MultipleConfig<BattleActionParameters> attackData, IParticleService particleService)
|
||||
|
||||
public BattleActionPlayerAttackMMA(MultipleConfig<BattleActionParameters> attackData,
|
||||
IParticleService particleService)
|
||||
{
|
||||
_particleService = particleService;
|
||||
_attackData = (AttackActionConfig)
|
||||
attackData.Configs.SingleOrDefault(x=>x.ActionType == ActionType);
|
||||
_attackData = (AttackActionConfig)
|
||||
attackData.Configs.SingleOrDefault(x => x.ActionType == ActionType);
|
||||
}
|
||||
|
||||
public override BattleActionType ActionType => BattleActionType.PlayerAttackMMA;
|
||||
|
||||
public override bool Tick(float tickDelta,
|
||||
EntityProperties entity,
|
||||
BattleActionDecisionResult battleActionDecisionResult,
|
||||
public override void Tick(float tickDelta,
|
||||
EntityProperties entity,
|
||||
BattleActionDecisionResult battleActionDecisionResult,
|
||||
BattleActionData battleActionData)
|
||||
{
|
||||
if (battleActionData is not AttackBattleActionData actionData) return true;
|
||||
if (battleActionData is not AttackBattleActionData actionData
|
||||
|| battleActionDecisionResult is not RotationBattleActionDeciderResult deciderResult)
|
||||
{
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (actionData.CurrentFrame == 0)
|
||||
{
|
||||
var entityAnimator = entity.animator;
|
||||
actionData.TransitionFrames = _attackData.TransitionFrames;
|
||||
actionData.TotalFrames = _attackData.TotalFrames;
|
||||
|
||||
// TODO Root Motion не должен быть использован в нормальных анимациях
|
||||
|
||||
entityAnimator.applyRootMotion = true;
|
||||
entityAnimator.Play(_attackData.StateHash, 0, 0f);
|
||||
entityAnimator.Update(0f);
|
||||
|
||||
entityAnimator.ResetAnimatorPosition();
|
||||
entity.SyncRootMotionToMotor();
|
||||
|
||||
var entityAnimator = entity.animator;
|
||||
entityAnimator.applyRootMotion = true;
|
||||
|
||||
var transitionTime = actionData.TransitionFrames * BattleTimeline.TickDelta;
|
||||
entityAnimator.CrossFadeInFixedTime(_attackData.StateHash, transitionTime);
|
||||
}
|
||||
else if (actionData.CurrentFrame <= actionData.TransitionFrames
|
||||
&& actionData.TransitionFrames > 0)
|
||||
{
|
||||
float t = (float)actionData.CurrentFrame / actionData.TransitionFrames;
|
||||
Quaternion transientRotation = Quaternion.Lerp(
|
||||
entity.KinematicCharacterMotor.TransientRotation,
|
||||
deciderResult.Rotation,
|
||||
t);
|
||||
|
||||
entity.KinematicCharacterMotor.SetRotation(transientRotation);
|
||||
}
|
||||
|
||||
actionData.CurrentFrame++;
|
||||
|
||||
if (actionData.CurrentFrame >= _attackData.HitboxStartFrame
|
||||
if (actionData.CurrentFrame >= _attackData.HitboxStartFrame
|
||||
&& actionData.CurrentFrame <= _attackData.HitboxEndFrame)
|
||||
{
|
||||
entity.EntityHitBoxes.CheckHitsForCurrentFrame(OnEnemyHit, _attackData.AttackingBodyPart);
|
||||
}
|
||||
|
||||
if (actionData.CurrentFrame >= _attackData.TotalFrames)
|
||||
if (actionData.CurrentFrame >= actionData.TotalFrames)
|
||||
{
|
||||
var entityAnimator = entity.animator;
|
||||
|
||||
entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
|
||||
entity.SyncRootMotionToMotor();
|
||||
|
||||
entityAnimator.Play(EntryStateHash, 0, 0f);
|
||||
entityAnimator.Update(0f);
|
||||
|
||||
// TODO С нормальными анимациями не нужен будет рут моушен√
|
||||
entityAnimator.ResetAnimatorPosition();
|
||||
|
||||
return true;
|
||||
battleActionData.Finished = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override DeciderType DeciderType => DeciderType.Position;
|
||||
public override DeciderType DeciderType => DeciderType.Rotation;
|
||||
|
||||
public override BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result)
|
||||
{
|
||||
return new AttackBattleActionData() { TotalFrames = _attackData.TotalFrames };
|
||||
@@ -74,11 +92,11 @@ namespace Code.OfficeGame.BattleMode.Actions.Attacks
|
||||
private void OnEnemyHit(Collider enemyCollider)
|
||||
{
|
||||
var originEntityId = enemyCollider.GetComponentInParent<EntityProperties>().GetEntityId();
|
||||
|
||||
var actionData = new StuggerActionData { TotalFrames = _attackData.StuggerFrames, CurrentFrame = 0};
|
||||
var decisionResult = new AsIsBattleActionDeciderResult {ActionType = BattleActionType.Stugger};
|
||||
|
||||
BattleTimeline.ForceDecideAction(originEntityId, actionData, decisionResult);
|
||||
|
||||
var actionData = new StuggerActionData { TotalFrames = _attackData.StuggerFrames, CurrentFrame = 0 };
|
||||
var decisionResult = new AsIsBattleActionDeciderResult { ActionType = BattleActionType.Stugger };
|
||||
|
||||
battleTimeline.ForceDecideAction(originEntityId, actionData, decisionResult);
|
||||
|
||||
_particleService.EmitOneShot(
|
||||
new ParticlePartitionIDPair(ParticlePartition.HitParticles, 0),
|
||||
|
||||
@@ -5,12 +5,12 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public abstract class BattleAction
|
||||
{
|
||||
public abstract bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult, BattleActionData battleActionData);
|
||||
public abstract void Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult, BattleActionData battleActionData);
|
||||
public abstract BattleActionType ActionType { get; }
|
||||
public abstract DeciderType DeciderType { get; }
|
||||
public abstract BattleActionData MountAndGetFrames(EntityProperties entity, BattleActionDecisionResult result);
|
||||
|
||||
protected BattleTimeline BattleTimeline;
|
||||
public void SetBattleTimeline(BattleTimeline battleTimeline) => BattleTimeline = battleTimeline;
|
||||
protected BattleTimeline battleTimeline;
|
||||
public void SetBattleTimeline(BattleTimeline battleTimeline) => this.battleTimeline = battleTimeline;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,50 @@
|
||||
using System.Threading;
|
||||
using Code.Game.Gameplay.Player;
|
||||
using Code.Infrastructure.Camera;
|
||||
using Code.Infrastructure.Input;
|
||||
using Code.OfficeGame.BattleMode.Actions.Deciders;
|
||||
using Code.OfficeGame.BattleMode.Actions.Player;
|
||||
using Code.OfficeGame.BattleMode.Marker;
|
||||
using Code.OfficeGame.BattleMode.Timeline;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using KinematicCharacterController;
|
||||
using Reflex.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public class BattleActionMove : BattleAction
|
||||
{
|
||||
private const float StoppingDistance = 0.15f;
|
||||
private const float RotateDistance = 1f;
|
||||
|
||||
public override bool Tick(
|
||||
private int MoveStateHash => Animator.StringToHash("WalkBlend");
|
||||
|
||||
public override void Tick(
|
||||
float tickDelta,
|
||||
EntityProperties entity,
|
||||
BattleActionDecisionResult battleActionDecisionResult,
|
||||
BattleActionData battleActionData)
|
||||
{
|
||||
var movementActionData = (MovementBattleActionData) battleActionData;
|
||||
|
||||
if(battleActionData is not MovementBattleActionData movementActionData
|
||||
|| battleActionDecisionResult is not PositionBattleActionDeciderResult positionDecisionResult)
|
||||
{
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var entityMovementController = entity.MovementController;
|
||||
|
||||
if (movementActionData.Corners == null || movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length)
|
||||
{
|
||||
StopMovementAndFinish(entityMovementController);
|
||||
return true;
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!movementActionData.WasStarted)
|
||||
{
|
||||
movementActionData.WasStarted = true;
|
||||
|
||||
float transitionDurationSeconds = 10 * BattleTimeline.TickDelta;
|
||||
|
||||
entity.animator.CrossFadeInFixedTime(MoveStateHash, transitionDurationSeconds);
|
||||
}
|
||||
|
||||
Vector3 currentPos = entity.KinematicCharacterMotor.TransientPosition;
|
||||
|
||||
while (movementActionData.CurrentCornerIndex < movementActionData.Corners.Length)
|
||||
@@ -56,32 +66,42 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
KinematicCharacterSystem.CharacterMotors,
|
||||
KinematicCharacterSystem.PhysicsMovers
|
||||
);
|
||||
return true;
|
||||
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Vector3 targetCorner = movementActionData.Corners[movementActionData.CurrentCornerIndex];
|
||||
|
||||
Vector3 moveDirection = targetCorner - currentPos;
|
||||
moveDirection.y = 0f;
|
||||
|
||||
float distance = moveDirection.magnitude;
|
||||
Vector3 moveNormal = distance > 0.0001f ? moveDirection / distance : Vector3.zero;
|
||||
|
||||
|
||||
bool preLastPoint = movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1
|
||||
&& distance < RotateDistance;
|
||||
|
||||
float maxStep = entityMovementController.MaxStableMoveSpeed * tickDelta;
|
||||
float inputMagnitude =
|
||||
(movementActionData.CurrentCornerIndex == movementActionData.Corners.Length - 1
|
||||
&& distance < maxStep
|
||||
&& maxStep > 0f)
|
||||
? distance / maxStep
|
||||
: 1f;
|
||||
? distance / maxStep
|
||||
: 1f;
|
||||
|
||||
Quaternion lookRotation;
|
||||
|
||||
if(preLastPoint) lookRotation = positionDecisionResult.Rotation;
|
||||
else lookRotation = moveNormal.sqrMagnitude > 0.0001f
|
||||
? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp)
|
||||
: entityMovementController.Motor.TransientRotation;
|
||||
|
||||
// TODO Для последнего кадра должен быть особый Look совпадающий с маркером
|
||||
var aiInputs = new AICharacterInputs
|
||||
{
|
||||
MoveVector = moveNormal * inputMagnitude,
|
||||
Look = moveNormal.sqrMagnitude > 0.0001f
|
||||
? Quaternion.LookRotation(moveNormal, entityMovementController.Motor.CharacterUp)
|
||||
: entityMovementController.Motor.TransientRotation
|
||||
Look = lookRotation
|
||||
};
|
||||
|
||||
entityMovementController.SetInputs(ref aiInputs);
|
||||
@@ -99,11 +119,10 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
if (movementActionData.CurrentCornerIndex >= movementActionData.Corners.Length - 1
|
||||
&& toFinal.sqrMagnitude <= StoppingDistance * StoppingDistance)
|
||||
{
|
||||
entity.KinematicCharacterMotor.SetRotation(positionDecisionResult.Rotation);
|
||||
StopMovementAndFinish(entityMovementController);
|
||||
return true;
|
||||
battleActionData.Finished = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void StopMovementAndFinish(MovementController movementController)
|
||||
|
||||
@@ -4,10 +4,10 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public class BattleActionNone : BattleAction
|
||||
{
|
||||
public override bool Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult,
|
||||
public override void Tick(float tickDelta, EntityProperties entity, BattleActionDecisionResult battleActionDecisionResult,
|
||||
BattleActionData battleActionData)
|
||||
{
|
||||
return true;
|
||||
battleActionData.Finished = true;
|
||||
}
|
||||
|
||||
public override BattleActionType ActionType => BattleActionType.None;
|
||||
|
||||
@@ -14,5 +14,6 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
public int StateHash => Animator.StringToHash(AnimationClip.name);
|
||||
|
||||
public int TotalFrames;
|
||||
public int TransitionFrames;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using Code.Infrastructure.Utils;
|
||||
using Code.OfficeGame.BattleMode.Actions.Deciders;
|
||||
using Code.OfficeGame.BattleMode.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
@@ -24,27 +25,30 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
return new StuggerActionData { TotalFrames = _actionParameters.TotalFrames};
|
||||
}
|
||||
|
||||
public override bool Tick(float tickDelta,
|
||||
public override void Tick(float tickDelta,
|
||||
EntityProperties entity,
|
||||
BattleActionDecisionResult battleActionDecisionResult,
|
||||
BattleActionData battleActionData)
|
||||
{
|
||||
if (battleActionData is not StuggerActionData stuggerActionData) return true;
|
||||
if (battleActionData is not StuggerActionData stuggerActionData)
|
||||
{
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stuggerActionData.CurrentFrame == 0)
|
||||
{
|
||||
entity.animator.Play(StuggerStateHash, 0, 0f);
|
||||
var transitionTime = stuggerActionData.TransitionFrames * BattleTimeline.TickDelta;
|
||||
entity.animator.ResetAnimatorPosition();
|
||||
entity.animator.CrossFadeInFixedTime(StuggerStateHash, transitionTime);
|
||||
}
|
||||
|
||||
stuggerActionData.CurrentFrame++;
|
||||
|
||||
if (stuggerActionData.CurrentFrame >= _actionParameters.TotalFrames)
|
||||
{
|
||||
entity.animator.Play(EntryStateHash, 0, 0f);
|
||||
return true;
|
||||
battleActionData.Finished = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Code.Infrastructure.Utils;
|
||||
using Code.OfficeGame.BattleMode.Actions.Deciders;
|
||||
using Code.OfficeGame.BattleMode.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
@@ -8,12 +9,16 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
private int EntryStateHash => Animator.StringToHash("WalkBlend");
|
||||
|
||||
public override bool Tick(float tickDelta,
|
||||
public override void Tick(float tickDelta,
|
||||
EntityProperties entity,
|
||||
BattleActionDecisionResult battleActionDecisionResult,
|
||||
BattleActionData battleActionData)
|
||||
{
|
||||
if (battleActionData is not WaitBattleActionData waitActionData) return true;
|
||||
if (battleActionData is not WaitBattleActionData waitActionData)
|
||||
{
|
||||
battleActionData.Finished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!waitActionData.StartedWaiting)
|
||||
{
|
||||
@@ -21,17 +26,22 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
|
||||
var entityAnimator = entity.animator;
|
||||
|
||||
entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
|
||||
entityAnimator.Update(0f);
|
||||
float transitionDurationSeconds = 30 * BattleTimeline.TickDelta;
|
||||
|
||||
entityAnimator.CrossFadeInFixedTime(EntryStateHash, transitionDurationSeconds);
|
||||
|
||||
// entityAnimator.Play(EntryStateHash, 0, 0f); // Создать None State и кинуть эту штуку туда
|
||||
// entityAnimator.Update(0f);
|
||||
|
||||
// TODO С нормальными анимациями не нужен будет рут моушен√
|
||||
entityAnimator.ResetAnimatorPosition();
|
||||
}
|
||||
|
||||
waitActionData.WaitFrames--;
|
||||
if (waitActionData.WaitFrames <= 0) return true;
|
||||
|
||||
return false;
|
||||
if (waitActionData.WaitFrames <= 0)
|
||||
{
|
||||
battleActionData.Finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override BattleActionType ActionType => BattleActionType.Wait;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
public override void UnmountDecider()
|
||||
{ }
|
||||
|
||||
public override void MountDecider()
|
||||
public override void MountDecider(EntityProperties entityProperties)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,6 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
|
||||
public abstract void UnmountDecider();
|
||||
|
||||
public abstract void MountDecider();
|
||||
public abstract void MountDecider(EntityProperties entityProperties);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,28 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
ResultCode = deciderResult.ResultCode;
|
||||
}
|
||||
}
|
||||
public class RotationBattleActionDeciderResult : BattleActionDecisionResult
|
||||
{
|
||||
public Quaternion Rotation;
|
||||
|
||||
public override BattleActionDecisionResult GetSnapshot()
|
||||
{
|
||||
return new RotationBattleActionDeciderResult
|
||||
{
|
||||
Rotation = Rotation,
|
||||
ActionType = ActionType,
|
||||
ResultCode = ResultCode
|
||||
};
|
||||
}
|
||||
|
||||
public override void RestoreSnapshot(BattleActionDecisionResult snapshot)
|
||||
{
|
||||
if (snapshot is not RotationBattleActionDeciderResult deciderResult) return;
|
||||
Rotation = deciderResult.Rotation;
|
||||
ActionType = deciderResult.ActionType;
|
||||
ResultCode = deciderResult.ResultCode;
|
||||
}
|
||||
}
|
||||
public class FrameBattleActionDeciderResult : BattleActionDecisionResult
|
||||
{
|
||||
public int FrameCount;
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
private IInputService _inputService;
|
||||
|
||||
private Vector3 _targetPosition;
|
||||
private Quaternion _targetRotation;
|
||||
|
||||
private LayerMask _navMeshMask;
|
||||
|
||||
private const float StoppingDistance = 0.15f;
|
||||
|
||||
public BattleActionPositionDecider(
|
||||
CameraStateMachine cameraStateMachine,
|
||||
IInputService inputService,
|
||||
@@ -51,7 +51,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
return new PositionBattleActionDeciderResult()
|
||||
{
|
||||
ResultCode = _decisionResultCode,
|
||||
Position = _targetPosition
|
||||
Position = _targetPosition,
|
||||
Rotation = _targetRotation
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,6 +64,10 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
|
||||
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
|
||||
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
|
||||
ActivateRotationChange);
|
||||
_inputService.UnbindActionCancelled((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
|
||||
DeactivateRotationChange);
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
|
||||
SelectPosition);
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
|
||||
@@ -75,10 +80,24 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
IsMounted = false;
|
||||
}
|
||||
|
||||
public override void MountDecider()
|
||||
private bool _rotationChanging = false;
|
||||
private void ActivateRotationChange(InputAction.CallbackContext obj)
|
||||
{
|
||||
_rotationChanging = true;
|
||||
}
|
||||
private void DeactivateRotationChange(InputAction.CallbackContext obj)
|
||||
{
|
||||
_rotationChanging = false;
|
||||
}
|
||||
|
||||
public override void MountDecider(EntityProperties entityProperties)
|
||||
{
|
||||
if (IsMounted) return;
|
||||
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
|
||||
ActivateRotationChange);
|
||||
_inputService.BindActionCancelled((int)ActionMappings.BattleMode, (int)BattleModeActions.RightClick,
|
||||
DeactivateRotationChange);
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
|
||||
SelectPosition);
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
|
||||
@@ -92,6 +111,8 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
_playerDecisionMarker = _markerManager.GetMarkerInstance();
|
||||
_playerDecisionMarker.Toggle(true);
|
||||
|
||||
_targetRotation = Quaternion.identity;
|
||||
|
||||
IsMounted = true;
|
||||
}
|
||||
|
||||
@@ -111,7 +132,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
}
|
||||
|
||||
private RaycastHit[] _hitBuffer = new RaycastHit[1];
|
||||
private Vector3 _lastViewedPosition;
|
||||
private Vector3 _lastViewedPosition = Vector3.zero;
|
||||
private PlayerDecisionMarker _playerDecisionMarker;
|
||||
private PlayerDecisionMarker _playerLockedDecisionMarker;
|
||||
|
||||
@@ -125,10 +146,23 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
if (hitCount == 0) return;
|
||||
|
||||
var hit = _hitBuffer[0];
|
||||
|
||||
_playerDecisionMarker.MoveMarker(hit.point);
|
||||
if(!_rotationChanging)
|
||||
{
|
||||
_playerDecisionMarker.MoveMarker(hit.point);
|
||||
|
||||
_lastViewedPosition = hit.point;
|
||||
_lastViewedPosition = hit.point;
|
||||
}
|
||||
else if(_lastViewedPosition != Vector3.zero)
|
||||
{
|
||||
var lookDirection = hit.point - _lastViewedPosition;
|
||||
|
||||
lookDirection.y = 0;
|
||||
|
||||
_targetRotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up);
|
||||
|
||||
_playerDecisionMarker.transform.rotation = _targetRotation;
|
||||
_playerLockedDecisionMarker.transform.rotation = _targetRotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectPosition(InputAction.CallbackContext input)
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
public enum DeciderType
|
||||
{
|
||||
AsIs,
|
||||
Position
|
||||
Position,
|
||||
Rotation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Code.Infrastructure.Camera;
|
||||
using Code.Infrastructure.Input;
|
||||
using Code.OfficeGame.BattleMode.Actions.Player;
|
||||
using Code.OfficeGame.BattleMode.Marker;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions.Deciders
|
||||
{
|
||||
public class RotationBattleActionDecider: BattleActionDecider
|
||||
{
|
||||
private CameraStateMachine _cameraStateMachine;
|
||||
private IInputService _inputService;
|
||||
|
||||
private Quaternion _targetRotation;
|
||||
|
||||
private LayerMask _navMeshMask;
|
||||
|
||||
public RotationBattleActionDecider(
|
||||
CameraStateMachine cameraStateMachine,
|
||||
IInputService inputService,
|
||||
MarkerManager markerManager)
|
||||
{
|
||||
_markerManager = markerManager;
|
||||
_cameraStateMachine = cameraStateMachine;
|
||||
_inputService = inputService;
|
||||
_navMeshMask = LayerMask.GetMask("Environment");
|
||||
}
|
||||
|
||||
private CancellationTokenSource _waitForEnterTokenSource;
|
||||
private BattleActionDecisionResultCode _decisionResultCode;
|
||||
|
||||
public override DeciderType DeciderType => DeciderType.Rotation;
|
||||
|
||||
public override async UniTask<BattleActionDecisionResult> GetDecision()
|
||||
{
|
||||
if (!IsMounted) throw new Exception("Decider is not mounted"); // А почему бы и нет?
|
||||
|
||||
_waitForEnterTokenSource = new CancellationTokenSource();
|
||||
_decisionResultCode = BattleActionDecisionResultCode.Deciding;
|
||||
|
||||
await UniTask.WaitUntilCanceled(_waitForEnterTokenSource.Token).SuppressCancellationThrow();
|
||||
|
||||
_waitForEnterTokenSource.Dispose();
|
||||
_waitForEnterTokenSource = null;
|
||||
|
||||
return new RotationBattleActionDeciderResult()
|
||||
{
|
||||
ResultCode = _decisionResultCode,
|
||||
Rotation = _targetRotation
|
||||
};
|
||||
}
|
||||
|
||||
public override void UnmountDecider()
|
||||
{
|
||||
if (!IsMounted) return;
|
||||
|
||||
_playerDecisionMarker.Toggle(false);
|
||||
_markerManager.ReleaseMarkerInstance(_playerDecisionMarker);
|
||||
_playerLockedDecisionMarker.Toggle(false);
|
||||
_markerManager.ReleaseMarkerInstance(_playerLockedDecisionMarker);
|
||||
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
|
||||
SelectRotation);
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
|
||||
ConfirmRotation);
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
|
||||
CancelRotation);
|
||||
_inputService.UnbindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
|
||||
MarkPosition);
|
||||
|
||||
IsMounted = false;
|
||||
}
|
||||
|
||||
public override void MountDecider(EntityProperties entityProperties)
|
||||
{
|
||||
if (IsMounted) return;
|
||||
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Select,
|
||||
SelectRotation);
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Confirm,
|
||||
ConfirmRotation);
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Cancel,
|
||||
CancelRotation);
|
||||
_inputService.BindActionPerformed((int)ActionMappings.BattleMode, (int)BattleModeActions.Watchment,
|
||||
MarkPosition);
|
||||
|
||||
|
||||
_playerLockedDecisionMarker = _markerManager.GetMarkerInstance();
|
||||
|
||||
_playerDecisionMarker = _markerManager.GetMarkerInstance();
|
||||
_playerDecisionMarker.Toggle(true);
|
||||
|
||||
_targetRotation = entityProperties.KinematicCharacterMotor.TransientRotation;
|
||||
_entityPosition = entityProperties.KinematicCharacterMotor.TransientPosition;
|
||||
|
||||
IsMounted = true;
|
||||
}
|
||||
|
||||
private MarkerManager _markerManager;
|
||||
|
||||
private void CancelRotation(InputAction.CallbackContext obj)
|
||||
{
|
||||
_decisionResultCode = BattleActionDecisionResultCode.CancelDecision;
|
||||
_waitForEnterTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private void ConfirmRotation(InputAction.CallbackContext input)
|
||||
{
|
||||
_decisionResultCode = BattleActionDecisionResultCode.Decided;
|
||||
|
||||
_waitForEnterTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private RaycastHit[] _hitBuffer = new RaycastHit[1];
|
||||
private PlayerDecisionMarker _playerDecisionMarker;
|
||||
private PlayerDecisionMarker _playerLockedDecisionMarker;
|
||||
private Vector3 _entityPosition;
|
||||
|
||||
private void MarkPosition(InputAction.CallbackContext input)
|
||||
{
|
||||
var position = _cameraStateMachine.Camera.transform.position;
|
||||
var direction = _cameraStateMachine.Camera.transform.forward;
|
||||
|
||||
var hitCount = Physics.RaycastNonAlloc(position, direction, _hitBuffer, Mathf.Infinity, _navMeshMask);
|
||||
|
||||
if (hitCount == 0
|
||||
|| _entityPosition == Vector3.zero) return;
|
||||
|
||||
var hit = _hitBuffer[0];
|
||||
|
||||
var lookDirection = hit.point - _entityPosition;
|
||||
_targetRotation = Quaternion.LookRotation(lookDirection.normalized, Vector3.up);
|
||||
|
||||
_playerDecisionMarker.transform.rotation = _targetRotation;
|
||||
_playerDecisionMarker.transform.position = hit.point;
|
||||
}
|
||||
|
||||
private void SelectRotation(InputAction.CallbackContext input)
|
||||
{
|
||||
_playerLockedDecisionMarker.MoveMarker(_entityPosition);
|
||||
_playerLockedDecisionMarker.transform.rotation = _targetRotation;
|
||||
_playerLockedDecisionMarker.Toggle(true);
|
||||
|
||||
_waitForEnterTokenSource?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 741d431fb1944bd48206d1410ee3f098
|
||||
timeCreated: 1793890803
|
||||
@@ -1,38 +1,37 @@
|
||||
using System;
|
||||
using Code.Infrastructure.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.Actions
|
||||
{
|
||||
public struct EntitySnapshot
|
||||
{
|
||||
// Transforms
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
|
||||
// Animator
|
||||
AnimatorSnapshot AnimatorSnapshot;
|
||||
public AnimatorSnapshot AnimatorSnapshot;
|
||||
|
||||
public static EntitySnapshot GetSnapshot(EntityProperties entityProperties)
|
||||
{
|
||||
EntitySnapshot snapshot = new EntitySnapshot()
|
||||
return new EntitySnapshot
|
||||
{
|
||||
Position = entityProperties.KinematicCharacterMotor.TransientPosition,
|
||||
Rotation = entityProperties.KinematicCharacterMotor.TransientRotation,
|
||||
AnimatorSnapshot = entityProperties.animator.GetSnapshot()
|
||||
AnimatorSnapshot = AnimatorSnapshot.GetSnapshot(entityProperties.animator)
|
||||
};
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public static void RestoreSnapshot(EntityProperties entityProperties, EntitySnapshot snapshot)
|
||||
{
|
||||
entityProperties.animator.RestoreSnapshot(snapshot.AnimatorSnapshot);
|
||||
|
||||
// 1. Возвращаем капсулу KCC
|
||||
entityProperties.KinematicCharacterMotor.SetPositionAndRotation(snapshot.Position, snapshot.Rotation);
|
||||
entityProperties.KinematicCharacterMotor.BaseVelocity = Vector3.zero;
|
||||
|
||||
// 2. Возвращаем позу и локальное смещение меша
|
||||
snapshot.AnimatorSnapshot.RestoreTo(entityProperties.animator);
|
||||
|
||||
// 3. Синхронизируем физику
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct AnimatorSnapshot
|
||||
{
|
||||
public struct LayerStateSnapshot
|
||||
@@ -53,14 +52,10 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
|
||||
private LayerStateSnapshot[] _layers;
|
||||
private ParameterSnapshot[] _parameters;
|
||||
|
||||
private Vector3 _localPosition;
|
||||
private Quaternion _localRotation;
|
||||
private float _speed;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт новый экземпляр снапшота с текущим состоянием аниматора
|
||||
/// </summary>
|
||||
public static AnimatorSnapshot GetSnapshot(Animator animator)
|
||||
{
|
||||
var snapshot = new AnimatorSnapshot();
|
||||
@@ -68,32 +63,22 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Записывает текущее состояние аниматора в этот снапшот (без лишних аллокаций при повторном вызове)
|
||||
/// </summary>
|
||||
public void CaptureFrom(Animator animator)
|
||||
{
|
||||
if (animator == null) return;
|
||||
|
||||
// 1. Сохраняем локальный трансформ модели (защита от смещения Root Motion)
|
||||
Transform animTransform = animator.transform;
|
||||
_localPosition = animTransform.localPosition;
|
||||
_localRotation = animTransform.localRotation;
|
||||
_speed = animator.speed;
|
||||
|
||||
// 2. Сохраняем состояния и кадры всех слоёв
|
||||
int layerCount = animator.layerCount;
|
||||
if (_layers == null || _layers.Length != layerCount)
|
||||
{
|
||||
_layers = new LayerStateSnapshot[layerCount];
|
||||
}
|
||||
|
||||
for (int i = 0; i < layerCount; i++)
|
||||
{
|
||||
// Если аниматор находился в процессе перехода (Transition),
|
||||
// можно при необходимости брать GetNextAnimatorStateInfo(i), но для покадрового Play обычно используется текущий:
|
||||
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(i);
|
||||
|
||||
_layers[i] = new LayerStateSnapshot
|
||||
{
|
||||
FullPathHash = stateInfo.fullPathHash,
|
||||
@@ -102,23 +87,15 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Сохраняем значения всех параметров
|
||||
int paramCount = animator.parameterCount;
|
||||
if (_parameters == null || _parameters.Length != paramCount)
|
||||
{
|
||||
_parameters = new ParameterSnapshot[paramCount];
|
||||
}
|
||||
|
||||
for (int i = 0; i < paramCount; i++)
|
||||
{
|
||||
AnimatorControllerParameter param = animator.GetParameter(i);
|
||||
int hash = param.nameHash;
|
||||
|
||||
var paramSnapshot = new ParameterSnapshot
|
||||
{
|
||||
NameHash = hash,
|
||||
Type = param.type
|
||||
};
|
||||
var paramSnapshot = new ParameterSnapshot { NameHash = hash, Type = param.type };
|
||||
|
||||
switch (param.type)
|
||||
{
|
||||
@@ -133,31 +110,26 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
paramSnapshot.BoolValue = animator.GetBool(hash);
|
||||
break;
|
||||
}
|
||||
|
||||
_parameters[i] = paramSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Восстанавливает состояние в целевой аниматор (оригинал или клон-призрак)
|
||||
/// </summary>
|
||||
public void RestoreTo(Animator targetAnimator)
|
||||
{
|
||||
if (targetAnimator == null || _layers == null) return;
|
||||
|
||||
// 1. Временно выключаем Root Motion, чтобы Update(0f) не сдвинул модель повторно
|
||||
targetAnimator.Rebind();
|
||||
|
||||
// Отключаем applyRootMotion на время Update(0f), чтобы Play() не начислил дельту смещения
|
||||
bool prevRootMotion = targetAnimator.applyRootMotion;
|
||||
targetAnimator.applyRootMotion = false;
|
||||
targetAnimator.speed = _speed;
|
||||
|
||||
// 2. Восстанавливаем параметры
|
||||
if (_parameters != null)
|
||||
{
|
||||
for (int i = 0; i < _parameters.Length; i++)
|
||||
{
|
||||
var p = _parameters[i];
|
||||
|
||||
// Пропускаем параметры, которые управляются кривыми внутри самой анимации
|
||||
if (targetAnimator.IsParameterControlledByCurve(p.NameHash))
|
||||
continue;
|
||||
|
||||
@@ -180,7 +152,6 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Восстанавливаем стейты и точный кадр (NormalizedTime) на каждом слое
|
||||
int layerCount = Mathf.Min(_layers.Length, targetAnimator.layerCount);
|
||||
for (int i = 0; i < layerCount; i++)
|
||||
{
|
||||
@@ -189,43 +160,14 @@ namespace Code.OfficeGame.BattleMode.Actions
|
||||
targetAnimator.Play(layer.FullPathHash, i, layer.NormalizedTime);
|
||||
}
|
||||
|
||||
// 4. Принудительно пересчитываем кости скелета в текущем кадре
|
||||
targetAnimator.Update(0f);
|
||||
|
||||
// 5. Возвращаем локальное положение модели и настройки Root Motion
|
||||
// ОБЯЗАТЕЛЬНО восстанавливаем сохраненные локальные координаты меша после Update(0f)!
|
||||
Transform targetTransform = targetAnimator.transform;
|
||||
targetTransform.localPosition = _localPosition;
|
||||
targetTransform.localRotation = _localRotation;
|
||||
|
||||
targetAnimator.applyRootMotion = prevRootMotion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт глубокую копию самого снапшота
|
||||
/// </summary>
|
||||
public AnimatorSnapshot Clone()
|
||||
{
|
||||
var clone = new AnimatorSnapshot
|
||||
{
|
||||
_localPosition = _localPosition,
|
||||
_localRotation = _localRotation,
|
||||
_speed = _speed
|
||||
};
|
||||
|
||||
if (_layers != null)
|
||||
{
|
||||
clone._layers = new LayerStateSnapshot[_layers.Length];
|
||||
Array.Copy(_layers, clone._layers, _layers.Length);
|
||||
}
|
||||
|
||||
if (_parameters != null)
|
||||
{
|
||||
clone._parameters = new ParameterSnapshot[_parameters.Length];
|
||||
Array.Copy(_parameters, clone._parameters, _parameters.Length);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,6 +110,7 @@ namespace Code.Game.OfficeGame.GameStates
|
||||
builder.RegisterType(typeof(BattleActionStugger), new[]{ typeof(BattleAction) }, Lifetime.Singleton, Resolution.Eager);
|
||||
|
||||
builder.RegisterType(typeof(BattleActionPositionDecider), new[]{ typeof(BattleActionDecider) } , Lifetime.Singleton, Resolution.Eager);
|
||||
builder.RegisterType(typeof(RotationBattleActionDecider), new[]{ typeof(BattleActionDecider) } , Lifetime.Singleton, Resolution.Eager);
|
||||
builder.RegisterType(typeof(AsIsBattleActionDecider), new[]{ typeof(BattleActionDecider) } , Lifetime.Singleton, Resolution.Eager);
|
||||
|
||||
builder.RegisterType(typeof(BattleTimeline), Lifetime.Singleton, Resolution.Eager);
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
public ReadOnlyReactiveProperty<int> CurrentBattleFrame => _currentBattleFrame;
|
||||
|
||||
private readonly BattleTimelinePanel _timelinePanel;
|
||||
private List<EntityProperties> _entities;
|
||||
private DisposableBag _battleSubscriptions;
|
||||
|
||||
private Dictionary<BattleActionType, BattleAction> _battleActions;
|
||||
private Dictionary<DeciderType, BattleActionDecider> _battleActionDeciders;
|
||||
|
||||
private Dictionary<EntityId, BattleActionDecisionResult> _actionDecisionResults = new();
|
||||
private Dictionary<EntityId, EntityProperties> _entities;
|
||||
private Dictionary<EntityId, BattleActionData> _battleActionDatas = new();
|
||||
private Dictionary<EntityId, BattleActionDecisionResult> _actionDecisionResults = new();
|
||||
|
||||
private BattleActionDecisionPanel _battleActionDecisionPanel;
|
||||
private CameraStateMachine _cameraStateMachine;
|
||||
@@ -52,13 +52,13 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
battleAction.SetBattleTimeline(this);
|
||||
_battleActions.Add(battleAction.ActionType, battleAction);
|
||||
}
|
||||
|
||||
|
||||
_battleActionDeciders = battleActionDeciders.ToDictionary(x => x.DeciderType);
|
||||
}
|
||||
|
||||
public async UniTask RunBattleLoopAsync(List<EntityProperties> entities, CancellationToken ctx)
|
||||
{
|
||||
_entities = entities;
|
||||
_entities = entities.ToDictionary(x => x.GetEntityId());
|
||||
_currentBattleFrame.Value = 0;
|
||||
|
||||
_battleSubscriptions = new DisposableBag();
|
||||
@@ -69,20 +69,11 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
.Subscribe(_timelinePanel.OnFrameTick)
|
||||
.AddTo(ref _battleSubscriptions);
|
||||
|
||||
// foreach (var entity in entities)
|
||||
// entity.OnActionSet
|
||||
// .Subscribe(action =>
|
||||
// {
|
||||
// action.Frame += _currentBattleFrame.Value;
|
||||
// _timelinePanel.SetAction(action);
|
||||
// })
|
||||
// .AddTo(ref _battleSubscriptions);
|
||||
|
||||
await DecideActions(_entities, ctx);
|
||||
await DecideActions(_entities.Keys.ToArray(), ctx);
|
||||
|
||||
while (!ctx.IsCancellationRequested)
|
||||
{
|
||||
List<EntityProperties> entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
|
||||
EntityId[] entitiesNeedOrders = await PlayTimelineUntilPause(ctx);
|
||||
|
||||
await DecideActions(entitiesNeedOrders, ctx);
|
||||
}
|
||||
@@ -93,12 +84,11 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<List<EntityProperties>> PlayTimelineUntilPause(CancellationToken ctx)
|
||||
private async UniTask<EntityId[]> PlayTimelineUntilPause(CancellationToken ctx)
|
||||
{
|
||||
float accumulator = 0f;
|
||||
var readyEntities = new List<EntityProperties>();
|
||||
|
||||
while (readyEntities.Count == 0 && !ctx.IsCancellationRequested)
|
||||
while (!ctx.IsCancellationRequested)
|
||||
{
|
||||
await UniTask.Yield(PlayerLoopTiming.Update, ctx);
|
||||
|
||||
@@ -110,80 +100,86 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
|
||||
_currentBattleFrame.Value++;
|
||||
|
||||
SimulateSingleFrame(readyEntities, _entities);
|
||||
var readyEntities = SimulateSingleFrame(_entities);
|
||||
|
||||
if (readyEntities.Count > 0) return readyEntities;
|
||||
if (readyEntities.Length > 0) return readyEntities;
|
||||
}
|
||||
}
|
||||
|
||||
return readyEntities;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async UniTask PreviewBattleLoopAsync(int frameCount,
|
||||
List<EntityProperties> entities,
|
||||
private async UniTask PreviewBattleLoopAsync(int frameCount,
|
||||
Dictionary<EntityId, EntityProperties> entities,
|
||||
CancellationToken ctx)
|
||||
{
|
||||
while (!ctx.IsCancellationRequested)
|
||||
Dictionary<EntityId, BattleActionData> actionDataSnapshots = new();
|
||||
Dictionary<EntityId, BattleActionDecisionResult> resultsSnapshots = new();
|
||||
Dictionary<EntityId, EntitySnapshot> entitySnapshots = new();
|
||||
|
||||
foreach (var (entityId, entity) in entities)
|
||||
{
|
||||
Dictionary<EntityId, BattleActionData> actionDataSnapshots = new();
|
||||
Dictionary<EntityId, BattleActionDecisionResult> resultsSnapshots = new();
|
||||
Dictionary<EntityId, EntitySnapshot> entitySnapshots = new();
|
||||
if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult)
|
||||
|| !_battleActionDatas.TryGetValue(entityId, out var battleActionData))
|
||||
continue;
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
var entityId = entity.GetEntityId();
|
||||
resultsSnapshots[entityId] = actionDecisionResult.GetSnapshot();
|
||||
actionDataSnapshots[entityId] = battleActionData.GetSnapshot();
|
||||
entitySnapshots[entityId] = entity.GetSnapshot();
|
||||
}
|
||||
|
||||
if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult)
|
||||
|| !_battleActionDatas.TryGetValue(entityId, out var battleActionData))
|
||||
continue;
|
||||
|
||||
resultsSnapshots[entityId] = actionDecisionResult.GetSnapshot();
|
||||
actionDataSnapshots[entityId] = battleActionData.GetSnapshot();
|
||||
entitySnapshots[entityId] = entity.GetSnapshot();
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
while (!ctx.IsCancellationRequested)
|
||||
{
|
||||
RestoreFromSnapshots(entities, resultsSnapshots, actionDataSnapshots, entitySnapshots);
|
||||
|
||||
await PlayTimelineForCertainFrames(frameCount, ctx).AttachExternalCancellation(ctx);
|
||||
}
|
||||
finally
|
||||
}
|
||||
finally
|
||||
{
|
||||
RestoreFromSnapshots(entities, resultsSnapshots, actionDataSnapshots, entitySnapshots);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreFromSnapshots(Dictionary<EntityId, EntityProperties> entities,
|
||||
Dictionary<EntityId, BattleActionDecisionResult> resultsSnapshots,
|
||||
Dictionary<EntityId, BattleActionData> actionDataSnapshots,
|
||||
Dictionary<EntityId, EntitySnapshot> entitySnapshots)
|
||||
{
|
||||
foreach (var (entityId, entity) in entities)
|
||||
{
|
||||
if (!resultsSnapshots.TryGetValue(entityId, out var actionDecisionResult)
|
||||
|| !actionDataSnapshots.TryGetValue(entityId, out var battleActionData))
|
||||
continue;
|
||||
|
||||
bool actionChanged = false;
|
||||
|
||||
if (!_actionDecisionResults.TryAdd(entityId, actionDecisionResult))
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
var entityId = entity.GetEntityId();
|
||||
actionChanged = actionDecisionResult.ActionType
|
||||
!= _actionDecisionResults[entityId].ActionType;
|
||||
|
||||
if (!resultsSnapshots.TryGetValue(entityId, out var actionDecisionResult)
|
||||
|| !actionDataSnapshots.TryGetValue(entityId, out var battleActionData))
|
||||
continue;
|
||||
|
||||
bool actionChanged = false;
|
||||
|
||||
if (!_actionDecisionResults.TryAdd(entityId, actionDecisionResult))
|
||||
{
|
||||
actionChanged = actionDecisionResult != _actionDecisionResults[entityId];
|
||||
|
||||
if(actionChanged)
|
||||
_actionDecisionResults[entityId] = actionDecisionResult;
|
||||
else
|
||||
_actionDecisionResults[entityId]?.RestoreSnapshot(actionDecisionResult);
|
||||
}
|
||||
|
||||
if (!_battleActionDatas.TryAdd(entityId, battleActionData))
|
||||
{
|
||||
if(actionChanged) _battleActionDatas[entityId] = battleActionData;
|
||||
else _battleActionDatas[entityId]?.RestoreSnapshot(battleActionData);
|
||||
}
|
||||
|
||||
entity.RestoreSnapshot(entitySnapshots[entityId]);
|
||||
}
|
||||
if (actionChanged)
|
||||
_actionDecisionResults[entityId] = actionDecisionResult;
|
||||
else
|
||||
_actionDecisionResults[entityId]?.RestoreSnapshot(actionDecisionResult);
|
||||
}
|
||||
|
||||
if (!_battleActionDatas.TryAdd(entityId, battleActionData))
|
||||
{
|
||||
if (actionChanged) _battleActionDatas[entityId] = battleActionData;
|
||||
else _battleActionDatas[entityId]?.RestoreSnapshot(battleActionData);
|
||||
}
|
||||
|
||||
entity.RestoreSnapshot(entitySnapshots[entityId]);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask PlayTimelineForCertainFrames(int frameCount, CancellationToken ctx)
|
||||
{
|
||||
float accumulator = 0f;
|
||||
var readyEntities = new List<EntityProperties>();
|
||||
|
||||
while (frameCount > 0 && !ctx.IsCancellationRequested)
|
||||
{
|
||||
@@ -197,14 +193,12 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
|
||||
frameCount--;
|
||||
|
||||
SimulateSingleFrame(readyEntities, _entities);
|
||||
var readyEntities = SimulateSingleFrame(_entities);
|
||||
|
||||
if (readyEntities.Count > 0)
|
||||
if (readyEntities.Length > 0)
|
||||
{
|
||||
foreach (var entity in readyEntities)
|
||||
foreach (var entityID in readyEntities)
|
||||
{
|
||||
var entityID = entity.GetEntityId();
|
||||
|
||||
_battleActionDatas.Remove(entityID);
|
||||
|
||||
_actionDecisionResults.Remove(entityID);
|
||||
@@ -214,44 +208,42 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
private void SimulateSingleFrame(List<EntityProperties> outReadyEntities, List<EntityProperties> inEntities)
|
||||
private EntityId[] SimulateSingleFrame(Dictionary<EntityId, EntityProperties> inEntities)
|
||||
{
|
||||
foreach (var entity in inEntities)
|
||||
{
|
||||
var entityId = entity.GetEntityId();
|
||||
var entityId = entity.Key;
|
||||
|
||||
if (!_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult)
|
||||
|| !_battleActionDatas.TryGetValue(entityId, out var battleActionData))
|
||||
continue;
|
||||
|
||||
var finished = _battleActions[actionDecisionResult.ActionType]
|
||||
_battleActions[actionDecisionResult.ActionType]
|
||||
.Tick(
|
||||
TickDelta,
|
||||
entity,
|
||||
entity.Value,
|
||||
actionDecisionResult,
|
||||
battleActionData);
|
||||
|
||||
entity.animator.Update(TickDelta);
|
||||
|
||||
if (finished)
|
||||
{
|
||||
outReadyEntities.Add(entity);
|
||||
}
|
||||
entity.Value.animator.Update(TickDelta);
|
||||
}
|
||||
|
||||
Physics.Simulate(TickDelta);
|
||||
Physics.SyncTransforms();
|
||||
|
||||
var readyEntities = _battleActionDatas
|
||||
.Where(x => x.Value.Finished)
|
||||
.Select(x => x.Key)
|
||||
.ToArray();
|
||||
|
||||
return readyEntities;
|
||||
}
|
||||
|
||||
private async UniTask DecideActions(List<EntityProperties> entities, CancellationToken ctx)
|
||||
private async UniTask DecideActions(EntityId[] entities, CancellationToken ctx)
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
foreach (var entityId in entities)
|
||||
{
|
||||
Debug.Log($"Deciding action for {entity.name}");
|
||||
|
||||
var entityId = entity.GetEntityId();
|
||||
|
||||
var (decisionResult, battleActionData) = await DecideAction(ctx, entity);
|
||||
var (decisionResult, battleActionData) = await DecideAction(ctx, _entities[entityId]);
|
||||
|
||||
_actionDecisionResults[entityId] = decisionResult;
|
||||
_battleActionDatas[entityId] = battleActionData;
|
||||
@@ -278,7 +270,10 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
entityActionDataSnapshot = battleActionData.GetSnapshot();
|
||||
|
||||
if (_actionDecisionResults.TryGetValue(entityId, out var actionDecisionResult))
|
||||
{
|
||||
entityResultSnapshot = actionDecisionResult.GetSnapshot();
|
||||
_timelinePanel.SetPreviousActionText(entityResultSnapshot.ActionType.ToString());
|
||||
}
|
||||
|
||||
while (decidedActionType == BattleActionType.None || ctx.IsCancellationRequested)
|
||||
{
|
||||
@@ -294,7 +289,7 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
|
||||
var deciderType = _battleActions[decidedActionType].DeciderType;
|
||||
|
||||
_battleActionDeciders[deciderType].MountDecider();
|
||||
_battleActionDeciders[deciderType].MountDecider(entity);
|
||||
|
||||
CancellationTokenSource previewCancellationToken = new();
|
||||
|
||||
@@ -330,7 +325,6 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
break;
|
||||
}
|
||||
else break;
|
||||
|
||||
}
|
||||
|
||||
_battleActionDeciders[deciderType].UnmountDecider();
|
||||
@@ -351,13 +345,12 @@ namespace Code.OfficeGame.BattleMode.Timeline
|
||||
|
||||
public void ForceDecideAction(
|
||||
EntityId entityId,
|
||||
BattleActionData forcedBattleActionData,
|
||||
BattleActionData forcedBattleActionData,
|
||||
BattleActionDecisionResult forcedBattleActionDataResult)
|
||||
{
|
||||
|
||||
_battleActionDatas[entityId] = forcedBattleActionData;
|
||||
_actionDecisionResults[entityId] = forcedBattleActionDataResult;
|
||||
|
||||
|
||||
Debug.Log($"Задано действие {forcedBattleActionDataResult.ActionType} для {entityId}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Code.OfficeGame.BattleMode.Timeline;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using LitMotion;
|
||||
using LitMotion.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.BattleMode.UI
|
||||
@@ -15,6 +16,9 @@ namespace Code.OfficeGame.BattleMode.UI
|
||||
[SerializeField] private GameObject timelineKeyPrefab;
|
||||
[SerializeField] private float duration = 0.35f;
|
||||
[SerializeField] private RectTransform timelineItself;
|
||||
[SerializeField] private TMP_Text prevAction;
|
||||
|
||||
public void SetPreviousActionText(string text) => prevAction.text = text;
|
||||
|
||||
// TODO Вырезать или пощадить
|
||||
private readonly Dictionary<EntityId, int> _entityEndFrames = new();
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using KinematicCharacterController;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Code.OfficeGame.Player
|
||||
{
|
||||
public class EntityKCCAnimatorSync : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Animator animator;
|
||||
[SerializeField] private KinematicCharacterMotor motor;
|
||||
|
||||
private void OnAnimatorMove()
|
||||
{
|
||||
// motor.MoveCharacter(motor.TransientPosition + animator.deltaPosition);
|
||||
// motor.SetRotation(motor.TransientRotation * animator.deltaRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b74f4c8fe25411f9660cb939149108a
|
||||
timeCreated: 1793897947
|
||||
Reference in new issue
Block a user