I have a problem, I need to block the creation of a new object (prefab) if there is already one prefab on the stage.I solved it with GameObject.FindWithTag, but maybe there is some other way
using UnityEngine;
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public GameObject ballPrefab;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (GameObject.FindWithTag("ballBullet") == null)
{
CreatingBulletBall();
}
}
}
void CreatingBulletBall()
{
Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
}
}
Here is what you should do:
Create script Bullet.cs with code from below;
Add it as component to the bullet prefab;
Change the CreateBullet class as shown below;
Assign bullet prefab to the ballPrefab field of the CreateBullet component (if unassigned after script change).
How it works: if _bullet field is null, new bullet is created and stored there. When bullet is destroyed, it fires event Destroyed. Callback OnBulletDestroyed will assign null to _bullet, so it can be created again.
File CreateBullet.cs:
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public Bullet ballPrefab;
private void Update()
{
if (Input.GetMouseButtonDown(0))
TryCreateBulletBall();
}
private Bullet? _bullet = null;
private void TryCreateBulletBall()
{
if (this._bullet != null)
return;
this._bullet = Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
this._bullet.Destroyed += this.OnBulletDestroyed;
}
private void OnBulletDestroyed()
{
this._bullet.Destroyed -= this.OnBulletDestroyed;
this._bullet = null;
}
}
File Bullet.cs:
public class Bullet : MonoBehaviour
{
public event Action? Destroyed;
private void OnDestroy()
{
this.Destroyed?.Invoke();
}
}
Not tested, but it should work. It works.
Update:
About performance:
In terms of performance it's perfectly fine. It is idiomatic to store the reference to the object once (through Inspector, in Start/Awake hooks, when object is instantiated, etc), instead of calling any of Find...() or GetComponent() methods in the Update hook. In fact, docs for Find() method state that you should avoid calling it every frame.
Simplified version of code.
As #derHugo noted in comment, it's sufficient to have this code only:
File CreateBullet.cs:
public class CreateBullet : MonoBehaviour
{
public Transform firePoint;
public GameObject ballPrefab;
private void Update()
{
if (Input.GetMouseButtonDown(0))
TryCreateBulletBall();
}
private GameObject _bullet;
private void TryCreateBulletBall()
{
if (this._bullet)
return;
this._bullet = Instantiate(ballPrefab, firePoint.position, firePoint.rotation);
}
}
Related
I am recently new to c# and I need some help.
Essentially I have two scripts, one for spawning objects and one for moving an object along a path. I need a way to combine these two mechanics so that when a new object is instantiated it automatically joins the path and follows it.
The path is made using iTween.
![The objects the scripts are attached to] (https://i.stack.imgur.com/QPQn2.png)
I've tried changing the variable m_PlayerObj to the cube prefab and I've tried adding the Path script to the instantiation script but nothing seems to work.
The scripts attached do nkt include these attempts I made as I wanted to make the code very clear.
Spawner script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(cubeprefab, transform.position, Quaternion.identity);
}
}
}
Path script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Path : MonoBehaviour
{
public GameObject m_PlayerObj;
public Transform[] positionPoint;
[Range(0, 1)]
public float value;
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
float tempTime;
// Update is called once per frame
void Update()
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
As stated above, any help would be greatly appreciated as I am really stuck on this conceot and since I am new to Unity I really can’t see a way around it // how to fix it.
Instead of only storing the player object in the Path script, store a collection of objects. That way, the path can keep track of more than one object.
//public GameObject m_PlayerObj; // Get rid of this
public List<GameObject> followers; // Add this
Then, in your Update loop, you can loop through all of them.
void Update()
{
for (var i = 0; i < followers.Length; ++i)
{
if (value < 1)
{
value += Time.deltaTime / 10;
}
iTween.PutOnPath(m_PlayerObj, positionPoint, value);
}
}
Of course, now, you need to make sure you pass your cube instance to the Path GameObject when you spawn it, so the path knows about the cube follower. That means your spawner also needs to know about the path.
public class SpawnerScript : MonoBehaviour
{
public GameObject cubeprefab;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var cubeInst = Instantiate(cubeprefab, transform.position, Quaternion.identity);
path.followers.Add(cubeInst);
}
}
}
Now a new problem is going to be that each object is going to be at the same position on the path, because the path only stores one value - a better term might be progress. So if they're all the same, like the cube, you won't be able to tell because they'd overlap.
So you have to decide what you want to do instead. Evenly space them? You could do that with some math. Or have them all start from the beginning and keep track of their progress separately? Then you'd need to store progress for each of them. A better place to do that is probably on the cube object, which means you need to add a new script to your cube prefab:
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}
And, you need to start referring to the prefab by this script, instead of just GameObject:
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
//...
Finally, you need to make sure to use the individual progress for each path follower, rather than a single progress value like your old Path script did:
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
Putting it all together (separate files of course, with their own includes!):
public class SpawnerScript : MonoBehaviour
{
public PathFollower pathFollower;
public Path path; // Need to populate this in the Editor, or fetch it during Awake()
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var followerInst = Instantiate(pathFollower, transform.position, Quaternion.identity);
path.followers.Add(followerInst);
}
}
}
public class Path : MonoBehaviour
{
//public GameObject m_PlayerObj; // Get rid of this
public List<PathFollower> followers; // Add this
public Transform[] positionPoint;
//[Range(0, 1)]
//public float value; // Don't need this anymore either
// Start is called before the first frame update
void Start()
{
Debug.Log(iTween.PathLength(positionPoint));
}
// Update is called once per frame
void Update()
{
for (var i = 0; i < followers.Count; ++i)
{
if (followers[i].pathProgress < 1)
{
followers[i].pathProgress += Time.deltaTime / 10;
}
iTween.PutOnPath(followers[i].gameObject, positionPoint, followers[i].pathProgress);
}
}
private void OnDrawGizmos()
{
iTween.DrawPath(positionPoint,Color.green);
}
}
public class PathFollower : MonoBehaviour
{
[Range(0, 1)]
public float pathProgress;
}
I'm trying to make a simple damage boost I have 2 scripts, one attached to the projectile and one to the booster
Projectile:
public class DamageDealer : MonoBehaviour
{
[SerializeField] public int damage = 25;
public int GetDamage()
{
return damage;
}
public void Hit()
{
Destroy(gameObject);
}
public void IncreaseDamage(int value)
{
damage += value;
Mathf.Clamp(damage, 0, int.MaxValue);
}
}
Booster:
public class DamageUp : MonoBehaviour
{
int damageBoost = 50;
DamageDealer damageDealer;
void Awake()
{
damageDealer = FindObjectOfType<DamageDealer>();
Debug.Log(damageDealer); // this returns as null
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
damageDealer.IncreaseDamage(damageBoost);
}
}
}
As you can see in the Booster I've already identified what's returning as null, but I'm quite new to unity and C# so I don't know what I'm doing wrong
Some more info the Projectile is a prefab while the Booster is not (yet)
scripts are attached and enabled, objects are enabled too.
Hit() and GetDamage() are for another script I have no issue with.
Thanks in advance.
You're probably trying to find the object before it's instantiated, change the Awake to Start or try to change Script Execution Order settings
Check Also; Order of execution for event functions
and thank you for looking at this in advance.
I have yet another problem that i need to solve and it goes like this:
I have a list of objects that i select with a raycast, and i would like to teleport them to a specific location in the scene. Like for example i have selected a cube and a sphere and they are added to my list called playersTagged.
How can i get the objects in my list to that specific location when OnCollisionEnter with my player that has the tag "Tagger"?
My code looks like this:
PlayerTagged Class
using System.Collections.Generic;
using UnityEngine;
public class PlayerTagged : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera fpsCam;
public List<GameObject> playersTagged;
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
void Shoot()
{
RaycastHit hit;
if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Target target = hit.transform.GetComponent<Target>();
if (target != null && target.isHit == false)
{
target.takeDamage(damage);
if(hit.collider.tag == "Taggable")
playersTagged.Add(hit.collider.gameObject);
target.isHit = true;
}
}
}
}
Teleport class:
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Transform teleportTarget;
public PlayerTagged player;
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Tagger")
{
Debug.Log("You hit the can");
}
}
}
You need to have the PlayerTagged reference in your Teleport component. If both objects will always exist in your scene, only create a public field in your Teleport and drag and drop your PlayerTagged ref, otherwise you will need to fill this ref by code using some "find" approach, for example, GameObject.FindObjectWithTag().
You can also make an event that trigger when the object that entered the OnTriggerEnter is valid (I mean, when your tag condition pass) and then make sure that PlayerTagged is being registered as listener to this event.
First one is easier to setup, but if you plan to make unique things with this OnTriggerEnter as playing sounds, changing data or something like that, the second one is a better approach.
EDIT: I'll try to insert some code, let me know if you still having problems on getting the idea
"If both objects will always exist in your scene..."
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Transform teleportTarget;
public PlayerTagged player; // I didn't saw that you already have an ref here, my bad, so you only need to access the fields
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Tagger")
{
Debug.Log("You hit the can");
var list = player.playersTagged; // this is how you will access your list
// Do some stuff with your list
}
}
}
Ignore the rest, it will only complicate things
EDIT 2: Making the teleport happen in player, and teleport every object
using System.Collections.Generic;
using UnityEngine;
public class PlayerTagged : MonoBehaviour
{
public float damage = 10f;
public float range = 100f;
public Camera fpsCam;
public List<GameObject> playersTagged;
private void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
// Option 1:
private void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Teleport Tag")
{
var teleport = col.gameObject.GetComponent<Teleport>();
TeleportObjectsTo(teleport.teleportTarget.position);
}
}
void Shoot()
{
RaycastHit hit;
if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range))
{
Debug.Log(hit.transform.name);
Target target = hit.transform.GetComponent<Target>();
if (target != null && target.isHit == false)
{
target.takeDamage(damage);
if(hit.collider.tag == "Taggable")
playersTagged.Add(hit.collider.gameObject);
target.isHit = true;
}
}
}
public void TeleportObjectsTo(Vector3 targetPosition)
{
foreach(taggedObjects in playersTagged)
{
taggedObjects.transform.position = targetPosition;
}
}
}
You can also make the teleport happen on teleport script:
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Transform teleportTarget;
public PlayerTagged player; // I didn't saw that you already have an ref here, my bad, so you only need to access the fields
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Tagger")
{
Debug.Log("You hit the can");
// Option 2:
player.TeleportObjectsTo(teleportTarget);
// Option 3: Or you can iterate here instead of make use of the API
foreach(var taggedObjects in player.playersTagged)
{
taggedObjects.transform.position = targetPosition;
}
}
}
}
Chose one option, and delete/comment others
--N
This is my code. Tried repasting the code. And multiple other things like writing the function again.
The brackets wont connect(at the start and the end of the code) when I make new function. The closing bracket from new function will jump to the one that summarizes the whole code. Even placing it properly does nothing. Thanks in advance.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooter : MonoBehaviour
{
[SerializeField] GameObject projectile;
[SerializeField] Transform gunPos;
public void Fire()
{
if (!gunPos) { return; }
GameObject projectileInstance = Instantiate(projectile, gunPos.transform.position, Quaternion.identity) as GameObject;
}
private void SetLaneSpawner()
{
public AttackerSpawner[] spawners = FindObjectsOfType<AttackerSpawner>();
}
private void Start()
{
SetLaneSpawner();
}
public void Update()
{
if (null)
{
Debug.Log("pew pew");
//TODO attack animation
}
else
{
Debug.Log("wait");
//TODO idle animation
}
}
}
The error is in the line:
public AttackerSpawner[] spawners = FindObjectsOfType<AttackerSpawner>();
// Remove the public so it becomes.
AttackerSpawner[] spawners = FindObjectsOfType<AttackerSpawner>();
Accessibility modifiers (public, private, etc) can only be applied at the class and property level. You cannot use them inside a function.
I want to have a singleton which will store, update and show player score through all levels (scenes), but something works wrong.
This my singletone script GameStatus.cs:
using UnityEngine;
using TMPro;
public class GameStatus : MonoBehaviour
{
public static GameStatus instance;
[SerializeField] int pointsPerBlock = 50;
[SerializeField] TextMeshProUGUI scoreText;
[SerializeField] public static int currentScore = 0;
private void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
public void AddToScore()
{
currentScore += pointsPerBlock;
scoreText.text = currentScore.ToString();
}
}
I also have another script for objects player need to destroy, called Block.cs. Here it is:
using UnityEngine;
public class Block : MonoBehaviour
{
Level level;
GameStatus gameStatus;
private void Start()
{
level = FindObjectOfType<Level>();
gameStatus = FindObjectOfType<GameStatus>();
level.CountBreakableBlocks();
}
private void OnCollisionEnter2D(Collision2D collision)
{
DestroyBlock();
}
private void DestroyBlock()
{
level.BlockDestroyed();
gameStatus.AddToScore();
Destroy(gameObject);
}
}
And on level 1 everything works fine, but when the game goes to the next level this happens:
Player score stops updating.
If I use Debug.Log(currentScore); in GameStatus.cs I can see that this variable doesn't change when player breaks blocks, but if use Debug.Log(gameStatus.currentScore); in Block.cs then I can see that this variable is getting updated.
Debug.Log(FindObjectsOfType().Length); shows that there is one GameStatus object in the first level and two objects in the next levels, although I can't see the second one GameStatus in hierarchy.
So my question is - what's wrong and how to fix it?
If you use singleton, there is no point to make
currentScore
static variable, just make it
public int currentScore;
Also in your block cs you can just call
GameStatus.instance.AddToScore();
No need to make reference at start