added example folder

This commit is contained in:
2025-10-15 14:40:51 +02:00
parent e9fc57ec7f
commit 6a65e82516
3 changed files with 563 additions and 0 deletions

63
Example/README.md Normal file
View File

@ -0,0 +1,63 @@
# Unity UDP Example
This example demonstrates how to use the UnityUDP Flutter app to send commands to a Unity game.
## Setup
### Unity Setup
1. Copy `UDPCommandListener.cs` into your Unity project's `Assets/Scripts/` folder
2. Create a new empty GameObject in your scene (GameObject → Create Empty)
3. Rename it to "UDP Manager"
4. Attach the `UDPCommandListener` script to the UDP Manager GameObject
5. Set the port to 7777 (or any port you prefer) in the inspector
### Example Scene Setup
For the example commands to work, create a simple test scene:
1. Create a Cube (GameObject → 3D Object → Cube)
2. Name it "TestCube"
3. Create a UI Canvas with a Text element showing a timer (optional, for the timer command)
### Testing the Example
1. Run your Unity game
2. Open the UnityUDP Flutter app
3. Copy/move the `unityudp_projects.json` file to your Documents folder
4. Select a command and send it to your Unity game (make sure the IP address matches your computer's local IP and port is 7777)
## Sample Commands
The example includes these simple commands:
- **Toggle Cube**: Enables/Disables the TestCube GameObject
- **Start Timer**: Starts a 10-second countdown timer
- **Reset Scene**: Reloads the current scene
## UDP Protocol
The UDP commands are sent as JSON strings with the following format:
```json
{
"command": "command_name",
"value": "optional_value"
}
```
The Unity script parses these JSON commands and executes the corresponding action.
## Customizing
You can add your own commands by:
1. Adding a new case in the `ProcessCommand` method in `UDPCommandListener.cs`
2. Creating a corresponding JSON entry in your project file
## Troubleshooting
- **Commands not working**: Check that your firewall allows UDP traffic on the specified port
- **Can't connect**: Verify the IP address is correct (use `ipconfig` on Windows or `ifconfig` on Mac/Linux)
- **Unity crashes**: Make sure all GameObject names match the ones in the script

View File

@ -0,0 +1,419 @@
using UnityEngine;
using UnityEngine.SceneManagement;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
/// <summary>
/// Simple UDP Command Listener for Unity
/// Listens for JSON commands on a specified UDP port and executes them
/// </summary>
public class UDPCommandListener : MonoBehaviour
{
[Header("UDP Settings")]
[Tooltip("Port to listen on for UDP commands")]
public int port = 7777;
[Header("References")]
[Tooltip("GameObject to toggle/manipulate (e.g., a cube)")]
public GameObject testObject;
[Header("Debug")]
public bool showDebugLogs = true;
// UDP client for receiving messages
private UdpClient udpClient;
private Thread receiveThread;
private bool isRunning = false;
// Timer state
private float timerDuration = 0f;
private float timerRemaining = 0f;
private bool timerActive = false;
// Rotation state
private float rotationSpeed = 0f;
void Start()
{
// Find the test object if not assigned
if (testObject == null)
{
testObject = GameObject.Find("TestCube");
if (testObject == null)
{
LogMessage("Warning: TestCube not found in scene. Create a GameObject named 'TestCube' or assign one manually.");
}
}
// Start listening for UDP messages
StartUDPListener();
}
void Update()
{
// Update timer
if (timerActive && timerRemaining > 0)
{
timerRemaining -= Time.deltaTime;
if (timerRemaining <= 0)
{
timerRemaining = 0;
timerActive = false;
LogMessage("Timer finished!");
}
}
// Update rotation
if (rotationSpeed != 0 && testObject != null)
{
testObject.transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
}
}
void OnGUI()
{
// Display timer on screen
if (timerActive && timerRemaining > 0)
{
GUIStyle style = new GUIStyle();
style.fontSize = 30;
style.normal.textColor = Color.white;
style.alignment = TextAnchor.UpperCenter;
GUI.Label(new Rect(0, 10, Screen.width, 50),
$"Timer: {timerRemaining:F1}s", style);
}
// Display UDP status
GUIStyle statusStyle = new GUIStyle();
statusStyle.fontSize = 14;
statusStyle.normal.textColor = isRunning ? Color.green : Color.red;
statusStyle.alignment = TextAnchor.UpperLeft;
GUI.Label(new Rect(10, 10, 300, 30),
$"UDP Listener: {(isRunning ? "Running" : "Stopped")} (Port {port})",
statusStyle);
}
/// <summary>
/// Starts the UDP listener thread
/// </summary>
void StartUDPListener()
{
try
{
udpClient = new UdpClient(port);
receiveThread = new Thread(new ThreadStart(ReceiveData));
receiveThread.IsBackground = true;
receiveThread.Start();
isRunning = true;
LogMessage($"UDP Listener started on port {port}");
}
catch (Exception e)
{
LogMessage($"Error starting UDP listener: {e.Message}");
}
}
/// <summary>
/// Receives data on a separate thread
/// </summary>
void ReceiveData()
{
while (isRunning)
{
try
{
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
byte[] data = udpClient.Receive(ref remoteEndPoint);
string message = Encoding.UTF8.GetString(data);
LogMessage($"Received UDP message: {message}");
// Process the command on the main thread
UnityMainThreadDispatcher.Instance().Enqueue(() => ProcessCommand(message));
}
catch (Exception e)
{
if (isRunning)
{
LogMessage($"Error receiving UDP data: {e.Message}");
}
}
}
}
/// <summary>
/// Processes a received command
/// </summary>
void ProcessCommand(string jsonCommand)
{
try
{
// Parse the JSON command
UDPCommand command = JsonUtility.FromJson<UDPCommand>(jsonCommand);
if (command == null || string.IsNullOrEmpty(command.command))
{
LogMessage("Invalid command format");
return;
}
LogMessage($"Processing command: {command.command}");
// Execute the command
switch (command.command.ToLower())
{
case "toggle_cube":
ToggleObject();
break;
case "set_object":
SetObjectState(command.target, command.value);
break;
case "start_timer":
StartTimer(command.value);
break;
case "stop_timer":
StopTimer();
break;
case "reset_scene":
ResetScene();
break;
case "rotate_cube":
SetRotation(command.value);
break;
case "change_color":
ChangeColor(command.value);
break;
default:
LogMessage($"Unknown command: {command.command}");
break;
}
}
catch (Exception e)
{
LogMessage($"Error processing command: {e.Message}");
}
}
// ========== Command Implementations ==========
void ToggleObject()
{
if (testObject != null)
{
testObject.SetActive(!testObject.activeSelf);
LogMessage($"TestCube toggled to: {testObject.activeSelf}");
}
else
{
LogMessage("TestCube not found!");
}
}
void SetObjectState(string targetName, string state)
{
GameObject target = string.IsNullOrEmpty(targetName) ? testObject : GameObject.Find(targetName);
if (target != null)
{
bool active = state.ToLower() == "true" || state == "1";
target.SetActive(active);
LogMessage($"{target.name} set to: {active}");
}
else
{
LogMessage($"GameObject '{targetName}' not found!");
}
}
void StartTimer(string durationStr)
{
if (float.TryParse(durationStr, out float duration))
{
timerDuration = duration;
timerRemaining = duration;
timerActive = true;
LogMessage($"Timer started for {duration} seconds");
}
else
{
LogMessage("Invalid timer duration");
}
}
void StopTimer()
{
timerActive = false;
timerRemaining = 0;
LogMessage("Timer stopped");
}
void ResetScene()
{
LogMessage("Resetting scene...");
Scene currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
}
void SetRotation(string speedStr)
{
if (float.TryParse(speedStr, out float speed))
{
rotationSpeed = speed;
LogMessage($"Rotation speed set to: {speed}");
}
else
{
LogMessage("Invalid rotation speed");
}
}
void ChangeColor(string colorName)
{
if (testObject != null)
{
Renderer renderer = testObject.GetComponent<Renderer>();
if (renderer != null)
{
Color newColor = Color.white;
switch (colorName.ToLower())
{
case "red":
newColor = Color.red;
break;
case "blue":
newColor = Color.blue;
break;
case "green":
newColor = Color.green;
break;
case "yellow":
newColor = Color.yellow;
break;
case "white":
newColor = Color.white;
break;
case "black":
newColor = Color.black;
break;
default:
LogMessage($"Unknown color: {colorName}");
return;
}
renderer.material.color = newColor;
LogMessage($"Color changed to: {colorName}");
}
else
{
LogMessage("TestCube has no Renderer component!");
}
}
else
{
LogMessage("TestCube not found!");
}
}
// ========== Utility Methods ==========
void LogMessage(string message)
{
if (showDebugLogs)
{
Debug.Log($"[UDP] {message}");
}
}
void OnApplicationQuit()
{
StopUDPListener();
}
void OnDestroy()
{
StopUDPListener();
}
void StopUDPListener()
{
isRunning = false;
if (receiveThread != null && receiveThread.IsAlive)
{
receiveThread.Abort();
}
if (udpClient != null)
{
udpClient.Close();
}
LogMessage("UDP Listener stopped");
}
}
/// <summary>
/// Data class for UDP commands
/// </summary>
[System.Serializable]
public class UDPCommand
{
public string command;
public string target;
public string value;
}
/// <summary>
/// Helper class to execute actions on the main Unity thread
/// This is necessary because UDP receives data on a separate thread
/// </summary>
public class UnityMainThreadDispatcher : MonoBehaviour
{
private static UnityMainThreadDispatcher _instance;
private static readonly System.Collections.Generic.Queue<Action> _executionQueue = new System.Collections.Generic.Queue<Action>();
public static UnityMainThreadDispatcher Instance()
{
if (_instance == null)
{
GameObject go = new GameObject("MainThreadDispatcher");
_instance = go.AddComponent<UnityMainThreadDispatcher>();
DontDestroyOnLoad(go);
}
return _instance;
}
public void Enqueue(Action action)
{
lock (_executionQueue)
{
_executionQueue.Enqueue(action);
}
}
void Update()
{
lock (_executionQueue)
{
while (_executionQueue.Count > 0)
{
_executionQueue.Dequeue().Invoke();
}
}
}
}

View File

@ -0,0 +1,81 @@
{
"id": "example-project-001",
"name": "Unity Example Project",
"ipAddress": "127.0.0.1",
"port": 7777,
"packages": [
{
"id": "cmd-001",
"name": "Toggle Cube",
"data": "{\"command\":\"toggle_cube\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-002",
"name": "Enable Cube",
"data": "{\"command\":\"set_object\",\"target\":\"TestCube\",\"value\":\"true\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-003",
"name": "Disable Cube",
"data": "{\"command\":\"set_object\",\"target\":\"TestCube\",\"value\":\"false\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-004",
"name": "Start Timer (10s)",
"data": "{\"command\":\"start_timer\",\"value\":\"10\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-005",
"name": "Stop Timer",
"data": "{\"command\":\"stop_timer\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-006",
"name": "Reset Scene",
"data": "{\"command\":\"reset_scene\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-007",
"name": "Rotate Cube Fast",
"data": "{\"command\":\"rotate_cube\",\"value\":\"100\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-008",
"name": "Rotate Cube Slow",
"data": "{\"command\":\"rotate_cube\",\"value\":\"20\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-009",
"name": "Stop Rotation",
"data": "{\"command\":\"rotate_cube\",\"value\":\"0\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-010",
"name": "Change Color Red",
"data": "{\"command\":\"change_color\",\"value\":\"red\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-011",
"name": "Change Color Blue",
"data": "{\"command\":\"change_color\",\"value\":\"blue\"}",
"ipAddress": "127.0.0.1"
},
{
"id": "cmd-012",
"name": "Change Color Green",
"data": "{\"command\":\"change_color\",\"value\":\"green\"}",
"ipAddress": "127.0.0.1"
}
]
}