I have a Unity script in c# where a public list of sprites is declared and then populated manually in the inspector.
The list consists of 19 sprites of cars.
These cars are available only if the player completed each level.
For example: When you open the game for the first time, you only have 1 available car but if you complete level 1, you unlock another car, when you complete level 2, you get another and so on.
My question is, being that I manually populated the list, how do I go about re-adding the same sprite back when a level is completed?
I was going to use cars.RemoveRange(1, 18) to remove them from the list but do not know a way to add them back without calling every sprite back manually. I believe there is a simpler, better way that I do not know about.
This is the script:
public class CarSelection : MonoBehaviour
{
public List<Sprite> cars; //store all your images in here at design time
public Image displayImage; //The current image thats visible
public Button nextCar; //Button to view next image
public Button previousCar; //Button to view previous image
private int i = 0; //Will control where in the array you are
void OnEnable()
{
//Register Button Events
nextCar.onClick.AddListener(() => NextCarButton());
previousCar.onClick.AddListener(() => PreviousCarButton());
}
private void Start()
{
cars.RemoveRange(1, 18);
if (FinishLineTouch.levelOneComplete == true) {
//Re-adding the same sprites here
}
}
public void NextCarButton()
{
if (i + 1 < cars.Count)
{
i++;
}
}
public void PreviousCarButton()
{
if (i - 1 >= 0)
{
i--;
}
}
void Update()
{
if (LevelSelect.isCarChosen == true) {
nextCar.interactable = false;
previousCar.interactable = false;
}
displayImage.sprite = cars[i];
}
}
Thanks in advance.
You have a small flaw in your logic here.
Saying that you add the car sprites in the inspector, removing all the sprites is not ideal.
You can have a list of all the sprites, and a list with available options (starts with sprite[0] and adds each time you complete the level).
However, if you want to know how to add them back after deleting them, you need to make a copy in a global variable, so it's more efficient to try what I am suggesting above.
You should have two different arrays, one for the sprites, and the other one for if the player unlocked it yet. Or you should create a custom class that has the sprite of the car, and if it is unlocked yet.
Sounds like all you really need to do is make sure the index doesn't exceed the unlocked cars
if(i + 1 < cars.Count && i + 1 <= amountOfFinishedLevels)
{
i++;
// and then you really should do this in both methods and not every frame
displayImage.sprite = cars[i];
}
and don't remove/readd any items at all.
Related
if (Input.GetMouseButtonDown(0))
{
if (currentanim >= 3)
{
currentanim = 1;
}
if (swordanimactive == false)
{
if (swordblockanimactive == 1)
{
currentanim = currentanim + 1;
//the problem seems to be here
Swordcontroller.speed = attackspeed;
Swordcontroller.SetInteger("attackindex", currentanim);
Swordcontroller.SetTrigger("attack");
}
}
}
The problem is when I set the attack speed over 1 (regular speed), after a few clicks it just stops working. I have no idea what causes this and I don't know how to fix it since I just implemnted this new animation system. Also, the current animation is just because there are 3 sword animations. However, if I set it to only one animation it works perfectly fine. When I use 3 different animations it doesn't work.
First of all, I do not really know how you set up the animator, but I recommend this structure.
Multi Animation Animator
Has Exit Time
Maybe you can create some dataType like
public class MeleeAttackInstance
{
public string animationName;
public float animationSpeed;
public float modifiedSpeed;
public void ApplySpeedModifier(int queCount)
{
animationSpeed*(queCount +1);
}
}
And make a queue of this attacks. When craracter should attack, you place item in queue and get the top one applying modified speed.
I'm making a 2D game where you're in the middle of the screen and you move round an endless green (screen) world and white cubes spawn randomly around you, and I have finished the game mechanics and a main menu and game over screens. The one thing I'm trying to add now is a high score. I did a bit of research and found PlayerPrefs is probably the way to do it. I have a seperate scene for my main menu and my gameplay level (which includes the game over screen). I have no error messages. I have created a HSSetter (High Score Setter) script on the high score text in the main menu screen:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Start is called before the first frame update
void Start()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
highScoreText.text = "High Score: " + PlayerPrefs.GetInt("HighScore").ToString();
}
}
and in my score script which is in my actual game level, here's the bit where I try to create the high score:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class score : MonoBehaviour
{
public int scoreCount = 0;
public int highScoreIFA;
void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
void Update()
{
if (scoreCount >= highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
}
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
}
}
}
In AddToScore(), I increment scoreCount.
Through some debugging, I have found that everything in the HSSetter script works - when I change the highScoreText.text, the text on screen changes, which led me to believe the issue might be with the change of scenes? Thanks!
Multiple things you should do here
The first you already updated in your question afterwards: You had the condition wrong and always updated only
if(highScoreIFA > scoreCount)
which would almost always be the case.
Now you have changed it to
if(scoreCount >= highScoreIFA)
which still is not good since if the score is equal there is no reason to update it, yet.
I would rather use
if(scoreCount > highScoreIFA)
so only really update it when needed.
Secondly in both scripts do not use Update at all! That is extremely inefficient.
I would rather use event driven approach and only change and set stuff in the one single moment it actually happens.
You should only one single class (e.g. the score) be responsible and allowed to read and write the PlayerPrefs for this. I know lot of people tent to use the PlayerPrefs for quick and dirty cross access to variables. But it is exactly this: Quick but very dirty and error prone.
If you change the keyname in the future you'll have to do it in multiple scripts.
Instead rather let only the score do it but then let other scripts reference it and retrieve the values directly from that script instead
And finally you should use
PlayerPrefs.Save();
to create checkpoints. It is automatically done in OnApplicationQuit, bit in case your app is force closed or crashes the User would lose progress ;)
Might look like
public class score : MonoBehaviour
{
public int scoreCount = 0;
// Use an event so every other script that is interested
// can just register callbacks to this
public event Action<int> onHighScoreChanged;
// Use a property that simply always invoked the event whenever
// the value of the backing field is changed
public int highScoreIFA
{
get => _highScoreIFA;
set
{
_highScoreIFA = value;
onHighScoreChanged?.Invoke(value);
}
}
// backing field for the public property
private int _highScoreIFA;
private void Start()
{
highScoreIFA = PlayerPrefs.GetInt("HighScore");
}
public void AddToScore()
{
if (isHit == true) // i know this if loop works
{
scoreCount += 1; // and this, I use it to change the score text in-game.
isHit = false;
// Only update the Highscore if it is greater
// not greater or equal
if (scoreCount > highScoreIFA)
{
PlayerPrefs.SetInt("HighScore", scoreCount);
// Save is called automatically in OnApplicationQuit
// On certain checkpoints you should call it anyway to avoid data loss
// in case the app is force closed or crashes for some reason
PlayerPrefs.Save();
}
}
}
}
Then your other script only listens to the event and updates its display accordingly. It is even questionable if both scripts should not rather simply be one ;)
public class HSSetter : MonoBehaviour
{
public Text highScoreText;
// Reference your Score script here
[SerializeField] private score _score;
private void Awake ()
{
// Find it on runtime as fallback
if(!_score) _score = FindObjectOfType<score>();
// Register a callback to be invoked everytime there is a new Highscore
// Including the loaded one from Start
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnDestroy()
{
_score.onHighScoreChanged += OnHighScoreChanged;
}
private void OnHighScoreChanged(int newHighScore)
{
highScoreText.text = $"High Score: {newHighScore}";
}
}
I have game for multiple players where each user selects their hero before game starts and that loads the selected heroes into the battle arena.
I have small issue with getting the instantiation to spawn in correct numbers of players
The method that I have for Spawning the characters:
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
This script checks for amount of selected hero cards and puts it against my list that has all the available heroes to choose from(prefabs).
The debug.log shows that only the correct heroes get called.
Instantiate ends up spawning a loot of heroes instead of the selected amount.
For clarity I attach full class:
{
private int playerSize; //amount of choices for card selection
private GameManager GM;
[Header("Lists for Spawning in Heroes")]
public List<GameObject> SelectedCards;
public List<GameObject> AvailableHeroes;
public List<Transform> PlayerSpawnLocation;
[Header("Canvas used for the game")]
public Transform GameCanvas;
public Transform CharacterCanvas;
//When scene starts it takes how many players will be picking a card.
void Start()
{
//connects this script with gamenmanager to be able to manipulate the cameras
GM = GameObject.Find("GameManager").GetComponent<GameManager>();
//gets playersize information from main menu selection
PlayerPrefs.GetInt("PlayerSize");
playerSize = PlayerPrefs.GetInt("PlayerSize");
SelectedCards = new List<GameObject>();
//enables/disables correct canvas not to cause any problems when we initiate this scene
GameCanvas.gameObject.SetActive(false);
CharacterCanvas.gameObject.SetActive(true);
}
// Update is called once per frame
void Update()
{
if (playerSize <= 0)
{
Placement();
GM.CharacterSelectionCamera.enabled = false;
GameCanvas.gameObject.SetActive(true);
CharacterCanvas.gameObject.SetActive(false);
GM.BattleCamera.enabled = true;
}
}
public void PlayerSelected(int cardPicked)
{
playerSize -= cardPicked;
}
private void Placement()
{
for (int i = 0; i < SelectedCards.Count; i++)
{
for (int t = 0; t < AvailableHeroes.Count; t++)
{
if (AvailableHeroes[t].name == SelectedCards[i].name)
{
Debug.Log(AvailableHeroes[t]);
// Instantiate(AvailableHeroes[t], PlayerSpawnLocation[t].transform.position, transform.rotation);
}
{
}
}
}
}
}
I hope someone can explain where I am going wrong with this.
Thanks,
I got the answer, I guess I was just being tired from working and could not see the obvious.
For those who wonder what solution is
The method gets called each frame thus it continues to endlessly spawn objects
There are 2 ways to fix it
1 Make coroutine and then return after you make your initial batch
2 Use a boolean at update so not only it checks player size but also whenever it can spawn it or not, you set the boolean to false after method get called.
I did not even notice the update function part.
Just a heads up, in your start function, PlayerPrefs.GetInt("PlayerSize"); is not doing anything since the value is not saved anywhere.
Please look at example - http://www.mathplayground.com/mancala.html
Can anyone suggest the logic to :
1) spawn objects at positions
2) Pick up all objects on click and distribute them one by one.
3) Is it better to create all objects or instantiate them on the fly. ?
I tried code below but it just instantiates all objects at once.
if (HoleHandler.gemCount_Hole1_Update_Flag == true)
{
foreach (GameObject g in gemList1)
{
Destroy(g);
//want to add a time delay of 2 secs here
}
if (gemCount_Hole1 > 0)
{
for (int i = 0; i < gemCount_Hole1; i++)
{
int Gem_prefabIndex = UnityEngine.Random.Range(0, 9);
gemList1.Add(Instantiate(Gem_prefabList[Gem_prefabIndex], new Vector2((xPos_Hole1 + (Random.Range(-20, 20))) * 2.0F, (-229 + (20 * i))), Quaternion.identity));
}
}
}
I'm not 100% sure of what you're trying to achieve but I will answer as best I can.
For a start, any gameobject you are going to be instantiating (spawning) at run time should ideally be done so from a prefab.
Secondly, to spawn them at random intervals you want to be checking if they should be spawned at different time frames. This can be achieved through a co-routine or the Update function. I would recommend Update if this is new to you.
Update is called every frame.. and it's with this that you can achieve timed events. You can use a variety of helper methods to determine the time since the last frame or the real time elapsed.
For example
public class MyGameObject : Monobehaviour {
void Start() {
//This is called first, use it to set up whatever you want.
}
void Update() {
//This will be called every frame.
//Each frame or time lapse will determine if I should spawn
// a new gameobject.
}
}
Update
After looking at the game you have linked in your post I can offer the following advice.
Something like the following may point you in the right direction.
public int[] gemsInCups = new int [] {4,4,4,4,4,4,0,4,4,4,4,4,4,0};
public void Distribute(int position){
int gems = gemsInCups[position];
for(int i = position + 1; gems > 0; i++){
gemsInCups[position] ++;
gems --;
//Check the end of the array has not been reached.
//If it has, start distributing again from the first position provided
// there are still gems to distribute.
}
}
You will need some additional logic to finish this.
What you should remember is, I usually find it much more manageable keeping my data and my view (gameobjects) under different scopes... but the view will change to reflect the data and does not directly alter it. Now you know how many gems there are in each cup, you can simply update this each frame.
I would like to make a bowling game in unity and I want to change the players after they threw the ball two times and the whole game to continue for 4 turns.
so I have two balls and each one has a script with a playerController that moves around the balls and after collision they respawn in the original position.
so for the turns I made a gameController that enables player1 (ball) input and disables player2 input then it enables player2 and disables player1.
How can I make player 1 to play twice and then change to player 2.
the script is:
public IEnumerator gamePlay()
{
if (pl1.hasPlay == false)
{
pl1.gameObject.SendMessage("Activate");
pl2.gameObject.SendMessage("Deactivate");
}
if (pl1.hasPlay == true)
{
pl2.gameObject.SendMessage("Activate");
pl1.gameObject.SendMessage("Deactivate");
}
yield return 0;
}
I'd recommend using two integers: one to store the number of plays and another to store the number of rounds.
It would look like this:
private int plays;
private int rounds;
private void Start()
{
plays = 0;
rounds = 0;
StartCoroutine(gamePlay());
}
public void NextBall()
{
plays++;
// Here you can change the logic behind the 2 balls
// (I remember it changes depending on whether you did a strike or not, if it's your last play or not, ...)
if (plays >= 2)
{
plays = 0;
rounds++;
StartCoroutine(gamePlay());
}
}
public IEnumerator gamePlay()
{
// This is based on player 1 being the first player
pl1.hasPlay = (rounds % 2 == 0);
pl2.hasPlay = !pl1.hasPlay;
pl1.gameObject.SendMessage(pl1.hasPlay ? "Activate" : "Deactivate");
pl2.gameObject.SendMessage(pl2.hasPlay ? "Activate" : "Deactivate");
yield return 0;
}
Also i'm not sure why you declared gamePlay() as an IEnumerator and not as a method but I guess you needed it this way :)
I changed the if/else condition by a ternary operator since the content on both part was similar, I find it easier to read like this.
Hope this helps,