Audio doesn't play when instantiate new object - c#

I'm facing audio problems in my project. There are three paddle game objects and one cube. Cube has Rigidbody2d or BoxCollider2d. And there is also a button script attach to cube which has button function when we click on button cube Kinematic becomes false and drop on the paddle. When it's collide with any paddle cube destroy and instantiate again with new prefab. When Cube is falling sound is play and cube is destroy. New cube is instantiate when again click on button then error came.
MissingReferenceException: The object of type AudioSource 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.
Scripts on all paddles:
public class Paddle : MonoBehaviour
{
[SerializeField]
private AudioSource source;
[SerializeField]
private AudioClip hit;
[SerializeField]
private BoxCollider2D collide;
[SerializeField]
private GameObject Clone;
void Awake()
{
collide.isTrigger = true;
}
void OnTriggerEnter2D(Collider2D target)
{
source.PlayOneShot(hit);
Destroy(target.gameObject);
Instantiate(Clone, new Vector3(0f, 4.51f, 0f), Quaternion.identity);
}
}
Cube Script:
public class Cube : MonoBehaviour
{
[SerializeField]
private AudioSource source;
[SerializeField]
private Rigidbody2D body;
[SerializeField]
private AudioClip play;
private Button bt;
private float pos;
public bool check;
void Start()
{
bt = GameObject.FindGameObjectWithTag("Button").GetComponent<Button>();
bt.onClick.AddListener(Fire);
body.isKinematic = true;
}
void Update()
{
if (check)
{
return;
}
Vector3 temp = transform.position;
pos = Camera.main.ScreenToWorldPoint(Input.mousePosition).x;
temp.x = Mathf.Clamp(pos, -6.25f, 6.25f);
body.position = temp;
}
public void Fire()
{
GameObject.FindGameObjectWithTag("Player").GetComponent<Cube>().check = true;
GameObject.FindGameObjectWithTag("Player").GetComponent<Rigidbody2D>().isKinematic = false;
source.PlayOneShot(play);
}
}
Cube image:
Paddles image:
New Export package:
https://drive.google.com/file/d/0B1H5fdK2PJAnSXVPdmE5Z3J1SUU
Problem in Video:
https://drive.google.com/file/d/0B1H5fdK2PJAnYzRfVnlQT1FyTlE

Your linked package works fine, it does play the sound every time I launch and destroy a cube.
Nonetheless, you should remove the method on destroy. Unity does not throw any error nor warning when a non-persistent listener is null, kinda weird but this is how it is. You should remove it manually:
void OnDestroy(){
bt.onClick.RemoveListener (Fire);
}
But your package does not throw any error when I run it.
Though, I would rethink your approach, instead of the cube assigning its Fire method to the Button event, I would have a script on the button containing the Fire method as well as the AudoiSource and clip. Then on Start, the Cube would pass itself so that the button could access its Cube and Rigidbody2D component.
Best would be to pass a class that contains those are members:
public class CubeArgument{
public readonly Rigidbody2D rg = null;
public readonly Cube cube = null;
public CubeArgument(Rigidbody2D rg, Cube cube){
this.rg = rg;
this.cube = cube;
}
}
then here goes your Cube start method:
void Start () {
bt = GameObject.FindGameObjectWithTag ("Button");
bt.GetComponent<ButtonController> ().Init(new CubeArgument(body, this));
body.isKinematic = true;
}
The ButtonController reference could even be made static since there is only one for the whole level.
and then on the button you have a ButtonController:
public class ButtonController : MonoBehaviour{
Cube currentCube = null;
Rigodbody2D currentRig = null;
public void Init(CubeArgument ca){
currentRig = ca.rg;
currentCube = ca.cube;
}
public void Fire(){
if(currentCube != null){ currentCube.check = true; }
if(currentRig != null) { currentRig.isKinematic = false; }
}
}
Fire is passed as listener to the Button onClick and this is it.

