
Unity 3D 大学物理虚拟试验平台后端搭建
Unity 3D 大学物理虚拟试验平台后端搭建
此文档即为 Halluce 0.1 版本。
0. 需求分析与前置工作
与后端有关的项目需求:
前端使用 Unity 3D 与 C# 代码,便于导入 3dMax 中的模型;
使用 Python 代码实现相关算法,算法结果为文本/图片;
项目可以通过 Web 访问,如浏览器输入网址方式可以直接查看。
解决方案:
Unity 支持 WebGL 导出方式,导出文件含
index.html
,可以将 WebGL 项目部署在远程服务器上;C# 代码通过发 HTTP 请求的方式,调用Python后端代码,再通过Python代码进行计算,获取返回值;或者Python代码将图片结果生成至服务器某路径下,形成图床
C# 代码通过发 HTTP 请求的方式,获取在服务器内储存的生成图片(图床);
注意:Unity项目以WebGL形式导出时,所有C#代码最终会以JavaScript代码形式呈现,所以到最后还是JS代码发请求
技术栈使用:
前端呈现:Unity2021.3.20f1c1 + C# + WebGL2
后端与算法程序:Python 3.12
算法框架:matpotlib 3.8.2
后端框架:Flask 3.0.1
服务器部署:阿里云ECS CentOS7.9 + Nginx 1.20.2 + 宝塔面板 BtPanel 免费版8.0.5
本文档需要以下技术基础:
Unity + C# 基本使用
Python3 基本语法
云服务器的购买与配置 Linux命令基础
宝塔面板BtPanel的安装以及宝塔面板站点的配置
1. WebGL 的导出与部署
1.1 将Unity项目更改为WebGL格式
打开Files -> Build Settings ,将 Platform 切换为 WebGL(点击Switch Platform) ,若本地不存在 WebGL 环境可以根据相应提示下载安装。
1.2 WebGL 格式配置
可以看出现在无法 Build And Run,这是因为我们还没有配置好WebGL。
我们首先点开左下角的 Player Settings... 这里列出几个比较核心的部分,其余部分不在此深究。
Other Settings - 取消勾选 Auto Graphics API ,选择WebGL 2;
Other Settings - C++ Compiler Configuration 改为 Debug:便于 Js控制台打印Console.log信息
这时 Ctrl+S 保存,可以看到Build and Run变为可以点击:
1.3 WebGL 项目导出
右键 Build And Run 选择一个合适的目录
桌面下的一个英文文件夹即可
不要将输出目录放在项目本体文件夹下
大约20s~5min后,浏览器自动弹出本地网页的窗口,WebGL项目导出成功:
打开导出的项目目录,可以看到以下文件:
1.4 WebGL 项目部署在服务器上
在宝塔面板上添加一个站点,相关配置参考如下:
将刚才导出的文件传到站点目录中
运行8085端口的项目。确保宝塔面板和阿里云放行端口后即可访问该端口,看到页面。
2. Unity 项目读取图片
Unity 项目前端一般使用RawImage组件来加载图片,后端逻辑目前有两种方式实现:
RawImage组件读取项目的Assets/StreamingAssets/xxx.jpg加载图片,即C#代码中的
Application.StreamingAssetsPath
向后端Web图床(服务器内/公有云)发起Http请求,RawImage组件加载后端返回的图片
如果项目在本地运行,为了减少各种服务器的配置,我会采取第一种方案;但由于WebGL无法读取Assets/StreamingAssets目录(javascript代码在控制台报错),我们只能使用第二种方案——有点Web前后端的感觉。
以下我们用宝塔面板配置图床地址为例:
2.1 Nginx 请求配置
首先,打开编辑Nginx的配置文件(宝塔面板位于ngixn管理->配置修改);
我们只需要关注http.server
模块,即服务器块。
当你配置 Nginx,你主要通过定义服务器块(server blocks)来指定不同虚拟主机的行为。每个服务器块对应一个虚拟主机,允许你在同一台服务器上托管多个域名或应用。以下是典型的 Nginx 服务器块的配置结构:
server {
listen 80; # 监听的端口
server_name example.com; # 域名
root /path/to/your/site; # 网站根目录
# 配置项...
location / {
# 处理根路径请求的配置
}
location /images {
# 处理 /images 路径请求的配置
}
# 其他配置...
}
server {
listen 8080;
# 其他配置...
}
让我们解释这个配置的各个部分:
server
: 定义一个服务器块的开始,可以有多个服务器块。listen
: 指定服务器块监听的端口。例如,listen 80;
表示监听 HTTP 请求的默认端口。server_name
: 指定该虚拟主机对应的域名。多个域名可以用空格分隔,或者使用通配符。root
: 指定服务器块的根目录,即网站文件的基本路径。location
: 配置不同路径的请求处理。可以有多个location
块,每个块可以有自己的配置项,例如alias
、proxy_pass
等。
本项目的参考配置代码:
server {
listen 8085; # 你的端口号
server_name 47.93.19.130; # 服务器的IP地址
root /www/wwwroot/47.93.19.130:8085; # Web 应用程序的根目录
location / {
try_files $uri $uri/ /index.html; # 处理请求,优先尝试直接访问文件,然后尝试访问目录,最后返回index.html
}
location /image.jpg {
alias /www/wwwroot/47.93.19.130:8085/StreamingAssets/sp1.jpg; # 图片存储目录的绝对路径
try_files $uri $uri/ /index.html;
}
}
此时访问 http://47.93.19.130:8085/image.jpg
便可以看到/下载图片:
2.2 C#代码发起请求
这里使用协程加载图片,`LoadButton` 方法需要关联到按钮代码中,`imgPath` 视情况修改:
void LoadButton(){
private string imgPath = "http://47.93.19.130:8085/image.jpg";
StartCoroutine(LoadRemoteImage(imgPath));
}
IEnumerator LoadRemoteImage(string url)
{
using (UnityWebRequest www = UnityWebRequestTexture.GetTexture(url))
{
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
// 获取下载的纹理
Texture2D texture = ((DownloadHandlerTexture)www.downloadHandler).texture;
showImage.texture = texture;
}
else
{
Debug.LogError($"Failed to load image: {www.error}");
}
}
}
在 Unity 中运行相关代码,可以看见成功读取了图片:
2.3 Nginx 跨域配置
然而,当我们将项目以WebGL形式导出,以本地形式运行时,点击按钮,却报出以下错误:
看到 CROS policy ,脑海里第一反应就是:跨域问题!
关于什么是跨域问题:查看链接
我们可以在 Nginx解决跨域问题,在 location /image.jpg
处添加以下代码:
location /image.jpg {
# 图片存储目录的绝对路径
alias /www/wwwroot/47.93.19.130:8085/StreamingAssets/sp1.jpg;
# 处理请求,优先尝试直接访问文件,然后尝试访问目录,最后返回index.html
try_files $uri $uri/ /index.html;
# 解决跨域问题
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
再次点击按钮,不报相关错误,正确显示图片:
3. Flask 后端配置
3.1 Python 项目基本情况
该 Python 项目使用 matpotlib 框架,需要进行计算后输出结果。
3.2 Flask 后端程序
3.2.1 选型原因
我们后端程序接收到请求之后,要运行一个Python程序,因此选择 Python 语言的后端程序最方便。
而Python后端框架中,Django大而全,需要更多配置;Flask更轻量,导包后最少一个文件就可以做一个Controller接口,几乎不需要几行代码就可以实现。
因此,本项目使用Flask框架。
3.2.2 程序示例
# 导Flask相关包
from flask import Flask
from flask_cors import CORS
# 导算法程序模块
import test
# 基础配置
app = Flask(__name__)
application = app
CORS(app) # 跨域配置
location = r'/www/wwwroot/47.93.19.130:8085/StreamingAssets'
# 路由配置
@app.route('/<param1>/<param2>/<param3>', methods=['GET'])
def vision(param1, param2, param3):
# 执行算法程序
test.generate(param1, param2, param3, location)
# 返回相关内容
return "{\"success\": \"" + "true\", " + "\"data\": \"" + param1 + ' ' + param2 + ' ' + param3 + ' ' + location + "\"}\n"
# 启动类
if __name__ == '__main__':
# 关闭debug模式
app.debug = False
# 运行Flask程序,host字段表示所有ip均可访问,端口为8089
app.run (host = '0.0.0.0', port = 8089)
3.3 Flask 项目配置
3.2.1 准备:服务器 Python 版本
Flask 要求服务器 Python 版本要在3.9以上,目前服务器内为3.6版本,我们需要手动再装一个:
方案一:使用 Anaconda (略)
方案二:使用宝塔面板
宝塔面板 -> 网站 -> Python项目 -> Python版本管理 ->安装合适的版本,这里安装了3.12.0:
路径一般位于:`/www/server/pyporject_evn/versions/3.12.0/bin/python3.12`
3.2.2 项目配置
方案一 使用 Pycharm专业版 远程开发
优点:这种方法只需要宝塔面板打开端口,然后剩下的全是PyCharm的任务,方便Python程序员,无需进行Git协作,导包方便
缺点:不在宝塔面板管理范围内,不方便控制项目
第一步:配置项目:
打开 Pycharm 2023.3.2 专业版 ,找到 Remote Development
选择 SSH Connection的New Connection,直接根据提示输入信息即可:
成功打开后,不要忘记切换Python版本:
然后就可以愉快开发了:
第二步:导包
用PyCharm 最方便的一点就是,可以在PyCharm里直接用图形界面导包(和在Windows上开发一样),不用再去宝塔面板/服务器命令搞事情,很大程度上方便了Python程序员的导包问题
第三步:开端口
这需要阿里云安全组+宝塔面板同时开端口
方案二 使用宝塔SSH + 宝塔Python项目配置
首先将文件上传到目录:
然后进入网站->Python项目->添加Python项目,注意框架、运行方式、网络协议
提交,运行!
3.4 Flask 跨域问题
请求Flask后端的跨域问题可以通过导包+一行代码解决:
from flask_cors import CORS
# 基础配置
app = Flask(__name__)
application = app
CORS(app) # 跨域配置
3.5 C#代码发起请求
向之前请求图片一样,这里也发送请求,可以得到返回值(在本地的WebGL项目运行需要先对Flusk进行跨域处理)
IEnumerator SendRequest()
{
string url = baseUrl + '/'+ lambda + '/' + radius + "/" +mode;
Debug.Log(url);
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
string responseBody = request.downloadHandler.text;
Debug.Log("Response: " + responseBody);
// 在这里处理返回值,可以根据需要进行解析或其他操作
// 继续其他操作...
}
else
{
Debug.LogError("Error: " + request.error);
}
}
}
4. 目前的问题
程序发请求时,点击按钮后,加载图片响应不及时,必须多点击几次后才能正确加载。
解决方案:(1)延长加载时间,按按钮后增加程序的停止时间 (2)必须等待加载完图片后返回请求,必须等待请求返回后再发送加载图片的请求;(3)换一种加载图片的方法:接口本身直接返回图片,两个接口变成一个