I'm making a new game in Unity and I'm stuck in my script. I'm really noob in C# scripting. I'm already looking for all information but no luck. The game is very simple, it is 2D game where need just click on bubbles and they are rotating when are clicked. I will describe what I exactly need to create. I need a script when all objects are clicked then scene automatically changes to the next level + I need it to have a timeline, for example, for each level have 30 seconds to click all bubbles, when the time is over the game is over and a window pops up with a message "Game Over" and you can press the Reply and Exit buttons. I really hope that someone helps me. Thanks!
P.S. This is my script now for my gameObjects:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class pop : MonoBehaviour
{
public AudioClip sound;
AudioSource audio;
// Start is called before the first frame update
void Start()
{
audio = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
}
bool wasClicked = false;
private void OnMouseDown()
{
if (!wasClicked)
{
wasClicked = true;
transform.Rotate(0, 0, 180);
audio.PlayOneShot(sound);
}
}
}
You should separate these.
I would have a central manager for the scene change like e.g.
public class Manager : MonoBehaviour
{
[SerializeField] private float timer = 30;
private void Awake ()
{
// Register to an event we will add which is fired everytime
// a pop was clicked
pop.onClicked += PopClicked;
}
private void OnDestroy ()
{
// Don't forget to remove the callback as soon as not needed anymore
pop.onClicked -= PopClicked;
}
private void PopClicked ()
{
// Check if there is no Unclicked pop remaining
if(pop.Unclicked.Count == 0)
{
// if so go to the next scene
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
private void Update ()
{
// Every Frame reduce the timer by the time passed since the last frame
timer -= Time.deltaTime;
// Maybe also update a timer display text here
if(timer <= 0)
{
// if it reaches 0 -> GameOver scene
SceneManager.LoadScene("GameOver");
}
}
}
And then modify your Pop class accordingly with some additional things:
public class pop : MonoBehaviour
{
// Stores all Unclicked instances
// As this is static it is "shared" between all instances or better said
// it is part of the type itself
private static HashSet<pop> _unclicked = new HashSet<pop>();
// Public readonly access
public static HashSet<pop> Unclicked => new HashSet<pop>(_unclicked);
// Event that is invoked everytime a pop is clicked
public static event Action onClicked;
public AudioClip sound;
[SerializeField] AudioSource audio;
void Awake()
{
if(!audio) audio = GetComponent<AudioSource>();
// Add yourself to the collection of Unclicked instances
_uncliked.Add(this);
}
private void OnDestroy ()
{
// Don't forget to also remove in case this is destroyed
// e.g. due to the scene change to GameOver
if(_unclicked.Contains(this)) _unclicked.Remove(this);
}
private void OnMouseDown()
{
// Is this still Unclicked?
if (!_unclicked.Contains(this)) return;
transform.Rotate(0, 0, 180);
audio.PlayOneShot(sound);
// Remove yourself from the Unclicked instances
_unclicked.Remove(this);
// Invoke the event
onClicked?.Invoke();
}
}
Related
I don't know how to reload a scene so that the user can press a key and reset the game. Whenever I do it, the engine just crashes. The game is based on a cat chasing glasses around with a 120-second timer, and each of them has unique abilities. On collision, it should reload the scene. Is there any way that I can do this?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class gamemanager : MonoBehaviour
{
public GameObject catwin;
public GameObject glasseswin;
public timer timer;
public Transform catpos;
public Transform glassespos;
public Vector3 catspawn;
public Vector3 glassesspawn;
public bool gameover = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKey("r"))
{
GameReset();
}
}
public void CW()
{
catwin.SetActive(true);
gameover = true;
while (gameover)
{
if (Input.GetKey(KeyCode.R))
{
GameReset();
}
}
}
public void GW()
{
glasseswin.SetActive(true);
if (Input.GetKey("r"))
{
GameReset();
}
}
public void GameReset()
{
catwin.SetActive(false);
glasseswin.SetActive(false);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
It crashes because the while loop inside the CW method is actually a while (true). When something calls this method it should wait until method is completed, but it'll never happen because of the loop. As a result, the program gets stuck while trying to execute infinitely many commands in one frame, so it has no other choice but to crash.
In order to fix it remove the loop from the method and check whether the key was pressed in Update. Something like this:
if (gameover && Input.GetKey("r"))
GameReset();
You can replace gameover with something else in order to include other conditions
I'm developing a simple VR shooter in unity, i use object pooling for the bullets (Lasers here)
this is the ObjectPool script, here i istantiate a list of bullets in the start() method and disable them.
public class ObjectPool : MonoBehaviour
{
public static ObjectPool instance;
private List<GameObject> pooledObject = new List<GameObject>();
private int amountToPool = 20;
[SerializeField] private GameObject laserPrefab;
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < amountToPool; i++)
{
GameObject obj = Instantiate(laserPrefab);
obj.SetActive(false);
pooledObject.Add(obj);
}
}
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObject.Count; i++)
{
if (!pooledObject[i].activeInHierarchy)
{
return pooledObject[i];
}
}
return null;
}
}
This is the Script attached to the gun where i get the pooled bullet and set it to active
public class FireLaserGun : MonoBehaviour
{
public GameObject laserBeamModel;
public Transform laserSpawnPoint;
// Start is called before the first frame update
public void FireGun()
{
GameObject laser = ObjectPool.instance.GetPooledObject();
if (laser != null)
{
laser.transform.position = laserSpawnPoint.position;
laser.transform.rotation = laserSpawnPoint.rotation;
laser.SetActive(true);
Debug.Log("BOOM");
}
else
{
Debug.Log("Laser is null");
}
}
}
I'm trying to disable the bullet after two seconds was fired using a coroutine in the script that moves the bullets:
public class LaserBeamMove : MonoBehaviour
{
private Rigidbody rb;
public float thrust = 10.0f;
float waitTime = 2.0f;
private IEnumerator coroutine;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
coroutine = WaitToDisable(waitTime);
StartCoroutine(coroutine);
}
void FixedUpdate()
{
rb.velocity = transform.forward * thrust;
}
private IEnumerator WaitToDisable(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
Debug.Log("bullet disabled after " + waitTime + "seconds");
}
}
Strangely first seconds of the game everything seems fine, all the bullets start as inactive and every bullet becomes active when fired and inactive after two seconds.
After some seconds bullets dont become inactive anymore (actually only some of them do).
this is a screenshot of the console log, when i fire i print "BOOM" and when a bullet becomes inactive i print "bullet disabled after 2 seconds"
As you can see this down't work for every bullet and i don't understand why.
Am i doing something wrong with the courutine?
https://i.stack.imgur.com/rMJgg.png
Take the code you have in void Start() and move it to a new void OnEnable. The reason is that Start is only called once, the first time the object holding the script is enabled (e.g when the scene loads, when you instantiate a bullet etc), OnEnable is called every time the object is enabled, which is what you want here. You could also use Awake but IMHO that's best kept separately for initialisation stuff only so you can keep the code tidy and separate out game logic from init logic.
the scenario is, i've 15 objects in my scene, each game objects has it own mesh, so i want to change the color of the game object when the player click on it with the mouse, if the player click on the same game objects several times the game objects should change the color randomly, and if the player does not click in the scene within 20 seconds a button should be activated to ask the player to load a new scene, and all my game object should be spawn in the new scene, in the new scene the player has to click on all 15 objects within 15 seconds, if he click on all objects within the 15 seconds the game will over else it the game will reload the first scene,
Just to clarify what you are trying to achieve, as I understand you want:
You have some objects in "sceneA"
whenever one object is clicked it changes its color randomly (let's say it becomes "activated"
after ??? seconds an activated objects turns inactivated again going back to the original color
if the user manages to get them colored (active) all at the same time within 15 seconds a new scene "sceneB" is loaded
So I would rather have a quite simple class on your clickable objects like e.g.
using System.Collections.Generic;
using UnityEngine;
public class ClickableObject : MonoBehaviour
{
// Will hold the references to all currently existing ClickableObject instances
private static readonly List<ClickableObject> _instances = new List<ClickableObject>();
[Header("References")]
[SerializeField] private Renderer _renderer;
[Header("debugging")]
[SerializeField] private bool isActivated;
[SerializeField] private Color _originalColor;
// public read-only accesses
public bool IsActivated => isActivated;
public static IReadOnlyList<ClickableObject> Instances => _instances;
private void Awake()
{
_instances.Add(this);
if (!_renderer)
{
_renderer = GetComponent<Renderer>();
}
_originalColor = _renderer.material.color;
}
private void OnDestroy()
{
_instances.Remove(this);
}
public void SetActivated(bool activated)
{
isActivated = activated;
_renderer.material.color = activated ? new Color(Random.value, Random.value, Random.value, 1.0f) : _originalColor;
}
}
and then handle the clicks and timeout rather in one central controller like e.g.
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ClickController : MonoBehaviour
{
[Header("References")]
// If possible already reference this via the Inspector
[SerializeField] private Camera _mainCamera;
[Header("Settings")]
// Configure this via the Inspector
[SerializeField] private float timeout = 15f;
// Configure this via the Inspector and select the layer you have assigned to the clickable objects
[SerializeField] private LayerMask clickableObjectsLayer = LayerMask.NameToLayer("Default");
// configure this via the Inspector and enter the name or path of your target scene to load
[SerializeField] private string targetScene = "sceneB";
// will store a reference to the currently running reset routine
private Coroutine currenResetRoutine;
private void Awake()
{
if (_mainCamera) _mainCamera = Camera.main;
}
private void Start()
{
// Initially reset all objects to be not "activated"
ResetClickableObjects();
}
private void Update()
{
// wait for a mouse click
if (Input.GetMouseButtonDown(0))
{
// shoot out a raycast and check if you hit something on the clickableObjectsLayer
var ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out var hit, float.PositiveInfinity, clickableObjectsLayer))
{
// Just to be sure check if it is really a ClickableObject
if (hit.transform.TryGetComponent<ClickableObject>(out var clickableObject))
{
// Check if this is not active yet, otherwise ignore the click
if (!clickableObject.IsActivated)
{
// set this object to active -> colored
clickableObject.SetActivated(true);
// if there is no reset routine running start one with the first clicked object
// => from now on have "timout" seconds to click also the rest of the objects
if (currenResetRoutine != null)
{
currenResetRoutine = StartCoroutine(ResetAfterFifteenSecondsRoutine());
}
// Check if all objects are active at the same time
if (ClickableObject.Instances.All(co => co.IsActivated))
{
// cancel the reset routine
StopCoroutine(currenResetRoutine);
// load the targetScene after 2 seconds
// just to have time for e.g. a "YEAH YOU DID IT YOU AWESOME USER!"
// we use "Invoke" since it also works on a disabled component
Invoke(nameof(LoadScene), 2f);
// disable this component so no clicks are tracked anymore
enabled = false;
}
}
}
}
}
}
private void LoadScene()
{
// Go to the target scene configured in the Inspector
SceneManager.LoadScene(targetScene);
}
private static void ResetClickableObjects()
{
// Reset all ClickableObject to be not activated
foreach (var clickableObject in ClickableObject.Instances)
{
clickableObject.SetActivated(false);
}
}
private IEnumerator ResetAfterFifteenSecondsRoutine()
{
// wait for the timeout configured in the Inspector
yield return new WaitForSeconds(timeout);
// Then reset all ClickableObject to be not activated
ResetClickableObjects();
// reset to allow the next timeout to begin
currenResetRoutine = null;
}
}
I have made 2 codes to manage my interstitial ads by making the ads show every 5 mins when the player losses but the problem is that I tried to reset them when the player passes the 5 mins and press the button when he losses but it didn't work so how to reset the timer when the player presses the button?
this is the 1st code :
public int LastShownIntTime = 300;
void Start()
{
#if UNITY_ANDROID
Advertisement.Initialize(androidID);
#endif
}
public void Update()
{
LastShownIntTime = PlayerPrefs.GetInt("LastShownIntTime");
}
public void showInterstitial()
{
if (LastShownIntTime <=0)
{
showInterstitialwith5mint();
}
}
public void showInterstitialwith5mint()
{
Advertisement.Show("video");
PlayerPrefs.SetInt("LastShownIntTime", 300);
}
and the 2nd one :
public float LastShownIntTimefloat;
public int LastShownIntTime = 300;
void Start()
{
LastShownIntTime = PlayerPrefs.GetInt("LastShownIntTime");
LastShownIntTimefloat = LastShownIntTime;
}
public void Update()
{
LastShownIntTimefloat -= Time.deltaTime;
LastShownIntTime = (int)LastShownIntTimefloat;
PlayerPrefs.SetInt("LastShownIntTime", LastShownIntTime);
}
}
The main issue here:
You would have to reset the LastShownIntTimefloat in your script2!
Otherwise you simply continue overwriting it with new values reducing the value more and write it back to PlayerPrefs
→ the next time your script1 polls the value it is not reset but already overwritten by script2!
In general: You should not use PlayerPrefs in order to make two components communicate!
In your case here I wouldn't even separate the logic and bother with implementing the communication between them but rather merge them into one single component.
Then it is not necessary to read and write PlayerPrefs every frame but rather only on certain checkpoints like
Read once in Start
Write once in OnApplicationQuit
Write once in OnDestroy (This is for the case you e.g. switch Scene but don't quit the app)
Write once ever time your user loses (showInterstitial is called)
Write once when resetting the value after showing the advertisement
I would also simply directly use a float and GetFloat and SetFloat instead of converting it from and to an int.
public class MergedClass : MonoBehaviour
{
// Rather sue a FLOAT for time!
public float LastShownTime = 300;
void Start()
{
#if UNITY_ANDROID
Advertisement.Initialize(androidID);
#endif
// use 300 as default value if no PlayerPrefs found
LastShownTime = PlayerPrefs.GetFloat("LastShownTime", 300f);
}
public void Update()
{
if(LastShownTime > 0f) LastShownTime -= Time.deltaTime;
}
public void showInterstitial()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
if (LastShownTime <= 0f)
{
showInterstitialwith5mint();
}
}
public void showInterstitialwith5mint()
{
#if UNITY_ANDROID
Advertisement.Show("video");
#else
LastShownTime = 300f;
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
private void OnApplicationQuit()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
private void OnDestroy()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
}
I tried both setting enabled to true and SetActive(true) to diplay a Gameover Image. However, none of them works. I have a public Image gameOverImage declared and set the gameOverImage.enabled in the Start() to false.
private void Start()
{
gameOverImage.enabled = false;
}
Then in one of my function, I put:
public void killAt(Vector2 loc)
{
foreach (GameObject obj in setup.GetActive())
{
if (obj.GetComponent<PieceInfo>().GetLocation() == loc)
{
if (obj.GetComponent<PieceInfo>().GetPieceType() == 'G')
{
gameOver = true;
gameOverImage.enabled = true;
Debug.Log("?????");
}
setup.deactivate(obj);
break;
}
}
}
The console does have ????? logged but no gameOverImage displayed in the game view. The game is over because I couldn't click my game any more. Any ideas? I also tried UI text. It doesn't work as well.
In Unity in order to activate an object you need to have it in the scene. If the GameObject does not exist in the scene, or in your case the UI Element that contains the Image, is not in your scene SetActive(true) or Enabled = true will not have any effect. You will need to instantiate the object. To make it exist in your world.
Prefabs, are useful to store a common configuration that can be used multiple times in your game but they do not exist in the scene, that is why you have to instantiate them.
For your GameOver Image you have a few options the simplest is this:
using UnityEngine;
using UnityEngine.UI;
public class EnableUI : MonoBehaviour {
public Image GameOverImage;
// Use this for initialization
void Start () {
GameOverImage.gameObject.SetActive(false);
}
// Update is called once per frame
void Update () {
if(Input.GetKeyDown(KeyCode.Space))
{
GameOverImage.gameObject.SetActive(true);
}
}
}
If you want to instantiate it:
using UnityEngine;
public class EnableUI : MonoBehaviour {
// This is a prefab that is canvas with your game over image nested as a child image UI under it
public GameObject GameOverObject;
// Use this for initialization
void Start () { }
// Update is called once per frame
void Update () {
if(Input.GetKeyDown(KeyCode.Space))
{
Instantiate(GameOverObject);
}
}
}