The first two scripts attached to gameobjects on one scene:
When the game start I want to play the splash screen:
In the script I'm setting the splash flag to true:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadScenes : MonoBehaviour
{
// Use this for initialization
void Start()
{
GameControl.splash = true;
if (!SceneManager.GetSceneByName("The Space Station").IsValid())
{
SceneManager.LoadSceneAsync(1, LoadSceneMode.Additive);
StartCoroutine(WaitForSceneLoad(SceneManager.GetSceneByName("The Space Station")));
}
}
public IEnumerator WaitForSceneLoad(Scene scene)
{
while (!scene.isLoaded)
{
yield return null;
}
SceneManager.SetActiveScene(SceneManager.GetSceneByBuildIndex(1));
}
}
When I click/press the escape key it should load back the main menu and it does but I want it to load back the main menu without playing the splash screen again:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadMainMenuOnclick : MonoBehaviour
{
private Scene scene;
// Use this for initialization
void Start ()
{
scene = SceneManager.GetActiveScene();
}
// Update is called once per frame
void Update ()
{
if (scene.name != "Menu" && GameControl.player.activeSelf)
{
if (Input.GetKeyDown(KeyCode.Escape))
{
SceneManager.LoadScene(0, LoadSceneMode.Additive);
StartCoroutine(WaitForSceneLoad(SceneManager.GetSceneByName("Menu")));
}
}
}
public IEnumerator WaitForSceneLoad(Scene scene)
{
while (!scene.isLoaded)
{
yield return null;
}
GameControl.splash = false;
SceneManager.SetActiveScene(SceneManager.GetSceneByBuildIndex(1));
GameControl.player.SetActive(false);
Cursor.visible = true;
}
}
This is the GameControl script static class that is not attached to any gameobject. This class is to be able to access variables in two scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class GameControl
{
public static GameObject player;
public static bool splash;
}
And this script that play the splash screens is attached to gameobject on the second scene the main menu scene:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Assertions.Must;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Splashes : UnityEngine.MonoBehaviour
{
[Header("Splash Screen")]
public bool useSplashScreen = true;
public GameObject splashesContent;
private List<Graphic> splashes = new List<Graphic>();
public float splashStayDiration = 3f;
public float splashCrossFadeTime = 1f;
void Start()
{
if (GameControl.splash == false)
useSplashScreen = GameControl.splash;
if (!useSplashScreen || splashesContent.GetComponentsInChildren<Graphic>(true).Length <= 0) return;
//if we use splash screens and we have splash screens
#region Get All Splashes
//if you build on PC Standalone - you can uncomment this
//foreach (var splash in splashesContent.GetComponentsInChildren<Graphic>(true).Where(splash => splash != splashesContent.GetComponent<Graphic>()))
//{
// splashes.Add(splash);
//}
for (var i = 0; i < splashesContent.GetComponentsInChildren<Graphic>(true).Length; i++)
{
var splash = splashesContent.GetComponentsInChildren<Graphic>(true)[i];
if (splash != splashesContent.GetComponent<Graphic>())
{
splashes.Add(splash);
}
}
#endregion
//And starting playing splashes
StartCoroutine(PlayAllSplashes());
}
private IEnumerator PlayAllSplashes()
{
//Enabling Splashes root transform
if (!splashesContent.activeSelf) splashesContent.SetActive(true);
//main loop for playing
foreach (var t in splashes)
{
t.gameObject.SetActive(true);
t.canvasRenderer.SetAlpha(0.0f);
t.CrossFadeAlpha(1, splashCrossFadeTime, false);
yield return new WaitForSeconds(splashStayDiration + splashCrossFadeTime);
t.CrossFadeAlpha(0, splashCrossFadeTime, false);
yield return new WaitForSeconds(splashCrossFadeTime);
t.gameObject.SetActive(false);
}
//Smooth main menu enabling
splashesContent.GetComponent<Graphic>().CrossFadeAlpha(0, 0.5f, false);
yield return new WaitForSeconds(0.5f);
splashesContent.gameObject.SetActive(false);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
StopCoroutine(PlayAllSplashes());
}
}
public void ExitGame()
{
Application.Quit();
}
}
I added two lines:
if (GameControl.splash == false)
useSplashScreen = GameControl.splash;
And I used a break point. When running the game it's getting to this lines and splash is true. but when I click/press the escape key it's getting to the check line but splash is still true even if I set it to false in the LoadMainMenuOnclick script.
The splash screen should be playing only when starting the game with the LoadScenes script.
Related
I have a game that is similar to "Flappy Bird" and I have main menu where I can start game and change skin of a pigeon. My skin collection is implemented with scroll rect and in the center there is a trigger which starts an animation of scaling a pigeon, it works fine until I click "start" and the scene changes to game and when I return to my main menu and click "skins" this trigger doesn't work anymore.
Script what is attached to all scroll rect elements to detect collisions with trigger:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResizeFieldScript : MonoBehaviour
{
private Animator _anim;
private void Start()
{
_anim = GetComponent<Animator>();
}
public void OnTriggerEnter2D(Collider2D collider)
{
Debug.Log("Trigger is working");
if(collider.tag == "ResizeField")
{
Debug.Log("Condition is working");
_anim.SetBool("isInTrigger", true);
}
}
public void OnTriggerExit2D(Collider2D collider)
{
_anim.SetBool("isInTrigger", false);
}
}
Script what is attached to an empty object to change scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
public class UIController : MonoBehaviour
{
[SerializeField] private List<string> sceneNameList;
private string sceneToFind;
private int index = 0;
public void SceneChanger()
{
sceneToFind = EventSystem.current.currentSelectedGameObject.name;
foreach(string str in sceneNameList)
{
if(str == sceneToFind)
{
SceneManager.LoadScene(index);
index = 0;
break;
}
index++;
}
}
public void Exit()
{
Application.Quit();
}
public void BackMenu()
{
SceneManager.LoadScene(4);
}
}
OnTriggerEnter2D doesn't work after switching scenes. We could use OnTriggerEnter2D to jump scenes.
code show as below:
private void Update() {
// If E is pressed
if (Input. GetKeyDown(KeyCode. E)) {
// scene switching
SceneManager.LoadScene(4);
}
}
private void OnTriggerEnter2D(Collider collision) {
if (collision. tag == "ResizeField") {
// The UI prompts the user to press E to jump
EnterDialog.SetActive(true);
Debug.Log("Condition is working");
// _anim.SetBool("isInTrigger", true);
}
}
Hope it helps you.
specifically it sends as many outputs as there are objects with the code in them that allows them to be interacted with, even if I press E when not looking at anything. I wanted to make an inventory system, but this causes all objects that have this code to be interacted with. I included all the scripts that im using for this system if that can help. I genuinely don't know what I did wrong
the interactor code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using System;
public class Interactable : MonoBehaviour
{
public LayerMask interactableLayerMask;
//ITEM VARIABLE
public Item item;
//PICK UP RADIUS
public float radius = 4f;
public void Interact()
{
Debug.Log("Interacted with " + transform.name);
PickUp();
}
//PICK UP INTERACTION
void PickUp()
{
Debug.Log("Picking up " + item.name);
bool wasPickedUp = Inventory.instance.Add(item);
if (wasPickedUp)
Destroy(gameObject);
}
//INTERACTION
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("Added item -----------------------------------------");
RaycastHit hit;
if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hit, radius))
{
Interactable interactable = hit.collider.GetComponent<Interactable>();
if (interactable != null)
{
Interact();
}
} else
{
Debug.Log("Nothing");
}
}
}
}
the Inventory code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
#region Singleton
public static Inventory instance;
void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one instance of Inventory found!");
return;
}
instance = this;
}
#endregion
public delegate void OnItemChanged();
public OnItemChanged onItemChangedCallback;
public int space = 20;
public List<Item> items = new List<Item>();
public bool Add (Item item)
{
if (!item.isDefaultItem)
{
if (items.Count >= space)
{
Debug.Log("Note enough space in inventory");
return false;
}
else
{
items.Add(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
}
return true;
}
public void Remove(Item item)
{
items.Remove(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
}
Item code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]
public class Item : ScriptableObject
{
new public string name = "New Item";
public Sprite icon = null;
public bool isDefaultItem = false;
}
To my understanding, your code goes into the Input.GetKeyDown(KeyCode.E) for every object in the scene, but it does not add it to the inventory.
That is because you use the Update function in the Interactable class, which are your objects. Instead, try moving the exact same block of code into your Inventory class. Make sure you make your Interact method public, so you can call it.
so everything seems to be running as far as the AI switching different States, however when it gets in range with the player the State freezes in attack and does not play any Attack Animation, I have created the AI with Bone Rigging, Im wondering if this may be affecting it, here is my script for calling the Animation and the script for one the animation has finished to go back to idleFollow state.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MeleeWeapon : Weapon
{
[SerializeField] private float attackDelay = 1f;
private Collider2D damageAreaCollider2D;
private Animator animatorAttack;
private bool attacking;
private readonly int useMeleeWeapon = Animator.StringToHash(name:"UseMeleeWeapon");
private void Start()
{
damageAreaCollider2D = GetComponent<BoxCollider2D>();
animatorAttack = GetComponent<Animator>();
//animatorAttack.Play(useMeleeWeapon);
}
public override void UseWeapon()
{
StartCoroutine(routine: Attack());
}
/*protected override void Update()
{
base.Update();
// FlipMeleeWeapon();
}*/
private IEnumerator Attack()
{
if (attacking)
{
yield break;
}
// Attack
attacking = true;
damageAreaCollider2D.enabled = true;
animatorAttack.Play(useMeleeWeapon);
// Stop Attack
yield return new WaitForSeconds(attackDelay);
damageAreaCollider2D.enabled = false;
attacking = false;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "AI/Decisions/Attack Completed", fileName = "AttackCompleted")]
public class DecisionAttackCompleted : AIDecision
{
public override bool Decide(StateController controller)
{
return AttackCompleted(controller);
}
private bool AttackCompleted(StateController controller)
{
if (controller.CharacterWeapon.CurrentWeapon.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).length
> controller.CharacterWeapon.CurrentWeapon.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).normalizedTime)
{
return true;
}
return false;
}
}
Ok I fixed the issue, I ended up placing this code into the WeaponMelee script, but now im dealing with a weird glitch where the AI keeps tring to face the left instead of the player when they are close to eachother. this has to do with the boxcollider2D so ive placed a capsulecollider2d inside of him and changed the script to capsulecollider2d as well. but the issue is still happening
private void OnTriggerEnter2D(Collider2D collision)
{
StartCoroutine(routine: Attack());
}
For now I created array of Text and then dragging in the editor one by one to the inspector to the array.
The problem is that some Text ui are childs or childs of childs and it's not easy to find them all one by one.
How can I loop over the hierarchy or find all the text ui and then to disable/enable them ?
I need it for my game pause/resume.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BackToMainMenu : MonoBehaviour
{
public Text[] uiTexts;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
SceneManager.UnloadSceneAsync(0);
DisableEnableUiTexts(false);
Cursor.visible = false;
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
MenuController.LoadSceneForSavedGame = false;
DisableEnableUiTexts(true);
SceneManager.LoadScene(0, LoadSceneMode.Additive);
Cursor.visible = true;
}
}
}
private void DisableEnableUiTexts(bool uiTextEnabled)
{
if (uiTexts.Length > 0)
{
foreach (Text ui in uiTexts)
{
if (uiTextEnabled)
{
ui.GetComponent<Text>().enabled = false;
}
else
{
ui.GetComponent<Text>().enabled = true;
}
}
}
}
}
Update :
I tried this :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BackToMainMenu : MonoBehaviour
{
[ContextMenuItem("Fetch", nameof(FetchAllTexts))]
public Text[] uiTexts;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
SceneManager.UnloadSceneAsync(0);
DisableEnableUiTexts(false);
Cursor.visible = false;
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
MenuController.LoadSceneForSavedGame = false;
DisableEnableUiTexts(true);
SceneManager.LoadScene(0, LoadSceneMode.Additive);
Cursor.visible = true;
}
}
}
private void FetchAllTexts()
{
var tmp = new List<Text>();
for (var i = 0; i < SceneManager.sceneCount; i++)
{
foreach (var root in SceneManager.GetSceneAt(i).GetRootGameObjects())
{
tmp.AddRange(root.GetComponentsInChildren<Text>(true));
}
}
Text[] texts = tmp.ToArray();
uiTexts = texts;
}
private void DisableEnableUiTexts(bool uiTextEnabled)
{
if (uiTexts.Length > 0)
{
foreach (Text ui in uiTexts)
{
ui.enabled = uiTextEnabled;
}
}
}
}
but I don't see the "Fetch" ContextMenuItem anywhere. Tried right click on the object in hierarchy where the script is attached to tried in the Assets tried in the editor menu/s it's not there.
As said either use FindObjectsOfType
Text[] texts = FindObjectsOfType<Text>();
will return all active and enabled instances of Text.
However to get also disabled/inactive ones you can use
var tmp = new List<Text>();
for(var i = 0; i < SceneManager.sceneCount; i++)
{
foreach(var root in SceneManager.GetSceneAt(i).GetRootGameObjects)
{
tmp.AddRange(root.GetComponentsInChildren<Text>(true));
}
}
Text[] texts = tmp.ToArray();
which iterates over all loaded scenes and fetches ALL instances also currently inactive or disabled ones.
See
SceneManager.sceneCount
SceneManager.GetSceneAt
Scene.GetRootGameObjects
GameObject.GetComponentsInChildren
Then you can e.g. fetch this once either in Start or even earlier via the Inspector itself so you don't have to do it at every app start using [ContextMenuItem].
public class BackToMainMenu : MonoBehaviour
{
[ContextMenuItem("Fetch", nameof(FetchAllTexts)]
public Text[] uiTexts;
private void FetchAllTexts()
{
uiTexts = // One of the methods shown above
}
...
}
Why is this a good thing? As said this way you don't have to do it every time in Start and thus delaying the app start until it is done.
Instead you store all the references already via the Inspector in a SerializeField so when your app starts, this component already "knows" these references and you don't have to get them first.
Simply right click on the uiTexts field in the Inspector and hit Fetch
Then in general you would simply write
private void DisableEnableUiTexts(bool uiTextEnabled)
{
foreach (Text ui in uiTexts)
{
ui.enabled = uiTextEnabled;
}
}
I hope you all are doing well. I have been following a Unity tutorial for a rhythm game and I have found this bug that I could not get past. Essentially, my OnTriggerExit2D is getting called too early. I'll include a picture in the conclusion of this post. I have tried logging the game object and it seems that all of my button objects suffer the same fate. I have included a link of the tutorial that I have been following in the conclusion. Any help towards figuring this out would be helpful.
Tutorial Link: https://www.youtube.com/watch?v=PMfhS-kEvc0&ab_channel=gamesplusjames
What my game looks like, the missed shows up when I've hit it.
Debug Output
GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public AudioSource theMusic;
public bool startPlaying;
public BeatScroller theBS;
public static GameManager instance;
public int currentScore;
public int scorePerNote = 100;
public int scorePerGoodNote = 125;
public int scorePerPerfectNote = 150;
public int currentMultiplier;
public int multiplierTracker;
public int [] multiplierTresholds;
public Text scoreText;
public Text multiText;
// Start is called before the first frame update
void Start()
{
instance = this;
scoreText.text = "Score: 0";
multiText.text = "Multiplier: x1";
currentMultiplier = 1;
}
// Update is called once per frame
void Update()
{
if(!startPlaying){
if(Input.anyKeyDown){
startPlaying = true;
theBS.hasStarted = true;
theMusic.Play();
}
}
}
public void NoteHit(){
Debug.Log("Note Hit On Time");
if(currentMultiplier-1 < multiplierTresholds.Length){
multiplierTracker++;
if(multiplierTresholds[currentMultiplier-1] <= multiplierTracker){
multiplierTracker = 0;
currentMultiplier++;
}
}
multiText.text = "Multiplier: x"+currentMultiplier;
//currentScore += scorePerNote * currentMultiplier;
scoreText.text = "Score: "+currentScore;
}
public void NormalHit(){
currentScore += scorePerNote * currentMultiplier;
NoteHit();
}
public void GoodHit(){
currentScore += scorePerGoodNote * currentMultiplier;
NoteHit();
}
public void PerfectHit(){
currentScore += scorePerPerfectNote * currentMultiplier;
NoteHit();
}
public void NoteMissed(){
Debug.Log("MISSED!");
multiText.text = "Multiplier: x1";
}
}
BeatScroller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeatScroller : MonoBehaviour
{
public float beatTempo;
public bool hasStarted;
// Start is called before the first frame update
void Start()
{
beatTempo = beatTempo / 60f;
}
// Update is called once per frame
void Update()
{
if(!hasStarted){
}else{
transform.position -= new Vector3(0f, beatTempo*Time.deltaTime, 0f);
}
}
}
ButtonController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonController : MonoBehaviour
{
// Start is called before the first frame update
private SpriteRenderer theSR;
public Sprite defaultImage;
public Sprite pressedImage;
public KeyCode keyToPress;
void Start()
{
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(keyToPress))
{
theSR.sprite = pressedImage;
}
if(Input.GetKeyUp(keyToPress))
{
theSR.sprite = defaultImage;
}
}
}
noteObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class noteObject : MonoBehaviour
{
public bool canBePressed;
public KeyCode KeyToPress;
public GameObject hitEffect, goodEffect, perfectEffect, missedEffect;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyToPress))
{
if(canBePressed)
{
gameObject.SetActive(false);
if(Mathf.Abs(transform.position.y) > 0.25){
GameManager.instance.NormalHit();
Debug.Log("Normal Hit!");
Instantiate(hitEffect,transform.position, hitEffect.transform.rotation);
}else if(Mathf.Abs(transform.position.y) > 0.05f){
GameManager.instance.GoodHit();
Debug.Log("Good Hit!!");
Instantiate(goodEffect,transform.position, goodEffect.transform.rotation);
}else{
GameManager.instance.PerfectHit();
Debug.Log("PERFECT HIT!!!");
Instantiate(perfectEffect,transform.position, perfectEffect.transform.rotation);
}
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if(other.tag == "Activator")
{
canBePressed = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator")
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}
}
EffectObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectObject : MonoBehaviour
{
public float lifeTime = 1f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Destroy(gameObject, lifeTime);
}
}
Hmm ... You say that OnTriggerExit2D is called to early? I assume it's called when the elements are still inside one another? If that's the case I guess your bounding boxes don't have the right size, or the right shape. I see arrows, do they have a rectangular bounding box or a polygon one that follows their shape? Are all your bounding boxes the right size?
I figured out what was wrong thanks to AdrAs's comment.
Turns out I had to check the y position of my arrow and collider were well enough below the height of my button box collider. In addition to that, I reshaped my colliders. I found that this code did the trick for me. Thank You All For the Nudges in the right direction.
noteObject -> new OnTriggerExit2D
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator" && transform.position.y < -0.32)
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}