Why is it SO Hard to Just Mute a Sound in Unity? - unity3d

I have spent all day looking for a solution to this problem, and I simply can't find one. Using JavaScript in Unity 3D, I have a script where I want to play a sound when the player's velocity on the X axis reaches a certain point, and if it's not at that point, then the sound will be muted. And I believe I have all the structure right, it's just the line of code that says to mute the audio that won't work. I've tried all kinds of different combinations, and I get an error for each one.
The script looks like this:
#pragma strict
var playing = false;
var audioSource = GetComponent.<AudioSource>();
function Update () {
if (transform.GetComponent.<Rigidbody>().velocity.x <= 2.5 &&
transform.GetComponent.<Rigidbody>().velocity.x >= -2.5)
{
Mute();
} else {
Unmute();
}
}
function Mute () {
audioSource.mute = true;
}
function Unmute () {
audioSource.mute = false;
Sound();
}
function Sound () {
if (transform.GetComponent.<Rigidbody>().velocity.x >= 2.5 && playing ==
false)
{
playing = true;
GetComponent.<AudioSource>().Play();
yield WaitForSeconds(2);
playing = false;
}
if (transform.GetComponent.<Rigidbody>().velocity.x <= -2.5 &&
playing == false)
{
playing = true;
GetComponent.<AudioSource>().Play();
yield WaitForSeconds(2);
playing = false;
}
}
I've gotten all kinds of different errors, but the one I seem to be getting the most says "UnityException: GetComponentFastPath is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour 'motioncheck' on game object 'Ball'." I'm not sure what this means, since I'm still kinda a nub at JavaScript.
I feel like it shouldn't be this hard to just mute a sound. I'm going to assume that the answer to this is really simple and that I'm just really dumb. That's what usually seems to happen, lol.
In the mean time, I'm going to continue my rampage across the internet in search for answers to this problem.

Your mute code is fine.
"UnityException: GetComponentFastPath is not allowed to be called from
a MonoBehaviour constructor (or instance field initializer), call it
in Awake or Start instead. Called from MonoBehaviour 'motioncheck' on
game object 'Ball'." I'm not sure what this means, since I'm still
kinda a nub at JavaScript.
See this:
var audioSource = GetComponent.<AudioSource>();
That's a Unity API and you have to call their functions from inside a function. The Awake or Start function is appropriate for initializing component variables.
var audioSource : AudioSource;
function Start()
{
audioSource = GetComponent.<AudioSource>();
}
Note that Unityscript/Javascript is now discontinued. They no longer update the doc on this and you cannot create new scripts from the Editor anymore. It still works as for now but the compiler will be removed soon. Please learn and start using C# before its support is totally removed.

Related

Unity3D New Input System: Is it really so hard to stop UI clickthroughs (or figure out if cursor is over a UI object)?

