When my player dies and respawns, I'm trying to have my camera look at the newly-instantiated player. Here's the code
public class CameraController : MonoBehaviour
{
void Update()
{
var target = GameObject.FindGameObjectWithTag("Player");
Transform target;
transform.LookAt(target);
}
}
I'm trying to do it without a public "drag-and-drop" Transform variable.
I didn't have any problems with just my base player, but when I tried to respawn it with Instantiate(), the camera cannot detect the copies.
well remove that Transform target; and rather use
transform.LookAt(target.transform);
... in general for performance sake you should avoid using any of the Find variations in Update but do it Once in e.g. Start and reuse the reference
public class CameraController : MonoBehaviour
{
private Transform target;
private void Start()
{
target = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
transform.LookAt(target);
}
}
The initial problem you're having is you are finding the GameObject of the player which is the type 'var' will be set to. After that, it's being overwritten when you set target to be of type Transform with a null value. Getting the Transform component of the Player and setting target to equal that will solve your issue.
Performance-wise however:
You want to avoid searching for gameobjects in an Update(), instead find what you're searching for one time and cache it. Finding a gameobject is an expensive action and having it inside of Update() will perform that action every cycle.
A more CPU-friendly way to handle it would look like:
Transform target;
public void findTarget(string targetToFind)
{
// potentially do some error handling here in case the gameobject hasn't loaded in yet
target = GameObject.FindGameObjectWithTag(targetToFind).transform;
}
void Update()
{
transform.LookAt(target);
}
How I would have this work is upon character respawn, call the findTarget method on the script passing it the player tag. It also makes the method more generalized so you can do additional camera shift tricks if you desire to have the camera move to a different target for a cinematic event or something similar.
I've found a solution. To any other people looking for something similar:
public class CameraController : MonoBehaviour
{
Transform target;
void Update()
{
GameObject target1 = GameObject.FindGameObjectWithTag("Player");
target = target1.transform;
transform.LookAt(target);
}
}
Related
I'm currently making a 3D endless runner game with the choice to select different skins for the player. Everything's going smoothly until I come across a problem, in which I try to assign two collision game objects to each character prefab in the container that can detect the player's collision box when it collides with a powerup.
It only detects the first playerContainer's (Milo) 'Coin Detect' game object (eventhough it's been deactivated) and does not recognize the 'Coin Detect' collision game object in the Baddie playerContainer (which the player has chosen to play)
So my question is, how am I able to get the script to recognize the child that's active in the third playerContainer game object instead of it automatically detecting the first playerContainer game object's child?
From the Magnet powerup script I've made, the script detects the first playerContainer's 'Coin Detect' game object only. As shown in the attached picture below:
Here's my current script where the player is able to select their preferred characters.
using System.Collections.Generic;
using UnityEngine;
public class EnablePlayerSelector : MonoBehaviour
{
public GameObject[] players;
public int currPlayerCount;
void Start()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
foreach (GameObject player in players)
player.SetActive(false);
players[currPlayerCount].SetActive(true);
}
}
and here's the script where I deactivate the 'Coin Detect' collision game Object and activate it when the player collides with the powerup.
using System.Collections.Generic;
using UnityEngine;
public class Magnet : MonoBehaviour
{
public GameObject coinDetect;
void Start()
{
coinDetect = GameObject.FindGameObjectWithTag("CoinDetect");
coinDetect.SetActive(false);
Debug.Log("False");
}
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.tag == "Player")
{
StartCoroutine(coinActivate());
Destroy(transform.GetChild(0).gameObject);
}
}
IEnumerator coinActivate()
{
Debug.Log("Hi");
coinDetect.SetActive(true);
yield return new WaitForSeconds(5f);
coinDetect.SetActive(false);
}
}
Well your issue is rather that you are doing GameObject.FindGameObjectWithTag("CoinDetect") which will return whatever is the first encountered CoinDetect in your scene in this moment. So unless EnablePlayerSelector.Start is executed before the Magnet.Start it will always be the first one since they are still all active by then.
You could probably already solve your issue by following the general thumb-rule:
Do things where you do not depend on other components already in Awake
Do things where you o depend on other components in Start
This way you already cover most of the use cases and are sure that components are self-initialized (Awake) before anything is accessing them in Start.
So simply change the EnablePlayerSelector.Start into Awake and you should be fine.
In very specific cases you might still have to adjust the Script Execution Order or use events.
Instead of doing this at all though I would rather in the moment of the collision request the current one from the PlayerContainer directly:
// put this on each player root object
public class Player : MonoBehaviour
{
// reference these via the Inspector
[SerializeField] private GameObject coinDetector;
[SerializeField] private GameObject playerrange;
// ... and in general any information bout this Player that is relevant for other scripts
// Readonly accessor property
public GameObject CoinDetector => coinDetector;
}
and then rather have
public class EnablePlayerSelector : MonoBehaviour
{
[SerializeField] private Player[] players;
[SerializeField] private int currPlayerCount;
public Player CurrentPlayer => players[currPlayerCount];
// In general do things where you don't depend on other scripts already in Awake
// This way you could actually already solve your issue by keeping things where you do
// depend on others in Start
private void Awake()
{
currPlayerCount = PlayerPrefs.GetInt("SelectedPlayer", 0);
for(var i = 0; i < players.Length; i++)
{
players[i].gameObject.SetActive(i == currPlayerCount);
}
}
}
and then finally do e.g.
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
var playerSelector = other.GetComponentInParent<EnablePlayerSelector>();
if(!playerSelector) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = playerSelector.CurrentPlayer.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}
or alternatively if the other in this case refers to the object with the Player component anyway you could also directly do
public class Magnet : MonoBehaviour
{
public float effectDuration = 5f;
private IEnumerator OnTriggerEnter(Collider other)
{
if(!other.TryGetComponent<Player>(out var player)) yield break;
Debug.Log("Hi");
Destroy(transform.GetChild(0).gameObject);
var coinDetect = player.CoinDetector;
coinDetect.SetActive(true);
yield return new WaitForSeconds(effectDuration);
coinDetect.SetActive(false);
}
}
I'm working on Unity/C# now and I'm stuck with accessing CinemachineVirtualCamera's camera distance value in the script. What I'm trying to do is change the value of camera distance in the body section.
First of all, how can I access the CinemachineVirtualCamera component in this game object? The MoveScript is what I attached to a player game object, and I want to zoom out the camera depending on the player's movement. Since the game I'm making is small, I won't make other .cs files.
I wrote
public class MoveScript: MonoBehaviour
{
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
camObj.GetComponent<CinemachineVirtualCamera>(); // <- but I get error saying, The type or namespace name 'CinemachineVirtualCamera' could not be found
}
}
I also read this document and I think the m_CameraDistance is what I'm looking for but how can I access that value?
For anyone else who wondering
GetComponent<CinemachineVirtualCamera>()
.GetCinemachineComponent<CinemachineFramingTransposer>()
.m_CameraDistance
As stated in the linked document, these classes are in Cinemachine namespace. To access the classes you have to either add
using Cinemachine;
to the beginning of your script or change your script to
public class MoveScript: MonoBehaviour
{
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
camObj.GetComponent<Cinemachine.CinemachineVirtualCamera>();
}
}
And as for accessing the m_CameraDistance variable, HuySora already answered that part
Try this and don't forgot to mention namespace
public class MoveScript: MonoBehaviour
{
private CinemachineVirtualCamera virtualCamera;
private GameObject camObj;
void Start()
{
camObj = GameObject.Find("Vertical Follow Camera");
virtualCamera = camObj.GetComponent<CinemachineVirtualCamera>();
float f = virtualCamera.m_CameraDistance;
}
}
I'm new to programming and I'm currently trying to do a simple game in Unity.
The code makes some things dissapear when they touch the ground and it works well but the "Evaporated" variable does not update in the Unity Inspector. When something touch the ground, evaporated should be incremented, but it stays at 0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class EnemyEvaporate : MonoBehaviour
{
public GameObject Enemy;
public int Evaporated;
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.gameObject.name == "Map" || CollisionInfo.gameObject.name == "Player")
{
Thread.Sleep(15);
gameObject.SetActive(false);
Evaporated++;
}
}
}
I'm not really sure what's the behavior you're trying to get in this script. If Evaporated is some kind of 'Global counter' for score, it need to be written different and not store inside script for this gameObject.
It might be related to your gameObject.SetActive(false) which will deactivate your object, so in fact it will disables also the scripts connected with it (and especially this one. SetActive documentation) and removes them from Update() for a given gameObject (so given enemy. If you have 4 Enemy objects on the Scene, in fact you have 4 instances of this script and each has own Evaporated variable)
Why won't you move the void OnCollisionEnter(Collision CollisionInfo) to your Enemy scipt? I think Enemy should now if it collided with anything, not some external script.
You can also use tags instead of names when detecting Collisions.
Also, gameObject.SetActive(false) - if you set your gameObject to disabled, nothing more from its script will happen unless you set it to active again. It e.g. cancels all Coroutine. The gameObject is just "sleeping", I would say. It's not destroyed - you still can see it in your Hierarchy window - but it can't do anything. (By the way, in this case you're setting EnemyEvaporate gameObject as disabled, not Enemy gameObject.)
Also, you can use Coroutine instead of Thread.Sleep(15). It is more common to use Coroutines than Threads in Unity.
Now, Evaporation. If you want to count how many Enemies evaporated (I suppose, looking at Evaporated++;) you should have some external object to count it. The simplest way I can propose fow now is creating a GameObject with EvaporationCounter : MonoBehaviour script attached to it. You can also use Singleton pattern to be sure there's only one object of this type.
public class EvaporationCounter : MonoBehaviour
{
private int evaporates;
public int Evaporates {
get { return evaporates; }
private set
{
if (value >= 0) evaporates = value;
}
}
public void AddEvaporation()
{
Evaporates++;
}
}
Your Enemy class could look like:
public class Enemy : MonoBehaviour
{
private IEnumerator coroutine;
private EvaporationCounter evaporationCounter;
private void Start()
{
evaporationCounter = FindObjectOfType<EvaporationCounter>();
}
private IEnumerator WaitAndSetInactive(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision CollisionInfo)
{
if (!CollisionInfo.gameObject.CompareTag("Map") && !CollisionInfo.gameObject.CompareTag("Player")) return;
if (evaporationCounter != null)
evaporationCounter.AddEvaporation();
StartCoroutine(WaitAndSetInactive(15f));
}
}
Calling evaporationCounter.AddEvaporation() from the Enemy script is not the best solution, since it does not apply to the Dependency Inversion principle but I would say it's good for the beginning with Unity.
I'm creating a 2.5D fighting game in Unity with C#. Currently, I'm trying to make a bumper appear around the player and disappear after a set amount of time. I've managed to make the bumper appear and disappear once, but after that, when I try to make the bumper appear again, Unity has an error for me: "The object of type 'GameObject' has been destroyed but you are still trying to access it."
I've tried using the "instantiate" and "destroy" commands, following a tutorial by "Brackeys" on 2D shooting. After also following some questions on forums about the same issue, I've altered my code again, but the problem persists.
The firePoint is an empty object, from where the BumperPrefab is instantiated.
using UnityEngine;
public class weapon: MonoBehaviour
{
public Transform firePoint;
public GameObject BumperPrefab;
public float lifetime = 0.2f;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
attack();
}
}
void attack()
{
BumperPrefab = (GameObject) Instantiate(BumperPrefab, firePoint.position, firePoint.rotation);
Destroy(BumperPrefab, lifetime);
}
}
I expect the GameObject "BumperPrefab" to appear, stick around for 0.2 seconds and disappear. I should be able to repeat that as many times as I want, but what actually happens is that I can do this only once and then the error "The object of type 'GameObject' has been destroyed but you are still trying to access it" shows up and I can't make the BumperPrefab appear again.
Any help is much appreciated!
using UnityEngine;
public class weapon: MonoBehaviour
{
public Transform firePoint;
public GameObject BumperPrefab;
public float lifetime = 0.2f;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
attack();
}
}
void attack()
{
var bumper = (GameObject) Instantiate(BumperPrefab, firePoint.position, firePoint.rotation);
Destroy(bumper, lifetime);
}
Right now you're overwriting your public field containing the prefab object with your instantiated object, then destroying it. Set the instantiated object as a variable and you should be fine.
The problem is that in your code you don't care about is your GameObject exist. So for example if (for some reason) the object BumperPrefab will not be created, Destory() will try to act on null.
You can try add to BumperPrefab script bumper.cs with:
float lifetime = 0.2f;
private void OnEnable()
{
Desroy(this, lifetime)
}
problem is you are destroying BumperPrefab
when you Instantiate a new GameObject you should add it to the local variable like this
var newbumper = (GameObject) Instantiate(BumperPrefab, firePoint.position,firePoint.rotation);
and you must destroy your local variable which contains newly created gameObject
Destroy(newbumper , lifetime);
I'm trying to learn how to use Unity and following online tutorials but I am currently having a problem that I don't understand how to fix.
I have a Sprite in my scene and I have attached a script to it however in the Inspector it shows the script is there but I cannot see the variables inside? I had this problem previously and it sorted itself out.
What is the cause of this problem/how do I fix it?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpaceShip : MonoBehaviour {
public float speed = 30;
public GameObject theBullet;
private void FixedUpdate()
{
float horzMove = Input.GetAxisRaw("Horizontal");
GetComponent<Rigidbody2D>().velocity = new Vector2(horzMove, 0) *
speed;
}
// Update is called once per frame
void Update () {
if (Input.GetButtonDown("Jump"))
{
Instantiate(theBullet, transform.position, Quaternion.identity);
}
}
}
Edit: The problem was solved by reimporting.
You either need to declare the variables as Public or [SerializeField] for member variables to appear in the inspector. Note that by declaring something as public allows access to the variable from outside the class (from other scripts/classes for example). By default, private is assigned to member variables.
Example:
public class testscript : MonoBehaviour
{
public int foo; // shows up in inspector
[SerializeField] private int bar; // also shows up while still being private
void Start()
{
}
}
Not is a problem, You forget to do something surely.
It is common at first with Unity.
Start again.
In the scene create a new GameObject and add you script.
If the inspector shows not variable:
The varible do not is public (false, if is public in you script)
There is some syntax error in the script!
or
You were not adding the correct script to the GameObject.
There are not many secrets to that, if all is well enough that the variable is public and this outside of a method of the script so that it is seen in the inspector.
One tip, do not use a GetComponent or Instantiate inside a FixedUpdate or Update because they are expensive, save the Rigidbody2D in a variable in the Start and then use it.
Sorry for my English and good luck.