your problem can be from many different places. my guesses:
1)problem with your build: rebuild your code or export all of your package to new project and retest.
2)your target framework: latest version of unity(i have 5.1) just support to .net 3,5 and unity 4.x supports 2,5 i think. so chek your target framework to not to use functions that is not functional in your version
3)settings of your platform that your editor is running on: it can be volume of your platform to many other settings, first option to know that is run your project on other machine (maybe its a unity bug that part of code is not good for your hardware or driver on platform)

Edit: OP was using version 5.1.1. This error does not repeat after Unity 5.2.
Tried your package and it is working fine!
No errors. Audio played when instantiated.
Are you still facing errors? Weird, dude. Sounds like something is not clean enough.
Maybe there is some issue with your build.
Few things you could try:
1- Try restarting your Unity. Maybe this will force Clean things.
2- Create a new project and import your own package to test it!
3- I'm using Unity 5.3.0f4 what Unity version are you using? Try an update.
If none of above works, there is something wicked going on with your AudioSource reference and I can't help you with my actual knowledge. But still we can try to do different approaches for that.
Instead of physically referenced it (Dragging and dropping), do it at the start of your script.
Desperate approach 1
On Cube.cs:
First remove [SerializeField] located above of private AudioSource source; and add the following line on the Start() method:
source = gameObject.GetComponent<AudioSource> ();
Practically the same, but now the script is referencing the own object Audio Source for you. Do the same approach with Paddle.cs. [remember to check the audio name when applying the approach for Paddle script. On paddle the audio name is PlayOneShot (hit) not PlayOneShot (play)].
If this still trigger some error. Let's try another approach.
Desperate approach 2
On Cube.cs:
Remove/Comment the following line on Fire() method:
source.PlayOneShot (play);
Add the following line to Fire() method:
gameObject.GetComponent<AudioSource> ().PlayOneShot (play);
This will get your actual AudioSource on the go. Do the same approach on Paddle.cs [remember to check the audio name when applying the approach for Paddle script. On paddle the audio name is PlayOneShot (hit) not PlayOneShot (play)].

Related

Unity2D I can't change the TrailRenderer material of a cloned object

In the game I'm making, I clone a Ball object whenever a "split ball" power-up is acquired. Everything works as intended except the TrailRenderer material. The Ball prefab has a default material used for TrailRenderer in the beginning. This, however, changes when the ball hits an object controlled by the player (which is called a "bumper"). The material changes work perfectly on collision. Here is the shortened script for the Ball object:
[NonSerialized]
public TrailRenderer trailRenderer;
[SerializeField]
private Material defaultTrailMaterial;
void Start()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (CollidedObjectIsABumper(collision))
{
// SetTrailColorToBumperColor() is called along other calculations
}
}
private bool CollidedObjectIsABumper(Collision2D collision)
{
return collision.gameObject.CompareTag("Bumper");
}
private void SetTrailColorToBumperColor()
{
trailRenderer.material = lastCollidedBumper.material;
}
I discarded a few things for clarity.
Now, here is the shortened script for the split ball power-up:
void OnTriggerEnter2D(Collider2D collision)
{
if (!CollidedWithABall(collision))
return;
Ball mainBall = collision.gameObject.GetComponent<Ball>();
// I added this part as a desperate attempt, didn't work.
mainBall.trailRenderer.material = mainBall.lastCollidedBumper.material;
Ball splitBall = Instantiate(mainBall, pos, Quaternion.identity);
splitBall.tag = "Split Ball";
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
Destroy(gameObject);
}
private bool CollidedWithABall(Collider2D collision)
{
return collision.gameObject.CompareTag("Ball") || collision.gameObject.CompareTag("Split Ball");
}
pos is a Vector3 variable that is declared in the cut portion. After getting the power-up, this is how the game scene looks like:
None of the balls touched a bumper after getting the power-up. I expect the split ball to have a red trail but it doesn't. I'm sure I'm missing something with Instantiate() but I don't know what.
One thing I assumed was that Instantiate() used the prefab of the main ball, in which case the trail would have a neutral color, but I added an assignment statement after Instantiate() so I don't think that's the only problem here. For reference, the split ball DOES change its trail color when it hits a bumper.
Thank you and please let me know if you need additional information.
As mentioned this is a timing issue.
You have
void Start()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
which overwrites your material.
The call of Start on new instantiated objects is delayed until the beginning the next frame for the purpose of being able to still change some field values right after Instantiate before Start is called.
So you set the material in
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
But then it is later changed again by Start.
Awake however is called right away.
So either you change it to
void Awake()
{
trailRenderer = GetComponent<TrailRenderer>();
trailRenderer.material = defaultTrailMaterial;
}
so this is done first and then the line
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
can correctly overwrite the material or alternatively you could also make
public Material defaultTrailMaterial;
and instead of directly setting the material in
splitBall.GetComponent<TrailRenderer>().material = mainBall.lastCollidedBumper.material;
you rather only set
splitBall.GetComponent<YourComponent>().defaultTrailMaterial = mainBall.lastCollidedBumper.material;
and then let Start do the job as currently.

