Unity says that 'Button' is not a Component and doesn't derive from Monobehavior - unity3d

This was originally coded in unity 2018 then ported to unity 2019 because my main project is on that version. It works perfectly in unity 2018 (aside from that button.clicked += used to be button.AddListener())
The only relevant bit of the code is lines 12 - 17 but who knows.
Script is attached to object with the Button component.
Unity 2019.4.11f1
Returns the following error with the setup listed above:
ArgumentException: GetComponent requires that the requested component 'Button' derives from MonoBehaviour or Component or is an interface.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using UnityEngine.Events;
public class ButtonProperties : MonoBehaviour {
public ButtonType buttonType;
public string dest;
Button button;
void Start()
{
button = GetComponent<Button>();
//perform different functions dependant on the button type
if (buttonType == ButtonType.Scene)
{
//add ButtonLoadScene as a listener to the the button
button.clicked += ButtonLoadScene;
}
else if (buttonType == ButtonType.Menu)
{
button.clicked += ButtonLoadMenu;
}
//change button text to "Load Scene" + the name of the scene
string str = "Load " + dest;
GetComponentInChildren<TextElement>().text = str;
}
//private scene loader because event listeners cant take arguments >:(
void ButtonLoadScene()
{
SceneManager.LoadScene(dest);
}
void ButtonLoadMenu()
{
//array of all canvases in the scene
Canvas[] canvases = GameObject.FindObjectsOfType<Canvas>();
//foreach canvas
foreach (Canvas canvas in canvases)
{
//if the canvases name is the desired name
if (canvas.name == dest)
{
//turn all canvases off
foreach (Canvas c in canvases)
{
c.enabled = false;
}
//turn current canvas on
canvas.enabled = true;
//break loop
break;
}
}
}
}
public enum ButtonType
{
Scene,
Menu
};
I also tested this by adding a NewBehaviourScript to the button with Button button; and, in the start function, Start() {button = GetComponent<Button>();}
Same error

I believe you are using the wrong namespace for your button. Try changing:
using UnityEngine.UIElements;
to
using UnityEngine.UI;
There is a Button in both namespaces, but the one in UnityEngine.UI is likely the component you're working with.

Had somebody else on the team test the build, as I started to think from the comment thread, I was missing the UI library. Changing it to the UI library was the solution but not for me until I restarted the computer. Thank you Thomas Finch for the help.

Related

How to load next scene when pressed a button while playing in unity

This what I tried,
I was watching Brackeys
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
};
Your script probably doesn't work if you have 1 scene, or the current scene is at the end of the list in Build Settings.
Also you can use it this way:
//Add scenes in inspector
[SerializeField] private List<Scene> _sceneList;
public void LoadNextScene()
{
int currentScene = SceneManager.GetActiveScene().buildIndex;
if (currentScene < _sceneList.Count)
SceneManager.LoadScene(_sceneList[currentScene + 1].buildIndex);
else
print("Its last scene");
}
You can use SceneManager.LoadScene() method to load the Scene by its name or index in Build Settings.
The SceneManager.GetActiveScene().buildIndex gives you the index number of the current scene and you can add an incremental value to navigate to the next scene.
To do that,
Create a new script named SceneController and methods as follows,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneController : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void LoadMenuScene() {
SceneManager.LoadScene("MenuScene");
}
public void NextScene() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
public void ReloadScene() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
Add the script to the Canvas object
Add the method to the button OnClick event in the inspector
NB: You can also use name or index values to load the scene (like LoadMenuScene)
For more: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html

unity ads not showing up

i followed the official unity tutorial on how to put bannner ads on my game, i set everything up in the unity dashboard, and in the editor i see a rectangle that says the ad should be there, but when i build the game nothing shows up, here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Advertisements;
public class bannerad : MonoBehaviour
{
bool testMode = false;
public string gameId = "******" (my game id is here i just dot want to share it);
// Start is called before the first frame update
IEnumerator Start()
{
Advertisement.Initialize(gameId, testMode);
while (!Advertisement.IsReady("Lost_Ad"))
{
yield return null;
}
Advertisement.Banner.SetPosition(BannerPosition.BOTTOM_CENTER);
Advertisement.Banner.Show("Lost_Ad");
}
}
anyone knows what is the solution?
Are you ever calling this function? If you just replaced the Start function with this IEnumerator it will not get called unless you call it.
private void Start()
{
Advertisement.Initialize(gameId, testMode);
StartCoroutine(StartAds());
}
private IEnumerator StartAds()
{
// your code here
}

