Im having problem with an endless runner game. The scores are not updating after the first object collided. The script is attached to a prefab object. After the first object collided, the score updated to +100, the second object collides, no change.
void Start()
{
player = GameObject.FindGameObjectWithTag("Gameship");
YourScore = GameObject.Find("Score").GetComponentInChildren<TextMeshProUGUI>();
}
void Update()
{
}
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Border")
{
Destroy(this.gameObject);
}
else if(collision.tag == "Gameship")
{
if(this.gameObject.tag == "Reward")
{
Destroy(this.gameObject);
ChangeScore(100);
}
}
}
void ChangeScore(int changeValue)
{
this.score += changeValue;
YourScore.text = score.ToString();
}
}
Assuming your score is starting from zero i.e. score = 0, after colliding with Reward tagged gameObject, your code does the following:
The Score increases i.e. score += 100 -> score = 100
Updates the text. text = "100"
GameObject gets destroyed.
On destruction of gameObject, your score variable also gets destroyed,
assuming you are instantiating this object again, the whole script is executed again means score = 0, hence doing the above 3 steps over again and setting the text to 100 again which it already is.
I would suggest to use a Singleton class or just store the score variable in a different script that is not getting destroyed.
Currently you destroy the Object and so the scorevalue after it got triggered.
You will need an extra class with a static variable which has a score variable which you increase instead of increasing the local one.
public class ScoreHolder {
public static long score = 0;
}
void Start() {
player = GameObject.FindGameObjectWithTag("Gameship");
YourScore = GameObject.Find("Score").GetComponentInChildren<TextMeshProUGUI>();
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.tag == "Border") {
Destroy(this.gameObject);
} else if(collision.tag == "Gameship") {
if(this.gameObject.tag == "Reward") {
ChangeScore(100);
Destroy(this.gameObject);
}
}
}
void ChangeScore(int changeValue) {
ScoreHolder.score += changeValue;
YourScore.text = ScoreHolder.score.ToString();
}
Related
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");
}
}
}
}
How to make the end of the game in pakman when all the dots have been eaten?
This is the end game code now
void OnTriggerEnter2D(Collider2D co)
{
if (co.name == "PacMan")
{
Destroy(co.gameObject);
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
This is the point eating code
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
If what you're asking is "how do I let the game know the level is over and trigger the end" then just have a variable to hold how many dots are in the level, and every time you eat one and that trigger collider fires, have a counter go up. When the counter equals the total, level ends.
In your class for the dots you could use something like
public class YourDotClass : MonoBehaviour
{
// Keeps track of all currently existing instances of this class
private readonly static HashSet<YourDotClass> _instances = new HashSet<YourDotClass>();
private void Awake ()
{
// Register yourself to the instances
_instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the instances
_instances.Remove(this);
// Check if any instance still exists
if(_instances.Count == 0)
{
// => The last dot was just destroyed!
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
}
However, you should really rethink/restructure your code and think about who shall be responsible for what.
Personally I would not like the dots be responsible for increasing scores and end the game .. I would rather have a component on the player itself, let it check the collision, increase its own points and eventually tell some manager to end the game as soon as all dots have been destroyed ;)
I think you could use something like this. Just store all of the pacdots in an array and once the array is empty you could end the game.
GameObject[] pacdots = GameObject.FindGameObjectsWithTag("pacdot");
void OnTriggerEnter2D(Collider2D collision)
{
// pacman collided with a dot
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
if (pacdots.length == 0)
{
// All dots hit do something
}
}
}
My ideia basically is when i score i get 2 points, but when i touch a certain collider that changes to 3 and i am having a lot a trouble figuring out what colliders to use and how to use them.
I thnik i need to use another ontriggerenter
when i touch the cube it should change to 3
if (i touch a certain collider)
{
void OnTriggerEnter(Collider other)
{
ScoringSystem.theScore += 3;
}
}
else
{
ScoringSystem.theScore += 2;
}
First of all we need to create a GameManager to handle the Bool that checks if we are currently on the Line or not. We do this so we can acces it from all the scripts.
This code should be in a GameManager Object.
// Variable to check if the player is on the line or not
public bool stayingOnLine = false;
#region Singelton
public static GameManager instance;
void Awake()
{
if (instance != null) {
Debug.LogWarning("More than one Instance of GameManager found");
return;
}
instance = this;
}
#endregion
Then we add this Code to the GameObject that handles the LineCollider, to handle when the player enters the Line and when he leaves it. When this happens we change the Varible from the GameManager.
This code should be in your GameObject where the LineCollider that is set to IsTrigger is located.
GameManager gm;
void Start() {
gm = GameManager.instance;
}
void OnTriggerEnter(Collider col) {
// Player has entered the Line ColliderBox
if (col.CompareTag("Player Tag"))
gm.stayingOnLine = true;
}
void OnTriggerExit(Collider col) {
// Player has left the Line ColliderBox
if (col.CompareTag("Player Tag"))
gm.stayingOnLine = false;
}
After that we also need to add code to the GameObject that manages the HoopCollider. Because when the Ball enters we need to check if stayingOnline is true or false and then give different amount of points.
GameManager gm;
void Start() {
gm = GameManager.instance;
}
void OnTriggerEnter(Collider col) {
// Ball has entered the Hoop ColliderBox
if (!col.CompareTag("Ball Tag"))
return;
if (gm.stayingOnLine)
ScoringSystem.theScore += 3;
else
ScoringSystem.theScore += 2;
}
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
Previously I had a script that I could look at a player but only when I had the Tab buttoned pressed down but could not cycle through enemies or anything. Looking all day for answers I came by this and decided it would be great I can now find things tagged enemies and cycle though them but I have not been able to figure out a way to lookat the enemy and stay looking at them and be able to move around them in a circle.
public class debug : MonoBehaviour
{
public List<Transform> targets;
public Transform selectedTarget;
public string targetTag = "Enemy";
private Transform myTransform;
//Use this for initialization
void Start()
{
targets = new List<Transform>();
selectedTarget = null;
myTransform = transform;
AddAllEnemies();
}
public void AddAllEnemies()
{
GameObject[] go = GameObject.FindGameObjectsWithTag(targetTag);
foreach (GameObject enemy in go)
{
AddTarget(enemy.transform);
}
}
public void AddTarget(Transform enemy)
{
targets.Add(enemy);
}
/*private void SortTargetsByDistance()
{
targets.Sort(delegate (Transform t1, Transform t2) {
return (Vector3.Distance(t1.position, myTransform.position).CompareTo)
(Vector3.Distance(t2.position, myTransform.position));
});
} */
private void SortTargetsByDistance()
{
targets.RemoveAll(target => target == null);
targets.Sort(delegate (Transform t1, Transform t2) {
return (Vector3.Distance(t1.position, myTransform.position).CompareTo)
(Vector3.Distance(t2.position, myTransform.position));
});
}
private void TargetEnemy()
{
if (selectedTarget == null)
{
SortTargetsByDistance();
selectedTarget = targets[0];
}
else
{
int index = targets.IndexOf(selectedTarget);
if (index < targets.Count - 1)
{
index++;
}
else
{
index = 0;
}
selectedTarget = targets[index];
}
}
//Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
TargetEnemy();
}
}
}
I am trying to use transform.LookAt(TargetEnemy); in void update but I get an error saying cannot convert from method group to transform. Im really stuck on this now any help is appreciated.
Edit: I am a big dummy I used transform.LookAt(selectedTarget); instead and the character for a split second will turn and lookat the enemy. Now i need to keep the player looking at the target and move around them.
Transform.LookAt
You need to pass it a Transform, not void.
TargetEnemy();
transform.LookAt(selectedTarget);
Alternatively, you can make TargetEnemy() return a Transform.:
private Transform TargetEnemy()
{
/* stuff */
return selectedTarget;
}
transform.LookAt(TargetEnemy());
Hello thanks to everyone that looked and thank #SonicBlue22 for your answer I figured it out after a couple more hours.
void Update()
{
if (Input.GetKeyUp(KeyCode.Tab))
{
TargetEnemy();
transform.LookAt(selectedTarget);
}
if (selectedTarget != null)
{
transform.LookAt(selectedTarget);
}
if (Input.GetKeyUp(KeyCode.Q))
{
selectedTarget = null;
return;
}
}
}