UPDATE:
The code from the answer didn't work but I had some progress, but I am still having issues. Updated.
This is my UpgradeUI script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
OnClick functions do work also with the Debug.Log. The code below is the Stats script which also updates my HealthUI.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class Stats
{
public static Stats st;
[SerializeField]
private BarScript bar;
public float maxVal;
public float currentVal;
public int playerDamage = 20;
public float CurrentVal
{
get
{
return currentVal;
}
set
{
this.currentVal = Mathf.Clamp(value, 0, maxVal);
bar.Value = currentVal;
}
}
public float MaxVal
{
get
{
return maxVal;
}
set
{
this.maxVal = value;
bar.MaxValue = value;
}
}
public void Initialize()
{
this.MaxVal = maxVal;
this.CurrentVal = currentVal;
}
}
I did progress a bit. I placed this code:
public void HpUp () { stats.maxVal += 20; stats.currentVal += 20; }
public void DmgUp() { stats.playerDamage += 10; }
into my PlayerManager and it connects with the Stats script I shared and my health does update. I removed the UpgradeMenuUI. Now the problem is that my damage does not update. Well actually it updates but the value doesn't reach where I need it to. I added a
public int playerDamage;
in my Stats script. In my Damage script, which calculates the damage for my Player and the enemy, I changed
aiManager.Enemy1Damage(playerDamage); to
aiManager.Enemy1Damage(stats.playerDamage);
and I call my playerDamage from Stats into my Damage script with these:
public Stats stats;
and
public void Awake()
{
stats = new Stats();
}
So the problem is that if I change the playerDamage in my Stats script from
this: public int playerDamage;
to
public int playerDamage = 20;
The value does work and my Player damage is indeed 20 when I hit the AI. However in the inspector my damage(the public int from Stats script) is 0 and if I press my upgrade damage button it goes up by 10 (that's what I set in my PlayerManager script:
public void DmgUp()
{
stats.playerDamage += 10;
}
), but that doesn't affect in any way the value 20. So it is just useless, like pumping air. I don't know how to make the public int playerDamage to update from the OnClick. If make it just playerDamage; without a number nothing updates and the damages is 0.
You have the Stats as a Singleton but are accessing a new instance of it on your button. Try changing:
public void HpUpgrade()
{
stats.maxVal = (int)(stats.Instance.maxVal += hpUp);
}
public void DmgUpgrade()
{
dmg.playerDamage = (int)(stats.Instance.maxVal += dmgUp);
}
I can't quite tell if you've confirmed that your OnClick event is triggering for the buttons. If they aren't, make sure that you have the EventSystem game object in your scene.
Related
I am a beginner developer and I am trying to create a timer that shows how many seconds have passed since the start of the game on Unity. I want to change the text component (I am using TextMeshPro to be specific) of the object with the script.
I have been searching on google but nothing has worked yet. I can't find a way to link the public Text to the text component, and I get an error because the text is null.
Well you should reference the text in your code and then change it with a coroutine
public class Timer : MonoBehaviour
{
[SerializeField]
private TMP_Text textComponent;
private int seconds = 0;
private IEnumerator TimeRoutine()
{
while(Application.isPlaying)
{
textComponent.text = seconds.ToString();
yield return new WaitForSeconds(1f);
seconds++;
}
}
private void Start()
{
StartCoroutine(nameof(TimeRoutine));
}
}
Code for Timer with Text mesh pro
using UnityEngine;
using TMPro;
public class Timerexample : MonoBehaviour
{
float cntdnw = 30.0f;
public TMP_Text disvar;
void Update()
{
if(cntdnw>0)
{
cntdnw -= Time.deltaTime;
}
double b = System.Math.Round (cntdnw, 2);
disvar.text = b.ToString ();
if(cntdnw < 0)
{
Debug.Log ("Completed");
}
}
}
Source: Unity timer
For some reason I need to start the scene with the menu open, then close it, then grab the coin and go to the shop menu in order for the shop ui to update my moneyAmount. If i start the scene with the shop menu closed and pick up the coin then go to my shop menu it doesnt update. And when i buy my helmet it says reference not set to an object even though all im doing is getting my player health component and adding 50 to it so why do i need to reference any kind of object? here are the scripts with my GameControl script first.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameControl : MonoBehaviour
{
public Text moneyText;
public static int moneyAmount;
int isHelmetSold;
int isBeltSold;
int isPantsSold;
int isShirtSold;
int isBootsSold;
// Start is called before the first frame update
void Start()
{
moneyAmount = PlayerPrefs.GetInt("MoneyAmount");
isHelmetSold = PlayerPrefs.GetInt("IsHelmetSold");
isBeltSold = PlayerPrefs.GetInt("IsBeltSold");
isPantsSold = PlayerPrefs.GetInt("IsPantsSold");
isShirtSold = PlayerPrefs.GetInt("IsShirtSold");
isBootsSold = PlayerPrefs.GetInt("IsBootsSold");
}
// Update is called once per frame
void Update()
{
moneyText.text = moneyAmount.ToString();
}
Here is my PageOneShop script which makes my item buyable when amount is reached (for the time being ive only finished my helmet not the rest so if you can ignore all of the public texts and buttons as i know i havent added them yet)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class PageOneShop : MonoBehaviour
{
public static int moneyAmount;
int isHelmetSold;
int isBeltSold;
int isPantsSold;
int isShirtSold;
int isBootsSold;
public Text moneyText;
public Text helmetPrice;
public Text beltPrice;
public Text pantsPrice;
public Text shirtPrice;
public Text bootsPrice;
public Button buyHelmetButton;
public Button buyBeltButton;
public Button buyPantsButton;
public Button buyShirtButton;
public Button buyBootsButton;
void Start()
{
moneyAmount = PlayerPrefs.GetInt("MoneyAmount");
isHelmetSold = PlayerPrefs.GetInt("IsHelmetSold");
isBeltSold = PlayerPrefs.GetInt("IsBeltSold");
isPantsSold = PlayerPrefs.GetInt("IsPantsSold");
isShirtSold = PlayerPrefs.GetInt("IsShirtSold");
isBootsSold = PlayerPrefs.GetInt("IsBootsSold");
}
// Update is called once per frame
void FixedUpdate()
{
moneyText.text = moneyAmount.ToString();
isHelmetSold = PlayerPrefs.GetInt("IsHelmetSold");
if (moneyAmount >= 150 && isHelmetSold == 0)
buyHelmetButton.interactable = true;
else
buyHelmetButton.interactable = false;
isBeltSold = PlayerPrefs.GetInt("IsBeltSold");
if (moneyAmount >= 120 && isBeltSold == 0)
buyBeltButton.interactable = true;
else
buyBeltButton.interactable = false;
isPantsSold = PlayerPrefs.GetInt("IsPantsSold");
if (moneyAmount >= 100 && isPantsSold == 0)
buyPantsButton.interactable = true;
else
buyPantsButton.interactable = false;
isShirtSold = PlayerPrefs.GetInt("IsShirtSold");
if (moneyAmount >= 100 && isShirtSold == 0)
buyShirtButton.interactable = true;
else
buyShirtButton.interactable = false;
isBootsSold = PlayerPrefs.GetInt("IsBootsSold");
if (moneyAmount >= 80 && isBootsSold == 0)
buyBootsButton.interactable = true;
else
buyBootsButton.interactable = false;
}
public void buyHelmet()
{
moneyAmount -= 150;
GetComponent<PlayerHealth>().maxHealth += 50;
PlayerPrefs.SetInt("IsHelmetSold", 1);
helmetPrice.text = "Sold!";
buyHelmetButton.gameObject.SetActive(false);
}
public void buyBelt()
{
moneyAmount -= 120;
GetComponent<PlayerHealth>().maxHealth += 50;
PlayerPrefs.SetInt("IsBeltSold", 1);
helmetPrice.text = "Sold!";
buyBeltButton.gameObject.SetActive(false);
}
public void buyShirt()
{
moneyAmount -= 100;
GetComponent<PlayerHealth>().maxHealth += 50;
PlayerPrefs.SetInt("IsShirtSold", 1);
helmetPrice.text = "Sold!";
buyShirtButton.gameObject.SetActive(false);
}
public void buyPants()
{
moneyAmount -= 100;
GetComponent<PlayerHealth>().maxHealth += 50;
PlayerPrefs.SetInt("IsPantsSold", 1);
helmetPrice.text = "Sold!";
buyPantsButton.gameObject.SetActive(false);
}
public void buyBoots()
{
moneyAmount -= 80;
GetComponent<PlayerHealth>().maxHealth += 50;
PlayerPrefs.SetInt("IsBootsSold", 1);
helmetPrice.text = "Sold!";
buyBootsButton.gameObject.SetActive(false);
}
And here is my coin script on my coins that i pick up.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Coin : MonoBehaviour
{
void OnTriggerEnter2D (Collider2D col)
{
PageOneShop.moneyAmount += 200;
GameControl.moneyAmount += 200;
Destroy(gameObject);
}
It's important to figure out the pieces involved. Starting from the minimum amount of code, I think this is a good start:
"GameControl"
using UnityEngine;
using UnityEngine.UI;
public class GameControl : MonoBehaviour
{
public Text moneyText;
public static int moneyAmount;
// Start is called before the first frame update
// Start is only called while this is active and enabled
void Start()
{
moneyAmount = PlayerPrefs.GetInt("MoneyAmount");
}
void Update()
{
moneyText.text = moneyAmount.ToString();
}
}
"PageOneShop"
using UnityEngine;
using UnityEngine.UI;
public class PageOneShop : MonoBehaviour
{
public static int moneyAmount;
public Text moneyAmountText;
public Text helmetPrice;
// Start is called before the first frame update
// Start is only called while this is active and enabled
void Start()
{
moneyAmount = PlayerPrefs.GetInt("MoneyAmount");
}
// Update is called once per frame
void Update()
{
moneyAmountText.text = moneyAmount.ToString();
}
public void buyHelmet()
{
moneyAmount -= 150;
helmetPrice.text = "Sold!";
}
}
"Coin"
using UnityEngine;
public class Coin : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D col)
{
PageOneShop.moneyAmount += 200;
GameControl.moneyAmount += 200;
Destroy(gameObject);
}
}
Everything in these files is used, and we can replicate our issue. Before this point, my previous answer was correct and you weren't updating the moneyAmount instance inside PageOneShop. Now there is a new issue and it's more subtle, because it's strictly speaking not in your code.
Start is a Unity called method, and is called "before the first frame update". Start is also only called if the component is enabled and the object it's attached to is active. If the object it's attached to is de-activated (like I'm guessing your menu is when you load your scene) Start will be called the first time you activate the object (open the menu). Since nowhere are you updating the "MoneyAmount" in PlayerPrefs, that value is still 0. If you pick up the coin, then open the menu for the first time, the value would be 200 but instead is re-set to 0 when this runs:
void Start()
{
moneyAmount = PlayerPrefs.GetInt("MoneyAmount");
}
Probably the quickest way to fix this is to add this line to coin:
public class Coin : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D col)
{
PageOneShop.moneyAmount += 200;
GameControl.moneyAmount += 200;
// Set the PlayerPrefs value
PlayerPrefs.SetInt("MoneyAmount", GameControl.moneyAmount);
Destroy(gameObject);
}
}
At this point even if the menu opens after the coin is picked up, the correct value is retrieved from PlayerPrefs so nothing is overwritten.
Potentially a new problem has been introduced, which is even in the editor during testing, this write to PlayerPrefs is occurring so money may accumulate as multiple play sessions are run.
There are a few other issues as well. As #derHugo accurately points out you should stop polling for changes in update to an event based approach, and the use of two static variables which are kept in sync like this is at best wasteful, and at worst can cause some real serious headaches.
Let's start by addressing the double static variable use. You could replace all the references of one with the other, but I propose introducing a third class to handle the player's "Wallet" where they keep their money. You could write something as simple as:
public static class Wallet
{
public static int MoneyAmount;
}
All the references to the other two static moneyAmount variables could be replaced with this Wallet.MoneyAmount.
The polling could now be replaced with events pretty quickly by adding an event to the Wallet class, and replacing the field with a property:
using System;
public static class Wallet
{
public static event Action OnMoneyAmountChanged;
private static int moneyAmount;
public static int MoneyAmount
{
get
{
return moneyAmount;
}
set
{
moneyAmount = value;
// maybe null check, or follow a "never null" approach
OnMoneyAmountChanged();
}
}
}
Instead of always checking moneyAmount you can now add some code like:
void Start()
{
// Listen for changes
Wallet.OnMoneyAmountChanged += handleMoneyAmountChanged;
}
private void OnDestroy()
{
// Remember to always remove your listeners when you're done with them
Wallet.OnMoneyAmountChanged -= handleMoneyAmountChanged;
}
// set the text to the value held in the wallet whenever the amount changes
void handleMoneyAmountChanged()
{
moneyText.text = Wallet.MoneyAmount.ToString();
}
Maybe you don't want the PlayerPrefs.SetInt call in Coin any more, and instead you could move it into the Wallet now. If you put it in a [Conditional] method you could also protect yourself from constantly overwriting PlayerPrefs with every play session in the editor.
using System;
using System.Diagnostics;
using UnityEngine;
public static class Wallet
{
public static event Action OnMoneyAmountChanged;
private static readonly string moneyAmountKey = "MoneyAmount";
private static int moneyAmount;
public static int MoneyAmount
{
get
{
return moneyAmount;
}
set
{
moneyAmount = value;
OnMoneyAmountChanged();
// this method call is conditional on a defined constant
saveMoneyAmount();
}
}
// This part will only run if ENABLE_PLAYERSPREFS is defined.
[Conditional("ENABLE_PLAYERPREFS")]
private static void saveMoneyAmount()
{
PlayerPrefs.SetInt(moneyAmountKey, moneyAmount);
}
}
You can type whatever you want into the scripting define symbols text box described in the "Platform custom #defines" section on this page. If you define "ENABLE_PLAYERPREFS" that method will call.
You probably now want to initialize your wallet as well, and to keep things symmetrical you could "close" it as well. This would let you cut down on a lot of reads and writes to player prefs.
public static void Open()
{
// this is another way to interact with platform define constants
#if ENABLE_PLAYERPREFS
moneyAmount = PlayerPrefs.GetInt(moneyAmountKey);
#else
moneyAmount = 0;
#endif
}
public static void Close()
{
// maybe you want to do other stuff in here
saveMoneyAmount();
}
If you called Open at the start of your application, and Close at the end you could just use the variables in memory the rest of the time and remove the saveMoneyAmount call from the MoneyAmount setter.
As for this second question you added later than my original answer, "And when i buy my helmet it says reference not set to an object even though all im doing is getting my player health component and adding 50 to it so why do i need to reference any kind of object?". You didn't post the line number, but I can guess that it originates on any/all the calls that look like this GetComponent<PlayerHealth>().maxHealth += 50; since those are all in methods on PageOneShop so unless you've added your PlayerHealth component to your menu object that GetComponent will be null, at which point you can refer to What is a NullReferenceException, and how do I fix it?
Recently, I've been working on a racing game that requires the player to avoid Radioactive Barrels that should subtract 15 seconds if they happen to collide into them; Below is code for my 'Timer' script, and my Barrel Collision Script.
Timer Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
public float timeRemaining = 10;
public bool timerIsRunning = false;
public Text timeText;
public void Start()
{
// Starts the timer automatically
timerIsRunning = true;
}
public void Update()
{
if (timerIsRunning)
{
if (timeRemaining > 0)
{
timeRemaining -= Time.deltaTime;
DisplayTime(timeRemaining);
}
else
{
Debug.Log("Time has run out!");
timeRemaining = 0;
timerIsRunning = false;
}
}
}
public void DisplayTime(float timeToDisplay)
{
timeToDisplay += 1;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
float seconds = Mathf.FloorToInt(timeToDisplay % 60);
timeText.text = string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
Next, is my Barrel Collision Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExplosionTrigger : MonoBehaviour
{
public AudioSource ExplosionSound;
public ParticleSystem Explosion;
public void OnTriggerEnter(Collider collider)
{
Explosion.Play();
ExplosionSound.Play();
Timer.timeRemaining += 1.0f;
}
}
Is there anyway I could subtract time by colliding into said Barrels from the Timer script?
The easiest way to get the timeRemaining exposed to other classes is using the static keyword.
public static float timeRemaining = 10;
By making the variable static, it can be referenced by other classes. If you would rather not expose the variable completely, you can make static setter/getters. The variable would then be private static float timeRemaining = 10; when using the setter/getter.
public static float TimeRemaining
{
get{ return timeRemaining;}
set { timeRemaining = value;}
}
If you happen to want to expose more variables or methods to your classes from a script, I would recommend either implementing the Singleton Pattern or possibly implement your own Event System which uses the Singleton Pattern to pass events freely in your project. That way, you can subscribe and fire events for various scripts to listen for.
Yes, you could use GetComponent<>() to access the script from a different game object. You should set a GameObject variable, and drag the game object with the script on it. Add this to your barrel script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExplosionTrigger : MonoBehaviour
{
public AudioSource ExplosionSound;
public ParticleSystem Explosion;
public GameObject Timer;
public float timeLoss;
public Timer timer;
void Start()
{
timer = Timer.GetComponent<TimerScript>();
}
public void OnTriggerEnter(Collider collider)
{
timer.timeRemaining -= 1f;
Explosion.Play();
ExplosionSound.Play();
}
}
You can change the values in the TimerScript using a period. Make sure to change TimerScript to the name of the script on the Timer game object.
hi I am new to unity and C# and I am making my first game. In it I have a Scriptable object named "sss".It contains values like coins,speed,jump power. It works fine in the editor but after I build it it wont work.what I mean by it wont work is after the player dies in the game depending on how long they live they will get a certain amount of coins when they go to the store they will see those coins displayed as a UI and they can spend them on things like speed and jump boost. this works in the editor but not in a build. Dose anybody know why this is? here is the code I have in my Scriptable object
using UnityEngine;
[CreateAssetMenu(fileName = "data",menuName = "sss",order = 1)]
public class sss : ScriptableObject
{
public float coins = 0f;
public float speed = 10f;
public float jump = -9.81f;
public bool set = true;
public int face = 1;
}
here is the code I use to display the coins float
coinstext.text = sss.coins.ToString();
and here is the whole store-manager script used to buy stuff
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Security;
using UnityEngine;
using UnityEngine.UI;
public class storemanager : MonoBehaviour
{
public sss sss;
public Text coinstext;
public Slider sliders;
public Slider sliderj;
public int maxcap = 30;
public int maxcapj = 500;
public void Start()
{
coinstext.text = sss.coins.ToString();
}
public void speedbuy()
{
if (sss.coins >= 5 && sss.speed < maxcap)
{
sss.speed += 1;
sss.buyspeed = true;
sss.coins -= 5;
sliders.value += 1;
coinstext.text = sss.coins.ToString();
}
}
public void jumpbuy()
{
if (sss.coins >= 7 && sss.jump < maxcapj)
{
sss.jump += 10;
sss.buyjump = true;
sss.coins -= 7;
sliderj.value += 10;
coinstext.text = sss.coins.ToString();
}
}
}
While changes in ScriptableObject within the UnityEditor are persistent, they are not persistent in a build!
When you use the Editor, you can save data to ScriptableObjects while editing and at run time because ScriptableObjects use the Editor namespace and Editor scripting. In a deployed build, however, you can’t use ScriptableObjects to save data, but you can use the saved data from the ScriptableObject Assets that you set up during development.
So after a build the data in the asset is kind of baked and you can change it at runtime but when restarting the app you will always re-load the data of the build.
You would need to store your data instead in a local file stored on the device itself e.g. using Json like
using UnityEngine;
using System.IO;
...
public class sss : ScriptableObject
{
public float coins = 0f;
public float speed = 10f;
public float jump = -9.81f;
public bool set = true;
public int face = 1;
private const string FILENAME = "sss.dat";
public void SaveToFile()
{
var filePath = Path.Combine(Application.persistentDataPath, FILENAME);
if(!File.Exists(filePath))
{
File.Create(filePath);
}
var json = JsonUtility.ToJson(this);
File.WriteAllText(filePath, json);
}
public void LoadDataFromFile()
{
var filePath = Path.Combine(Application.persistentDataPath, FILENAME);
if(!File.Exists(filePath))
{
Debug.LogWarning($"File \"{filePath}\" not found!", this);
return;
}
var json = File.ReadAllText(filePath);
JsonUtility.FromJsonOverwrite(json, this);
}
}
And then in your manager script call the load and save methods e.g.
public class storemanager : MonoBehaviour
{
public sss sss;
public Text coinstext;
public Slider sliders;
public Slider sliderj;
public int maxcap = 30;
public int maxcapj = 500;
public void Start()
{
sss.LoadFromFile();
coinstext.text = sss.coins.ToString();
}
// Called when the application is going to quit
private void OnApplicationQuit()
{
sss.SaveToFile();
}
Note: Typed on smartphone but I hope the idea gets clear
So, i created two scripts, one named "Stats.cs" registers the player stats and the other one named "PlayerHealth.cs" "makes" the player take damage on contact and updates the Hearts in the HUD. My problem is, whenever i collide with an object that has a tag named "Projectile" it simply doesn't work, my player doesn't take damage at all. The Stats.cs script isn't in any object, the PlayerHealth.cs is in my Player object.
Stats.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Stats{
private int health;
public int maxHP = 3;
public int Health
{
get
{
//Some code
return health;
}
set
{
//Some code
health = Mathf.Clamp(value, 0, maxHP);
}
}
public void SetHealth()
{
Health = maxHP;
}
}
PlayerHealth.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerHealth : MonoBehaviour
{
Stats playerStats = new Stats();
public int curHealth;
public int numOfHearts = 3;
public Image[] hearts;
public Sprite fullHeart;
public Sprite emptyHeart;
void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.CompareTag("Projectile"))
{
Debug.Log("Hello");
DamagePlayer(1);
Destroy(other.gameObject);
}
}
public void DamagePlayer(int damage)
{
playerStats.Health -= damage;
}
// Start is called before the first frame update
void Start()
{
playerStats.SetHealth();
curHealth = numOfHearts;
}
// Update is called once per frame
void Update()
{
curHealth = playerStats.Health;
numOfHearts = playerStats.maxHP;
if (curHealth>numOfHearts){
curHealth = numOfHearts;
}
if(curHealth <= 0){
Die();
}
for (int i = 0; i < hearts.Length; i++)
{
if(i < curHealth){
hearts[i].sprite = fullHeart;
} else
{
hearts[i].sprite = emptyHeart;
}
if(i < numOfHearts){
hearts[i].enabled = true;
} else {
hearts[i].enabled = false;
}
}
}
void Die(){
//Restart
Application.LoadLevel(Application.loadedLevel);
}
}
curHealth is updating so it will stay as the actual Health in Stats and will change the images in HUD.
The Player has a RigidBody2D on him two colliders, one is a box for the body, and the other is a circle collider, so when the player crouches, the circle collider disables.
The Projectiles also have and RigidBody2D with 0 gravity (so it won't fall in mid air) and a BoxCollider2D.
I would check and make sure that the projectile is tagged as Projectile and that the BoxCollider doesn't have "Is Trigger" checked.
I should also say, iterating with that for loop in the Update is very bad practice performance wise. That is happening literally as fast as the machine can loop it and it is doing that every time. I would look into updating it on an event.
Hope this helps!