Even the official documentation has borderline insane recommendations to solve what is probably one of the most common UI/3D interaction issues:
If I click while the cursor is over a UI button, both the button (via the graphics raycaster) and the 3D world (via the physics raycaster) will receive the event.
The official manual:
https://docs.unity3d.com/Packages/com.unity.inputsystem#1.2/manual/UISupport.html#handling-ambiguities-for-pointer-type-input essentially says "how about you design your game so you don't need 3D and UI at the same time?".
I cannot believe this is not a solved problem. But everything I've tried failed. EventSystem.current.currentSelectedGameObject is sticky, not hover. PointerData is protected and thus not accessible (and one guy offered a workaround via deriving your own class from Standalone Input Module to get around that, but that workaround apparently doesn't work anymore). The old IsPointerOverGameObject throws a warning if you query it in the callback and is always true if you query it in Update().
That's all just mental. Please someone tell me there's a simple, obvious solution to this common, trivial problem that I'm just missing. The graphics raycaster certainly stores somewhere if it's over a UI element, right? Please?
I've looked into this a fair bit and in the end, the easiest solution seems to be to do what the manual says and put it in the Update function.
bool pointerOverUI = false;
void Update()
{
pointerOverUI = EventSystem.current.IsPointerOverGameObject();
}
Your frustration is well founded: there are NO examples of making UI work with NewInput which I've found. I can share a more robust version of the Raycaster workaround, from Youtube:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
/* Danndx 2021 (youtube.com/danndx)
From video: youtu.be/7h1cnGggY2M
thanks - delete me! :) */
public class SCR_UiInteraction : MonoBehaviour
{
public GameObject ui_canvas;
GraphicRaycaster ui_raycaster;
PointerEventData click_data;
List<RaycastResult> click_results;
void Start()
{
ui_raycaster = ui_canvas.GetComponent<GraphicRaycaster>();
click_data = new PointerEventData(EventSystem.current);
click_results = new List<RaycastResult>();
}
void Update()
{
// use isPressed if you wish to ray cast every frame:
//if(Mouse.current.leftButton.isPressed)
// use wasReleasedThisFrame if you wish to ray cast just once per click:
if(Mouse.current.leftButton.wasReleasedThisFrame)
{
GetUiElementsClicked();
}
}
void GetUiElementsClicked()
{
/** Get all the UI elements clicked, using the current mouse position and raycasting. **/
click_data.position = Mouse.current.position.ReadValue();
click_results.Clear();
ui_raycaster.Raycast(click_data, click_results);
foreach(RaycastResult result in click_results)
{
GameObject ui_element = result.gameObject;
Debug.Log(ui_element.name);
}
}
}
So, just drop into my "Menusscript.cs"?
But as a pattern, this is terrible for separating UI concerns. I'm currently rewiring EVERY separately-concerned PointerEventData click I had already working, and my question is, Why? I can't even find how it's supposed to work: to your point there IS no official guide at all around clicking UI, and it does NOT just drop-on-top.
Anyway, I haven't found anything yet which makes new input work easily on UI, and definitely not found how I'm going to sensibly separate Menuclicks from Activityclicks while keeping game & ui assemblies separate.
Good luck to us all.
Unity documentation for this issue with regard to Unity.InputSystem can be found at https://docs.unity3d.com/Packages/com.unity.inputsystem#1.3/manual/UISupport.html#handling-ambiguities-for-pointer-type-input.
IsPointerOverGameObject() can always return true if the extent of your canvas covers the camera's entire field of view.
For clarity, here is the solution which I found worked best (accumulated from several other posts across the web).
Attach this script to your UI Canvas object:
public class CanvasHitDetector : MonoBehaviour {
private GraphicRaycaster _graphicRaycaster;
private void Start()
{
// This instance is needed to compare between UI interactions and
// game interactions with the mouse.
_graphicRaycaster = GetComponent<GraphicRaycaster>();
}
public bool IsPointerOverUI()
{
// Obtain the current mouse position.
var mousePosition = Mouse.current.position.ReadValue();
// Create a pointer event data structure with the current mouse position.
var pointerEventData = new PointerEventData(EventSystem.current);
pointerEventData.position = mousePosition;
// Use the GraphicRaycaster instance to determine how many UI items
// the pointer event hits. If this value is greater-than zero, skip
// further processing.
var results = new List<RaycastResult>();
_graphicRaycaster.Raycast(pointerEventData, results);
return results.Count > 0;
}
}
In class containing the method which is handling the mouse clicks, obtain the reference to the Canvas UI either using GameObject.Find() or a public exposed variable, and call IsPointerOverUI() to filter clicks when over UI.
Reply to #Milad Qasemi's answer
From the docs you have attached in your answer, I have tried the following to check if the user clicked on a UI element or not.
// gets called in the Update method
if(Input.GetMouseButton(0) {
int layerMask = 1 << 5;
// raycast in the UI layer
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity, layerMask);
// if the ray hit any UI element, return
// don't handle player movement
if (hit.collider) { return; }
Debug.Log("Touched not on UI");
playerController.HandlePlayerMovement(x);
}
The raycast doesn't seem to detect collisions on UI elements. Below is a picture of the Graphics Raycaster component of the Canvas:
Reply to #Lowelltech
Your solution worked for me except that instead of Mouse I used Touchscreen
// Obtain the current touch position.
var pointerPosition = Touchscreen.current.position.ReadValue();
An InputSytem is a way to receive new inputs provided by Unity. You can't use existing scripts there, and you'll run into problems like the original questioner. Answers with code like "if(Input.GetMouseButton(0)" are invalid because they use the old system.

transporting a bool across scene's while changing it

