For my college game project, I have a Unity 3D third person shooter game where I plan to modularize all the attacks of all enemies units (and the player, after I figure it out). I planned on each attack being initialize-able in the prefab for each type of enemy, since each enemy in the game will be “pre-spawned” into the game. Currently, I want to know if I approached this the right way, or if there are better, more efficient ways to do this, before I finish the logic and debug it.
I created a serializable parent Attack() class with a virtual DoAttack() method, so I can create children classes that can inherit it and implement different behaviors inside the DoAttack() method depending on the type of attack. I currently have three children classes that override the DoAttack() method.
[System.Serializable]
public class Attack
{
public string Name;
public float AttackScaling;
public int StartupTime;
public int ActiveTime;
public int RecoveryTime;
public float MovementForce;
public float InterruptValue;
public virtual void DoAttack()
{
}
}
public class HitboxAttack : Attack
{
public Collider attackCollider;
public override void DoAttack()
{
//wait in StartupTime seconds
//activate attackCollider for ActiveTime seconds
//wait in RecoveryTime seconds
}
}
public class ProjectileAttack : Attack
{
public GameObject projectile;
public float verticalForce;
public float horizontalForce;
public override void DoAttack()
{
//wait in StartupTime seconds
//instantiate projectile using verticalForce and horizontalForce
//destroy projectile after hit or after ActiveTime seconds
//wait in RecoveryTime seconds
}
}
public class CollisionAttack : Attack
{
public Collider unitCollider;
public float CollisionSpeed;
public override void DoAttack()
{
//wait in StartupTime seconds
//charge at player at CollisionSpeed speed using collider for ActiveTime seconds
//wait in RecoverTime seconds
}
}
I also created an AttackSequence() class that serves as a container for each attack, mainly to accommodate attacks that are composed of multiple hits. It has a List of Attack() objects and a method that just iterates through the list and executes each DoAttack() in Attack().
[System.Serializable]
public class AttackSequence
{
public float ActivationRange;
public List<Attack> Sequence = new();
public void DoSequence()
{
//iterate through each attack in Sequence and call DoAttack()
}
}
Finally, each enemy unit has an EnemyUnit script that contains a List of AttackSequence() objects, with some logic about how each AttackSequence() object is chosen. (I only included the relevant code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class EnemyUnit : MonoBehaviour
{
[SerializeField] private Transform Player;
public NavMeshAgent Agent;
public List<AttackSequence> Attacks;
[Header("Settings")]
public LayerMask PlayerLayer;
public float SightRange;
private float AttackRange;
public float SafeRange;
private AttackSequence ActiveAttackSequence;
private AttackSequence QueuedAttack;
[SerializeField] private bool IsPlayerInSight;
[SerializeField] private bool IsPlayerInAttackRange;
[SerializeField] private bool IsSafeToAttack;
[SerializeField] private bool IsAttacking;
private void Update()
{
if (IsAttacking) return;
IsPlayerInSight = Physics.CheckSphere(transform.position, SightRange, PlayerLayer);
IsSafeToAttack = !Physics.CheckSphere(transform.position, SafeRange, PlayerLayer);
if (IsPlayerInSight)
{
transform.LookAt(Player);
if (IsSafeToAttack)
{
QueuedAttack = ChooseAttack();
Debug.Log(QueuedAttack);
if (QueuedAttack != null)
{
AttackRange = QueuedAttack.ActivationRange; //this is for gizmo debugging
DoAttackSequence(QueuedAttack);
}
else Agent.SetDestination(Player.position);
}
else
{
Agent.SetDestination(Player.position);
Agent.speed = -Agent.speed;
}
}
else Agent.SetDestination(transform.position);
}
private AttackSequence ChooseAttack()
{
List<AttackSequence> DoableAttacks = Attacks.FindAll(attack => Physics.CheckSphere(transform.position, attack.ActivationRange, PlayerLayer));
DoableAttacks.Sort((a,b) => a.ActivationRange.CompareTo(b.ActivationRange));
return DoableAttacks.Count > 0 ? DoableAttacks[0] : null;
}
private void DoAttackSequence(AttackSequence attack)
{
IsAttacking = true;
ActiveAttackSequence = attack;
//call DoSequence() in attack
IsAttacking = false;
}
}
COLE SAMUEL BALAJADIA is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.