Getting a NullReferenceException when trying to Instantiate a GameObject

I'm programming a JRPG off of a tutorial online to try and learn Unity and C# code. This is a program for creating a menu of enemy units to attack:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
//using UnityEngine.Experimental.UIElements;
public class CreateEnemyMenuItem : MonoBehaviour {
[SerializeField]
private GameObject targetEnemyUnitPrefab;
[SerializeField]
private Sprite menuItemSprite;
[SerializeField]
private Vector2 initialPosition, itemDimensions;
[SerializeField]
private KillEnemy killEnemyScript;
private void Awake()
{
GameObject enemyUnitsMenu = GameObject.Find("EnemyUnitsMenu");
GameObject[] existingItems = GameObject.FindGameObjectsWithTag("TargetEnemyUnit");
Vector2 nextPosition = new Vector2(this.initialPosition.x + (existingItems.Length * this.itemDimensions.x), this.initialPosition.y);
GameObject targetEnemyUnit = Instantiate(this.targetEnemyUnitPrefab, enemyUnitsMenu.transform) as GameObject;
targetEnemyUnit.name = "Target" + this.gameObject.name;
targetEnemyUnit.transform.localPosition = nextPosition;
targetEnemyUnit.transform.localScale = new Vector2(0.7f, 0.7f);
targetEnemyUnit.GetComponent<Button>().onClick.AddListener (() => selectEnemyTarget());
targetEnemyUnit.GetComponent<Image>().sprite = this.menuItemSprite;
killEnemyScript.menuItem = targetEnemyUnit;
}
public void selectEnemyTarget()
{
}
}
At line 29 (where targetEnemyUnit is instantiated) during game tests I get a NullReferenceException error which I figure means Instantiate isn't working (it also highlights here but doesn't in Visual Studio). I'm working largely off of a tutorial but this matches and I'm not sure what exactly I'm doing wrong here.
Either targetEnemyUnitPrefab was not set in the inspector, in which case you just need to click on the GameObject that contains this script, in the inspector drag and drop your prefab into 'targetEnemyUnitPrefab' or enemyUnitsMenu was not tagged properly, in which case click on the 'GameObject' that is supposed to be tagged and make sure in the top right of the inspector it is tagged as 'EnemyUnitsMenu'
note when you create a new tag and have a game object selected it doesn't apply the tag, you have to go back to the game object and select it from the bottom of the drop down.
GameObject targetEnemyUnit = Instantiate(this.targetEnemyUnitPrefab, enemyUnitsMenu.transform) as GameObject;
okay you have a private object as a prefab ->
private GameObject targetEnemyUnitPrefab <-
and you didnt put anything in that prefab so its nothing. so you are getting nullreference exception because of it.
make a public game object prefab and drag your prefab on it.
Or make something like this before instatiate the object ~ targetEnemyUnitPrefab= GameObject.Find("targetEnemyUnitPrefab");~ or whatever tag it has.
have a nice day.

Understanding Unity's GameObject.Find(), GetComponent() and objects recycling

