179 lines
6.7 KiB
HTML
179 lines
6.7 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>元景 W-10 运行时执行引擎 — 游戏运行页</title>
|
||
<style>
|
||
html, body { margin:0; padding:0; height:100%; background:#0f1520; color:#dbe4f0;
|
||
font-family:"Microsoft YaHei", system-ui, sans-serif; }
|
||
#app { display:flex; flex-direction:column; height:100%; }
|
||
header { display:flex; align-items:center; gap:14px; padding:10px 16px;
|
||
background:#151d2e; border-bottom:1px solid #26344a; }
|
||
header h1 { font-size:16px; margin:0; font-weight:600; }
|
||
#statusBar { font-size:12px; color:#8fa3c0; }
|
||
#statusBar .ok { color:#4cd964; }
|
||
#statusBar .warn { color:#ffcc00; }
|
||
#controls { margin-left:auto; display:flex; gap:8px; }
|
||
button { background:#1e2a44; color:#dbe4f0; border:1px solid #33466b; border-radius:6px;
|
||
padding:6px 12px; cursor:pointer; font-size:13px; }
|
||
button:hover { background:#2a3a5e; }
|
||
button:disabled { opacity:.45; cursor:not-allowed; }
|
||
main { flex:1; display:flex; overflow:hidden; }
|
||
#stageWrap { flex:1; display:flex; align-items:center; justify-content:center;
|
||
background:#0b1018; }
|
||
canvas { background:#101a2c; border:1px solid #26344a; border-radius:8px; }
|
||
#side { width:320px; border-left:1px solid #26344a; display:flex; flex-direction:column; }
|
||
#side h3 { margin:0; padding:8px 12px; font-size:13px; color:#8fa3c0;
|
||
border-bottom:1px solid #26344a; }
|
||
#logBox { flex:1; overflow:auto; padding:8px 12px; font-size:12px; line-height:1.7; }
|
||
#logBox div { border-bottom:1px dashed #1d2940; padding:2px 0; }
|
||
#logBox .err { color:#ff6b6b; }
|
||
#logBox .evt { color:#5aa9ff; }
|
||
#tips { padding:8px 12px; font-size:12px; color:#7d90ad; border-top:1px solid #26344a; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<header>
|
||
<h1>🎮 元景 W-10 运行时执行引擎</h1>
|
||
<div id="statusBar">初始化中…</div>
|
||
<div id="controls">
|
||
<button id="btnStart">▶ 启动</button>
|
||
<button id="btnPause">⏸ 暂停</button>
|
||
<button id="btnResume">⏵ 恢复</button>
|
||
<button id="btnStop">⏹ 停止</button>
|
||
</div>
|
||
</header>
|
||
<main>
|
||
<div id="stageWrap"><canvas id="stage" width="760" height="500"></canvas></div>
|
||
<div id="side">
|
||
<h3>运行日志</h3>
|
||
<div id="logBox"></div>
|
||
<div id="tips">💡 点击画布中的实体触发 click 事件 → 路由到该实体脚本执行;<br>
|
||
定时器每 3 秒触发 box1 下移;角色移动后与箱子碰撞触发碰撞检测;<br>
|
||
点击「箱子B」触发故意抛错的脚本,验证降级不崩溃(W-10h)。</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<script src="/runtime/runtime.js"></script>
|
||
<script>
|
||
(function () {
|
||
'use strict';
|
||
var canvas = document.getElementById('stage');
|
||
var ctx = canvas.getContext('2d');
|
||
var logBox = document.getElementById('logBox');
|
||
var statusBar = document.getElementById('statusBar');
|
||
var btnStart = document.getElementById('btnStart');
|
||
var btnPause = document.getElementById('btnPause');
|
||
var btnResume = document.getElementById('btnResume');
|
||
var btnStop = document.getElementById('btnStop');
|
||
|
||
var rt = new window.RuntimeEngine({ worldId: 'w10_demo', canvas: canvas, tickMs: 16 });
|
||
var colors = { character: '#5aa9ff', prop: '#ffb454', npc: '#4cd964', object: '#9aa7bd' };
|
||
|
||
function log(msg, cls) {
|
||
var d = document.createElement('div');
|
||
d.textContent = msg;
|
||
if (cls) { d.className = cls; }
|
||
logBox.appendChild(d);
|
||
logBox.scrollTop = logBox.scrollHeight;
|
||
}
|
||
|
||
function draw() {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
rt.getEntities().forEach(function (e) {
|
||
ctx.fillStyle = colors[e.type] || colors.object;
|
||
ctx.strokeStyle = '#ffffff';
|
||
ctx.lineWidth = e.state === 'active' ? 3 : 1;
|
||
ctx.beginPath();
|
||
ctx.roundRect(e.x, e.y, e.width, e.height, 8);
|
||
ctx.fill();
|
||
ctx.stroke();
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.font = '12px sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(e.name, e.x + e.width / 2, e.y - 6);
|
||
// 状态小字
|
||
ctx.font = '10px sans-serif';
|
||
ctx.fillStyle = '#b8c6dc';
|
||
ctx.fillText(e.state, e.x + e.width / 2, e.y + e.height / 2 + 4);
|
||
});
|
||
}
|
||
|
||
function refreshStatus() {
|
||
var s = rt.getStatus();
|
||
var cls = s.degraded ? 'warn' : 'ok';
|
||
var modeTxt = s.mode === 'server' ? '服务端模式' : '独立模式(降级)';
|
||
statusBar.innerHTML = '<span class="' + cls + '">' + modeTxt + '</span>'
|
||
+ ' · 实体 ' + s.entities + ' · 帧 ' + s.frame
|
||
+ ' · 队列 ' + s.eventQueue + ' · 定时器 ' + s.timers
|
||
+ ' · 错误 ' + s.errorCount;
|
||
}
|
||
|
||
// 事件订阅(UI 侧)
|
||
rt.on('log', function (m) {
|
||
log(m.message, /错误/.test(m.message) ? 'err' : '');
|
||
});
|
||
rt.on('error', function (m) {
|
||
log('[降级] ' + m.message + '(引擎继续运行)', 'err');
|
||
});
|
||
rt.on('world', function (d) {
|
||
log('世界 ' + (d.world.name || d.world.id) + ' 初始化完成,模式=' + d.mode + ',实体=' + d.entities.length);
|
||
});
|
||
|
||
// 每帧重绘
|
||
rt.on('frame', function () { draw(); refreshStatus(); });
|
||
|
||
// 定时器演示:给 box1 绑一个 3s 定时器
|
||
rt.addTimer(3000, function () {
|
||
rt.dispatchEvent('timer', 'box1', {});
|
||
});
|
||
|
||
// 点击画布 -> 命中检测 -> 触发实体 click 事件(W-10d 路由入口)
|
||
canvas.addEventListener('click', function (ev) {
|
||
var rect = canvas.getBoundingClientRect();
|
||
var px = ev.clientX - rect.left;
|
||
var py = ev.clientY - rect.top;
|
||
var hit = null;
|
||
rt.getEntities().forEach(function (e) {
|
||
if (px >= e.x && px <= e.x + e.width && py >= e.y && py <= e.y + e.height) {
|
||
hit = e;
|
||
}
|
||
});
|
||
if (hit) {
|
||
log('点击实体 ' + hit.id + ' -> 触发 click 事件', 'evt');
|
||
rt.trigger(hit.id, 'click');
|
||
} else {
|
||
log('点击空白处(无实体命中)');
|
||
}
|
||
});
|
||
|
||
// 控件
|
||
btnStart.onclick = function () {
|
||
// 尝试服务端辅助初始化;失败自动降级独立模式(W-10i)
|
||
fetch('/runtime/api/load_world.dspy?world_id=w10_demo', { method: 'GET' })
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (data) {
|
||
rt.initWorld(data);
|
||
rt.start();
|
||
})
|
||
.catch(function (err) {
|
||
log('服务端不可用:' + err.message + ' -> 独立模式', 'err');
|
||
rt.initWorld(null);
|
||
rt.start();
|
||
});
|
||
refreshStatus();
|
||
};
|
||
btnPause.onclick = function () { rt.pause(); refreshStatus(); };
|
||
btnResume.onclick = function () { rt.resume(); refreshStatus(); };
|
||
btnStop.onclick = function () { rt.stop(); refreshStatus(); };
|
||
|
||
// 页面加载即自动启动(验收:开始玩初始化实体树)
|
||
btnStart.click();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|