The state transition is triggered when the timer has run out. In your case, you want to manually trigger the state so a simple solution is to set the stateTimer
to something that will never be reached by the game (like float.MaxValue
) when transitioning to State.Shooting
and then, when you get the signal that the projectile has hit the target, you manually set that to 0 (or -1) and the state will transition to State.Cooloff
Edit
I personally don’t like switch
clauses, and especially state machines running on them, so in my own project I have a different type of state machine. Here is a simple example:
Create a method for each state - note that they all have the same signature (the TODO’s are addressed a little later)
private void AimingState(float deltaTime)
{
Vector3 aimDir = (targetUnit.GetWorldPosition() - unit.GetWorldPosition()).normalized;
float rotateSpeed = 10f;
transform.forward = Vector3.Lerp(transform.forward, aimDir, deltaTime * rotateSpeed);
// Moved this out of Update
stateTimer -= deltaTime;
if (stateTimer <= 0f)
{
float shootingStateTime = 0.1f;
stateTimer = shootingStateTime;
// TODO: Transition to ShootingState
}
}
private void ShootingState(float deltaTime)
{
if (canShootBullet)
{
Shoot(); // target damage has been removed from Shoot()
canShootBullet = false;
}
// Moved this out of Update
stateTimer -= deltaTime;
if (stateTimer <= 0f)
{
float waitingStateTime = 0.1f;
stateTimer = waitingStateTime;
// TODO: Transition to WaitForHitState
}
}
private void WaitForHitState(float deltaTime)
{
// Note that is a 'limbo'. We're just waiting for the projectile to reach the target
}
private void CoolDownState(float deltaTime)
{
ActionComplete();
}
Now, we create a variable to hold the current state
private System.Action<float> currentState;
When the action starts, we’ll set ‘aiming’ as the current state
public override void TakeAction(GridPosition gridPosition, Action onActionComplete)
{
targetUnit = LevelGrid.Instance.GetUnitAtGridPosition(gridPosition);
float aimingStateTime = 1f;
stateTimer = aimingStateTime;
currentState = AimingState;
canShootBullet = true;
ActionStart(onActionComplete);
}
As mentioned earlier, we need to transition states. This is simply done by replacing the current state with another
// TODO: Transition to ShootingState
currentState = ShootingState;
// TODO: Transition to WaitForHitState
currentState = WaitForHitState;
Now we need Update
to run the current action
private void Update()
{
if (!isActive)
{
return;
}
currentAction?.Invoke(Time.deltaTime);
}
And lastly, we need to react when the projectile hits the target. I will go with the assumption that we subscribed to a OnHitTarget
event on the projectile
private void ProjectileOnHitTarget(object sender, EventArgs e)
{
targetUnit.Damage(40);
currentState = CoolDownState;
}
That’s about it. States will transition when their timers run out, but the wait state will only transition when the event is triggered.
We technically don’t need the WaitForHitState
because the ShootingState
won’t do anything after it has run once except countdown its timer, so if you want to go that route, it will look like this
private void ShootingState(float deltaTime)
{
if (canShootBullet)
{
Shoot(); // target damage has been removed from Shoot()
canShootBullet = false;
}
}
No timer countdown here 'cos we will stay here until the bullet hits the target