chore: allow tracking Build folder and add current build
@@ -3,8 +3,8 @@
|
|||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|
||||||
# --- Build / Output ---
|
# --- Build / Output ---
|
||||||
[Bb]uild/
|
# [Bb]uild/ # Modified by Codex to allow checking in Build folder
|
||||||
[Bb]uilds/
|
# [Bb]uilds/ # Modified by Codex to allow checking in Build folder
|
||||||
[Oo]utput/
|
[Oo]utput/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
[Ll]ogs/
|
[Ll]ogs/
|
||||||
|
|||||||
|
After Width: | Height: | Size: 665 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 96 B |
|
After Width: | Height: | Size: 109 B |
|
After Width: | Height: | Size: 74 B |
|
After Width: | Height: | Size: 84 B |
@@ -0,0 +1,16 @@
|
|||||||
|
body { padding: 0; margin: 0 }
|
||||||
|
#unity-container { position: absolute }
|
||||||
|
#unity-container.unity-desktop { left: 50%; top: 50%; transform: translate(-50%, -50%) }
|
||||||
|
#unity-container.unity-mobile { position: fixed; width: 100%; height: 100% }
|
||||||
|
#unity-canvas { background: #231F20 }
|
||||||
|
.unity-mobile #unity-canvas { width: 100%; height: 100% }
|
||||||
|
#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
|
||||||
|
#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }
|
||||||
|
#unity-progress-bar-empty { width: 141px; height: 18px; margin-top: 10px; margin-left: 6.5px; background: url('progress-bar-empty-dark.png') no-repeat center }
|
||||||
|
#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
|
||||||
|
#unity-footer { position: relative }
|
||||||
|
.unity-mobile #unity-footer { display: none }
|
||||||
|
#unity-webgl-logo { float:left; width: 204px; height: 38px; background: url('webgl-logo.png') no-repeat center }
|
||||||
|
#unity-build-title { float: right; margin-right: 10px; line-height: 38px; font-family: arial; font-size: 18px }
|
||||||
|
#unity-fullscreen-button { cursor:pointer; float: right; width: 38px; height: 38px; background: url('fullscreen-button.png') no-repeat center }
|
||||||
|
#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Unity WebGL Player | 运动伤害现场急救虚拟仿真实验</title>
|
||||||
|
<link rel="shortcut icon" href="TemplateData/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="TemplateData/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="unity-container" class="unity-desktop">
|
||||||
|
<canvas id="unity-canvas" width=1920 height=1080 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");
|
||||||
|
|
||||||
|
// Shows a temporary message banner/ribbon for a few seconds, or
|
||||||
|
// a permanent error message on top of the canvas if type=='error'.
|
||||||
|
// If type=='warning', a yellow highlight color is used.
|
||||||
|
// Modify or remove this function to customize the visually presented
|
||||||
|
// way that non-critical warnings and error messages are presented to the
|
||||||
|
// user.
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// By default, Unity keeps WebGL canvas render target size matched with
|
||||||
|
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
|
||||||
|
// Set this to false if you want to decouple this synchronization from
|
||||||
|
// happening inside the engine, and you would instead like to size up
|
||||||
|
// the canvas DOM size and WebGL render target sizes yourself.
|
||||||
|
// config.matchWebGLToCanvasSize = false;
|
||||||
|
|
||||||
|
// If you would like all file writes inside Unity Application.persistentDataPath
|
||||||
|
// directory to automatically persist so that the contents are remembered when
|
||||||
|
// the user revisits the site the next time, uncomment the following line:
|
||||||
|
// config.autoSyncPersistentDataPath = true;
|
||||||
|
// This autosyncing is currently not the default behavior to avoid regressing
|
||||||
|
// existing user projects that might rely on the earlier manual
|
||||||
|
// JS_FileSystem_Sync() behavior, but in future Unity version, this will be
|
||||||
|
// expected to change.
|
||||||
|
|
||||||
|
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
||||||
|
// Mobile device style: fill the whole browser client area with the game canvas:
|
||||||
|
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.name = 'viewport';
|
||||||
|
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
container.className = "unity-mobile";
|
||||||
|
canvas.className = "unity-mobile";
|
||||||
|
|
||||||
|
// To lower canvas resolution on mobile devices to gain some
|
||||||
|
// performance, uncomment the following line:
|
||||||
|
// config.devicePixelRatio = 1;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Desktop style: Render the game canvas in a window that can be maximized to fullscreen:
|
||||||
|
|
||||||
|
canvas.style.width = "1920px";
|
||||||
|
canvas.style.height = "1080px";
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingBar.style.display = "block";
|
||||||
|
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
script.onload = () => {
|
||||||
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
|
progressBarFull.style.width = 100 * progress + "%";
|
||||||
|
}).then((unityInstance) => {
|
||||||
|
loadingBar.style.display = "none";
|
||||||
|
fullscreenButton.onclick = () => {
|
||||||
|
unityInstance.SetFullscreen(1);
|
||||||
|
};
|
||||||
|
}).catch((message) => {
|
||||||
|
alert(message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "Build",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 8080;
|
||||||
|
const ROOT = __dirname;
|
||||||
|
|
||||||
|
const MIME = {
|
||||||
|
'.html': 'text/html; charset=utf-8',
|
||||||
|
'.js': 'application/javascript',
|
||||||
|
'.wasm': 'application/wasm',
|
||||||
|
'.data': 'application/octet-stream',
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.json': 'application/json',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
};
|
||||||
|
|
||||||
|
function extType(p) {
|
||||||
|
return MIME[path.extname(p).toLowerCase()] || 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首页:基于原始 index.html,注入启动封面(不修改任何 Unity 加载逻辑)
|
||||||
|
function serveIndex(res) {
|
||||||
|
try {
|
||||||
|
let html = fs.readFileSync(path.join(ROOT, 'index.html'), 'utf-8');
|
||||||
|
|
||||||
|
// 注入到 </head> 之前的 CSS
|
||||||
|
const css = `
|
||||||
|
<style>
|
||||||
|
#landing-overlay {
|
||||||
|
position: fixed; inset: 0; z-index: 9999;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
background: radial-gradient(ellipse at center, #2a2526 0%, #1a1516 70%, #0d0a0b 100%);
|
||||||
|
transition: opacity 0.5s ease;
|
||||||
|
}
|
||||||
|
#landing-overlay.hidden { opacity: 0; pointer-events: none; }
|
||||||
|
#landing-overlay .logo-area { margin-bottom: 60px; text-align: center; }
|
||||||
|
#landing-overlay .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-overlay 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-overlay .subtitle {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: clamp(14px, 1.8vw, 18px);
|
||||||
|
color: rgba(255,255,255,0.45); letter-spacing: 0.15em;
|
||||||
|
}
|
||||||
|
#landing-btn {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
#landing-btn:hover {
|
||||||
|
transform: translateY(-2px) scale(1.03);
|
||||||
|
box-shadow: 0 12px 40px rgba(74, 144, 217, 0.5);
|
||||||
|
}
|
||||||
|
#landing-btn:active { transform: translateY(0) scale(0.98); }
|
||||||
|
#landing-btn .arrow {
|
||||||
|
display: inline-block; font-size: 1.2em;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
#landing-btn:hover .arrow { transform: translateX(4px); }
|
||||||
|
#landing-overlay .hint {
|
||||||
|
margin-top: 40px; font-size: 13px;
|
||||||
|
color: rgba(255,255,255,0.25); letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
</style>`;
|
||||||
|
|
||||||
|
// 注入到 <body> 最前面的 overlay
|
||||||
|
const overlay = `
|
||||||
|
<div id="landing-overlay">
|
||||||
|
<div class="logo-area">
|
||||||
|
<div class="icon"></div>
|
||||||
|
<h1>运动伤害现场急救虚拟仿真实验</h1>
|
||||||
|
<p class="subtitle">VIRTUAL SIMULATION EXPERIMENT</p>
|
||||||
|
</div>
|
||||||
|
<button id="landing-btn" onclick="enterExperiment()">
|
||||||
|
进入全屏实验
|
||||||
|
<span class="arrow">→</span>
|
||||||
|
</button>
|
||||||
|
<p class="hint">点击按钮进入全屏沉浸式实验环境</p>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
// 注入到 </body> 之前的 JS(不修改原始 Unity 加载逻辑)
|
||||||
|
const js = `
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var overlay = document.getElementById('landing-overlay');
|
||||||
|
|
||||||
|
window.enterExperiment = function() {
|
||||||
|
var el = document.documentElement;
|
||||||
|
var requestFS = el.requestFullscreen || el.webkitRequestFullscreen
|
||||||
|
|| el.msRequestFullscreen || el.mozRequestFullScreen;
|
||||||
|
|
||||||
|
function go() {
|
||||||
|
overlay.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestFS) {
|
||||||
|
requestFS.call(el).then(go).catch(function(err) {
|
||||||
|
console.warn('全屏请求失败:', err);
|
||||||
|
go();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
go();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>`;
|
||||||
|
|
||||||
|
html = html.replace('</head>', css + '\n</head>');
|
||||||
|
html = html.replace('<body>', '<body>\n' + overlay);
|
||||||
|
html = html.replace('</body>', js + '\n</body>');
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(html);
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(500);
|
||||||
|
res.end('Internal Server Error');
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态文件
|
||||||
|
function serveStatic(req, res) {
|
||||||
|
let fp = path.join(ROOT, req.url.split('?')[0]);
|
||||||
|
if (!fp.startsWith(ROOT)) { res.writeHead(403); res.end('Forbidden'); return; }
|
||||||
|
fs.readFile(fp, (err, data) => {
|
||||||
|
if (err) { res.writeHead(404); res.end('Not Found'); return; }
|
||||||
|
res.writeHead(200, { 'Content-Type': extType(fp) });
|
||||||
|
res.end(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
http.createServer((req, res) => {
|
||||||
|
if (req.url === '/' || req.url === '/index.html') return serveIndex(res);
|
||||||
|
serveStatic(req, res);
|
||||||
|
}).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('');
|
||||||
|
});
|
||||||