Why Do My Obstacles Do This In My Object Pooler? - c#

I am almost done with my Object Pooling System for my PickAxe GameObject, but I have this one problem I need help with.
What I am trying to figure out is why all of my spawning restarts whenever the first PickAxe hits the wall? How do I make to where the PickAxe just goes back into the "pooler" whenever it hits the wall?
I took two screenshots and I'll post my code below too. The first one is just before the first Pickaxe that was spawned hits the wall, and the second screenshot is right after that same PickAxe hit the wall.
Some of my code below:
This script spawns my PickAxes. I am 99% sure my problem is where I am calling my Event function in my CoRoutine. Am I right?
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Obstacle4 // Pick Axe Obstacle
{
public float SpawnWait; // Time in seconds between next wave of obstacle 4.
public float StartGameWait; // Time in seconds between when the game starts and when the fourth obstacle start spawning.
public float WaveSpawnWait; // Time in seconds between waves when the next wave of obstacle 4 will spawn.
}
public class SpawnPickAxe : MonoBehaviour
{
public GameObject pickAxePrefab;
public Obstacle4 obstacle4_;
void Start ()
{
PickAxePoolManager.instance.CreatePool (pickAxePrefab, 15); //CreatePool is a method in PickAxePoolManager
StartCoroutine (PickAxeSpawner ());
}
IEnumerator PickAxeSpawner ()
{
yield return new WaitForSeconds (obstacle4_.StartGameWait);
while (true)
{
for (int i = 0; i < 5; i++)
{
Vector3 newSpawnPosition = new Vector3 (Random.Range(-10.0f, 10.0f), 1.2f, 30.0f);
Quaternion newSpawnRotation = Quaternion.Euler (0.0f, -90.0f, 0.0f);
PickAxePoolManager.instance.ReuseObject (pickAxePrefab, newSpawnPosition, newSpawnRotation); //ReuseObject is also a method in PickAxePoolManager
//Instantiate (obstacle4.pickAxe, spawnPosition, spawnRotation);
yield return new WaitForSeconds (obstacle4_.SpawnWait);
ResetByWall.instance.onTriggerEntered += delegate(GameObject obj)
{
PickAxePoolManager.instance.ReuseObject(pickAxePrefab, newSpawnPosition, newSpawnRotation);
};
}
yield return new WaitForSeconds (obstacle4_.WaveSpawnWait);
}
}
}
Below is my "ResetByWall" script. This script is also important because it contains the public Event that allows me to detect a collision with whatever collides with my wall, and my wall is the trigger.
using System;
using UnityEngine;
using System.Collections;
public class ResetByWall : MonoBehaviour
{
//public bool collided;
//public GameObject pickAxePrefab;
//NOTE:
//Singleton Pattern from lines 12 to 25 //
// ************************************
static ResetByWall _instance; // Reference to the Reset By Wall script
public static ResetByWall instance // This is the accessor
{
get
{
if(_instance == null) // Check to see if _instance is null
{
_instance = FindObjectOfType<ResetByWall>(); //Find the instance in the Reset By Wall script in the currently active scene
}
return _instance;
}
}
public event Action <GameObject> onTriggerEntered;
void OnTriggerEnter(Collider other)
{
if (onTriggerEntered != null) {
onTriggerEntered (other.gameObject);
}
}
}
And below is my PoolManager Script. You might not even need to look at this script but I'll post it anyways just in case.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PickAxePoolManager : MonoBehaviour {
Dictionary<int,Queue<GameObject>> poolDictionary = new Dictionary<int,Queue<GameObject>>();
//NOTE:
//Singleton Pattern used from lines 12 to 25
static PickAxePoolManager _instance; // Reference to the Pool Manager script
public static PickAxePoolManager instance // This is the accessor
{
get
{
if(_instance == null) // Check to see if _instance is null
{
_instance = FindObjectOfType<PickAxePoolManager>(); //Find the instance in the Pool Manager script in the currently active scene
}
return _instance;
}
}
/// <summary>
/// Creates the pool.
/// </summary>
/// <param name="prefab">Prefab.</param>
/// <param name="poolSize">Pool size.</param>
public void CreatePool(GameObject prefab, int poolSize)
{
int poolKey = prefab.GetInstanceID (); // Unique integer for every GameObject
if (!poolDictionary.ContainsKey (poolKey)) //Make sure poolKey is not already in the Dictionary,
//if it's not then we can create the pool
{
poolDictionary.Add(poolKey, new Queue<GameObject>());
for (int i = 0; i < poolSize; i++) //Instantiate the prefabs as dictated by the "poolSize" integer
{
GameObject newObject = Instantiate (prefab) as GameObject; //Instantiate as a GameObject
newObject.SetActive(false); // Don't want it to be visible in the scene yet
poolDictionary [poolKey].Enqueue(newObject); // Add it to our Pool
}
}
}

