0a1fce450c
- 删除三套旧菜单系统 (MainMenu.cs, MainMenuUI.cs, ModeSelectionUI.cs) - Ground 添加 BoxCollider 并设置 tag 为 Ground - UIManager.OnReturnClicked 改为通过 PageController 返回首页 - PageController 增加 ReturnToHome 方法,管理 BtnFixationBack 可见性 - 清理废弃场景 Experiment.unity / ModeSelection.unity 及历史截图
366 lines
11 KiB
JavaScript
366 lines
11 KiB
JavaScript
const express = require('express');
|
||
const path = require('path');
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 8080;
|
||
|
||
const ROOT = __dirname;
|
||
|
||
// 首页 —— 全屏启动页面
|
||
// 必须在静态文件中间件之前注册,否则 express.static 会优先返回 index.html
|
||
app.get('/', (req, res) => {
|
||
res.send(`<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||
<title>运动伤害现场急救虚拟仿真实验</title>
|
||
<link rel="shortcut icon" href="TemplateData/favicon.ico">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||
background: #231F20;
|
||
color: #fff;
|
||
overflow: hidden;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
}
|
||
|
||
/* ========== 启动封面 ========== */
|
||
#landing {
|
||
position: fixed; inset: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: radial-gradient(ellipse at center, #2a2526 0%, #1a1516 70%, #0d0a0b 100%);
|
||
z-index: 100;
|
||
transition: opacity 0.5s ease;
|
||
}
|
||
|
||
#landing.hidden {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
#landing .logo-area {
|
||
margin-bottom: 60px;
|
||
text-align: center;
|
||
}
|
||
|
||
#landing .logo-area .icon {
|
||
width: 120px;
|
||
height: 120px;
|
||
margin: 0 auto 30px;
|
||
background: url('TemplateData/unity-logo-light.png') no-repeat center;
|
||
background-size: contain;
|
||
filter: drop-shadow(0 0 30px rgba(255,255,255,0.15));
|
||
}
|
||
|
||
#landing h1 {
|
||
font-size: clamp(24px, 4vw, 42px);
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
color: #fff;
|
||
text-shadow: 0 2px 20px rgba(255,255,255,0.2);
|
||
}
|
||
|
||
#landing .subtitle {
|
||
margin-top: 12px;
|
||
font-size: clamp(14px, 1.8vw, 18px);
|
||
color: rgba(255,255,255,0.45);
|
||
letter-spacing: 0.15em;
|
||
}
|
||
|
||
#btn-start {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 56px;
|
||
font-size: clamp(16px, 2vw, 20px);
|
||
font-weight: 600;
|
||
letter-spacing: 0.1em;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, #4a90d9 0%, #357abd 100%);
|
||
border: none;
|
||
border-radius: 50px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
box-shadow: 0 8px 32px rgba(74, 144, 217, 0.35);
|
||
}
|
||
|
||
#btn-start:hover {
|
||
transform: translateY(-2px) scale(1.03);
|
||
box-shadow: 0 12px 40px rgba(74, 144, 217, 0.5);
|
||
}
|
||
|
||
#btn-start:active {
|
||
transform: translateY(0) scale(0.98);
|
||
}
|
||
|
||
#btn-start .arrow {
|
||
display: inline-block;
|
||
font-size: 1.2em;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
#btn-start:hover .arrow {
|
||
transform: translateX(4px);
|
||
}
|
||
|
||
#landing .hint {
|
||
margin-top: 40px;
|
||
font-size: 13px;
|
||
color: rgba(255,255,255,0.25);
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
/* ========== Unity 容器 (默认隐藏) ========== */
|
||
#unity-container {
|
||
position: fixed;
|
||
inset: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
display: none;
|
||
background: #231F20;
|
||
}
|
||
|
||
#unity-container.active {
|
||
display: block;
|
||
}
|
||
|
||
#unity-canvas {
|
||
width: 100% !important;
|
||
height: 100% !important;
|
||
background: #231F20;
|
||
display: block;
|
||
}
|
||
|
||
/* ========== 加载进度条 ========== */
|
||
#unity-loading-bar {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
display: none;
|
||
text-align: center;
|
||
z-index: 10;
|
||
}
|
||
#unity-loading-bar.show { display: block; }
|
||
|
||
#unity-logo {
|
||
width: 154px; height: 130px;
|
||
margin: 0 auto;
|
||
background: url('TemplateData/unity-logo-dark.png') no-repeat center;
|
||
}
|
||
#unity-progress-bar-empty {
|
||
width: 141px; height: 18px;
|
||
margin: 10px auto 0;
|
||
background: url('TemplateData/progress-bar-empty-dark.png') no-repeat center;
|
||
}
|
||
#unity-progress-bar-full {
|
||
width: 0%; height: 18px;
|
||
background: url('TemplateData/progress-bar-full-dark.png') no-repeat center;
|
||
}
|
||
|
||
#unity-footer {
|
||
position: absolute;
|
||
bottom: 16px;
|
||
left: 16px;
|
||
right: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 10;
|
||
}
|
||
#unity-webgl-logo {
|
||
width: 204px; height: 38px;
|
||
background: url('TemplateData/webgl-logo.png') no-repeat center;
|
||
}
|
||
#unity-build-title {
|
||
color: rgba(255,255,255,0.5);
|
||
font-family: arial, sans-serif;
|
||
font-size: 14px;
|
||
}
|
||
#unity-fullscreen-button {
|
||
cursor: pointer;
|
||
width: 38px; height: 38px;
|
||
background: url('TemplateData/fullscreen-button.png') no-repeat center;
|
||
}
|
||
#unity-warning {
|
||
position: absolute; left: 50%; top: 5%;
|
||
transform: translate(-50%);
|
||
background: white; color: #333;
|
||
padding: 10px; display: none; z-index: 20;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ===== 启动封面 ===== -->
|
||
<div id="landing">
|
||
<div class="logo-area">
|
||
<div class="icon"></div>
|
||
<h1>运动伤害现场急救虚拟仿真实验</h1>
|
||
<p class="subtitle">VIRTUAL SIMULATION EXPERIMENT</p>
|
||
</div>
|
||
<button id="btn-start" onclick="enterFullscreen()">
|
||
进入全屏实验
|
||
<span class="arrow">→</span>
|
||
</button>
|
||
<p class="hint">点击按钮进入全屏沉浸式实验环境</p>
|
||
</div>
|
||
|
||
<!-- ===== Unity 容器 ===== -->
|
||
<div id="unity-container">
|
||
<canvas id="unity-canvas" tabindex="-1"></canvas>
|
||
<div id="unity-loading-bar">
|
||
<div id="unity-logo"></div>
|
||
<div id="unity-progress-bar-empty">
|
||
<div id="unity-progress-bar-full"></div>
|
||
</div>
|
||
</div>
|
||
<div id="unity-warning"></div>
|
||
<div id="unity-footer">
|
||
<div id="unity-webgl-logo"></div>
|
||
<div id="unity-fullscreen-button"></div>
|
||
<div id="unity-build-title">运动伤害现场急救虚拟仿真实验</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
var container = document.querySelector("#unity-container");
|
||
var canvas = document.querySelector("#unity-canvas");
|
||
var loadingBar = document.querySelector("#unity-loading-bar");
|
||
var progressBarFull = document.querySelector("#unity-progress-bar-full");
|
||
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
|
||
var warningBanner = document.querySelector("#unity-warning");
|
||
var landing = document.querySelector("#landing");
|
||
|
||
function unityShowBanner(msg, type) {
|
||
function updateBannerVisibility() {
|
||
warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
|
||
}
|
||
var div = document.createElement('div');
|
||
div.innerHTML = msg;
|
||
warningBanner.appendChild(div);
|
||
if (type == 'error') div.style = 'background: red; padding: 10px;';
|
||
else {
|
||
if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
|
||
setTimeout(function() {
|
||
warningBanner.removeChild(div);
|
||
updateBannerVisibility();
|
||
}, 5000);
|
||
}
|
||
updateBannerVisibility();
|
||
}
|
||
|
||
var buildUrl = "Build";
|
||
var loaderUrl = buildUrl + "/Build.loader.js";
|
||
var config = {
|
||
dataUrl: buildUrl + "/Build.data",
|
||
frameworkUrl: buildUrl + "/Build.framework.js",
|
||
codeUrl: buildUrl + "/Build.wasm",
|
||
streamingAssetsUrl: "StreamingAssets",
|
||
companyName: "DefaultCompany",
|
||
productName: "运动伤害现场急救虚拟仿真实验",
|
||
productVersion: "0.1.0",
|
||
showBanner: unityShowBanner,
|
||
};
|
||
|
||
var unityInstance = null;
|
||
|
||
// ========== 进入全屏 ==========
|
||
function enterFullscreen() {
|
||
var el = document.documentElement;
|
||
|
||
var requestFS = el.requestFullscreen || el.webkitRequestFullscreen
|
||
|| el.msRequestFullscreen || el.mozRequestFullScreen;
|
||
|
||
if (requestFS) {
|
||
requestFS.call(el).then(function() {
|
||
startUnity();
|
||
}).catch(function(err) {
|
||
// 全屏被拒绝(例如用户取消),仍然启动 Unity
|
||
console.warn('全屏请求失败,以普通模式启动:', err);
|
||
startUnity();
|
||
});
|
||
} else {
|
||
// 不支持全屏 API 的浏览器
|
||
startUnity();
|
||
}
|
||
}
|
||
|
||
// ========== 启动 Unity ==========
|
||
function startUnity() {
|
||
// 隐藏封面
|
||
landing.classList.add('hidden');
|
||
|
||
// 显示 Unity 容器
|
||
container.classList.add('active');
|
||
loadingBar.classList.add('show');
|
||
|
||
// 加载 Unity
|
||
var script = document.createElement("script");
|
||
script.src = loaderUrl;
|
||
script.onload = function() {
|
||
createUnityInstance(canvas, config, function(progress) {
|
||
progressBarFull.style.width = 100 * progress + "%";
|
||
}).then(function(instance) {
|
||
unityInstance = instance;
|
||
loadingBar.classList.remove('show');
|
||
|
||
// 全屏按钮
|
||
fullscreenButton.onclick = function() {
|
||
unityInstance.SetFullscreen(1);
|
||
};
|
||
}).catch(function(message) {
|
||
alert('加载失败: ' + message);
|
||
});
|
||
};
|
||
document.body.appendChild(script);
|
||
}
|
||
|
||
// ========== 监听全屏退出 ==========
|
||
document.addEventListener('fullscreenchange', function() {
|
||
if (!document.fullscreenElement && unityInstance) {
|
||
canvas.style.width = '100%';
|
||
canvas.style.height = '100%';
|
||
}
|
||
});
|
||
|
||
// ========== 防止意外滚动 ==========
|
||
document.addEventListener('gesturestart', function(e) { e.preventDefault(); });
|
||
</script>
|
||
</body>
|
||
</html>`);
|
||
});
|
||
|
||
// 静态文件服务
|
||
app.use(express.static(ROOT, {
|
||
setHeaders: (res, filePath) => {
|
||
// 确保 .wasm 有正确的 MIME type(Chrome 需要)
|
||
if (filePath.endsWith('.wasm')) {
|
||
res.setHeader('Content-Type', 'application/wasm');
|
||
}
|
||
if (filePath.endsWith('.js')) {
|
||
res.setHeader('Content-Type', 'application/javascript');
|
||
}
|
||
}
|
||
}));
|
||
|
||
// 启动服务
|
||
app.listen(PORT, () => {
|
||
console.log('');
|
||
console.log(' ╔══════════════════════════════════════════════════╗');
|
||
console.log(' ║ 运动伤害现场急救虚拟仿真实验 - WebGL Server ║');
|
||
console.log(' ║ ║');
|
||
console.log(' ║ Server running at: http://localhost:' + PORT + ' ║');
|
||
console.log(' ║ Press Ctrl+C to stop ║');
|
||
console.log(' ╚══════════════════════════════════════════════════╝');
|
||
console.log('');
|
||
});
|