I was having trouble converting an object pool script from UnityScript to C#, which I got a lot of good help with here. Now I'm having an issue trying to actually get a game object from the pool. I have three scripts all interacting with one another, so I'm not quite sure where it's going wrong. Here are the two scripts for the object pool, which I believe are all squared away and they're not giving any errors:
public class EasyObjectPool : MonoBehaviour {
[System.Serializable]
public class PoolInfo{
[SerializeField]
public string poolName;
public GameObject prefab;
public int poolSize;
public bool canGrowPoolSize = true;
}
[System.Serializable]
public class Pool{
public List<PoolObject> list = new List<PoolObject>();
public bool canGrowPoolSize;
public void Add (PoolObject poolObject){
list.Add(poolObject);
}
public int Count (){
return list.Count;
}
public PoolObject ObjectAt ( int index ){
PoolObject result = null;
if(index < list.Count) {
result = list[index];
}
return result;
}
}
static public EasyObjectPool instance ;
[SerializeField]
PoolInfo[] poolInfo = null;
private Dictionary<string, Pool> poolDictionary = new Dictionary<string, Pool>();
void Start () {
instance = this;
CheckForDuplicatePoolNames();
CreatePools();
}
private void CheckForDuplicatePoolNames() {
for (int index = 0; index < poolInfo.Length; index++) {
string poolName= poolInfo[index].poolName;
if(poolName.Length == 0) {
Debug.LogError(string.Format("Pool {0} does not have a name!",index));
}
for (int internalIndex = index + 1; internalIndex < poolInfo.Length; internalIndex++) {
if(poolName == poolInfo[internalIndex].poolName) {
Debug.LogError(string.Format("Pool {0} & {1} have the same name. Assign different names.", index, internalIndex));
}
}
}
}
private void CreatePools() {
foreach(PoolInfo currentPoolInfo in poolInfo){
Pool pool = new Pool();
pool.canGrowPoolSize = currentPoolInfo.canGrowPoolSize;
for(int index = 0; index < currentPoolInfo.poolSize; index++) {
//instantiate
GameObject go = Instantiate(currentPoolInfo.prefab) as GameObject;
PoolObject poolObject = go.GetComponent<PoolObject>();
if(poolObject == null) {
Debug.LogError("Prefab must have PoolObject script attached!: " + currentPoolInfo.poolName);
} else {
//set state
poolObject.ReturnToPool();
//add to pool
pool.Add(poolObject);
}
}
Debug.Log("Adding pool for: " + currentPoolInfo.poolName);
poolDictionary[currentPoolInfo.poolName] = pool;
}
}
public PoolObject GetObjectFromPool ( string poolName ){
PoolObject poolObject = null;
if(poolDictionary.ContainsKey(poolName)) {
Pool pool = poolDictionary[poolName];
//get the available object
for (int index = 0; index < pool.Count(); index++) {
PoolObject currentObject = pool.ObjectAt(index);
if(currentObject.AvailableForReuse()) {
//found an available object in pool
poolObject = currentObject;
break;
}
}
if(poolObject == null) {
if(pool.canGrowPoolSize) {
Debug.Log("Increasing pool size by 1: " + poolName);
foreach (PoolInfo currentPoolInfo in poolInfo) {
if(poolName == currentPoolInfo.poolName) {
GameObject go = Instantiate(currentPoolInfo.prefab) as GameObject;
poolObject = go.GetComponent<PoolObject>();
//set state
poolObject.ReturnToPool();
//add to pool
pool.Add(poolObject);
break;
}
}
} else {
Debug.LogWarning("No object available in pool. Consider setting canGrowPoolSize to true.: " + poolName);
}
}
} else {
Debug.LogError("Invalid pool name specified: " + poolName);
}
return poolObject;
}
}
And:
public class PoolObject : MonoBehaviour {
[HideInInspector]
public bool availableForReuse = true;
void Activate () {
availableForReuse = false;
gameObject.SetActive(true);
}
public void ReturnToPool () {
gameObject.SetActive(false);
availableForReuse = true;
}
public bool AvailableForReuse () {
//true when isAvailableForReuse & inactive in hierarchy
return availableForReuse && (gameObject.activeInHierarchy == false);
}
}
The original UnityScript said to retrieve an object from the pool with this statement:
var poolObject : PoolObject = EasyObjectPool.instance.GetObjectFromPool(poolName);
This is how I tried to do that in my shooting script with it trying to fire a bullet prefab from the pool:
public class ShootScript : MonoBehaviour {
public PoolObject poolObject;
private Transform myTransform;
private Transform cameraTransform;
private Vector3 launchPosition = new Vector3();
public float fireRate = 0.2f;
public float nextFire = 0;
// Use this for initialization
void Start () {
myTransform = transform;
cameraTransform = myTransform.FindChild("BulletSpawn");
}
void Update () {
poolObject = EasyObjectPool.instance.GetObjectFromPool<poolName>();
if(Input.GetButton("Fire1") && Time.time > nextFire){
nextFire = Time.time + fireRate;
launchPosition = cameraTransform.TransformPoint(0, 0, 0.2f);
poolObject.Activate();
poolObject.transform.position = launchPosition;
poolObject.transform.rotation = Quaternion.Euler(cameraTransform.eulerAngles.x + 90, myTransform.eulerAngles.y, 0);
}
}
}
My shoot script is giving me two errors:
1. The type or namespace name 'poolName' could not be found. Are you missing a using directive or an assembly reference?
For the line:
poolObject = EasyObjectPool.instance.GetObjectFromPool<poolName>();
2. 'PoolObject.Activate()' is inaccessible due to its protection level
For the line:
poolObject.Activate();
Did I mis-translate the UnityScript or am I missing something else? Any input is greatly appreciated
I think your object pool is incorrectly programmed. When you have a method like CheckForDuplicatePoolNames() ask yourself if you need a different collection like dictionary.
Keep it simple.
I recently implemented one and I have tested. It works pretty well.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class PoolObject{
public GameObject prefab;
public int startNum;
public int allocateNum;
public Transform anchor;
}
public class ObjectPool<T> : MonoBehaviour where T:Component{
public PoolObject[] poolObjects;
public bool ______________;
public Dictionary<string,Queue<T>> pool = new Dictionary<string, Queue<T>> ();
public Dictionary<string, List<T>> spawned = new Dictionary<string, List<T>> ();
public Transform anchor;
public void Init(){
InitPool ();
StartAllocate ();
}
public T Spawn(string type){
if (pool [type].Count == 0) {
int i = FindPrefabPosition(type);
if(poolObjects[i].allocateNum == 0)
return null;
Allocate(poolObjects[i], poolObjects[i].allocateNum);
}
T t = pool [type].Dequeue ();
spawned[type].Add (t);
t.gameObject.SetActive (true);
return t;
}
public void Recycle(T t){
if (spawned [t.name].Remove (t)) {
pool[t.name].Enqueue(t);
t.gameObject.SetActive(false);
}
}
private void Allocate(PoolObject po, int number){
for (int i=0; i<number; i++) {
GameObject go = Instantiate (po.prefab) as GameObject;
go.name = po.prefab.name;
go.transform.parent = po.anchor != null? po.anchor : anchor;
T t = go.GetComponent<T>();
pool[t.name].Enqueue(t);
t.gameObject.SetActive(false);
}
}
private int FindPrefabPosition(string type){
int position = 0;
while (poolObjects[position].prefab.name != type) {
position++;
}
return position;
}
void InitPool(){
if(anchor == null)
anchor = new GameObject("AnchorPool").transform;
for (int i =0; i<poolObjects.Length; i++) {
T t = poolObjects[i].prefab.GetComponent<T>();
t.name = poolObjects[i].prefab.name;
pool[t.name] = new Queue<T>();
spawned[t.name] = new List<T>();
}
}
void StartAllocate(){
for (int i=0; i<poolObjects.Length; i++) {
Allocate(poolObjects[i], poolObjects[i].startNum );
}
}
}
Example:
public class CoinsPool : ObjectPool<CoinScript>{}
In the unity inspector configure the coin prefab, the startNum and the allocateNum.
Somewhere:
void Awake(){
coinsPool = GetComponent<CoinsPool>();
coinsPool.Init();
}
CoinScript cs = coinsPool.Spawn("Coin"); //Coin is the name of the coin prefab.
Later
coinsPool.Recycle(cs);
The thing you write within <> should be a class name like PoolObject if the function is generic, which it is not. So to solve this you simply need to change
poolObject = EasyObjectPool.instance.GetObjectFromPool<poolName>();
to
string poolName = "thePoolNameYouWant";
poolObject = EasyObjectPool.instance.GetObjectFromPool(poolName);
Function are private by default so to solve the "inaccessible due to its protection level" error you need to make the function public by changing this
void Activate () {
availableForReuse = false;
gameObject.SetActive(true);
}
to this
public void Activate () {
availableForReuse = false;
gameObject.SetActive(true);
}
Related
I created an object pooling function, and when I call a Get() function in a player script, I don't want to pass it a number like Get(0), but instead I want to call it like Get((int)ObjPrefabs.PlayerBullet).
So I tried to use "enum", but I don't know how to assign the prefab object to enum.
public class ObjectManagerFirst : MonoBehaviour
{
public GameObject[] prefabs;
public enum ObjPrefabs {PlayerBullet};
List<GameObject>[] pools;
void Awake()
{
pools = new List<GameObject>[prefabs.Length];
for (int index = 0; index < pools.Length; index++)
{
pools[index] = new List<GameObject>();
}
}
public GameObject Get(int index)
{
GameObject select = null;
foreach(GameObject item in pools[index])
{
if(!item.activeSelf)
{
select = item;
select.SetActive(true);
break;
}
}
if(!select)
{
select = Instantiate(prefabs[index], transform);
pools[index].Add(select);
}
return select;
}
}
This is current way I call it in the Player Script:
GameObject bullet = objectManager.Get(0);
But I want:
GameObject bullet = objectManager.Get((int)ObjPrefabs.PlayerBullet);
I tried already tried the following, but it didn't work:
List<int> ObjList = new List<int>();
int value = ObjList[(int)ObjPrefabs.PlayerBullet]
You can assign int values to enum and then pass it to function here is an example
enum ObjPrefabs
{
PlayerBullet = 0,
EnemyBullet = 1
}
and then call your Get function like this
GameObject bullet = objectManager.Get((int)ObjPrefabs.PlayerBullet);
and it'll work.
Instead of using int for the index why not rather stick to the enum and use a
private Dictionary<ObjPrefabs, XXX> pools;
and rather use
public GameObject Get(ObjPrefabs type)
{
if(!pools.TryGetValue(type, out var pool)
{
Debug.LogError($"Pool for {type} has not been initialized!");
return null;
}
// otherwise get object from "pool"
}
For the XXX see below
Now to the pool itself .. an array is fine but even better would be to e.g. simply use a Queue.
I would recommend a more complex but easier to handle pooling approach:
[Serializable]
public class Pool
{
public class PoolObject : MonoBehaviour
{
private Pool _pool;
public void Initialize (Pool pool)
{
_pool = pool;
}
// Use this component and method to release instances instead of calling Destroy!
public void Release()
{
_pool.Release(this);
}
private void OnDestroy ()
{
Debug.LogWarning("Destroyed a pooled object! Rather use Release!", this);
_pool.OnDestroyed(this);
}
}
// In theory you could even get rid of this enum and use the prefab itself as key ;)
[SerializeField] private ObjPrefab _type;
[SerializeField] private GameObject _prefab;
[SerializeField] [Min(1)] private int _initialAmount;
private readonly Queue<PoolObject> _availableInstances = new();
private readonly List<PoolObject> _hotInstaces = new();
public ObjPrefabs Type => _type;
public void Initialize (Transform inactiveParent)
{
for(var i = 0; i < _initialAmount; i++)
{
var instance = CreateInstance();
instance.gameObject.SetActive(false);
_availableInstances.Enqueue(instance);
}
}
private PoolObject CreateInstance ()
{
var instance = Instantiate (_prefab, inactiveParent);
if(! instance.TryGetComponent<PoolObject>(out var obj))
{
obj = instance.AddComponent<PoolObject>();
}
obj.Initialize(this);
}
private void OnDestroyed (PoolObject obj)
{
hotInstaces.Remove(obj);
}
public PoolObject GetInstance()
{
var instance = _avaialableInstances.Count != 0 ? _availableInstances.Dequeue() : CreateInstance();
instance.gameObject.SetActive(true);
hotInstances.Add(instance);
return instance;
}
public void Release(PoolObject instance)
{
if(_hotInstances.Remove(instance))
{
instance.gameObject.SetActive(false);
_availableInstances.Enqueue(instance);
}
else
{
Debug.LogError($"{instance} isn't an active object!");
}
}
}
And then finally you would use
[SerializeField] private Pool[] _pools;
private readonly Dictionary<ObjPrefabs, Pool> _activePools = new();
private void Awake ()
{
foreach(var pool in _pools)
{
if(_activePools.ContainsKey(pool.Type))
{
Debug.LogError($"Duplicate pools for type {Type}!");
continue;
}
pool.Initialize(transform);
_activePools[pool.Type] = pool;
}
}
public Pool.PoolObject Get(ObjPrefabs type)
{
if(!pools.TryGetValue(type, out var pool)
{
Debug.LogError($"Pool for {type} has not been initialized!");
return null;
}
return pool.GetInstance();
}
I am developing a Tower Defence game and I need a wave spawner. I tried to use Brackeys Wave spawner but it only supports one type of enemy per wave and I tried to make one myself like this:
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
instead of this:
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
This is the code from the 40min Brackeys video
using UnityEngine;
using System.Collections;
public class WaveSpawner : MonoBehaviour {
public enum SpawnState { SPAWNING, WAITING, COUNTING };
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
public Wave[] waves;
private int nextWave = 0;
public int NextWave
{
get { return nextWave + 1; }
}
public Transform[] spawnPoints;
public float timeBetweenWaves = 5f;
private float waveCountdown;
public float WaveCountdown
{
get { return waveCountdown; }
}
private float searchCountdown = 1f;
private SpawnState state = SpawnState.COUNTING;
public SpawnState State
{
get { return state; }
}
void Start()
{
if (spawnPoints.Length == 0)
{
Debug.LogError("No spawn points referenced.");
}
waveCountdown = timeBetweenWaves;
}
void Update()
{
if (state == SpawnState.WAITING)
{
state = SpawnState.COUNTING;
/*if (!EnemyIsAlive())
{
WaveCompleted();
}
else
{
return;
}*/
}
if (waveCountdown <= 0)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine( SpawnWave ( waves[nextWave] ) );
}
}
else
{
waveCountdown -= Time.deltaTime;
}
}
void WaveCompleted()
{
Debug.Log("Wave Completed!");
state = SpawnState.COUNTING;
waveCountdown = timeBetweenWaves;
if (nextWave + 1 > waves.Length - 1)
{
nextWave = 0;
Debug.Log("ALL WAVES COMPLETE! Looping...");
}
else
{
nextWave++;
}
}
bool EnemyIsAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0f)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectWithTag("Enemy") == null)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
for (int i = 0; i < _wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform _sp = spawnPoints[ Random.Range (0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
}
And I need to add this to the code to get what I am expecting
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
But I was not able to, can anyone help me?
Thanks in advance,
Dev
You should look at his code, there will be those lines of code :
IEnumerator SpawnWave(Wave _wave)
{
state = SpawnState.SPAWNING;
for (int i = 0; i < wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
And also those lines of code :
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
So what is the problem, the problem is that the SpawnEnemy() method is using only one type of enemies each wave, using _enemy from class Wave which is in [System.Serializable], so my idea is that we will make another class Wave, same same as his code :
[System.Serializable]
public class Wave
{
public string name;
public Transform[] enemies;
public int count;
public float rate;
}
And change a little bit in SpawnEnemy() method, which I will add Random.Rage() to choose a different transform ( or we can say enemy )
void SpawnEnemy(Transform[] _enemies)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
int randomIndex = Random.Range(0, _enemies.Count());
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemies[randomIndex], _sp.position, _sp.rotation);
}
If having any trouble when doing, just comment below, yeah
After reading through the existing comments, I think you really only need one additional edit within the SpawnWave code to achieve what you are looking for - Add a foreach loop to loop through each Wave Contents object and spawn the appropriate number and type of enemy
Given your updated objects, with one update to a field name for clarity
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] WaveContents;
public float Rate = 5f;
}
Then you just need to loop through the array of WaveContent and call SpawnEnemy for each one, using the WaveContent.Count for the inner loop.
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
// Loop through your Wave Contents
foreach (var waveContent in Wave.WaveContents)
{
// Spawn the number and type of enemy for each content object
for (int i = 0; i < waveContent.count; i++)
{
SpawnEnemy(waveContent.enemy);
yield return new WaitForSeconds(1f / _wave.rate);
}
}
state = SpawnState.WAITING;
yield break;
}
My Game is working perfectly in Unity but when I export apk tp Android Mobile 2nd Scene doesn't load properly and does not work.
Please look at the image. It is the second Scene and not loaded properly and the Run button is not working which is working perfectly in unity play mode.
enter image description here
CODES ARE BELOW
SCENE 1 CODE#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_ANALYTICS
using UnityEngine.Analytics;
#endif
#if UNITY_PURCHASING
using UnityEngine.Purchasing;
#endif
public class StartButton : MonoBehaviour
{
public void StartGame()
{
if (PlayerData.instance.ftueLevel == 0)
{
PlayerData.instance.ftueLevel = 1;
PlayerData.instance.Save();
#if UNITY_ANALYTICS
AnalyticsEvent.FirstInteraction("start_button_pressed");
#endif
}
#if UNITY_PURCHASING
var module = StandardPurchasingModule.Instance();
#endif
SceneManager.LoadScene("main");
}
}
SCENE 2 CODE#
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
#if UNITY_ANALYTICS
using UnityEngine.Analytics;
#endif
/// <summary>
/// State pushed on the GameManager during the Loadout, when player select player, theme and accessories
/// Take care of init the UI, load all the data used for it etc.
/// </summary>
public class LoadoutState : AState
{
public Canvas inventoryCanvas;
[Header("Char UI")]
public Text charNameDisplay;
public RectTransform charSelect;
public Transform charPosition;
[Header("Theme UI")]
public Text themeNameDisplay;
public RectTransform themeSelect;
public Image themeIcon;
[Header("PowerUp UI")]
public RectTransform powerupSelect;
public Image powerupIcon;
public Text powerupCount;
public Sprite noItemIcon;
[Header("Accessory UI")]
public RectTransform accessoriesSelector;
public Text accesoryNameDisplay;
public Image accessoryIconDisplay;
[Header("Other Data")]
public Leaderboard leaderboard;
public MissionUI missionPopup;
public Button runButton;
public GameObject tutorialBlocker;
public GameObject tutorialPrompt;
public MeshFilter skyMeshFilter;
public MeshFilter UIGroundFilter;
public AudioClip menuTheme;
[Header("Prefabs")]
public ConsumableIcon consumableIcon;
Consumable.ConsumableType m_PowerupToUse = Consumable.ConsumableType.NONE;
protected GameObject m_Character;
protected List<int> m_OwnedAccesories = new List<int>();
protected int m_UsedAccessory = -1;
protected int m_UsedPowerupIndex;
protected bool m_IsLoadingCharacter;
protected Modifier m_CurrentModifier = new Modifier();
protected const float k_CharacterRotationSpeed = 45f;
protected const string k_ShopSceneName = "shop";
protected const float k_OwnedAccessoriesCharacterOffset = -0.1f;
protected int k_UILayer;
protected readonly Quaternion k_FlippedYAxisRotation = Quaternion.Euler (0f, 180f, 0f);
public override void Enter(AState from)
{
tutorialBlocker.SetActive(!PlayerData.instance.tutorialDone);
tutorialPrompt.SetActive(false);
inventoryCanvas.gameObject.SetActive(true);
missionPopup.gameObject.SetActive(false);
charNameDisplay.text = "";
themeNameDisplay.text = "";
k_UILayer = LayerMask.NameToLayer("UI");
skyMeshFilter.gameObject.SetActive(true);
UIGroundFilter.gameObject.SetActive(true);
// Reseting the global blinking value. Can happen if the game unexpectedly exited while still blinking
Shader.SetGlobalFloat("_BlinkingValue", 0.0f);
if (MusicPlayer.instance.GetStem(0) != menuTheme)
{
MusicPlayer.instance.SetStem(0, menuTheme);
StartCoroutine(MusicPlayer.instance.RestartAllStems());
}
runButton.interactable = false;
runButton.GetComponentInChildren<Text>().text = "Loading...";
if(m_PowerupToUse != Consumable.ConsumableType.NONE)
{
//if we come back from a run and we don't have any more of the powerup we wanted to use, we reset the powerup to use to NONE
if (!PlayerData.instance.consumables.ContainsKey(m_PowerupToUse) || PlayerData.instance.consumables[m_PowerupToUse] == 0)
m_PowerupToUse = Consumable.ConsumableType.NONE;
}
Refresh();
}
public override void Exit(AState to)
{
missionPopup.gameObject.SetActive(false);
inventoryCanvas.gameObject.SetActive(false);
if (m_Character != null) Addressables.ReleaseInstance(m_Character);
GameState gs = to as GameState;
skyMeshFilter.gameObject.SetActive(false);
UIGroundFilter.gameObject.SetActive(false);
if (gs != null)
{
gs.currentModifier = m_CurrentModifier;
// We reset the modifier to a default one, for next run (if a new modifier is applied, it will replace this default one before the run starts)
m_CurrentModifier = new Modifier();
if (m_PowerupToUse != Consumable.ConsumableType.NONE)
{
PlayerData.instance.Consume(m_PowerupToUse);
Consumable inv = Instantiate(ConsumableDatabase.GetConsumbale(m_PowerupToUse));
inv.gameObject.SetActive(false);
gs.trackManager.characterController.inventory = inv;
}
}
}
public void Refresh()
{
PopulatePowerup();
StartCoroutine(PopulateCharacters());
StartCoroutine(PopulateTheme());
}
public override string GetName()
{
return "Loadout";
}
public override void Tick()
{
if (!runButton.interactable)
{
bool interactable = ThemeDatabase.loaded && CharacterDatabase.loaded;
if(interactable)
{
runButton.interactable = true;
runButton.GetComponentInChildren<Text>().text = "Run!";
//we can always enabled, as the parent will be disabled if tutorial is already done
tutorialPrompt.SetActive(true);
}
}
if(m_Character != null)
{
m_Character.transform.Rotate(0, k_CharacterRotationSpeed * Time.deltaTime, 0, Space.Self);
}
charSelect.gameObject.SetActive(PlayerData.instance.characters.Count > 1);
themeSelect.gameObject.SetActive(PlayerData.instance.themes.Count > 1);
}
public void GoToStore()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(k_ShopSceneName, UnityEngine.SceneManagement.LoadSceneMode.Additive);
}
public void ChangeCharacter(int dir)
{
PlayerData.instance.usedCharacter += dir;
if (PlayerData.instance.usedCharacter >= PlayerData.instance.characters.Count)
PlayerData.instance.usedCharacter = 0;
else if(PlayerData.instance.usedCharacter < 0)
PlayerData.instance.usedCharacter = PlayerData.instance.characters.Count-1;
StartCoroutine(PopulateCharacters());
}
public void ChangeAccessory(int dir)
{
m_UsedAccessory += dir;
if (m_UsedAccessory >= m_OwnedAccesories.Count)
m_UsedAccessory = -1;
else if (m_UsedAccessory < -1)
m_UsedAccessory = m_OwnedAccesories.Count-1;
if (m_UsedAccessory != -1)
PlayerData.instance.usedAccessory = m_OwnedAccesories[m_UsedAccessory];
else
PlayerData.instance.usedAccessory = -1;
SetupAccessory();
}
public void ChangeTheme(int dir)
{
PlayerData.instance.usedTheme += dir;
if (PlayerData.instance.usedTheme >= PlayerData.instance.themes.Count)
PlayerData.instance.usedTheme = 0;
else if (PlayerData.instance.usedTheme < 0)
PlayerData.instance.usedTheme = PlayerData.instance.themes.Count - 1;
StartCoroutine(PopulateTheme());
}
public IEnumerator PopulateTheme()
{
ThemeData t = null;
while (t == null)
{
t = ThemeDatabase.GetThemeData(PlayerData.instance.themes[PlayerData.instance.usedTheme]);
yield return null;
}
themeNameDisplay.text = t.themeName;
themeIcon.sprite = t.themeIcon;
skyMeshFilter.sharedMesh = t.skyMesh;
UIGroundFilter.sharedMesh = t.UIGroundMesh;
}
public IEnumerator PopulateCharacters()
{
accessoriesSelector.gameObject.SetActive(false);
PlayerData.instance.usedAccessory = -1;
m_UsedAccessory = -1;
if (!m_IsLoadingCharacter)
{
m_IsLoadingCharacter = true;
GameObject newChar = null;
while (newChar == null)
{
Character c = CharacterDatabase.GetCharacter(PlayerData.instance.characters[PlayerData.instance.usedCharacter]);
if (c != null)
{
m_OwnedAccesories.Clear();
for (int i = 0; i < c.accessories.Length; ++i)
{
// Check which accessories we own.
string compoundName = c.characterName + ":" + c.accessories[i].accessoryName;
if (PlayerData.instance.characterAccessories.Contains(compoundName))
{
m_OwnedAccesories.Add(i);
}
}
Vector3 pos = charPosition.transform.position;
if (m_OwnedAccesories.Count > 0)
{
pos.x = k_OwnedAccessoriesCharacterOffset;
}
else
{
pos.x = 0.0f;
}
charPosition.transform.position = pos;
accessoriesSelector.gameObject.SetActive(m_OwnedAccesories.Count > 0);
AsyncOperationHandle op = Addressables.InstantiateAsync(c.characterName);
yield return op;
if (op.Result == null || !(op.Result is GameObject))
{
Debug.LogWarning(string.Format("Unable to load character {0}.", c.characterName));
yield break;
}
newChar = op.Result as GameObject;
Helpers.SetRendererLayerRecursive(newChar, k_UILayer);
newChar.transform.SetParent(charPosition, false);
newChar.transform.rotation = k_FlippedYAxisRotation;
if (m_Character != null)
Addressables.ReleaseInstance(m_Character);
m_Character = newChar;
charNameDisplay.text = c.characterName;
m_Character.transform.localPosition = Vector3.right * 1000;
//animator will take a frame to initialize, during which the character will be in a T-pose.
//So we move the character off screen, wait that initialised frame, then move the character back in place.
//That avoid an ugly "T-pose" flash time
yield return new WaitForEndOfFrame();
m_Character.transform.localPosition = Vector3.zero;
SetupAccessory();
}
else
yield return new WaitForSeconds(1.0f);
}
m_IsLoadingCharacter = false;
}
}
void SetupAccessory()
{
Character c = m_Character.GetComponent<Character>();
c.SetupAccesory(PlayerData.instance.usedAccessory);
if (PlayerData.instance.usedAccessory == -1)
{
accesoryNameDisplay.text = "None";
accessoryIconDisplay.enabled = false;
}
else
{
accessoryIconDisplay.enabled = true;
accesoryNameDisplay.text = c.accessories[PlayerData.instance.usedAccessory].accessoryName;
accessoryIconDisplay.sprite = c.accessories[PlayerData.instance.usedAccessory].accessoryIcon;
}
}
void PopulatePowerup()
{
powerupIcon.gameObject.SetActive(true);
if (PlayerData.instance.consumables.Count > 0)
{
Consumable c = ConsumableDatabase.GetConsumbale(m_PowerupToUse);
powerupSelect.gameObject.SetActive(true);
if (c != null)
{
powerupIcon.sprite = c.icon;
powerupCount.text = PlayerData.instance.consumables[m_PowerupToUse].ToString();
}
else
{
powerupIcon.sprite = noItemIcon;
powerupCount.text = "";
}
}
else
{
powerupSelect.gameObject.SetActive(false);
}
}
public void ChangeConsumable(int dir)
{
bool found = false;
do
{
m_UsedPowerupIndex += dir;
if(m_UsedPowerupIndex >= (int)Consumable.ConsumableType.MAX_COUNT)
{
m_UsedPowerupIndex = 0;
}
else if(m_UsedPowerupIndex < 0)
{
m_UsedPowerupIndex = (int)Consumable.ConsumableType.MAX_COUNT - 1;
}
int count = 0;
if(PlayerData.instance.consumables.TryGetValue((Consumable.ConsumableType)m_UsedPowerupIndex, out count) && count > 0)
{
found = true;
}
} while (m_UsedPowerupIndex != 0 && !found);
m_PowerupToUse = (Consumable.ConsumableType)m_UsedPowerupIndex;
PopulatePowerup();
}
public void UnequipPowerup()
{
m_PowerupToUse = Consumable.ConsumableType.NONE;
}
public void SetModifier(Modifier modifier)
{
m_CurrentModifier = modifier;
}
public void StartGame()
{
if (PlayerData.instance.tutorialDone)
{
if (PlayerData.instance.ftueLevel == 1)
{
PlayerData.instance.ftueLevel = 2;
PlayerData.instance.Save();
}
}
manager.SwitchState("Game");
}
public void Openleaderboard()
{
leaderboard.displayPlayer = false;
leaderboard.forcePlayerDisplay = false;
leaderboard.Open();
}
}
When you are building an APK file it can be splited to APK + OBB.
So, it is possible that you are trying to load a scene that is absent in your APK. You need to force the creation of the APK without the OBB. To do it you need to check Player Settings in Android, go to Publishing Settings and uncheck Split Application Binary.
so I have like 5 game object in my scene but I only scale each of them separately. However when I try to do that all of them start scaling simultaneously. Also, I have a placement indicator that would be used to instantiate the object on the plane. It seems that instead of the object itself, the placement indicator is the one that gets scaled. How should I fix that?
I have tried deactivating the placement indicator but did not work.
Here is the code for instantiating objects:
I limited the obj number to 5.
I use this script instead of the usual "PlaceonPlane" script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.Experimental.XR;
using UnityEngine.UI;
using UnityEngine.XR.ARSubsystems;
public class ARTaptoPlaceObject : MonoBehaviour
{
private ARSessionOrigin arOrigin;
GameObject spawnedobj;
public GameObject placementIndicator;
private ARRaycastManager arRaycast;
public Pose placementPose;
public UIContoller sc;
public bool placementPoseIsValid = false;
private int count;
private string valu;
string prefabs;
void Start()
{
arOrigin = FindObjectOfType<ARSessionOrigin>();
arRaycast = FindObjectOfType<ARRaycastManager>();
count = 0;
}
// Update is called once per frame
void Update()
{
UpdatePlacementPose();
UpdatePlacementIndicator();
for (var i = 0; i < Input.touchCount; ++i)
{
if (Input.GetTouch(i).phase == TouchPhase.Began)
{
if (placementPoseIsValid && Input.GetTouch(i).tapCount == 2)
{
PlaceObject();
}
}
}
}
public void PlaceObject()
{
if (count <= 4)
{
if (sc.objectToPlace != null)
{
spawnedobj = Instantiate(sc.objectToPlace, placementPose.position, placementPose.rotation);
arOrigin.MakeContentAppearAt(spawnedobj.transform, spawnedobj.transform.position, spawnedobj.transform.rotation);
count++;
}
}
else
{
placementIndicator.SetActive(false);
}
}
private void UpdatePlacementIndicator()
{
if (placementPoseIsValid && count <= 4 && sc.active == false)
{
placementIndicator.SetActive(true);
placementIndicator.transform.SetPositionAndRotation(placementPose.position, placementPose.rotation);
}
else
{
placementIndicator.SetActive(false);
}
}
private void UpdatePlacementPose()
{
var screenCenter = Camera.current.ViewportToScreenPoint(new Vector3(0.5f, 0.5f));
var hits = new List<ARRaycastHit>();
arRaycast.Raycast(screenCenter, hits, UnityEngine.XR.ARSubsystems.TrackableType.Planes);
placementPoseIsValid = hits.Count > 0;
if (placementPoseIsValid)
{
placementPose = hits[0].pose;
var cameraForward = Camera.current.transform.forward;
var cameraBearing = new Vector3(cameraForward.x, 0, cameraForward.z).normalized;
placementPose.rotation = Quaternion.LookRotation(cameraBearing);
}
}
}
and here is the Scaler script that's attached to the button that would scale the object.
public class Scaler : MonoBehaviour
{
public UIContoller uc;
public ARTaptoPlaceObject ap;
private GameObject ReferenceToScale;
public void OnValueChange()
{
ReferenceToScale = (UnityEngine.GameObject)Resources.Load(uc.s_count, typeof(GameObject));
Vector3 t = ReferenceToScale.transform.localScale;
Vector3 scaleValue = t * 1.1f;
ReferenceToScale.transform.localScale = scaleValue;
}
Also the "objectToPlace" itself is in the "UI.Controller" script as I could not view it in the scene when it was in the "ARTaptoPlace" script
Today I'm coming to you because I have a weird StackOverflow Exception and do not know how to fix it at all...
First off, this seems to only happen on windows after I build the game.
This is what I see in the output_log.txt :
onMoneyChanged is being called! (4145)
UnityEngine.DebugLogHandler:Internal_Log(LogType, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
Player:set_Money(Int32) (at /Users/Name/Desktop/My Game/Assets/Scripts/Mobs/Player.cs:89)
Coin:OnPickup(ItemCollector) (at /Users/Name/Desktop/My Game/Assets/Scripts/Items/Coin.cs:12)
ItemCollector:Update() (at /Users/Name/Desktop/My Game/Assets/Scripts/Items/ItemCollector.cs:35)
(Filename: /Users/Name/Desktop/My Game/Assets/Scripts/Mobs/Player.cs Line: 89)
onMoneyChanged is being called! (4150)
UnityEngine.DebugLogHandler:Internal_Log(LogType, String, Object)
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
Player:set_Money(Int32) (at /Users/Name/Desktop/My Game/Assets/Scripts/Mobs/Player.cs:89)
Coin:OnPickup(ItemCollector) (at /Users/Name/Desktop/My Game/Assets/Scripts/Items/Coin.cs:12)
ItemCollector:Update() (at /Users/Name/Desktop/My Game/Assets/Scripts/Items/ItemCollector.cs:35)
(Filename: /Users/Name/Desktop/My Game/Assets/Scripts/Mobs/Player.cs Line: 89)
Uploading Crash Report
StackOverflowException: The requested operation caused a stack overflow.
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
at (wrapper delegate-invoke) System.Action:invoke_void__this__ ()
I have looked everywhere and can't seem to understand where it comes from. I might not be seeing something very simple...
Here is the player script:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
using UnityEngine;
using UnityEngine.Events;
using PixelUtilities;
public class Player : Mob, IUpgradable, IXmlSerializable {
#region Static Section
private static readonly AnimationParameter Skill2SpeedId = "Skill 2 Speed";
private static readonly string[] AttackAxisNames = new string[] {
null,
"Attack1",
"Attack2",
"Attack3"
};
//It seems this must be longer in duration than the transition into the attack state, due to the potential transition interruption sources!
private const float AttackUnjoggableTime = 0.65f;
#endregion
[Header("Player")]
[SerializeField] private float skill2Speed = 1;
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckRadius = 0.25f;
[SerializeField] private LayerMask groundLayers;
[SerializeField] private bool isGrounded;
[SerializeField] GameObject weaponSpecial;
private new Rigidbody2D rigidbody;
private new BoxCollider2D collider;
private float horizontal;
private float smoothedHorizontal;
private Vector3 velocity;
private int superCharge; //Represents the number of enemies defeated that counts towards allowing the player to use their super attack.
private bool canIncreaseSuperCharge = true;
private bool canSuperSmash;
private int money = 0;
//private int roundMoney = 0;
private int[] levels = new int[2];
//DO NOT show in the inspector. That will make it changeable without actually updating the data
//that needs to change based based on this upgradeLevel.
private int baseDamage;
private float timeLastAttacked;
public event Action onMoneyChanged;
public bool CanSuperSmash {
get { return canSuperSmash; }
set {
//Prevent from redundant setting, because we'll need to know
//just when the value changed from false to true
if (canSuperSmash == value)
return;
canSuperSmash = value;
weaponSpecial.SetActive(value);
if (value)
superCharge = GameManager.Mage.SpecialEnemyCount;
}
}
public BoxCollider2D Collider {
get { return collider; }
}
public int SuperCharge {
get { return superCharge; }
}
public int LevelCount {
get { return levels.Length; }
}
public int Money {
get { return money; }
set {
if (GameManager.IsDebugMode)
Debug.Log("Setting the player's money from " + money + " to " + value + ".");
money = value;
Debug.Log("onMoneyChanged is being called! (" + money + ")");
if (onMoneyChanged != null)
onMoneyChanged();
}
}
//public int RoundMoney {
// get { return roundMoney; }
// set {
// roundMoney = value;
// Debug.Log("roundMoney has been set to " + roundMoney + ".");
// }
//}
public override void Reset() {
base.Reset();
groundLayers = LayerMask.GetMask("Ground");
}
public override void Awake() {
base.Awake();
collider = GetComponentInChildren<BoxCollider2D>();
HPStatus.onDeath += OnDeath;
baseDamage = StandTallCurves.GetNthStepInEnemyHealthCurve(0) / 2;
}
public override void Start() {
rigidbody = GetComponent<Rigidbody2D>();
}
public int GetLevel(int levelIndex) {
return levels[levelIndex];
}
public void SetLevel(int levelIndex, int value) {
value = Mathf.Max(0, value);
levels[levelIndex] = value;
switch (levelIndex) {
case 0:
baseDamage = StandTallCurves.GetNthStepInEnemyHealthCurve(value) / 2;
break;
case 1:
HPStatus.HP = HPStatus.MaxHP = StandTallCurves.GetNthStepInEnemyHealthCurve(value);
break;
}
}
public override void StartAttack(int attackNumber) {
base.StartAttack(attackNumber);
timeLastAttacked = Time.time;
}
protected override void OnUpdate() {
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayers);
if (CanPerformActions)
AcceptAttackInput();
AcceptMovementInput();
//AcceptJumpInput();
//AcceptRotationalInput();
if (GameManager.IsDeveloperMode) {
if (Input.GetKeyDown(KeyCode.C)) {
Money += 1000;
}
if (Input.GetKeyDown(KeyCode.Y)) {
CanSuperSmash = true;
}
if (Input.GetKeyDown(KeyCode.T) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftCommand))) {
Time.timeScale = (Time.timeScale == 1) ? 5 : 1;
}
}
}
private void AcceptMovementInput() {
horizontal = hInput.GetAxis("Horizontal");
//smoothedHorizontal = Input.GetAxis("Horizontal");
velocity = rigidbody.velocity;
velocity.x = Mathf.Sign(horizontal) * FinalMovementSpeed;
rigidbody.velocity = velocity;
//Section for updating their rotation
if (horizontal < 0) {
Quaternion newRotation = transform.rotation;
newRotation.y = 180;
transform.rotation = newRotation;
}
if (horizontal > 0) {
Quaternion newRotation = transform.rotation;
newRotation.y = 0;
transform.rotation = newRotation;
}
}
private void AcceptAttackInput() {
for (int i = 1; i <= 2; i++) {
if (hInput.GetButtonDown(AttackAxisNames[i])) {
StartAttack(i);
return;
}
}
//Attack 3 is special for the player -- their super smash -- and is handled differently.
if (hInput.GetButtonDown(AttackAxisNames[3])) {
if (canSuperSmash) {
StartSuperSmash();
} else {
if (GameManager.IsDebugMode) {
Debug.Log("Attempted to super smash, but " + name + " was unable to super smash!");
}
}
}
}
protected override float CalculateFinalMovementSpeed() {
float finalMovementSpeed = Mathf.Abs(horizontal) * MoveSettings.MoveSpeed;
finalMovementSpeed *= MovementFactor;
return finalMovementSpeed;
}
protected override void UpdateContinuousAnimatorParameters() {
animator.SetBool(Animations.IsMovingId, Time.time - timeLastAttacked > AttackUnjoggableTime && Mathf.Abs(horizontal) > 0.08f && Mathf.Abs(velocity.x) > 0.08f);
//animator.SetFloat(Skill2SpeedId, skill2Speed);
}
/* Uncomment to enable jumping
private void AcceptJumpInput() {
if (Input.GetButtonDown("Jump") && isGrounded) {
rigidbody.velocity = new Vector3(rigidbody.velocity.x, JumpFactor * MoveSettings.JumpSpeed, 0);
if (JumpFactor > 0)
animator.SetTrigger(JumpId);
}
}*/
/// <summary>
/// This tells the whether or not the super charge count can be increased.
/// </summary>
public void SetSuperChargeActive(bool value) {
canIncreaseSuperCharge = value;
}
public bool GainSuperCharge() {
return GainSuperCharge(1);
}
public bool GainSuperCharge(int amount) {
//If they're already charged up, then don't allow superCharge to increment
if (!GameManager.Mage.gameObject.activeSelf || !canIncreaseSuperCharge || canSuperSmash)
return false;
superCharge = Mathf.Clamp(superCharge + amount, 0, GameManager.Mage.SpecialEnemyCount);
if (superCharge == GameManager.Mage.SpecialEnemyCount) {
CanSuperSmash = true; //Important to call the C# property here, NOT directly access the field, "canSuperSmash".
}
return true;
}
private void StartSuperSmash() {
SetSuperChargeActive(false);
CanSuperSmash = false;
StartCoroutine(AttackAfterDelay(3, 0)); //0 was initially 0.8f;
}
public void ClearSuperCharge() {
if (GameManager.IsDebugMode)
Debug.Log("superCharge set to 0!");
superCharge = 0;
}
private IEnumerator AttackAfterDelay(int attackNumber, float initialDelay) {
if (initialDelay > 0)
yield return new WaitForSeconds(initialDelay);
StartAttack(attackNumber);
}
public override void OnDrawGizmosSelected() {
base.OnDrawGizmosSelected();
if (groundCheck != null) {
Gizmos.color = new Color(0.6f, 1, 0, 1);
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
}
attackChecker.DrawGizmos(Color.red);
}
private void InstantKillAllInView() {
Camera camera = Camera.main;
Vector3 cameraPos = camera.transform.position;
float height = 2 * camera.orthographicSize;
float width = height * camera.aspect; //h * (w/h) = w
RaycastHit2D[] hits = Physics2D.BoxCastAll((Vector2) cameraPos, new Vector2(width, height), 0, Vector2.right, 0.01f, enemyLayers, -0.01f, 0.01f);
for (int i = 0; i < hits.Length; i++) {
Mob target = hits[i].transform.GetComponent<Mob>();
if (target == null)
continue;
target.InstantSuperKill(this);
}
}
public void DamageOthersInRangeFromPlayer() {
DamageOthersInRange(baseDamage);
}
private void OnDeath(DamageInfo finalDamage) {
GameManager.StartGameOverScreen();
}
public void Load(XElement element) {
if (element == null)
return;
int intValue;
XElement child = element.Element("Money");
if (child != null && int.TryParse(child.Value, out intValue))
Money = intValue;
child = element.Element("HealthUpgrade");
if (child != null) {
//Old serialized layout
//if (int.TryParse(child.Value, out intValue))
// HealthUpgradeLevel = intValue;
//child = element.Element("DamageUpgrade");
//if (child != null && int.TryParse(child.Value, out intValue))
// DamageUpgradeLevel = intValue;
} else {
//New serialized layout
child = element.Element("Levels");
if (child != null)
LoadLevels(child, ref levels);
}
}
public XElement Save() {
XElement element = new XElement(GetType().Name);
element.Add(new XElement("Money", money));
//e.Add(new XElement("HealthUpgrade", healthUpgradeLevel));
//e.Add(new XElement("DamageUpgrade", damageUpgradeLevel));
element.Add(SaveLevels(ref levels));
return element;
}
#region Public Animator Methods
public void AnimatorAllowSuperSmashToRestart() {
ClearSuperCharge();
SetSuperChargeActive(true);
}
#endregion
}
Here is the Coin script:
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class Coin : MonoBehaviour, IItem {
[SerializeField] private int moneyAmount = 5;
public void OnValidate() {
moneyAmount = Mathf.Max(0, moneyAmount);
}
public void OnPickup(ItemCollector collector) {
collector.Owner.Money += moneyAmount;
//collector.Owner.RoundMoney += moneyAmount;
}
}
And finally, the itemCollector script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemCollector : MonoBehaviour {
[SerializeField] private float radius = 0.5f;
[SerializeField] private LayerMask itemMask;
private Player owner;
private Collider2D[] results = new Collider2D[16];
private int resultsCount;
public Player Owner {
get { return owner; }
}
public void Awake() {
owner = GetComponentInParent<Player>();
if (owner == null) {
if (GameManager.IsDebugMode) {
Debug.LogError(" require a " + typeof(Player).Name + " to be in a parent!" +
"\nThe " + GetType().Name + " will be destroyed, as it would not be able to function.");
}
DestroyImmediate(gameObject);
}
}
public void Update() {
resultsCount = Physics2D.OverlapCircleNonAlloc(transform.position, radius, results, itemMask);
for (int i = 0; i < resultsCount; i++) {
IItem item = results[i].GetComponent<IItem>();
if (item != null) {
MonoBehaviour m = item as MonoBehaviour;
if (m != null && m.enabled) {
item.OnPickup(this);
m.enabled = false;
GameObject.Destroy(m.gameObject);
}
}
}
}
public void OnDrawGizmosSelected() {
Gizmos.DrawWireSphere(transform.position, radius);
}
}
Thank you in advance for you help.
Regards!
For anyone who has a problem like that.
I fixed the problem. I had a .onMoneyChanged in an update function that was making the game crash.
I moved it to the onEnable and onDisable method and now it works just fine.