I’m creating an experiment in Unity using some C# and it nearly completely works, except for Haptic Scenario 3. In the experiment, there are 4 scenarios – a visual only scenario, a haptics only scenario, a haptics then visual scenario and a visual then haptics scenario. 3 of these scenarios work exactly as intended, but for the life of me I can’t get Haptic Scenario 3 to work properly. The visual and haptic modalities appear in the correct order, but the game object does not follow the tracked point properly for some reason / the game object movement code does not work in the same way it works for the other 3. Does anyone know why this is from just looking at the code?
Full code included below to give context etc. around the problem.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HapE.Unity;
public class CartAppearance : MonoBehaviour
{
public GameObject[] objectsToShow; // Array to hold the game objects to choose from
public GameObject BatObject; // Show Bat object
public float distanceFromHand = 2f; // Distance from the hand where the object will appear
private bool objectActivated = false; // Flag to track if the object is activated
private GameObject activeObject = null; // Reference to the currently active object
private bool firstObjectShown = false; // Flag to track if the first object has been shown
private Dictionary<GameObject, int> objectAppearanceCounts = new Dictionary<GameObject, int>(); // Dictionary to track appearance counts for each object
private Dictionary<GameObject, int[]> hapticScenarioCounts = new Dictionary<GameObject, int[]>(); // Dictionary to track haptic scenario counts for each object
private Dictionary<GameObject, Quaternion> objectOrientation = new Dictionary<GameObject, Quaternion>(); // Added dictionary to store object orientations
private Cart_Pathing mineCartController; // Reference to the MineCartController script
public Canvas sliderCanvas; // Reference to the canvas containing the slider
public Canvas scoreCanvas;
private string randomObjectName;
private float objectSelectionTime; // Time when the object is deactivated
private bool spacebarPressed = false;
private bool handtriggerpressed = false;
public static float reactionTime;
public Canvas endcanvas;
public bool isPlaying = false;
private bool objectMoving = false; // Flag to track if the object is currently moving
public string[] jsonFilePaths;
public HapEDeviceManager hapticDevice;
public GameObject Bat;
public GameObject Bird;
public GameObject Fern;
public GameObject Rock;
public static float Timestamp { get; private set; }
public float objectAppearanceTime { get; private set; } // Time when the object appears
public int SelectedHapticScenario { get; private set; }
public Quaternion GetObjectOrientation(GameObject obj)
{
Quaternion orientation = Quaternion.identity;
if (objectOrientation.ContainsKey(obj))
{
orientation = objectOrientation[obj];
}
return orientation;
}
public GameObject GetActiveObject()
{
return activeObject;
}
private bool AllObjectsReachedCount()
{
foreach (var obj in objectAppearanceCounts)
{
if (objectAppearanceCounts[obj.Key] < 8)
{
return false;
}
}
return true;
}
// Dictionary to map each GameObject to its corresponding haptic feedback file
public Dictionary<GameObject, string> objectToHapticFeedbackMap;
// Public variable to hold the tracked point
public GameObject trackedPoint;
// Method to set the object appearance time
public float GetObjectAppearanceTime()
{
return objectAppearanceTime;
}
public bool GetSpacebarPressed()
{
return spacebarPressed;
}
public bool GetHandTriggerPressed()
{
return handtriggerpressed;
}
void Start()
{
// Find the MineCart object and get the MineCartController script
GameObject mineCartObject = GameObject.Find("MineCart");
if (mineCartObject != null)
{
mineCartController = mineCartObject.GetComponent<Cart_Pathing>();
}
// Initialize appearance counts for each object to 0
foreach (GameObject obj in objectsToShow)
{
objectAppearanceCounts[obj] = 0;
hapticScenarioCounts[obj] = new int[4]; // Four haptic scenarios
// Initialize the counts to 0
for (int i = 0; i < hapticScenarioCounts[obj].Length; i++)
{
hapticScenarioCounts[obj][i] = 0;
}
}
// Disable canvas UI on startup
sliderCanvas.enabled = false;
endcanvas.enabled = false;
// Initialize the object to haptic feedback mapping
InitializeObjectToHapticFeedbackMap();
// Start the coroutine after initialization
StartCoroutine(ShowRandomObject());
}
void InitializeObjectToHapticFeedbackMap()
{
// Initialize the object to haptic feedback mapping
objectToHapticFeedbackMap = new Dictionary<GameObject, string>();
objectToHapticFeedbackMap.Add(Bat, "C:/Users/o.singleton/Desktop/Bat Cave Environment/Assets/StreamingAssets/haptics/Bat.json");
objectToHapticFeedbackMap.Add(Bird, "C:/Users/o.singleton/Desktop/Bat Cave Environment/Assets/StreamingAssets/haptics/Raindrops.json");
objectToHapticFeedbackMap.Add(Fern, "C:/Users/o.singleton/Desktop/Bat Cave Environment/Assets/StreamingAssets/haptics/Fern.json");
objectToHapticFeedbackMap.Add(Rock, "C:/Users/o.singleton/Desktop/Bat Cave Environment/Assets/StreamingAssets/haptics/Rock.json");
objectToHapticFeedbackMap.Add(BatObject, "C:/Users/o.singleton/Desktop/Bat Cave Environment/Assets/StreamingAssets/haptics/BatContinuous.json");
}
void Update()
{
Debug.Log("Object activated: " + objectActivated);
// Check if spacebar key is pressed
if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger, OVRInput.Controller.LTouch))
{
spacebarPressed = true;
if (objectActivated)
{
// Calculate the reaction time if the object is activated
reactionTime = Time.time - objectSelectionTime;
Debug.Log("Reaction Time: " + reactionTime);
}
}
if (OVRInput.GetDown(OVRInput.Button.PrimaryHandTrigger, OVRInput.Controller.LTouch))
{
handtriggerpressed = true;
}
// Check if 'k' key is pressed
if (Input.GetKeyDown(KeyCode.K))
{
Debug.Log("K key pressed");
// If there's an active object, deactivate it
if (activeObject != null)
{
activeObject.SetActive(false);
Debug.Log("Deactivated active object: " + activeObject.name);
}
// Toggle the object's activation state
objectActivated = !objectActivated;
Debug.Log("Object activated: " + objectActivated);
// If the object is activated and it's the first object to be shown
if (objectActivated && !firstObjectShown)
{
firstObjectShown = true; // Set flag to true
Debug.Log("First object shown");
StartCoroutine(ShowRandomObject()); // Start coroutine to show random object
}
}
// If the object is activated, continuously update its position to follow the tracked point and store the name of the active object
if (objectActivated)
{
// Calculate the position of the active object to follow the tracked point
if (activeObject != null && trackedPoint != null)
{
if (!objectMoving)
{
StartCoroutine(MoveObjectFromLeftToRight(activeObject));
}
}
}
}
public string GetActiveObjectName()
{
return randomObjectName;
}
IEnumerator ShowRandomObject()
{
while (objectActivated)
{
// Reset spacebar press status and object appearance time at the beginning of each cycle
spacebarPressed = false;
objectAppearanceTime = Time.time;
// Wait for a random time between 10 and 30 seconds
float objectdelay = Random.Range(10f, 30f);
yield return new WaitForSeconds(objectdelay);
GameObject obj = null;
int randomIndex;
do
{
randomIndex = Random.Range(0, objectsToShow.Length);
obj = objectsToShow[randomIndex];
} while (objectAppearanceCounts[obj] >= 8);
activeObject = obj;
randomObjectName = activeObject.name; // Store the name of the random object
Debug.Log("Assigned active object: " + activeObject.name);
objectSelectionTime = Time.time;
Timestamp = Time.time;
scoreCanvas.enabled = false;
// If the object's appearance count is less than 8, activate it
if (objectAppearanceCounts[obj] < 8)
{
int hapticScenario;
do
{
hapticScenario = Random.Range(0, 4);
} while (hapticScenarioCounts[obj][hapticScenario] >= 2);
SelectedHapticScenario = hapticScenario; // Store the selected haptic scenario
if (hapticScenario == 0) // Object appears twice with no haptics
{
// Set a random orientation for the object
Quaternion orientation = Quaternion.Euler(Random.Range(0, 360), Random.Range(0, 360), Random.Range(0, 360));
objectOrientation[obj] = orientation; // Store the orientation
activeObject.transform.rotation = orientation;
activeObject.SetActive(true);
// Deactivate the object after 1 seconds
StartCoroutine(DeactivateObjectAfterDelay(activeObject, 1f));
Debug.Log("Haptic Scenario 0");
}
else if (hapticScenario == 1) //haptics occur twice with no visuals
{
activeObject.SetActive(false);
string hapticFeedbackFilePath;
// Play the corresponding haptic feedback for the object twice
if (objectToHapticFeedbackMap.TryGetValue(obj, out hapticFeedbackFilePath))
{
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
// Check for spacebar press continuously during scenario 1
StartCoroutine(CheckSpacebarPressDuringScenario1());
yield return new WaitForSeconds(0.5f);
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
yield return new WaitForSeconds(0.5f);
}
// Deactivate the object after 0.5 seconds
StartCoroutine(DeactivateObjectAfterDelay2(0.5f));
Debug.Log("Haptic Scenario 1");
}
else if (hapticScenario == 2) // haptics occur before object appears
{
activeObject.SetActive(false);
// Play the corresponding haptic feedback for the object
string hapticFeedbackFilePath;
if (objectToHapticFeedbackMap.TryGetValue(obj, out hapticFeedbackFilePath))
{
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
yield return new WaitForSeconds(0.5f);
}
// Set a random orientation for the object
Quaternion orientation = Quaternion.Euler(Random.Range(0, 360), Random.Range(0, 360), Random.Range(0, 360));
objectOrientation[obj] = orientation; // Store the orientation
activeObject.transform.rotation = orientation;
activeObject.SetActive(true);
// Deactivate the object after 0.5 seconds
StartCoroutine(DeactivateObjectAfterDelay(activeObject, 0.5f));
Debug.Log("Haptic Scenario 2");
}
else if (hapticScenario == 3) // haptics occur after object appears
{
activeObject.SetActive(false);
// Set a random orientation for the object
Quaternion orientation = Quaternion.Euler(Random.Range(0, 360), Random.Range(0, 360), Random.Range(0, 360));
objectOrientation[obj] = orientation; // Store the orientation
activeObject.transform.rotation = orientation;
activeObject.SetActive(true);
// Deactivate the object after 0.5 seconds
StartCoroutine(DeactivateObjectAfterDelay2(0.5f));
string hapticFeedbackFilePath;
if (objectToHapticFeedbackMap.TryGetValue(obj, out hapticFeedbackFilePath))
{
yield return new WaitForSeconds(0.5f);
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
}
Debug.Log("Haptic Scenario 3");
}
// Increment the appearance count & haptic scenario for this object
objectAppearanceCounts[obj]++;
hapticScenarioCounts[obj][hapticScenario]++;
if (AllObjectsReachedCount())
{
yield return new WaitForSeconds(10f);
scoreCanvas.enabled = false;
endcanvas.enabled = true;
}
}
// Pause movement of the MineCart
if (mineCartController != null)
{
mineCartController.PauseMovement();
}
}
}
IEnumerator DeactivateObjectAfterDelay(GameObject obj, float delay)
{
yield return new WaitForSeconds(delay);
obj.SetActive(false); // Deactivate the object after delay
activeObject = null; // Reset the active object reference
// Add delay before enabling the slider canvas
yield return new WaitForSeconds(2f);
// Enable the slider canvas
sliderCanvas.enabled = true;
// Calculate the position of the Bat object to follow the tracked point
Vector3 Batposition = trackedPoint.transform.position + trackedPoint.transform.forward * distanceFromHand;
// Calculate the desired rotation relative to the tracked point's rotation
Quaternion desiredRotation = Quaternion.Euler(0f, 90f, 90f);
Quaternion finalRotation = trackedPoint.transform.rotation * desiredRotation;
// Move the Bat Game object to the position of the tracked point
BatObject.SetActive(true);
BatObject.transform.position = Batposition;
BatObject.transform.rotation = finalRotation;
// Play haptic feedback
string hapticFeedbackFilePath;
if (objectToHapticFeedbackMap.TryGetValue(BatObject, out hapticFeedbackFilePath))
{
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
}
// Wait until the slider has been submitted
yield return new WaitUntil(() => !sliderCanvas.enabled);
// Disable the slider canvas
sliderCanvas.enabled = false;
scoreCanvas.enabled = true;
// Disable the Bat Object
BatObject.SetActive(false);
hapticDevice.StopHaptics();
// Resume movement of the MineCart
if (mineCartController != null)
{
mineCartController.ResumeMovement();
}
// Get the slider value from the ButtonBehaviour script
float sliderValue = sliderCanvas.GetComponent<ButtonBehaviour>().sliderValue;
}
IEnumerator DeactivateObjectAfterDelay2(float delay)
{
yield return new WaitForSeconds(delay);
activeObject = null; // Reset the active object reference
// Add delay before enabling the slider canvas
yield return new WaitForSeconds(2f);
// Enable the slider canvas
sliderCanvas.enabled = true;
// Calculate the position of the Bat object to follow the tracked point
Vector3 Batposition = trackedPoint.transform.position + trackedPoint.transform.forward * distanceFromHand;
// Calculate the desired rotation relative to the tracked point's rotation
Quaternion desiredRotation = Quaternion.Euler(0f, 90f, 90f);
Quaternion finalRotation = trackedPoint.transform.rotation * desiredRotation;
// Move the Bat Game object to the position of the tracked point
BatObject.SetActive(true);
BatObject.transform.position = Batposition;
BatObject.transform.rotation = finalRotation;
// Play haptic feedback
string hapticFeedbackFilePath;
if (objectToHapticFeedbackMap.TryGetValue(BatObject, out hapticFeedbackFilePath))
{
hapticDevice.PlayHapEJSON(hapticFeedbackFilePath);
}
// Wait until the slider has been submitted
yield return new WaitUntil(() => !sliderCanvas.enabled);
// Disable the slider canvas
sliderCanvas.enabled = false;
scoreCanvas.enabled = true;
// Disable the Bat Object
BatObject.SetActive(false);
hapticDevice.StopHaptics();
// Resume movement of the MineCart
if (mineCartController != null)
{
mineCartController.ResumeMovement();
}
// Get the slider value from the ButtonBehaviour script
float sliderValue = sliderCanvas.GetComponent<ButtonBehaviour>().sliderValue;
}
IEnumerator CheckSpacebarPressDuringScenario1()
{
while (true)
{
if (Input.GetKeyDown(KeyCode.Space))
{
spacebarPressed = true;
break;
}
yield return null;
}
}
IEnumerator MoveObjectFromLeftToRight(GameObject obj)
{
objectMoving = true;
float startTime = Time.time;
Vector3 initialPosition = trackedPoint.transform.position + trackedPoint.transform.right * -0.05f + trackedPoint.transform.forward * distanceFromHand;
Vector3 targetPosition = trackedPoint.transform.position + trackedPoint.transform.right * 0.05f + trackedPoint.transform.forward * distanceFromHand;
while (Time.time - startTime < 0.5f)
{
float t = (Time.time - startTime) / 0.5f;
obj.transform.position = Vector3.Lerp(initialPosition, targetPosition, t);
yield return null;
}
obj.transform.position = targetPosition; // Ensuring it reaches the rightmost position
objectMoving = false;
}
IEnumerator MoveObjectFromLeftToRight2(GameObject obj)
{
objectMoving = true;
float startTime = Time.time;
Vector3 initialPosition = trackedPoint.transform.position + trackedPoint.transform.right * -0.05f + trackedPoint.transform.forward * distanceFromHand;
Vector3 targetPosition = trackedPoint.transform.position + trackedPoint.transform.right * 0.05f + trackedPoint.transform.forward * distanceFromHand;
while (Time.time - startTime < 0.5f)
{
float t = (Time.time - startTime) / 0.5f;
obj.transform.position = Vector3.Lerp(initialPosition, targetPosition, t);
yield return null;
}
obj.transform.position = targetPosition; // Ensuring it reaches the rightmost position
objectMoving = false;
// Deactivate the object after 0.5 seconds
StartCoroutine(DeactivateObjectAfterDelay(activeObject, 0f));
}
}
I have tried moving the MoveObjectFromLeftToRight coroutine inside of the haptic 3 if clause, I have tried re-writing the code so that each haptic scenario is written in full without using coroutines, I have tried messing with yield return wait(for seconds or until coroutine has finished).
I feel like there is something obvious I’m missing regarding the logic of how my code executes due to my relative inexperience coding, but I can’t see what and I don’t understand why only haptic scenario 3 isn’t working when it contains the same code as the other scenarios.