For my game you need to complete a mini-game to unlock abilities. But I atually have no clue how to do it cause the value gets resetted to false whenever I load the main-level.
Code playerMovement:
static bool FistAttackEnabled;
void Update ()
{
if (FistAttackEnabled == true)
{
if (Input.GetMouseButtonDown(0))
{
Debug.Log("Attack");
PlayerMovement.SetFloat("Attacking", 1f);
HitArea.SetActive(true);
}
}
}
Code miniGame:
void Start()
{
FistAttackEnabled = Player.GetComponent<Player_Movement>().FistAttackEnabledPortable;
}
void Update()
{
if (SheepsAmountGuess == NeededAni)
{
FistAttackEnabled = true;
}
}
But this doesnt work. I tried making a portable bool (FistAttackEnabledStatic = FistAttackEnabled) Because you cant transport static bool value's across scripts, but this also didn't work. Does anyone have a clue how to do it?
PS: The code is bigger but it doesn't have anything to do with the attack.
Each time the scene is loaded the scripts are reloaded, so variables will go to their "default" state
You can avoid the destruction of the gameObject by usign DontDestroyOnLoad(this.gameObject);
Since the GameObject wont be deleted each time you reload the scene a new copy will be created to solve that you should use a singleton(look for it you will find information easyly)
Both things will solve the problem temporaly but once you close the game everything will go to the original state. You should use some method to save the progress, PlayerPrefs is a really easy way to do it.

Unity2d AudioClip leaves a lasting echo

I have a mono .wav file that I'm trying to play as a PlayOneShot() AudioClip in my game using an AudioSource. The sound plays perfectly in the Unity editor but for some reason echos and leaves a lasting reverb in the game that never goes away. It's called using just a simple PlaySound function from here:
public static void PlaySound (string clip) {
switch (clip) {
case "death":
audioSrc.PlayOneShot (deathSound);
break;
case "move":
audioSrc.PlayOneShot (move);
break;
case "success":
audioSrc.PlayOneShot (success);
break;
}
}
And this AudioSource handles all other sounds perfectly. I don't know what I can even try to change because I'm not messing with any AudioSource options to begin with. Does Unity2d not handle mono sounds well?
I solved my own problem using information found here: https://answers.unity.com/questions/451895/distorted-audio-using-playoneshot.html
The problem was that the PlayOneShot was being called multiple times because the condition to play the noise was met for an extended period of time. To handle this I set a bool to false after it plays one time, and check to see if the bool is true before playing the sound to begin with.
You could do something like this:
public AudioClip[] otherSounds;
AudioSource audioSource;
void Start(){
audioSource = GetComponent<AudioSource>();
}
void Update() {
if(){
audioSource.clip = otherSounds[Index];
}
}

Unity: Weird scene transition bug after adding a "Persistent-Scene" with a GameManager

