How do I save the score every time the scene resets and make it my new highscore when the score is higher than the highscore? - unity3d

I'm currently recreating Jetpack Joyride and I'm having trouble with adding a highscore. I currently track my score on where my player is on the Y position and placing the score on a canvas. So I was wondering how do I save the score every time the score is higher that the highscore and when the scene resets.
This is what I currently use to track Y position of my player:
public class Score : MonoBehaviour
{
public Transform player;
public Text scoreText;
void Update()
{
scoreText.text = player.position.x.ToString("0" + "M");
}
}

There is multiple ways to implement high score.
You can use PlayerPrefs to store data in each scene and then load the data from storage and save again if the latest score is higher than the previous one.
You can create a global object which is not destroyed when new scenes load. In that object, you can attach a high score script that will keep track of the score.
Example Script for the 2nd Option
using UnityEngine;
using System.Collections;
public class MyCustomScript : MonoBehaviour {
public int score = 0;
void Awake()
{
GameObject[] objs = GameObject.FindGameObjectsWithTag("global");
if (objs.Length > 1)
{
Destroy(this.gameObject);
}
DontDestroyOnLoad(this.gameObject);
}
// Update is called once per frame
void Update () {
if( score < **getScore()** ){
score = getScore();
}
}

You should be saving that value when your player lose the game look at the comments I added to understand.
public class Score : MonoBehaviour
{
public Transform player;
public Text scoreText;
public Text highScoreText;
public float score;
bool lost;
private void Start()
{
HighScoreCheck();
}
void Update()
{
score = player.position.x;
//this is storing the score
scoreText.text = score.ToString("0" + "M");
highScoreText.text = PlayerPrefs.GetFloat("HighScore").ToString();
//this is showing the highest score recorded
LoseCheck();
}
private void HighScoreCheck()
{
if (!PlayerPrefs.HasKey("HighScore"))
//checking if this key has any value saved to it
{
Debug.Log("No High Score recorded Yet");
}
else
{
Debug.Log("HighScore is : " + PlayerPrefs.GetFloat("HighScore"));
}
}
private void LoseCheck()
{
if (lost)
{
if (score> PlayerPrefs.GetFloat("HighScore"))
{
PlayerPrefs.SetFloat("HighScore", score);
//this is how you save a float/int into a key that is stored in the device
}
else
{
Debug.Log("No new high score");
}
}
}
}

Related

How replay score to 0 using unity?

I have quiz game which is the last game the score is come out.
the score is save and can show when the game is over, but when i replay the game the score don't return to zero
Here is my code in question and answer
public class QuestionAnswer : MonoBehaviour
{
public GameObject feedback_benar, feedback_salah;
public void answer(bool QuestionAnswer){
if (QuestionAnswer) {
feedback_benar.SetActive(false);
feedback_benar.SetActive(true);
int skor = PlayerPrefs.GetInt ("skor") + 10;
PlayerPrefs.SetInt ("skor", skor);
} else{
feedback_salah.SetActive(false);
feedback_salah.SetActive(true);
}
gameObject.SetActive (false);
transform.parent.GetChild(gameObject.transform.GetSiblingIndex()+1).gameObject.SetActive (true);
gameObject.SetActive (true);
}
}
and this in my score script code
public class Skor : MonoBehaviour
{
void Update()
{
GetComponent<Text> ().text = PlayerPrefs.GetInt ("skor").ToString();}}
}
}
If you want the score to reset each time you play the quiz simply don't save it, however if you want to implement a high score system you would do something like this.
private float score;
private void Update()
{
if (QuestionAnswered)
{
//Adds one to score if its right
score++;
}
}
void EndGame()
{
// score only gets saved if it is higher than the previously saved highscore
if (score > PlayerPrefs.GetFloat("HighScore", 0f))
{
PlayerPrefs.SetFloat("HighScore", score);
}
}
Then you simply call the endgame method when you want the game to end and it will compare highscore with score and if score is greater than saved highscore it will get updated.

