179 lines
6.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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="/scense_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>