Unity Engine - Instantiate a prefab by collision - c#

I have a GameObject called Player.
Attached to Player there is a script component called Player ( same )
Inside the Player's script, I have a field called _weaponPrefab ( GameObject type )
In the Inspector, I can easily drag & drop any prefabs from my Prefab folder, inside the _weaponPrefab variable.
All good so far. What I want to archive is: to be able to add my Prefabs based on a Collision2D. So if my Player collides with a Prefab, let's say a Sword, the prefab of that sword will be automatically attached and insert into the _weaponPrefab field inside the Player script.
Below my Player script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField] private float _speed = 5.0f;
[SerializeField] private float _fireRate = 0.2f;
private bool _canFire = true;
[SerializeField] private GameObject _weaponPrefab; <- to populate runtime
// Use this for initialization
void Start ()
{
transform.position = Vector3.zero;
}
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.Space) && _canFire)
StartCoroutine(Shoot());
}
void OnTriggerEnter2D(Collider2D other)
{
//here i don't know how to continue.
}
public IEnumerator Shoot()
{
_canFire = false;
Instantiate(_weaponPrefab, transform.position + new Vector3(0, 1, 0), Quaternion.identity);
yield return new WaitForSeconds(_fireRate);
_canFire = true;
}
}
Edit: i just wrote a comment of where i don't know how to proceed.

There are many ways to accomplish what you desire.
My suggestion may be out of your comfort zone at first, but it will provide you with most flexibility and eventually easy of programming/ designing/ maintaining your game (in my opinion).
First make a scriptable object (what is a scriptable object and how do i use it?)
using UnityEngine;
using System.Collections;
using UnityEditor;
public class Item
{
[CreateAssetMenu(menuName = "new Item")]
public static void CreateMyAsset()
{
public GameObject prefab;
// you can add other variables here aswell, like cost, durability etc.
}
}
Create a new item (in Unity, Assets/new Item)
Then create a monobehaviour script which can hold the item. In your case let's name it "Pickup".
public class Pickup : MonoBehaviour
{
public Item item;
}
Finally in your player script, change your on TriggerEnter to:
void OnTriggerEnter2D(Collider2D other)
{
if(other.GetComponentInChildren<Pickup>())
{
var item = other.GetComponentInChildren<Pickup>().item;
_weaponPrefab = item.prefab;
}
}

When you instantiate a GameObject you need to pass basically 3 parameters (However not all of them are mandatory, check):
The prefab
The position
The rotation
Let's assume you have a placeholder in the hand or in the back of your character, to keep there the weapon once collected. This placeholder can be an empty gameobject (no prefab attached, but will have a transform.position component)
Now let's assume you have a list of weapons in the scene with their prefab and with a different tag each. Then you can do something like this:
GameObject weapon;
GameObject placeholder;
public Transform sword;
public Transform bow;
...
void OnTriggerEnter2D(Collider2D other)
{
//You can use a switch/case instead
if(other.gameObject.tag == "sword"){
Instantiate(sword, placeholder.transform.position, Quaternion.identity);
}else if(other.gameObject.tag == "bow"){
Instantiate(bow, placeholder.transform.position, Quaternion.identity);
}...
}

What I'll do is load the prefabs first in a Dictionary<string, GameObject> from the resources folder.
First I populate the dictionary with all the weapon prefabs in the Start method with the tag of the object as a key. Then in the OnTriggerEnter2D I check if the tag is in the dictionary then I instantiate it from there.
Take a look at the code :
private Dictionary<string, GameObject> prefabs;
// Use this for initialization
void Start () {
var sword = (GameObject) Resources.Load("Weapons/sword", typeof(GameObject));
prefabs.Add("sword", sword);
// Add other prefabs
};
void OnTriggerEnter2D(Collider2D other)
{
if (prefabs.ContainsKey(other.tag))
Instantiate(prefabs[other.tag]);
}
NOTE : your prefabs needs to be in the folder Assets/Resources/Weapons because I used Resources.Load("Weapons/sword", typeof(GameObject));

Related

Collision detection for individual objects in a list Unity3D