I solved the problem. I am just going to use this in my ResetByWall script:
void OnTriggerEnter (Collider obstacle)
{
if (obstacle.gameObject.tag == "Pick Axe")
{
obstacle.gameObject.SetActive (false);
}
}
Thanks! :)

Related

How do I change properties of a new spawned in object?

I'm fairly new to programming and right now I'm working on a 2D-Game for Android with Unity. The basic concept right now is that a object moves to the middle of the screen and it needs to "touch" an other object so you can press on a button and the moving object is getting destroyed. After it got destroyed I want another to spawn in and keep that cycle going. There is my problem. I have the (moving) object as a prefab and added it as the public GameObject "RedTriangle". The object is getting spawned in and moves to the center of the screen but it doesn't change the layer of the new objects when I press the button (I guess because its not the same object as the "RedTriangle" object). I really don't know how to change the Layer of new spawned in objects and hope I can get help here. Thanks already for all responses.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class Red : MonoBehaviour, IPointerClickHandler
{
public GameObject RedTriangle;
public GameObject FalseTriangle;
public Transform spawnPos;
public float spawnT;
private void Start()
{
GameObject newTriangle = Instantiate(RedTriangle, spawnPos.position, Quaternion.identity);
}
IEnumerator touchCD()
{
while (true)
{
yield return new WaitForSeconds(0.1f);
if (RedTriangle == null)
{
}
else if (RedTriangle != null)
{
Debug.Log("!= null");
RedTriangle.layer = 0;
}
}
}
public void Update()
{
if (RedTriangle == null)
{
spawnT = Random.Range(1, 4);
if (spawnT == 3)
{
GameObject newTriangle = Instantiate(FalseTriangle, spawnPos.position, Quaternion.identity);
}
else
{
GameObject newFalseTriangle = Instantiate(RedTriangle, spawnPos.position, Quaternion.identity);
}
}
}
public void OnPointerClick(PointerEventData eventData)
{
RedTriangle.layer = 7;
Debug.Log("button");
StartCoroutine(touchCD());
}
Assuming you want to change the layer of the object immediately after spawning, just do exactly that. For example:
GameObject newTriangle = Instantiate(FalseTriangle, spawnPos.position, Quaternion.identity);
newTriangle.layer = 0;
If you want to change the current (MonoBehaviour) object's own layer from its own attached component script e.g. when a certain event occurs, you don't need to keep the reference in your own variable as you can just use for example:
gameObject.layer = 0;

How can I use a singleton when switching/loading scenes?

In the Hierarchy I have 3 objects I want to keep and to not destroy when starting a new game.
But I want to destroy them when switching back to the main menu.
The objects are : Player , Game Manager , Scene Loader
On the 3 objects Player , Game Manager , Scene Loader I added to each one a script name PersistentManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PersistentManager : MonoBehaviour
{
public static PersistentManager Instance { get; private set; }
private void Awake()
{
if(Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
The Scene Loader script that attached to Scene Loader :
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
private bool loadScene = false;
[SerializeField]
private int scene;
[SerializeField]
private Text loadingText;
// Updates once per frame
void Update()
{
// If the player has pressed the space bar and a new scene is not loading yet...
if (Input.GetKeyUp(KeyCode.Space) && loadScene == false)
{
LoadScene(scene);
}
// If the new scene has started loading...
if (loadScene == true)
{
// ...then pulse the transparency of the loading text to let the player know that the computer is still working.
loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
}
}
// The coroutine runs on its own at the same time as Update() and takes an integer indicating which scene to load.
IEnumerator LoadNewScene()
{
// This line waits for 3 seconds before executing the next line in the coroutine.
// This line is only necessary for this demo. The scenes are so simple that they load too fast to read the "Loading..." text.
//yield return new WaitForSeconds(3);
// Start an asynchronous operation to load the scene that was passed to the LoadNewScene coroutine.
AsyncOperation async = SceneManager.LoadSceneAsync(scene);
// While the asynchronous operation to load the new scene is not yet complete, continue waiting until it's done.
while (!async.isDone)
{
yield return null;
}
}
public void LoadScene(int scene)
{
// ...set the loadScene boolean to true to prevent loading a new scene more than once...
loadScene = true;
// ...change the instruction text to read "Loading..."
loadingText.text = "Loading...";
this.scene = scene;
// ...and start a coroutine that will load the desired scene.
StartCoroutine(LoadNewScene());
}
}
And the Game Manager script that attached to Game Manager :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public SceneLoader sceneLoader;
public PlayerController playerController;
public CamMouseLook camMouseLook;
public static bool backToMainMenu = false;
public static bool togglePauseGame;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
PauseGame();
}
if (Input.GetKeyDown(KeyCode.Escape))
{
BackToMainMenu();
}
}
public void PauseGame()
{
togglePauseGame = !togglePauseGame;
if (togglePauseGame == true)
{
playerController.enabled = false;
camMouseLook.enabled = false;
Time.timeScale = 0f;
}
else
{
playerController.enabled = true;
camMouseLook.enabled = true;
Time.timeScale = 1f;
}
}
private void BackToMainMenu()
{
sceneLoader.LoadScene(0);
playerController.enabled = false;
camMouseLook.enabled = false;
Cursor.lockState = CursorLockMode.None;
Time.timeScale = 0f;
backToMainMenu = true;
}
}
Now when I'm running the game and then pressing the space bar for starting a new game the Main Menu scene is removed not sure why.
Before using the PersistentManager script I used on each of the 3 objects another script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DontDestroy : MonoBehaviour
{
private void Awake()
{
if (GameManager.backToMainMenu == false)
{
DontDestroyOnLoad(transform);
}
}
}
That keep the 3 objects alive when starting a new game and if switching back to the main menu the problem was that the 3 objects was also in the main menu when switching back to the main menu.
So I had 6 copies of the objects. 3 in the main menu scene and 3 in the DontDestroyOnLoad scene.
That's why I'm trying to use singleton.
I want that when I'm switching back to the main menu the 3 object will not be in the main menu scene in the hierarchy only on the DontDestroyOnLoad.
The problem is that the class you are inheriting from has it's instance in the parent
public static PersistentManager Instance { get; private set; }
this means that the first object will go
Is instance null? yes
Set me as instance
Set a don't destroy on load
the other two objects will check the instance, see that it's not null because the first object is referenced, and they will destroy themselves.
In general I would advise against using singletons as much as possible, as some might consider them an anti design pattern.
However, if you still want to use one; I'm attaching here a script I used in games that are in production for multiple years with millions of users, so it's already set up against most of the edge cases we encountered.
#region Using
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
#endregion
/// <summary>
/// Generic singleton Class. Extend this class to make singleton component.
/// Example:
/// <code>
/// public class Foo : GenericSingleton<Foo>
/// </code>
/// . To get the instance of Foo class, use <code>Foo.instance</code>
/// Override <code>Init()</code> method instead of using <code>Awake()</code>
/// from this class.
/// </summary>
public abstract class GenericSingleton<T> : MonoBehaviour where T : GenericSingleton<T>
{
private static T _instance;
[SerializeField, Tooltip("If set to true, the gameobject will deactive on Awake")] private bool _deactivateOnLoad;
[SerializeField, Tooltip("If set to true, the singleton will be marked as \"don't destroy on load\"")] private bool _dontDestroyOnLoad;
private bool _isInitialized;
public static T instance
{
get
{
// Instance required for the first time, we look for it
if (_instance != null)
{
return _instance;
}
var instances = Resources.FindObjectsOfTypeAll<T>();
if (instances == null || instances.Length == 0)
{
return null;
}
_instance = instances.FirstOrDefault(i => i.gameObject.scene.buildIndex != -1);
if (Application.isPlaying)
{
_instance?.Init();
}
return _instance;
}
}
// If no other monobehaviour request the instance in an awake function
// executing before this one, no need to search the object.
protected virtual void Awake()
{
if (_instance == null || !_instance || !_instance.gameObject)
{
_instance = (T)this;
}
else if (_instance != this)
{
Debug.LogError($"Another instance of {GetType()} already exist! Destroying self...");
Destroy(this);
return;
}
_instance.Init();
}
/// <summary>
/// This function is called when the instance is used the first time
/// Put all the initializations you need here, as you would do in Awake
/// </summary>
public void Init()
{
if (_isInitialized)
{
return;
}
if (_dontDestroyOnLoad)
{
DontDestroyOnLoad(gameObject);
}
if (_deactivateOnLoad)
{
gameObject.SetActive(false);
}
SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
InternalInit();
_isInitialized = true;
}
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
{
// Sanity
if (!instance || gameObject == null)
{
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
_instance = null;
return;
}
if (_dontDestroyOnLoad)
{
return;
}
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
_instance = null;
}
protected abstract void InternalInit();
/// Make sure the instance isn't referenced anymore when the user quit, just in case.
private void OnApplicationQuit()
{
_instance = null;
}
void OnDestroy()
{
// Clear static listener OnDestroy
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
StopAllCoroutines();
InternalOnDestroy();
if (_instance != this)
{
return;
}
_instance = null;
_isInitialized = false;
}
protected abstract void InternalOnDestroy();
}