New to unity.
So I created a simple a simple muzzle flash particle animation that is supposed to be displayed on enemies gun when the player gets close to him, simulating a shot without the actual bullet. However I get a null reference exception in this part muzzleFlash.Play(); I believe it's because I am not actually getting the muzzle flash component in the start function with the code I have, actually I know that is it after going to in to debug mode I found out. I am having a really hard time figuring out how to access that component. Below is my code and I'm also posting a picture of my hierarchy. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StaticShootingEnemy : MonoBehaviour
{
[SerializeField] private float _range = 12f;
private Transform _player;
private bool _alive;
private float _distance;
private ParticleSystem muzzleFlash;
// Use this for initialization
void Start()
{
_player = GameObject.Find("Player").transform;
_alive = true;
muzzleFlash = (ParticleSystem)this.gameObject.GetComponent("muzzleFLash");
}
// Update is called once per frame
void Update()
{
_distance = Vector3.Distance(this.transform.position, _player.transform.position);
if (_alive && _distance < _range)
AttackPlayer();
}
private void AttackPlayer()
{
//Turning enemy to look at player
transform.LookAt(_player);
Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.SphereCast(ray, 0.75f, out hit))
{
//TODO: Fix enemy shooting fast when gettting close to him.
GameObject hitObject = hit.transform.gameObject;
if (hitObject.GetComponent<PlayerController>())
{
muzzleFlash.Play();
Debug.Log("Player Hit!");
}
else
muzzleFlash.Stop();
}
}
public void SetAlive(bool alive)
{
_alive = alive;
}
}
You probably have an object "muzzleFlash" as child to object your script attached to. So, in this case you'd better have a reference to your ParticleSystem object that is called muzzleFlash.
[SerializeField] private ParticleSystem muzzleFlash; // drag and drop your ParticleSystem muzzleFlash in inspector
or at least you could find that muzzleFlash like this
GameObject muzzleFlashObj = GameObject.Find("muzzleFlash");
ParticleSystem muzzleFlash = muzzleFlashObj.GetComponent<ParticleSystem>();
In your case it's null because there is probably no component that is called MuzzleFlash on that object. The component you want to get is ParticleSystem.
What component is the staticshootingenemy script on? if it is not on the same component as the particle system then its not finding it because this.gameObject.GetComponent("muzzleFLash") does not exist on that component. You can use GameObject.Find("muzzleFLash") to search for the particle system.
So back to your comment, you could implement something like a pool for your muzzle flashes.
public class MuzzleFlashEffect : MonoBehaviour
{
[SerializeField] private ParticleSystem particleEffect;
private Queue<MuzzleFlashEffect> poolQueue;
public void SetPoolQueue(Queue<MuzzleFlashEffect> queue)
{
poolQueue = queue;
}
public void Play()
{
StartCoroutine(Playing());
}
private IEnumerator Playing()
{
particleEffect.Play();
while (particleEffect.isPlaying)
{
yield return null; // wait until particle animation is done, then recycle effect
}
particleEffect.Stop();
poolQueue.Enqueue(this); // recycle this effect
}
// you can do the same thing for Animation as well, or even write some abstract PoolableVFX class that would be usefull for Animation , ParticleSystems etc..
}
//assume you have some game controller that manage what is going on in the scene
public class GameController : MonoBehaviour
{
[SerializeField] private MuzzleFlashEffect muzzleFlashPrefab;
private Queue<MuzzleFlashEffect> poolQueue = new Queue<MuzzleFlashEffect>(10); // 10 is enough i guess and it's good to set it at instantiation to avoid memory fragmentation
private MuzzleFlashEffect GetMuzzleFlash(Vector3 pos, Quaternion rot)
{
MuzzleFlashEffect muzzleFlash;
// if we already have some effects, then play them, otherwise make a new one and recycle it then
if (poolQueue.Count > 0)
{
muzzleFlash = poolQueue.Dequeue();
}
else
{
muzzleFlash = Instantiate(muzzleFlashPrefab);
muzzleFlash.SetPoolQueue(poolQueue);
}
muzzleFlash.transform.position = pos;
muzzleFlash.transform.rotation = rot;
return muzzleFlash;
}
void Update()
{
// your fancy logic ...
GameObject mutantGunEnd = new GameObject("mutant");
//assume that here you want your muzzle flash effect, so you do:
var muzzleFlash = GetMuzzleFlash(mutantGunEnd.transform.position, mutantGunEnd.transform.rotation); // or you might want to pass mutantGunEnd.transform.forward instead, it depends...
muzzleFlash.Play();
// your fancy logic ...
}
}
So, in this case you have only as many instance of ParticleEffect as you need and saving some resources. You could also create a universal generic pool for any type of object you want to recycle. (you want to recycle instead of instantiation, cuz Instantiation is cpu expensive).
M.b this is a bit overkill here, but i just wanted to share how would i think about this here

