How do I make an instantiated object keep following my player after its instantiated? - c#

I drew a little red animation thing for when my player gets hit. I have an arrow shooter in my level but my problem is the red damage effect instantiates where my player is at, but when my player moves the object stays in the player's old position. Like lets say my player is at x:10. The effect will spawn at x:10 but when I move to x:17 the effect will stay at x:10.
Here's my code
public int damagedealt;
public Player playerscript;
public GameObject RedDamage;
public Vector2 playerpos;
public GameObject player;
// Start is called before the first frame update
void Start()
{
playerscript = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
playerpos = player.transform.position;
player = GameObject.FindGameObjectWithTag("Player");
transform.Translate(Vector2.right * speed * Time.deltaTime);
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
playerscript.health -= damagedealt;
Instantiate(RedDamage, playerpos, transform.rotation);
Destroy(gameObject);
}
}
and if it's useful at all here's the damage effect script
{
public float duration;
// Start is called before the first frame update
void Start()
{
duration = .7f;
}
// Update is called once per frame
void Update()
{
duration -= Time.deltaTime;
if (duration <= .13f )
{
Destroy(gameObject);
}
}
}

You can either add a follow script to the object you would like to follow your player or you can child the object to the player. When an object is childed to another, its transform becomes relative to its parent. In your case, your instantiated object would move relative to your player.
As this seems like a temporary effect that is repeated multiple times, it might make sense to child an empty object for the sole purpose of spawning your effect there. Create an empty gameObject that is childed (meaning placed under in the scene hierarchy) to your player object. Make sure to set the position of this new empty object in a place where you would like to spawn your object.
Once you are happy with where the object is placed, you can alter your Instantiate slightly to fit the new approach.Instantiate has a few overloads to the method. The one I will use is as follows
public static Object Instantiate(Object original, Transform parent);
Instead of specifying a rotation, position, etc, you will specify the parent at which the object will be placed and childed to. Here is what the script would now look like
public int damagedealt;
public Player playerscript;
public GameObject RedDamage;
public Vector2 playerpos;
public GameObject player;
// serializing a field will expose it in the editor even if marked private
// while not allowing other scripts to access it
[SerializeField] private Transform DisplayDamageFeedback = null;
// Start is called before the first frame update
void Start()
{
playerscript = FindObjectOfType<Player>();
}
// Update is called once per frame
void Update()
{
playerpos = player.transform.position;
player = GameObject.FindGameObjectWithTag("Player");
transform.Translate(Vector2.right * speed * Time.deltaTime);
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
playerscript.health -= damagedealt;
Instantiate(RedDamage, DisplayDamageFeedback);
Destroy(gameObject);
}
}
Make sure to assign the reference to DamageFeedback in the inspector to the empty gameObject you created earlier. If you need help on any of the steps just let me know.

Try something like this.
in the effects' 'Update' script:
GameObject player = GameObject.FindGameObjectWithTag("Player");;
public void MoveGameObject()
{
transform.position = new Vector3(player.transfom.position.x,player.transform.position.y,player.transform.position.z);
}
This means that every Update, the player object is found, and the effect's position is set to match the players.
See the following link for Unity's example of position:
https://docs.unity3d.com/ScriptReference/Transform-position.html

Related

Bullet script problem in unity: transform is not responding

I'm trying to use a script to make bullets spawn and move but they just spawn and drop to the ground due to the gravity on the rigidbody, but no gravity seems to be applied.
I'm suspecting I need to declare something I didn't declare, but I'm pretty new to coding so I can't seem to solve it. Here's the script I've got so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public GameObject projectile;
public float speed = 1000;
void Start()
{
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
Instantiate(projectile, transform.position, projectile.transform.rotation);
projectile.transform.Translate(Vector3.back * Time.deltaTime * speed);
Debug.Log("Shoot!");
}
}
}
And here's a screen recording of the problem:
https://drive.google.com/file/d/14KTSx7fJMhs-se40sZFDFmTJGv0Kqylk/view?usp=sharing
projectile refers to the bullet prefab I've created and attached to the spawnPoint gameObject, which as you can see it's on the tip of the rifle
Any help would be highly appreciated!
There are multiple issues here
You spawn a new copy of (I assume) the prefab projectile but then instead of moving this new spawned object you try to move the original prefab projectile.
→ You rather want to move the new spawned object instead!
you move only for the one single frame where GetKeyDown is true.
→ For a continuous movement you would need to keep calling the movement continously once every frame!
So what you would (see bottom point) be doing is rather have a dedicated component on the projectile prefab
public class Bullet : MonoBehaviour
{
public float speed = 1000;
private void Update ()
{
// might have to tweak the direction e.g. Vector3.forward according to your needs
transform.Translate(Vector3.back * Time.deltaTime * speed);
}
}
And then simply do
public class Shoot : MonoBehaviour
{
public Bullet projectile;
void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
Instantiate(projectile, transform.position, transform.rotation);
Debug.Log("Shoot!");
}
}
}
However, since you are using a Rigidbody and want to move your bullet using Physics and get a working Collision Detection you shouldn't use Transform at all.
Rather disable the gravity on the Rigidbody and then do
public class Shoot : MonoBehaviour
{
public Rigidbody projectile;
public float speed = 1000;
void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0))
{
var newBullet = Instantiate(projectile, transform.position, transform.rotation);
// Might have to tweak the direction using -transform.forward according to your needs
newBullet.velocity = transform.forward * speed;
Debug.Log("Shoot!");
}
}
}

