Compare commits

..

3 Commits

Author SHA1 Message Date
zephyr 5667884f75 Merge branch 'main' into feature-enemy-behavior
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-19 15:13:12 -05:00
Branden J Brown ec2ede72ed simplify runtime set access
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-16 21:54:48 -05:00
Branden J Brown db7559cd16 initial work on enemy behavior
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-08-16 15:17:32 -05:00
2 changed files with 176 additions and 4 deletions

View File

@ -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)
{
// 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;
}
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;
}
} }
} }

View File

@ -37,5 +37,7 @@ namespace RuntimeSet
{ {
return GetEnumerator(); return GetEnumerator();
} }
public T this[int index] { get { return items[index]; } }
} }
} }