WIP: enemy behavior #26
@ -1,11 +1,181 @@
|
|||||||
|
using RuntimeSet;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
|
||||||
namespace DefaultNamespace
|
namespace DefaultNamespace
|
||||||
{
|
{
|
||||||
// TODO (Michael): Empty behavior until we decide more on how enemies should be structured. Mainly being used for
|
|
||||||
// other systems to have something to reference.
|
|
||||||
public class Enemy : MonoBehaviour
|
public class Enemy : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
private enum BehaviorState
|
||||||
|
{
|
||||||
|
Create,
|
||||||
|
Spawning,
|
||||||
|
Chasing,
|
||||||
|
Attacking,
|
||||||
|
Feared,
|
||||||
|
Dead,
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private EnemyAttributesSO attributes;
|
||||||
|
[SerializeField] private EnemyRuntimeSetSO enemySet;
|
||||||
|
[SerializeField] private HeroUnitRuntimeSetSO heroSet;
|
||||||
|
[SerializeField] private GameObject target;
|
||||||
|
// TODO(zeph): Having behavior as a serialized member is convenient for
|
||||||
|
// experimentation early on so we can change it mid-play through the
|
||||||
|
// inspector, but later we'll more likely want it to be a property.
|
||||||
|
[SerializeField] private BehaviorState _behavior = BehaviorState.Spawning;
|
||||||
|
[SerializeField] private float _behaviorTime = 0;
|
||||||
|
[SerializeField] private float _fear = 0;
|
||||||
|
[SerializeField] private long _hp;
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// When the current behavior indicates movement, we do it in Update
|
||||||
|
// to ensure it is smooth at all framerates.
|
||||||
|
if (_behavior != BehaviorState.Chasing && _behavior != BehaviorState.Feared)
|
||||||
|
{
|
||||||
|
// No movement.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
// No target. Find a new one.
|
||||||
|
Retarget();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO(zeph): how do we actually,, get SPD attribute
|
||||||
|
var spd = SpaceSpeed(1000, Time.deltaTime);
|
||||||
|
var to = Vector3.MoveTowards(transform.position, target.transform.position, spd);
|
||||||
|
if (_behavior == BehaviorState.Feared)
|
||||||
|
{
|
||||||
|
// Move away rather than toward.
|
||||||
|
// TODO(zeph): this will limit speed moving away by distance
|
||||||
|
// to target if we're close
|
||||||
|
to = transform.position - 2 * (transform.position - to);
|
||||||
|
}
|
||||||
|
transform.position = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
// We do behavior changes in the fixed update so that enemy actions
|
||||||
|
// do not depend on the framerate. Since movement happens in
|
||||||
|
// Update, the overall effect as framerate varies is that enemies
|
||||||
|
// might change their behaviors at slightly different distances,
|
||||||
|
// but they'll always be in a given state for the same duration.
|
||||||
|
// TODO(zeph): we could probably do this with coroutines instead,
|
||||||
|
// also i still haven't read anything about the actual fsm system
|
||||||
|
_behaviorTime += Time.fixedDeltaTime;
|
||||||
|
float dd;
|
||||||
|
switch (_behavior)
|
||||||
|
{
|
||||||
|
case BehaviorState.Create:
|
||||||
|
// TODO(zeph): calculate starting hp from CON attribute
|
||||||
|
_hp = 1;
|
||||||
|
SetBehavior(BehaviorState.Spawning);
|
||||||
|
break;
|
||||||
|
case BehaviorState.Spawning:
|
||||||
|
// TODO(zeph): move spawn time to an asset
|
||||||
|
if (_behaviorTime >= 0.5)
|
||||||
|
{
|
||||||
|
Retarget();
|
||||||
|
SetBehavior(BehaviorState.Chasing);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BehaviorState.Chasing:
|
||||||
|
dd = SquareDistanceToTarget();
|
||||||
|
// TODO(zeph): RNG attribute
|
||||||
|
if (dd <= SpaceRange(20000))
|
||||||
|
{
|
||||||
|
SetBehavior(BehaviorState.Attacking);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BehaviorState.Attacking:
|
||||||
|
dd = SquareDistanceToTarget();
|
||||||
|
if (dd > SpaceRange(20000))
|
||||||
|
{
|
||||||
|
SetBehavior(BehaviorState.Chasing);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BehaviorState.Feared:
|
||||||
|
if (_behaviorTime > _fear)
|
||||||
|
{
|
||||||
|
SetBehavior(BehaviorState.Chasing);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BehaviorState.Dead:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// TODO(zeph): unreachable
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (_behavior != BehaviorState.Dead && _hp == 0)
|
||||||
|
{
|
||||||
|
SetBehavior(BehaviorState.Dead);
|
||||||
|
// TODO(zeph): put a dead time constant somewhere
|
||||||
|
Destroy(gameObject, 10f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
Assert.IsNotNull(enemySet);
|
||||||
|
enemySet.Add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
enemySet.Remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Retarget()
|
||||||
|
{
|
||||||
|
if (heroSet.IsEmpty)
|
||||||
zephyr marked this conversation as resolved
Outdated
|
|||||||
|
{
|
||||||
|
// No heroes to target.
|
||||||
|
// TODO(zeph): switch to a special behavior?
|
||||||
|
target = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO(zeph): target based on threat, once threat exists
|
||||||
|
var k = Random.Range(0, heroSet.Count);
|
||||||
|
target = heroSet[k].gameObject;
|
||||||
zephyr marked this conversation as resolved
Outdated
madxmike
commented
We probably just want to implement https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/using-indexers on RuntimeSet We probably just want to implement https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/using-indexers on RuntimeSet
|
|||||||
|
}
|
||||||
|
|
||||||
|
private void SetBehavior(BehaviorState behavior)
|
||||||
|
{
|
||||||
|
_behavior = behavior;
|
||||||
|
_behaviorTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float SquareDistanceToTarget()
|
||||||
|
{
|
||||||
|
return Vector3.Magnitude(transform.position - target.transform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Fear(float dur)
|
||||||
|
{
|
||||||
|
// If the enemy is already feared for a longer duration, don't
|
||||||
|
// shorten it.
|
||||||
|
if (_behavior == BehaviorState.Feared && _fear - _behaviorTime >= dur)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_fear = dur;
|
||||||
|
SetBehavior(BehaviorState.Feared);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float SpaceSpeed(long spd, float dt)
|
||||||
|
{
|
||||||
|
// For now, treat SPD as units of ten thousandths of a unit per second.
|
||||||
|
return (spd / 10000) * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float SpaceRange(long rng)
|
||||||
|
{
|
||||||
|
// Treat RNG as units of ten thousandths of a sqrt-meter.
|
||||||
|
return rng / 10000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,5 +37,7 @@ IEnumerator IEnumerable.GetEnumerator()
|
|||||||
{
|
{
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T this[int index] { get { return items[index]; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user
heroSet.IsEmpty