Recycling a Button for individual object - Unity3D - c#

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.

Related

Using the same script for dealing with different triggers

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

Selecting and deselecting gameobject to change material

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

UI text not updating

I know, so many of people have asked this question. I have gone to all of those posts, and none of the solutions have solved my problem. i am creating a level progress bar , here in this script i am trying to update text (levelNumber) and add +1 to levelNumber every time level is completed, here i am using playerprefs to save levelNumber so data can passes through next levels(scenes). but problem is levelNumber not even updating in Text field , as per script Text_1 should start from 0 and Text_2 from 1 when game starts its showing values which i have given in inspector 1 and 2. to confirm that i have changed values in inspector to 21 and 30 again when game starts its showing 21 and 30. that means UI Text not updating from script .
public class PrograssBar : MonoBehaviour
{
[SerializeField] private Text Text_1;
[SerializeField] private Text Text_2;
private int levelNumber = 0;
[SerializeField] Transform startTransform;
Transform finishTransform;
[SerializeField] Slider slider;
float maxDistance;
void OnEnable()
{
levelNumber = 0;
}
void Start()
{
finishTransform = GameObject.FindGameObjectWithTag("CloneGoal").GetComponent<Transform>();
maxDistance = getDistance();
levelNumber = PlayerPrefs.GetInt("LevelNumber");
}
void Update()
{
if (startTransform.position.z <= maxDistance)
{
float distance = 1 - (getDistance() / maxDistance);
setProgress(distance);
}
}
float getDistance()
{
return Vector3.Distance(startTransform.position, (finishTransform.position - new Vector3(0, 0, 22)));
}
void setProgress(float p)
{
slider.value = p;
}
private void LevelUp()
{
UpdateLevel(levelNumber + 1);
PlayerPrefs.SetInt("LevelNumber", levelNumber);
}
private void UpdateLevel(int levelNumber)
{
this.levelNumber = levelNumber;
Text_1.text = " " +this.levelNumber.ToString();
Text_2.text = " " +(this.levelNumber + 1).ToString();
}
}
The problem is that you never save your levelNumber to prefs during your execution, so your LevelUp function should look like:
private void LevelUp()
{
UpdateLevel(levelNumber + 1);
PlayerPrefs.SetInt("LevelNumber", levelNumber);
PlayerPrefs.Save();
}
See this doc
In the method private void UpdateLevel(int level) you're setting this.levelNumber = levelNumber.
And the attribute of a method is int level.
So you might want to do:
this.levelNumber = level;
And I'm not quite sure where you're calling the method private void LevelUp() since it's private and it's not called anywhere inside this class.

How can I successfully store and load data from a game character?

