initial upload
This commit is contained in:
839
Unity-Master/Assets/Scripts/VRExperimentController.cs
Normal file
839
Unity-Master/Assets/Scripts/VRExperimentController.cs
Normal file
@ -0,0 +1,839 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// VR Experiment Controller
|
||||
/// Listens for UDP messages from the supervisor and manages avatars and objects
|
||||
/// based on the current experimental condition.
|
||||
/// </summary>
|
||||
public class VRExperimentController : MonoBehaviour
|
||||
{
|
||||
[Header("Network Settings")]
|
||||
[SerializeField] private int udpPort = 1221;
|
||||
[SerializeField] private bool allowPortSharing = true; // For local testing with multiple components
|
||||
|
||||
[Header("Avatar Assignments")]
|
||||
[SerializeField] private GameObject helpfulAvatar;
|
||||
[SerializeField] private GameObject demotivatingAvatar;
|
||||
[SerializeField] private GameObject nonInteractiveAvatar;
|
||||
[SerializeField] private GameObject practiceAvatar;
|
||||
|
||||
[Header("Object Assignments")]
|
||||
[SerializeField] private GameObject brickObject;
|
||||
[SerializeField] private GameObject paperclipObject;
|
||||
[SerializeField] private GameObject ropeObject;
|
||||
[SerializeField] private GameObject bookObject;
|
||||
|
||||
[Header("Debug Settings")]
|
||||
[SerializeField] private bool enableDebugLogging = true;
|
||||
|
||||
// Network components
|
||||
private UdpClient udpClient;
|
||||
private Thread udpListenerThread;
|
||||
private bool isListening = false;
|
||||
|
||||
// Current experiment state
|
||||
private string currentConditionType = "";
|
||||
private string currentObjectType = "";
|
||||
private int currentConditionIndex = -1;
|
||||
private bool practiceRoundActive = false;
|
||||
|
||||
// Message queue for thread-safe communication
|
||||
private Queue<string> messageQueue = new Queue<string>();
|
||||
private readonly object queueLock = new object();
|
||||
|
||||
// Object mapping for easy access
|
||||
private Dictionary<string, GameObject> objectMap;
|
||||
private Dictionary<string, GameObject> avatarMap;
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeObjectMaps();
|
||||
StartUDPListener();
|
||||
|
||||
// Initially deactivate all objects and avatars
|
||||
DeactivateAllObjects();
|
||||
DeactivateAllAvatars();
|
||||
|
||||
LogMessage("VR Experiment Controller started. Listening on port 1221 (shared with avatar sync). Waiting for supervisor commands...");
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Process messages from the UDP thread in the main thread
|
||||
ProcessMessageQueue();
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
StopUDPListener();
|
||||
}
|
||||
|
||||
void OnApplicationPause(bool pauseStatus)
|
||||
{
|
||||
if (pauseStatus)
|
||||
{
|
||||
LogMessage("Application paused - stopping UDP listener");
|
||||
StopUDPListener();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage("Application unpaused - restarting UDP listener");
|
||||
StartUDPListener();
|
||||
}
|
||||
}
|
||||
|
||||
void OnApplicationFocus(bool hasFocus)
|
||||
{
|
||||
// Don't stop UDP listener when losing focus - supervisor GUI needs to communicate
|
||||
// Only restart if we had stopped for some reason
|
||||
if (hasFocus && !isListening)
|
||||
{
|
||||
LogMessage("Application regained focus - restarting UDP listener");
|
||||
StartUDPListener();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize object and avatar mappings for easy access
|
||||
/// </summary>
|
||||
private void InitializeObjectMaps()
|
||||
{
|
||||
// Initialize object map
|
||||
objectMap = new Dictionary<string, GameObject>
|
||||
{
|
||||
{ "Brick", brickObject },
|
||||
{ "Paperclip", paperclipObject },
|
||||
{ "Rope", ropeObject },
|
||||
{ "Book", bookObject }
|
||||
};
|
||||
|
||||
// Initialize avatar map
|
||||
avatarMap = new Dictionary<string, GameObject>
|
||||
{
|
||||
{ "Helpful", helpfulAvatar },
|
||||
{ "Demotivating", demotivatingAvatar },
|
||||
{ "Non-Interactive", nonInteractiveAvatar }
|
||||
};
|
||||
|
||||
// Validate assignments
|
||||
ValidateAssignments();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that all required objects and avatars are assigned
|
||||
/// </summary>
|
||||
private void ValidateAssignments()
|
||||
{
|
||||
bool hasErrors = false;
|
||||
|
||||
// Check objects
|
||||
foreach (var kvp in objectMap)
|
||||
{
|
||||
if (kvp.Value == null)
|
||||
{
|
||||
LogError($"Object not assigned: {kvp.Key}");
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check avatars
|
||||
foreach (var kvp in avatarMap)
|
||||
{
|
||||
if (kvp.Value == null)
|
||||
{
|
||||
LogError($"Avatar not assigned: {kvp.Key}");
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check practice avatar
|
||||
if (practiceAvatar == null)
|
||||
{
|
||||
LogError("Practice avatar not assigned!");
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
// Check non-interactive avatar
|
||||
if (nonInteractiveAvatar == null)
|
||||
{
|
||||
LogError("Non-interactive avatar not assigned!");
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
if (hasErrors)
|
||||
{
|
||||
LogError("Please assign all required objects and avatars in the inspector!");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage("All objects and avatars properly assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start listening for UDP messages
|
||||
/// </summary>
|
||||
private void StartUDPListener()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (allowPortSharing)
|
||||
{
|
||||
// Create UDP client with port reuse for local testing
|
||||
udpClient = new UdpClient();
|
||||
udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, udpPort));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standard UDP client binding
|
||||
udpClient = new UdpClient(udpPort);
|
||||
}
|
||||
|
||||
udpListenerThread = new Thread(new ThreadStart(UDPListenerLoop));
|
||||
udpListenerThread.IsBackground = true;
|
||||
isListening = true;
|
||||
udpListenerThread.Start();
|
||||
|
||||
LogMessage($"UDP Experiment Control Listener started on port {udpPort} (filtering JSON messages only, Port sharing: {allowPortSharing})");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (allowPortSharing)
|
||||
{
|
||||
LogError($"Failed to start UDP listener with port sharing: {e.Message}");
|
||||
LogMessage("Trying with different port...");
|
||||
TryAlternativePort();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError($"Failed to start UDP listener: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try alternative ports for local testing
|
||||
/// </summary>
|
||||
private void TryAlternativePort()
|
||||
{
|
||||
// Try a few alternative ports for local testing
|
||||
int[] alternativePorts = { 1222, 1223, 1224, 1225, 1226 };
|
||||
|
||||
foreach (int port in alternativePorts)
|
||||
{
|
||||
try
|
||||
{
|
||||
udpClient = new UdpClient(port);
|
||||
udpListenerThread = new Thread(new ThreadStart(UDPListenerLoop));
|
||||
udpListenerThread.IsBackground = true;
|
||||
isListening = true;
|
||||
udpListenerThread.Start();
|
||||
|
||||
LogMessage($"UDP Experiment Control Listener started on alternative port {port}");
|
||||
return;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Try next port
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LogError("Failed to start UDP listener on any available port");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop listening for UDP messages
|
||||
/// </summary>
|
||||
private void StopUDPListener()
|
||||
{
|
||||
isListening = false;
|
||||
|
||||
if (udpClient != null)
|
||||
{
|
||||
udpClient.Close();
|
||||
udpClient = null;
|
||||
}
|
||||
|
||||
if (udpListenerThread != null && udpListenerThread.IsAlive)
|
||||
{
|
||||
udpListenerThread.Join(1000); // Wait up to 1 second
|
||||
if (udpListenerThread.IsAlive)
|
||||
{
|
||||
udpListenerThread.Abort();
|
||||
}
|
||||
udpListenerThread = null;
|
||||
}
|
||||
|
||||
LogMessage("UDP Listener stopped");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UDP listener loop (runs in separate thread)
|
||||
/// </summary>
|
||||
private void UDPListenerLoop()
|
||||
{
|
||||
while (isListening)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, udpPort);
|
||||
byte[] data = udpClient.Receive(ref remoteEndPoint);
|
||||
|
||||
// Check if this looks like a JSON experiment control message
|
||||
if (IsExperimentControlMessage(data))
|
||||
{
|
||||
string message = Encoding.UTF8.GetString(data);
|
||||
|
||||
// Add message to queue for main thread processing
|
||||
lock (queueLock)
|
||||
{
|
||||
messageQueue.Enqueue(message);
|
||||
}
|
||||
}
|
||||
// If it's not an experiment control message (likely avatar data), ignore it
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (isListening) // Only log if we're still supposed to be listening
|
||||
{
|
||||
LogError($"UDP Listener error: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the received data is an experiment control message (JSON) vs avatar data (binary)
|
||||
/// </summary>
|
||||
private bool IsExperimentControlMessage(byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Experiment control messages are JSON and should be reasonably small
|
||||
if (data.Length > 1024) // Avatar data is typically much larger
|
||||
return false;
|
||||
|
||||
// Try to decode as UTF-8 text
|
||||
string text = Encoding.UTF8.GetString(data);
|
||||
|
||||
// Check if it looks like JSON (starts with '{' and contains "command")
|
||||
if (text.TrimStart().StartsWith("{") && text.Contains("\"command\""))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't decode as UTF-8 or any other error, it's probably binary avatar data
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process messages from the queue in the main thread
|
||||
/// </summary>
|
||||
private void ProcessMessageQueue()
|
||||
{
|
||||
lock (queueLock)
|
||||
{
|
||||
while (messageQueue.Count > 0)
|
||||
{
|
||||
string message = messageQueue.Dequeue();
|
||||
ProcessUDPMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process incoming UDP message
|
||||
/// </summary>
|
||||
private void ProcessUDPMessage(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogMessage($"Received UDP message: {message}");
|
||||
|
||||
// Parse JSON message
|
||||
var messageData = JsonConvert.DeserializeObject<Dictionary<string, object>>(message);
|
||||
|
||||
if (messageData.ContainsKey("command"))
|
||||
{
|
||||
string command = messageData["command"].ToString();
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "start_condition":
|
||||
case "next_condition":
|
||||
HandleConditionChange(messageData);
|
||||
break;
|
||||
case "start_practice_round":
|
||||
HandlePracticeRoundStart(messageData);
|
||||
break;
|
||||
case "end_practice_round":
|
||||
HandlePracticeRoundEnd(messageData);
|
||||
break;
|
||||
case "disable_all":
|
||||
HandleDisableAll(messageData);
|
||||
break;
|
||||
default:
|
||||
LogMessage($"Unknown command received: {command}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"Failed to process UDP message: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle practice round start command
|
||||
/// </summary>
|
||||
private void HandlePracticeRoundStart(Dictionary<string, object> messageData)
|
||||
{
|
||||
try
|
||||
{
|
||||
int duration = messageData.ContainsKey("duration") ? Convert.ToInt32(messageData["duration"]) : 300;
|
||||
LogMessage($"Practice round started. Duration: {duration} seconds");
|
||||
|
||||
practiceRoundActive = true;
|
||||
|
||||
// Deactivate all objects and regular avatars
|
||||
DeactivateAllObjects();
|
||||
DeactivateAllAvatars();
|
||||
|
||||
// Activate only the practice avatar
|
||||
if (practiceAvatar != null)
|
||||
{
|
||||
practiceAvatar.SetActive(true);
|
||||
LogMessage("Practice avatar activated");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("Practice avatar not assigned!");
|
||||
}
|
||||
|
||||
// Clear current condition state
|
||||
currentConditionType = "";
|
||||
currentObjectType = "";
|
||||
currentConditionIndex = -1;
|
||||
|
||||
LogMessage("Practice round active - only practice avatar is present");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"Failed to handle practice round start: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle practice round end command
|
||||
/// </summary>
|
||||
private void HandlePracticeRoundEnd(Dictionary<string, object> messageData)
|
||||
{
|
||||
try
|
||||
{
|
||||
string reason = messageData.ContainsKey("reason") ? messageData["reason"].ToString() : "unknown";
|
||||
LogMessage($"Practice round ended. Reason: {reason}");
|
||||
|
||||
practiceRoundActive = false;
|
||||
|
||||
// Deactivate practice avatar
|
||||
if (practiceAvatar != null)
|
||||
{
|
||||
practiceAvatar.SetActive(false);
|
||||
LogMessage("Practice avatar deactivated");
|
||||
}
|
||||
|
||||
// Ensure all objects and avatars are deactivated
|
||||
DeactivateAllObjects();
|
||||
DeactivateAllAvatars();
|
||||
|
||||
LogMessage("Practice round completed - all avatars and objects disabled");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"Failed to handle practice round end: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle disable all command (when timer expires)
|
||||
/// </summary>
|
||||
private void HandleDisableAll(Dictionary<string, object> messageData)
|
||||
{
|
||||
try
|
||||
{
|
||||
string reason = messageData.ContainsKey("reason") ? messageData["reason"].ToString() : "unknown";
|
||||
LogMessage($"Disable all command received. Reason: {reason}");
|
||||
|
||||
// Deactivate all objects and avatars
|
||||
DeactivateAllObjects();
|
||||
DeactivateAllAvatars();
|
||||
|
||||
// Deactivate practice avatar if active
|
||||
if (practiceAvatar != null)
|
||||
{
|
||||
practiceAvatar.SetActive(false);
|
||||
}
|
||||
|
||||
// Clear current condition state
|
||||
currentConditionType = "";
|
||||
currentObjectType = "";
|
||||
practiceRoundActive = false;
|
||||
|
||||
LogMessage("All objects and avatars disabled - block finished");
|
||||
|
||||
// Trigger condition changed event
|
||||
OnConditionChanged();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"Failed to handle disable all command: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle condition change messages
|
||||
/// </summary>
|
||||
private void HandleConditionChange(Dictionary<string, object> messageData)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Extract condition data
|
||||
currentConditionType = messageData["condition_type"].ToString();
|
||||
currentObjectType = messageData["object_type"].ToString();
|
||||
currentConditionIndex = Convert.ToInt32(messageData["condition_index"]);
|
||||
|
||||
LogMessage($"Condition changed to: {currentConditionType} with {currentObjectType} (Index: {currentConditionIndex})");
|
||||
|
||||
// End practice round if it was active
|
||||
if (practiceRoundActive)
|
||||
{
|
||||
practiceRoundActive = false;
|
||||
if (practiceAvatar != null)
|
||||
{
|
||||
practiceAvatar.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the scene based on the new condition
|
||||
UpdateSceneForCondition();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"Failed to handle condition change: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the scene based on the current condition
|
||||
/// </summary>
|
||||
private void UpdateSceneForCondition()
|
||||
{
|
||||
// First, deactivate everything
|
||||
DeactivateAllObjects();
|
||||
DeactivateAllAvatars();
|
||||
|
||||
// Deactivate practice avatar
|
||||
if (practiceAvatar != null)
|
||||
{
|
||||
practiceAvatar.SetActive(false);
|
||||
}
|
||||
|
||||
// Activate the required object
|
||||
if (objectMap.ContainsKey(currentObjectType) && objectMap[currentObjectType] != null)
|
||||
{
|
||||
objectMap[currentObjectType].SetActive(true);
|
||||
LogMessage($"Activated object: {currentObjectType}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError($"Object not found or not assigned: {currentObjectType}");
|
||||
}
|
||||
|
||||
// Activate the required avatar (only for non-control conditions)
|
||||
if (currentConditionType != "Control")
|
||||
{
|
||||
if (avatarMap.ContainsKey(currentConditionType) && avatarMap[currentConditionType] != null)
|
||||
{
|
||||
avatarMap[currentConditionType].SetActive(true);
|
||||
LogMessage($"Activated avatar: {currentConditionType}");
|
||||
|
||||
// Handle non-interactive avatar behavior
|
||||
if (currentConditionType == "Non-Interactive")
|
||||
{
|
||||
HandleNonInteractiveAvatar();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError($"Avatar not found or not assigned: {currentConditionType}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage("Control condition - no avatar activated");
|
||||
}
|
||||
|
||||
// Trigger any additional condition-specific behavior
|
||||
OnConditionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle non-interactive avatar behavior
|
||||
/// </summary>
|
||||
private void HandleNonInteractiveAvatar()
|
||||
{
|
||||
// The non-interactive avatar is a dedicated avatar that should not respond to interactions
|
||||
// You can add specific behavior here, such as:
|
||||
// - Disabling interaction scripts
|
||||
// - Setting the avatar to a passive state
|
||||
// - Disabling speech or gesture systems
|
||||
|
||||
LogMessage("Non-interactive avatar activated - avatar will not respond to interactions");
|
||||
|
||||
// Example: Disable interaction components if they exist
|
||||
var interactionComponents = avatarMap["Non-Interactive"].GetComponents<MonoBehaviour>();
|
||||
foreach (var component in interactionComponents)
|
||||
{
|
||||
// Disable specific interaction scripts (adjust based on your actual component names)
|
||||
if (component.GetType().Name.Contains("Interaction") ||
|
||||
component.GetType().Name.Contains("Speech") ||
|
||||
component.GetType().Name.Contains("Gesture"))
|
||||
{
|
||||
component.enabled = false;
|
||||
LogMessage($"Disabled interaction component: {component.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate all objects
|
||||
/// </summary>
|
||||
private void DeactivateAllObjects()
|
||||
{
|
||||
foreach (var kvp in objectMap)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
kvp.Value.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate all avatars
|
||||
/// </summary>
|
||||
private void DeactivateAllAvatars()
|
||||
{
|
||||
foreach (var kvp in avatarMap)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
kvp.Value.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when condition changes - override this for custom behavior
|
||||
/// </summary>
|
||||
protected virtual void OnConditionChanged()
|
||||
{
|
||||
// Send Unity event or trigger custom behavior here
|
||||
// This can be overridden by derived classes for specific experiment needs
|
||||
|
||||
if (practiceRoundActive)
|
||||
{
|
||||
LogMessage("Practice round active - only practice avatar is present");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMessage($"Condition change complete. Current state: {currentConditionType} - {currentObjectType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the actual UDP port being used
|
||||
/// </summary>
|
||||
private int GetActualListenPort()
|
||||
{
|
||||
if (udpClient?.Client?.LocalEndPoint != null)
|
||||
{
|
||||
return ((IPEndPoint)udpClient.Client.LocalEndPoint).Port;
|
||||
}
|
||||
return udpPort;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current condition information
|
||||
/// </summary>
|
||||
public string GetCurrentCondition()
|
||||
{
|
||||
if (practiceRoundActive)
|
||||
{
|
||||
return "Practice Round Active";
|
||||
}
|
||||
return $"{currentConditionType} - {currentObjectType} (Index: {currentConditionIndex})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a specific object is currently active
|
||||
/// </summary>
|
||||
public bool IsObjectActive(string objectType)
|
||||
{
|
||||
if (objectMap.ContainsKey(objectType) && objectMap[objectType] != null)
|
||||
{
|
||||
return objectMap[objectType].activeInHierarchy;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a specific avatar is currently active
|
||||
/// </summary>
|
||||
public bool IsAvatarActive(string avatarType)
|
||||
{
|
||||
if (avatarMap.ContainsKey(avatarType) && avatarMap[avatarType] != null)
|
||||
{
|
||||
return avatarMap[avatarType].activeInHierarchy;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if practice round is active
|
||||
/// </summary>
|
||||
public bool IsPracticeRoundActive()
|
||||
{
|
||||
return practiceRoundActive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log message with timestamp
|
||||
/// </summary>
|
||||
private void LogMessage(string message)
|
||||
{
|
||||
if (enableDebugLogging)
|
||||
{
|
||||
Debug.Log($"[VRExperimentController] {DateTime.Now:HH:mm:ss} - {message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log error message
|
||||
/// </summary>
|
||||
private void LogError(string message)
|
||||
{
|
||||
Debug.LogError($"[VRExperimentController] {DateTime.Now:HH:mm:ss} - ERROR: {message}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manual test methods for debugging (can be called from inspector)
|
||||
/// </summary>
|
||||
[ContextMenu("Test Helpful Condition")]
|
||||
public void TestHelpfulCondition()
|
||||
{
|
||||
var testMessage = new Dictionary<string, object>
|
||||
{
|
||||
{"command", "start_condition"},
|
||||
{"condition_type", "Helpful"},
|
||||
{"object_type", "Brick"},
|
||||
{"condition_index", 0}
|
||||
};
|
||||
HandleConditionChange(testMessage);
|
||||
}
|
||||
|
||||
[ContextMenu("Test Demotivating Condition")]
|
||||
public void TestDemotivatingCondition()
|
||||
{
|
||||
var testMessage = new Dictionary<string, object>
|
||||
{
|
||||
{"command", "start_condition"},
|
||||
{"condition_type", "Demotivating"},
|
||||
{"object_type", "Paperclip"},
|
||||
{"condition_index", 1}
|
||||
};
|
||||
HandleConditionChange(testMessage);
|
||||
}
|
||||
|
||||
[ContextMenu("Test Control Condition")]
|
||||
public void TestControlCondition()
|
||||
{
|
||||
var testMessage = new Dictionary<string, object>
|
||||
{
|
||||
{"command", "start_condition"},
|
||||
{"condition_type", "Control"},
|
||||
{"object_type", "Rope"},
|
||||
{"condition_index", 2}
|
||||
};
|
||||
HandleConditionChange(testMessage);
|
||||
}
|
||||
|
||||
[ContextMenu("Test Non-Interactive Condition")]
|
||||
public void TestNonInteractiveCondition()
|
||||
{
|
||||
var testMessage = new Dictionary<string, object>
|
||||
{
|
||||
{"command", "start_condition"},
|
||||
{"condition_type", "Non-Interactive"},
|
||||
{"object_type", "Book"},
|
||||
{"condition_index", 3}
|
||||
};
|
||||
HandleConditionChange(testMessage);
|
||||
}
|
||||
|
||||
[ContextMenu("Test Practice Round")]
|
||||
public void TestPracticeRound()
|
||||
{
|
||||
var testMessage = new Dictionary<string, object>
|
||||
{
|
||||
{"command", "start_practice_round"},
|
||||
{"duration", 300}
|
||||
};
|
||||
HandlePracticeRoundStart(testMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional debug GUI for monitoring experiment controller status
|
||||
/// </summary>
|
||||
void OnGUI()
|
||||
{
|
||||
if (!enableDebugLogging) return;
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 10, 350, 250));
|
||||
GUILayout.Label($"VR Experiment Controller (JSON Only)");
|
||||
GUILayout.Label($"Status: {(isListening ? "Listening" : "Stopped")}");
|
||||
GUILayout.Label($"Listen Port: {GetActualListenPort()}{(GetActualListenPort() != udpPort ? " (alt)" : "")}");
|
||||
GUILayout.Label($"Port Sharing: {allowPortSharing}");
|
||||
GUILayout.Label($"Practice Round: {(practiceRoundActive ? "Active" : "Inactive")}");
|
||||
GUILayout.Label($"Current Condition: {currentConditionType}");
|
||||
GUILayout.Label($"Current Object: {currentObjectType}");
|
||||
GUILayout.Label($"Condition Index: {currentConditionIndex}");
|
||||
GUILayout.Label($"Has Condition: {!string.IsNullOrEmpty(currentConditionType)}");
|
||||
|
||||
if (GUILayout.Button(isListening ? "Stop Listener" : "Start Listener"))
|
||||
{
|
||||
if (isListening)
|
||||
StopUDPListener();
|
||||
else
|
||||
StartUDPListener();
|
||||
}
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user