Unity finding Transform object in scene

I am new at game dev, and I have question, I have my enemies prefabs, and enemy script, contains
public Transform player;
So Instead of every time putting my player into that 'slot', I want to make, script will be finding my player, I tried
private Transform player = GameObject.Find("player")
but it shows error
Here is the full script
public class Enemies : MonoBehaviour
{
public Transform player = GameObject.Find("Player");
private Rigidbody2D rb;
private Vector2 movement;
public float speed = 5f;
public int health;
// Start is called before the first frame update
void Start()
{
rb = this.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
Vector2 direction = player.position - transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
rb.rotation = angle;
direction.Normalize();
movement = direction;
}
private void FixedUpdate()
{
Move(movement);
}
void Move(Vector2 direction)
{
rb.MovePosition((Vector2)transform.position + (direction * speed * Time.deltaTime));
}
private void OnMouseDown()
{
health = health - 1;
if(health <= 0)
{
Destroy(gameObject);
}
}
}
First of all you can't use Find in a static context. (Which is probably the error you are referring to.)
It goes a bit deeper into how c# works but in simple words: The class fields are all initialized even before the constructor is executed and thus at a moment when there still is no instance.
Secondly: GameObject.Find returns a GameObject not a Transform.
So if anything it would probably rather be
// Best would still be to drag this in if possible
[SerializeField] private Transform player;
void Start()
{
if(!player) player = GameObject.Find("Player").transform;
rb = this.GetComponent<Rigidbody2D>();
}
In general I always recommend to not use Find at all if anyhow possible. It is basically just a more expensive way of using some static or manager/provider based code

Possible to create a more efficient homing missile system in Unity3D?