Next Scene Not Loading

My problem is that when all the enemies are killed the scene that should be loaded is not loading. I did add the scene to the Build setting (it has an index of 3) but it is still not loading. The script I created is attached to an empty object and not directly to the sprite (is that okay?). Can someone tell me why the scene isn't loading? Thank you.
This image is for to show you the EnemySpawner empty object inspector
EnemySpawner Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemySpawner : MonoBehaviour {
[SerializeField] GameObject EnemyPreFab;
[SerializeField] int MaxEnemies = 30;
[SerializeField] float EnemySpawnTime = 1.00001f;
[SerializeField] GameObject FirstWaypoint;
int CurrentNumOfEnemies = 0;
public LevelManager myLevelManager;
public int maximumnumberofhits = 0;
int timesEnemyHit;
IEnumerator SpawningEnemies()
{
while(CurrentNumOfEnemies <= MaxEnemies)
{
GameObject Enemy = Instantiate(EnemyPreFab, this.transform.position, Quaternion.identity);
CurrentNumOfEnemies++;
yield return new WaitForSeconds(EnemySpawnTime);
}
}
void Start()
{
StartCoroutine(SpawningEnemies());
timesEnemyHit = 0;
if (this.gameObject.tag == "EnemyHit")
{
CurrentNumOfEnemies++;
}
}
void OnCollisionEnter2D()
{
timesEnemyHit++;
if (timesEnemyHit == maximumnumberofhits)
{
CurrentNumOfEnemies--;
Destroy(this.gameObject);
}
if (CurrentNumOfEnemies == 0)
{
myLevelManager.LoadLevel("NextLevelMenu");
Debug.Log("LevelLoaded");
}
}
}
LevelManger script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelManager : MonoBehaviour {
public void LoadLevel(string name)
{
print("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
EnemyShooting Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyShooting : MonoBehaviour {
[SerializeField] float EnemyLaserSpeed = 10f;
[SerializeField] float EnemyLaserFireTime;
[SerializeField] GameObject LaserBulletEnemyPreFab;
[SerializeField] int MaxNumberOfHits = 1;
int CurrentNumberOfHits = 0;
Coroutine FireCoroutine;
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag == "PlayerLaser")
{
if(CurrentNumberOfHits < MaxNumberOfHits)
{
CurrentNumberOfHits++;
Destroy(collider.gameObject);
Score.ScoreValue += 2;//The user will be rewarded 1 point
}
}
}
void DestroyEnemy()
{
if(CurrentNumberOfHits >= MaxNumberOfHits)
{
Destroy(gameObject);
}
}
private void Fire()
{
FireCoroutine = StartCoroutine(ShootContinuously());
}
void BecomeVisible()
{
Fire();
}
IEnumerator ShootContinuously()
{
while (true)
{
GameObject LaserBulletEnemy = Instantiate(LaserBulletEnemyPreFab, this.transform.position, Quaternion.identity) as GameObject;
LaserBulletEnemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0, EnemyLaserSpeed);
EnemyLaserFireTime = Random.Range(0.5f, 0.9f);
yield return new WaitForSeconds(EnemyLaserFireTime);
}
}
// Use this for initialization
void Start () {
BecomeVisible();
}
// Update is called once per frame
void Update () {
DestroyEnemy();
}
}
EnemyPathing script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyPathing : MonoBehaviour {
[SerializeField] List<Transform> WayPoints;
[SerializeField] float EnemyMovingSpeed = 5f;
int WayPointIndex = 0;
void EnemyMoving()
{
if (WayPointIndex <= WayPoints.Count - 1)
{
var TargetedPosition = WayPoints[WayPointIndex].transform.position; //The position of where the enemy needs to go
TargetedPosition.z = 0f;
var MoveThisFrame = EnemyMovingSpeed * Time.deltaTime;
transform.position = Vector2.MoveTowards(this.transform.position, TargetedPosition, MoveThisFrame);
if(transform.position == TargetedPosition)
{
WayPointIndex++;
}
}
else
{
Destroy(gameObject);
}
}
// Use this for initialization
void Start () {
transform.position = WayPoints[WayPointIndex].transform.position;
}
// Update is called once per frame
void Update () {
EnemyMoving();
}
}
Problem
You're checking for collisions on the SPAWNER; when someone hits the Spawner it counts down enemies. But the Spawner doesn't have a collision box in the screenshot so it can never be hit. The Scene changing code can never be called.
So the game, based on the code, looks like this:
Spawn X enemies,
Hit the Spawner X times,
(Removed: Destroy the Spawner,)
Change scene.
I'm guessing this is conceptually incorrect and you actually want to check collisions on the spawned enemies, which will then count up the amount of destroyed enemies, and change the scene when they are all dead.
Solution
Conceptually, what you want is:
Spawn X enemies
Count up variable for every enemy
On Enemy death, count it down
When 0, change scene
So how do we code this?
Well, every enemy needs a reference to the object that holds the count. You can do this in several ways, when I personally do it I usually have just one spawner that is responsible for everyone so I make that one a Singleton, that can be references from anywhere:
EnemySpawner
public class EnemySpawner : MonoBehaviour
{
public static Spawner Instance = null;
int CurrentNumOfEnemies = 0;
// ... etc
void Start()
{
if (Instance == null)
Instance = this;
// Spawn enemies like you do already, CurrentNumOfEnemies++ for every spawned
}
public OnEnemyDeath() {
CurrentNumOfEnemies--;
if (CurrentNumOfEnemies < 1)
{
// You killed everyone, change scene:
LevelManager.LoadLevel("Your Level");
}
}
}
Enemy script (I don't know how your current code looks, but here's a minimal solution based on how I THINK your code looks):
void OnDestroy()
{
// This will run automatically when you run Destroy() on this gameObject
EnemySpawner.Instance.OnEnemyDeath(); // Tell the EnemySpawner that someone died
}
This will only work if you have exactly only ONE spawner. If you have multiple ones you will have to send a reference to the instance of its spawner to every spawned enemy. I can show you how to do ths too, if you wish.
Bonus content
LevelManager doesn't need to be on a GameObject, it can be static instead:
Remove the LevelManager script from any GameObject
Change your LevelManager code to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class LevelManager
{
public static void LoadLevel(string name)
{
Debug.Log("Level loading requested for" + name);
SceneManager.LoadScene(name);
}
}
Now you can use it from ANYWHERE, without needing to initialize a reference to any script or GameObject:
LevelManager.LoadLevel("My Level");
myLevelManager.LoadLevel("NextLevelMenu"); is never executed, because you destroy the object in the if-test above.

Passing Monster variables to Battle controller

I'm building a RPG game in Unity and I have some trouble with my battle system. I have a world scene which is populated by various monsters, if the player collides with the monster it loads the battle Scene. I want to pass the name and ID# of the enemy controller so it can load the correct sprites and prefab (the battle scene is generic). The problem is that my monster object (in this case GrassSlug) is in a different scene (world scene) than my battle controller (battle scene). How can I communicate these values to my battle controller? Do I need to add an extra controller which doesn't get destroyed on load? (I current have a GameManager like that and a SkillManager).
GrassSlug.cs (GameObject in world scene)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GrassSlug : Monster {
private string enemyName = "Grass Slug";
private int enemyID = 001;
}
Monster.cs (GameObject in world scene)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Monster : Character {
void OnCollisionEnter2D(Collision2D other)
{
if (other.collider.tag == "Player") {
SceneManager.LoadScene("Battle");
}
}
}
BattleController.cs (GameObject in Battle scene)
using Random = System.Random;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class BattleController : MonoBehaviour {
private Stat playerHealth;
private Stat enemyHealth;
private static Random random = new Random();
private int playerDamage;
private int enemyDamage;
public Button attackButton;
public Button escapeButton;
public Transform victoryText;
public GameObject canvas;
public GameObject button;
public Canvas battleUI;
void Start () {
InitHealth();
InitUI();
}
// <summary>
// Init player and enemy health
// </summary>
private void InitHealth()
{
playerHealth.Initialize(100, 100);
enemyHealth.Initialize(100, 100);
}
// <summary>
// Set up main battle UI
// </summary>
private void InitUI()
{
Button AttackButton = attackButton.GetComponent<Button>();
AttackButton.onClick.AddListener(PlayerAttack);
Button EscapeButton = escapeButton.GetComponent<Button>();
EscapeButton.onClick.AddListener(ReturnScene);
}
// <summary>
// Function for the player attack
// Calculates player's damage and adds XP to the skills
// </summary>
private void PlayerAttack ()
{
playerDamage = random.Next(20);
enemyHealth.CurrentValue -= playerDamage;
GameController.Instance.attackXP += playerDamage;
GameController.Instance.hitpointsXP += playerDamage;
if(enemyHealth.CurrentValue <= 0) {
Victory();
} else {
EnemyAttack();
}
}
// <summary>
// Function for enemy attack
// Calculates enemy's damage and adds XP to the players' skills
// </summary>
private void EnemyAttack () {
enemyDamage = random.Next(10);
playerHealth.CurrentValue -= enemyDamage;
GameController.Instance.defenceXP += enemyDamage;
GameController.Instance.SkillDebug();
if(playerHealth.CurrentValue <= 0) {
Debug.Log("You are defeated!");
}
}
// <summary>
// Change UI when player wins the battle
// </summary>
private void Victory() {
Instantiate(victoryText, new Vector3(0, 1.75f, 0), Quaternion.identity);
GameObject newCanvas = Instantiate(canvas) as GameObject;
GameObject newButton = Instantiate(button) as GameObject;
newButton.transform.SetParent(newCanvas.transform, false);
Button NewButton = newButton.GetComponent<Button>();
NewButton.onClick.AddListener(ReturnScene);
battleUI.gameObject.SetActive(false);
}
// <summary>
// Return to the previous screen
// Note: Currently only supports going back to the "Hometown" scene
// </summary>
private void ReturnScene () {
SceneManager.LoadScene("Hometown");
}
}
You can either create a new Don't Destroy On Load object that will hold that information for you, or just keep those data in static field. Static fields are not destroyed while changing scenes.
I guess you could also create a ScriptableObject for keeping this data, but this is not the best solution in this case in my opinion.
A simple solution is to create a static method and private static fields in your BattleController.
public class BattleController : MonoBehaviour {
private static string enemyName;
private static int enemyID;
public static void SetEnemy(string name, int id)
{
enemyName = name;
enemyID = id;
}
//other code
}
Then call the method before loading the scene.
public class Monster : Character {
void OnCollisionEnter2D(Collision2D other)
{
if (other.collider.tag == "Player") {
BattleController.SetEnemy(enemyName, enemyID)
SceneManager.LoadScene("Battle");
}
}
}
In your example the monster's name and ID are set as private fields in the GrassSlug class, but for this solution you'd need to add them to the Monster class as protected fields, then set them in GrassSlug.
If you need to share more information with the Battle Controller, you might want to just pass the Monster implementation to the Battle Controller instead of just its name and ID.

Unity: GameObject repeated instantiation issue

I am trying to instantiate a zombie prefab every time I call NewSpawn(), which is when reSpawn variable becomes 'true'(through inspector). However, once I instantiate one zombie it does an infinite loop of clones of the zombie. [Making clones of clones of clones] How do I limit it to one zombie instantiation per method call? (I'd like to make 10 zombies, not 10,000.
Thank you!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Zombie : MonoBehaviour {
public GameObject zombiePrefab;
public Transform zombieSpawnPoint;
private Transform[] spawnPositions;
public bool reSpawn = false;
private bool lastToggle = false;
// Use this for initialization
void Start () {
spawnPositions = zombieSpawnPoint.GetComponentsInChildren<Transform>();
}
private void NewSpawn() //spawn location of newZombie
{
Instantiate(zombiePrefab, transform.position, transform.rotation);
int i = Random.Range(1, spawnPositions.Length);
transform.position = spawnPositions[i].transform.position;
}
void Update () { //T-toggle
if (reSpawn != lastToggle)
{
NewSpawn();
reSpawn = false;
}
else
lastToggle = reSpawn;
}
}
Just call NewSpawn() in your button click event instead of setting reSpawn to true.
Besides that this doesn't make any sense:
else
{
lastToggle = reSpawn;
}
They are already equal.

Categories

Resources