Need a way to use button click functionality vs GetKeyDown in Coroutine

This animator script works, however, in place of the Keycode input inside the WHILE LOOP, I need to use a UI button for mobile, which I haven't been able to figure out. I found an answer about putting a wrapper around it to make method available to click event, but have no idea how that's supposed to work within update function. It took me a long time to get this far, being a newbie to unity AND c#, so if you could provide a detailed answer or suggestion, I can get my life back, please help if you can.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using SWS;
public class goat_question : MonoBehaviour
{
private Animator anim;
public GameObject player;
public Text ResultText;
public Text AnswerText;
public Text AnswerText2;
public Button GoatButton;
void Start()
{
anim = GetComponent<Animator>();
Button btn = GoatButton.GetComponent<Button>();
btn.onClick.AddListener(TaskOnClick);
}
void TaskOnClick()
{
Debug.Log("You have clicked the button!");
}
// Update is called once per frame
void Update()
{
if (AnswerText.text.Equals(AnswerText2.text))
{
StartCoroutine(GoatWalkPathCoroutine());
}
IEnumerator GoatWalkPathCoroutine()
{
while (true)
{
if (Input.GetKeyDown(KeyCode.K))
{
anim.Play("goat_hi_walk");
player.GetComponent<splineMove>().enabled = true;
yield return new WaitForSeconds(27);
anim.Play("goat_hi_licking");
}
yield return null;
}
}
}
}
In a separate script for just the UI button, have a bool called isClicked or something, and when the button gets clicked set that to true. In this main script, you can reference the one you just made, and instead of the Input.GetKey, you can say, if(otherScript.isClicked).

Simplest way to carry dialogue between scenes without use of Player Prefs, etc