I've written a homing missile script for my action platformer and I cant help but notice that this might not be the most efficient of weapons.
void Start()
{
target = GameObject.FindGameObjectWithTag("Enemy");
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate()
{
direction = (target.transform.position - transform.position).normalized;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
rotatetoTarget = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, rotatetoTarget, Time.deltaTime * rotationSpeed);
rb.velocity = new Vector2(direction.x * fowrardSpeed, direction.y * fowrardSpeed);
}
It works perfectly but there are a few issues with it. How do I ask it to randomly select a different enemy every time it is instantiated? Rather than all going for one enemy?
Also once that enemy has died, it should choose a new target, but wont performing GameObject.Find() in Update() be a bad thing? I understand that GameObject.Find() is to be avoided as it goes over all of the gameObjects in the scene until it finds what it is looking for, and if need be should only be used in Start(). Now I had to use GameObject.Find() when the weapon instantiates as I could not find of any other way to locate the target for the weapon. So is there a better way to chose a new target once that target is destroyed? My game is a game where reaction time matters and I dont want to create any unnecessary lag due to this weapon
Thank you
You could have an EnemyCache and MissileSpawner script.
Your EnemyCache (most likely a singleton) would have a list of enemies in the world;
Enemies are added into that list when they are spawned, and removed from that list when they die.
Your MissileSpawner (or something that spawns the projectiles) script would then need to assign the missiles a target each time it spawns a new missile.
It can fetch a target for the new missile via the EnemyCache. (You can even filter the list to get the closest target too!)
Finally, your missile script can fetch a new target from the EnemyCache if the old target died.
Overall, it should look similar to this:
YourMissile.cs
public class YourMissile : MonoBehaviour {
// ...
GameObject target;
public void Update() {
// target is destroyed or gone
if (target == null) {
SetTargetFromEnemyCache();
}
}
private void SetTargetFromEnemyCache() {
if (EnemyCache.Instance.TryGetFirstEnemy(out Enemy newTarget)) {
target = newTarget.gameObject;
} else {
Debug.LogWarning("No enemy for missile to target!");
}
}
// ...
public void SetTarget(GameObject targetToSet) {
target = targetToSet;
}
// ...
}
EnemyCache.cs
public class EnemyCache : MonoBehaviour {
// Singleton
public static EnemyCache Instance { get; private set; }
private List<Enemy> cachedEnemies;
private void Awake() {
Instance = this;
cachedEnemies = new List<Enemy>();
// TODO: Subscribe to a delegate or event, that adds into the 'cachedEnemy' whenever an enemies spawned.
// Also, an event that removes from 'cachedEnemy' when an enemy dies too.
}
// ...
/// <summary>
/// Tries to fetch the first enemy in the cache.
/// </summary>
/// <param name="enemy">The fetched enemy; Null if there was nothing in cache</param>
/// <returns>True if there is an enemy fetched; False if none</returns>
public bool TryGetFirstEnemy(out Enemy enemy) {
if (cachedEnemies.Count > 0) {
enemy = cachedEnemies[0];
return true;
}
enemy = null;
return false;
}
}
YourMissileSpawner.cs
public class YourMissileSpawner : MonoBehaviour {
[SerializeField]
private YourMissile missilePrefab;
// ...
public void SpawnProjectile() {
YourMissile newMissile = Instantiate(missilePrefab);
// Set position... etc...
// Try to get a target for the new missile
if (EnemyCache.Instance.TryGetFirstEnemy(out Enemy enemyToTarget)) {
newMissile.SetTarget(enemyToTarget.gameObject);
} else {
Debug.LogWarning("No enemy for newly spawned missile to target!");
}
// NOTE: The above is optional,
// since 'YourMissile' sets a new target from EnemyCache
// if the target is null; (checks per update)
// But I included it here in case you want it to filter
// what kind of enemy it needs to target on start :)
}
}

Destroy particle system when initalised programmatically

I have a game with a player and an enemy.
I also have a particle system for when the player dies, like an explosion.
I have made this particle system a prefab so I can use it multiple times per level as someone might die a lot.
So in my enemy.cs script, attached to my enemy, I have:
public GameObject deathParticle;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "Player" && !player.dead){
player.dead = true;
Instantiate(deathParticle, player.transform.position, player.transform.rotation);
player.animator.SetTrigger("Death");
}
}
So this plays my particle system when the player gets killed by an enemy. Now on my player script, I have this. This specific function gets played after the death animation:
public void RespawnPlayer()
{
Rigidbody2D playerBody = GetComponent<Rigidbody2D>();
playerBody.transform.position = spawnLocation.transform.position;
dead = false;
animator.Play("Idle");
Enemy enemy = FindObjectOfType<Enemy>();
Destroy(enemy.deathParticle);
}
This respawns the player like normal but in my project each time I die I have a death (clone) object which I don't want. The last 2 lines are meant to delete this but it doesn't.
I have also tried this which didn't work:
Enemy enemy = FindObjectOfType<Enemy>();
ParticleSystem deathParticles = enemy.GetComponent<ParticleSystem>();
Destroy(deathParticles);
There is no need to Instantiate and Destroy the death particle, that will create a lot of overhead, you can simply replay it when you want it to start and stop it, when you dont need it
ParticleSystem deathParticleSystem;
private void OnTriggerEnter2D(Collider2D collision)
{
#rest of the code
deathParticleSystem.time = 0;
deathParticleSystem.Play();
}
public void RespawnPlayer()
{
//rest of the code
deathParticleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
}
or you can enable and disable the gameObject associated with your particle prefab
public GameObject deathParticlePrefab;
private void OnTriggerEnter2D(Collider2D collision)
{
#rest of the code
deathParticlePrefab.SetActive(true);
}
public void RespawnPlayer()
{
//rest of the code
deathParticlePrefab.SetActive(false);
}
You could always create a new script and assign it to the prefab that destroys it after a certain amount of time:
using UnityEngine;
using System.Collections;
public class destroyOverTime : MonoBehaviour {
public float lifeTime;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
lifeTime -= Time.deltaTime;
if(lifeTime <= 0f)
{
Destroy(gameObject);
}
}
}
So in this case you would assign this to your deathParticle. This script is useful in a multitude of scenarios if you're instantiating objects so that you don't have a load of unnecessary objects.

Unity Engine - Instantiate a prefab by collision

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));

Categories