Here's a look at how I code through an NPC class I wrote for my 2024 gamejam submission 'Dancing Mania'. Players need to perform a combo to recruit NPCs who proceed to circle and dance around the player. Two classes inherit from this; aggressive attackers who chase the player and special NPCs who have dialogue.
public class scr_npc : MonoBehaviour
{
//Variables for performing combos
[SerializeField] protected int comboIndex;
Combo comboVal;
public SpriteRenderer icon;
//Designate the movement modes of the NPC
public enum Movement
{
Following,
Dying,
None
}
private Movement currentMovement = Movement.None;
//Movement variables, a random value is selected at random intervals
[SerializeField, Range(0.1f, 0.25f)]
public float maxSpeed = 0.2f;
[SerializeField, Range(0.01f, 0.1f)]
protected float minSpeed = 0.05f;
[SerializeField] protected float maxVar; //Variable variance!
protected (float, float) var;
protected float currentSpeed;
protected Vector3 target; //Point the NPC is moving towards
int depth;
//References to other objects
protected GameObject p;
protected scr_player player;
protected Transform player_pos;
//References to other components on the NPC
public SpriteRenderer sprite;
public SpriteRenderer shadow;
//Variables used in animating the sprite's bobbing animation
public Transform iconTrans;
public Transform spriteTrans;
protected float aniSpeed;
[SerializeField] protected float pointToReach;
protected float baseX;
protected float baseY;
protected int upOrDown = 1;
//Variables used in animating the sprite's frames
[SerializeField] float maxAniSpeed;
protected int time = 0;
public Animator animator;
//Getters and setters
public int Cv { get => comboIndex; set => comboIndex = value; }
public Movement CurrentMovement { get => currentMovement; set => currentMovement = value; }
void Start()
{
GenerateRecruitCombo();
InitialiseVariables();
}
//Generate a combo needed to recruit this npc within the range of possible combos
//the player will have unlocked at this point
private void GenerateRecruitCombo()
{
if (comboIndex >= 0)
{
comboIndex = Random.Range(0, Cv);
comboVal = db_combo.findCombo(comboIndex);
icon.sprite = comboVal.Image;
//Chance to flip the sprite around
if (Random.Range(0, 2) == 1)
{
transform.localScale = new Vector2(-1, 1);
iconTrans.localScale = new Vector2(-1, 1);
}
}
}
private void InitialiseVariables()
{
//Get player variables
p = GameObject.FindWithTag("player");
player = p.GetComponent<scr_player>();
player_pos = p.GetComponent<Transform>();
//Only the chaser npc needs this
if (GetType() == typeof(scr_chaser_npc))
{
AIDestinationSetter dest = GetComponent<AIDestinationSetter>();
if (dest != null)
{
dest.target = player_pos;
}
}
//Initial variables
baseX = spriteTrans.localPosition.x;
baseY = spriteTrans.localPosition.y;
aniSpeed = maxAniSpeed;
}
void Update()
{
SetMovement();
SetLayerOrder();
}
private void SetMovement()
{
switch (CurrentMovement)
{
case Movement.Dying: break;
case Movement.Following:
target = new Vector3(player_pos.position.x + var.Item1, player_pos.position.y + var.Item2, 0);
if (target == transform.position)
{
UpdateMovement();
}
else
{
transform.position = Vector2.MoveTowards(transform.position, target, currentSpeed);
}
BobSprite();
break;
case Movement.None:
BobSprite();
break;
}
}
//Update the order in layer
private void SetLayerOrder()
{
depth = Mathf.RoundToInt(-shadow.transform.position.y * 100f);
sprite.sortingOrder = depth;
shadow.sortingOrder = depth;
}
//Update the movement by providing a random speed and a random direction
public void UpdateMovement()
{
currentSpeed = Random.Range(minSpeed, maxSpeed);
float inc = player.CurrentIncrease;
var = (Random.Range(-maxVar * inc, maxVar * inc), Random.Range(-maxVar * inc, maxVar * inc));
}
//Triggered after being recruited
public void BeginFollowing()
{
CurrentMovement = Movement.Following;
Destroy(icon);
UpdateMovement();
//Only special NPCs have a dance animation
if (animator != null)
{
animator.enabled = true;
}
}
//Only performs code for the chaser npc inherited class
protected virtual void BobSprite() { }
//Collision properties
//Triggers when this npc enters the players aura, allowing them to be recruited
public void OnTriggerEnter2D(Collider2D collision)
{
if ((collision.isTrigger) && (CurrentMovement == Movement.None))
{
player.addToNPCList(this);
}
}
//Triggers when this npc exits the players aura
public void OnTriggerExit2D(Collider2D collision)
{
if ((collision.isTrigger) && (CurrentMovement == Movement.None))
{
player.removeFromNPCList(this);
}
}
}