I have 15 or so items currently in the game, but plan to have hundreds. Each item is a scriptable object. Each weapon item is mapped to a particular attack in my Attacks.cs class based on its name and a bunch of IF statements, and each attack follows the same basic pattern. That is, spawn attack (missile, ball etc), place on cooldown, reset cooldownBoolean after attack speed so that attacks can’t be spammed. This can be seen below:
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine.EventSystems;
using UnityEngine;
public class Attacks : MonoBehaviour
{
public EventSystem Event;
public GameObject spawnEgg;
private int maxSpawnEggs = 3;
public List<GameObject> spawnEggs = new List<GameObject>();
private float spawnEggAS = 0.1f;
private bool spawnEggCD = true;
public GameObject fireExplosion;
private float explosionAS = 0.5f;
private bool explosionOffCD = true;
public GameObject player;
public float explosionRange;
public AudioManager AudioManager;
public GameObject MagicBall;
public GameObject Karma;
// bool for karma cooldown
private bool karmaOffCD = true;
// Karma attack speed
private float karmaAS = 3f;
public GameObject bomb;
private bool bombOffCD = true;
private float bombAS = 0.5f;
public GameObject missileSeeker;
private bool missileSeekerOffCD = true;
private float missileSeekerAS = 0.5f;
public GameObject missileHoming;
private bool missileHomingOffCD = true;
private float missileHomingAS = 0.5f;
public GameObject LightningBall;
private bool lightningBallOffCD = true;
private float lightningBallAS = 2f;
private Vector3[] spawnLocationsMagicBall;
public int magicBallSpawnAmount;
public GameObject ElectricBall;
private bool ElectricBallOffCD = true;
private float ElectricBallAS = 0.5f;
// Delay between each bullet of a burst is fired
private float ElectricBallDelay = 0.05f;
private int ElectricBallSpawnAmount = 5;
public float electricBallForce;
public float blockSpawnDistace;
public Equipment selected;
public Equipment selectedOff;
public GameObject Wand_Stream_Pellet;
private float pelletForce = 100f;
public delegate void AttackMethod();
public AttackMethod attackFunction;
public AttackMethod attackFunctionOff;
public GameObject Zombie;
private int maxZombies = 3;
public List <GameObject> currentZombies = new List <GameObject>();
private bool zombieSpawnOffCD = true;
private float zombieAS = 0.3f;
private float zombieLifetime = 20f;
[SerializeField] GameObject clusterBomb;
bool clusterBombOffCD = true;
float clusterBombAS = 0.5f;
[SerializeField] GameObject sniperBlast;
bool sniperBlastOffCD = true;
float sniperBlastAS = 2f;
[SerializeField] float sniperBlastForce = 100f;
[SerializeField] GameObject arcaneBlast;
public bool arcaneBlastOffCD = true;
float arcaneBlastAS = 4f;
void Start()
{
// Locations start at top of character going clockwise
spawnLocationsMagicBall = new Vector3[8];
spawnLocationsMagicBall[0] = new Vector3(0f, 1f, 0);
spawnLocationsMagicBall[1] = new Vector3(0.5f, 0.5f, 0);
spawnLocationsMagicBall[2] = new Vector3(1f, 0f, 0);
spawnLocationsMagicBall[3] = new Vector3(0.5f, -0.5f, 0);
spawnLocationsMagicBall[4] = new Vector3(0f, -1f, 0);
spawnLocationsMagicBall[5] = new Vector3(-0.5f, -0.5f, 0);
spawnLocationsMagicBall[6] = new Vector3(-1f, 0f, 0);
spawnLocationsMagicBall[7] = new Vector3(-0.5f, 0.5f, 0);
magicBallSpawnAmount = 8;
electricBallForce = 70f;
player = gameObject;
explosionRange = 5f;
AudioManager = AudioManager.instance;
karmaAS = 1f;
attackFunction = attackSelection(selected);
attackFunctionOff = attackSelection(selectedOff);
Event = GameObject.Find("EventSystem").GetComponent<EventSystem>();
}
public void updateSelection(){
selected = GetComponent<PlayerHotBar>().selected;
selectedOff = GetComponent<PlayerHotBar>().selectedOff;
attackFunction = attackSelection(selected);
attackFunctionOff = attackSelection(selectedOff);
}
void Update(){
try
{
if (Input.GetButton("Main") && !EventSystem.current.IsPointerOverGameObject()){
attackFunction();
}
if (Input.GetButton("Alt") && !EventSystem.current.IsPointerOverGameObject()){
attackFunctionOff();
}
}
catch
{
Debug.Log("No attack selected");
}
if (Input.GetKeyDown(KeyCode.Q)){
spawnMagicBall();
}
if(currentZombies.Count > 0){
currentZombies.RemoveAll(GameObject => GameObject == null);
}
}
AttackMethod attackSelection(Equipment Equipped){
if(Equipped != null){
if(Equipped.isMelee){
return Melee;
}
if(Equipped.name == "Wand_Stream"){
return Electric_Ball;
}
else if(Equipped.name == "Launcher"){
return missile_Homing;
}
else if(Equipped.name == "Staff_Lightning"){
return Lightning_Ball;
}
else if(Equipped.name == "Staff_Explosion"){
return Explosion;
}
else if (Equipped.name == "Staff_Holy"){
return karma;
}
else if (Equipped.name == "Staff_Vine"){
return ArcaneBlast;
}
else if (Equipped.name == "Staff_Undead"){
return ZombieAlly;
}
else if(Equipped.name == "Launcher_Seeker"){
return missile_Seeker;
}
else if(Equipped.name == "SpawnEgg"){
return throwEgg;
}
else if(Equipped.name == "Staff_Sword"){
return clusterBombFire;
}
}
return doNothing;
}
void doNothing(){
}
void Melee(){
}
void ArcaneBlast(){
if(arcaneBlastOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(arcaneBlast,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * 300f);
arcaneBlastOffCD = false;
WaitAndDo(arcaneBlastAS, () => arcaneBlastOffCD = true);
}
}
void SniperBlast(){
if(sniperBlastOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(sniperBlast,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * sniperBlastForce);
sniperBlastOffCD = false;
WaitAndDo(sniperBlastAS, () => sniperBlastOffCD = true);
}
}
void throwEgg(){
if(spawnEggs.Count < maxSpawnEggs && spawnEggCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(spawnEgg,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points object at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * 500f);
spawnEggs.Add(objectInstance);
spawnEggCD = false;
WaitAndDo(spawnEggAS, () => spawnEggCD = true);
}
}
void clusterBombFire(){
if(clusterBombOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(clusterBomb,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * 500f);
clusterBombOffCD = false;
WaitAndDo(clusterBombAS, () => clusterBombOffCD = true);
}
}
void bombLaunch(){
if(bombOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(bomb,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * 500f);
bombOffCD = false;
WaitAndDo(bombAS, () => bombOffCD = true);
}
}
void missile_Seeker(){
if(missileSeekerOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(missileSeeker,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
missileSeekerOffCD = false;
WaitAndDo(missileSeekerAS, () => missileSeekerOffCD = true);
}
}
void missile_Homing(){
if(missileHomingOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(missileHoming,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
// Points missile at mouse pos
float rot_z = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
objectInstance.transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
missileHomingOffCD = false;
WaitAndDo(missileHomingAS, () => missileHomingOffCD = true);
}
}
void Lightning_Ball(){
if(lightningBallOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(LightningBall,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * electricBallForce/3);
lightningBallOffCD = false;
WaitAndDo(lightningBallAS, () => lightningBallOffCD = true);
}
}
void ZombieAlly(){
if(zombieSpawnOffCD == true && currentZombies.Count < maxZombies){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
Vector3 spawnPos = UnityEngine.Camera.main.ScreenToWorldPoint(Input.mousePosition);
spawnPos.z = 0;
GameObject objectInstance = Instantiate(Zombie,
spawnPos, Quaternion.Euler(new Vector3(0, 0, 0)));
objectInstance.GetComponent<Enemy>().setAllegiance("Ally");
objectInstance.GetComponent<Enemy>().Invoke("allyDeath", zombieLifetime);
currentZombies.Add(objectInstance);
zombieSpawnOffCD = false;
WaitAndDo(zombieAS, () => zombieSpawnOffCD = true);
}
}
void Wand_Stream(){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
var randX = UnityEngine.Random.Range(-.5f, .5f);
var randY = UnityEngine.Random.Range(-.5f, .5f);
Vector3 Pos = new Vector3(transform.position.x + randX, transform.position.y + randY, 0);
GameObject objectInstance = Instantiate(Wand_Stream_Pellet,
Pos, Quaternion.identity);
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * pelletForce);
}
void karma(){
if(karmaOffCD == true){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(Karma,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * electricBallForce);
karmaOffCD = false;
// Invoke("karmaCDReset", karmaAS);
WaitAndDo(karmaAS, () => karmaOffCD = true);
}
}
void Electric_Ball(){
if(ElectricBallOffCD == true){
for (int i = 0; i < ElectricBallSpawnAmount; i++)
{
Invoke("spawnElectricBall", ElectricBallDelay*i);
}
ElectricBallOffCD = false;
WaitAndDo(ElectricBallAS, () => ElectricBallOffCD = true);
}
}
// Call this function as --> WaitAndDo(karmaAS, () => karmaOffCD = true);
// Put in bool and float attack speed and works!
public Coroutine WaitAndDo(float timeInSeconds, Action action)
{
return StartCoroutine(Execute(timeInSeconds, action));
}
private IEnumerator Execute(float timeInSeconds, Action action)
{
yield return new WaitForSeconds(timeInSeconds);
if (Application.isPlaying) action();
}
void spawnElectricBall(){
Vector3 sp = Camera.main.WorldToScreenPoint(transform.position);
Vector3 dir = (Input.mousePosition - sp).normalized;
GameObject objectInstance = Instantiate(ElectricBall,
gameObject.transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
objectInstance.GetComponent<Rigidbody2D>().AddForce(dir * electricBallForce);
}
void spawnMagicBall(){
GameObject [] Attacks = GameObject.FindGameObjectsWithTag("Attack");
GameObject [] magicBalls = new GameObject [Attacks.Length];
for (int i = 0; i < Attacks.Length; i++)
{
// if (Attacks[i].GetComponent<AttackClass>().Type == "MagicBall"){
// magicBalls[i] = Attacks[i];
// }
}
if (magicBalls != null){
for (int i = 0; i < magicBalls.Length; i++) {
Destroy(magicBalls[i]);
}
}
int v = 0;
for (int i = 0; i < magicBallSpawnAmount; i++) {
GameObject objectInstance = Instantiate(MagicBall,
spawnLocationsMagicBall[v] + player.transform.position
, Quaternion.Euler(new Vector3(0, 0, 0)));
if (magicBallSpawnAmount == 4){
v = v+1;
}
else if (magicBallSpawnAmount == 2){
v = v+3;
}
v++;
}
}
void Explosion(){
if(explosionOffCD == true){
Vector3 playerPosition = player.transform.position;
Vector3 spawnPosition = UnityEngine.Camera.main.ScreenToWorldPoint(Input.mousePosition);
spawnPosition.z = 0.0f;
// If its in explosionRange spawn where it is
if (Vector3.Distance(playerPosition, spawnPosition) <=explosionRange){
AudioManager.GetComponent<AudioManager>().PlaySound("Explosion");
GameObject objectInstance = Instantiate(fireExplosion,
spawnPosition, Quaternion.Euler(new Vector3(0, 0, 0)));
explosionOffCD = false;
WaitAndDo(explosionAS, () => explosionOffCD = true);
}
// Else do calculations
else {
Vector3 newSpawnPosition = spawnPosition;
bool toRight = true;
bool abovePlayer = true;
// If spawn position is to the right of player
if (spawnPosition.x > playerPosition.x){
toRight = true;
}
else {
toRight = false;
}
// If spawn position is above player
if (spawnPosition.y > playerPosition.y){
abovePlayer = true;
}
else {
abovePlayer = false;
}
// If its to the right and out of explosionRange
if (toRight == true){
if ((spawnPosition.x - playerPosition.x) > explosionRange){
newSpawnPosition.x = playerPosition.x + explosionRange;
}
}
else {
if ((playerPosition.x - spawnPosition.x) > explosionRange){
newSpawnPosition.x = playerPosition.x - explosionRange;
}
}
if (abovePlayer == true){
if ((spawnPosition.y - playerPosition.y) > explosionRange){
newSpawnPosition.y = playerPosition.y + explosionRange;
}
}
else {
if ((playerPosition.y - spawnPosition.y) > explosionRange){
newSpawnPosition.y = playerPosition.y - explosionRange;
}
}
AudioManager.GetComponent<AudioManager>().PlaySound("Explosion");
GameObject objectInstance = Instantiate(fireExplosion,
(newSpawnPosition), Quaternion.Euler(new Vector3(0, 0, 0)));
explosionOffCD = false;
WaitAndDo(explosionAS, () => explosionOffCD = true);
}
}
}
public GameObject FindClosestObject(string name, Vector3 Position)
{
GameObject[] gos;
gos = GameObject.FindGameObjectsWithTag(name);
GameObject closest = null;
float distance = Mathf.Infinity;
Vector3 position = Position;
foreach (GameObject go in gos)
{
Vector3 diff = go.transform.position - position;
float curDistance = diff.sqrMagnitude;
if (curDistance < distance)
{
closest = go;
distance = curDistance;
}
}
return closest;
}
}
This is obviously bad practice as there will be way too many variables and functions to count. So what i want to be able to do is optimize this code and make it lots more efficient, so that i don’t have to keep writing out variables, functions and if statements for each weapon i create.
Here is my scriptableobject class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Tilemaps;
using enums;
using UnityEngine;
[CreateAssetMenu(fileName = "New Equipment", menuName = "Inventory/Equipment")]
public class Equipment : InventoryItem
{
public EquipmentSlot equipSlot;
public GameObject prefabItem;
public Tile prefab;
public bool hasGravity;
public string type;
public int armorModifier;
public int damageModifier;
public float speedModifier;
public float jumpForceModifier;
public float miningEfficiency;
public bool providesLight = false;
public bool rockFriend = false;
public bool lightningProtection = false;
public int soulCost = 10;
public bool isMelee = false;
public float meleeBaseDamage = 0;
public float meleeHitRadius;
public override void Use(){
base.Use();
Player = GameObject.Find("Player");
RemoveFromInventory();
Player.GetComponent<EquipmentManager>().Equip(this);
}
}
namespace enums{
public enum EquipmentSlot {MainHand, Head, Chest, Legs, Gloves, Feet, Necklace, Ring}
}
// public enum EquipmentSlot {MainHand, Head, Chest, Legs, Gloves, Feet, Necklace, Ring}
public enum Type {Block, Weapon, Armor}
One solution i thought of was to create a ‘Weapon’ class that inherits from ‘Equipment’ that i can create a scriptable object from, and my Attacks.cs class would use variables from that scriptableobject instead.
Example:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Equipment", menuName = "Inventory/Weapon")]
public class Weapon : Equipment
{
public bool offCD = true;
public float AS = 1f;
public GameObject Attack;
public float force;
}
Would this work? Is there a better solution i could implement? Any help would be appreciated, thanks.
P.S not all attacks will work the same. Most will follow the basic formula of spawn attack and reset CD etc but some will have different functions, like spawning 5 missiles instead of 1 etc.