I'm working with Unity 5.3 with C# as code editor
These are what I have :
I have 2 scenes in my project : home and options. I have bg object on both scenes. Both bg objects have Audio Source component, which contains same background music that play on awake. I don't use any codes for these background musics, I only click the Add Component button from Unity and add Audio Source.
This is what I want :
Options scene can toggle the background music on/off for all scenes. Therefore, there are btnOn and btnOff in Options scene.
This is my code in Audio Manager.cs :
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class AudioManager : MonoBehaviour {
public Button btnOn;
public Button btnOff;
// Use this for initialization
void Start () {
btnOn = GetComponent<Button>();
btnOff = GetComponent<Button>();
btnOn.onClick.AddListener(() => PlayAudio());
btnOff.onClick.AddListener(() => StopAudio());
}
void PlayAudio()
{
AudioSource.volume = 0.5f;
}
void StopAudio()
{
AudioSource.volume = 0f;
}
}
This is the problem :
I have this error : An object reference is required to access non-static member UnityEngine.AudioSource.volume. Maybe, this is because I don't write public AudioSource audioSource in my code. But, if I write this, I have to add another audio in Get Component box, and I will have double Audio Source in one scene. What should I do? Thanks
As you have a bg object in both scenes and as your AudioManager isn't marked as [DontDestroyOnLoad] (then I assume you also have one AudioManager in both scenes), it's Start() function will fire each time you load a scene.
So you can declare a private AudioSource and get a reference of it by finding your bg object in your scene and calling GetComponent on it :
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class AudioManager : MonoBehaviour
{
public Button btnOn;
public Button btnOff;
private AudioSource audioSource;
// Use this for initialization
void Start()
{
btnOn = GetComponent<Button>();
btnOff = GetComponent<Button>();
btnOn.onClick.AddListener(() => PlayAudio());
btnOff.onClick.AddListener(() => StopAudio());
audioSource = GameObject.Find("bg").GetComponent<AudioSource>();
}
void PlayAudio()
{
audioSource.volume = 0.5f;
}
void StopAudio()
{
audioSource.volume = 0f;
}
}
The error is raised because you need to assign the property on an individual object; the volume is not shared between sources. For this reason you will either need a field to assign in the inspector, or get a reference with GetComponent.
While using different scenes for handling options is not wrong, it is a tad clumsy; the current scene has to be unloaded (destroying all object not marked as DontDestroyOnLoad and information associated with them), after which the options are loaded, to then load the previous scene. While unloading the music most likely stops playing, which, after loading, starts at the beginning again. Not mention any settings on these objects are lost (volume, change of track, etc.).
The afore mentioned DontDestroyOnLoad can help since you make your changes on the same object, but you will have to deal with duplicates each time a scene is loaded where such an object exists... you can use the function OnLevelWasLoaded (documentation is a tad lacking at the moment) as a moment to determine which objects to destroy.
Another point is that you currently have public Button fields. This allows the for the assignment of them via the inspector, but that is rather moot as you overwrite them in Start (new value being the first button component assigned to the same object). I would make these fields private, or at least make sure they are not assigned. But I'm getting slightly of topic on how to keep settings persistent between scenes.
Here is some code to give you an idea, but be warned it is untested as I currently have no access to an environment to test it. This solution uses an object which is persistent between scenes. Keep in mind that any connections established to this object via the editor are lost after another scene has loaded.
public class AudioManager : MonoBehaviour
{
private AudioSource source;
// Called regardless of whether the object is enabled or not.
// Should not be called in a new scene.
private void Awake()
{
// Protect this object from being destroyed so its volume
// is maintained between scenes.
DontDestroyOnLoad(this.gameObject);
source = GetComponent<AudioSource>();
}
public void DestroyPossibleDuplicates()
{
AudioManager[] managers = FindObjectsOfType(typeof(AudioManager)) as AudioManager[];
foreach (AudioManager manager in managers)
{
// Use something to determine whether a manager is a duplicate.
// It must not be this, but have something in common; a name perhaps?
if ((manager != this) && (manager.gameObject.name == this.gameObject.name))
{
// Destroy the duplicates so their sound won't interfere.
Destroy(manager.gameObject);
}
}
}
private void BindToInterface()
{
Slider[] sliders = FindObjectsOfType(typeof(Slider)) as Slider[];
foreach (Slider slider in sliders)
{
// Determine whether the specified slider should have effect on this object.
// If the slider's name contains this object's name assume it should.
if (slider.gameObject.name.indexOf(this.gameObject.name)!=-1)
{
slider.onValueChanged.addListener((float value)=>
{
// In this case a slider is used for more control over the volume.
// Different elements require other logic to function.
source.volume = value;
});
}
}
}
// If my memory serves correct this method is only called on objects
// that were in the scene before it started loading.
// Just to be safe, don't have it do anything depending on this difference.
private void OnLevelWasLoaded()
{
DestroyPossibleDuplicates();
BindToInterface();
}
}
Related
I have a game wehre you are a cube and you dodge obstacles, I just implemented the ability to change the color of the cube like changing skin. I did that by assigning a different material to the player when he presses "2".
Here is the script:
void Update()
{
if (Input.GetKeyDown("2"))
{
Object.GetComponent<MeshRenderer>().material = Material1;
}
}
When you die the scene resets and when you win a new scene is loaded, I would like the game to remember the material change even after the scene is reset or a new scene is loaded. I have done some research and found something called "PlayerPrefs" and I have been playing around with it but nothing even got close to working and I didn't really understand what i was doing.
I really want to understand how this works becuase I know i will be using it alot when making games. Can someone help me understand?
Thanks.
Create one gameobject and apply this script
public class SavingMaterial : MonoBehaviour
{
public static SavingMaterial instance;
public Material mat;
void Awake()
{
if(instance == null)
{
instance = this;
DontDestroyOnLoad(base.gameObject);
}
else
{
Destroy(base.gameObject);
}
}
public void StoreMaterial(MeshRenderer mesh)
{
mat = mesh.material;
}
}
When you want to store material call this function like this
SavingMaterial.instance.StoreMaterial(you meshrenderer component);
and when you need this material just get from this class like this
material = SavingMaterial.instance.mat;
and note that if you quit the game you loose saved material as it is store in variable otherwise scene change and reset won't affect it.
you only save int,string and bool in playerprefs. you have to store values of materials into string and then save this string into playerprefs.
alternatively you can assign this material to local variable and set that script on Dontdestroyonload so it will not reset as scene is destroy or reset.
The states and animations not my own.
I have a prefab of a character that have Animator component attached to it. The character in the Hierarchy have Animator with some states and blends and also have some animation clips.
Now when i play/run a State of the Animator is that meaning i'm playing animation? Or the animation clips is something else ?
And why i can't playing the animation clips ? I'm not getting errors or exceptions it's just not playing it.
Here is a script that attached to this character and i also added to this character a Animation component. So now the character object have both Animator and Animation components.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class SwitchAnimations : MonoBehaviour
{
private Animator animator;
private int index = 0;
private static UnityEditor.Animations.AnimatorController controller;
private UnityEditor.Animations.AnimatorState[] an;
// Use this for initialization
void Start()
{
animator = GetComponent<Animator>();
an = GetStateNames(animator);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
animator.Play(an[index].name);
if (++index == an.Length)
index = 0;
}
}
private static UnityEditor.Animations.AnimatorState[] GetStateNames(Animator animator)
{
controller = animator ? animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController : null;
return controller == null ? null : controller.layers.SelectMany(l => l.stateMachine.states).Select(s => s.state).ToArray();
}
private void RollSound()
{
}
private void CantRotate()
{
}
private void EndRoll()
{
}
private void EndPickup()
{
}
}
Inside the variable an there are 9 states:
Aiming
Death_A
Use
Grounded
Roll
PickupObject
Reload_Rifle
Shoot_Rifle
Empty
When i click on A key it's playing the states one by one each time i click on A Everything is working fine i can play the 9 states by clicking on A.
what i don't understand with the States is inside the editor when i'm in the hierarchy selecting the character and in the menu make: Window > Animator i see the 9 states on the LegsLayer:
But then if i click on state Grounded double click i see a blend tree and i'm not sure if this is also states or maybe this is animation clips ? Then how can i get access to them ?
Ok this is one part i don't understand yet about the Animator and what and how i can play with it.
The second part is related to AnimationClip/s and why i can't play them ?
The script for animations clips:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
public class SwitchAnimations : MonoBehaviour
{
private Animation animation;
private AnimationClip[] animationClips;
// Use this for initialization
void Start()
{
animation = GetComponent<Animation>();
animationClips = AnimationUtility.GetAnimationClips(gameObject);
foreach (AnimationClip clip in animationClips)
{
animation.AddClip(clip, clip.name);
}
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
animation.clip = animationClips[6];
animation.Play(animationClips[6].name);
}
}
private void RollSound()
{
}
private void CantRotate()
{
}
private void EndRoll()
{
}
private void EndPickup()
{
}
private void FootStep()
{
}
}
And in the variable animationClips i have 9 animations:
Rifle_Aiming_Idle
Rifle_Aiming_Walk_F_RM
Rifle_Aiming_Walk_B_RM
Rifle_Aiming_Walk_L_RM
Rifle_Aiming_Walk_R_RM
Rifle_Idle
Rifle_Walk_F_RM
Rifle_Run_F_RM
Rifle_Sprint_F_RM
When i'm running the game and clicking on A this time i see in the Inspector inside the Animation component all the animation clips and in Animation i see the animation clip to play.
I'm not getting any errors or exceptions but it's just not playing it. The character stay still idle. Even if i choose another animation clip index to play 5 or 3 or 1 nothing is playing.
I wonder why it's not playing the animations when i click A. In this case animation number 6 from the List but still not playing.
The Animation component is a legacy component, kept in Unity to ensure backward compatibility with older projects, so you should never use it for animations.
Basically, the system works this way:
First of all, you need an Animator Controller asset. This Animator is a Finite State Machine (with substates etc.), and every single state can have an Animation Clip (you assign it via script of via Inspector in the Motion field). When the FSM enters a specific state, it will play the Animation Clip assigned.
Clearly the Animator, in order to be used by a game object, has to be assigned to that object via the Animator component.
Animation Clips are assets which contain the actual animation, they're never assigned as components to game objects, but instead they must be referenced by an Animator to be played, as I said before.
Blend trees are used to blend different animations in a seamless way by using linear interpolation. They're a "special" state of the Animator, which has multiple Animation Clips that you can interpolate to display transitions from a state to another, for example when you need to change the animation among running forward, running left and running right.
The argument is very broad, you can start to get into it by reading the official documentation about Animators, Animation Clips and Blend Trees here:
https://docs.unity3d.com/Manual/AnimationSection.html
I have a particle effect that I would like to trigger to play, and then stop. I'm sure this is an easy fix that I am over looking.
The particle can instantiate and play, but this obviously leaves overhead and particles that are active in the hierarchy when they don't need to be.
public void EmitFX(ParticleSystem particle)
{
Instantiate(particle, particlePos, Qauternion.identity)
}
I would like to use methods within ParticleSystem but am running into some problems. I have been using the manual and am still running into a block. I've googled this up and down, based on problems others had I changed my code to the following. It still does not work and is now a monster based on hacks other people found useful :/
public void EmitFX(ParticleSystem particle)
{
particle = particle.GetComponent<ParticleSystem>();
particle.transform.position = ballPos;
var em = particle.emission;
em.enabled = true;
particle.Play();
}
Here is a s/c of a particle in the Inspector.
First of all, not sure what the particle = particle.GetComponent<ParticleSystem>(); line is supposed to do? The particle variable is the ParticleSystem provide to your EmitFX() method so no need to call for this.
My guess is you have some references problems in your script (some variables referring to your prefab, then what you instantiate overrides this reference, ...) so I wrote you a "cleaner" version of your code (by merging your two scripts):
#region Attributes
[SerializeField]
private ParticleSystem particle;
private ParticleSystem generatedParticle;
#endregion
#region MonoBehaviour
protected void Start()
{
generatedParticle = null;
}
protected void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
EmitFX(particle);
}
}
#endregion
public void EmitFX(ParticleSystem a_Particle)
{
if(generatedParticle == null)
{
generatedParticle = Instantiate(particle, particlePos, Qauternion.identity);
}
generatedParticle.transform.position = ballPos;
generatedParticle.Play();
// You can set a fixed duration here if your particle system is looping
// (I assumed it was not so I used the duration of the particle system to detect the end of it)
StartCoroutine(StopFXAfterDelay(generatedParticle.main.duration));
}
private IEnumerator StopFXAfterDelay(float a_Delay)
{
yield return new WaitForSeconds(a_Delay);
generatedParticle.Stop();
}
What it does is it store the instantiated particle in a variable so it can access it later and remember it has been generated. Also I added a coroutine to deactivate it at the end of the effect.
Hope this helps,
I know this is an old question but I am going to place here what worked for me since the problem persists in the later versions of Unity.
Instead of just calling: particle.Play() first stop the particle system like the code below.
particles.Stop();
if (particles.isStopped)
{
particles.Play();
}
Where 'particles' is a component of type "ParticleSystem".
I'm creating a project using the Gear VR, where you can rotate an object and display information based on the swipe and tap controls on the side of the headset.
Everything works great, I can rotate and select stuff when I use the touchpad on the side of the Gear VR, but when I change scenes and return to the main menu, and then go back into the scene I was just on, the functionality stops working.
I am using this script I've made:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System;
public class GearVRTouchpad : MonoBehaviour
{
public GameObject heart;
public float speed;
Rigidbody heartRb;
void Start ()
{
OVRTouchpad.Create();
OVRTouchpad.TouchHandler += Touchpad;
heartRb = heart.GetComponent<Rigidbody>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
SceneManager.LoadScene("Main Menu");
}
}
void Touchpad(object sender, EventArgs e)
{
var touches = (OVRTouchpad.TouchArgs)e;
switch (touches.TouchType)
{
case OVRTouchpad.TouchEvent.SingleTap:
// Do some stuff
break;
case OVRTouchpad.TouchEvent.Up:
// Do some stuff
break;
//etc for other directions
}
}
}
I've noticed that when I start my game, an OVRTouchpadHelper is created. I don't know if that has anything to do with my problem.
The error I am getting is:
MissingReferenceException: The object of type 'GearVRTouchpad' has
been destroyed but you are still trying to access it. Your script
should either check if it is null or you should not destroy the
object.
BUT I have not referenced this script anywhere else.
When I check my scene in play mode, the script is still there with the variable assignments still present.
Any help would be great!
OVRTouchpad.TouchHandler is a static EventHandler (so it will persist through the lifetime of the game). Your script is subscribing to it when it's created but isn't unsubscribing when it's destroyed. When you reload the scene the old subscription is still in the event but the old GearVRTouchpad instance is gone. This will result in the MissingReferenceException next time the TouchHandler event fires. Add this to your class:
void OnDestroy() {
OVRTouchpad.TouchHandler -= Touchpad;
}
Now, whenever a GameObject with the GearVRTouchpad behaviour is destroyed, the static event in OVRTouchpad will no longer have a reference to it.
I've been working on a simple 2D game in unity and it just has three scenes, the start scene, the game scene, and the game over scene. I want to display the score from the game in the game over screen. I created a score manager game object in the game scene that uses the DontDestroyOnLoad() function to carry it over into the game over screen and I gave it access to the score which is managed by the game manager. I've been debugging my code and the score is translated over into the score manager and is maintained when the game over screen loads, but for some reason it won't let me update the score text object. Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour {
public static ScoreManager Instance;
private GameController gameController;
private int scoreInstance;
private Text scoreText;
// When scene is first loaded
void Awake() {
this.InstantiateController();
}
// Use this for initialization
void Start () {
GameObject gameControllerObject = GameObject.FindWithTag("GameController");
if (gameControllerObject != null)
{
gameController = gameControllerObject.GetComponent<GameController>();
}
GameObject scoreTextObject = GameObject.FindWithTag("ScoreText");
if (scoreTextObject != null)
{
scoreText = scoreTextObject.GetComponent<Text>();
}
scoreInstance = 0;
scoreText.text = "";
}
// Update is called once per frame
void Update () {
scoreInstance = gameController.score;
Debug.Log("Score: " + scoreInstance.ToString());
scoreText.text = scoreInstance.ToString();
}
private void InstantiateController ()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this);
}
else if (this != Instance)
{
Destroy(this.gameObject);
}
}
}
So I tried to programmatically gather the "score text" ui component in the start function because I figured I can't just make it public and drag in the text component because the score manager is actually in a different scene than the score text object. I also tried adding this whole bit of code to gather the text component into the update function so that it can do that when the score manager is actually a part of game over screen. Nothing seems to work and I have no idea why. Can anybody please help me with this? Also I keep getting a "NullReferenceException: Object reference not set to an instance of an object" error. Thanks in advance for any help.
Unity Start function is only called the first time the script is enabled, i.e. not every time the scene changes for a DontDestroyOnLoad object.
So if you need to wire up some changes after a scene change, you need to either detect the scene change, or have an object that starts in that scene trigger the code you want to run.
Having another object on the new scene trigger things is easy and pretty fool-proof, but there's a builtin function you can add to your other objects:
void OnLevelWasLoaded(int currentLevel)
{
}
This will be called on level changes, and give you the level's number (not name sadly). However, the above is deprecated and they want you to use Unity's SceneManager, so the proper way to set this up is now:
Unity 5 OnLevelWasLoaded?
Start()
{
SceneManager.sceneLoaded += this.OnLoadCallback;
}
void OnLoadCallback(Scene scene, LoadSceneMode sceneMode)
{
// you can query the name of the loaded scene here
}