Java 和 Unity 运行在两个独立的进程中,它们之间不能直接调用函数,必须通过某种“桥梁”进行进程间通信。
最主流、最稳定、也是 Unity 官方推荐的方式是 Socket 通信,还有 HTTP REST API、文件共享、以及更底层的 JNI 等方式。
下面我将详细介绍最推荐的 Socket 通信 方案,并简要介绍其他方案。
核心方案:Socket 通信 (TCP/UDP)
这种方式好比 Java 和 Unity 各自有一个“电话”,它们通过一个共同的“电话号码”(IP 地址和端口号)来拨打电话,互相传递“消息”(字符串、二进制数据等)。
工作流程
- Unity (服务器端):启动一个 Socket 服务器,监听来自 Java 客户端的连接请求。
- Java (客户端):作为客户端,知道 Unity 服务器的 IP 和端口,主动发起连接。
- 通信:
- Java -> Unity:Java 客户端将请求("LOAD_SCENE_MAIN")打包成消息,通过 Socket 发送给 Unity。
- Unity (接收):Unity 服务器接收到消息,解析出内容,然后执行相应的操作(如加载场景、移动物体、播放动画等)。
- Unity -> Java:Unity 执行完毕后,可以将结果("Scene loaded successfully")打包,再通过 Socket 发送回 Java。
- Java (接收):Java 客户端接收到 Unity 的响应,进行后续处理。
详细实现步骤
第一步:Unity 部分 (作为 Socket 服务器)
我们需要在 Unity 中创建一个脚本来处理网络通信。
-
创建 C# 脚本:在 Unity 项目中创建一个新的 C# 脚本,命名为
UnitySocketServer.cs。 -
编写服务器代码:
using UnityEngine; using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; public class UnitySocketServer : MonoBehaviour { // 在 Inspector 面板中配置端口号 public int port = 8888; private TcpListener server; private Thread serverThread; private bool isRunning = false; void Start() { // 在一个单独的线程中运行服务器,避免阻塞 Unity 的主线程 serverThread = new Thread(new ThreadStart(StartServer)); serverThread.IsBackground = true; serverThread.Start(); Debug.Log("Unity Socket Server 启动中..."); } void StartServer() { try { server = new TcpListener(IPAddress.Any, port); server.Start(); isRunning = true; Debug.Log($"服务器已启动,监听端口: {port}"); while (isRunning) { // 接受客户端的连接 TcpClient client = server.AcceptTcpClient(); Debug.Log("Java 客户端已连接!"); // 为每个客户端连接创建一个新的线程来处理 Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient)); clientThread.IsBackground = true; clientThread.Start(client); } } catch (SocketException e) { Debug.LogError($"Socket 错误: {e}"); } finally { server?.Stop(); } } void HandleClient(object obj) { TcpClient client = (TcpClient)obj; NetworkStream stream = client.GetStream(); try { byte[] buffer = new byte[1024]; int bytesRead; // 循环读取客户端发送的数据 while (isRunning && (bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) { // 将接收到的字节数组转换为字符串 string message = Encoding.UTF8.GetString(buffer, 0, bytesRead); Debug.Log($"收到来自 Java 的消息: {message}"); // --- 在这里处理来自 Java 的请求 --- string response = ProcessRequest(message); // 将响应发送回客户端 byte[] responseData = Encoding.UTF8.GetBytes(response); stream.Write(responseData, 0, responseData.Length); Debug.Log($"已向 Java 发送响应: {response}"); } } catch (Exception e) { Debug.LogError($"处理客户端时出错: {e}"); } finally { stream?.Close(); client?.Close(); Debug.Log("客户端连接已关闭。"); } } string ProcessRequest(string request) { // 这里实现你的业务逻辑 // 根据请求字符串执行不同的 Unity 操作 switch (request) { case "LOAD_SCENE_MAIN": // 加载主场景 (注意:场景切换必须在主线程进行) // 这里我们只是简单返回,实际场景切换需要更复杂的处理 // 使用队列将切换请求传递给主线程 return "命令已接收: 正在准备加载主场景..."; case "MOVE_PLAYER": // 移动玩家 // GameObject player = GameObject.Find("Player"); // if (player != null) player.transform.Translate(Vector3.forward * 1.0f); return "命令已接收: 正在移动玩家..."; case "GET_PLAYER_POS": // 获取玩家位置并返回 // GameObject player = GameObject.Find("Player"); // string pos = "Player position: " + player.transform.position.ToString(); // return pos; return "Player position: (0, 0, 0)"; // 模拟返回 default: return "未知命令: " + request; } } void OnApplicationQuit() { isRunning = false; server?.Stop(); serverThread?.Interrupt(); } } -
配置和运行:
- 将
UnitySocketServer.cs脚本挂载到 Unity 场景中的任意一个 GameObject 上(比如一个空对象)。 - 在 Inspector 面板中,你可以看到
Port变量,可以修改为你想要的端口号。 - 打开 Unity 的 Console 窗口,然后点击 Play 按钮,当看到 "Unity Socket Server 启动中..." 和 "服务器已启动..." 的消息时,服务器就成功运行了。记住这个端口号。
- 将
第二步:Java 部分 (作为 Socket 客户端)
现在我们来创建一个 Java 程序作为客户端,去连接 Unity 服务器。
-
创建 Java 项目:你可以使用任何 IDE(如 IntelliJ IDEA, Eclipse)或 Maven/Gradle 创建一个简单的 Java 项目。
-
编写客户端代码:
import java.io.*; import java.net.Socket; import java.net.UnknownHostException; public class UnityJavaClient { private static final String UNITY_HOST = "127.0.0.1"; // Unity 运行在同一台机器上,所以是本地回环地址 private static final int UNITY_PORT = 8888; // 必须与 Unity 中设置的端口号一致 public static void main(String[] args) { try (Socket socket = new Socket(UNITY_HOST, UNITY_PORT); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { System.out.println("成功连接到 Unity 服务器!"); // --- 发送请求并接收响应 --- // 示例1: 加载场景 sendCommand(out, in, "LOAD_SCENE_MAIN"); // 示例2: 移动玩家 sendCommand(out, in, "MOVE_PLAYER"); // 示例3: 获取玩家位置 sendCommand(out, in, "GET_PLAYER_POS"); } catch (UnknownHostException e) { System.err.println("无法找到主机: " + UNITY_HOST); e.printStackTrace(); } catch (IOException e) { System.err.println("I/O 错误: " + e.getMessage()); e.printStackTrace(); } } /** * 发送命令并打印响应 * @param out 输出流 * @param in 输入流 * @param command 要发送的命令 * @throws IOException */ private static void sendCommand(PrintWriter out, BufferedReader in, String command) throws IOException { System.out.println("\n>>> 发送命令: " + command); out.println(command); // 发送命令 String response = in.readLine(); // 读取 Unity 的响应 System.out.println("<<< 收到响应: " + response); } } -
运行:
- 确保 Unity 的 Play 模式正在运行,并且服务器已启动。
- 运行 Java 的
main方法。 - 观察控制台输出:
- Java 控制台会显示连接成功,以及发送和接收的消息。
- Unity 的 Console 窗口也会显示相应的日志,如 "Java 客户端已连接!" 和 "收到来自 Java 的消息: LOAD_SCENE_MAIN"。
至此,你已经成功实现了 Java 调用 Unity3D 的基本通信!
其他调用方式简介
HTTP REST API
这种方式基于 Web 通信,非常适合与 Web 技术栈集成。
- Unity (服务器):使用 Unity 的
UnityWebRequest或第三方库(如FastEndpoints)创建一个 HTTP 服务器,Java 发送 HTTP 请求(GET/POST)到 Unity 的某个 URL,Unity 在对应的 Endpoint 处理请求并返回 JSON 或 XML 格式的响应。 - Java (客户端):使用
HttpClient(Java 11+) 或OkHttp、RestTemplate等库发送 HTTP 请求。 - 优点:标准、易于调试(可用浏览器或 Postman 测试)、跨语言通用。
- 缺点:相比 Socket,协议开销较大,不适合超高频率、低延迟的实时数据交换。
文件共享
- 原理:Java 将指令写入一个预设的文件(如
request.txt),Unity 定时轮询这个文件,读取内容后执行,并可能将结果写入另一个文件(如response.txt)。 - 优点:实现极其简单,无需网络知识。
- 缺点:
- 性能差:频繁的文件 I/O 操作很慢。
- 不可靠:文件读写容易出错,且无法保证原子性。
- 耦合度高:需要约定好文件的格式和位置。
- 仅适用于低频、非实时的任务。
JNI (Java Native Interface)
这是最底层、最复杂的方式。
- 原理:将 Unity 的 C++/C# 库编译成一个动态链接库(
.dllfor Windows,.sofor Linux,.dylibfor macOS),然后通过 JNI 让 Java 加载并调用这个库中的 native 函数。 - 优点:性能最高,可以实现近乎原生的调用。
- 缺点:
- 极其复杂:需要深入理解 JNI、C/C++ 以及 Unity 的底层构建流程。
- 平台依赖:为不同平台编译和维护库非常繁琐。
- 调试困难:跨语言调试非常痛苦。
- 破坏架构:将 Java 和 Unity 紧密耦合,违背了“松耦合”的设计原则。
- 除非你有特殊需求(如必须在同一个进程内进行高性能计算),否则强烈不推荐初学者或大多数项目使用 JNI。
总结与推荐
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Socket (TCP/UDP) | 性能好、实时性强、架构清晰、跨平台、Unity 官方推荐 | 需要自己设计消息协议(如 JSON, Protobuf) | 绝大多数场景,特别是游戏逻辑控制、设备数据交互、实时指令下发 |
| HTTP REST API | 标准通用、易于调试、与 Web 集成方便 | 协议开销大、不适合超高频率通信 | 需要提供 Web 服务接口、与前端/后端系统集成的场景 |
| 文件共享 | 实现简单、无网络依赖 | 性能差、不可靠、耦合度高 | 极低频的、非实时的任务,如日志记录、配置更新 |
| JNI | 性能最高、进程内调用 | 极其复杂、平台依赖、调试困难、破坏架构 | 特殊的高性能计算需求,且能接受巨大的开发和维护成本 |
对于绝大多数 Java 调用 Unity3D 的需求,Socket 通信是首选和最佳实践。 它在性能、灵活性和开发难度之间取得了最好的平衡。
