Compare commits
No commits in common. "ce56b6d829291817feea3940fc75031e027e24d1" and "dc9184075b30b71e8cdc45af6f095129ac308232" have entirely different histories.
ce56b6d829
...
dc9184075b
3 changed files with 34 additions and 60 deletions
|
|
@ -14,5 +14,6 @@ MonoBehaviour:
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.EnemyAbilities.BlinkAbilityDefinition
|
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.EnemyAbilities.BlinkAbilityDefinition
|
||||||
DisplayName: Blink
|
DisplayName: Blink
|
||||||
Description: Enemies occasionally teleport a short distance ahead.
|
Description: Enemies occasionally teleport a short distance ahead.
|
||||||
IntervalSeconds: 8
|
IntervalSeconds: 4
|
||||||
BlinkDistanceMeters: 4
|
BlinkWaypoints: 2
|
||||||
|
StartJitterSeconds: 1.5
|
||||||
|
|
|
||||||
|
|
@ -25,22 +25,26 @@ namespace TD.Gameplay.EnemyAbilities
|
||||||
[Header("Blink")]
|
[Header("Blink")]
|
||||||
[Tooltip("Seconds between blinks.")]
|
[Tooltip("Seconds between blinks.")]
|
||||||
[Min(0.1f)]
|
[Min(0.1f)]
|
||||||
public float IntervalSeconds = 8f;
|
public float IntervalSeconds = 4f;
|
||||||
|
|
||||||
[Tooltip("World units to advance along the path per blink.")]
|
[Tooltip("How many path waypoints to skip per blink. Note that path smoothing can make " +
|
||||||
[Min(0.1f)]
|
"one waypoint span several tiles, so this is a coarser dial than it looks.")]
|
||||||
public float BlinkDistanceMeters = 4f;
|
[Min(1)]
|
||||||
|
public int BlinkWaypoints = 2;
|
||||||
|
|
||||||
|
[Tooltip("Random spread (seconds) added to each enemy's first blink so a wave doesn't " +
|
||||||
|
"blink in one synchronized block. Applied once, at spawn.")]
|
||||||
|
[Min(0f)]
|
||||||
|
public float StartJitterSeconds = 1.5f;
|
||||||
|
|
||||||
public override void ServerOnSpawn(EnemyAbility instance, int abilityIndex)
|
public override void ServerOnSpawn(EnemyAbility instance, int abilityIndex)
|
||||||
{
|
{
|
||||||
// Seed each enemy's cooldown with a random point within the full interval, as if it
|
// Seed each enemy's cooldown with a different negative offset so the wave's first
|
||||||
// spawned already partway through a cooldown. A small jitter window near zero would
|
// blink is staggered. Done once, here, rather than on the first tick — a tick-time
|
||||||
// still let a burst of enemies spawned together sync up and blink as a visible cluster
|
|
||||||
// every cycle; spreading across the whole interval avoids that regardless of how
|
|
||||||
// IntervalSeconds is tuned. Done once, here, rather than on the first tick — a tick-time
|
|
||||||
// check would have to distinguish "never started" from "just blinked", and both
|
// check would have to distinguish "never started" from "just blinked", and both
|
||||||
// states read as a zero timer.
|
// states read as a zero timer.
|
||||||
instance.TimerFor(abilityIndex) = -Random.Range(0f, IntervalSeconds);
|
if (StartJitterSeconds > 0f)
|
||||||
|
instance.TimerFor(abilityIndex) = -Random.Range(0f, StartJitterSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ServerTick(EnemyAbility instance, int abilityIndex, float dt)
|
public override void ServerTick(EnemyAbility instance, int abilityIndex, float dt)
|
||||||
|
|
@ -50,7 +54,7 @@ namespace TD.Gameplay.EnemyAbilities
|
||||||
if (timer < IntervalSeconds) return;
|
if (timer < IntervalSeconds) return;
|
||||||
|
|
||||||
timer = 0f;
|
timer = 0f;
|
||||||
instance.GetComponent<EnemyMovement>()?.ServerBlinkForward(BlinkDistanceMeters);
|
instance.GetComponent<EnemyMovement>()?.ServerBlinkForward(BlinkWaypoints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -286,70 +286,39 @@ namespace TD.Gameplay
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server-only: teleport this enemy forward along its existing path by up to
|
/// Server-only: teleport this enemy forward along its existing path by up to
|
||||||
/// <paramref name="meters"/> world units. Used by the Blink wave buff.
|
/// <paramref name="tiles"/> waypoints. Used by the Blink wave buff.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <b>Follows the path rather than the straight line to the goal.</b> Jumping toward the
|
/// <b>Follows the path rather than the straight line to the goal.</b> Jumping toward the
|
||||||
/// goal as the crow flies would let a grounded enemy blink through walls, which is the
|
/// goal as the crow flies would let a grounded enemy blink through walls, which is the
|
||||||
/// flying buff's job, not this one. Walking the path keeps the enemy inside the maze —
|
/// flying buff's job, not this one. Skipping waypoints keeps the enemy inside the maze —
|
||||||
/// it cuts corners, it doesn't ignore them.
|
/// it cuts corners, it doesn't ignore them.
|
||||||
///
|
///
|
||||||
/// <para>Distance is measured along the path (waypoint to waypoint), consuming the
|
/// <para>Zone tracking still runs on the destination tile, so an enemy that blinks out of
|
||||||
/// budget one segment at a time, so the landing spot can fall mid-segment rather than
|
/// its origin zone still credits its owner a leak. Skipping that would make the buff a way
|
||||||
/// snapping to a waypoint. This keeps the blink distance predictable regardless of how
|
/// to launder leaks past the no-leak bonus.</para>
|
||||||
/// far apart the path's waypoints happen to be — unlike skipping a fixed waypoint count,
|
|
||||||
/// where one blink could span a few tiles or the length of the whole map depending on
|
|
||||||
/// path smoothing.</para>
|
|
||||||
///
|
///
|
||||||
/// <para>Zone tracking runs once, on the final landing tile, so an enemy that blinks out
|
/// <para>Blinking onto the final waypoint resolves as a normal goal arrival, despawn and
|
||||||
/// of its origin zone still credits its owner a leak. Skipping that would make the buff a
|
/// life cost included.</para>
|
||||||
/// way to launder leaks past the no-leak bonus.</para>
|
|
||||||
///
|
|
||||||
/// <para>Blinking onto (or past) the final waypoint resolves as a normal goal arrival,
|
|
||||||
/// despawn and life cost included.</para>
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void ServerBlinkForward(float meters)
|
public void ServerBlinkForward(int tiles)
|
||||||
{
|
{
|
||||||
if (!IsServer || meters <= 0f) return;
|
if (!IsServer || tiles <= 0) return;
|
||||||
if (hasReachedGoal || remainingPath.Count == 0) return;
|
if (hasReachedGoal || remainingPath.Count == 0) return;
|
||||||
if (health != null && health.IsHeld) return;
|
if (health != null && health.IsHeld) return;
|
||||||
|
|
||||||
float remaining = meters;
|
int skip = Mathf.Min(tiles, remainingPath.Count);
|
||||||
Vector3 position = transform.position;
|
Vector2Int destination = remainingPath[skip - 1];
|
||||||
|
remainingPath.RemoveRange(0, skip);
|
||||||
while (remaining > 0f && remainingPath.Count > 0)
|
|
||||||
{
|
|
||||||
Vector3 targetWorld = GridCoordinates.GridToWorld(remainingPath[0]);
|
|
||||||
Vector3 toTarget = targetWorld - position;
|
|
||||||
toTarget.y = 0f;
|
|
||||||
float distance = toTarget.magnitude;
|
|
||||||
|
|
||||||
if (distance <= remaining)
|
|
||||||
{
|
|
||||||
// Reach this waypoint fully and keep walking the leftover budget
|
|
||||||
// into the next segment.
|
|
||||||
remaining -= distance;
|
|
||||||
position = new Vector3(targetWorld.x, position.y, targetWorld.z);
|
|
||||||
remainingPath.RemoveAt(0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Budget runs out partway along this segment.
|
|
||||||
position += toTarget.normalized * remaining;
|
|
||||||
remaining = 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve Y so flyers stay at altitude and grounded pivots don't sink.
|
// Preserve Y so flyers stay at altitude and grounded pivots don't sink.
|
||||||
transform.position = position;
|
Vector3 world = GridCoordinates.GridToWorld(destination);
|
||||||
|
transform.position = new Vector3(world.x, transform.position.y, world.z);
|
||||||
|
|
||||||
|
CheckZoneTransition(destination);
|
||||||
|
|
||||||
if (remainingPath.Count == 0)
|
if (remainingPath.Count == 0)
|
||||||
{
|
|
||||||
HandleGoalReached();
|
HandleGoalReached();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckZoneTransition(GridCoordinates.WorldToGrid(position));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Path invalidation ----------------------------------------------
|
// ----- Path invalidation ----------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue