Unity3D: How to do object pooling without a spawner singleton - c#

Usually, if you use object pooling, you make a singleton like in this video.
After seeing this video, I discovered how messy singleton can be. Is there any other way to do object pooling without using singletons? I wanna instead use Events.

You would need to hold the pool in a class which is not a singleton, and handle your gameobject pool according to your events.
Regarding to call them with events, "I want to use events" is not a very concrete question. You need to set your events to listen (method subscribe) and to call them in the code wherever they're supposed to occur, this is invoke the method. I suggest that if you are not clear about this, try to use the unity events (OnTriggerEnter, if(Input.GetMouseButtonDown(0)) in the Update etc) until you dig in the topic enough to understand them and make ones of you own with c# events or UnityEvents when needed.
Find two template scripts, a pool and and event handler to handle your objects in the scene. You can check those out in an empty scene with your respective two gameObject to attach, and the object you want in the pool, pressing 'space' and 'A' to create from pool and return to pool respectively.
Pool manager:
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
private Queue<GameObject> objPool;
private Queue<GameObject> activeObj;
private int poolSize = 10;
public GameObject objPrefab;
void Start()
{
//queues init
objPool = new Queue<GameObject>();
activeObj = new Queue<GameObject>();
//pool init
for (int i = 0; i < poolSize; i++)
{
GameObject newObj = Instantiate(objPrefab);
objPool.Enqueue(newObj);
newObj.SetActive(false);
}
}
public GameObject GetRandomActiveGO() {
GameObject lastActive = default;
if (activeObj.Count > 0)
lastActive = activeObj.Dequeue();
else {
Debug.LogError("Active object queue is empty");
}
return lastActive;
}
//get from pool
public GameObject GetObjFromPool(Vector3 newPosition, Quaternion newRotation)
{
GameObject newObject = objPool.Dequeue();
newObject.SetActive(true);
newObject.transform.SetPositionAndRotation(newPosition, newRotation);
//keep actives to be retrieved
activeObj.Enqueue(newObject);
return newObject;
}
//return to pool
public void ReturnObjToPool(GameObject go)
{
go.SetActive(false);
objPool.Enqueue(go);
}
}
Event handler:
using UnityEngine;
public class EventHandler : MonoBehaviour
{
public delegate GameObject OnSpacePressed(Vector3 newPosition, Quaternion newRotation);
public OnSpacePressed onSpacePressed;
public delegate void OnAKeyPressed(GameObject go);
public OnAKeyPressed onAKeyPressed;
public PoolManager poolManager;
void Start()
{
onSpacePressed = poolManager.GetObjFromPool;
onAKeyPressed = poolManager.ReturnObjToPool;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
onSpacePressed?.Invoke(new Vector3(0, 0, 0), Quaternion.identity);
}
//here I get a random active, however this would be called in the specific objects remove circumstances,
//so you should have a reference to that specific gameobje when rerunrning it to the pool.
if (Input.GetKeyDown(KeyCode.A))
{
GameObject go = poolManager.GetRandomActiveGO();
onAKeyPressed?.Invoke(go);
}
}
}
Edit: Singleton pattern
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T _instance;
public static T instance
{
get
{
if (_instance == null)
{
_instance = GameObject.FindObjectOfType<T>();
if (_instance == null)
{
_instance = new GameObject(typeof(T).Name).AddComponent<T>();
}
}
return _instance;
}
}
}

Related

UnityEngine.MonoBehaviour:.ctor ()

I am trying to build a system that finds the nearest position of an object and uses it to transform the Player to a specific position. I understand the issue but I couldn't eliminate the yellow exclamation mark. The program is somehow working, but I'd like to see the solution.
The issue according to UnityConsole is: You are trying to create a MonoBehaviour using the 'new' keyword. This is not allowed. MonoBehaviours can only be added using AddComponent(). Alternatively, your script can inherit from ScriptableObject or no base class at all
UnityEngine.MonoBehaviour:.ctor ()
Scripts are:
#1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class ScoreMultiplerFinder : MonoBehaviour
{
private static ScoreMultiplerFinder instance;
private List<GameObject> checkpoints = new List<GameObject>();
public List<GameObject> Checkpoints { get { return checkpoints; } }
public static ScoreMultiplerFinder Singleton
{
get
{
if (instance == null)
{
instance = new ScoreMultiplerFinder();
instance.Checkpoints.AddRange(
GameObject.FindGameObjectsWithTag("Checkpoint"));
instance.checkpoints = instance.checkpoints.OrderBy(waypoint => waypoint.name).ToList();
}
return instance;
}
}
}
#2(inPlayerObject)
public void FindClosestMultiplier()
{
float lastDist = Mathf.Infinity;
for (int i = 0; i < ScoreMultiplerFinder.Singleton.Checkpoints.Count; i++)
{
GameObject thisWP = ScoreMultiplerFinder.Singleton.Checkpoints[i];
float distance = Vector3.Distance(transform.position, thisWP.transform.position);
if (distance < lastDist)
{
currentIndex = i;
lastDist = distance;
lastScoreMultip.position = ScoreMultiplerFinder.Singleton.Checkpoints[currentIndex].transform.position;
}
}
}
Thanks in advance and have a great day!
The error/warning is pretty much telling you what to do instead. A correct way to lazy initialize the Singleton would be
private List<GameObject> checkpoints;
// First of all be consequent and let nobody change the content of this
public IReadOnlyList<GameObject> Checkpoints => checkpoints;
public static ScoreMultiplerFinder Singleton
{
get
{
if(instance) return instance;
// before creating one check if there maybe is one already first
instance = FindObjectOfType<ScoreMultiplerFinder>();
if(instance) return instance;
// GameObject is one of the very few UnityEngine.Object types where using
// "new" is actually okey
instance = new GameObject(nameof(ScoreMultiplerFinder)).AddComponent<ScoreMultiplerFinder>();
return instance;
}
}
private void Awake ()
{
// Without this your "Singleton" is not complete and you rather only have a
// lazy factory property.
// For a valid singleton pattern you have to make sure that there actually
// exists only one single instance at a time!
if(instance && instance != this)
{
Destroy (gameObject);
return;
}
instance = this;
checkpoints = GameObject.FindGameObjectsWithTag("Checkpoint").OrderBy(waypoint => waypoint.name).ToList();
}
And then if you already use Linq you are already familiar with OrderBy.
Your second script can be shrinked down to
public void FindClosestMultiplier()
{
// Simply get the waypoints
// - "OrderBy" them by distance. using the "sqrMagnitude" is faster and for ordering has exactly the same effect as
// "Distance" which uses the more expensive "magnitude"
// - use "FirstOrDefault" to get either the closest item or "null" if there wasn't any at all
var closest = ScoreMultiplerFinder.Singleton.Checkpoints.OrderBy(c => (transform.position - c.transform.position).sqrMagnitude).FirstOrDefault();
// Was there any at all?
if(!closest) return;
// Otherwise "closest" is the closest waypoint -> do something with it
lastScoreMultip.position = closest.position;
}
Actually as mentioned I don't think you need a MonoBehaviour at all.
You could as well simply have
public static class ScoreMultiplerFinder
{
private static List<GameObject> checkpoints;
public static IReadOnlyList<GameObject> Checkpoints
{
get
{
if(checkpoints == null) checkpoints = GameObject.FindGameObjectsWithTag("Checkpoint").OrderBy(waypoint => waypoint.name).ToList();
return checkpoints;
}
}
}
and then accordingly do
public void FindClosestMultiplier()
{
var closest = ScoreMultiplerFinder.Checkpoints.OrderBy(c => (transform.position - c.transform.position).sqrMagnitude).FirstOrDefault();
if(!closest) return;
lastScoreMultip.position = closest.position;
}

Unity android. Loading Function after killing an app doesn't work properly (Object Reference not set to an instance of an object)

I am at begginner level with unity.
I have Load() function that goes off in OnApplicationPause(false). It works fine if I block the screen or minimalise app, and come back to it. However, when I kill it, I get error and the data doesnt get loaded.
Below is the script attached to the GameObject "SaveManager"
using System.Collections.Generic;
using UnityEngine;
using System;
public class SaveManager : MonoBehaviour
{
public GameObject ZwierzetaGroup;
public GameObject JedzeniaGroup;
public GameObject PrzedmiotyGroup;
public List<GameObject> zwierzeta_sprites;
public List<GameObject> jedzenia_sprites;
public List<GameObject> przedmioty_sprites;
public static DateTime oldDate;
Camera mainCamera;
public SaveState saveState;
void Start()
{
mainCamera = Camera.main;
FillArrays();
}
public void Save()
{
Debug.Log("Saving.");
SaveSpriteArray("zwierze", zwierzeta_sprites);
SaveSpriteArray("przedmiot", przedmioty_sprites);
SaveSpriteArray("jedzenie", jedzenia_sprites);
PlayerPrefs.SetInt("pieniazki", saveState.GetPieniazki());
PlayerPrefs.SetInt("HayAmount", saveState.GetHayAmount());
PlayerPrefs.SetInt("HayMax", saveState.GetHayMax());
PlayerPrefs.SetInt("FruitAmount", saveState.GetFruitAmount());
PlayerPrefs.SetInt("FruitMax", saveState.GetFruitMax());
//time:
PlayerPrefs.SetString("sysString", System.DateTime.Now.ToBinary().ToString());
PlayerPrefs.SetInt("First", 1);
}
public void SaveSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (sprites[i].activeSelf)
{
PlayerPrefs.SetInt(saveName + i, 1);
}
else
{
PlayerPrefs.SetInt(saveName + i, 0);
}
}
}
public void Load()
{
Debug.Log("Loading.");
//wczytanie czasu:
long temp = Convert.ToInt64(PlayerPrefs.GetString("sysString"));
oldDate = DateTime.FromBinary(temp);
Debug.Log("oldDate: " + oldDate);
//wczytywanie aktywnych sprite'ow
LoadSpriteArray("zwierze", zwierzeta_sprites);
LoadSpriteArray("przedmiot", przedmioty_sprites);
LoadSpriteArray("jedzenie", jedzenia_sprites);
saveState.SetPieniazki(PlayerPrefs.GetInt("pieniazki"));
saveState.SetHayAmount(PlayerPrefs.GetInt("HayAmount"));
saveState.SetHayMax(PlayerPrefs.GetInt("HayMax"));
saveState.SetFruitAmount(PlayerPrefs.GetInt("FruitAmount"));
saveState.SetFruitMax(PlayerPrefs.GetInt("FruitMax"));
mainCamera.GetComponent<UpdateMoney>().MoneyUpdate();
}
public void LoadSpriteArray(string saveName, List<GameObject> sprites)
{
for (int i = 0; i < sprites.Count; i++)
{
if (PlayerPrefs.GetInt(saveName + i) == 1)
{
sprites[i].SetActive(true);
}
else
{
sprites[i].SetActive(false);
}
}
}
private void FillArrays()
{
//find children
foreach (Transform child in ZwierzetaGroup.transform)
{
zwierzeta_sprites.Add(child.gameObject);
}
foreach (Transform child in PrzedmiotyGroup.transform)
{
przedmioty_sprites.Add(child.gameObject);
}
foreach (Transform child in JedzeniaGroup.transform)
{
jedzenia_sprites.Add(child.gameObject);
}
}
}
Below is a chunk of script attached to the main camera (probably a mistake). SaveManager GameObject with Script is attached to this one in inspector. This script is pretty big, so I'll skip the parts that I don't find relevant.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ManageEncouters: MonoBehaviour
{
DateTime currentDate;
public int First;
public SaveState saveState;
public SaveManager saveManager;
public HayBar hayBar;
public FruitBar fruitBar;
public GameObject[] jedzenia_sprites;
void Start()
{
}
void OnApplicationPause(bool pauseStatus)
{
if (!pauseStatus)
{
currentDate = System.DateTime.Now;
//Sprawdzanie czy jest to piersze uruchomienie gry (brak zapisu)
First = PlayerPrefs.GetInt("First");
if (First == 0)
{
Debug.Log("First time in app.");
RandomiseAnimals();
SaveManager.oldDate = currentDate;
hayBar.SetHayMax(1);
hayBar.SetHay(0);
fruitBar.SetFruitMax(1);
fruitBar.SetFruit(0);
saveState.SetPieniazki(100);
this.GetComponent<UpdateMoney>().MoneyUpdate();
}
else
{
Debug.Log("Not the first time in app.");
saveManager.Load();
}
if (TimeInSeconds(currentDate, SaveManager.oldDate) > 12)
{
Debug.Log("It's been more than 12 seconds sience last time.");
EatFood(currentDate, SaveManager.oldDate);
RandomiseAnimals();
}
else
{
Debug.Log("It's been less than 12 seconds sience last time.");
}
}
if (pauseStatus)
{
saveManager.Save();
}
}
private int TimeInSeconds(DateTime newD, DateTime oldD)
{
TimeSpan difference = newD.Subtract(oldD);
int seconds = (int)difference.TotalSeconds;
return seconds;
}
}
Below is the error I get, I don't know how to copy the text, so it's an Image.
I'm pretty sure that what you have here is a timing issue.
OnApplicationPause
Note: MonoBehaviour.OnApplicationPause is called as a GameObject starts. The call is made after Awake. Each GameObject will cause this call to be made.
So to me this sounds like it might be called when your SaveManager is not yet initialized, in particular the mainCamera.
I think you could already solve the issue by moving the initialization into Awake instead
private void Awake()
{
mainCamera = Camera.main;
FillArrays();
}
In general my little thumb rule is
use Awake wherever possible. In particular initialize everything where you don't depend on other scripts (initialize fields, use GetComponent, etc)
use Start when you need other scripts to be initialized already (call methods on other components, collect and pass on instances of some prefabs spawned in Awake, etc)
This covers most of cases. Where this isn't enough you would need to bother with the execution order or use events.

How to prevent the reload of already loaded gameobjects in Unity?

I'm currently developing a game in Unity and I ran into a small problem. I'm working on a restart function that gets called automatically when the player dies and loads the first scene again. However for some reason when reloading the scene games objects are duplicated with the version of the gameobject that was active at the time of death being inactive and the version that gets loaded as should be loaded getting set to active and so on every time the player dies adding a new duplicate of the same gameobjects to the hierarchy. I tried to solve this problem in multiple ways. First by trying to check each the gameobjects that get duplicated already have an instance of themselves running by attaching a script that checks every time a change in scene occurs wether or not their already is an instance of the gameobjects present:
public static GameObject Instance;
void Awake()
{
if(Instance){
DestroyImmediate(gameObject);
}else
{
DontDestroyOnLoad(gameObject);
Instance = this;
}
}
This seemed to solve the problem at first but it became to teadious towards the end because the scripts made all of my other scene objects behave badly or not at all so I chose to look for another solution.
Secondly I tried to Destroy each individual gameobject before I start loading the first scene. This also seemed to work at first but now my object pooler just recreates new intances of the gameobjects that it adds too the hierarchy esentially displacing the same problem to other gameobjects.
Finally in order to solve this problem I tried to make my objectpooler run only once when the scene that requires it to be loaded gets called but this didn't seem to work either. Does anyone have any idea how I could solve this problem. This is part of the script responsible for loading the original scene upon player death:
void Restart()
{
GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject gos in allObjects)
{
if (gos.activeInHierarchy)
{
if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound"))
{
gos.SetActive(false);
}
}
}
MySceneManager.LoadScene(0, this);
}
How could I change this in order to be able to reload the original scene without having any previously loaded GameObject get duplicated and behave according to how it should in the scene in which it got loaded originally?
The class responsible for loading and deloading scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class MySceneManager
{
private static int lastLoadedScene = 0;
public static void LoadScene(int index, MonoBehaviour caller)
{
ObjectPooler objP = new ObjectPooler();
objP.ReleaseAll();
caller.StartCoroutine(loadNextScene(index));
}
private static IEnumerator loadNextScene(int index)
{
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
yield return null;
}
_async.allowSceneActivation = true;
while (!_async.isDone)
{
yield return null;
}
var newScene = SceneManager.GetSceneByBuildIndex(index);
if (!newScene.IsValid()) yield break;
SceneManager.SetActiveScene(newScene);
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
lastLoadedScene = index;
}
}
This is my ObjectPooler:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
#region Singleton
public static ObjectPooler Instance;
private void Awake()
{
if (Instance)
{
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
#endregion
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private Dictionary<string, Pool> prefabPools;
void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
DontDestroyOnLoad(obj);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
private List<GameObject> currentlySpawnedObjects = new List<GameObject>();
public void Release(GameObject obj)
{
currentlySpawnedObjects.Remove(obj);
obj.SetActive(false);
obj.transform.SetParent(transform);
poolDictionary[obj.tag].Enqueue(obj);
DontDestroyOnLoad(obj);
}
public void ReleaseAll()
{
foreach (var child in currentlySpawnedObjects)
{
Release(child);
}
}
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
return null;
}
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
objectToSpawn.SetActive(true);
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();
if (pooledObj != null)
{
pooledObj.OnObjectSpawn();
}
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
currentlySpawnedObjects.Add(objectToSpawn);
return objectToSpawn;
}
}
Depends of your needs you can try next ways:
Use singleton pattern if you need save single instance of objects. This case relevant for saving managers (GameplayManager, SceneController, AssetBundleManager, etc.) in other cases will be better to use other ways. To read more about implementation you can see this article.
Destroy all old objects when loaded new scene. To do this you can use SceneManager.LoadScene method with LoadSceneMode.Single as parameter. It will keep DontDestoryOnLoad objects but will remove all others.
I'm not sure but the first possible issue to me already seems to be that it is running in Coroutine on an object in the scene you are going to unload.
It is cool that this is doable but have in mind that the Coroutine will stop working as soon as the caller object/component is destroyed or disabled.
To avoid that I would move your script to an object in the DontDestroyOnLoadScene using a Singleton pattern.
The next issue might be you going by SceneIndex ... both scenes, the one you want to unload and the one you want to load have index 0!
So maybe you get a conflict between the scene additively loading and the one you want to unload.
This also might happen again when you called
var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);
To avoid this I would rather go by scene reference for the unloading
public class MySceneManager : MonoBehaviour
{
private static MySceneManager instance;
// Lazy initialization
// With this you wouldn't even need this object in the scene
public static MySceneManager Instance
{
if(instance) return instance;
instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();
DontDestroyOnLoad(instance);
}
// Usual instant initialization having this object in the scene
private void Awake ()
{
if(instance && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}
public void LoadScene(int index)
{
StartCoroutine(loadNextScene(index));
}
private IEnumerator loadNextScene(int index)
{
// I didn't completely go through your ObjectPooler but I guess you need to do this
ObjectPooler.Instance.ReleaseAll();
// Instead of the index get the actual current scene instance
var currentScene = SceneManager.GetActiveScene();
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
yield return new WaitWhile(() => _async.progress < 0.9f);
_async.allowSceneActivation = true;
yield return new WaitUntil(() => _async.isDone);
// You have to do this before otherwise you might again
// get by index the previous scene
var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
yield return new WaitUntil(()=>unloadAsync.isDone);
var newScene = SceneManager.GetSceneByBuildIndex(index);
SceneManager.SetActiveScene(newScene);
}
}
Alternatively since anyway you do nothing special while loading/unloading the scenes:
why using Additive scene loading at all if you could also simply call
ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);
without making it additive so the current scene is simply removed automatically as soon as the new scene is fully loaded.
Note: Types on Smartphone so no warranty but I hope the idea gets clear

Client doesnt get spawned gameObjects from server, and the other way around

So, I'm struggling with the whole Unet system. right now, all I want to do is to spawn object from one end to the other. but even that doesn't work. It spawns the object on each the server and the client. but doesn't sync. I've searched so many places and watched so many videos - none helped. here are the code and the inspector details
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class NetworkController : NetworkBehaviour
{
public GameObject[] Spawns;
GameObject Canvas;
GameObject spawn;
[SyncVar]
public NetworkInstanceId ParentId;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
[Command]
public void CmdspawnPrefab()
{
Transform trns = GameObject.Find("Canvas").transform;
trns.position = trns.position + new Vector3(100, 200, 0);
GameObject go = Instantiate(Spawns[0], trns);
NetworkServer.Spawn(go);
}
}
What am I missing?
As it seems, my whole workflow was not correct. the solution is firstly to attach a listener to the button, instead of using the onclick function on the inspector -
Button b = BoardController.Buttons[0].GetComponent<Button>();
b.onClick.AddListener(delegate () { Cmd_ButtonPressed(1); });
the second is to create gameobjects, containing a script "GameManager" who controls your board, and one "RpcManager" which contains the Rpc functions. then in the playerobject script, use the commands.
Here are the classes i have used, to update an image instead of spawning an object, the idea is basically the same - to undersand the fundamentals of Unet and passing commands.
public class PlayerObject : NetworkBehaviour {
public BoardManager BoardController;
public ClientRPCmanager ClientRPCManager;
// Use this for initialization
void Start () {
ClientRPCManager = GameObject.FindGameObjectWithTag("ClientRPCmanager").GetComponent<ClientRPCmanager>();
BoardController = ClientRPCManager.BoardController;
Button b = BoardController.Buttons[0].GetComponent<Button>();
b.onClick.AddListener(delegate () { Cmd_ButtonPressed(1); });
}
// Update is called once per frame
void Update () {
}
public void SetButton(int i)
{
BoardController.SetButton(i);
}
[Command]
public void Cmd_ButtonPressed(int i)
{
ClientRPCManager.Rpc_ButtonPressed(i);
}
boardmanager
public class BoardManager : NetworkBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public GameObject[] Buttons;
public Sprite[] Sprites;
public void SetButton(int index)
{
GameObject img = GameObject.Find("X");
img.GetComponent<Image>().sprite = Sprites[0];
}
RpcManager
public class ClientRPCmanager : NetworkBehaviour {
public BoardManager BoardController;
public NetworkManager Manager;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
[ClientRpc]
public void Rpc_ButtonPressed(int index)
{
BoardController.SetButton(index);
}
hopefully, this will somewhat help people with understanding Unet.
I learned this by carefully looking at this project https://www.youtube.com/watch?v=8Kd2RAfgzW0

How to achieve awareness of "kill" events in a scene

I have been doing a RPG game in Unity with C # and when doing a system of quests, specifically those of killing a certain number of enemies, I found the problem of having 3 enemies in the scene and being the target of the quest: Kill 3 enemies. If I kill them before activating the quest and later active the quest does not give me the reward (in this case experience). How can I tell the enemies and make that if the quest detects that I have already killed the necessary enemies to get the quest give me the reward equally?
Here the two needed scripts i think:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestObject : MonoBehaviour {
public int questNumber;
public QuestManager qManager;
public string startText;
public string endText;
public bool isItemQuest;
public string targetItem;
public bool isEnemyQuest;
public string targetEnemy;
public int enemiesToKill;
private int enemyKillCount;
private PlayerStats playerStats;
public int EXPToGive;
void Start () {
playerStats = FindObjectOfType <PlayerStats> ();
}
void Update () {
if (isItemQuest) {
if (qManager.itemCollected == targetItem) {
qManager.itemCollected = null;
EndQuest ();
}
}
if (isEnemyQuest) {
if (qManager.enemyKilled == targetEnemy) {
qManager.enemyKilled = null;
enemyKillCount++;
}
if (enemyKillCount >= enemiesToKill) {
EndQuest ();
}
}
}
public void StartQuest (){
qManager.ShowQuestText (startText);
}
public void EndQuest (){
qManager.ShowQuestText (endText);
playerStats.AddEXP (EXPToGive);
qManager.questCompleted [questNumber] = true;
gameObject.SetActive (false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public int startingHealth;
public int currentHealth;
public GameObject damageBurst;
private PlayerStats playerStats;
public int EXPToGive;
public string enemyQuestName;
private QuestManager qManager;
void Start ()
{
// Setting up the references.
//anim = GetComponent <Animator> ();
//enemyAudio = GetComponent <AudioSource> ();
//enemyMovement = GetComponent <EnemyMovement> ();
//enemyAttacking = GetComponentInChildren <EnemyAttack> ();
// Set the initial health of the player.
currentHealth = startingHealth;
playerStats = FindObjectOfType <PlayerStats> ();
qManager = FindObjectOfType <QuestManager> ();
}
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
public void TakeDamage (int amountDamage)
{
// Reduce the current health by the damage amount.
currentHealth -= amountDamage;
Instantiate (damageBurst, transform.position, transform.rotation);
}
public void SetMaxHelth () {
currentHealth = startingHealth;
}
}
One Aproach would be to create some type of "WorldManager" which counts every Enemy which has been slain. And when Starting a quest this quest could check the WorldManagers kill count and add it to it's own count.
public void StartQuest (){
qManager.ShowQuestText (startText);
this.enemyKillCount += worldManager.GetKillCount();
}
In your enemy class you have to add a kill to your worldManager.
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
this.worldManager.AddKill(this)
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
Alternative:
Make your QManager be aware of every kill in a Scene.
You can achieve this through many ways.
One of them is passing your EnemyObject an reference of your Qmanager and do the same as with the "WorldManager" provided above, or you use Messaging and fire a Message targeting the QManager when an enemy is slain.
Alternative 2:
Throw an Event when an enemy has been slain and subscribe to it on your QManager/WorldManager. This way u can reuse your enemy class in every game. From my point of view static dependencies are evil, but there are many discussions and SO and everywhere on the internet about that.
You can several approach. The most straight-forward is to use static.
The purpose of static is for the variable/method to belong to the class instead of an instance of the class.
In your case, you want each enemy to have its own health, this cannot be static.
And you want to count how many instances there are in the scene from the class. So static is fine.
public class Enemy:MonoBehaviour
{
private static int enemyCount = 0;
public static int EnemyCount {get{ return enemyCount;} }
public event Action<int> RaiseEnemyDeath;
public static void ResetEnemyCount(){
enemyCount = 0;
}
private int health;
public void Damage(int damage)
{
CheckForDamage(); // here you check that damage is not neg or too big...
this.health -= damage;
if(this.health <= 0)
{
OnDeath();
}
}
void OnActivate()
{
enemyCount++;
this.health = 20;
}
void OnDeath()
{
enemyCount--;
RaiseEnemyDeath(enemyCount); // Should check for nullity...
}
}
This one is fairly simple. The first part is all static and is relevant to the class. The second part is relevant to the instance. If you use a pool of enemy and then reuse the same instance multiple times, the OnActivate method is called when you make the enemy alive in the scene (it may have been there for a while as inactive). Then when the health is down, kill the enemy (there are not all the required actions there...) and trigger the event.
Using the public static property, you can know what is the enemy count from a GameManager (Enemy should not affect the gameplay, only takes care of the enemy).
public class GameManager:MonoBehaviour
{
void Start()
{
Enemy.RaiseEnemyDeath += Enemy_RaiseEnemyDeath;
}
void Enemy_RaiseEnemyDeath(int count)
{
if(count < 0){ // End of level }
// You can also access enemyCount
int count = Enemy.EnemyCount;
}
}
The good point of using this principle is that Enemy has no clue about GameManager and can be reused in another game without any modification. The GameManager is a higher level entity and knows about it.

Categories