I have been working on a dialogue system for my game and I was wondering if anyone knows how to keep the system between different scenes. I know you can use things such as Player Prefs but for one, I do not understand it and upon research, people do not generally recommend it for storing large complicated things. I managed to get close to doing so by using dontDestroy just as you would with a character, however, it did not work completely as the button to switch to the next line of text, of course, broke along with the singleton I created for my system. What would be the best way for me to go about this?
Here is all of my code just in case it is needed:
Making the scriptable object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "New Dialogue", menuName = "Dialogues")]
public class Dialogue : ScriptableObject
{
[System.Serializable]
public class Info
{
public string myName;
public Sprite portrait;
[TextArea(4, 8)]
public string mytext;
}
[Header("Insert Dialogue Info Below")]
public Info[] dialogueInfoSection;
}
Main code for system (sigleton breaks here while switching scenes):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MainDialogueManager : MonoBehaviour
{
public static MainDialogueManager instance;
private void Awake()
{
if(instance != null)
{
Debug.LogWarning("FIX THIS" + gameObject.name);
}
else
{
instance = this;
}
}
public GameObject DialogueBoX;
public Text dialogueNameofChar;
public Text characterSays;
public Image characterPortrait;
private float textDelay = 0.005f;
public Queue<Dialogue.Info> dialogueInfoSection = new Queue<Dialogue.Info>();
public void EnqueueDialogue(Dialogue db)
{
DialogueBoX.SetActive(true);
dialogueInfoSection.Clear();
foreach(Dialogue.Info info in db.dialogueInfoSection)
{
dialogueInfoSection.Enqueue(info);
}
DequeueDialogue();
}
public void DequeueDialogue()
{
if (dialogueInfoSection.Count==0)
{
ReachedEndOfDialogue();
return; /////
}
Dialogue.Info info = dialogueInfoSection.Dequeue();
dialogueNameofChar.text = info.myName;
characterSays.text = info.mytext;
characterPortrait.sprite = info.portrait;
StartCoroutine(TypeText(info));
}
IEnumerator TypeText(Dialogue.Info info)
{
characterSays.text= "";
foreach(char c in info.mytext.ToCharArray())
{
yield return new WaitForSeconds(textDelay);
characterSays.text += c;
yield return null;
}
}
public void ReachedEndOfDialogue()
{
DialogueBoX.SetActive(false);
}
}
Dialogue Activation:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainDialogueActivation : MonoBehaviour
{
public Dialogue dialogue;
public void startActivationofDialogue()
{
MainDialogueManager.instance.EnqueueDialogue(dialogue);
}
private void Start()
{
startActivationofDialogue();
}
}
Go to next dialogue line:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MainDialogueButtons : MonoBehaviour
{
public void GoToNextDialogueLine()
{
MainDialogueManager.instance.DequeueDialogue();
}
}
How about something like this?
The idea is pretty similar to what you're doing, with a few tweaks:
I'm storing the active dialog in a scriptable object (DialogueSystem) so that it can persist between scenes. Each time I load a new scene, I check if there's an active dialog, and if I so I show the dialog popup in Start().
Whereas you remove the dialog section that you're currently showing to the player from the current dialog, I don't remove the current section until the player clicks to the next section. That's necessary because you may need to re-show the same section if you move to a new scene.
Make sure to create an instance of the DialogueSystem scriptable object and assign it to MainDialogueActivation and MainDialogManager
MainDialogActiviation has some testing code in it so you can hit a key to start a new dialog or switch between scenes.
MainDialogueActiviation.cs
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainDialogueActivation : MonoBehaviour
{
public Dialogue dialogue;
// This scriptable object stores the active dialog so that you
// can persist it between scenes
public DialogueSystem dialogSystem;
private void Start()
{
// If we had an active dialog from the previous scene, resume that dialog
if (dialogSystem?.dialogInfoSections.Count > 0)
{
GetComponent<MainDialogueManager>().ShowDialog();
}
}
private void Update()
{
// Pressing D queues and shows a new dialog
if (Input.GetKeyDown(KeyCode.D))
{
GetComponent<MainDialogueManager>().EnqueueDialogue(this.dialogue);
}
// Pressing C ends the current dialog
if (Input.GetKeyDown(KeyCode.C))
{
this.dialogSystem.dialogInfoSections.Clear();
GetComponent<MainDialogueManager>().ReachedEndOfDialogue();
}
// Pressing S swaps between two scenes so you can see the dialog
// persisting
if (Input.GetKeyDown(KeyCode.S))
{
if (SceneManager.GetActiveScene().name == "Scene 1")
{
SceneManager.LoadScene("Scene 2");
}
else if (SceneManager.GetActiveScene().name == "Scene 2")
{
SceneManager.LoadScene("Scene 1");
}
}
}
}
MainDialogueManager.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class MainDialogueManager : MonoBehaviour
{
// This scriptable object stores the active dialog
public DialogueSystem dialogSystem;
public GameObject DialogueBox;
public Text dialogueNameofChar;
public Text characterSays;
public Image characterPortrait;
private float textDelay = 0.005f;
// The game object for the dialog box that is instantiated in this
// scene
private GameObject dialogBoxGameObject;
/// <summary>
/// Shows the dialog window for the dialog that is in this object's
/// dialogSystem property.
/// </summary>
public void ShowDialog()
{
// Instantiate the dialog box prefab
this.dialogBoxGameObject = Instantiate(this.DialogueBox);
// I'd recommend putting a script on your "dialog box" prefab to
// handle this stuff, so that this script doesn't need to get a
// reference to each text element within the dialog prefab. But
// this is just a quick and dirty example for this answer
this.dialogueNameofChar = GameObject.Find("Character Name").GetComponent<Text>();
this.characterSays = GameObject.Find("Character Text").GetComponent<Text>();
this.characterPortrait = GameObject.Find("Character Image").GetComponent<Image>();
// If you have multiple response options, you'd wire them up here.
// Again; I recommend putting this into a script on your dialog box
GameObject.Find("Response Button 1").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);
GameObject.Find("Response Button 2").GetComponent<Button>().onClick.AddListener(ShowNextDialogSection);
ShowDialogSection(this.dialogSystem.dialogInfoSections.Peek());
}
/// <summary>
/// Puts a dialog into this object's dialogSystem property and
/// opens a dialog window that will show that dialog.
/// </summary>
public void EnqueueDialogue(Dialogue db)
{
foreach (Dialogue.Info info in db.dialogueInfoSection)
{
this.dialogSystem.dialogInfoSections.Enqueue(info);
}
ShowDialog();
}
/// <summary>
/// Removes the dialog section at the head of the dialog queue,
/// and shows the following dialog statement to the player. This
/// is a difference in the overall logic, because now the dialog
/// section at the head of the queue is the dialog that's currently
/// being show, rather than the previous one that was shown
/// </summary>
public void ShowNextDialogSection()
{
this.dialogSystem.dialogInfoSections.Dequeue();
if (this.dialogSystem.dialogInfoSections.Count == 0)
{
ReachedEndOfDialogue();
return;
}
Dialogue.Info dialogSection = this.dialogSystem.dialogInfoSections.Peek();
ShowDialogSection(dialogSection);
}
/// <summary>
/// Shows the specified dialog statement to the player.
/// </summary>
public void ShowDialogSection(Dialogue.Info dialogSection)
{
dialogueNameofChar.text = dialogSection.myName;
characterSays.text = dialogSection.mytext;
characterPortrait.sprite = dialogSection.portrait;
StartCoroutine(TypeText(dialogSection));
}
IEnumerator TypeText(Dialogue.Info info)
{
characterSays.text = "";
foreach (char c in info.mytext.ToCharArray())
{
yield return new WaitForSeconds(textDelay);
characterSays.text += c;
yield return null;
}
}
public void ReachedEndOfDialogue()
{
// Destroy the dialog box
Destroy(this.dialogBoxGameObject);
}
}
DialogSystem.cs
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Dialogues/Dialog System")]
public class DialogueSystem : ScriptableObject
{
public Queue<Dialogue.Info> dialogInfoSections = new Queue<Dialogue.Info>();
}
Here's what my dialog box prefab looks like
Every scene needs an object (presumably a prefab to make it easy to add to every scene) that has MainDialogActiviation and MainDialogManager on it. Mine looks like this:
This might be a bit of an unpopular opinion but using Singleton's are fine. It's just that MonoBehaviour singletons are tricky, you can use Object.DontDestroyOnLoad(instance). But things get ugly because it doesn't get destroyed when the scene changes (good) but if you go back to the scene it will load another one (bad). There's a few ways to get around that like having the object destroy itself if there's already an instance or having a subscene.
I would suggest not using MonoBehaviour singletons and use ScriptableObject singletons. You can lazy instantiate by putting the asset in a resource folder and use Resource.Load like this.
public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableSingleton<T> {
private static string ResourcePath {
get {
return typeof(T).Name;
}
}
public static T Instance {
get {
if (instance == null) {
instance = Resources.Load(ResourcePath) as T;
}
return instance;
}
}
private static T instance;
}
With this code you create a Singleton class say DialogueManager you create a DialogueManager.asset for it and put it in a "Resources" folder.

