Unity changing components of different assets when a button is clicked - c#

I am very new to Unity and c# in general but I currently have a game that has 2 different backgrounds, and I would like to figure out how to swap between the 2 backgrounds - LightBackground and NightBackground - when a button is clicked, with the default background being the LightBackground and when the button is clicked, the NightBackground is used. My idea was to change the order in layer (sortingOrder) of the NightBackground when the button is clicked but was very unsuccessful upon attempting many things.
At the moment I have made a Night/Dark script which I have placed in the button on-click and it changes the sortingOrder of the Nightbackground but how can i make it simultaneously change the sortingOrder of the LightBackground. In addition, currently i have a game object called Game Manager which is linked to the Gameover Canvas which displays the button I want and the replay button; but as soon as the button is clicked, the background does change but as soon as the replay button is clicked, the background goes back to the LightBackground.
NightDark Script:
public class NightDark : MonoBehaviour
{
public GameObject NightBackground;
// Start is called before the first frame update
void Start()
{
NightBackground.GetComponent<SpriteRenderer>().sortingOrder++;
}
// Update is called once per frame
void Update()
{
}
}
Game Manager Script:
public class GameManager : MonoBehaviour
{
public GameObject gameOverCanvas;
private void Start()
{
Time.timeScale = 1;
}
public void GameOver()
{
gameOverCanvas.SetActive(true);
Time.timeScale = 0;
}
public void Replay()
{
SceneManager.LoadScene(0);
}
}
It may be easier to view the Game Manager and NightDark here.
If any of you could help in the slightest, it would be much appreciated since I am really struggling at the moment. Thanks again.

What speaks against having one single background controller and rather only switch out the Sprite you display in the backgrounds SpriteRenderer.sprite property
public class BackgroundController : MonoBehaviour
{
// Drag all these in via the Inspector
[Header("References")]
[SerielizeField] private SpriteRenderer backgroundRenderer;
[Header("Assets")]
[SerielizeField] private Sprite daySprite;
[SerielizeField] private Sprite nightSprite;
// This is static so it keeps its value session wide also after
// reloading the scene
private static bool _isDay;
private void Start()
{
// inverts the _isDay -> starts as day the first time
SwitchBackground();
}
// This you call when the button is clicked
public void SwitchBackground()
{
// invert the flag
_isDay = !_isDay;
// chose the new sprite according to the flag
backgroundRenderer.sprite = _isDay ? daySprite : nightSprite;
}
}

Related

Unity: Even when the "player" game object gets destroyed, the text doesn't appear

I've been using Unity to create a simple 2D game but the problem is that even when the game object "player" gets destroyed, the gameobject "isDead" (text) doesn't appear.
This is my script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class youDied_Text : MonoBehaviour
{
private Transform player;
private Text isDead;
// public static bool isDead;
// Start is called before the first frame update
private void Start() {
isDead = GetComponent<Text>();
}
void checkForDeath()
{
if (player==false)
{
isDead.gameObject.SetActive(true);
}
else
{
isDead.gameObject.SetActive(false);
}
}
// Update is called once per frame
void Update()
{
player = GameObject.FindWithTag("Player").transform;
checkForDeath();
}
}
This script is attached in the text which I need to display in UI element.
As was noted currently you would get a NullReferenceException which is definitely not what you want.
There is absolutely no need / redundancy going through Transform at all actually. Simply store the GameObject reference instead
You are currently setting the object to inactive which has the Text attached ... which is the same object your component is attached to as well!
=> As soon as you end up in the second case once you set it to inactive => from now on Update is never called anymore!
In general as it sounds like this should only happen once anyway I would use a more event driven approach and have a component on your player like e.g.
public class Player : MonoBehaviour
{
public UnityEvent onDied;
private void OnDestroy ()
{
onDied.Invoke();
}
}
And then simply attach a listener/callback to that event once without poll checking states. You can do this either via the Inspector directly (just like in e.g. Button.onClick) or via code like e.g.
public class youDied_Text : MonoBehaviour
{
// Already reference things via the Inspector if possible!
[SerializeField] private GameObject player;
[SerializeField] private Text isDead;
private void Awake()
{
if(!isDead) isDead = GetComponent<Text>();
isDead.gameObject.SetActive(false);
// If you want to rather set it via the Inspector remove all the rest here
//if(!player) player = GameObject.FindWithTag("Player"). GetComponent<Player>();
// or even simpler
if(!player) player = FindObjectOfType<Player>();
player.onDied.AddListener(OnPlayerDied);
}
// If you want to rather set it via the Inspector make this public
private void OnPlayerDied()
{
isDead.gameObject.SetActive(true);
}
}

How to have a button run while it is being pressed