How to have a powerup have a slight chance of spawning when killing an enemy?

so we have a class assignment and pretty much we are doing the Survival Shooter project from the Unity tutorials. I've managed to make health packs and little boxes that give you speed when you pick them up, but now I want the power-ups to have a slight chance of dropping when enemies die. Can someone help me out? I'm not really asking for entire code, I have some down below:
public float percentDrop = 50f;
public GameObject HealthPack;
void Awake()
{
HealthPack = GetComponent<GameObject>();
}
public void TakeDamage (int amount, Vector3 hitPoint) { if(isDead) return;
enemyAudio.Play ();
currentHealth -= amount;
hitParticles.transform.position = hitPoint;
hitParticles.Play();
if(currentHealth <= 0)
{
Death ();
}
}
void Death ()
{
isDead = true;
capsuleCollider.isTrigger = true;
anim.SetTrigger ("Dead");
enemyAudio.clip = deathClip;
enemyAudio.Play ();
var randChance = Random.Range(0f, 100f);
if (randChance < percentDrop)
{
//GameObject.Healthpack.setActice(true);
}
}
I'm not too sure how to make the Game Object spawn when they die, can someone help me out?
Create a "Health Pack" prefab from the editor. If you need more information about prefabs, the information is out there on the web. To create a prefab, simply drag and drop a GameObject from the scene to your project. Its name in the scene will become blue. You can delete it from the scene and it will still be in the project.
Drag the prefab from the project to the HealthPack slot in the inspector of your ennemy. The ennemy will now have a reference to the prefab.
When you want to create a new health pack, you can use the Object.Instantiate static method as specified by rutter. Here is the official doc. When you instantiate your new Health Pack, I guess you want it to appear where the ennemy is, wich means you'll want to use one of the method's overloads which takes a Vector3D position as a parameter, which will most likely be transform.position. Since those methods also ask for a Quaternion, just pass in the Quaternion.identity constant.
This is how your code could look:
if (randChance < percentDrop)
{
Object.Instantiate(HealthPack, transform.position, Quaternion.identity);
}
Another problem, as mentioned in my comment, is the Awake function: HealthPack = GetComponent<GameObject>();.
The HealthPack prefab should be assigned to the ennemy from the inspector. That line in the Awake function will assign your ennemy's GameObject component to HealthPack, which is not desirable in the current context.
I hope this helps!

Unity Networking: can't spawn scene objects on all clients

I can't really figure out how to solve my problem. I have been looking for an answer but I couldn't find anything.
I have a button in my scene that can be pressed both by client and host. When the button is pressed, it creates a cube in the scene. The problem is that: the cube can be created only by the host and the host is the only user that can see it and manipulate it.
My code is:
public class CreateCube : NetworkBehaviour {
GameObject cubo;
float lastCollisionTime=0;
float collisionTime=0;
void OnCollisionExit(Collision other) {
collisionTime = Time.time;
if (collisionTime - lastCollisionTime >1.5) {
CmdCreaCubo ();
lastCollisionTime = collisionTime;
}
}
}
}
[Command]
void CmdCreaCubo(){
GameObject cubo=Instantiate(Resources.Load("MyPrefabs\\Oggetti\\CubeGrasp")) as GameObject;
cubo.transform.position = new Vector3 (-5.88f, 7.51f, -19f);
cubo.name = "CubeGrasp";
NetworkServer.Spawn (cubo);
}
}
Could anyone help me please?
Thank you so much
Instead using simple Instantiate you should need to use Network.Instantiate
The given prefab will be instanted on all clients in the game.
Synchronization is automatically set up so there is no extra work
involved.

Categories