added example folder
This commit is contained in:
63
Example/README.md
Normal file
63
Example/README.md
Normal 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
|
||||
|
||||
419
Example/UDPCommandListener.cs
Normal file
419
Example/UDPCommandListener.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
Example/unityudp_projects.json
Normal file
81
Example/unityudp_projects.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user