- Home /
More efficient way to spawn enemies in my game...?
Hi everyone! My game is a turn-based dungeon crawler where enemies have a chance to spawn every time the player takes a step forwards. It works, but I feel like it's way too mish-mash and inefficient, not to mention potentially prone to bugs... When an enemy spawns, I prevent the player from moving, and spawn an enemy from a list, giving it health/attack values. Below is the script attached to my player that handles moving as well as spawning enemies - while I'm not having errors, I'm not too experienced and would really appreciate pointers in the right direction as to what I can improve :)
PlayerMovement.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class PlayerMovement : MonoBehaviour {
public static bool CanMoveForward;
public static bool CanMoveBackward;
public static bool CanMoveLeft;
public static bool CanMoveRight;
public static bool CanRotateLeft;
public static bool CanRotateRight;
public static bool CanMoveForwardCache;
public static bool CanMoveBackwardCache;
public static bool CanMoveLeftCache;
public static bool CanMoveRightCache;
public static bool CanRotateLeftCache;
public static bool CanRotateRightCache;
public static bool InCombat = false;
public static int chancetospawn;
public static bool CanSpawnEnemy = true;
public Texture2D EnemyObject;
public RawImage EnemyImage;
public Texture2D Enemy1;
public Texture2D Enemy2;
public Texture2D Enemy3;
public Texture2D Enemy4;
public Texture2D Enemy5;
public Texture2D Enemy6;
public Texture2D Enemy7;
public Texture2D Enemy8;
public Texture2D Enemy9;
public Texture2D Enemy10;
public Texture2D EnemyNull;
public static int EnemyCurHealth;
public static int EnemyMaxHealth;
public static int EnemyDamage;
//Translation:
float movSpeed = 4.0f;
Vector3 pos;
Transform tr ;
public static bool moving = false;
//Rotation:
public static bool rotating = false;
public float rotSpeed = 360f;
float rotDegrees = 0f;
Quaternion rotToAngle ;
void Start () {
pos = transform.position;
tr = transform;
CanMoveForward = true;
CanMoveBackward = true;
CanMoveLeft = true;
CanMoveRight = true;
CanRotateLeft = true;
CanRotateRight = true;
}
void SpawnEnemy() {
CanMoveForwardCache = CanMoveForward;
CanMoveBackwardCache = CanMoveBackward;
CanMoveLeftCache = CanMoveLeft;
CanMoveRightCache = CanMoveRight;
CanRotateLeftCache = CanRotateLeft;
CanRotateRightCache = CanRotateRight;
CanMoveForward = false;
CanMoveBackward = false;
CanMoveLeft = false;
CanMoveRight = false;
CanRotateLeft = false;
CanRotateRight = false;
int i = Random.Range (1, 10);
if(i == 1) { EnemyObject = Enemy1; EnemyMaxHealth = 11; EnemyCurHealth = 11; EnemyDamage = 1; }
if(i == 2) { EnemyObject = Enemy2; EnemyMaxHealth = 12; EnemyCurHealth = 12; EnemyDamage = 1; }
if(i == 3) { EnemyObject = Enemy3; EnemyMaxHealth = 13; EnemyCurHealth = 13; EnemyDamage = 1; }
if(i == 4) { EnemyObject = Enemy4; EnemyMaxHealth = 14; EnemyCurHealth = 14; EnemyDamage = 2; }
if(i == 5) { EnemyObject = Enemy5; EnemyMaxHealth = 15; EnemyCurHealth = 15; EnemyDamage = 2; }
if(i == 6) { EnemyObject = Enemy6; EnemyMaxHealth = 16; EnemyCurHealth = 16; EnemyDamage = 3; }
if(i == 7) { EnemyObject = Enemy7; EnemyMaxHealth = 17; EnemyCurHealth = 17; EnemyDamage = 3; }
if(i == 8) { EnemyObject = Enemy8; EnemyMaxHealth = 18; EnemyCurHealth = 18; EnemyDamage = 3; }
if(i == 9) { EnemyObject = Enemy9; EnemyMaxHealth = 19; EnemyCurHealth = 19; EnemyDamage = 4; }
if(i == 10) { EnemyObject = Enemy10; EnemyMaxHealth = 19; EnemyCurHealth = 19; EnemyDamage = 5; }
EnemyImage.GetComponent<RawImage>().texture = EnemyObject;
InCombat = true;
}
void EnemySpawned() {
chancetospawn = 0;
CanMoveForward = CanMoveForwardCache;
CanMoveBackward = CanMoveBackwardCache;
CanMoveLeft = CanMoveLeftCache;
CanMoveRight = CanMoveRightCache;
CanRotateLeft = CanRotateLeftCache;
CanRotateRight = CanRotateRightCache;
PlayerStats.CurrentXP += 12;
Debug.Log("Enemy defeated. You have gained 12 XP points.");
EnemyImage.GetComponent<RawImage>().texture = EnemyNull;
EnemyDamage = 0;
EnemyCurHealth = 0;
EnemyMaxHealth = 0;
InCombat = false;
}
void Update () {
if(CameraLook.AtLeftEnd == true && Input.GetButtonUp("Fire2")) {
RotLeft();
}
if(CameraLook.AtRightEnd == true && Input.GetButtonUp("Fire2")) {
RotRight();
}
if (InCombat && EnemyCurHealth <= 0) {
EnemySpawned();
}
Debug.DrawRay(transform.position, transform.forward, Color.red);
//Input:
if (!moving && !rotating) {
if (Input.GetKey(KeyCode.D) && tr.position == pos && CanMoveRight) {
MoveRight();
} else if (Input.GetKey(KeyCode.A) && tr.position == pos && CanMoveLeft) {
MoveLeft();
} else if (Input.GetKey(KeyCode.W) && tr.position == pos && CanMoveForward) {
MoveForward();
} else if (Input.GetKey(KeyCode.S) && tr.position == pos && CanMoveBackward) {
MoveBackward();
} else if (Input.GetKey(KeyCode.Q) && tr.position == pos && CanRotateLeft) {
RotLeft();
} else if (Input.GetKey(KeyCode.E) && tr.position == pos && CanRotateRight) {
RotRight();
}
}
//Translation:
if (moving) {
if (Vector3.Distance(transform.position,pos) <0.05f){
transform.position = pos;
moving=false;
// Debug.Log("FINISHED MOVE!!!!!!!!");
if (chancetospawn == 1) {
SpawnEnemy();
}
} else {
transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * movSpeed);
}
}
//Rotation:
if (rotating) {
if (Quaternion.Angle(transform.rotation,rotToAngle) <10f) {
transform.rotation = rotToAngle;
rotating=false;
} else {
transform.rotation = Quaternion.RotateTowards(transform.rotation, rotToAngle, rotSpeed * Time.deltaTime);
}
}
if(EnemyCurHealth <= 0) {
EnemyCurHealth = 0;
}
}
public void MoveForward() {
if (tr.position == pos && CanMoveForward) {
pos += transform.forward;
moving=true;
chancetospawn = Random.Range(1, 10);
}
}
public void MoveBackward() {
if (tr.position == pos && CanMoveBackward) {
pos += -transform.forward;
moving=true;
chancetospawn = Random.Range(1, 10);
}
}
public void MoveLeft() {
if (tr.position == pos && CanMoveLeft) {
pos += -transform.right;
moving=true;
chancetospawn = Random.Range(1, 10);
}
}
public void MoveRight() {
if (tr.position == pos && CanMoveRight) {
pos += transform.right;
moving=true;
chancetospawn = Random.Range(1, 10);
}
}
public void RotLeft() {
if (tr.position == pos && CanRotateLeft) {
rotDegrees -= 90f;
rotToAngle = Quaternion.Euler(0, rotDegrees, 0);
rotating = true;
}
}
public void RotRight() {
if (tr.position == pos && CanRotateRight) {
rotDegrees += 90f;
rotToAngle = Quaternion.Euler(0, rotDegrees, 0);
rotating = true;
}
}
}
Answer by fafase · Mar 17, 2018 at 10:01 AM
Instead of fully assigning the value to your enemies in the code, create prefab and store them in a array. Then your code sums up to :
public GameObject [] prefabs;
void SpawnEnemy()
{
Instantiate(this.prefabs[Random.Range(0, this.prefabs.Length)]);
}
All the values are set in the editor.
Thanks for the answer :) That'd instantiate the prefab, but what about assigning the health values and such? The way I see it, I'd still have to have the condition checks I had in my script. Is there a better way to have them in a list and call upon them randomly, retrieving the sprite, health, and damage values of the enemy? Thanks in advance!
All the values are set in the inspector of the prefab. That's the whole point of it. You set them manually.
Answer by Maritto7 · Mar 17, 2018 at 04:12 PM
Have a look at unity scriptable objects, Here is a good link tutorial for that: https://www.youtube.com/watch?v=aPXvoWVabPY
Then you can use a pooling system for each enemy character, this is a personal one i usually use on my projects:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolingSystem : MonoBehaviour {
public GameObjectPool[] gameObjectPool;
//Each pool private properties.
[System.Serializable]
public class GameObjectPool
{
public string Nome;
public GameObject prefabToPool;
public int count;
private List<GameObject> instantiatedObjectList = new List<GameObject>();
//Instantiate individually each pool.
public void FillObjectList()
{
if (prefabToPool != null)
{
for (int x = 0; x < count; x++)
{
AddNewObjectToList();
instantiatedObjectList[x].SetActive(false);
}
}
else
{
Debug.LogError("You didn't assign one of the prefabs to the pooling system.");
UnityEditor.EditorApplication.isPlaying = false;
}
}
//Insert a new object slot to the item list.
private void AddNewObjectToList()
{
GameObject newObj = Instantiate(prefabToPool, Vector3.zero, Quaternion.identity);
newObj.SetActive(false);
instantiatedObjectList.Add( newObj ) ;
}
//Returns an item from the list
public GameObject GetInactiveItem()
{
if (!DoWeHaveFreeItems()) AddNewObjectToList();
GameObject activeObj = instantiatedObjectList[instantiatedObjectList.Count-1] ;
instantiatedObjectList.Remove(activeObj);
activeObj.SetActive(true);
return activeObj;
}
//Checks for items not used in the list.
private bool DoWeHaveFreeItems()
{
if (instantiatedObjectList.Count > 0) return true;
else return false;
}
//Checks if that item belongs in this item pool.
public bool DoesItBelongHere(GameObject toTest)
{
if (!DoWeHaveFreeItems()) AddNewObjectToList();
if (toTest.name == instantiatedObjectList[0].name) return true;
else return false;
}
//After an item is no longer usefull it get deactivated and reinserted back into the list.
public void ReInsertBackIntoList(GameObject objToReinsertBackToTheList)
{
objToReinsertBackToTheList.SetActive(false);
instantiatedObjectList.Add(objToReinsertBackToTheList);
}
}
//Create an editor De-Buggable gizmo so you know an object could not be created because something went wrong yet an object shoulda
//been instantiated at the position this gizmo is at.
public class ErrorGameObjectPoolingSystem : MonoBehaviour
{
public float gizmoSize = 2.0f;
public Color cor = Color.red;
private void OnDrawGizmos()
{
Gizmos.color = cor;
Gizmos.DrawWireSphere(transform.position, gizmoSize);
}
}
private GameObject CreateErrorGameObj()
{
GameObject newObj = new GameObject();
newObj.AddComponent<ErrorGameObjectPoolingSystem>();
return newObj;
}
//Instantiate GameobjectArrays for the pool system at the start.
private void Start()
{
foreach (GameObjectPool x in gameObjectPool)
{
x.FillObjectList();
}
}
//Returns an inactive object.
//Transform and scale ARE NOT reset every deactivation.
//If in the future we may add a normalization feature so it is used in games where we need to alter gameobjects transform at runtime.
public GameObject InstantiatePoolObject(string gameObjName)
{
foreach (GameObjectPool obj in gameObjectPool)
{
if (obj.Nome == gameObjName)
return obj.GetInactiveItem();
}
Debug.LogError("There is no GameObject with such a name. Empty game was assigned");
return CreateErrorGameObj();
}
public GameObject InstantiatePoolObject(int objPosition)
{
try
{
return gameObjectPool[objPosition].GetInactiveItem();
}
catch (System.Exception)
{
Debug.LogError("There is no GameObject with that index. Empty game was assigned");
return CreateErrorGameObj();
}
}
//Unused at this point. May be usefull sometime in the future to get list index with the name.
private int GetIndex(string prefabPoolName)
{
for (int i = 0; i < gameObjectPool.Length; i++)
{
if (gameObjectPool[i].Nome == prefabPoolName)
return i;
}
return -1;
}
//While it is "destroying" the gameobject if it does not have any in the list to compare it to it will create a new instance of it to compare it to.
//Comparison is done by name. So changing the name will break item connection with the pooling system.
public void DestroyPoolItem(GameObject objToDeactivate)
{
foreach (GameObjectPool poolItem in gameObjectPool)
{
if (poolItem.DoesItBelongHere(objToDeactivate))
{
poolItem.ReInsertBackIntoList(objToDeactivate);
return;
}
}
Debug.LogError("This item does not have a pool or the item name has been changed during runtime. Still destroyed before it shapeshifts again!");
Destroy(objToDeactivate);
}
public void DestroyPoolItem(GameObject objToDeactivate, float time)
{
StartCoroutine(WaitB4Deactivating(objToDeactivate,time));
}
private IEnumerator WaitB4Deactivating(GameObject objToDeactivate, float howLong)
{
yield return new WaitForSeconds(howLong);
foreach (GameObjectPool poolItem in gameObjectPool)
{
if (poolItem.DoesItBelongHere(objToDeactivate))
{
poolItem.ReInsertBackIntoList(objToDeactivate);
yield break;
}
}
Debug.LogError("This item does not have a pool or the item name has been changed during runtime. Still destroyed before it shapeshifts again!");
Destroy(objToDeactivate);
}
}
That would be a much more effective and more scalable solution.