I want to have it so that a single game object from a list is destroyed when it hits the ground. So far I am able to spawn random prefabs in set spawn locations in the scene successfully, but I am having trouble implementing the collision detection for when they fall to the ground. I have tried both raycasting and OnCollisionEnter but neither is working, the collision is not being detected. I am missing something but not sure what. Below is my attempt with OnCollisionEnter:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectSpawner : MonoBehaviour
{
public List<GameObject> prefabObjects;
public List<GameObject> locations;
public List<GameObject> spawnedObjects;
void Start()
{
//create empty list of spawned objects
spawnedObjects = new List<GameObject>();
SpawnObject(RandomObjects());
}
// Update is called once per frame
void Update()
{
}
void SpawnObject(GameObject obj)
{
GameObject newObject = Instantiate(obj, transform.position, Quaternion.identity );
spawnedObjects.Add(newObject);
}
GameObject RandomObjects()
{
int rand = Random.Range(0, prefabObjects.Count);
return prefabObjects[rand];
}
void OnCollisionEnter(Collision collision)
{
for (var i = 0; i < spawnedObjects.Count; i++){
GameObject obj = spawnedObjects[i];
if (collision.collider.tag == "Ground"){
Debug.Log("Hit ground");
spawnedObjects.Remove(obj);
Destroy(obj);
}
}
}
Your void OnCollisionEnter(Collision collision) method is in the wrong place, you dont want the ObjectSpawner itself to collide but the instancianted obj's. Place a new script on your prefabs and implement OnCollisionEnter there. For the method to trigger, be sure to set up colliders and rigidbodies correctly. Your prefabs and the ground should have colliders and either one of these a non-kinematic rigidbody too.

There is no Rigidbody 2D attached to the game object but the script is trying to access it

Here is the code I'm using. It throws a MissingComponentException: There is no Rigidbody 2D attached to "Bird" game object but the script is trying to access it.
using System.Collections;
public class Bird : MonoBehaviour
{
public float UpForce; //Upward force of the "flap".
private bool _isDead = false; //Has the player collided with a wall?
private Animator _anim; //Reference to the Animator component.
private Rigidbody2D _rb2d; //Holds a reference to the Rigidbody2D component of the bird.
void Start()
{
//Get reference to the Animator component attached to this GameObject.
_anim = GetComponent<Animator> ();
//Get and store a reference to the Rigidbody2D attached to this GameObject.
_rb2d = GetComponent<Rigidbody2D>();
}
void Update()
{
//Don't allow control if the bird has died.
if (_isDead == false)
{
//Look for input to trigger a "flap".
if (Input.GetMouseButtonDown(0))
{
//...tell the animator about it and then...
_anim.SetTrigger("Flap");
//...zero out the birds current y velocity before...
_rb2d.velocity = Vector2.zero;
// new Vector2(rb2d.velocity.x, 0);
//..giving the bird some upward force.
_rb2d.AddForce(new Vector2(0, UpForce));
}
}
}
void OnCollisionEnter2D(Collision2D other)
{
// Zero out the bird's velocity
_rb2d.velocity = Vector2.zero;
// If the bird collides with something set it to dead...
_isDead = true;
//...tell the Animator about it...
_anim.SetTrigger ("Die");
//...and tell the game control about it.
GameControl.instance.BirdDied ();
}
}
How do I provide the reference it wants?
The GameObject this script is attached to should have a RigidBidy2D component (because the script is using it to apply forces to GameObject). You can either add a Rigidbody2D in inspector (choose the GameObject the script is attached to and use "Add Component" menu), or make it so the script adds a component automatically when you attach it to GameObject by adding a RequireComponent attribute like this:
[RequireComponent(typeof(Rigidbody2D))]
public class Bird : MonoBehaviour
{
//your Bird class code here
}
See also these questions - that's the same problem.

Unity3D playing sound when Player collides with an object with a specific tag

I using Unity 2019.2.14f1 to create a simple 3D game.
In that game, I want to play a sound anytime my Player collides with a gameObject with a specific tag.
The MainCamera has an Audio Listener and I am using Cinemachine Free Look, that is following my avatar, inside the ThridPersonController (I am using the one that comes on Standard Assets - but I have hidden Ethan and added my own character/avatar).
The gameObject with the tag that I want to destroy has an Audio Source:
In order to make the sound playing on the collision, I started by creating an empty gameObject to serve as the AudioManager, and added a new component (C# script) to it:
using UnityEngine.Audio;
using System;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public Sound[] sounds;
// Start is called before the first frame update
void Awake()
{
foreach (Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
// Update is called once per frame
public void Play (string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
And created the script Sound.cs:
using UnityEngine.Audio;
using UnityEngine;
[System.Serializable]
public class Sound
{
public string name;
public AudioClip clip;
[Range(0f, 1f)]
public float volume;
[Range(.1f, 3f)]
public float pitch;
[HideInInspector]
public AudioSource source;
}
After that, in the Unity UI, I went to the Inspector in the gameObject AudioManager, and added a new element in the script that I named: CatchingPresent.
On the Third Person Character script, in order to destroy a gameObject (with a specific tag) when colliding with it, I have added the following:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
}
}
It is working properly as that specific object is disappearing on collision. Now, in order to play the sound "CatchingPresent" anytime the Player collides with the object with the tag, in this case, Present, I have tried adding the following to the if in the OnCollisionEnter:
FindObjectOfType<AudioManager>().Play("CatchingPresent");
But I get the error:
The type or namespace name 'AudioManager' could not be found (are you
missing a using directive or an assembly reference?)
AudioManager.instance.Play("CatchingPresent");
But I get the error:
The name 'AudioManager' does not exist in the current context
As all the compiler errors need to be fixed before entering the Playmode, any guidance on how to make the sound playing after a collision between the player and the gameObject with the tag Present is appreciated.
Edit 1: Assuming that it is helpful, here it goes the full ThirdPersonUserControl.cs:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Characters.ThirdPerson
{
[RequireComponent(typeof (ThirdPersonCharacter))]
public class ThirdPersonUserControl : MonoBehaviour
{
public Text countText;
public Text winText;
private int count;
private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
private Transform m_Cam; // A reference to the main camera in the scenes transform
private Vector3 m_CamForward; // The current forward direction of the camera
private Vector3 m_Move;
private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input.
private void Start()
{
count = 20;
SetCountText();
winText.text = "";
// get the transform of the main camera
if (Camera.main != null)
{
m_Cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
// we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
}
// get the third person character ( this should never be null due to require component )
m_Character = GetComponent<ThirdPersonCharacter>();
}
private void Update()
{
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
}
// Fixed update is called in sync with physics
private void FixedUpdate()
{
// read inputs
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
// calculate move direction to pass to character
if (m_Cam != null)
{
// calculate camera relative direction to move:
m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v*m_CamForward + h*m_Cam.right;
}
else
{
// we use world-relative directions in the case of no main camera
m_Move = v*Vector3.forward + h*Vector3.right;
}
#if !MOBILE_INPUT
// walk speed multiplier
if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif
// pass all parameters to the character control script
m_Character.Move(m_Move, crouch, m_Jump);
m_Jump = false;
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
//FindObjectOfType<AudioManager>().Play("CatchingPresent");
AudioManager.instance.Play("CatchingPresent");
}
}
void SetCountText()
{
countText.text = "Missing: " + count.ToString();
if (count == 0)
{
winText.text = "You saved Christmas!";
}
}
}
}
Edit 2: Hierarchy in Unity:
Reformulated the approach that I was following and solved the problem by simply adding an Audio Source to the ThirdPersonController (with the AudioClip that I wanted to call) and added GetComponent<AudioSource>().Play(); to the if statement as it follows:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
GetComponent<AudioSource>().Play();
}
}
Importing your scripts myself works without any issues when using FindObjectOfType<AudioManager>().Play("CatchingPresent");. Try reimporting your scripts from the editor (right click in the project folder > reimport all. this might take a while depending on the size of your project)
to use AudioManager.instance.Play("CatchingPresent"); you would first need to create a static variable that holds instance like this (this only works as a singleton, and will break if multiple AudioManager's are in the scene):
public class AudioManager : MonoBehaviour
{
//Create a static AudioManager that will hold the reference to this instance of AudioManager
public static AudioManager Instance;
public Sound[] sounds;
//Assign Instance to the instance of this AudioManager in the constructor
AudioManager()
{
Instance = this;
}
// Rest of the AudioManager code
}
Doing it like this, and using the rest of your code also works for me.

Unity3D Shooter: Using tags to switch level after killing all enemies

I am new to Unity and was trying, after some suggestions, to use tags to know the number of enemies i have in each level and move to the next scene right after eliminating all enemies. This is the script i use on enemy gameobjects. I've also tagged each of them with the "enemy" tag in unity inspector but it still doesn't work when i run the game. After killing all the enemies, it didnĀ“t change to next scene (Success!). Any ideas on what I'm doing wrong? Any other suggestions?
Thanks a lot for the help.
Enemies Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BadguyScript : MonoBehaviour
{
public GameObject[] enemies;
public int maxHealth;
public int curHealth;
private Animator myAnimator;
private bool isDead;
[SerializeField]
private float DespawnTime = 2.5f;
[SerializeField]
private string DeathAnimHash = "isDead";
void Start()
{
myAnimator = GetComponent<Animator>();
myAnimator.enabled =true;
myAnimator.SetBool (DeathAnimHash ,isDead);
maxHealth = 1;
curHealth = maxHealth;
}
void Update()
{
if (curHealth < 1)
{
isDead = true;
myAnimator.SetBool (DeathAnimHash ,isDead);
Destroy(gameObject,DespawnTime);
}
enemies = GameObject.FindGameObjectsWithTag("enemy"); // Checks if enemies are available with tag "Enemy".
if (enemies.Length == 0)
{
SceneManager.LoadScene("SucessScene"); // Load the scene with name "SucessScene"
}
}
void OnTriggerEnter2D(Collider2D col)
{
if (isDead)
return;
if (col.tag == "bullet")
{
curHealth -= 1;
Destroy(col.gameObject);
}
}
}
I would create a script holder gameobject for this and put a GameManager script inside it. And inside GameManager.cs which should be a singleton class you can have a property like this:
int _enemyNumber;
public int EnemyNumber{
get{
return _enemyNumber;
}
set{
_enemyNumber = value;
}
}
And when you need to change these values, use some functions you will create inside this game controller such as:
public void DecreaseEnemyCount(){
//do the logic here
}
public void SetEnemyCount(){
//do the logic here
}
Also you can find information about creating a singleton class here
You create a list with all enemies, its a good practice, cause you'll gain performance. But you're verifing if enemies.Lenght == 0, what will never occur, because before you are adding the gameObject in the list enemies = GameObject.FindGameObjectsWithTag("enemy");
In the start method, you can search for all enemies and add then in your array, and in the update or onTriggerEnter you remove it from your array and validate the array lenght. I think it'll be more easy.
Instead of adding the script to a new gameManager script attached to an empty game object cause now once all enemies are killed the script will not be in work but if added to an empty gameobject it will be working always.

how can assign a gameObject from code using c#

I'm working on random coin generator. I have included the code below:
timer -= Time.deltaTime;
if (timer <= 0)
{
Vector3 position = new Vector3(Random.Range(11f, 14f), Random.Range(-0.7f, 4.5f), 0);
Instantiate(coins, position, transform.rotation);
timer = delayTimer;
}
and I add a collision to pick the coin and make a score text:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class Collisions : MonoBehaviour {
public Text scoreText;
private float score;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
scoreText.text = "Score : " + score;
}
private void OnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.tag == "Player")
{
Destroy(gameObject);
scoreText.text = "Score : " + score;
score++;
}
}
}
so now when I need to add the Text to the prefab I can't
and if I assign the text in somehow to the prefab it doesn't count any score
You need a script or a prefab to know about a certain object in the scene. right?
Whenever you find yourself in this situation you must revise your approach. Keep in mind:
You can't assign a reference of an object from a scene to a prefab. That's basically impossible. prefabs aren't supposed to know about the scene.
You can assign a reference of an object from a scene to another object from the same scene.
You can assign a reference of an object from (or within) a prefab to any object (either to another prefab or same prefab or within a prefab or to an object in the scene)
In other words:
you can drag and drop anything anywhere except for from scene to prefab
Alternative?
There are many.
Why not try singleton pattern? It's easy to learn and easy to use. It's impeccable with objects which only have one instance. Like a GameController or GuiController or a UserProfileController etc.
Having a singleton GuiController:
public class GuiController : MonoBehaviour
{
//singleton setup
static GuiController instance;
void Awake() { instance = this; }
//now it's ready for static calls
public UnityEngine.UI.Text MyText;
//static method which you can call from anywhere
public static void SetMyText(string txt) { instance.MyText.text = txt; }
}
Have an object in the scene (better be the canvas). attach this script to it. Set the reference of My Text in it. In your collisions script just call GuiController.SetMyText("Score : " + score)
Edit
There is actually a slight mistake with your Collision script.
the field score which is defined in Collisions resets to 0 in the new object every time a game object with this script is being instantiated. This happens because score is not defined as a static member of Collisions and any new object naturally has its own members i.e. a new score. In fact, it shouldn't be in Collisions it is better to have a GameController script handling these kinds of data.
Again with the singleton pattern:
public class GameController : MonoBehaviour
{
static GameController instance;
void Awake() { instance = this; }
float score;
public static float Score { get { return instance.score; } set { instance.score = value; } }
}
Attach the script to a game object and instead of score++ you can write GameController.Score++;

Categories