Skip to content

Commit

Permalink
Fire animation event for combat system to consume
Browse files Browse the repository at this point in the history
Fire animation event for combat system to consume + code refactoring
  • Loading branch information
0x7c13 committed Sep 19, 2024
1 parent 85d2e15 commit 927d62f
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ RenderTexture:
m_ImageContentsHash:
serializedVersion: 2
Hash: 00000000000000000000000000000000
m_ForcedFallbackFormat: 4
m_DownscaleFallback: 0
m_IsAlphaChannelOptional: 0
serializedVersion: 5
serializedVersion: 6
m_Width: 800
m_Height: 448
m_AntiAliasing: 1
Expand All @@ -24,6 +22,7 @@ RenderTexture:
m_GenerateMips: 1
m_SRGB: 0
m_UseDynamicScale: 0
m_UseDynamicScaleExplicit: 0
m_BindMS: 0
m_EnableCompatibleFormat: 1
m_EnableRandomWrite: 0
Expand Down
2 changes: 1 addition & 1 deletion Assets/Scripts/Engine/Services/TextureCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void Add(string key, ITexture2D texture, bool hasAlphaChannel)
}
else
{
_textureCache[key] = new (texture, hasAlphaChannel);
_textureCache[key] = (texture, hasAlphaChannel);
}
}

Expand Down
3 changes: 1 addition & 2 deletions Assets/Scripts/Pal3.Game/Actor/ActorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ public string GetMovementAction(MovementMode mode)
// TODO: Get weapon based on inventory context
public string GetTagObjectName()
{
return ActorConstants.MainActorWeaponMap.TryGetValue(Name, out var value) ?
value : null;
return ActorConstants.MainActorWeaponMap.GetValueOrDefault(Name);
}

