Is there any simple way to show text notification in front of the camera (while in the game) and make it follow the camera while the player is moving?
We are creating a game where the player can give commands with voice, and when the command is recognized, some text notification must be shown (like toast notifications in Android).
I know about text meshes and that we can put it in front of the camera and show/hide it, but we need it to be done programmatically.
I ended up with creating a class which generates toast on fly.
Code is something like this (initial version, needs small re-factorizations):
using UnityEngine;
public class ToastText3D : MonoBehaviour
{
public static ToastText3D Instance { get; private set; }
private static bool isShowingTextMessage;
private static GameObject textMeshGameObject;
// Preventing instantiation of class by making constructor protoected (or private)
protected ToastText3D() { }
private void Awake()
{
// Check if instance already exists
if (Instance == null)
// if not, set instance to this
Instance = this;
else if (Instance != this)
// Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
Destroy(gameObject);
// Sets this to not be destroyed when reloading scene
DontDestroyOnLoad(gameObject);
}
private void Update()
{
if (!isShowingTextMessage)
return;
var headPosition = Camera.main.transform.position;
var gazeDirection = Camera.main.transform.forward;
float distanceFromCamera = 2; // TODO: Move this to const
Vector3 desiredPosition = headPosition + gazeDirection * distanceFromCamera;
textMeshGameObject.transform.position = desiredPosition;
// Rotate the object to face the user.
Quaternion toQuat = Camera.main.transform.localRotation;
toQuat.x = 0;
toQuat.z = 0;
textMeshGameObject.transform.rotation = toQuat;
}
// Returns current text mesh or creates new one if there is no
private TextMesh getCurrentTextMesh()
{
if (!textMeshGameObject)
textMeshGameObject = new GameObject();
TextMesh curTextMesh = textMeshGameObject.GetComponent(typeof(TextMesh)) as TextMesh;
if (!curTextMesh)
{
// TODO: Make constants
curTextMesh = textMeshGameObject.AddComponent(typeof(TextMesh)) as TextMesh;
MeshRenderer meshRenderer = textMeshGameObject.GetComponent(typeof(MeshRenderer)) as MeshRenderer;
meshRenderer.enabled = true;
curTextMesh.anchor = TextAnchor.MiddleCenter;
curTextMesh.alignment = TextAlignment.Center;
curTextMesh.fontStyle = FontStyle.Bold;
curTextMesh.fontSize = 20;
curTextMesh.transform.localScale = new Vector3(0.02f, 0.02f, 0.02f);
}
return curTextMesh;
}
public void Show3DTextToast(string textToShow, int timeout = 5)
{
if (timeout < 0 || textToShow == "")
return;
TextMesh curTextMesh = getCurrentTextMesh();
curTextMesh.text = textToShow;
textMeshGameObject.SetActive(true);
isShowingTextMessage = true;
// Canceling message hiding invokation if there was any
CancelInvoke("Hide3DTextToast"); // TODO: Move function name to const
// Hiding text if there is any timeout
if (timeout != 0)
Invoke("Hide3DTextToast", timeout);
}
public void Hide3DTextToast()
{
textMeshGameObject.SetActive(false);
isShowingTextMessage = false;
}
}
Now all we need is to call function Show3DTextToast by passing him text and interval.
Example:
ToastText3D.Instance.Show3DTextToast("Text Message", 10);
Related
I want GameTipUI with a blank space for a certain amount of time and certain message(==NormalGuideTip) for a certain amount of time using CoRoutine.
I was thinking of putting an 'if' in the CoRoutine to check the time,so make it easy to empty the message, and display the message again.
However, the message just keeps getting blank.
What could be the problem?
I wanted to repeat the randomly selected game tip message for 5 seconds and the blank message for 7 seconds.
And I wanted to use realtimeForGameTip to immediately display a special message(==UrgentGameTip ) according to a specific game action later and repeat the above process again.
bool isGameTipOn= true;
public GameObject gameTipTitle;
public GameObject gameTipMsg;
Color normalTipColor = new Color(255f, 255f, 255f, 220f);
Color UrgentTipColor = Color.green;
float timeBetGameTip = 7f; //
float timeOfGameMsg = 5f; //
public readonly WaitForSeconds m_waitForSecondsForGameTip = new WaitForSeconds(7f);
float realtimeForGameTip= 0f;
public string[] NormalGuideTips;
public string[] UrgentGuideTips;
private void Start()
{
#region GameTipSetUp
OnOffGameTip(true);
StartCoroutine("GameTipUI");
#endregion GameTipSetUp
}
public void OnOffGameTip(bool OnOff)
{
isGameTipOn = OnOff;
gameTipTitle.gameObject.SetActive(isGameTipOn);
gameTipMsg.gameObject.SetActive(isGameTipOn);
}
private void ResetGameTip()
{
if (!isGameTipOn) return;
realtimeForGameTip = 0f ;
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = NormalGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = normalTipColor;
}
public void UrgentGameTip(string id)
{
if (!isGameTipOn) return;
=
int index = Random.Range(0, NormalGuideTips.Length);
string selectedMsg = UrgentGuideTips[index];
Debug.Log(selectedMsg);
gameTipMsg.GetComponent<Text>().text = selectedMsg;
gameTipMsg.GetComponent<Text>().color = UrgentTipColor;
realtimeForGameTip = timeOfGameMsg;
}
public void MakeTermBetGameTip()
{
gameTipMsg.GetComponent<Text>().text = " blank ";
}
IEnumerator GameTipUI()
{
while (isGameTipOn)
{
if(realtimeForGameTip < timeOfGameMsg)
{
Debug.Log(realtimeForGameTip);
realtimeForGameTip += Time.deltaTime;
continue;
}
MakeTermBetGameTip();
yield return new WaitForSeconds(m_waitForSecondsForGameTip);
ResetGameTip();
}
}
Hello i have 2 scripts one is for Flashlight "Battery UI script" Storing how many batteries i have left
for reload a flashlight max 5 that script is working totally fine but when i try to loot a battery it occur a error
and i dont understand why because i setup input "Use" in Project Settings correctly because another key "Grab" is working fine on Key "Q" but "Use" on Key "F" not working and i getting this error what i do wrong?!
i will paste code bellow with screenshots as well
" Get Error in this line (see screenshot) "
Click for code screenshot error
battery GUI on top right corner 3/5 collected batteries
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class BatteryPickup : MonoBehaviour {
private Transform myTransform;
private GameObject MessageLabel;
private GameObject BatteryUIScript;
public bool EnableMessageMax = true;
public bool Enabled;
public float BatteryAdd = 0.01f;
public AudioClip pickupSound;//sound to playe when picking up this item
public string MaxBatteryText = "You have Max Batteries";
public Color MaxBatteryTextColor = Color.white;
public bool PickupMessage;
public string PickupTEXT = "Battery +1";
public Color PickupTextColor = Color.white;
void Start () {
myTransform = transform;//manually set transform for efficiency
}
public void UseObject (){
BatteryUIScript = GameObject.Find("Flashlight");
BatteryUI BatteryComponent = BatteryUIScript.GetComponent<BatteryUI>();
if (BatteryComponent.EnableBattery == true)
{
Enabled = true;
}
if(BatteryComponent.EnableBattery == false){
Enabled = false;
if(EnableMessageMax){StartCoroutine(MaxBatteries());}
}
if(Enabled){
StartCoroutine(SendMessage());
BatteryComponent.Batteries += BatteryAdd;
if(pickupSound){AudioSource.PlayClipAtPoint(pickupSound, myTransform.position, 0.75f);}
this.GetComponent<Renderer>().enabled = false;
this.GetComponent<Collider>().enabled = false;
}
}
public IEnumerator SendMessage (){
MessageLabel = GameObject.Find("UI_MessageLabel");
Text Message = MessageLabel.GetComponent<Text>();
/* Message Line */
EnableMessageMax = false;
Message.enabled = true;
Message.color = PickupTextColor;
Message.text = PickupTEXT;
yield return new WaitForSeconds(2);
Message.enabled = false;
EnableMessageMax = true;
}
public IEnumerator MaxBatteries (){
MessageLabel = GameObject.Find("UI_MessageLabel");
Text Message = MessageLabel.GetComponent<Text>();
/* Message Line */
if(!Enabled){
EnableMessageMax = false;
Message.enabled = true;
Message.color = MaxBatteryTextColor;
Message.text = MaxBatteryText;
yield return new WaitForSeconds(3);
Message.CrossFadeAlpha(0f, 2.0f, false);
yield return new WaitForSeconds(4);
Message.enabled = false;
EnableMessageMax = true;
}
}
}
I'm trying to animate the button while it is being gazed on. I have got the following code in which I Raycast from a sphere to find the button that it hits.
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
eventDataCurrentPosition.position = screenPosition;
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
foreach (var result in results)
{
Debug.Log(result.gameObject.name);
}
In order to animate the button, I'm adding the following code to the button. Unfortunately, the OnPointerEnter or ``OnPointerExit is never being called.
[RequireComponent(typeof(Button))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
public Image progressImage;
public bool isEntered = false;
RectTransform rt;
Button _button;
float timeElapsed;
Image cursor;
float GazeActivationTime = 5;
// Use this for initialization
void Awake()
{
_button = GetComponent<Button>();
rt = GetComponent<RectTransform>();
}
void Update()
{
if (isEntered)
{
timeElapsed += Time.deltaTime;
progressImage.fillAmount = Mathf.Clamp(timeElapsed / GazeActivationTime, 0, 1);
if (timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
_button.onClick.Invoke();
progressImage.fillAmount = 0;
isEntered = false;
}
}
else
{
timeElapsed = 0;
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("entered");
isEntered = true;
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit(PointerEventData eventData)
{
CodelabUtils._ShowAndroidToastMessage("exit");
isEntered = false;
progressImage.fillAmount = 0;
}
#endregion
}
am I missing something ? or is there any other way of achieving this?
I guess your Raycast supposed to trigger the OnPointerEnter on all results.
You will need to use ExecuteEvents.Execute like e.g.
ExecuteEvents.Execute(result.gameObject, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
And will also at some point have to invoke pointerExitHandler.
I would therefore store a HashSet like
using System.Linq;
private HashSet<GameObject> previousEnters = new HashSet<GameObject>();
...
foreach (var result in results.Select(r => r.gameObject))
{
Debug.Log(result.name);
ExecuteEvents.Execute(result, eventDataCurrentPosition, ExecuteEvents.pointerEnterHandler);
// Store the item so you can later invoke exit on them
if(!previousEnters.Contains(result)) previousEnters.Add(result);
}
// This uses Linq in order to get only those entries from previousEnters that are not in the results
var exits = previousEnters.Except(results);
foreach(var item in exits)
{
if(item) ExecuteEvents.Execute(item, eventDataCurrentPosition, ExecuteEvents.pointerExitHandler);
}
You might actually want to implement your own custom PointerInputModule.
Alternative/Example
As a starter you could also use my answer to Using Raycast instead of Gaze Pointer from a while ago where I created a script based on Steam's VR Laserpointer which allows to interact with 3D objects and UI elements.
Note: Typed on smartphone but I hope the idea gets clear
I'm writing a script for locking scale of objects in Unity.
Since objectTransformScale = objectTransform.localScale.
Changes made on objectTransformScale should also affect objectTransform.localScale, but it doesn't.
Hence I have to set the value back as objectTransform.localScale = objectTransformScale;
Why doesn't it work?
public string demension;
private Transform objectTransform;
private Vector3 objectTransformScale;
private float originalX;
private float originalY;
private float originalZ;
// Use this for initialization
void Start () {
objectTransform = GetComponent<Transform>();
objectTransformScale = objectTransform.localScale;
originalX = objectTransformScale.x;
originalY = objectTransformScale.y;
originalZ = objectTransformScale.z;
}
// Update is called once per frame
void Update () {
objectTransformScale = objectTransform.localScale;
if (demension.Equals("x"))
{
objectTransformScale.x = originalX;
}
else if(demension.Equals("y"))
{
objectTransformScale.y = originalY;
}
else if(demension.Equals("z"))
{
objectTransformScale.z = originalZ;
}
else if (demension.Equals("a"))
{
objectTransformScale.z = originalZ;
objectTransformScale.y = originalY;
objectTransformScale.x = originalX;
}
//The scale of object won't be locked if I command the line below.
objectTransform.localScale = objectTransformScale;
}
objectTransformScale = Vector3(ref objectTransform.localScale);
Structs are value types so they just pass the data without including a reference to themselves. You were essentially making a copy of local scale and editing the copy. Using ref in a constructor makes sure the two are linked.
For instance, I found out that the Unity plugin "Octave3d Level Design" has this feature
I hope to make my own prefab manager, since Octave3d prefab manager is not fit for my need. So the question is: when mouse is overlapping Scene View, how to make mouse seize a object and put it in Scene View?
Edit:
Actually found out that you'll need to use SceneView.lastActiveSceneView.camera as your camera, and you can use Gizmos.DrawMesh() to draw the mesh instead of instantiating..
There's also SceneView.OnSceneGUIDelegate that can help you access the scene view.
here's some sample code on how to set that up
void OnGUI() {
if (objectThatFollowsMouse != null) {
SceneView.onSceneGUIDelegate -= OnSceneGUI;
SceneView.onSceneGUIDelegate += OnSceneGUI;
}
else { SceneView.onSceneGUIDelegate -= OnSceneGUI; }
}
void OnSceneGUI(SceneView sceneView) {
...
}
with some additional editor scripting, you can do something like this
Vector3 mousePoint = Camera.main.ViewportToWorldPoint(Input.mousePosition);
if (objectShouldFollowMouse) {
objectThatFollowsMouse.transform.position = mousePoint;
if (Event.current.type == EventType.MouseUp) {
objectShouldFollowMouse = false;
objectThatFollowsMouse = null;
}
}
if (prefabGotClicked) {
GameObject obj = Object.Instantiate(someObject,mousePoint,Quaternion.identity) as GameObject;
objectShouldFollowMouse = true;
objectThatFollowsMouse = obj;
}
In Octave3d, they implemented this function by:
When select the prefab in the custom editor window, instantiate a preview gameobject of the prefab in Scene, the preview gameobject will follow the mouse (by their custom raycast detection). The preview gameobject's parent is the octave manager or something like this.
When click in the scene, instantiate another gameobject in the scene at the mouse-scene intersection point.
And I think it was a bit overkill, something like Unity's own DragAndDrop is enough: a prefab can be drag from custom editorwindow, and drop on sceneview, hierarchy or object field. And this CUSTOM drag and drop can be implement like this:
In your custom editor window, listen to MouseDownEvent to start the DragAndDrop
In your custom editor window, listen to DragUpdatedEvent to move the dragging object
If you want to detect the Dropping on SceneView or Hierarchy, listen to DragPerform and DragExited in there ongui callback.
Code is here:
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class MyWindow : EditorWindow
{
[MenuItem("Window/MyWindow")]
private static void ShowWindow()
{
GetWindow<MyWindow>("MyWindow");
}
private GameObject prefab;
private bool m_IsDragPerformed = false;
private bool m_IsDragging = false;
private void OnEnable()
{
prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Cube.prefab");
var box = new VisualElement();
box.style.backgroundColor = Color.red;
box.style.flexGrow = 1f;
box.RegisterCallback<MouseDownEvent>(evt =>
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.StartDrag("Dragging");
DragAndDrop.objectReferences = new Object[] { prefab };
Selection.activeGameObject = null;
m_IsDragPerformed = false;
m_IsDragging = true;
});
box.RegisterCallback<DragUpdatedEvent>(evt =>
{
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
});
rootVisualElement.Add(box);
SceneView.duringSceneGui += sv => OnDragEnd();
EditorApplication.hierarchyWindowItemOnGUI += (id, rect) => OnDragEnd();
}
private void OnDragEnd()
{
if (Event.current.type == EventType.DragPerform)
{
m_IsDragPerformed = true;
}
if (Event.current.type == EventType.DragExited)
{
if (m_IsDragging && m_IsDragPerformed)
{
m_IsDragging = false;
m_IsDragPerformed = false;
var go = Selection.activeGameObject;
// Do your **OnDragEnd callback on go** here
}
}
}
private void OnDestroy()
{
SceneView.duringSceneGui -= sv => OnDragEnd();
EditorApplication.hierarchyWindowItemOnGUI -= (id, rect) => OnDragEnd();
}
}
Reference is here, this guy is a genius: How to start DragAndDrop action so that SceneView and Hierarchy accept it like with prefabs