I was wrote a script that combines whale characters to create a new character. However, it is difficult to save and load data from a list of names, levels and locations of the newly created characters.
Whale data does not seem to be stored and loaded when the game is played.
I'd really thanks it if you could tell me where's wrong.
using system;
using System.Collections;
using System.Collections.General;
using system.IO;
using LitJson;
using UnityEngine;
using Random = UnityEngine.Random;
[System.Serializable]
Public class WhaleData
{
Public in dataLevel{get; set;// whale level
public Vector3 dataMousePosition{get; set;//the whale location
public string dataName{get; set;} //whale name
}
Public class Whale: MonoBehaviour
{
public int level;
Private pool isDrag;
Public GameObject NextPrepab;
Private Vector3 mousePosition;
Public WhaleData whaleData;
Private List[WhaleData] WhaleDatas = new List[WhaleData]();
private pool IsSave; //bool value to check for stored data
void start()
{
IsSave = PlayerPrefs.HasKey ("0_name"); //saved_level Check if a key value named //saved_level exists
//initialize all data values if no save data exists
If (!IsSave)
{
Debug.Log ("No data stored).");
PlayerPrefs.DeleteAll();
}
//recall value if save data exists
else
{
Debug.Log ("Stored Data").");
LoadData();
}
}
Private void Update ()
{
SaveData();
LoadData();
}
private void onMouseDown()
{
isDrag = true;
}
private void OnMouseDrag()
{
if (isDrag)
{
mousePosition.x = Input.mousePosition.x;
mousePosition.y = input.mousePositionY;
mousePosition.z = 10;
//change the mouse coordinates to the screen to world and set the position of this object
gameObject.transform.position = Camera.main.ScreenToWorldPoint(mousePosition);
// store the location of the whale in the whalData.dataMousePosition= gameObject.transform.position; //
}
}
private void onMouseUp()
{
//OverlapSphere : Return from the specified location to the collider array in contact within range
var colliders = Physics.OverlapSphere (transform.position, 0.1f);
foreach (var col in colliders) // arrangement of surrounding contacts
{
If (name!= col. gameObject.name) //if the name is different, go
{
If (level==col.gameObject.GetComponent[Whale>().level) //If the level of contact with the level stored in me is the same,
{
whatData.dataLevel = col.gameObject.GetComponent[Whale>().level; // store the level of the whale in the whalData class
var newWhale = Instantiate(NextPrepab, transform.position, quaternion.identity);
newWhale.name = Random.Range (0f, 200f).ToString(); //name is random.
dateData.dataName = name;// store the name of the whale in the whalData class
SaveData();
print(col.gameObject.name);
print(gameObject.name);
whatDatas.Add(whaleData);
Destroy (col.gameObject);
Destroy (gameObject);
// delete if there is any whale information with the same name as Whale's name that will disappear from the dateDatas list (use a simple calculation expression of the unknown method)
if (whaleDatas.Find(whaleData=>whaleData.dataName=colgameObject.GetComponent[Whale>(.name).dataName==colgameObject.GetComponent[Whale>(.name)
{
whaleDatasRemove (col.gameObject.GetComponent[Whale>(whaleData));
Destroy (col.gameObject);
Destroy (gameObject);
}
else
{
break;
}
break;
}
}
}
isDrag = false;
}
Public static void setVector3 (string key, Vector3 value)
{
PlayerPrefs.SetFloat (key + "X", value.x);
PlayerPrefs.SetFloat (key + "Y", value.y);
PlayerPrefs.SetFloat (key + "Z", value.z);
}
Public static Vector3 GetVector3 (string key)
{
Vector3 v3 = Vector3.zero;
v3.x = PlayerPrefs.GetFloat (key + "X");
v3.y = PlayerPrefs.GetFloat (key + "Y");
v3.z = PlayerPrefs.GetFloat (key + "Z");
return v3;
}
private void saveData()
{
for (int i=0; i <whaleDatas.Count; i++)
{
PlayerPrefs.SetString(i+"_name",whaleDatas[i‐dataName));
PlayerPrefs.SetInt(i+"_level",whaleDatas[i‐dataLevel));
Vector3 value = whatDatas[i].dataMousePosition;
string key = i + "_position";
SetVector3 (key,value);
PlayerPrefs.Save();
Debug.Log ("Saved");
}
}
private void LoadData()
{
for(int i=0; i <whaleDatas.Count; i++)
{
whaleDatas[i].dataName = PlayerPrefs.GetString(i+"_name");
whaleDatas[i].dataLevel = PlayerPrefs.GetInt(i+"_level");
string key = i + "_position";
whaleDatas[i].dataMousePosition = GetVector3(key);
}
}
}
Your code is actually broken in term of design.
First PlayerPref doesnt support boolean save by default.
IsSave = PlayerPrefs.HasKey //this will always return false.
thats why everytime you start the game all your saved data is deleted.
You can try this work around to save and load a boolean type How to save bool to PlayerPrefs Unity
Now, How on earth you decided to save and load data at update function?! , this is terrible and has huge performance cost if your data is huge. you just cant do it this way.
Instead, try to make events that store the data and load it instead, like once player enter a trigger box , the data is saved, and ofcourse you dont need to reload it if its saved , bec why you would have to load the data after its being saved?!!
Once again, dont read and write at Update Function like this all the time, this is bad design .

Error CS0029: Cannot implicitly convert type `UnityEngine.GameObject[]' to `UnityEngine.GameObject'

I get the error on:
lad = GameObject.FindGameObjectsWithTag ("Ladder");
I'm just trying to disable all the ladders in the scene while the alarm is on and then enable them again once the alarm goes away. I'm not sure what the error means but need it fixed as soon as possible. I'm using C# and Unity 5. Thanks for any help you guys can provide!
using UnityEngine;
using System.Collections;
public class LevelAlarm : MonoBehaviour {
public bool alarmOn = false;
public bool bPlayerHidden = false;
// How long an alarm lasts for
[SerializeField]
private float alarmTimer = 5.0f;
private float alarmCurrentTimer = 0.0f;
public GameObject lad;
// Use this for initialization
void Start ()
{
lad = GameObject.FindGameObjectsWithTag ("Ladder");
}
// Update is called once per frame
void Update ()
{
// While alarm is on, run a timer
if (alarmOn)
{
alarmCurrentTimer += Time.deltaTime;
lad.SetActive(false);
// Timer is complete, alarm resets
if(alarmCurrentTimer >= alarmTimer)
{
alarmOn = false;
alarmCurrentTimer = 0.0f;
lad.SetActive(true);
}
}
}
}
GameObject.FindGameObjectsWithTag() returns an array of GameObjects, if you want first object of this array, use this:
void Start ()
{
var objects = GameObject.FindGameObjectsWithTag ("Ladder");
if(objects != null && objects.Length > 0)
{
lad = objects[0];
}
}
It's GameObject.FindGameObjectWithTag, not GameObject.FindGameObjectsWithTag, don't has character "s" after "Object".
Error CS0029: Cannot implicitly convert type UnityEngine.GameObject[]' toUnityEngine.GameObject'
This error simply means that you try to stop alarm whose tag are "Ladder".It creates an array and the function you use accept only one element of utilityEngine.GameObject. So you may use foreach loop to access all elements in the array by using :
public bool alarmOn = false;
public bool bPlayerHidden = false;
// How long an alarm lasts for
[SerializeField]
private float alarmTimer = 5.0f;
private float alarmCurrentTimer = 0.0f;
**public GameObject[] lad;**
// Use this for initialization
void Start ()
{
lad = GameObject.FindGameObjectsWithTag ("Ladder");
}
// Update is called once per frame
void Update ()
{
// While alarm is on, run a timer
if (alarmOn)
{
alarmCurrentTimer += Time.deltaTime;
**foreach(var eachLad in lad)
{
eachLad.SetActive(false);
}**
// Timer is complete, alarm resets
if(alarmCurrentTimer >= alarmTimer)
{
alarmOn = false;
alarmCurrentTimer = 0.0f;
**foreach(var eachLad in lad)
{
eachLad.SetActive(true);
}**
}
}
}

Categories