Im trying to have a method continuously run while a button is pressed. At the moment the method only runs once when the UI button is pressed. I've tried implementing a coroutine but I couldn't figure out a way a condition for StopCoroutine() to run. I've also tried using the event triggers PointerUp and PointerDown that set a boolean to false and true that determine if a coroutine runs or not but that seemed to work.
Would anyone know how to implement a continuous on button hold? Any help is appreciated.
The method I want to continuously run while the button is held. This method is called in a PointerDown event trigger in the UI button.
public void spawnLaser()
{
GameObject laser = Instantiate(laserprefab, transform.position, Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(laserSpeed, 0);
}
Input.GetKeyDown returns true during the frame the user starts pressing down the key identified by name, while Input.GetKey returns true while the user holds down the key identified by name.
Seems you need to give Input.GetKey a try.
To keep it calling while pressed, call it from the Update().
public void spawnLaser()
{
GameObject laser = Instantiate(laserprefab, transform.position,
Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(laserSpeed, 0);
}
void Update()
{
if (Input.GetKey("up"))
{
print("up arrow key is held down");
spawnLaser();
}
}
In general you can just implement the IPointerDownHandler, IPointerUpHandler, IPointerExitHandler and IPointerEnterHandler interfaces like e.g.
public class WhilePressedButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler
{
public UnityEvent whilePressed;
// adjust according to your needs via the Inspector
[Min(0f)] public float delay = 0.3f;
public void OnPointerDown(PointerEventData pointerEventData)
{
StartCoroutine(ContinuousButton());
}
public void OnPointerUp(PointerEventData pointerEventData)
{
StopAllCoroutines();
}
public void OnPointerEnter(PointerEventData pointerEventData)
{
// Actually not really needed
// but not sure if maybe required for having OnPointerExit work
}
public void OnPointerExit(PointerEventData pointerEventData)
{
StopAllCoroutines();
}
private IEnumerator ContinuousButton()
{
// Whut? o.O
// No worries, this is fine in a Coroutine as long as you yield somewhere inside
while (true)
{
whilePressed.Invoke();
// Very important for not freezing the editor completely!
// This tells unity to "pause" the routine here
// render the current frame and continue
// from this point on the next frame
// If you want some delay between calls
if(delay > 0)
{
yield return new WaitForSeconds (delay);
}
else
{
// Otherwise it directly contineus in the next frame
yield return null;
}
}
}
}
And reference your spawnLaser method in whilePressed like usually in a Button onClick via the Inspector or in code.
Note: This is either on an UI element such as a UI.Button or on a 3D object with a collider but then you additionally require a PhysicsRaycaster component on your Camera.
You do NOT need an EventTrigger component for this! The interface methods OnPointerXY will simply be called by the UI itself!
However, are you really going to Instantiate a new object every frame while the button is pressed?
You should probably checkout Object Pooling or in general only activate and deactivate your object instead of Instantiate and delete it over and over again.
According to Unity Documentation:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;// Required when using Event data.
public class ExampleClass : MonoBehaviour, IPointerDownHandler// required interface when using the OnPointerDown method.
{
//Do this when the mouse is clicked over the selectable object this script is attached to.
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log(this.gameObject.name + " Was Clicked.");
}
}
Add the IPointerDownHandler interface to your class and implement the OnPointerDown(PointerEventData) method.
I recommend using a bool to track the state of the button (e.g true if pressed) and then in your Update() method, if the bool is true, make your lazer move.

How to set a GameObject active on one scene form a button click function on second scene that is loaded additive C#, Unity

I'm new to coding and Unity, I'm working on a simple click style game to learn the basics of both. I've created several scenes: MainMenu, UI, 1st level and 2nd level. After pressing 'Start' in main menu i'm loading UI and 1st level additively.
In UI layer I have a shop UI and other bits that I never want to unload. On the 1st and 2nd level i have the bits that i want to have only on those scenes.
So, what I'm trying to do is when i purchase an item or upgrade for the 1st level i want a GameObject (sprite) to be set as active.
What I've tried to do is to call a function in one script that is attached to GameObject in 1st level from script that is attached to a purchase button in UI scene, but from what I was able to understand from messing with it will set that game object active if it's assigned in the UI scene - so the whole thing is basicly pointless and i can do it much easier.
Code in script attached to GameObject in 1st level scene
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Huberd()
{
hubert12.SetActive(true);
}
}
Code in script attached to GameObject in UI scene
public class Shop : MonoBehaviour
{
public GameObject buyHubertOnebutton;
public GameObject test;
public void UnlockHubert1()
{
if (Global_Cash.CashCount >= 20)
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button>().interactable = false;
Work_Button sn = test.GetComponent<Work_Button>();
sn.Huberd();
}
}
}
If you have any remarks anout how i've spit scenes or anything else they will be more than welcome!
Thanks!
There's something you need to be aware of, and that is that you can't reference an object from one scene, in another. You CAN reference the same Prefab though, but that's a slightly different issue.
One way I find effective is to do something like this:
public class Work_Button : MonoBehaviour
{
public GameObject hubert12;
public void Awake ( )
{
Shop.Register ( this );
}
public void Huberd ( )
{
hubert12.SetActive ( true );
}
}
Then you can have a Shop manager that has instance and static components.
public class Shop : MonoBehaviour
{
// Static variables
private static Work_Button _workButton;
// Instance variables
public GameObject buyHubertOnebutton;
public static void Register ( Work_Button workButton )
{
_workButton = workButton;
}
public void UnlockHubert1 ( )
{
if ( Global_Cash.CashCount >= 20 )
{
Global_Cash.CashCount -= 20;
buyHubertOnebutton.GetComponent<UnityEngine.UI.Button> ( ).interactable = false;
if ( _workButton != null )
_workButton.Huberd ( );
}
}
}
This works by having your button register with the shop. And because we're using the static variable here, we don't need to "find" the Shop object. The instance method UnlockHubert1 will check to make sure there is a button registered.
This represents just one of many ways to accomplish this. It's also the most basic of basic implementations. But as of right now I'm fond of this method. Extending this, you could store the registered items in a Dictionary collection, and you could do some cleanup/deregister when a button is destroyed (i.e. OnDestroy ).
The assumption here is that there is only one Work_Button in the scene, otherwise you'll need a way to differentiate them (i.e. a Dictionary with am identifier as the key).

