So below you will find a short snippet of code. What this code does is it allows the player to hit the 'p' key to pause the game and when this happens a gui pops up and the players look and movement controls are disabled. My problem is with deactivating and reactivating the gui because it is a game object. It lets me deactivate it but when I try to activate it I get an error.
Code:
UnityEngine.Component walkScriptOld = GameObject.FindWithTag ("Player").GetComponent ("CharacterMotor");
UnityEngine.Behaviour walkScript = (UnityEngine.Behaviour)walkScriptOld;
UnityEngine.GameObject guiMenu = GameObject.FindWithTag ("Canvas");
if ((Input.GetKey ("p")) && (stoppedMovement == true)) {
stoppedMovement = false;
walkScript.enabled = true;
guiMenu.SetActive(true);
} else if ((Input.GetKey ("p")) && (stoppedMovement == false)) {
stoppedMovement = true;
walkScript.enabled = false;
guiMenu.SetActive(false);
}
Error:
NullReferenceException: Object reference not set to an instance of an object MouseLook.Update () (at Assets/Standard Assets/Character Controllers/Sources/Scripts/MouseLook.cs:44)
It seems that the code you've given here is in an Update. So the guiMenu object is being found and stored every frame.
What you want to do is cache the object in the Awake or Start function, and the rest of the code will work just fine. Also note that caching is always good practice.
//This is the Awake () function, part of the Monobehaviour class
//You can put this in Start () also
UnityEngine.GameObject guiMenu;
void Awake () {
guiMenu = GameObject.FindWithTag ("Canvas");
}
// Same as your code
void Update () {
if ((Input.GetKey ("p")) && (stoppedMovement == true)) {
stoppedMovement = false;
walkScript.enabled = true;
guiMenu.SetActive(true);
} else if ((Input.GetKey ("p")) && (stoppedMovement == false)) {
stoppedMovement = true;
walkScript.enabled = false;
guiMenu.SetActive(false);
}
}
Related
I'm developing a search game, where players must look for certain objects. Whenever the targeted object is found and has been picked up, the player wins and go to the next level. I tagged the targeted objects as "TargetObj". I successfully implemented this when there is only one object to look for. I want to modify my code to include cases where there is more than one object to look for. Here is my code :
public void someFunction() {
//if we press the button of choice
if (Input.GetKeyDown(KeyCode.Space)) {
//and we're not holding anything
if (currentlyPickedUpObject == null) {
//and we are looking an interactable object
if (lookObject != null) {
PickUpObject();
}
} else { //if we press the pickup button and have something, we drop it
BreakConnection();
}
}
}
/* ommitted lines */
public void PickUpObject() {
if (GameObject.FindGameObjectsWithTag("TargetObj").Length == 1 & lookObject.tag == "TargetObj") {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
} else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1) {
} else {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
}
I added this line, to check if there is more than one object to look for.
else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1)
How to implement this (if the player picked up all objects of tag "TargetObj", go to next level.)?
A fast way of doing this is to keep a counter of picked objects. Then, if the counter is equal with the number of objects with the "TargetObj" tag, then the player wins. As snippet you can have something like this:
Gameobject[] targetObjects; // an array where you will keep your objects with "TargetObj" tag
List<GameObject> targetObjectsList;
void Start()
{
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
targetObjectsList = new List<GameObject>();
}
.
.
.
// In your method (You didn't put all your code so I will use your snippet)
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (currentlyPickedUpObject == null)
{
//and we are looking an interactable object
if (lookObject != null )
{
PickUpObject();
// I suppose that "lookObject" is the gameobject that you want to pickup. If not, replase this variable with the right gameobject.
if(!targetObjectsList.Contains(lookObject.gameObject))
{
targetObjectsList.Add(lookObject.gameObject);
if (targetObjectsList.Count == targetObjects.Length)
{
//Finish the game
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
}
}
}
}
}
//if we press the pickup button and have something, we drop it
else
{
BreakConnection();
}
}
Then you modify your PickUpObject method, just to pick and drop objects.
I'm sorry if I miss something. I wrote this without an editor and I didn't test the code, so please tell me if is something that I miss.
I am trying to display a message saying 'Press E to talk to NPC' when the player is collided with the NPC collider and when the player is not collided with the NPC the message is disabled. The message does appear upon collision but it does not disabled when there are no collisions I have tried so many things but nothing seem to work. Can anyone help? HERE IS MY CODE AND SOME THINGS I HAVE TRIED:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Task_7 : MonoBehaviour
{
public GameObject PressEmsg;
//public bool isNearNPC = true;
// Start is called before the first frame update
void Start()
{
PressEmsg.gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
//bool isNearNPC = false;
//we are looping in the array hitColliders
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
PressEmsg.gameObject.SetActive(true);
print("NPC DETECTED");
//isNearNPC = true;
}
else
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
/*
else if(collider.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
*/
}
/*foreach(Collider collider1 in notnearbyColliders)
{
if(collider1.gameObject.tag != "NPC")
{
PressEmsg.gameObject.SetActive(false);
print("NPC NOT DETECTED");
}
}
*/
}
}
If you have no collisions at all you will not have any iteration of your loop. So the deactivate will not happen, except you have a nearby object without the tag NPC.
Further you are iterating through all the nearby objects and check for each of them if it has the tag NPC. So the loop fully depends on the order in which the colliders are iterated. It might e.g. happen that you first have a hit that has the tag, then you have a second hit that doesn't => you falsely deactivate the object again.
You should rather use Linq Any like e.g.
using System.Linq;
...
void Update()
{
var nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
// This will be true if any of the nearbyColliders has the tag "NPC"
// If there are no colliders this will automatically be false accordingly
var detected = nearbyColliders.Any(collider => collider.CompareTag("NPC"));
// Basically this equals somewhat doing
//var detected = false;
//foreach(var collider in nearbyColliders)
//{
// if(collider.CompareTag("NPC"))
// {
// detected = true;
// break;
// }
//}
PressEmsg.gameObject.SetActive(detected);
print(detected ? "NPC detected" : "NPC not detected");
}
In general for performance reasons avoid logging in Update! Even though your users don't see the log it is still done and is quite expensive.
Note: Typed on smartphone but I hope the idea gets clear
It seems like if you don't have any collisions, you won't get into your for loop.
Before looping over the found colliders I would default the message to be not active, but I'd use a variable so I only actually call the message state method once:
bool isNearNpc = false;
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 5f);
foreach(Collider collider in nearbyColliders)
{
if(collider.gameObject.tag == "NPC")
{
print("NPC DETECTED");
isNearNpc = true;
}
}
PressEmsg.gameObject.SetActive(isMessageActive);
print($"NPC DETECTED: { isNearNpc }");
In my 2D project I can't seem to get OnMouseUp working while dragging a UI object over it. I want to be able to detect that I dropped the UI object on a non-UI gameobject so I can activate the script that lets the position of the UI object be the same position as the non-UI object each Update.
Currenty I use the IOnDragHandler and IEndDragHandler on the UI object for the dragging process. When I want to drop the UI object on another UI object then IDropHandler helps me, but when I want to drop it on a non-UI GameObject, I want to be able to use the OnMouseUp event, but it doesn't fire.
Whenever I start dragging the object I use a CanvasGroup on the UI object and I make sure that it doesn't get hit by any raycasts.
I also tried making the collider of the non-UI object a trigger but it has no effect. Also, OnMouseOver and OnMouseExit do work when I am dragging the UI element over the non-UI element.
Thanks very much for looking into this. Here comes the code:
So on the UI object (A card) I use this:
public void OnDrag(PointerEventData eventData)
{
if (CanDrag)
{
if (!isDragging)
{
isDragging = true;
if (Slot != null)
{
Slot.GetComponent<FamilyTreePlacementScript>().CurrentFamilyCard = null;
}
if (card is FamilyCard || card is EquipmentCard) UIManagerScript.Instance.ToggleFamilyTree(null, true);
GetComponent<CanvasGroup>().blocksRaycasts = false;
CardManagerScript.CardBeingDragged = gameObject;
DataKeeperScript.Instance.MayDragCamera = false;
CardManagerScript.Instance.DeletePreview();
transform.SetParent(canvas);
}
transform.position = Input.mousePosition;
}
}
public void OnEndDrag(PointerEventData eventData)
{
if (CanDrag)
{
isDragging = false;
GetComponent<CanvasGroup>().blocksRaycasts = true;
CardManagerScript.CardBeingDragged = null;
DataKeeperScript.Instance.MayDragCamera = true;
if (Slot == null)
{
transform.SetParent(defaultParent);
}
else
{
Slot.GetComponent<FamilyTreePlacementScript>().CurrentFamilyCard = card as FamilyCard;
transform.SetParent(Slot);
transform.position = Slot.position;
}
}
}
And on the non-UI object that I want to "drop" the card on, I use:
private void OnMouseUp()
{
if (CardManagerScript.CardBeingDragged)
{
Card card = CardManagerScript.CardBeingDragged.GetComponent<CardObjectScript>().card;
if (card is TakeOverCard)
{
//Check if the card can actually be played
if (Business.Allegiance != CardManagerScript.Instance.PlayerFamily)
{
CurrentTakeOverCard = card;
CardManagerScript.CardBeingDragged.GetComponent<CardObjectScript>().SetWorldObject(transform);
}
}
}
}
Check the 'Queries Hit Triggers' (Project Settings > Physics). It should be set to 'true'
So I have got two scene change scripts, both which are attached to two different game objects. But for some reason, one script affects both game objects. So I am trying to temporarily disable one of the scripts when the other script is active on the other game object. Here is what I have but this is returning the error, "BasketballSceneChange' is a type, which is not valid in the given context".
**GameObject.Find("pPrism1").GetComponent(BasketballSceneChange).enabled = false;**
{
if (Input.GetMouseButtonDown(0) && SceneManager.GetActiveScene().name == "MWalk")
{
SceneManager.LoadScene("BWalk");
}
if (Input.GetMouseButton(0) && SceneManager.GetActiveScene().name == "BWalk")
{
SceneManager.LoadScene("Basketball");
}
if (Input.GetMouseButton(0) && SceneManager.GetActiveScene().name == "Football")
{
SceneManager.LoadScene("SWalk");
}
if (Input.GetMouseButton(0) && SceneManager.GetActiveScene().name == "SWalk")
{
SceneManager.LoadScene("MWalk");
}
}
}
Change this:
GameObject.Find("pPrism1").GetComponent(BasketballSceneChange).enabled = false;
to
var myVariable = GetComponent(BasketballSceneChange) as myBaseClass;
myVariable.enabled = false;
change myBaseClass to class which shows your Scene.
You could avoid using GetComponent just declaring a public variable and assigning it from the inspector.
public AnotherScript theOtherScript;
void Update()
{
if(theOtherScript != null && theOtherScript.enabled)
{
theOtherScript.enabled = false;
}
}
Check this Tutorial
Other way is Finding the gameobject and geting the component as you did.
I've read your code again and I found the error that doesn't compile the line:
GameObject.Find("pPrism1").GetComponent(BasketballSceneChange).enabled = false;
should be
GameObject.Find("pPrism1").GetComponent<BasketballSceneChange>().enabled = false;
I have this script where it would add the SelectMove script at runtime when a character was touched by the user.
public GameObject target;
void Start () {
}
// Update is called once per frame
void Update () {
if(Input.touchCount > 0 || Input.GetTouch(0).phase == TouchPhase.Began){
ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
Debug.DrawRay(ray.origin,ray.direction * 20,Color.red);
if(Physics.Raycast(ray, out hit,Mathf.Infinity)){
Debug.Log(hit.transform.gameObject.name);
target = hit.transform.gameObject;
//Destroy(hit.transform.gameObject);
selectedPlayer();
}
}
}
void selectedPlayer(){
target.AddComponent(Type.GetType("SelectMove"));
}
In the code above, if the user will click Player A, Player A will move using accelerometer. What I need to do is if I click another character, say Player B, I need Player A to stop moving and it is now Player B's time to move. But I seemed not to get what I want. I tried destroying the script by using Destroy(this) or by this code:
if (target != null)
{
var sphereMesh = target.GetComponent<SelectMove>();
Destroy(sphereMesh);
}
But it is still not working.
If I don't destroy the previous added script, if the user clicks for another character, the previous selected player still moves along with the new one.
Is there another way that I could achieve what I needed to do?
public GameObject target = null;
void Update ()
{
if(Input.touchCount > 0 || Input.GetTouch(0).phase == TouchPhase.Began)
{
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
Debug.DrawRay(ray.origin,ray.direction * 20,Color.red);
RaycastHit hit;
if(Physics.Raycast(ray, out hit,Mathf.Infinity))
{
Debug.Log(hit.transform.gameObject.name);
if(this.target != null){
SelectMove sm = this.target.GetComponent<SelectMove>()
if(sm != null){ sm.enabled = false; }
}
target = hit.transform.gameObject;
//Destroy(hit.transform.gameObject);
selectedPlayer();
}
}
}
void selectedPlayer(){
SelectMove sm = this.target.GetComponent<SelectMove>();
if(sm == null){
target.AddComponent<SelectMove>();
}
sm.enabled = true;
}
The code reuses your base, the new part is that you use the previous selected object to disable the SelectMove script (I assume it is the one moving things). Instead of destroying it, it enables/disables so you save that memory consumption.
I do not think Adding and destroying scripts is the convenient method here.
Try this:
1. Add the SelectMove script to all movable players.
2. Add a public property bool IsMoving = false; to the SelectMove script and make an if statement to move if IsMoving is true.
3. Change selectedPlayer() method to instead change the target player IsMoving to true, and set the previous one (or simply all other players) to false.