Feature/runtime cdn and plugin loader (#240)

* feat(ui): 完善 UI 布局系统和编辑器可视化工具

* refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统

* fix: 修复 CodeQL 警告并提升测试覆盖率

* refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题

* fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤

* docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明

* fix(ci): 修复 type-check 失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖

* fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖

* fix(ci): platform-web 添加缺失的 behavior-tree 依赖

* fix(lint): 移除正则表达式中不必要的转义字符
This commit is contained in:
YHH
2025-11-27 20:42:46 +08:00
committed by GitHub
parent 71869b1a58
commit 107439d70c
367 changed files with 10661 additions and 12473 deletions

View File

@@ -220,12 +220,33 @@ impl Engine {
self.renderer.render(self.context.gl(), &self.texture_manager)?;
// Render gizmos on top
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
if self.show_gizmos {
self.gizmo_renderer.render(self.context.gl(), self.renderer.camera());
// Render axis indicator in corner (always visible when gizmos are on)
// 在角落渲染坐标轴指示器(当 gizmos 开启时始终可见)
self.gizmo_renderer.render_axis_indicator(
self.context.gl(),
self.context.width() as f32,
self.context.height() as f32,
);
}
self.gizmo_renderer.clear();
Ok(())
}
/// Render sprites only without clearing the screen.
/// 仅渲染精灵,不清除屏幕。
///
/// This is used for overlay rendering (e.g., UI layer on top of world).
/// 用于叠加渲染例如UI 层叠加在世界上)。
pub fn render_overlay(&mut self) -> Result<()> {
// Render sprites without clearing
// 渲染精灵但不清屏
self.renderer.render(self.context.gl(), &self.texture_manager)?;
Ok(())
}
/// Add a rectangle gizmo.
/// 添加矩形Gizmo。
pub fn add_gizmo_rect(
@@ -447,6 +468,14 @@ impl Engine {
// Render gizmos if enabled
if show_gizmos {
self.gizmo_renderer.render(viewport.gl(), &camera);
// Render axis indicator in corner
// 在角落渲染坐标轴指示器
let (vp_width, vp_height) = viewport.dimensions();
self.gizmo_renderer.render_axis_indicator(
viewport.gl(),
vp_width as f32,
vp_height as f32,
);
}
self.gizmo_renderer.clear();

View File

@@ -163,6 +163,18 @@ impl GameEngine {
.map_err(|e| JsValue::from_str(&e.to_string()))
}
/// Render sprites as overlay (without clearing screen).
/// 渲染精灵作为叠加层(不清除屏幕)。
///
/// This is used for UI rendering on top of the world content.
/// 用于在世界内容上渲染 UI。
#[wasm_bindgen(js_name = renderOverlay)]
pub fn render_overlay(&mut self) -> std::result::Result<(), JsValue> {
self.engine
.render_overlay()
.map_err(|e| JsValue::from_str(&e.to_string()))
}
/// Load a texture from URL.
/// 从URL加载纹理。
///

View File

@@ -164,6 +164,221 @@ impl GizmoRenderer {
]);
}
/// Render axis indicator at top-right corner of the viewport.
/// 在视口右上角渲染坐标轴指示器。
///
/// This is drawn in screen space and is not affected by camera pan/zoom.
/// 这是在屏幕空间绘制的,不受相机平移/缩放影响。
pub fn render_axis_indicator(
&self,
gl: &WebGl2RenderingContext,
viewport_width: f32,
viewport_height: f32,
) {
// Skip if viewport is too small
if viewport_width < 100.0 || viewport_height < 100.0 {
return;
}
gl.use_program(Some(&self.program));
// Disable depth test for screen-space UI
gl.disable(WebGl2RenderingContext::DEPTH_TEST);
// Create orthographic projection for screen space (NDC: -1 to 1)
let half_w = viewport_width / 2.0;
let half_h = viewport_height / 2.0;
let projection = [
1.0 / half_w, 0.0, 0.0,
0.0, 1.0 / half_h, 0.0,
0.0, 0.0, 1.0,
];
let proj_loc = gl.get_uniform_location(&self.program, "u_projection");
gl.uniform_matrix3fv_with_f32_array(proj_loc.as_ref(), false, &projection);
let color_loc = gl.get_uniform_location(&self.program, "u_color");
gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&self.vertex_buffer));
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_with_i32(0, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);
// Position in top-right corner (increased padding to prevent X label clipping)
// 位置在右上角(增加边距防止 X 标签被裁剪)
let padding_x = 70.0; // More padding on X for the label
let padding_y = 55.0;
let center_x = half_w - padding_x;
let center_y = half_h - padding_y;
let axis_length = 30.0; // Longer axes for better visibility
let arrow_size = 8.0;
let label_offset = 10.0;
let label_size = 4.0;
// Draw semi-transparent background circle for better visibility
// 绘制半透明背景圆以提高可见性
let bg_segments = 32;
let bg_radius = 45.0;
let mut bg_vertices = Vec::with_capacity((bg_segments + 1) * 2);
bg_vertices.push(center_x);
bg_vertices.push(center_y);
for i in 0..=bg_segments {
let angle = (i as f32 / bg_segments as f32) * std::f32::consts::PI * 2.0;
bg_vertices.push(center_x + bg_radius * angle.cos());
bg_vertices.push(center_y + bg_radius * angle.sin());
}
unsafe {
let array = js_sys::Float32Array::view(&bg_vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.1, 0.1, 0.1, 0.7);
gl.draw_arrays(WebGl2RenderingContext::TRIANGLE_FAN, 0, (bg_segments + 2) as i32);
// Draw origin point (filled circle)
// 绘制原点(实心圆)
let origin_segments = 12;
let origin_radius = 3.0;
let mut origin_vertices = Vec::with_capacity((origin_segments + 1) * 2);
origin_vertices.push(center_x);
origin_vertices.push(center_y);
for i in 0..=origin_segments {
let angle = (i as f32 / origin_segments as f32) * std::f32::consts::PI * 2.0;
origin_vertices.push(center_x + origin_radius * angle.cos());
origin_vertices.push(center_y + origin_radius * angle.sin());
}
unsafe {
let array = js_sys::Float32Array::view(&origin_vertices);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.8, 0.8, 0.8, 1.0);
gl.draw_arrays(WebGl2RenderingContext::TRIANGLE_FAN, 0, (origin_segments + 2) as i32);
// X axis (red, pointing right)
let x_end_x = center_x + axis_length;
let x_end_y = center_y;
// X axis line (thicker effect with multiple lines)
let x_axis = [
center_x + origin_radius, center_y,
x_end_x - arrow_size * 0.3, x_end_y,
];
unsafe {
let array = js_sys::Float32Array::view(&x_axis);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 1.0, 0.4, 0.4, 1.0);
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2);
// X arrow head (filled triangle)
let x_arrow = [
x_end_x, x_end_y,
x_end_x - arrow_size, x_end_y + arrow_size * 0.4,
x_end_x - arrow_size, x_end_y - arrow_size * 0.4,
];
unsafe {
let array = js_sys::Float32Array::view(&x_arrow);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3);
// X label
let lx = x_end_x + label_offset;
let ly = x_end_y;
let x_label = [
lx - label_size, ly + label_size,
lx + label_size, ly - label_size,
lx - label_size, ly - label_size,
lx + label_size, ly + label_size,
];
unsafe {
let array = js_sys::Float32Array::view(&x_label);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 4);
// Y axis (green, pointing up)
let y_end_x = center_x;
let y_end_y = center_y + axis_length;
// Y axis line
let y_axis = [
center_x, center_y + origin_radius,
y_end_x, y_end_y - arrow_size * 0.3,
];
unsafe {
let array = js_sys::Float32Array::view(&y_axis);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.uniform4f(color_loc.as_ref(), 0.4, 1.0, 0.4, 1.0);
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 2);
// Y arrow head (filled triangle)
let y_arrow = [
y_end_x, y_end_y,
y_end_x - arrow_size * 0.4, y_end_y - arrow_size,
y_end_x + arrow_size * 0.4, y_end_y - arrow_size,
];
unsafe {
let array = js_sys::Float32Array::view(&y_arrow);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 3);
// Y label
let lx = y_end_x;
let ly = y_end_y + label_offset;
let y_label = [
lx - label_size, ly + label_size,
lx, ly,
lx + label_size, ly + label_size,
lx, ly,
lx, ly,
lx, ly - label_size * 0.8,
];
unsafe {
let array = js_sys::Float32Array::view(&y_label);
gl.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&array,
WebGl2RenderingContext::DYNAMIC_DRAW,
);
}
gl.draw_arrays(WebGl2RenderingContext::LINES, 0, 6);
// Cleanup
gl.disable_vertex_attrib_array(0);
}
/// Render all pending gizmos.
/// 渲染所有待渲染的Gizmo。
pub fn render(&mut self, gl: &WebGl2RenderingContext, camera: &Camera2D) {