I am having some issues with my code.
I am trying to create a highlighting feature so a player can click on a game object and see what they have selected.
I would like it to stay highlighted
till they select another object.
when they select another object the default material would be
applied back to the previous gameobject. and it
would continue for each object them
select and deselect.
can anyone help me. I've tried a few things but it just keeps the color the same.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SelectionManager : MonoBehaviour
{
[SerializeField] private string selectableTag = "Selectable";
[SerializeField] private Material highlightMaterial;
private Material originalMaterial;
private GameObject selected;
private GameObject oldSelected;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Renderer selectionRenderer;
//change the color to the highlight
if (Input.GetMouseButtonDown(0))
{
//if selected set to highlight color
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 300.0f))
{
selected = hit.transform.gameObject;
Debug.Log("1111 new Selection --> " + selected);
if (selected.CompareTag(selectableTag))
{
if(selected != oldSelected || oldSelected == null)
{
//save the original material
selectionRenderer = selected.GetComponent<Renderer>();
originalMaterial = selectionRenderer.material;
selectionRenderer.material = highlightMaterial;
Debug.Log("Old Selection --> " + oldSelected);
Debug.Log("new Selection --> " + selected);
}
else
{
oldSelected = selected;
Debug.Log("you have selected a new building");
oldSelected.GetComponent<Renderer>().sharedMaterial = originalMaterial;
}
//comment 2
}
}
//comment 3
}
//comment1
}
void ClearSelection()
{
if (selected == null)
{
return;
}
else
{
selected.GetComponent<Renderer>().sharedMaterial = originalMaterial;
selected = null;
}
}
}
So the problem is basically that you are not assign oldSelected correctly.
Fixing this, your code should look like:
using UnityEngine;
public class SelectionManager : MonoBehaviour
{
[SerializeField] private string selectableTag = "Selectable";
[SerializeField] private Material highlightMaterial;
private Material originalMaterial;
private GameObject selected = null;
private GameObject oldSelected = null;
private Renderer selectionRenderer;
private Renderer oldSelectionRenderer;
// Update is called once per frame
void Update()
{
//change the color to the highlight
if (Input.GetMouseButtonDown(0))
{
//if selected set to highlight color
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 300.0f))
{
selected = hit.transform.gameObject;
Debug.Log("1111 new Selection --> " + selected);
if (selected.CompareTag(selectableTag))
{
//newSelection
if(selected != oldSelected)
{
//Set original material to oldSelected if it's not the first selection
if(oldSelected != null)
{
oldSelectionRenderer = oldSelected.GetComponent<Renderer>();
oldSelectionRenderer.material = originalMaterial;
}
//Set highlighted material to selected
selectionRenderer = selected.GetComponent<Renderer>();
originalMaterial = selectionRenderer.material;
selectionRenderer.material = highlightMaterial;
//assign oldSelected GO to your current selection
oldSelected = selected;
}
}
}
}
}
}
Related
I am a beginner in Unity and I am currently making a simple game. I have a problem where the button should reappear on the object I am getting contact with. It only shows on the first object that I put the script on.
What I wanted to do is to have a recyclable button that will appear whenever I have contact with objects like the "view" button in most of the games. What keeps happening in my project is that the collision is triggered but the position of the object is in the first object where I put the script to set it active and set the position. I put it in another object because I want the button to show in that object but it keeps appearing in the first object.
This is the script I used:
using UnityEngine;
using UnityEngine.UI;
public class InteractNPC : MonoBehaviour
{
//public Button UI;
[SerializeField] GameObject uiUse, nameBtn, dialogBox;
[SerializeField] TextAsset characterData;
// [SerializeField] UnityEngine.UI.Text dialogMessage;
private Transform head;
private Vector3 offset = new Vector3(0, 1.0f, 0);
// Start is called before the first frame update
void Start()
{
//uiUse = Instantiate(UI, FindObjectOfType<Canvas>().transform).GetComponent<Button>();
uiUse.gameObject.SetActive(true);
head = this.transform.GetChild(0);
uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
nameBtn.GetComponent<Button>().onClick.AddListener(onNameClick);
}
// Update is called once per frame
void Update()
{
uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
}
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
//Character characterJson = JsonUtility.FromJson<Character>(characterData.text);
nameBtn.gameObject.SetActive(true);
// Text lbl = nameBtn.gameObject.GetComponentInChildren(typeof(Text), true) as Text;
// lbl.text = "BOBO";
// nameBtn.GetComponent<Button>().GetComponentInChildren<Text>().text = characterJson.name;
}
}
private void OnTriggerExit(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.gameObject.SetActive(false);
}
}
// DIALOGUE SYSTEM
public void onNameClick()
{
Text dialogMessage, dialogName;
if (dialogBox.gameObject.activeInHierarchy)
{
dialogName = GameObject.Find("Canvas/DialogBox/DialogueName").GetComponent<Text>();
dialogMessage = GameObject.Find("Canvas/DialogBox/Dialogue").GetComponent<Text>();
if (dialogMessage != null && dialogName != null)
{
loadCharacterData(dialogMessage, dialogName);
Debug.Log("not null dialog message");
}
else
{
Debug.Log("null dialog message");
}
}
}
public void loadCharacterData(Text dialogMessage, Text dialogName)
{
Character characterJson = JsonUtility.FromJson<Character>(characterData.text);
dialogName.text = characterJson.name;
dialogMessage.text = characterJson.dialogs[0].choices[1];
}
}
Well your issue is that each and every instnce of your scrip will all the time overwrite
void Update()
{
uiUse.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
nameBtn.transform.position = Camera.main.WorldToScreenPoint(head.position + offset);
}
every frame.
What you want is set it only once in
[SerializeField] private Button nameBtn;
pivate Character character;
private void Start()
{
...
// do this only once!
character = JsonUtility.FromJson<Character>(characterData.text);
}
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.gameObject.SetActive(true);
var lbl = nameBtn.gameObject.GetComponentInChildren<Text>(true);
lbl.text = characterJson.name;
var position = Camera.main.WorldToScreenPoint(head.position + offset);
uiUse.transform.position = position;
nameBtn.transform.position = position;
}
}
Further you will have another issue: Each and every instance of your script attaches a callback
nameBtn.GetComponent<Button>().onClick.AddListener(onNameClick);
that is not good! Now whenever you click the button once, the allback is fired for each and every instance of your script.
You would rather do this only if the click was actually invoked for the current object.
For this you could attach the listener only add-hoc when needed like e.g.
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player"))
{
...
nameBtn.onClick.RemoveListener(onNameClick);
nameBtn.onClick.AddListener(onNameClick);
}
}
private void OnTriggerExit(Collider other)
{
if (collisionInfo.CompareTag("Player"))
{
nameBtn.onClick.RemoveListener(onNameClick);
nameBtn.gameObject.SetActive(false);
}
}
ok we didn't know before your objects move
so well you will need to go back to what you had before and update the position continuously, BUT only while you are the current active object so e.g.
private static InteractNPC buttonOwner;
private Camera mainCamera;
void Update()
{
if(buttonOwner != this) return;
if(!mainCamera) mainCamera = Camera.main;
var position = mainCamera.WorldToScreenPoint(head.position + offset);
uiUse.transform.position = position;
nameBtn.transform.position = position;
}
private void OnTriggerEnter(Collider collisionInfo)
{
if (collisionInfo.CompareTag("Player") && !buttonOwner)
{
...
buttonOwner = this;
}
}
private void OnTriggerExit(Collider other)
{
if (collisionInfo.CompareTag("Player") && buttonOwner == this)
{
nameBtn.onClick.RemoveListener(onNameClick);
nameBtn.gameObject.SetActive(false);
buttonOwner = null;
}
}
As far as I see you never create a new Button, I guess that Unity may think, you want the same Button on all NPCs.
Maybe try to call the constructor for a new Button in the Start Method and see if it then generates a new Button per NPC.
I have a scene with multiple triggers and a Player with collider. My idea is to have one script to deal with all interactions between triggers and Player collider. I thought that using the same script to different GameObjects would be enough but this thing doesn't work properly. While working with one trigger I need to change one GameObject attached to this trigger but the other GameObjects with this script are changing as well.
One more detail about script is that I have two types of triggers: one is for GameObjects that need some input from user, some doesn't need it. I check it by checking two fields firstChoiceText and secondChoiceText. And if they are empty, the interaction with GameObject doesn't need any choice from the player. Otherwise, I show a plane with two option and change the text of this options using firstChoiceTextfield and secondChoiceTextfield.
So the main idea of script is that when the Player enter the trigger the script activates E key of keyboard. And if we press the E button and if this interaction wasn't done yet, we go further. We check the type of trigger and if we need to make a choice, we activate so called choiceMode of PlayerScript. PlayerScript is just a script for movements of the player, and we need choiceMode just to use W and S keys to select the choice. We make our choice and return to usual movement of the player. If we don't need to make a choice we just do some action and mark the trigger as isDone.
Everything works great with one trigger but when I use several triggers and GameObjects with InteractionScript, all this triggers are mixed up. While working with one trigger I accidentally change the other script. And I don't quite understand how to separate this scripts from each other.
Can anybody give me some piece of advice?
using UnityEngine;
using UnityEngine.UI;
public class InteractionScript : MonoBehaviour
{
[SerializeField]
private GameObject buttonE;
[SerializeField]
private GameObject choiceMenu;
[SerializeField]
private PlayerScript ps;
private int choiceNum = 0;
[SerializeField]
private Transform choicePoint;
[SerializeField]
private Text firstChoiceTextfield;
[SerializeField]
private Text secondChoiceTextfield;
[SerializeField]
private string firstChoiceText;
[SerializeField]
private string secondChoiceText;
[SerializeField]
private bool enteredMe;
[SerializeField]
private bool isDone;
void OnTriggerEnter2D() {
if (!isDone) {
enteredMe = true;
buttonE.SetActive(true);
Debug.Log("++");
}
}
void OnTriggerExit2D() {
if (!isDone) {
buttonE.SetActive(false);
enteredMe = false;
}
}
void Update() {
if (!isDone) {
if (Input.GetKeyDown(KeyCode.E) && buttonE.activeSelf && enteredMe) {
if (firstChoiceText != "" && secondChoiceText != "") {
choiceMenu.SetActive(true);
buttonE.SetActive(false);
ps.choiceMode = true;
Debug.Log("with choice");
} else {
Debug.Log("without choice");
//Action
buttonE.SetActive(false);
isDone = true;
}
}
}
if (ps.choiceMode) {
firstChoiceTextfield.text = firstChoiceText;
secondChoiceTextfield.text = secondChoiceText;
if (Input.GetKeyDown(KeyCode.W)) {
choiceNum += 1;
}
if (Input.GetKeyDown(KeyCode.S)) {
choiceNum -= 1;
}
if (choiceNum > 1) {
choiceNum = 0;
} else if (choiceNum < 0) {
choiceNum = 1;
}
if (choiceNum == 0) {
choicePoint.localPosition = new Vector3(0f, 24f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice1");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
} else if (choiceNum == 1) {
choicePoint.localPosition = new Vector3(0f, -21.5f, 0f);
if (Input.GetKeyDown(KeyCode.Return)) {
Debug.Log("choice2");
choiceMenu.SetActive(false);
isDone = true;
enteredMe = false;
ps.choiceMode = false;
}
}
}
}
}
Im working on an 2d local multiplayer platformer Game. In the game are obstacles (Spikes), when the player collides with them the player will die. I want the Players of the game to decide if they would like to enable or disable the Spikes (the link leads to an image that will help understanding my problem)by pressing a Key. I already wrote a script for that and added it to my dontdestroyOnLoad GameManager. So all my spikes I built are the same Prefabs. My idea was to disable the main Prefab from the Project folder to disable all the spikes in every scene, until you press a Key to reactivate them again. The Problem is, that only the texture itself in the Project Panel disables and not the Spikes Prefabs in the Hierarchy, because the prefabs turn into regular gameObjects. How can I fix this?
My Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//using System.Threading;
public class DisableSpikes : MonoBehaviour
{
[Header("Spikes")]
public KeyCode disableSpikes;
public float time;
public GameObject prefabSpikes;
public bool toggleSpikes = true;
[Header("Player Green")]
public KeyCode disableGreen;
public GameObject prefabGreen;
public bool toggleGreen = true;
[Header("Reset Score")]
public KeyCode resetScore;
// Start is called before the first frame update
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(disableSpikes) && toggleSpikes == true)
{
prefabSpikes.SetActive(false);
Debug.Log("Disable");
//Thread.Sleep(1000);
Invoke("SetFalse", time);
}
if (Input.GetKeyDown(disableSpikes) && toggleSpikes == false)
{
prefabSpikes.SetActive(true);
Debug.Log("Anable");
//Thread.Sleep(1000);
Invoke("SetTrue", time);
}
if (Input.GetKeyDown(disableGreen) && toggleGreen == true)
{
prefabGreen.SetActive(false);
Debug.Log("Disable");
//Thread.Sleep(1000);
Invoke("SetFalse", time);
}
if (Input.GetKeyDown(disableGreen) && toggleGreen == false)
{
prefabGreen.SetActive(true);
Debug.Log("Anable");
//Thread.Sleep(1000);
Invoke("SetTrue", time);
}
if (Input.GetKeyDown(resetScore))
{
ScoreScriptBlue.scoreValueBlue = 0;
ScoreScriptRed.scoreValueRed = 0;
ScoreScriptGreen.scoreValueGreen = 0;
RoundScript.scoreValueRound = 0;
TimeScript.scoreValueTime = 0;
}
}
public void SetFalse()
{
toggleGreen = false;
toggleSpikes = false;
}
public void SetTrue()
{
toggleGreen = true;
toggleSpikes = true;
}
}
void Update() {
if (Input.GetKeyDown(disableSpikes) && toggleSpikes == true){
// show
// renderer.enabled = true;
gameObject.GetComponent<Renderer>().enabled = true;
}
if (Input.GetKeyDown(disableSpikes) && toggleSpikes == false) {
// hide
// renderer.enabled = false;
gameObject.GetComponent<Renderer>().enabled = false;
}
}
I am building an AR application that spawns a 3d model when user taps the screen on to the ground plane.
But the issue I am facing is that, I have placed a button at the bottom of the screen to take a screenshot. On clicking the button,it does take screenshots,but it spawns an image below the button also.
How can I avoid this ? Can I use raycast to detect a button,and not spawn the object there ?
Below is the code I use to place objects
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.Experimental.XR;
using System;
public class ArTapToPlace : MonoBehaviour
{
public GameObject objectToPlace;
public GameObject placementIndicator;
private ARSessionOrigin arOrigin;
private Pose placementPose;
private bool placementPoseIsValid = false;
void Start()
{
arOrigin = FindObjectOfType<ARSessionOrigin>();
}
void Update()
{
UpdatePlacementPose();
UpdatePlacementIndicator();
if (placementPoseIsValid && Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
{
PlaceObject();
}
}
private void PlaceObject()
{
Instantiate(objectToPlace, placementPose.position, placementPose.rotation);
}
private void UpdatePlacementIndicator()
{
print(placementIndicator);
if (placementPoseIsValid)
{
placementIndicator.SetActive(true);
placementIndicator.transform.SetPositionAndRotation(placementPose.position, placementPose.rotation);
}
else
{
placementIndicator.SetActive(false);
}
}
private void UpdatePlacementPose()
{
var screenCenter = Camera.current.ViewportToScreenPoint(new Vector3(0.5f, 0.5f));
var hits = new List<ARRaycastHit>();
arOrigin.GetComponent<ARRaycastManager>().Raycast(screenCenter, hits, UnityEngine.XR.ARSubsystems.TrackableType.Planes);
print(hits.Count);
print(hits);
placementPoseIsValid = hits.Count > 0;
if (placementPoseIsValid)
{
placementPose = hits[0].pose;
var cameraForward = Camera.current.transform.forward;
var cameraBearing = new Vector3(cameraForward.x, 0, cameraForward.z).normalized;
placementPose.rotation = Quaternion.LookRotation(cameraBearing);
}
}
}
Depending on how your UI is setup you can probably use EventSystems.EventSystem.IsPointerOverGameObject for checking if the pointer is over a UI element and skip the placing in this case
if (placementPoseIsValid && Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
{
if(EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId))) return;
PlaceObject();
}
So I'm trying to mute a specific audio source once my death menu pops up ingame. I tried with:
public void toggleEndMenu(float score)
{
GameObject jumpGameObject = GameObject.Find("Jump");
if (jumpGameObject != null)
{
AudioSource jumpAudio = jumpGameObject.GetComponent<AudioSource>();
if (jumpAudio != null)
{
jumpAudio.mute = true;
}
}
gameObject.SetActive(true);
scoreText.text = ((int)score).ToString();
isShown = true;
This did not work the audiosource does not get muted, maybe it cannot find the gameobject or something?