How can I change this animation transition to happen automatically?

Currently, my code allows the player to go to the next scene by clicking. I want to, however, make the fade out into the next scene animation automatic after 4 seconds. How can I do this?
I've tried looking up information, but nothing seems to work.
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class LevelChanger : MonoBehaviour
{
// Start is called before the first frame update
float timer = 4f;
public Animator animator;
private int levelToLoad;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Invoke("FadeToLevel(1)", 2f);
}
}
public void FadeToLevel (int levelIndex)
{
levelToLoad = levelIndex;
animator.SetTrigger("FadeBlack");
}
public void OnF`enter code here`adeComplete()
{
SceneManager.LoadScene(levelToLoad);
}
}
The code works as intended, but I want the animation to happen automatically.
If I understood correctly, what you are aiming is to make an animation to play automatically when the player enters the new scene.
If that's the case, then you are looking for the sceneLoaded() method from SceneManager
Also, this discussion may be useful

How to make an object clickable depending on player distance?

I need to make an item in my scene clickable but only when the player is near the item. In my script I make the item in question go automatically to an empty GameObject that is child of my Player in the hierarchy to define the position but the click is able as soon as the camera have it framed. I'm using the character controller provided in the 2d physics and not a 2drigidbody so I'm even more confused because I can't use a collider.
I'm pretty sure you can have both a character controller and a collider on a gameobject (at least a trigger collider).
Then instead of whatever you're using to detect the click, you should use in an Update loop something like Input.GetKeyDown(KeyCode.Mouse0), and use a raycast where you can specify the length of the ray. https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
In order to make anything clickable I would recommend IPointerXHandler interfaces (replace X with Click, Enter, Exit, Down, Up, etc).
Note:
Ensure an EventSystem exists in the Scene to allow click detection. For click detection on non-UI GameObjects, ensure a PhysicsRaycaster is attached to the Camera.
If you only want to click IPointerClickHandler is enough. If you want some visiual feedback like changing colors etc you'll have to expand it with at least IPointerEnterHandler and IPointerExitHandler.
public class MyClickable : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(PointerEventData pointerEventData)
{
...
}
}
Then in order to get the distance between two obects you can simply use Vector3.Distance e.g. with a ceratin threshold
// configure those e.g. in the Inspector
public float distanceThreshold;
public Transofrm playerTransform;
public Transform itemTransform;
and than use something like
if(Vector3.Distance(playerTransform.position, itemTransform.position) <= distanceThreshold)
{
...
}
so if you directly implement this into the MyClickable you could do visual feedback also in Update something like
public class MyClickable : MonoBehaviour, IPointerClickHandler
{
public float distanceThreshold;
public Transofrm playerTransform;
// this gives you an event you can configure in the Inspector
// exactly like you would with a button
public UnityEvent onClick;
private bool isInRange;
public void OnPointerClick(PointerEventData pointerEventData)
{
// if too far away do nothing
if(Vector3.Distance(playerTransform.position, transform.position) > distanceThreshold) return;
....
onClick.Invoke();
}
private void Update()
{
if(Vector3.Distance(playerTransform.position, transform.position) <= distanceThreshold)
{
// e.g. make object green
}
else
{
// e.g. make object grey
}
}
}
My suggestion is to use the onMouseDown() method. If you have a collider or trigger attached to your gameObject, onMouseDown() will detect mouse clicks on the object. Then in then body of onMouseDown() you can test if it is in range.
This is an example script how it could work:
public class ItemClickable : MonoBehaviour
{
public Transform player; // player-transform reference (depends if you have a singleton or not)
public float range; // radius (maybe you have a range or radius already set in your player instance)
void Start()
{
// Setup for your references
}
private void OnMouseDown()
{
// Checks if the item is in the range of the player
if ((player.position-gameObject.transform.position).magnitude < range) // Vector3.Distance() is also possible
{
Destroy(gameObject); // or do whatever you want in here
}
}
}

Categories

Resources