I made a pretty basic 2D game to learn. I have 2 Scenes, and switching between them worked great. I used empty gameObjects as Start/Exit point of the Scene, so that the game would know to put player on point X after exiting through point X (for example exit outside house if I walk out the door).
Then I added a "Scene0", to use for persistent general scripts like GameManager, Sounds, Music, etc. With just one object called "Controller" that I DontDestroyOnLoad().
After adding this Scene and then just switching Scenes right away to my MainScene, all of a sudden the game starts acting really strange;
the first time I move from my MainScene (Scene1), to my secondary Scene (Scene2), it works fine, but then when I leave Scene2 to go back to Scene1, the player spawns in the middle of nowhere.
And this ONLY happens if I launch the game from my Persistent Scene.
I have no idea what is wrong, I don't add anything that interferes with my scene transitions, all I've added so far is playerHealth, for testing.
Scripts attached to my (persistent) Controller:
DDOL:
public class DDOL : MonoBehaviour {
// Use this for initialization
void Awake () {
DontDestroyOnLoad (gameObject);
}
}
GameManager:
public class GameManager : MonoBehaviour {
public static GameManager manager;
public int playerMaxHealth;
public int playerCurrentHealth;
void Awake(){
if (manager == null) {
manager = this;
} else if (manager != this) {
Destroy (gameObject);
}
}
// Use this for initialization
void Start () {
SceneManager.LoadScene("test_scene");
}
// Update is called once per frame
void Update () {
}
}
Scripts attached to my StartPoint:
PlayerStartPoint:
public class PlayerStartPoint : MonoBehaviour {
private PlayerController thePlayer;
private CameraController theCamera;
public Vector2 startDir;
public string pointName;
// Use this for initialization
void Start () {
thePlayer = FindObjectOfType<PlayerController> ();
if (thePlayer.startPoint == pointName) {
thePlayer.transform.position = transform.position;
thePlayer.lastMove = startDir;
theCamera = FindObjectOfType<CameraController> ();
theCamera.transform.position = new Vector3(transform.position.x, transform.position.y, theCamera.transform.position.z);
}
}
}
And finally ExitPoint:
LoadNewArea:
public class LoadNewArea : MonoBehaviour {
public string levelToLoad;
public string exitPoint;
private PlayerController thePlayer;
// Use this for initialization
void Start () {
thePlayer = FindObjectOfType<PlayerController> ();
}
void OnTriggerEnter2D(Collider2D other){
if (other.gameObject.name == "Player")
{
SceneManager.LoadScene(levelToLoad);
thePlayer.startPoint = exitPoint;
}
}
}
EDIT:
After moving all my DDOL gameObject to the Pre-Scene, it worked. So, I can assume the fault is inside Player or Cameras Start() functions since when they start in Scene1 they get called every time I enter the Scene (only DDOL).
I tried adjusting their Start()functions like follows:
Original camera:
void Start () {
Debug.Log("Starting camera");
if (!cameraExists) {
cameraExists = true;
DontDestroyOnLoad (gameObject);}
else{
Destroy (gameObject);
}
}
Changed Camera:
void Start () {
DontDestroyOnLoad (gameObject);
}
The exact same changes was made in Player.
Obviously this doesnt work because it creates a new Camera/Player every time I enter Scene1 (btw why does it not try to create them when I enter Scene2?, is it because they start in Scene1?). HOWEVER, the new player/camera do start at the correct position, and if I zoom out I can see the old player/camera at that same wrong position as before. So something weird happens when their Start() is called a second time it seems.
You've now mentioned that you had code something like this,
void Start () {
Debug.Log("Starting camera");
if (!cameraExists) {
cameraExists = true;
DontDestroyOnLoad (gameObject);}
else{
Destroy (gameObject);
}
}
Note that this is unfortunately just "utterly incorrect", heh :)
The issues you mention in the question (preload scenes etc) are just totally unrelated to the problem here.
In Unity if you have a character C that persists between scenes a, b, c as you load those scenes, you must kick-off C in it's own (perhaps otherwise empty) scene, you can not use "a" as a matter of convenience to kick off C.
The pattern is, in each of a, b, c just have a line of code like p = FindObjectOfType<Player>(); which runs when the scene loads, and position C as you wish.
Now, regarding your specific puzzle about the unusual behavior you are seeing.
I understand that you want to know why you are observing what you do.
It is a combination of confusion over the following issues: 1 - difference between Awake and Start, 2 - confusion over script execution order {but see below1} 3 - confusion about Destroy versus DestroyImmediate 4 - Not using Debug.Log enough, and not using gameObject.name in there (it's a common in Unity to be wildly confused about which object is talking in Debug.Log) 5 - where you mention you see the other object "off to the side", it's common to drastically confuse which one is which in such situations 6 - confusion between the computer programming concept of "instantiation" (ie, of a class or object) and "instantiating" (confusingly, it's the same word - utterly unrelated) game objects in nity.
If you fiddle around with all those issues, you'll discover an explanation for the behavior you're seeing!
But it doesn't amount to much; in Unity in the "C .. a b c" example you have to create C separately beforehand.
1 {aside, never fiddle with the script execution ordering system in Unity in an effort to solve problems; it's only there for R&D purposes; however it could in fact help you investigate the behavior at hand in this problem, if you are particularly keen to fully understand why you're seeing what you're apparently seeing}
Use the debugger. Have breakpoints at the relevant spots, like PlayerStartPoint.Start() and LoadNewArea.OnTriggerEnter2D() and check that they are executed
At the right time
The right number of times
With the expected values
This should make you see where things get out of hand.
If you use Visual Studio, install https://marketplace.visualstudio.com/items?itemName=SebastienLebreton.VisualStudio2015ToolsforUnity to be able to debug Unity from within Visual Studio.
If you are not using Visual Studio, you probably should.
Is player persistent between scenes (does he have DontDestroyOnLoad)? If no then this might be the reason - you can either try loading the scenes by using the additive mode or by instantiating the player on scene load in correct position.

AudioSource error

I am following directions out of a book to put together a simple FPS. However, I am not getting the same results as the book says I should.
The last thing I have been trying to do is add a sound to my "gun", so that this sound clip "pop.wav" plays when you fire.
Here is the working code before I took the steps to add the sound.
var projectile : Rigidbody;
var speed = 50;
function Update () {
if(Input.GetButtonDown("Fire1"))
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position +
Vector3(0,-1,0), transform.rotation);
instantiatedProjectile.velocity = transform.TransformDirection(Vector3(0,0,speed));
Physics.IgnoreCollision(instantiatedProjectile.collider, transform.root.collider);
}
}
So I did what the book said, and imported Pop.wav. Then it said to add two simple lines of code to the script.
var projectile : Rigidbody;
var speed = 50;
var pop : AudioClip;
function Update () {
if(Input.GetButtonDown("Fire1"))
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position +
Vector3(0,-1,0), transform.rotation);
instantiatedProjectile.velocity = transform.TransformDirection(Vector3(0,0,speed));
Physics.IgnoreCollision(instantiatedProjectile.collider, transform.root.collider);
audio.Play(pop);
}
}
Assets/FireProjectile.js(12,27): BCE0023: No appropriate version of 'UnityEngine.AudioSource.Play' for the argument list '(UnityEngine.AudioClip)' was found.
I would really appreciate some help on this.
I don't see you assigning the file to be played to the audio clip... something like
pop.clip = ...
A brief search on unity 3d mentions assigning the clip, and checking that it's ready to play.
Actually, you have overlooked something outside the script. You do in fact need to add an audio listener and an audio source in the scene. Then, you'll be able to hear the sound.
The reason for this is that unity allows for 3D sound, i.e. sound which has a location in 3D space, and will increase in volume if you go near it. The Audio Source is obviously where the sound is comming from, and Audio Listener is where your ears are, so to speak. Usually the audio listener is on your character. But you can rig it so that it is on any object, but then that object would have to get near the audio source, and not the character, which doesn't make any sense at all.
However, remember to only have ONE audio LISTENER at a time. Infinite audio SOURCES are allowed. :)
You need to assign your variable. It would be something like
pop.clip = (anything here)
You must use audio.PlayOneShot(pop) instead of audio.Play().
But if you want to use audio.Play(); you should know that it doesn't take AudioClip as argument.
Use following code and make sure you assign pop to Audio Clip property of Audio Source component by dragging pop sound from Project to inspector. audio.Play() uses default assigned Audio Clip in AudioSource.
var projectile : Rigidbody;
var speed = 50;
function Update () {
if(Input.GetButtonDown("Fire1"))
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position +
Vector3(0,-1,0), transform.rotation);
instantiatedProjectile.velocity = transform.TransformDirection(Vector3(0,0,speed));
Physics.IgnoreCollision(instantiatedProjectile.collider, transform.root.collider);
audio.Play();
}
}
#script RequireComponent(AudioSource)
Based on the Unity documentation (I've never used Unity so I can't be sure this is correct) I find that you probably want to do something like this.
var projectile : Rigidbody;
var speed = 50;
var pop : AudioClip;
...
if(Input.GetButtonDown("Fire1"))
{
var instantiatedProjectile : Rigidbody = Instantiate(projectile, transform.position +
Vector3(0,-1,0), transform.rotation);
instantiatedProjectile.velocity = transform.TransformDirection(Vector3(0,0,speed));
Physics.IgnoreCollision(instantiatedProjectile.collider, transform.root.collider);
audio.PlayOneShot(pop);
}
This is actually all you have to do. Assign a variable, and then tell it to play in the update function, whenever Fire1 is pressed, which is LMB by default.
PlayOneShot means the sound won't loop or anything, which I assume you want for gunshots or similar. Within the parentheses you simply name the sound you want to play.