Scene not properly loaded when switching back and forth

Problem
The Unity project I build is targeting iOS, Android and Windows X64. I have two scenes, A and B, whereas A is the main menu scene of my game and scene B is kind of a level selection scene in which the user can choose a level to play. From scene A I can navigate to scene B and back again. When running the game in the Unity Editor, everything behaves as expected. The problem arises, when I run the game on the target platforms (real devices). Then, when navigating like A --> B --> A, I end up in scene A being rendered as a black screen, except the FPSIndicator game object which is still rendered and doing its job. The FPSIndicator game object is a small piece of code which draws itself to the scene in the OnGUI callback. Nothing else is displayed.
Setup of Scene A
I have a Unity UI button there ("Drag and Drop"), which, when clicked, loads scene B using this code:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.EventSystems;
public class GameTypeButtonController : MonoBehaviour, IPointerClickHandler
{
public ButtonSounds ButtonSounds;
public string SceneNameToLoad;
public GameType GameType;
public void OnPointerClick(PointerEventData eventData)
{
StartCoroutine(Do());
}
private IEnumerator Do()
{
var animator = gameObject.GetComponent<Animator>();
if (animator != null)
{
animator.SetTrigger("Clicked");
}
var audioSource = gameObject.GetComponent<AudioSource>();
if (audioSource != null)
{
var clip = GetRandomAudioClip(ButtonSounds);
audioSource.clip = clip;
audioSource.Play();
yield return new WaitWhile(() => audioSource.isPlaying);
}
Logger.LogInfo("[GameTypeButtonController.Do] Setting game type " + GameType);
GameManager.Instance.CurrentGameType = GameType;
SceneManager.LoadScene(SceneNameToLoad);
}
private AudioClip GetRandomAudioClip(ButtonSounds buttonSounds)
{
var numberOfAudioClips = buttonSounds.AudioClips.Length;
var randomIndex = Random.Range(0, numberOfAudioClips);
return buttonSounds.AudioClips[randomIndex];
}
}
The scene looks like this:
Setup of Scene B
In scene B, I have a button in the lower left which brings me back to scene A when clicked. This is not a Unity UI button, but a regular sprite with a CircleCollider2D attached. The script on that button looks like this:
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class HomeButtonController : MonoBehaviour
{
public ButtonSounds ButtonSounds;
public string SceneNameToLoad;
void OnMouseDown()
{
StartCoroutine(Do());
}
private IEnumerator Do()
{
var animator = gameObject.GetComponent<Animator>();
if (animator != null)
{
animator.SetTrigger("Clicked");
}
var audioSource = gameObject.GetComponent<AudioSource>();
if (audioSource != null)
{
var clip = GetRandomAudioClip(ButtonSounds);
audioSource.clip = clip;
audioSource.Play();
yield return new WaitWhile(() => audioSource.isPlaying);
}
SceneManager.LoadScene(SceneNameToLoad);
}
private AudioClip GetRandomAudioClip(ButtonSounds buttonSounds)
{
var numberOfAudioClips = buttonSounds.AudioClips.Length;
var randomIndex = UnityEngine.Random.Range(0, numberOfAudioClips);
return buttonSounds.AudioClips[randomIndex];
}
}
The scene looks like this:
General Notes
Two objects use DontDestroyOnLoad: GameManager and MusicPlayer.
What I have checked so far
Scenes are properly referenced in Build Settings
As I use Unity Cloud Build, I have disabled the Library Caching feature to avoid issues with old build artifacts (so every time I build, I do a proper, clean build)
I can locally build all three platforms (Unity reports it as "Build successful"). So no build errors.
I am using LoadSceneMode.Single (default)
I am using the same Unity version locally and in Unity Cloud Build: 2018.3.0f2
Update 2019-02-19:
When I navigate from a third scene C back to scene A using the same mechanism (a sprite button calling a coroutine), I also end up on the very same black screen. So the issue probably exists within scene A?
Update 2 from 2019-02-19:
Here's my GameManager code:
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public EventHandler<LevelStartedEventArgs> LevelStarted;
public EventHandler<LevelFinishedEventArgs> LevelFinished;
// General
public GameType CurrentGameType;
public GameScene CurrentScene;
public int CurrentLevel;
public static GameManager Instance;
public GameLanguage Language;
public bool IsMusicEnabled;
private string gameStateFile;
void Start()
{
if (Instance == null)
{
gameStateFile = Application.persistentDataPath + "/gamestate.dat";
Load(gameStateFile);
DontDestroyOnLoad(gameObject);
Instance = this;
}
else if (Instance != this)
{
Destroy(gameObject);
}
}
public void Save()
{
Logger.LogInfo("[GameManager.Save] Saving game state to " + gameStateFile);
var bf = new BinaryFormatter();
var file = File.Create(gameStateFile);
var gameState = new GameState();
gameState.Language = Language;
gameState.IsMusicEnabled = IsMusicEnabled;
bf.Serialize(file, gameState);
file.Close();
Logger.LogInfo("[GameManager.Save] Successfully saved game state");
}
public void Load(string gameStateFile)
{
Logger.LogInfo("[GameManager.Load] Loading game state from " + gameStateFile);
if (File.Exists(gameStateFile))
{
var bf = new BinaryFormatter();
var file = File.Open(gameStateFile, FileMode.Open);
var gameState = (GameState)bf.Deserialize(file);
file.Close();
Language = gameState.Language;
IsMusicEnabled = gameState.IsMusicEnabled;
}
Logger.LogInfo("[GameManager.Load] Successfully loaded game state");
}
[Serializable]
class GameState {
public GameLanguage Language;
public bool IsMusicEnabled;
}
}
Thanks for any hints!
So, I finally managed to solve the issue on my own. Here's a list of steps I went through in order to eliminate my issue:
I installed the "Game development with Unity" workload in Visual Studio 2017 in order to be able to use the Visual Studio Debugger. That gave me the possibility to properly debug my scripts (setting breakpoints, inspect values, ...) and go through them, step by step. See this link for some details on how to accomplish this. Unfortunately, this did not solve my problem yet, but gave a good basis for troubleshooting my issue.
As the issue described above, also arised on Windows x64 builds, I've decided to troubleshoot my issue on a windows build. So I reconfigured my Windows build in the Build Player Settings, by setting Script Debugging to true, Development build to true, Copy PDB filesto true. Then I've ran another windows build.
If you now run your game, it will show a development console in the lower left screen, giving a lot of useful information (exceptions thrown, etc...). Also you can open the log file directly from within the game, which provides a LOT of useful informations to tackle down the real issue.
So, I had some PlatformNotSupportedExceptions in the logs, which I was able to fix and some other errors. Now it is working.
Although, I've answered my own question, I hope this will be useful to some of you.