How do I change the pitch of audio when slowing down time?

I've been making a game using Unity and I added a slow motion function. Afterwards, when I added audio, I wanted to change the pitch of all audio whenever the slow motion function ocurred, but I can't seem to do it. I've been using Brackey's audio tutorial (here if you wanna see it) to guide me into using audio in Unity
Here is my audio manager:
using UnityEngine.Audio;
using System;
public class soundManager : MonoBehaviour
{
public Sound[] sounds;
void Awake()
{
foreach(Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
public void Play(string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
here is my slow motion script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeManager : MonoBehaviour
{
public float slowdownFactor = 0.05f;
public float slowdownLength = 2.0f;
private void Update()
{
Time.timeScale += (1f / slowdownLength) * Time.unscaledDeltaTime;
Time.timeScale = Mathf.Clamp(Time.timeScale, 0f, 1f);
}
public void DoSlowMotion()
{
Time.timeScale = slowdownFactor;
Time.fixedDeltaTime = Time.timeScale * 0.02f;
}
}
I wanted to change the pitch of all audio
If you want to change the pitch of any song at runtime you can simply use the source of type AudioSource that is saved in the sound class and edit it's values directly.
If you then do this as a foreach loop, in your soundManager class, with each song in your array, you can pitch down all of them.
Change All Pitch Values:
public void ChangeAllPitchValues(float pitchDifference) {
// Loop through our whole sound array.
foreach (Sound s in sounds) {
// Adjust pitch value equal to the given difference.
s.source.pitch += pitchDifference;
}
}
Additionally it seems like you are missing the singleton pattern that Brackeys used. You should probably add that as well to easily call the soundManager from any other script including your TimeManager.
#region Singelton
public static soundManager instance;
private void Awake() {
// Check if instance is already defined
// and if this gameObject is not the current instance.
if (instance != null) {
Debug.LogWarning("Multiple instances found. Current instance was destroyed.");
Destroy (gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
foreach (Sound s in sounds) {
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
s.source.loop = s.loop;
}
}
#endregion
Once you've added the singleton pattern and the new function in the soundManager, then you can simply call that function from your DoSlowMotion() method.
Call the Method:
public void DoSlowMotion() {
...
// Pitch all songs down to 0.95f instead of 1f.
soundManager.instance.ChangeAllPitchValues(-slowdownFactor);
}

Changing card position

I am creating a card game in unity. I have 4 cards and instantiated them randomly 16 times on 4 zones when a button is clicked. Each zone contains 4 random cards. I want to know how can I know which card is in which zone and then change the position of that particular card to another zone.
This is the script attached to each of my card:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Click : MonoBehaviour
{
public GameObject Canvas;
public GameObject MyArea;
private bool isDragging=false;
private bool isOverBottomArea=false;
private GameObject BottomArea;
private GameObject startParent;
private Vector2 startPosition;
private void Awake()
{
MyArea=GameObject.Find("MyArea");
Canvas=GameObject.Find("Canvas");
}
// Update is called once per frame
void Update()
{
if (isDragging)
{
transform.position= new Vector2(Input.mousePosition.x,Input.mousePosition.y);
transform.SetParent(Canvas.transform, true);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
isOverBottomArea=true;
BottomArea=collision.gameObject;
}
private void OnCollisionExit2D(Collision2D collision)
{
isOverBottomArea=false;
BottomArea=null;
}
public void StartDrag()
{
startParent= transform.parent.gameObject;
startPosition=transform.position;
if(startParent != MyArea)
{
isDragging=false;
}
else
{
isDragging=true;
}
}
public void EndDrag()
{
isDragging=false;
if (isOverBottomArea && startParent==MyArea )
{
transform.SetParent(BottomArea.transform, false);
}
else
{
transform.position=startPosition;
transform.SetParent(startParent.transform, false);
}
}
}
And this is the script attached to the button which on clicking instantiate the 16 cards:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DrawCards : MonoBehaviour
{
public GameObject Card1;
public GameObject Card2;
public GameObject Card3;
public GameObject Card4;
public GameObject MyArea;
public GameObject LeftArea;
public GameObject RightArea;
public GameObject BottomArea;
List<GameObject> cards = new List<GameObject>();
void Start()
{
cards.Add(Card1);
cards.Add(Card2);
cards.Add(Card3);
cards.Add(Card4);
}
int count1 = 0;
public void OnClick()
{
count1++;
if (count1 == 1)
{
for (var i=0; i<4; i++)
{
GameObject playerCard = Instantiate(cards[Random.Range(0, cards.Count)], new Vector3(0,0,0), Quaternion.identity);
playerCard.transform.SetParent(MyArea.transform, false);
GameObject leftCard = Instantiate(cards[Random.Range(0, cards.Count)], new Vector3(0,0,0), Quaternion.identity);
leftCard.transform.SetParent(LeftArea.transform, false);
GameObject rightCard = Instantiate(cards[Random.Range(0, cards.Count)], new Vector3(0,0,0), Quaternion.identity);
rightCard.transform.SetParent(RightArea.transform, false);
GameObject bottomCard = Instantiate(cards[Random.Range(0, cards.Count)], new Vector3(0,0,0), Quaternion.identity);
bottomCard.transform.SetParent(BottomArea.transform, false);
}
}
}
}
I would consider storing the values to each of your 4 zones with card data to be utilized later. I do not exactly know what a zone is or what it means if there can be more than 4 cards to a zone, etc.
Here is a very general approach to having zones, cards and a zone manager. I can help clarify any details as to what the code does but I commented it quite extensively. I did not integrate it into your code but rather it should be used as a template by you to do so. The code I am providing is untested and should not just be copy-pasted as your question is still rather general. I can explain anything further if you need.
Card class on each moveable card
// this class is on each of your cards
public class Card : MonoBehaviour,
{
// current index of this card
private int cardIndex = -1;
// stores the current zone this card is in
private Zone currentZone = null;
// init this object with the zone it is in and the card index it has
public void InitCard(Zone zone, int idx)
{
cardIndex = idx;
currentZone = zone;
}
// I am using the same EndDrag - I am assuming you are using an editor component of EventTrigger with an
// OnBeginDrag and OnEndDrag instead of implementing the IHandlers in code
public void EndDrag()
{
// determine if we are over a zone - I am going to use the MouseInput as with a drag,
// you would be dragging the object using the cursor or a finger
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// only look for the layer of the Zone objects
// make sure to add a layer to just the zones and make sure
// it is spelled / case-sensative so exactly the same
int layer_mask = LayerMask.GetMask("ZoneLayer");
// if we hit a zone, determine if it is a new zone and to move the card
if(Physics.Raycast (ray, out hit, Mathf.Infinity, layer_mask))
{
Zone newZone = hit.gameObject.GetComponent<Zone>();
// we hit a zone, is it the same zone?
if(currentZone == newZone)
{
// handle however you want to do a case where it is the same zone
// reset the card position possibly
return;
}
// we are in a new zone, so need to remove this card from the old zone and add it to the new one
// I am not sure how you are handling max card counts in each zone, etc. so this is very basic implementation
currentZone.RemoveCard(this);
newZone.AddCard(this);
// update our local zone reference
currentZone = newZone;
}
}
// setters / getters
public int CardIndex
{
set{cardIndex = value;}
get{return cardIndex;}
}
public Zone CurrentZone
{
set{currentZone = value;}
get{return currentZone;}
}
}
ZoneManager should be on an object that manages your individual zones, so possibly the Canvas object that all of this data is on
// this class will manage each one of your zones
public class ZoneManager : MonoBehaviour
{
// list of our prefabs
[SerializeField] private List<GameObjects> allCardsPrefabList = new List<GameObjects>();
// create a list of zone data of size 4
[SerializeField] private Zone[] myZoneData = new Zone[MAX_ZONE_COUNT];
// max number of zones we can have
private const int MAX_ZONE_COUNT = 4;
// max number of cards we can have on Init
private const int MAX_INIT_CARD_COUNT = 4;
// init our zones in Start() - as I do not know if you are working with UI
// and UI components are only set before Start but not Awake
private void Start()
{
// iterate over each zone
for(int x = 0; x < MAX_ZONE_COUNT; ++x)
{
// iterate over 4 new cards
for(int y = 0; y < MAX_INIT_CARD_COUNT; ++y)
{
// I am using this overload of the Instantiate
// public static Object Instantiate(Object original, Transform parent, bool instantiateInWorldSpace);
// grab our new idx
int newCardIdx = Random.Range(0, cards.Count);
// spawn a new card childed to our zone at the new card idx
Card newCard = Instantiate(allCardsPrefabList[newCardIdx], myZoneData[x].transform, false).GetComponent<Card>();
// init our card zone / idx
newCard.Init(myZoneData[x], newCardIdx);
// add this card to a zone
myZoneData[x].AddNewCard(newCard);
}
}
}
}
The Zone class should be on each of your individual zones
// this is a script that will house each of your zones and will be used to add cards to your zone
public class Zone : MonoBehaviour
{
private const int MAX_INIT_CARDS = 4;
// list of our current cards of this zone
public List<CardData> zoneCards = new List<CardData>();
// called from when a cast is detected on a drop
public void AddNewCard(Card newCard)
{
zoneCards.Add(newCard);
}
// remove a card instance from our list as it is no longer apart of this zone
public void RemoveCard(Card oldCard)
{
zoneCards.Remove(oldCard);
}
}
Again, this is a general idea, not a straightforward answer. Use it as a template to add to your existing code.

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

PlayerPrefs for score in dynamically loaded levels

Im creating a brick breaker game in Unity with one scene called Game that loads every single level based on data received from a json file.
I.E :
Once all bricks are destroyed from 1 level, the second level is
loaded in the same scene, and so on.
Once you lose, a "Lose" scene is loaded with a "Play Again" button.
I'd like the high score information to be retained in the player prefs even after you press the "Play Again" button.
But I'm a bit confused to how this works. This is my code for score:
using UnityEngine;
using UnityEngine.UI;
public class Score : MonoBehaviour
{
public Text scoreText;
public Text highScoreText;
private int score;
private int highScore;
void Start()
{
score = 0;
GetHighScore();
}
void Update()
{
UpdateScore();
SetHighScore();
GetHighScore();
}
// TO UPDATE HIGH SCORE
void SetHighScore()
{
if (score > highScore)
{
PlayerPrefs.SetInt("HighScore", score);
}
}
void GetHighScore()
{
highScore = PlayerPrefs.GetInt("HighScore");
highScoreText.text = "High score: " + highScore;
}
// TO UPDATE HIGH SCORE
// TO UPDATE SCORE
public void AddPoints(int points)
{
score = score + points;
UpdateScore();
}
void UpdateScore()
{
scoreText.text = "score: " + score;
}
// TO UPDATE SCORE
}
So far the score updates fine, but nothing happens to the high score. Any help is appreciated!
This method here achives nothing
void GetHighScore()
{
PlayerPrefs.GetInt("HighScore");
}
It should be
void GetHighScore()
{
highScore = PlayerPrefs.GetInt("HighScore");
}
And i dont see the point in calling it every frame in Update. Call it once in Start
Also, you may want to update highScore in SetHighScore
if (score > highScore)
{
highScore = score;
PlayerPrefs.SetInt("HighScore", highScore);
highScoreText.text = "high score: " + highScore;
}