feat(工作台v2): 游戏切换/新建按钮/内联添加场景实体(去prompt)
This commit is contained in:
parent
a476ebffd9
commit
899ae02733
@ -32,6 +32,8 @@
|
||||
<body>
|
||||
<header>
|
||||
<h1>🎮 游戏工作台</h1>
|
||||
<select id="game_sel" style="padding:6px 10px;border-radius:4px;border:1px solid #374151;background:#1F2937;color:#F9FAFB"></select>
|
||||
<button class="btn-gray" onclick="wb.newGame()">+ 新建游戏</button>
|
||||
<input type="text" id="game_name" placeholder="游戏名称">
|
||||
<label><input type="checkbox" id="game_shared"> 世界共享</label>
|
||||
<button class="btn-primary" onclick="wb.saveGame()">保存游戏</button>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/* 元景游戏工作台:game 聚合根单页编辑器 */
|
||||
/* 元景游戏工作台 v2:game 聚合根单页编辑器(内联输入,无 prompt) */
|
||||
(function () {
|
||||
function api(path, params) {
|
||||
return fetch(path, {
|
||||
@ -9,7 +9,7 @@
|
||||
}
|
||||
function base() { return '/scense_game'; }
|
||||
|
||||
var state = { game: null, data: null, sel: null }; // sel: {type, id, name}
|
||||
var state = { game: null, data: null, sel: null, games: [], cur_script: null, inline: null };
|
||||
|
||||
function msg(t, ok) {
|
||||
var el = document.getElementById('msg');
|
||||
@ -17,31 +17,54 @@
|
||||
el.className = ok ? 'ok' : 'err';
|
||||
}
|
||||
|
||||
function newGame() {
|
||||
state.game = null; state.data = null; state.sel = null; state.cur_script = null;
|
||||
document.getElementById('game_name').value = '';
|
||||
document.getElementById('game_sel').value = '';
|
||||
renderTree(null);
|
||||
document.getElementById('cur_obj').textContent = '未选择对象';
|
||||
document.getElementById('code').value = '';
|
||||
msg('填写名称后点保存游戏(将自动创建 1:1 世界)', true);
|
||||
}
|
||||
|
||||
function saveGame() {
|
||||
var name = document.getElementById('game_name').value.trim();
|
||||
if (state.game) { msg('游戏已存在: ' + state.game.id, true); return; }
|
||||
if (state.game) { msg('当前游戏: ' + state.game.name + '(' + state.game.id + ')', true); return; }
|
||||
if (!name) { msg('先填游戏名称', false); return; }
|
||||
api(base() + '/game/api/game_create.dspy', {name: name,
|
||||
description: document.getElementById('game_shared').checked ? 'shared' : 'independent'})
|
||||
.then(function (r) {
|
||||
if (r.success) { msg('游戏创建成功,世界已自动建立', true); reload(); }
|
||||
if (r.success) { msg('游戏创建成功,世界已自动建立', true); reload(r.data.game_id); }
|
||||
else msg(r.message || '创建失败', false);
|
||||
});
|
||||
}
|
||||
|
||||
function reload() {
|
||||
// 列表取第一个 game 或新建后带入
|
||||
function switchGame(gid) {
|
||||
if (!gid) return;
|
||||
state.game = null; state.sel = null;
|
||||
reload(gid);
|
||||
}
|
||||
|
||||
function reload(forceGid) {
|
||||
api(base() + '/game/api/game_list.dspy', {}).then(function (r) {
|
||||
var games = (r.data && r.data.games) || [];
|
||||
if (!games.length) { state.game = null; renderTree(null); return; }
|
||||
var gid = games[0].game_id || games[0].id;
|
||||
var games = (r.data && r.data.games) || r || [];
|
||||
if (!Array.isArray(games)) games = [];
|
||||
state.games = games;
|
||||
var sel = document.getElementById('game_sel');
|
||||
sel.innerHTML = '<option value="">— 选择游戏 —</option>' +
|
||||
games.map(function (g) {
|
||||
var gid = g.game_id || g.id;
|
||||
return '<option value="' + gid + '">' + (g.game_name || g.name) + '</option>';
|
||||
}).join('');
|
||||
var gid = forceGid || (state.game && state.game.id) || (games[0] && (games[0].game_id || games[0].id));
|
||||
if (!gid) { state.game = null; renderTree(null); return; }
|
||||
sel.value = gid;
|
||||
api(base() + '/game/api/game_workbench.dspy', {game_id: gid}).then(function (w) {
|
||||
if (!w.success) { msg(w.message, false); return; }
|
||||
state.game = w.data.game; state.data = w.data;
|
||||
document.getElementById('game_name').value = (w.data.game && w.data.game.name) || '';
|
||||
renderTree(w.data);
|
||||
if (!state.sel) selectWorld();
|
||||
else loadSel();
|
||||
if (state.sel) loadSelKeep(); else selectWorld();
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -50,28 +73,68 @@
|
||||
var el = document.getElementById('tree_body');
|
||||
if (!d || !d.world) { el.innerHTML = '<div style="color:#9CA3AF">尚无游戏,先填名称保存</div>'; return; }
|
||||
var h = '';
|
||||
h += '<div class="node' + (state.sel && state.sel.type === 'world' ? ' sel' : '') + '" onclick="wb.selObj(\'world\',\'' + d.world.id + '\',\'' + esc(d.world.name) + '\')">🌍 ' + esc(d.world.name) + '</div>';
|
||||
h += node('world', d.world.id, '🌍 ' + esc(d.world.name));
|
||||
(d.scenes || []).forEach(function (s) {
|
||||
h += '<div class="node' + (state.sel && state.sel.id === s.id ? ' sel' : '') + '" style="margin-left:12px" onclick="wb.selObj(\'scene\',\'' + s.id + '\',\'' + esc(s.name) + '\')">🎬 ' + esc(s.name) + '</div>';
|
||||
h += node('scene', s.id, '🎬 ' + esc(s.name), 12);
|
||||
});
|
||||
h += '<div class="node" style="margin-left:12px;color:#2563EB" onclick="wb.addScene()">+ 场景</div>';
|
||||
h += addRow('scene', '+ 场景', 12);
|
||||
(d.entities || []).forEach(function (e) {
|
||||
h += '<div class="node' + (state.sel && state.sel.id === e.id ? ' sel' : '') + '" style="margin-left:24px" onclick="wb.selObj(\'entity\',\'' + e.id + '\',\'' + esc(e.name) + '\')">📦 ' + esc(e.name) + (e.scene_id ? '' : '(世界级)') + '</div>';
|
||||
h += node('entity', e.id, '📦 ' + esc(e.name) + (e.scene_id ? '' : '(世界级)'), 24);
|
||||
});
|
||||
h += '<div class="node" style="margin-left:24px;color:#2563EB" onclick="wb.addEntity()">+ 实体</div>';
|
||||
h += addRow('entity', '+ 实体', 24);
|
||||
el.innerHTML = h;
|
||||
}
|
||||
function esc(s) { return String(s || '').replace(/'/g, "\\'").replace(/</g, '<'); }
|
||||
function node(type, id, label, ml) {
|
||||
var sel = state.sel && state.sel.id === id ? ' sel' : '';
|
||||
return '<div class="node' + sel + '" style="margin-left:' + (ml || 0) + 'px" ' +
|
||||
'onclick="wb.selObj(\'' + type + '\',\'' + id + '\')">' + label + '</div>';
|
||||
}
|
||||
function addRow(type, label, ml) {
|
||||
if (state.inline === type) {
|
||||
return '<div class="node" style="margin-left:' + ml + 'px;flex-direction:column;align-items:stretch">' +
|
||||
'<input type="text" id="inline_name" placeholder="' + (type === 'scene' ? '场景名称' : '实体名称') + '" ' +
|
||||
'style="padding:4px 8px;border:1px solid #93C5FD;border-radius:4px;margin-bottom:4px" ' +
|
||||
'onkeydown="if(event.key===\'Enter\')wb.inlineOk(\'' + type + '\')">' +
|
||||
'<div style="display:flex;gap:6px"><button onclick="wb.inlineOk(\'' + type + '\')" style="flex:1;padding:4px;background:#2563EB;color:#fff;border:none;border-radius:4px;cursor:pointer">添加</button>' +
|
||||
'<button onclick="wb.inlineCancel()" style="flex:1;padding:4px;background:#E5E7EB;border:none;border-radius:4px;cursor:pointer">取消</button></div></div>';
|
||||
}
|
||||
return '<div class="node" style="margin-left:' + ml + 'px;color:#2563EB" onclick="wb.inlineStart(\'' + type + '\')">' + label + '</div>';
|
||||
}
|
||||
function esc(s) { return String(s || '').replace(/</g, '<'); }
|
||||
|
||||
function selectWorld() { if (state.data && state.data.world) selObj('world', state.data.world.id, state.data.world.name); }
|
||||
function inlineStart(type) { state.inline = type; renderTree(state.data); setTimeout(function () { var i = document.getElementById('inline_name'); if (i) i.focus(); }, 50); }
|
||||
function inlineCancel() { state.inline = null; renderTree(state.data); }
|
||||
function inlineOk(type) {
|
||||
var name = (document.getElementById('inline_name') || {}).value;
|
||||
if (!name || !name.trim()) { msg('名称不能为空', false); return; }
|
||||
state.inline = null;
|
||||
if (type === 'scene') {
|
||||
api(base() + '/game/api/scene_add.dspy', {game_id: state.game.id, name: name.trim()})
|
||||
.then(function (r) { r.success ? (msg('场景已添加', true), reload()) : msg(r.message, false); });
|
||||
} else {
|
||||
var sceneId = state.sel && state.sel.type === 'scene' ? state.sel.id : '';
|
||||
api(base() + '/game/api/entity_add.dspy', {game_id: state.game.id, scene_id: sceneId, name: name.trim()})
|
||||
.then(function (r) { r.success ? (msg('实体已添加' + (sceneId ? '' : '(世界级)'), true), reload()) : msg(r.message, false); });
|
||||
}
|
||||
}
|
||||
|
||||
function selObj(type, id, name) {
|
||||
state.sel = {type: type, id: id, name: name};
|
||||
function selectWorld() {
|
||||
if (state.data && state.data.world) selObj('world', state.data.world.id);
|
||||
}
|
||||
|
||||
function selObj(type, id) {
|
||||
var obj = null;
|
||||
if (type === 'world') obj = state.data.world;
|
||||
if (type === 'scene') obj = (state.data.scenes || []).find(function (x) { return x.id === id; });
|
||||
if (type === 'entity') obj = (state.data.entities || []).find(function (x) { return x.id === id; });
|
||||
if (!obj) return;
|
||||
state.sel = {type: type, id: id, name: obj.name};
|
||||
document.getElementById('cur_obj').textContent =
|
||||
({world: '🌍 世界', scene: '🎬 场景', entity: '📦 实体'})[type] + ' · ' + name;
|
||||
({world: '🌍 世界', scene: '🎬 场景', entity: '📦 实体'})[type] + ' · ' + obj.name;
|
||||
renderTree(state.data);
|
||||
loadScript();
|
||||
}
|
||||
function loadSelKeep() { if (state.sel) selObj(state.sel.type, state.sel.id); }
|
||||
|
||||
function bindParams() {
|
||||
var s = state.sel;
|
||||
@ -93,13 +156,14 @@
|
||||
});
|
||||
document.getElementById('code').value = sc ? (sc.content || '') : '';
|
||||
state.cur_script = sc || null;
|
||||
msg(sc ? '已加载现有脚本: ' + sc.name : '新脚本(保存后绑定到该对象+事件)', true);
|
||||
}
|
||||
|
||||
function saveScript() {
|
||||
var p = bindParams();
|
||||
if (!p) { msg('先选择对象', false); return; }
|
||||
if (!p) { msg('先在左侧选择对象', false); return; }
|
||||
p.name = state.sel.name + '_' + p.trigger_event;
|
||||
p.code = 'SC_' + (state.cur_script ? state.cur_script.code : (state.sel.id.slice(0, 8) + '_' + p.trigger_event));
|
||||
p.code = 'SC_' + state.sel.id.slice(0, 8) + '_' + p.trigger_event;
|
||||
p.content = document.getElementById('code').value;
|
||||
p.script_type = '0';
|
||||
if (state.cur_script) p.id = state.cur_script.id;
|
||||
@ -116,28 +180,16 @@
|
||||
.then(function (r) { msg(JSON.stringify(r).slice(0, 200), true); });
|
||||
}
|
||||
|
||||
function addScene() {
|
||||
var name = prompt('场景名称');
|
||||
if (!name) return;
|
||||
api(base() + '/game/api/scene_add.dspy', {game_id: state.game.id, name: name})
|
||||
.then(function (r) { r.success ? (msg('场景已加', true), reload()) : msg(r.message, false); });
|
||||
}
|
||||
|
||||
function addEntity() {
|
||||
var name = prompt('实体名称(留空场景ID=世界级实体)');
|
||||
if (!name) return;
|
||||
var sceneId = state.sel && state.sel.type === 'scene' ? state.sel.id : '';
|
||||
api(base() + '/game/api/entity_add.dspy', {game_id: state.game.id, scene_id: sceneId, name: name})
|
||||
.then(function (r) { r.success ? (msg('实体已加', true), reload()) : msg(r.message, false); });
|
||||
}
|
||||
|
||||
function test() {
|
||||
if (!state.game) { msg('先保存游戏', false); return; }
|
||||
var wid = state.data && state.data.world ? state.data.world.id : '';
|
||||
window.open('/scense_demo/demo_viewer.ui?world_id=' + wid + '&game_id=' + state.game.id, '_blank');
|
||||
}
|
||||
|
||||
window.wb = {saveGame: saveGame, reload: reload, selObj: selObj, addScene: addScene,
|
||||
addEntity: addEntity, saveScript: saveScript, validate: validate, test: test};
|
||||
window.wb = {newGame: newGame, saveGame: saveGame, switchGame: switchGame, reload: function () { reload(); },
|
||||
selObj: selObj, inlineStart: inlineStart, inlineCancel: inlineCancel, inlineOk: inlineOk,
|
||||
saveScript: saveScript, validate: validate, test: test};
|
||||
|
||||
document.getElementById('game_sel').onchange = function () { switchGame(this.value); };
|
||||
reload();
|
||||
})();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user