In my Unity project I have several levels and several steps. Each step and level have their own animations. With a button click I want to stop all animations, so I tried it the following way. For my project I have two scripts:
Start_Script.cs
//Steps for Level 01:
public static GameObject Step001, Step011, Step012;
//Each animation will play in Level 01:
public static Animation AnimationStep001, AnimationStep011, AnimationStep012;
int levelCounter;
void Awake()
{
levelCounter = 1;
Step001 = GameObject.Find("Step001");
Step011 = GameObject.Find("Step011");
Step012 = GameObject.Find("Step012");
AnimationStep001 = Step001.GetComponent<Animation>();
AnimationStep011 = Step011.GetComponent<Animation>();
AnimationStep012 = Step012.GetComponent<Animation>();
}
//Method is assigned to "Next Level" Button.
public void nextLevel()
{
switch(levelCounter)
{
case 1:
for(int i = 0; i == Array_Lists.animStrings.Length; i++)
{
Array_Lists.animAnimation[i].Stop(Array_Lists.animStrings[i]);
}
break;
}
levelCounter += 1;
}
Array_Lists.cs
// My Array for the animations name:
public static string[] animStrings = {"animStep1", "animStep1.1", "animStep1.2"};
// My Array for the animation objects:
public static Animation[] animAnimation = {
Start_Script.AnimationStep001,
Start_Script.AnimationStep011,
Start_Script.AnimationStep012
};
But unfortunately I always get a NullReferenceException: Object reference not set to an instance of an object for Array_Lists.animAnimation[i].Stop(Array_Lists.animStrings[i]);
In summary, I need to stop all animations when I hit the "Next Level" Button.
Edit
After my first NullReferenceException: I added a the following to my public void nextLevel() method:
public void nextLevel()
{
switch(levelCounter)
{
case 1:
for(int i = 0; i == Array_Lists.animStrings.Length; i++)
{
Debug.Log("Array Animations = " + Array_Lists.animAnimation[i];
Debug.Log("Array anim Name = " + Array_Lists.animStrings[i];
Array_Lists.animAnimation[i].Stop(Array_Lists.animStrings[i]);
}
break;
}
levelCounter += 1;
}
This was my output:
Array Animations = null
Array anim Name = animStep1
Array Animations = null
Array anim Name = animStep1.1
Array Animations = null
Array anim Name = animStep1.2
Your arrays are static, so they are created before Awake.
Your animAnimation is filled with nulls, and it doesn't change when you put components to Start_Script.AnimationStep001 and others. You need to fill animAnimation with components after Awake. And your scripts are interlinked, this is not very good. Consider creating and populating animAnimation from Start_Script.
UPD: add this at the end of your Start_Script.Awake:
Array_Lists.animAnimation = new [] {
AnimationStep001,
AnimationStep011,
AnimationStep012
};
i == Array_Lists.animStrings.Length
and not
i < Array_Lists.animStrings.Length
I suspect you never enter the for cycle.
Related
I am tackling a Unity course challenge, where we are supposed to implement data persistence between scenes and across sessions. After deciding to go with JSON, I managed to figure out how to save data generally between game sessions and scenes (in terms of preserving a game load or displaying the last played session playerName and playerScore). However, the high score table I'm using is puzzling me. When and where should I be triggering my high score entry, given that the high score table is in a separate scene with its own separate savefile path?
So far, I have a MainManager script that holds basic player data, which helps communicate between the MainMenuController script and the GameController script, located in the Main scene and Game scene respectively. The MainManager script has JSON save file functionality, while the HighScoreTable script (in the HiScore scene) has its own save function and filepath (currently using PlayerPrefs, which I'm going to change to JSON later). The script is based off CodeMonkey's script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class HighscoreTable : MonoBehaviour
{
private Transform entryContainer;
private Transform entryTemplate;
private List<Transform> highscoreEntryTransformList;
public string lastPlayerName;
public float lastPlayerScore;
private void Awake()
{
entryContainer = transform.Find("highscoreEntryContainer");
entryTemplate = entryContainer.Find("highscoreEntryTemplate");
entryTemplate.gameObject.SetActive(false);
string jsonString = PlayerPrefs.GetString("highscoreTable");
Highscores highscores = JsonUtility.FromJson<Highscores>(jsonString);
//if there is no stored table, initialize
if(highscores == null)
{
Debug.Log("Initializing table with default values...");
AddHighscoreEntry(1234, "KAL");
AddHighscoreEntry(345, "TAM");
AddHighscoreEntry(897621, "JOE");
// Reload
jsonString = PlayerPrefs.GetString("highscoreTable");
highscores = JsonUtility.FromJson<Highscores>(jsonString);
}
//for sorting without save / load(otherwise we can sort on a load function
//for (int i = 0; i < highscores.highscoreEntryList.Count; i++)
//{
// for (int j = i + 1; j < highscores.highscoreEntryList.Count; j++)
// {
// if (highscores.highscoreEntryList[j].score > highscores.highscoreEntryList[i].score)
// {
// //swap
// HighscoreEntry tmp = highscores.highscoreEntryList[i];
// highscores.highscoreEntryList[i] = highscores.highscoreEntryList[j];
// highscores.highscoreEntryList[j] = tmp;
// }
// }
//}
highscores.highscoreEntryList.Sort((x, y) => y.score.CompareTo(x.score));
//keep max count and delete extra
if (highscores.highscoreEntryList.Count > 10)
{
for (int h = highscores.highscoreEntryList.Count; h > 10; h--)
{
highscores.highscoreEntryList.RemoveAt(10);
}
}
highscoreEntryTransformList = new List<Transform>();
foreach (HighscoreEntry highscoreEntry in highscores.highscoreEntryList)
{
CreateHIghscoreEntryTransform(highscoreEntry, entryContainer, highscoreEntryTransformList);
}
}
private void Update()
{
}
private void CreateHIghscoreEntryTransform(HighscoreEntry highscoreEntry, Transform container, List<Transform> transformList)
{
float templateHeight = 30f;
Transform entryTransform = Instantiate(entryTemplate, container);
RectTransform entryRectTransform = entryTransform.GetComponent<RectTransform>();
entryRectTransform.anchoredPosition = new Vector2(0, -templateHeight * transformList.Count);
entryTransform.gameObject.SetActive(true);
int rank = transformList.Count + 1;
string rankString;
switch (rank)
{
default:
rankString = rank + "TH"; break;
case 1: rankString = "1ST"; break;
case 2: rankString = "2ND"; break;
case 3: rankString = "3RD"; break;
}
entryTransform.Find("posText").GetComponent<TMP_Text>().text = rankString;
string name = highscoreEntry.name;
entryTransform.Find("nameText").GetComponent<TMP_Text>().text = name;
float score = highscoreEntry.score;
entryTransform.Find("scoreText").GetComponent<TMP_Text>().text = score.ToString();
transformList.Add(entryTransform);
}
public void AddHighscoreEntry(float score, string name)
{
//create highscore entry
HighscoreEntry highscoreEntry = new HighscoreEntry { score = score, name = name };
//load saved highscores
string jsonString = PlayerPrefs.GetString("highscoreTable");
Highscores highscores = JsonUtility.FromJson<Highscores>(jsonString);
//if there's no stored table, initialize
if (highscores == null)
{
highscores = new Highscores()
{
highscoreEntryList = new List<HighscoreEntry>()
};
}
//add new entry to list
highscores.highscoreEntryList.Add(highscoreEntry);
//keep max count and delete extra
if (highscores.highscoreEntryList.Count > 10)
{
for (int h = highscores.highscoreEntryList.Count; h > 10; h--)
{
highscores.highscoreEntryList.RemoveAt(10);
}
}
//save updated highscores
string json = JsonUtility.ToJson(highscores);
PlayerPrefs.SetString("highscoreTable", json);
PlayerPrefs.Save();
}
private class Highscores
{
public List<HighscoreEntry> highscoreEntryList;
}
//represents a single highscore entry
[System.Serializable]
private class HighscoreEntry
{
public float score;
public string name;
}
}
Everything APPEARS to be working (so far). The highscore table doesn't initialize, displaying the default names and values provided in the code. However, I am struggling to figure out where/when to implement the "AddHighscoreEntry" method. Here is what I tried out:
When the player dies.
When the Game Over screen pops up.
When the High Score scene button is clicked on the Main Menu.
In the HighscoreTable update function, tracking the player's health through MainManager and creating a new entry when the player dies.
In some cases, the update function causes the entry to replicate infinitely, filling out the high score table. In other cases, it stops surrounding code entirely, bringing the game to a grinding halt. Sometimes it triggers the table to fill out the positions and a line of 0s (but no player names). I can clear those by deleting PlayerPrefs with a button I made... but I can't seem to instantiate a single entry with all the details.
Is the fact that I'm currently using two save files the root of the problem? And if I were to switch the file to JSON, I still have the problem of when/where to trigger the AddHighscoreEntry. Where would you recommend I put it? My Github repo of spaghetti code is here: https://github.com/scarecrowslady/JProgramming_DataPersistenceProject_R2.git
I am studying a course in programming (C#). Right now I am doing a task where I will create a version of a Dart 301 game.
I'm basically done with the game but would like to change the text color depending on which player is playing. This is not really necessary, it is not listed as something you should do but after I got the idea in my head I can not let go either lol. I really just want to know how to, been stuck now trying out different solutions.
When the game starts, you have to choose how many players to play. If it's 1 player, it plays against the computer, which I then solved to add the change in foreground color inside my foreach loop - for example:
Console.ForegroundColor = ConsoleColor.DarkCyan;
But when there is more than one player in the game I'm stuck.
Ideally I'd like to assign a color to each player joining the game. The player gets added through a AddPlayer method that I've created and then gets stored in a list of players in the Game class. There is also a Player class which holds a list of turns for each player.
What I've tried so far is
Creating a method for a random color (which is not ideal cause I'd like each player to get assigned a specific color):
private static ConsoleColor GetRandomColor()
{
Random randomColor = new Random();
var consoleColors = Enum.GetValues(typeof(ConsoleColor));
return (ConsoleColor)consoleColors.GetValue(randomColor.Next(consoleColors.Length));
}
In the foreach loop then changing the color for each player in the list
foreach (var player in ListOfPlayers)
{
for (int i =0; i <= ListOfPlayers.Count -1; i++)
{
Console.ForegroundColor = GetRandomColor();
}
Console.WriteLine("\n\t{0} it's your turn! What numbers did your arrows hit? " +
"\n\tREMEMBER: Each turn includes three throws with a possible score of 1-20 for each arrow. \n", player);
arrowOne = GetPlayerThrow();
arrowTwo = GetPlayerThrow();
arrowThree = GetPlayerThrow();
player.AddTurn(arrowOne, arrowTwo, arrowThree);
totalScore = player.CalculatePoints();
}
I've also tried different types of if and foreach
Hope my explanation did not get too messy. I would prefer to assign a color to a specific player in the list.
Ex:
private List <Player> ListOfPlayers = new List <Player> ();
List item 0 If this player exists, the text will appear in DarkYellow.
List item 1 If this player exists, the text will appear in DarkCyan.
List item 2 If this player exists, the text will appear in DarkMagneta.
List item 3 If this player exists, the text will appear in DarkGreen.
Here's a picture of what I've done when the game is computer vs player:
When the computer is playing the text is red but when it's the players turn it is blue:
Create a list with the number of desired players and assign to each the desired color with a switch. Then start the game. An example below.
public class Game {
private List<Player> ListOfPlayers = new List<Player>();
public Game(int nbplayers) {
InitPlayers(nbplayers);
}
public void InitPlayers(int nbplayers) {
for(int i=0; i < nbplayers; i++) {
var player = new Player();
switch(i) {
case 0: { player.Color = ConsoleColor.DarkYellow; break;}
case 1: { player.Color = ConsoleColor.DarkCyan; break;}
case 2: { player.Color = ConsoleColor.DarkMagneta; break;}
case 3: { player.Color = ConsoleColor.DarkGreen; break;}
}
ListOfPlayers.Add(player);
}
}
public void StartGame() {
while(!ListOfPlayers.Any(p => p.TotalScore > 100) {
StartTurn();
}
EndGame();
}
public void StartTurn() {
//each player throw
foreach(var player in ListOfPlayers) {
player.Play();
}
//calculate each player total score
...
}
public void EndGame() {
var winner;
}
}
public class Player {
private Random = new Random();
public ConsoleColor Color {get; set;}
public List<int> Throw = new List<int>();
public int TotalScope { get; set; } = 0;
public void Play() {
Throw.Clear();
for(int i=0; i < 3; i++) {
Throw.Add(random.Next(1, 20));
}
}
}
I'm making a board game in Unity with multiple branching paths where the players can decide which one to follow. (Think the Mario Part game board) But the player input on which path to follow isn't working.
So I have a script that has the tokens follow a set path and (Linked List for the path) with a for loop to have the tokens move their allocated amount. Within the loop, I have an If statement that checks how many paths the tile has and if there are more than one if runs a separate script that pauses the game and offers a menu prompt for the players to pick one path or the other.
I've tried tossing in a few contitions for continuing the game within the loop but they either ignore the player input, are an input behind or just break the code.
`for (int i = 0; i < spacesToMove; i++)
{
if (final_Waypoint == null)
{
final_Waypoint = Starting_Waypoint;
}
else
{
if (final_Waypoint.Next_Waypoint == null || final_Waypoint.Next_Waypoint.Length == 0)
{
//we are overshooting the final waypoint, so just return some nulls in the array
//just break and we'll return the array, which is going to have nulls at the end.
Debug.Log(Current_Waypoint);
break;
}
else if (final_Waypoint.Next_Waypoint.Length > 1) // && if playerID == 0 so it only appears for players.
{
menu.Pause_for_Choice();
//if (menu.ButtonPress == true)
final_Waypoint = final_Waypoint.Next_Waypoint[menu.choice_int];
//***There's a bug here. It calls menu as per normal, and the game pauses.
//But when I click 'no', choice_int isn't updated first: this function finishes first,
//so this function gets old choice_int. THEN the button function executes.
Debug.Log("test 3 " + menu.choice_int);
}
else
{
final_Waypoint = final_Waypoint.Next_Waypoint[0];
}
}
listOfWaypoints[i] = final_Waypoint;
Debug.Log("i:" + i);
}
return listOfWaypoints;
}`
`{
public GameObject choice_Menu;
public bool isPaused = true;
public int choice_int=0;
public int choice_placeholder = 0;
public void Pause_for_Choice()
{
isPaused = true;
choice_Menu.SetActive(true);
Time.timeScale = 0;
}
public void Yes()
{
choice_placeholder = 0;
UnPause_Game();
}
public void No()
{
choice_placeholder = 1;
UnPause_Game();
}
public void UnPause_Game()
{
isPaused = false;
choice_Menu.SetActive(false);
Time.timeScale = 1;
}
}`
My problem is inside playmode I get expected array sequence: showing all gameobjects, RedCube, Interior, BlueCube, and then GreenCube. But, after I built it and test in my android. I get a different array sequence: showing all gameobjects, GreenCube, BlueCube, RedCube and then Interior. This screenshot shows the sequence of my array.image0.
There are 4 gameobjects under gameobject - Levels Image1. Added a script to Levels.... Image2complete scene screenshot.
public GameObject[] levels;
public Button levelBtn;
int i = 0;
private void Awake()
{
levels = GameObject.FindGameObjectsWithTag("levels");
Button btn = levelBtn.GetComponent<Button>();
}
// Start is called before the first frame update
void Start()
{
levelBtn.onClick.AddListener(onLevelclick);
}
public void onLevelclick()
{
if (i < levels.Length - 1)
{
i++;
}
else if (i >= levels.Length - 1)
{
i = 0;
}
Debug.Log(i);
if (i == 0)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(true);
levels[3].SetActive(true);
}
else if (i == 1)
{
levels[0].SetActive(true);
levels[1].SetActive(false);
levels[2].SetActive(false);
levels[3].SetActive(false);
}
else if (i == 2)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(false);
levels[3].SetActive(false);
}
else if (i == 3)
{
levels[0].SetActive(true);
levels[1].SetActive(true);
levels[2].SetActive(true);
levels[3].SetActive(false);
}
}
Inside this script I have a gameobject array. When I click the UIbutton, it will loop within the array.length.... But the array sequence is different from playmode and built. I couldn't figure out why?
If you assign gameobjects to levels in the unity inspector like image0, you don't need to this line.
levels = GameObject.FindGameObjectsWithTag("levels");
This code generated a random game object but because there are only 9 game objects sometimes the randomly generated object keeps re-generating more than one time. How can i restrict that, and have one game object generated only once ?
public GameObject[] models;
public static GameObject currentPoint;
int index;
public static string randomName;
public AudioSource FindTheNumber;
public void PlayNumbers()
{
models = GameObject.FindGameObjectsWithTag ("numbers");
index = Random.Range (0,models.Length);
currentPoint = models [index];
randomName = currentPoint.name;
print ("Trackable " + randomName);
FindTheNumber.Play ();
currentPoint.GetComponent<AudioSource> ().PlayDelayed(2);
}
If I've understood correctly, how about keeping track of the selected game objects in a separate list.
public static List<GameObject> models;
public static List<GameObject> selectedModels = new List<GameObject>();
public static GameObject currentPoint;
int index;
public static string randomName;
public AudioSource FindTheNumber;
public static Random random = new Random();
public void PlayNumbers()
{
models = GameObject.FindGameObjectsWithTag("numbers").Except(selectedModels).ToList();
if ((models == null) || (!models.Any()))
{
Console.WriteLine("No new game objects");
}
else
{
index = random.Next(models.Count);
currentPoint = models[index];
randomName = currentPoint.name;
print ("Trackable " + randomName);
FindTheNumber.Play ();
currentPoint.GetComponent<AudioSource> ().PlayDelayed(2);
selectedModels.Add(currentPoint);
}
}
So your problem is that when you call currentPoint and create a sound for example you encounter the problem that the gameObject might get called more often creating the "same" object ?
If yes, you could save the last gameObject that was created and check if it is the same as the new one?
That way you always have a different gameObject every time ;). I don't know what's the delta between your calls and how long they last, but checking the gameObject should do the trick.
You could create a variable that stores information if the object is doing it's job.
Additionally you could check verify that the values differ from the gameObjects that are currently active.
If i understood your problem correct your code should look something like this.
public GameObject[] models;
public static GameObject currentPoint;
int index;
public static string randomName;
public AudioSource FindTheNumber;
public void PlayNumbers()
{
do {
models = GameObject.FindGameObjectsWithTag ("numbers");
index = Random.Range (0,models.Length);
currentPoint = models [index];
} while(currentPoint.getComponent<AudioSource>().isPlaying);
randomName = currentPoint.name;
print ("Trackable " + randomName);
FindTheNumber.Play ();
currentPoint.GetComponent<AudioSource> ().PlayDelayed(2);
}
Right now the code will loop atleast one time. If the object you are trying to use is already playing the code will loop through the array again.
Once it finds a object that isn't playing the code will proceed and do what it should do.
If you get any errors or if my solution is incorrect just post again and tell us what you want (in detail)
Have a nice day ;)
PS: The only thing that annoys me is it might double check some values due to the Random.Range...
One approach to the recommendation Vancold.at made
public void PlayNumbers()
{
var rnd = new Random();
models = GameObject.FindGameObjectsWithTag("numbers");
while (true)
{
index = rnd.Next(0, models.Length - 1);
var newPoint = models[index];
//assumes currentPoint is initially null the first time this method is called
if(currentPoint == null || newPoint.name != currentPoint.name)
{
currentPoint = newPoint;
break;
}
}
randomName = currentPoint.name;
print("Trackable " + randomName);
FindTheNumber.Play();
currentPoint.GetComponent<AudioSource>().PlayDelayed(2);
}
Keep in mind that this only assures the same GameObject instance isn't used two times in a row. It will not prevent same object from being used more than once. For example, if you have 9 GameObject instances with names "1", "2", "3", "4", "5", "6", "7", "8", "9", the code above allows the following output:
Trackable 5
Trackable 7
Trackable 5
Trackable 4
Trackable 7
Etc. If you want to cycle through the possible values once before repeating, then a different approach is needed.
I found the solution, I will post the code I wrote so it may serve to someone in the future:
public GameObject[] models;
public static GameObject currentPoint;
int index;
public static string randomName;
public AudioSource FindTheNumber;
int i =9;
public void PlayNumbers()
{
models = GameObject.FindGameObjectsWithTag ("numbers");
if (i >= 0)
{
index = i;
currentPoint = models [index];
randomName = currentPoint.name;
print ("Trackable " + randomName);
FindTheNumber.Play ();
currentPoint.GetComponent<AudioSource> ().PlayDelayed (2);
i--;
}
else
{
i=9;
index = i;
currentPoint = models [index];
randomName = currentPoint.name;
print ("Trackable " + randomName);
FindTheNumber.Play ();
currentPoint.GetComponent<AudioSource> ().PlayDelayed (2);
i--;
}
}