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
Related
In my Unity project I have an Empty GameObject which has 3 child GameObjects. The Empty GameObject is called Cable Strip and if the camera move to a special position, I want that the material of the three child Object start to change from white to yellow and back. Unfortunately all my solutions does not work.
This is what I have so far:
using UnityEngine
public class BlinkingObject : MonoBehaviou
{
public GameObject CableStrip;
public Material white, yellow;
public GameObject camManager; //Empty GameObject with the Main Camera
private Vector3 camPosition;
bool blinkObject;
void Update()
{
if(camManager.transform.position == camPosition)
{
InvokeRepeating("Blink", 0, 1);
}
}
public void Blink()
{
Renderer[] colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
foreach (var cableChild in colorCable)
{
if(blinkObject)
{
cableChild.material = yellow;
blinkObject = false;
} else
{
cableChild.material = white;
blinkObject = true;
}
}
}
}
How can I get my gameobject to blink in a nice way?
Edit
Sorry wrong definition of does not work. The problem is, that the color changes too fast. Even if I change the seconds by InvokeRepeating nothing changes. It still changes the color in milliseconds even if I write InvokeRepeating("Blink", 0, 10);.
Problem:
InvokeRepeating("Blink", 0, 1);
is called multiple times. So every frame the condition is fulfilled you add a new repeating invoke ... ending up with probably thousands of parallel invocations that are always repeating.
What you want to do instead is enabling the blink when the condition is fulfilled and disable it when not. I wouldn't use Invoke or InvokeReapting in this case. You can achieve it by simply using a timer:
private float timer;
private bool blinkObject;
private Renderer[] colorCable;
private void Awake()
{
// you want to do this only once!
colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
}
private void Update()
{
HandleBlink();
}
// As suggested by Cid you can ofcourse move it all to a method
// so you don't disturb other things happening in Update
private void HandleBlink()
{
// is the condition fulfilled?
var doBlink = camManager.transform.position == camPosition;
// if not do nothing
if(!doBlink) return;
// reduce the timer by time passed since last frame
timer -= Time.deltaTime;
// if timer not under 0 do nothing else
if(timer > 0) return;
// If timer exceeded invert the blinkObject flag
blinkObject = !blinkObject;
foreach (var cableChild in colorCable)
{
// Shorthand for if(blinkObject) { ... = yellow; } else { ... = white; }
cableChild.material = blinkObject ? yellow : white;
}
// and reset the timer
timer = 1f;
}
Alternatively the same thing could also be achieved by using a Coroutine which is often a bit cleaner (but that's opinions):
// flag for preventing concurrent routines
private bool isBlinking;
private Renderer[] colorCable;
private void Awake()
{
// you want to do this only once!
colorCable = CableStrip.GetComponentsInChildren<Renderer>(true);
}
private void Update()
{
// is the condition fulfilled?
if(camManager.transform.position == camPosition)
{
if(!isBlinking) StartCoroutine(BlinkRoutine());
}
else
{
StopAllCoroutines();
isBlinking = false;
}
}
private IEnumerator BlinkRoutine()
{
isBlinking = true;
var blinkObject = false;
// this looks scary but is fine in a Coroutine
// as long as you YIELD somewhere inside!
while(true)
{
blinkObject = !blinkObject;
foreach (var cableChild in colorCable)
{
// Shorthand for if(blinkObject) { ... = yellow; } else { ... = white; }
cableChild.material = blinkObject ? yellow : white;
}
yield return new WaitForSeconds(1);
}
}
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
So I have a canvas which doesnt get destroyed because it holds and slider whih shows a progressbar. It also has a button which is interactable when the progress of loading is 0.9.. The problem i am having is when I click the button (see activateNewScene()). Basically what happens is when I click on the button is that i load my scene and my disable my canvas. But the problem is that after I disable my canvas the old scene is still shown for about 0.5 sec and than the new scene is loaded. What I want is that after the canvas gets disabled the new scene should show up. without seeing the old scene for short time.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SceneLoaderGameObject : MonoBehaviour {
private SceneLoader sl;
public Canvas cav;
void Start () {
sl = new SceneLoader ();
cav.GetComponent<Canvas> ().enabled = false;
DontDestroyOnLoad (this.gameObject);
DontDestroyOnLoad (cav.transform.parent);
}
public void setNewNameAndLoadScene(string name){
if (cav != null) {
Slider slid = cav.transform.GetChild (1).GetComponent<Slider> ();
Text tx = cav.transform.GetChild (2).GetComponent<Text> ();
Button bttn = cav.transform.GetChild (3).GetComponent<Button> ();
sl.setNewScene (name);
sl.setSliderAndTextToChange (slid, tx);
bttn.onClick.AddListener (() => activateNewScene ());
cav.GetComponent<Canvas> ().enabled = true;
cav.GetComponent<Canvas> ().sortingOrder = 1;
StartCoroutine (sl.LoadAsynchron (name, bttn));
}
}
public void activateNewScene(){
sl.AsgetOP().allowSceneActivation=true;
cav.GetComponent<Canvas> ().sortingOrder = 1;
cav.GetComponent<Canvas> ().enabled = false;
}
}
EDIT: Here is the code which loads the scene:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class SceneLoader {
AsyncOperation operation;
string sceneName;
private Text txx =null;
private Slider slider=null;
public SceneLoader(){
}
public void setSliderAndTextToChange(Slider sl, Text tx){
txx = tx;
slider = sl;
}
public void setNewScene(string sceneName){
if (sceneName.Length == 0) {
throw new UnityException ("Please enter a name");
}
this.sceneName = sceneName;
}
public IEnumerator LoadAsynchron(string myMain, Button bttn){
operation = SceneManager.LoadSceneAsync (myMain);
operation.allowSceneActivation = false;
while (operation.isDone == false) {
float progress = Mathf.Clamp01 (operation.progress / 0.9f);
slider.value = progress;
txx.text = progress * 100f + " %";
if (progress * 100f == 100f) {
bttn.interactable = true;
}
yield return null;
}
}
public AsyncOperation AsgetOP(){
return operation;
}
}
I see. I think its because of the canvas. Try to put the code where you disable the canvas and set the order to 0 in the function OnLevelWasLoaded . So basically this:
void OnLevelWasLoaded(){
cav.GetComponent<Canvas> ().sortingOrder = 0;
cav.GetComponent<Canvas> ().enabled = false;
}
public void activateNewScene(string name){
sl.AsgetOP ().allowSceneActivation = true;
Scene sc = SceneManager.GetSceneByName(name);
if (sc.IsValid ()) {
SceneManager.SetActiveScene(sc);
}
}
I added the scene name to the function to activate it.
When SceneManager was first released, it is known that isDone will only be true in when two conditions are met:
1.When the scene is done loading
2.When scene is activated
I don't know if this is still true but I will assume it is.
Basically, when you set allowSceneActivation to false, condition 2 is not met and you will run into issues. It looks like while (operation.isDone == false) will still be executing causing bttn.interactable = true; to also be executing every frame while you are trying to load new scene. This is my guess.
Replace while (operation.isDone == false) with while (operation.progress < 0.9f) then add bttn.interactable = true; to the end of that function just in-case.
public IEnumerator LoadAsynchron(string myMain, Button bttn)
{
operation = SceneManager.LoadSceneAsync(myMain);
operation.allowSceneActivation = false;
while (operation.progress < 0.9f)
{
float progress = Mathf.Clamp01(operation.progress / 0.9f);
slider.value = progress;
txx.text = progress * 100f + " %";
if (progress * 100f == 100f)
{
bttn.interactable = true;
}
yield return null;
}
if (!bttn.interactable)
bttn.interactable = true;
}
If that does not work, I suggest you use SceneManager.SetActiveScene function. See the enableScene function from this post. I've also seen bugs with this, you may want to file for bug report from the Editor if this is still a problem.
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);
I got some trouble with my unity cardboard app. May some of you guys can help me.
I have build a little Island with Animations and A second island as a main menu.
So when the apps starts, you see the Island from above and the Logo of the App.
When the user pull down the magnet button on side the app will starts another level.
I used this scripts:
http://www.andrewnoske.com/wiki/Unity_-_Detecting_Google_Cardboard_Click
Detecting Google Cardboard Magnetic Button Click - Singleton Implementation
CardboardMagnetSensor.cs and CardboardTriggerControlMono.cs
I created a script in my asset folder(CardboardMagnetSensor.cs) like in the description from Link. Than I created a second script(CardboardTriggerControlMono.cs) like in the discription an dragged it onto my CardboardMain in may Projekt.
The CardboardTriggerControlMono.cs looks like:
using UnityEngine;
using System.Collections;
public class CardboardTriggerControlMono : MonoBehaviour {
public bool magnetDetectionEnabled = true;
void Start() {
CardboardMagnetSensor.SetEnabled(magnetDetectionEnabled);
// Disable screen dimming:
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void Update () {
if (!magnetDetectionEnabled) return;
if (CardboardMagnetSensor.CheckIfWasClicked()) {
Debug.Log("Cardboard trigger was just clicked");
Application.LoadLevel(1);
CardboardMagnetSensor.ResetClick();
}
}
}
The CarboardMagnetSensor:
using UnityEngine;
using System.Collections.Generic;
public class CardboardMagnetSensor {
// Constants:
private const int WINDOW_SIZE = 40;
private const int NUM_SEGMENTS = 2;
private const int SEGMENT_SIZE = WINDOW_SIZE / NUM_SEGMENTS;
private const int T1 = 30, T2 = 130;
// Variables:
private static bool wasClicked; // Flips to true once set off.
private static bool sensorEnabled; // Is sensor active.
private static List<Vector3> sensorData; // Keeps magnetic sensor data.
private static float[] offsets; // Offsets used to detect click.
// Call this once at beginning to enable detection.
public static void SetEnabled(bool enabled) {
Reset();
sensorEnabled = enabled;
Input.compass.enabled = sensorEnabled;
}
// Reset variables.
public static void Reset() {
sensorData = new List<Vector3>(WINDOW_SIZE);
offsets = new float[SEGMENT_SIZE];
wasClicked = false;
sensorEnabled = false;
}
// Poll this once every frame to detect when the magnet button was clicked
// and if it was clicked make sure to call "ResetClick()"
// after you've dealt with the action, or it will continue to return true.
public static bool CheckIfWasClicked() {
UpdateData();
return wasClicked;
}
// Call this after you've dealt with a click operation.
public static void ResetClick() {
wasClicked = false;
}
// Updates 'sensorData' and determines if magnet was clicked.
private static void UpdateData() {
Vector3 currentVector = Input.compass.rawVector;
if (currentVector.x == 0 && currentVector.y == 0 && currentVector.z == 0) {
return;
}
if(sensorData.Count >= WINDOW_SIZE) sensorData.RemoveAt(0);
sensorData.Add(currentVector);
// Evaluate model:
if(sensorData.Count < WINDOW_SIZE) return;
float[] means = new float[2];
float[] maximums = new float[2];
float[] minimums = new float[2];
Vector3 baseline = sensorData[sensorData.Count - 1];
for(int i = 0; i < NUM_SEGMENTS; i++) {
int segmentStart = 20 * i;
offsets = ComputeOffsets(segmentStart, baseline);
means[i] = ComputeMean(offsets);
maximums[i] = ComputeMaximum(offsets);
minimums[i] = ComputeMinimum(offsets);
}
float min1 = minimums[0];
float max2 = maximums[1];
// Determine if button was clicked.
if(min1 < T1 && max2 > T2) {
sensorData.Clear();
wasClicked = true; // Set button clicked to true.
// NOTE: 'wasClicked' will now remain true until "ResetClick()" is called.
}
}
private static float[] ComputeOffsets(int start, Vector3 baseline) {
for(int i = 0; i < SEGMENT_SIZE; i++) {
Vector3 point = sensorData[start + i];
Vector3 o = new Vector3(point.x - baseline.x, point.y - baseline.y, point.z - baseline.z);
offsets[i] = o.magnitude;
}
return offsets;
}
private static float ComputeMean(float[] offsets) {
float sum = 0;
foreach(float o in offsets) {
sum += o;
}
return sum / offsets.Length;
}
private static float ComputeMaximum(float[] offsets) {
float max = float.MinValue;
foreach(float o in offsets) {
max = Mathf.Max(o, max);
}
return max;
}
private static float ComputeMinimum(float[] offsets) {
float min = float.MaxValue;
foreach(float o in offsets) {
min = Mathf.Min(o, min);
}
return min;
}
}
And my steps:
http://www.directupload.net/file/d/3887/mtjygjan_jpg.htm
(sorry I´m not able to upload pictures here)
How ever, it wont work. When I start the app and pull down the magnet, nothing happens. May I did something wrong with switching the level over level index?
I use a nexus 4 and 5 for testing the app
Thanks allot and greetz to you!
Phillip
If you are using the Google Cardboard SDK for Unity, it currently has a bug that prevents Unity from seeing the magnet (and gyro, and accelerometer). That is probably why your script is not working. Until the bug is fixed, there is no good workaround, but you can instead use the property Cardboard.CardboardTriggered to detect if the magnet was pulled.
Update for Unity 5: The sensor bug is gone. Cardboard SDK does not block the sensors.