public bool HasAction(string actionName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Pal3.Game.Actor.Controllers
using Data;
using Engine.Core.Abstraction;
using Engine.Core.Implementation;
using Engine.Coroutine;
using Engine.Extensions;
using Engine.Logging;
using Engine.Renderer;
Expand Down Expand Up @@ -96,6 +97,13 @@ public void PerformAction(ActorActionType actorActionType,
PerformAction(ActorConstants.ActionToNameMap[actorActionType], overwrite, loopCount, waiter);
}

/// <summary>
/// Start playing the specified action.
/// </summary>
/// <param name="actionName">Name of the action to play.</param>
/// <param name="overwrite">Whether to overwrite the current action.</param>
/// <param name="loopCount">How many times to play the action. -1 for infinite loop. -2 for playing until the holding point.</param>
/// <param name="waiter">Waiter to wait until the action is done.</param>
public virtual void PerformAction(string actionName,
bool overwrite = false,
int loopCount = -1,
Expand All @@ -115,6 +123,16 @@ public virtual void PerformAction(string actionName,
}
}

/// <summary>
/// Perform one-time action and wait until it's done.
/// </summary>
public IEnumerator PerformActionAsync(string actionName)
{
WaitUntilCanceled waiter = new();
PerformAction(actionName, overwrite: true, loopCount: 1, waiter);
yield return CoroutineYieldInstruction.WaitUntil(() => !waiter.ShouldWait());
}

public abstract void PauseAnimation();

public abstract float GetActorHeight();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace Pal3.Game.Actor.Controllers

public sealed class CombatActorController : GameEntityScript
{
private const string COMBAT_ANIMATION_ATTACK_EVENT_NAME_PREFIX = "work";

private CombatActor _actor;
private ActorActionController _actionController;
private ElementPosition _elementPosition;
Expand All @@ -44,6 +46,14 @@ public void Init(CombatActor actor,
_elementPosition = elementPosition;
}

private void AnimationEventTriggered(object sender, string eventName)
{
if (eventName.StartsWith(COMBAT_ANIMATION_ATTACK_EVENT_NAME_PREFIX, StringComparison.Ordinal))
{
// TODO: Impl attack behavior
}
}

public ElementPosition GetElementPosition()
{
return _elementPosition;
Expand All @@ -61,28 +71,38 @@ public ActorActionController GetActionController()

private void Activate()
{
if (_actionController is VertexAnimationActorActionController vertexActionController)
{
vertexActionController.AnimationEventTriggered += AnimationEventTriggered;
}

_actionController.PerformAction(_actor.GetPreAttackAction());
}

private void DeActivate()
{
if (_actionController is VertexAnimationActorActionController vertexActionController)
{
vertexActionController.AnimationEventTriggered -= AnimationEventTriggered;
}

_actionController.DeActivate();
}

public IEnumerator StartNormalAttackAsync(CombatActorController combatActorController,
public IEnumerator StartNormalAttackAsync(CombatActorController enemyActorController,
CombatScene combatScene)
{
Quaternion currentRotation = _actionController.Transform.Rotation;

_actionController.PerformAction(_actor.GetMovementAction());

Vector3 enemySize = combatActorController.GetActionController().GetMeshBounds().size;
Vector3 enemySize = enemyActorController.GetActionController().GetMeshBounds().size;
float enemyRadius = MathF.Max(enemySize.x, enemySize.z) / 2f;

Vector3 mySize = _actionController.GetMeshBounds().size;
float myRadius = MathF.Max(mySize.x, mySize.z) / 2f;

Vector3 targetElementPosition = combatScene.GetWorldPosition(combatActorController.GetElementPosition());
Vector3 targetElementPosition = combatScene.GetWorldPosition(enemyActorController.GetElementPosition());
Vector3 myElementPosition = combatScene.GetWorldPosition(GetElementPosition());

targetElementPosition += (myElementPosition - targetElementPosition).normalized * (enemyRadius + myRadius);
Expand All @@ -92,11 +112,8 @@ public IEnumerator StartNormalAttackAsync(CombatActorController combatActorContr

_actionController.Transform.LookAt(targetElementPosition);
yield return Transform.MoveAsync(targetElementPosition, duration);

WaitUntilCanceled waiter = new WaitUntilCanceled();
_actionController.PerformAction(_actor.GetAttackAction(), overwrite: true, loopCount: 1, waiter);

yield return CoroutineYieldInstruction.WaitUntil(() => !waiter.ShouldWait());

yield return _actionController.PerformActionAsync(_actor.GetAttackAction());

_actionController.PerformAction(_actor.GetMovementAction());
_actionController.Transform.LookAt(myElementPosition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public sealed class VertexAnimationActorActionController : ActorActionController
ICommandExecutor<ActorStopActionCommand>,
ICommandExecutor<ActorChangeTextureCommand>
{
public event EventHandler<string> AnimationEventTriggered
{
add => _mv3ModelRenderer.AnimationEventTriggered += value;
remove => _mv3ModelRenderer.AnimationEventTriggered -= value;
}

private GameResourceProvider _resourceProvider;
private IMaterialManager _materialManager;
private ActorBase _actor;
Expand Down
94 changes: 64 additions & 30 deletions Assets/Scripts/Pal3.Game/Rendering/Renderer/Mv3ModelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace Pal3.Game.Rendering.Renderer
public sealed class Mv3ModelRenderer : GameEntityScript, IDisposable
{
public event EventHandler<int> AnimationLoopPointReached;
public event EventHandler<string> AnimationEventTriggered;

private const string MV3_ANIMATION_HOLD_EVENT_NAME = "hold";
private const string MV3_MODEL_DEFAULT_TEXTURE_EXTENSION = ".tga";
Expand Down Expand Up @@ -62,6 +63,7 @@ public sealed class Mv3ModelRenderer : GameEntityScript, IDisposable
public bool IsInitialized { get; private set; }
private bool _isActionInHoldState;
private uint _actionHoldingTick;
private int _lastCheckedTick = -1; // Track the last checked tick
private CancellationTokenSource _animationCts;

protected override void OnEnableGameEntity()
Expand Down Expand Up @@ -184,7 +186,7 @@ private void InitSubMeshes(int index,
string textureName = material.TextureFileNames[0];

_gbMaterials[index] = material;
_textures[index] = _textureProvider.GetTexture(textureName, out var hasAlphaChannel);
_textures[index] = _textureProvider.GetTexture(textureName, out bool hasAlphaChannel);
_textureHasAlphaChannel[index]= hasAlphaChannel;
_animationName[index] = mv3Mesh.Name;

Expand Down Expand Up @@ -361,46 +363,58 @@ private IEnumerator ResumeActionInternalAsync()
private IEnumerator PlayAnimationInternalAsync(int loopCount,
CancellationToken cancellationToken)
{
uint startTick = 0;
const uint startTick = 0;

if (loopCount == -1) // Infinite loop until cancelled
switch (loopCount)
{
while (!cancellationToken.IsCancellationRequested)
// Infinite loop until cancelled
case -1:
{
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
while (!cancellationToken.IsCancellationRequested)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
}
}

break;
}
}
else if (loopCount > 0)
{
while (!cancellationToken.IsCancellationRequested && --loopCount >= 0)
// Finite loop
case > 0:
{
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
while (!cancellationToken.IsCancellationRequested && --loopCount >= 0)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
if (!cancellationToken.IsCancellationRequested)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
}
}

break;
}
}
else if (loopCount == -2) // Play until action holding point
{
if (TryGetLastHoldingTick(out uint holdingTick))
{
_actionHoldingTick = holdingTick;
yield return PlayOneTimeAnimationInternalAsync(startTick, _actionHoldingTick, cancellationToken);
_isActionInHoldState = true;
}
else
// Play until action holding point
case -2:
{
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
}
if (TryGetLastHoldingTick(out uint holdingTick))
{
_actionHoldingTick = holdingTick;
yield return PlayOneTimeAnimationInternalAsync(startTick, _actionHoldingTick, cancellationToken);
_isActionInHoldState = true;
}
else
{
yield return PlayOneTimeAnimationInternalAsync(startTick, _totalGameBoxTicks, cancellationToken);
}

if (!cancellationToken.IsCancellationRequested)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
if (!cancellationToken.IsCancellationRequested)
{
AnimationLoopPointReached?.Invoke(this, loopCount);
}

break;
}
}
}
Expand All @@ -421,16 +435,36 @@ private bool TryGetLastHoldingTick(out uint holdingTick)
return false;
}

private void CheckForAnimationEvents(uint currentTick)
{
foreach (Mv3AnimationEvent animationEvent in _events)
{
if (animationEvent.GameBoxTick > _lastCheckedTick && animationEvent.GameBoxTick <= currentTick)
{
AnimationEventTriggered?.Invoke(this, animationEvent.Name);
break;
}
}

_lastCheckedTick = (int)currentTick;
}

private IEnumerator PlayOneTimeAnimationInternalAsync(uint startTick,
uint endTick,
CancellationToken cancellationToken)
{
double startTime = GameTimeProvider.Instance.TimeSinceStartup;
_lastCheckedTick = -1;

CheckForAnimationEvents(startTick); // In case the startTick is an event tick

while (!cancellationToken.IsCancellationRequested)
{
uint tick = ((float)(GameTimeProvider.Instance.TimeSinceStartup - startTime)).SecondsToGameBoxTick() + startTick;

// Trigger animation events
CheckForAnimationEvents(tick);

if (tick >= endTick)
{
yield break;
Expand Down Expand Up @@ -527,7 +561,7 @@ public void Dispose()
{
foreach (RenderMeshComponent renderMeshComponent in _renderMeshComponents)
{
var materials = renderMeshComponent.MeshRenderer.GetMaterials();
IMaterial[] materials = renderMeshComponent.MeshRenderer.GetMaterials();
if (materials != null)
{
#if PAL3A
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,27 @@ private void SetupAnimationTrack(MovFile movFile)
private IEnumerator PlayAnimationInternalAsync(int loopCount,
CancellationToken cancellationToken)
{
if (loopCount == -1) // Infinite loop until cancelled
switch (loopCount)
{
while (!cancellationToken.IsCancellationRequested)
// Infinite loop until cancelled
case -1:
{
yield return PlayOneTimeAnimationInternalAsync(cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
yield return PlayOneTimeAnimationInternalAsync(cancellationToken);
}

break;
}
}
else if (loopCount > 0)
{
while (!cancellationToken.IsCancellationRequested && --loopCount >= 0)
// Finite loop
case > 0:
{
yield return PlayOneTimeAnimationInternalAsync(cancellationToken);
while (!cancellationToken.IsCancellationRequested && --loopCount >= 0)
{
yield return PlayOneTimeAnimationInternalAsync(cancellationToken);
}

break;
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions Assets/Scripts/Pal3.Game/Scene/Scene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,7 @@ public void Execute(SceneActivateObjectCommand command)

public void Execute(ActorActivateCommand command)
{
if (!_actorEntities.ContainsKey(command.ActorId)) return;
IGameEntity actorGameEntity = _actorEntities[command.ActorId];
if (!_actorEntities.TryGetValue(command.ActorId, out IGameEntity actorGameEntity)) return;
actorGameEntity.GetComponent<ActorController>().IsActive = command.IsActive == 1;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ public override bool IsDirectlyInteractable(float distance)

public override IEnumerator InteractAsync(InteractionContext ctx)
{
if (!_deskIdToRequiredItemIdMap.ContainsKey(ObjectInfo.Id)) yield break;
if (!_deskIdToRequiredItemIdMap.TryGetValue(ObjectInfo.Id, out int requiredItemId)) yield break;

if (_inventoryManager.HaveItem(_deskIdToRequiredItemIdMap[ObjectInfo.Id]))
if (_inventoryManager.HaveItem(requiredItemId))
{
if (!IsInteractableBasedOnTimesCount()) yield break;

Expand Down
Loading

0 comments on commit 927d62f

Please sign in to comment.