Okay, so I have a toggle button, which controlls the music volume. And it works fine, but when i go to an other scene, and go back - the toggle doesn't work(doesn't change music volume). Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Music : MonoBehaviour
{
AudioSource audioSource;
private float volume = 1f;
Toggle toggle;
private void Awake()
{
SetMusicSingletone();
}
private void Start()
{
audioSource = GetComponent<AudioSource>();
toggle = FindObjectOfType<Toggle>();
}
private void Update()
{
audioSource.volume = this.volume;
}
public void ToggleMusic()
{
if (!toggle.isOn)
{
this.volume = 0f;
}
else if (toggle.isOn)
{
this.volume = 1f;
}
}
private void SetMusicSingletone()
{
var music = FindObjectsOfType<Music>();
if (music.Length > 1)
{
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
}
The toggle uses method ToggleMusic()
Alright so the problem is: You have referenced the ToggleMusic method from this instance of Music in your toggle .. then you change scenes and come back .. but due to the singleton thing you destroy the Music instance linked to this new Toggle -> Toggle does nothing now.
You already have a singleton pattern so why not directly make use of it:
public class Music : MonoBehaviour
{
private static Music _singleton;
public static Music Singleton => _singleton;
[SerializeField] private AudioSource audioSource;
private bool volumeOn;
public bool MusicIsOn => volumeOn;
private void Awake()
{
if (_singleton)
{
Destroy(gameObject);
return;
}
DontDestroyOnLoad(gameObject);
_singleton = this;
if(!audioSource) audioSource = GetComponent<AudioSource>();
ToggleMusic(true);
}
public void ToggleMusic(bool on)
{
audioSource.volume = on ? 1f : 0f;
volumeOn = on;
}
public void ToggleMusic()
{
ToggleMusic(!volumeOn);
}
}
And then on your toggle instead of assigning a callback via the Inspector rather do
public class MusicToggle : MonoBehaviour
{
[SerializeField] private Toggle toggle;
private void Awake ()
{
if(!toggle) toggle = GetComponent<Toggle>();
toggle.onValueChanged.AddListener(OnValueChanged);
toggle.isOn = Music.Singleton.MusicIsOn;
}
private void OnValueChanged (bool on)
{
Music.Singleton.ToggleMusic(on);
}
}
Related
Here is the code for the weapon
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponInfo : MonoBehaviour
{
[SerializeField] private Weapon weapon;
[SerializeField] private Transform muzzle;
[Header("Transform")]
public Transform playerCamera;
public Transform DefaultWeaponPos;
public Transform ADSWeaponPos;
AudioSource shootingSound;
[SerializeField] private AudioClip[] pistolClips = default;
private float timeSinceLastShot = 0f;
[HideInInspector] public bool aiming = false;
private void Start()
{
shootingSound = GetComponent<AudioSource>();
Player.shootInput += Shoot;
Player.reloadInput += StartReload;
Player.aimInput += Aim;
aiming = Player.shootInput != null;
}
public void StartReload()
{
if (!weapon.reloading)
StartCoroutine(Reload());
}
private IEnumerator Reload()
{
weapon.reloading = true;
shootingSound.PlayOneShot(pistolClips[2]);
yield return new WaitForSeconds(weapon.reloadTime);
weapon.currentAmmo = weapon.magSize;
weapon.reloading = false;
}
public void Aim()
{
aiming = true;
}
private bool CanShoot() => !weapon.reloading && timeSinceLastShot > 1f / (weapon.fireRate / 60f);
public void Shoot()
{
if(weapon.currentAmmo > 0)
{
if (CanShoot())
{
if (Physics.Raycast(playerCamera.position, playerCamera.forward, out RaycastHit hitInfo, weapon.maxDistance))
{
Debug.DrawLine(playerCamera.transform.position, hitInfo.point, Color.red, 10f);
print(hitInfo.transform.name);
}
shootingSound.PlayOneShot(pistolClips[0]);
weapon.currentAmmo--;
timeSinceLastShot = 0;
OnGunShot();
}
} else if (!weapon.reloading) shootingSound.PlayOneShot(pistolClips[1]);
}
private void Update()
{
timeSinceLastShot += Time.deltaTime;
Debug.DrawRay(playerCamera.position, playerCamera.forward);
transform.position = aiming ? ADSWeaponPos.position : DefaultWeaponPos.position;
}
private void OnGunShot()
{
}
}
Basically what I want is to make it so that when the player is Aiming it changes the weapon position to ADS, but when the player is not aiming it changes it back. I tried to make it so that when aiming = Aim != null but it didnt work, I also tried other methods but I dont know what to do since I tried looking for a solution but didnt find any
And here's the code for invoking the method in the Player Script
if (Input.GetKey(aimKey))
{
aimInput?.Invoke();
}
The aimInput is a public static Action
I figured it out I just need to create another method called sideAimInput in the player, and it makes it set to aiming = false in the weaponInfo script
I'm developing a TopDown 2D game on Unity with some RPG elements and I did as so, when the player steps on a set of tiles placed on the map, it triggers an animation with a UI showing some text. However, the animation gets called multiple times while the player keeps stepping on the tiles and even when he exits the trigger area.
What I need help with is how to make the animation only trigger once and, while the UI is in on the screen, the player is not allowed to move until the player presses the button in the UI (already programmed and working).
Here is the code I used to make the trigger function:
public class TriggerScript : MonoBehaviour
{
public string popUp;
public Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
PopUpSystem pop = GameObject.FindGameObjectWithTag("GameManager").GetComponent<PopUpSystem>();
pop.PopUp(popUp);
Debug.Log("trigger");
animator.SetTrigger("pop");
}
}
And here is the PopUpSystem that sets the text:
public class PopUpSystem : MonoBehaviour
{
public GameObject popUpBox;
public Animator popupanimation;
public TMP_Text popUpText;
public void PopUp(string text)
{
popUpBox.SetActive(true);
popUpText.text = text;
popupanimation.SetTrigger("pop");
}
}
If, in order to help me, you need more information and details, please ask me in the comment section.
Note that I am new to Unity and have zero experience with it so, if you can be patient and explain things in a simple way, I would enjoy that!
Thank you for reading.
Edit: this is the animator window:
Edit 2: The code that I use for the movement of the player:
public class PLayerController : MonoBehaviour
{
private Rigidbody2D MyRB;
private Animator myAnim;
public string popUp;
[SerializeField]
private float speed;
// Start is called before the first frame update
void Start()
{
MyRB = GetComponent<Rigidbody2D>();
myAnim = GetComponent<Animator>();
}
private void Move()
{
myAnim.SetTrigger("popUp");
}
// Update is called once per frame
void Update()
{
MyRB.velocity = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")) * speed * Time.deltaTime;
myAnim.SetFloat("moveX", MyRB.velocity.x);
myAnim.SetFloat("moveY", MyRB.velocity.y);
if ((Input.GetAxisRaw("Horizontal")==1) ||(Input.GetAxisRaw("Horizontal") == -1)||(Input.GetAxisRaw("Vertical") == 1)||(Input.GetAxisRaw("Vertical") == -1))
{
myAnim.SetFloat("LastMoveX", Input.GetAxisRaw("Horizontal"));
myAnim.SetFloat("LastMoveY", Input.GetAxisRaw("Vertical"));
}
}
}
Edit 3: Code with boolean:
public class TriggerScript : MonoBehaviour
{
public string popUp;
public Animator animator;
bool condition = false;
void Start()
{
animator = GetComponent<Animator>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (condition == false)
{
PopUpSystem pop = GameObject.FindGameObjectWithTag("GameManager").GetComponent<PopUpSystem>();
pop.PopUp(popUp);
Debug.Log("trigger");
animator.SetTrigger("pop");
condition = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
animator.SetTrigger("close");
}
All you have to do is to set your TriggerScript to something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TriggerScript : MonoBehaviour
{
public string popUp;
private void OnTriggerEnter2D(Collider2D collision)
{
PopUpSystem pop = GameObject.FindGameObjectWithTag("GameManager").GetComponent<PopUpSystem>();
if (collision.gameObject.tag == "Player")
{
pop.PopUp(popUp);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
PopUpSystem pop = GameObject.FindGameObjectWithTag("GameManager").GetComponent<PopUpSystem>();
pop.closeBox();
}
}
and then set your PopUpSystemScript to something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PopUpSystem : MonoBehaviour
{
public GameObject popUpBox;
public Animator popupanimation;
public TMP_Text popUpText;
public void PopUp(string text)
{
popUpBox.SetActive(true);
popUpText.text = text;
popupanimation.SetTrigger("pop");
}
public void closeBox()
{
popupanimation.SetTrigger("close");
}
}
Now click on the popUp and close animation clips (in Animations folder) and remove the Loop Time check mark. and you should be all set to see the animation appears and disappears.
To stop the player, you can either use Time.timeScale. or set the rigidbody to isKenimatic.
I made a portal to the 2D game. Normally the camera needs to follow the character. But after the portal scripts I wrote, "CameraFallowScript" does not work. The character is passing through the portal. but after passing "CameraFallowScript" disappears. I'm a little new and my English is bad.
thanks for helping.
Camera Fallow Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFallow : MonoBehaviour
{
public GameObject target;
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = new Vector3(target.transform.position.x, target.transform.position.y, transform.position.z);
}
}
Portal Script here :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Portal : MonoBehaviour
{
private Rigidbody2D enteredRigidbody;
private float enterVelocity, exitVelocity;
private void OnTriggerEnter2D(Collider2D collision)
{
enteredRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
enterVelocity = enteredRigidbody.velocity.x;
if (gameObject.name == "BluePortal")
{
PortalControl.portalControlInstance.DisableCollider("orange");
PortalControl.portalControlInstance.CreateClone("atOrange");
}
else if (gameObject.name == "OrangePortal")
{
{
PortalControl.portalControlInstance.DisableCollider("blue");
PortalControl.portalControlInstance.CreateClone("atBlue");
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
exitVelocity = enteredRigidbody.velocity.x;
if (enterVelocity != exitVelocity)
{
Destroy(GameObject.Find("Clone"));
}
Destroy(collision.gameObject);
PortalControl.portalControlInstance.EnableColliders();
GameObject.Find("Clone").name = "Character";
CameraFallow.DontDestroyOnLoad(transform.gameObject);
}
}
PortalControl Script Here :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PortalControl : MonoBehaviour
{
public static PortalControl portalControlInstance;
[SerializeField]
private GameObject bluePortal, orangePortal;
[SerializeField]
private Transform bluePortalSpawnPoint, orangePortalSpawnPoint;
private Collider2D bluePortalCollider, orangePortalCollider;
[SerializeField]
private GameObject clone;
void Start()
{
portalControlInstance = this;
bluePortalCollider = bluePortal.GetComponent<Collider2D>();
orangePortalCollider = orangePortal.GetComponent<Collider2D>();
}
// Update is called once per frame
public void CreateClone(string whereToCreate)
{
if (whereToCreate == "atBlue")
{
var instantiatedClone = Instantiate(clone, bluePortalSpawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
if (whereToCreate == "atOrange")
{
var instantiatedClone = Instantiate(clone, orangePortalSpawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
}
public void DisableCollider(string ColliderToDisable)
{
if (ColliderToDisable == "orange")
{
orangePortalCollider.enabled = false;
}
else if (ColliderToDisable == "blue")
{
bluePortalCollider.enabled = false;
}
}
public void EnableColliders()
{
orangePortalCollider.enabled = true;
bluePortalCollider.enabled = true;
}
}
In general I wouldn't create clone I guess ... why instantiate a new player? Why not simply move it to the new position?
What happens here is that after Destroy(collision.gameobject) the FollowCamera loses the reference to its target so you would need to reassign it after the cloning e.g. in
private void OnTriggerExit2D(Collider2D collision)
{
exitVelocity = enteredRigidbody.velocity.x;
if (enterVelocity != exitVelocity)
{
Destroy(GameObject.Find("Clone"));
}
Destroy(collision.gameObject);
PortalControl.portalControlInstance.EnableColliders();
var clone = GameObject.Find("Clone");
clone.name = "Character";
DontDestroyOnLoad(clone);
// It would ofcourse be way more efficient
// if you would store this reference somewhere
FindObjectOfType<CameraFollow>().target = clone;
}
Note that in general usage of Find is expensive and you should avoid it wherever possible! Rather pass on the clone reference between all required scripts.
Regarding coding style: passing around string parameters is not really good code.
I would suggest e.g. an enum like
public enum PortalSide
{
orange,
blue
}
and then use
private Dictionary<PortalSide, Transform> portalSpawns;
private Dictionary<PortalSide, Collider> portalColliders;
private void Awake()
{
portalSpawns = new Dictionary<PortalSide, Transform> { {PortalSide.blue, bluePortalSpawnPoint} , {PortalSide.orange, orangePortalSpawnPoint}};
portalColliders = new Dictionary<PortalSide, Collider> { {PortalSide.blue, bluePortalCollider}, {PortalSide.orange, orangePortalCollider} };
}
public void CreateClone(PortalSide whereToCreate)
{
var spawnPoint = PortalSides[whereToCreate];
var instantiatedClone = Instantiate(clone, spawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
public void DisableCollider(PortalSide ColliderToDisable)
{
var colliderToDisable = portalColliders[ColliderToDisable];
colliderToDisable.enabled = false;
}
I was following Unity 3d tutorial on the Learn Unity website, but here is the thing I wanted to do things a bit differently. It worked out well at start but in the end this turned out to be a bad decision and now I manually need to attach the script to every pickable object.
Here is my code:
Note: What it does is rotate the Pickups and display the score when the pickups collide with player ball.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PickUps : MonoBehaviour {
public Vector3 Rotate;
private int Score;
public Text ScoreGUI;
private void Start()
{
Rotate = new Vector3(0, 25, 0);
Score = 0;
DisplayScore();
}
void Update () {
transform.Rotate(Rotate*Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Ball"))
{
Destroy(this.gameObject);
Score = Score + 1;
DisplayScore();
}
}
void DisplayScore()
{
ScoreGUI.text = "SCORE " + Score.ToString();
}
}
Problem:
It works yes but I need to manually attach the text (under canvas) to every pickup object which is exhausting and not a good thing to do.
What I want To achieve:
Like in the tutorials mostly they use prefabs in this kind of work (I think), problem is I can attach the text to the pickups (objects/biscuits) in the current scene but I cannot drag and attach the text To the prefab of biscuits I made the text just wont attach in its blank for "Text".
You shouldn't change the score Text directly. Use a Controller to make the bridge instead. I would do something like this:
Put this script somewhere in your scene:
public class ScoreManager : Singleton<ScoreManager>
{
private int score = 0;
// Event that will be called everytime the score's changed
public static Action<int> OnScoreChanged;
public void SetScore(int score)
{
this.score = score;
InvokeOnScoreChanged();
}
public void AddScore(int score)
{
this.score += score;
InvokeOnScoreChanged();
}
// Tells to the listeners that the score's changed
private void InvokeOnScoreChanged()
{
if(OnScoreChanged != null)
{
OnScoreChanged(score);
}
}
}
This script attached in the Text game object:
[RequireComponent(typeof(Text))]
public class ScoreText : MonoBehaviour
{
private Text scoreText;
private void Awake()
{
scoreText = GetComponent<Text>();
RegisterEvents();
}
private void OnDestroy()
{
UnregisterEvents();
}
private void RegisterEvents()
{
// Register the listener to the manager's event
ScoreManager.OnScoreChanged += HandleOnScoreChanged;
}
private void UnregisterEvents()
{
// Unregister the listener
ScoreManager.OnScoreChanged -= HandleOnScoreChanged;
}
private void HandleOnScoreChanged(int newScore)
{
scoreText.text = newScore.ToString();
}
}
And in your PickUps class:
void DisplayScore()
{
ScoreManager.Instance.SetScore(Score); // Maybe what you need is AddScore to not
// reset the value everytime
}
A simple singleton you can use (you can find more complete ones on the internet):
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
static T instance;
public static T Instance
{
get
{
if (instance == null)
{
instance = (T)FindObjectOfType(typeof(T));
if (instance == null) Debug.LogError("Singleton of type " + typeof(T).ToString() + " not found in the scene.");
}
return instance;
}
}
}
But be careful, the singleton pattern can be a shot in the foot if not used correctly. You should only it them moderately for managers.
There is issue that i facing with two objects and one button. One is cube second is ground when we click on button cube is collide with ground destroy and instantiate again. On Cube collision score is decrement.Also in hierarchy there is Empty game object which name is controller which has method of text score.Score is working fine but i want that when score is 0 then button click does not work and cube is not instantiate.
Cube :
Ground :
Controller :
CubeScript:
public class Cube : MonoBehaviour {
Rigidbody2D body;
void Start () {
body = GetComponent<Rigidbody2D>();
body.isKinematic = true;
}
}
Ground Script:
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
void Start () {
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (() => Magnetic ());
}
void OnTriggerEnter2D(Collider2D target) {
Destroy (target.gameObject);
Instantiate (object1Clone, new Vector3 (0f, 4.12f, 0f), Quaternion.identity);
}
public void Magnetic(){
GameObject.FindGameObjectWithTag ("Player").GetComponent<Rigidbody2D> ().isKinematic = false;
}
}
ScoreScript:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
} else {
Score--;
Controller.instance.SetScore(Score);
}
}
}
Controller:
public class Controller : MonoBehaviour {
public static Controller instance;
public Text scoreText;
void Start () {
scoreText.text = ""+1;
if(instance==null){
instance=this;
}
}
public void SetScore(int score){
scoreText.text =""+score;
}
}
First change the listener registration to this:
button.onClick.AddListener (Magnetic);
this will make it easier to remove the listener.
I will show you two ways of doing it, an easy one and a proper one a bit harder to grasp. So if you don't quite get it, use the first and learn about the second.
Every time you decrease the score, check for it and call for the appropriate action:
public class ScoreScript : MonoBehaviour {
public static int Score=1;
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
GameObject.Find("ground").GetComponent<Ground>().ClearButtonListener();
}
}
}
And in the Ground component:
public void ClearButtonListener()
{
button.onClick.RemoveListener (Magnetic);
}
Now the second more appropriate way would be to use event and listener
public class ScoreScript : MonoBehaviour, IScoreHandler {
public static int Score=1;
public event Action OnScoreZero = () => {};
void OnTriggerEnter2D(Collider2D target)
{
Score--;
Controller.instance.SetScore(Score);
if(Score <= 0){
OnScoreZero();
}
}
}
public interface IScoreHandler{ event Action OnScoreZero; }
And your listeners listens.
public class Ground : MonoBehaviour {
private Button button;
private BoxCollider2D collide;
public GameObject object1Clone;
private IScoreHandler scoreHandler = null;
void Start () {
scoreHandler = GameObject.Find("Score").GetComponent<IScoreHandler>();
if(scoreHandler != null){
scoreHandler.OnScoreZero += ClearButtonListener;
}
collide = GetComponent<BoxCollider2D>();
collide.isTrigger = true;
button = GameObject.FindGameObjectWithTag ("Button").GetComponent<Button> ();
button.onClick.AddListener (Magnetic);
}
void OnDestroy(){
if(scoreHandler != null){
scoreHandler.OnScoreZero -= ClearButtonListener;
}
}
}
Thanks to interface and event, your class is no more relying on another class but on an interface which makes it more flexible and scalable.
You need to set the field interactable of the UnityEngine.UI.Button object to false, see http://docs.unity3d.com/ScriptReference/UI.Button.html, i.e. use
void OnTriggerEnter2D(Collider2D target) {
if (Score <=0) {
/* disable the button */
GameObject.FindGameObjectWithTag ("Button").GetComponent<Button>().interactable = false;
}
in your ScoreScript.cs.