I'm creating a game in Unity and I'm having some trouble with it. I have 3 different child objects within a parent object, i would like to randomly set 1 of these 3 child objects as the active object and simultaneously disable the other two. I would like this to happen on colliding with another object.
Thanks in advance.
public GameObject parentOfChild;
void OnTriggerEnter(Collider thing)
{
if("the collision condition")
{
int randomChild = Random.Range(0,2);
if(randomChild == 0)
{
parentOfChild.transform.GetChild(0).gameObject.SetActive(true);
parentOfChild.transform.GetChild(1).gameObject.SetActive(false);
parentOfChild.transform.GetChild(2).gameObject.SetActive(false);
}
else
if(randomChild == 1)
{
parentOfChild.transform.GetChild(0).gameObject.SetActive(false);
parentOfChild.transform.GetChild(1).gameObject.SetActive(true);
parentOfChild.transform.GetChild(2).gameObject.SetActive(false);
}
else
if(randomChild == 2)
{
parentOfChild.transform.GetChild(0).gameObject.SetActive(false);
parentOfChild.transform.GetChild(1).gameObject.SetActive(false);
parentOfChild.transform.GetChild(2).gameObject.SetActive(true);
}
}
}
This is considering that all three children are not visible until the collision.It will also work if all three children visible.
In the parentOfChild
object pass your gameobject having the 3 children
I recommend yo to do this instead
public GameObject parentOfChild;
void OnTriggerEnter(Collider thing)
{
int randomChild = Random.Range(0,2);
parentOfChild.transform.GetChild(0).gameObject.SetActive(false);
parentOfChild.transform.GetChild(1).gameObject.SetActive(false);
parentOfChild.transform.GetChild(2).gameObject.SetActive(false);
parentOfChild.transform.GetChild(randomChild).gameObject.SetActive(true);
}
Related
I tried to make a dynamic scroll view to display selectable options that can be both generated or destroyed during runtime but for some reason it only lets me to generate up to three, otherwise I get a Missing Reference Exception.
When I generate one then delete it, it will also give a Missing Reference Exception.
But when I generate two, then delete one or even two, everything works just fine no matter what I do.
How can I fix this strange Behaviour?
The code:
private List<JL> JLList;
public List<GameObject> JLID;
public GameObject ButtonPrefab;
public GameObject PrefabParent;
public GameObject JLMenuManager;
public GameObject ScrollViewContent;
private void Update()
{
JLList = JLMenuManager.GetComponent<AddJL>().JLList;
CheckForChange();
}
private void GenerateOptions()
{
foreach (Transform child in ScrollViewContent.transform)
{
Destroy(child.gameObject);
}
for (int i = 0; i < JLList.Count; i++)
{
GameObject newJLOption = Instantiate(ButtonPrefab, PrefabParent.transform);
JLID.Add(newJLOption);
newJLOption = JLID[i];
int JLIndex = i;
newJLOption.GetComponent<Button>().onClick.AddListener(() => LoadJLOptions(JLIndex));
}
}
private void LoadJLOptions(int JLIndex)
{
SendMessage("SetJL", JLIndex);
Debug.Log(JLIndex);
}
private void CheckForChange()
{
int allJLOptions = ScrollViewContent.transform.childCount;
if (allJLOptions != JLList.Count)
{
GenerateOptions();
}
}
It seems like it is only spawning in how many JLList.Count is set to, open your script JLList and there should be a variable called count . Check how many that is set to. If it is 3, then that is your issue. If not, then follow what #BugFinder said and try remove them from the list
I'm currently developing a game where I have 3 types of enemies (Common, Elite and Boss which I'm using tags to define the type).
Since I want to give different characteristics and spawning rate to every enemy type, I've been struggling finding a way better to call All enemies instead of calling with tags.
Like:
bool enemyIsAlive()
{
if (GameObject.FindGameObjectWithTag("Common") == null)
{
return false;
}
return true;
}
Here I'm trying to check if there's any enemy in the scene but I can't find a way to reference all enemies. I tried with "Find" but picks the name and the enemies gonna have different names since I can't use the same name for all.
Anyone can help me?
Avoiding using Unity's GameObject Find functions is usually a good thing in the long run for game performance. When handling multiple enemies in a game, I tend to sway towards using a manager class that will be responsible for tracking the spawning and despawning of the enemies and then be used as the source of checking if an enemy is still alive.
For example, lets say you have a simple enemy class that can be either a Common, Elite or Boss. By putting this class onto an object (your enemy) in your hierarchy you can use the type field to say what type this enemy is, and then create a prefab of that enemy to spawn in at runtime.
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Type type;
public enum Type
{
COMMON,
ELITE,
BOSS
}
private void OnEnable()
{
EnemySpawned();
}
private void OnDisable()
{
EnemyDied();
}
public void EnemySpawned()
{
Debug.Log("Enemy Spawned: " + type);
EnemyManager.RegisterEnemy(this);
}
public void EnemyDied()
{
Debug.Log("Enemy Died: " + type);
EnemyManager.UnregisterEnemy(this);
}
}
You can see that when an enemy spawns EnemySpawned() is called which uses an EnemyManager to register itself so that it can keep track of it. The same happens when the enemy dies EnemyDied() is called which then tells the EnemyManager to unregister that enemy.
Here is the EnemyManager class:
using System.Collections.Generic;
public class EnemyManager
{
private static Dictionary<Enemy.Type, int> enemies;
public static void RegisterEnemy(Enemy enemy)
{
if (enemies == null)
{
enemies = new Dictionary<Enemy.Type, int>();
}
if (enemies.ContainsKey(enemy.type))
{
enemies[enemy.type] += 1;
}
else
{
enemies.Add(enemy.type, 1);
}
}
public static void UnregisterEnemy(Enemy enemy)
{
if (enemies == null || !enemies.ContainsKey(enemy.type))
{
return;
}
enemies[enemy.type] -= 1;
}
public static bool IsEnemyTypeAlive(Enemy.Type enemyType)
{
return enemies != null && enemies.ContainsKey(enemyType) && enemies[enemyType] > 0;
}
}
This does not need to be added to an object in the scene hierarchy as it uses static fields and functions.
As you can see the EnemyManager will keep a Dictionary of the enemy types and how many of those enemies are registered. (You could also keep a reference to the Enemy class itself instead of just a count so you could later access those enemies through the EnemyManager too, but for simplicities sake, we will just keep a count)
When an Enemy is registered the count is incremented and when an Enemy is unregistered the count is decremented.
We can then use this to see if enemies are still alive by their type, by checking if the dictionary has firstly had anything registered, then if the dictionary has had that specific enemy type registered, and finally if there are still any registered enemies of that type as they may have all been unregistered.
Here is a demo class then of how that check could be used:
using UnityEngine;
public class Demo : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("Common Enemy Alive: " + IsCommonEnemyAlive());
}
if (Input.GetKeyDown(KeyCode.W))
{
Debug.Log("Elite Enemy Alive: " + IsEliteEnemyAlive());
}
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("Boss Enemy Alive: " + IsBossEnemyAlive());
}
}
public bool IsCommonEnemyAlive()
{
return EnemyManager.IsEnemyTypeAlive(Enemy.Type.COMMON);
}
public bool IsEliteEnemyAlive()
{
return EnemyManager.IsEnemyTypeAlive(Enemy.Type.ELITE);
}
public bool IsBossEnemyAlive()
{
return EnemyManager.IsEnemyTypeAlive(Enemy.Type.BOSS);
}
}
It may seem more complicated, but this solution will scale a lot better than using the GameObject find functions especially if you need to check if the enemies exist often.
To take it even further you could look into System.Action delegates and events to decouple the enemies and the EnemyManager even further and simply raise events when enemies are spawned or die.
you should not use Find that often, especially not in an update function since it is performance heavy.
Better:
Create a List where you register each enemy that spawns and also implement a function that removes them when they die. Also think about inheritance, i presume each enemy has common parameters so it would be a good approach to use it.
I am fairly new to Unity and C# and am having some trouble. I am designing a 2d game, which has multiple levels. Each level contains a LevelManager which stores whether the level has been completed or not. They also have the DontDestroyOnLoad command in them. I want to access all the LevelManager gameObjects in my game and then store them in a level select scene. I want to use the win/lose bool to determine if the level has been completed so I can unlock the next level. To be clear, I want a way to access all the LevelManagers in my ENTIRE game and then store them as an array in a GameManager script. How do I do that?
Below is the LevelManager script which declares whether the level has been won or not.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneController : MonoBehaviour
{
private GameObject[] StartHouseCount;
private GameObject[] StartDragonCount;
private GameObject[] LiveDragonCount;
private GameObject[] FinishedHouseCount;
public int NumOfHouses;
public int NumOfFinishedHouse;
public int NumOfDragons;
public int LiveNumOfDragons;
public GameObject[] Players;
public GameObject CurrentPlayer;
[Header("Player")]
public float RefuelRate;
public float RepairRate;
public GameObject canvas;
public bool GameIsPaused = false;
private GameObject MainPlayer;
public bool Win = false;
public int Level;
// Start is called before the first frame update
void Start()
{
CurrentPlayer = Players[0];
StartHouseCount = GameObject.FindGameObjectsWithTag("House");
StartDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
NumOfHouses = StartHouseCount.Length;
NumOfDragons = StartDragonCount.Length;
MainPlayer = Players[0];
}
// Update is called once per frame
void Update()
{
GameIsPaused = canvas.GetComponent<PauseMenu>().GameIsPaused;
LiveDragonCount = GameObject.FindGameObjectsWithTag("Dragon");
LiveNumOfDragons = LiveDragonCount.Length;
FinishedHouseCount = GameObject.FindGameObjectsWithTag("ThankYou");
NumOfFinishedHouse = FinishedHouseCount.Length;
if (Input.GetKeyDown(KeyCode.Alpha1))
{
CurrentPlayer = Players[0];
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
CurrentPlayer = Players[1];
}
if (NumOfFinishedHouse == NumOfHouses)
{
SceneManager.LoadScene("WinScene");
}
if (MainPlayer == null)
{
SceneManager.LoadScene("LoseScene");
}
if (MainPlayer.GetComponent<BasicHelicopterController>().CurrentFuel <= 0 || MainPlayer.GetComponent<BasicHelicopterController>().CurrentHealth <= 0)
{
SceneManager.LoadScene("LoseScene");
}
}
}
You can create a ScoreManager script to store scores of each level using PlayerPrefs and attach this script to a GameObject in the first scene.
public class ScoreManager: MonoBehavior {
public static ScoreManager Instance;
const string LEVEL_KEY = "SCORE_";
const string MAX_LEVEL_KEY = "MAX_LEVEL";
public static int MAX_LEVEL {
get { return PlayerPrefs.GetInt(MAX_LEVEL_KEY, 1); }
set {
if(value > MAX_LEVEL) {
PlayerPrefs.SetInt(MAX_LEVEL_KEY, value);
}
}
}
void Awake() {
if(Instance != null) {
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this);
}
public void SaveScore(int level, float score) {
PlayerPrefs.SetFloat(LEVEL_KEY + level, score);
MAX_LEVEL = level;
}
public float GetScore(int level) {
return PlayerPrefs.GetFloat(LEVEL_KEY + level, 0);
}
public bool IsLevelUnlocked(int level) {
// You can write your own level-unlock logic here.
return level <= MAX_LEVEL;
}
...
}
Then you can just call its functions and properties from other scripts.
...
public void GameOver() {
ScoreManager.Instance.SaveScore(level, score);
}
...
Having all LevelManger scripts with DontDestroyOnLoad will cause a memory leak and sometimes it will affect the game performance.
First, DontDestroyOnLoad is not supposed to do this - main usage for this is to implement things like unity-singleton (easy-to-use template). It means, you`ll have one instance of levelManager, not one per level.
To do it in your way, you need to load all the scenes additively, and get all the LevelManager instances. Or you can load all scenes one-by-one, getting level name and it`s isPassed value from current LevelManager. It can take a while, and its a wrong way.
The right way is to store inter-scene data in ScriptableObject models. It`s like a MonoBehaviour, but not related to scene - it just lays somewhere in project.
So, in your case, you can create ScriptableObject called LevelProgress. It will store the list of all passed levels. It will have public method LevelPassed(string levelName), as a parameter you can use anything - levels name, levels index in build, scene itself, whatever. Every time, when player passes any level, your LevelManager (remove DontDestoryOnLoad from it), which has a reference to LevelProgress scriptable object, will call LevelProgress.LevelPasses(currentLevel). And then, if you need to know, was some level passed, or not, you can just check, is LevelProgress.PassedLevels list contains this level.
Additionally, you need to implement between-sessions persistance for this scriptable object. IMO, the best way for this is to use JSONUtility to convert ScriptableObject into JSON string, and then write it to PlayerPrefs.
I seemed to have figured it out. I created a gameObject containing a GameManagement script, that had the DontDestroyOnLoad line. I had also added a specific tag. I then searched for that object in each level and updated my values to that. The GameManagement script had an array of bools for levels completed and levels unlocked. Each levelmanager decided whether the level was won and updated that. Using that I determined what level was unlocked. I did though need to use Fire King's Awake Command. It ensures that there are no other copies of the script in the game. Solves my problems.
I have a List that tracks "alive" objects in runtime. And when I apply some behavior in items from this list I've found something strange (at least to me), for example, let's say that I want to damage each enemy in current scene, so (Items is a List of custom class)
if (Input.GetKeyDown(KeyCode.C))
{
var enemiesWithLife = enemiesInScene.Items.FindAll(enemy => enemy.life > 1);
foreach (var enemy in enemiesWithLife)
enemy.TakeDamage(1);
}
The above code works as expected BUT if I want to apply something to ALL enemies and try something as
if (Input.GetKeyDown(KeyCode.C))
{
foreach (var enemy in enemiesInScene.Items)
enemy.TakeDamage(1);
}
When I press "C" only 1 enemy "TakeDamage" and an error about list been modified during iterating
EnemiesInScene is an ScriptableObject, so the enemies list exist only in assets
BUT if I try same code, but make an find before, as
if (Input.GetKeyDown(KeyCode.C))
{
var allEnemies = enemiesInScene.Items.FindAll(enemy => true);
foreach (var enemy in allEnemies)
enemy.TakeDamage(1);
}
Things work as expected again ... WHY?
Edit
Codes asked in comments:
public void TakeDamage(int damage)
{
if (currentLife <= 0)
return;
currentLife -= damage;
if (currentLife <= 0)
Die();
else
TakeHit();
}
EnemiesInScene is an RuntimeSet
public class RuntimeSet : ScriptableObject
{
public List<RuntimeItem> Items = new List<RuntimeItem>();
public void Add(RuntimeItem thing)
{
if (!Items.Contains(thing))
Items.Add(thing);
}
public void Remove(RuntimeItem thing)
{
if (Items.Contains(thing))
Items.Remove(thing);
}
}
Runtime item
public class RuntimeItem : LazyComponents
{
public RuntimeSet runtimeSet;
private void OnEnable()
=> runtimeSet.Add(this);
private void OnDisable()
=> runtimeSet.Remove(this);
}
And LazyComponents is only a lazy approach wrapper that inherits from monobehaviour to get some components as rigidbody2d, etc...
foreach uses an enumerator, and enumerators can't change the underlying collection, but can, however, change any objects referenced by an object in the collection see MSDN
2 solutions: copy the collection in Array or List (less performance)
if (Input.GetKeyDown(KeyCode.C))
{
foreach (var enemy in enemiesInScene.Items.ToArray())
enemy.TakeDamage(1);
}
or use for loop which allows the modification in collection (more performance) i recommand
for(int i = 0 ; i < enemiesInScene.Items.Length; i++)
enemiesInScene.Items[i].TakeDamage(1);
I think that I've found my error, enumerator runs asynchronous, so it'll change while other things happen, so
When I try it
if (Input.GetKeyDown(KeyCode.C))
{
var allEnemies = enemiesInScene.Items.FindAll(enemy => true);
foreach (var enemy in allEnemies)
enemy.TakeDamage(1);
}
AllEnemies is already filled, so any change in enemiesInScene.Items don't affect allEnemies, this is why in other case we got errors
for (int i = list.Count - 1; i != -1; i--)
list.RemoveAt(i); // replace with useful code that can modify this list
This is related to happyfuntimes plugin if you have used it .
I am trying to make a game on it and stuck at a point of displaying score along with name to display on large screen while user is playing on his mobile.(i have already tried to display name and score on mobile screens have seen in sample do not need that ). Please suggest how can this be done if you have used happyfuntimes plugin.
I could see the HFTgamepad input having public GameObject player name which I am trying to access ,do I have to make array ?
public string playerName;
I am trying to put these name on array.
Displaying anything in unity is really normal unity issue and not special to happyfuntimes. Games display highscore lists, item lists, inventory lists, etc... A list of players is no different
Use the GUI system display text.
According to the docs if you want to generate UI dynamically you should make a prefab
So basically you'd make a UI hierarchy in the scene. When a player is added to the game Instantiate your name-score prefab, search for the UI hierarchy gameObject (do that at init time), then set the parent of the preafab you just instantiated so it's in the UI hierarchy.
Look up the UI.Text components that represent name and score and set their text properties.
There's various auto layout components to help layout your scores
When a player disconnects Destroy the prefab you instantiated above.
OR ...
Conversely you can use the old immediate mode GUI. You'd make a GameObject, give it a component that has a OnGUI function. Somewhere keep a list of players. Add players to the list as they connect and remove them from the list as they disconnect. In your OnGUI function loop over the list of players and call the GUI.Label function or similar to draw each name and score
Here's a hacked up example.
Assuming you have a PlayerScript script component that's on each player that has a public accessor PlayerName for getting the player's name then you can make a GameObject and put a script like this on it.
using UnityEngine;
using System.Collections.Generic;
public class ExamplePlayerListGUI : MonoBehaviour
{
static ExamplePlayerListGUI s_instance;
List<PlayerScript> m_players = new List<PlayerScript>();
static public ExamplePlayerListGUI GetInstance()
{
return s_instance;
}
void Awake()
{
if (s_instance != null)
{
Debug.LogError("there should only be one ExamplePlayerListGUI");
}
s_instance = this;
}
public void AddPlayer(PlayerScript bs)
{
m_players.Add(bs);
}
public void RemovePlayer(PlayerScript bs)
{
m_players.Remove(bs);
}
public void OnGUI()
{
Rect r = new Rect(0, 0, 100, m_players.Count * 20);
GUI.Box(r, "");
r.height = 20;
foreach(var player in m_players)
{
GUI.Label(r, player.PlayerName);
r.y += 20;
}
}
}
PlayerScript can then call ExamplePlayerListGUI.GetInstance().AddPlayer in its Start function and ExamplePlayerListGUI.GetInstance().RemovePlayer in it's OnDestroy function
public PlayerScript : MonoBehaviour
{
private string m_playerName;
...
public string PlayerName
{
get
{
return m_playerName;
}
}
void Start()
{
...
ExamplePlayerListGUI playerListGUI = ExamplePlayerListGUI.GetInstance();
// Check if it exists so we can use it with or without the list
if (playerListGUI != null)
{
playerListGUI.AddPlayer(this);
}
}
void OnDestroy()
{
...
ExamplePlayerListGUI playerListGUI = ExamplePlayerListGUI.GetInstance();
if (playerListGUI != null)
{
playerListGUI.RemovePlayer(this);
}
}
}