EG/templates/main_template.py

830 lines
32 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""EG packaged project runtime template."""
from __future__ import annotations
import importlib
import json
import os
import sys
import traceback
from direct.showbase.ShowBase import ShowBase
from direct.showbase.ShowBaseGlobal import globalClock
from panda3d.core import (
CardMaker,
Filename,
Material,
MaterialAttrib,
MovieTexture,
Vec4,
TextNode,
TransparencyAttrib,
Vec3,
load_prc_file_data,
)
PROJECT_NAME = "__EG_PROJECT_NAME__"
def _bootstrap_paths():
if getattr(sys, "frozen", False):
project_root = getattr(sys, "_MEIPASS", "") or os.path.join(os.path.dirname(sys.executable), "_internal")
if not os.path.isdir(project_root):
project_root = os.path.dirname(sys.executable)
else:
source_root = os.path.dirname(os.path.abspath(__file__))
assets_root = os.path.normpath(os.path.join(source_root, "..", "assets"))
project_root = assets_root if os.path.isdir(assets_root) else source_root
os.chdir(project_root)
search_paths = [
project_root,
os.path.join(project_root, "third_party"),
os.path.join(project_root, "RenderPipelineFile"),
]
for path in search_paths:
if os.path.isdir(path) and path not in sys.path:
sys.path.insert(0, path)
return project_root
PROJECT_ROOT = _bootstrap_paths()
class MainApp(ShowBase):
def __init__(self):
self.project_path = PROJECT_ROOT
self.gui_elements = []
self.chinese_font = None
self.script_manager = None
self.render_pipeline = None
self._movie_textures = []
self.camera_control_enabled = True
self.mouse_controller = None
self.cameraSpeed = 20.0
self.cameraRotateSpeed = 10.0
self.lastMouseX = 0
self.lastMouseY = 0
self.mouseRightPressed = False
self.use_ssbo_mouse_picking = True
self.use_ssbo_scene_import = True
self.ssbo_runtime_importer = None
self._ssbo_visible_scene = None
load_prc_file_data(
"",
f"""
win-size 1380 750
window-title {PROJECT_NAME}
load-display pandagl
aux-display p3tinydisplay
sync-video false
show-frame-rate-meter false
support-threads false
""",
)
from rpcore import RenderPipeline
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
ShowBase.__init__(self)
self.render_pipeline.create(self)
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
self.disableMouse()
self._load_font()
self._init_script_manager()
self._init_ssbo_runtime_importer()
self._load_camera_state()
self._init_camera_controller()
self.load_full_scene()
self.load_gui_from_json()
def _init_script_manager(self):
script_module = importlib.import_module("core.script_system")
self.script_manager = script_module.ScriptManager(self)
self.script_manager.hot_reload_enabled = False
scripts_dir = self.get_resource_path("scripts")
if hasattr(self.script_manager, "set_scripts_directory"):
self.script_manager.set_scripts_directory(
scripts_dir,
create=False,
reload_scripts=False,
)
self.script_manager.start_system()
self.script_manager.set_hot_reload_enabled(False)
def _load_font(self):
font_candidates = [
"C:/Windows/Fonts/msyh.ttc",
"C:/Windows/Fonts/simhei.ttf",
]
for font_path in font_candidates:
if os.path.exists(font_path):
try:
self.chinese_font = self.loader.loadFont(font_path)
if self.chinese_font:
print(f"✓ 中文字体加载成功: {font_path}")
return
except Exception:
continue
print("⚠ 未找到可用中文字体,继续使用默认字体")
def _init_ssbo_runtime_importer(self):
if not (self.use_ssbo_mouse_picking and self.use_ssbo_scene_import):
return
try:
importer_module = importlib.import_module("ssbo_component.runtime_importer")
importer_cls = getattr(importer_module, "RuntimeSSBOSceneImporter", None)
if importer_cls:
self.ssbo_runtime_importer = importer_cls(self)
print("✓ 运行时 SSBO 场景导入器已启用")
except Exception as e:
print(f"⚠ 初始化运行时 SSBO 导入器失败: {e}")
def get_chinese_font(self):
return self.chinese_font
def get_resource_path(self, relative_path):
return os.path.normpath(os.path.join(PROJECT_ROOT, relative_path))
def _load_json_file(self, file_path, default_value):
if not os.path.exists(file_path):
return default_value
try:
with open(file_path, "r", encoding="utf-8-sig") as f:
return json.load(f)
except Exception as e:
print(f"⚠ 读取 JSON 失败 {file_path}: {e}")
return default_value
def _load_camera_state(self):
camera_state_path = self.get_resource_path("camera_state.json")
camera_state = self._load_json_file(camera_state_path, {})
if not isinstance(camera_state, dict):
camera_state = {}
position = camera_state.get("position") or [0, -20, 5]
rotation = camera_state.get("rotation") or [0, 0, 0]
self.camera_control_enabled = bool(camera_state.get("camera_control_enabled", True))
try:
if len(position) >= 3:
self.camera.setPos(position[0], position[1], position[2])
if len(rotation) >= 3:
self.camera.setHpr(rotation[0], rotation[1], rotation[2])
self.cam = self.camera
except Exception as e:
print(f"⚠ 恢复主相机状态失败: {e}")
def _init_camera_controller(self):
if not self.camera_control_enabled:
return
try:
controller_module = importlib.import_module("core.CustomMouseController")
controller_cls = getattr(controller_module, "CustomMouseController", None)
if not controller_cls:
return
self.mouse_controller = controller_cls(self)
self.mouse_controller.setUp()
self.accept("wheel_up", self.wheelForward)
self.accept("wheel_down", self.wheelBackward)
except Exception as e:
print(f"⚠ 初始化主相机控制失败: {e}")
def wheelForward(self, _data=None):
if not self.camera_control_enabled:
return
try:
forward = self.cam.getMat().getRow3(1)
distance = self.cameraSpeed * globalClock.getDt()
current_pos = self.cam.getPos()
self.cam.setPos(current_pos + forward * distance)
except Exception as e:
print(f"滚轮前进失败: {e}")
def wheelBackward(self, _data=None):
if not self.camera_control_enabled:
return
try:
forward = self.cam.getMat().getRow3(1)
distance = self.cameraSpeed * globalClock.getDt()
current_pos = self.cam.getPos()
self.cam.setPos(current_pos - forward * distance)
except Exception as e:
print(f"滚轮后退失败: {e}")
def _resolve_media_path(self, relative_path):
if not relative_path:
return ""
if str(relative_path).startswith(("http://", "https://")):
return relative_path
if os.path.isabs(relative_path):
return relative_path
return self.get_resource_path(relative_path.replace("/", os.sep))
def load_full_scene(self):
scene_file = self.get_resource_path("scene.bam")
if not os.path.exists(scene_file):
print(f"⚠ 未找到场景文件: {scene_file}")
return
metadata_scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
if not metadata_scene:
print("⚠ 场景文件加载失败")
return
visible_scene = metadata_scene
use_ssbo_scene = bool(self.ssbo_runtime_importer and self.use_ssbo_scene_import)
interactive_root_names = self._collect_runtime_interactive_model_names(metadata_scene)
if use_ssbo_scene:
try:
visible_scene = self.ssbo_runtime_importer.load_scene(
scene_file,
interactive_root_names=interactive_root_names,
)
self._ssbo_visible_scene = visible_scene
except Exception as e:
print(f"⚠ 运行时 SSBO 场景导入失败,回退普通加载: {e}")
traceback.print_exc()
visible_scene = metadata_scene
use_ssbo_scene = False
if not use_ssbo_scene:
visible_scene.reparentTo(self.render)
self._prepare_scene_for_render_pipeline(visible_scene)
self.process_scene_elements(metadata_scene, skip_model_nodes=use_ssbo_scene)
print("✓ 场景加载完成")
def _collect_runtime_interactive_model_names(self, metadata_scene):
interactive_names = set()
try:
for node in metadata_scene.find_all_matches("**"):
if not node.hasTag("is_model_root"):
continue
node_name = node.getName()
if not node_name:
continue
has_runtime_tag = (
node.hasTag("runtime_interactive")
and node.getTag("runtime_interactive").lower() == "true"
)
has_scripts = (
node.hasTag("has_scripts")
and node.getTag("has_scripts").lower() == "true"
) or node.hasTag("scripts_info")
if has_runtime_tag or has_scripts:
interactive_names.add(node_name)
except Exception as e:
print(f"收集运行时交互模型失败: {e}")
if interactive_names:
print(f"✓ 运行时交互模型数量: {len(interactive_names)}")
return interactive_names
def _prepare_scene_for_render_pipeline(self, scene):
self._bake_effective_geom_materials(scene)
self._ensure_geom_materials(scene)
try:
self.render_pipeline.prepare_scene(scene)
except Exception as e:
print(f"⚠ RenderPipeline 场景预处理失败,继续使用原始场景: {e}")
traceback.print_exc()
def _build_default_material(self):
material = Material()
material.setAmbient(Vec4(0.2, 0.2, 0.2, 1.0))
material.setDiffuse(Vec4(0.8, 0.8, 0.8, 1.0))
material.setSpecular(Vec4(0.0, 0.0, 0.0, 1.0))
material.setEmission(Vec4(0.0, 0.0, 0.0, 1.0))
material.setShininess(0.0)
return material
def _ensure_geom_materials(self, scene):
for geom_np in scene.find_all_matches("**/+GeomNode"):
try:
geom_node = geom_np.node()
except Exception:
continue
fallback_material = None
try:
if geom_np.hasMaterial():
fallback_material = geom_np.getMaterial()
except Exception:
fallback_material = None
for geom_index in range(geom_node.getNumGeoms()):
try:
geom_state = geom_node.getGeomState(geom_index)
except Exception:
continue
material = None
try:
if geom_state.hasAttrib(MaterialAttrib):
material_attrib = geom_state.getAttrib(MaterialAttrib)
material = material_attrib.getMaterial() if material_attrib else None
except Exception:
material = None
if material is None:
material = fallback_material or self._build_default_material()
try:
geom_node.setGeomState(
geom_index,
geom_state.setAttrib(MaterialAttrib.make(material)),
)
except Exception:
continue
def _bake_effective_geom_materials(self, scene):
for geom_np in scene.find_all_matches("**/+GeomNode"):
try:
geom_node = geom_np.node()
net_state = geom_np.getNetState()
except Exception:
continue
for geom_index in range(geom_node.getNumGeoms()):
try:
geom_state = geom_node.getGeomState(geom_index)
except Exception:
continue
material = None
try:
if geom_state.hasAttrib(MaterialAttrib):
material_attrib = geom_state.getAttrib(MaterialAttrib)
material = material_attrib.getMaterial() if material_attrib else None
except Exception:
material = None
if material is None:
try:
if net_state.hasAttrib(MaterialAttrib):
material_attrib = net_state.getAttrib(MaterialAttrib)
material = material_attrib.getMaterial() if material_attrib else None
except Exception:
material = None
if material is None:
try:
if geom_np.hasMaterial():
material = geom_np.getMaterial()
except Exception:
material = None
if material is None:
continue
try:
geom_node.setGeomState(
geom_index,
geom_state.setAttrib(MaterialAttrib.make(material)),
)
except Exception:
continue
def process_scene_elements(self, root_node, skip_model_nodes=False):
processed_lights = set()
def walk(node_path):
self._apply_user_visibility(node_path)
is_model_root = node_path.hasTag("is_model_root")
if skip_model_nodes and is_model_root:
runtime_target = None
if self.ssbo_runtime_importer:
runtime_target = self.ssbo_runtime_importer.get_runtime_node_for_name(node_path.getName())
if runtime_target is None:
runtime_target = self._ssbo_visible_scene
if node_path.hasTag("scripts_info") and runtime_target:
try:
scripts_info = json.loads(node_path.getTag("scripts_info"))
self.process_scripts(runtime_target, scripts_info)
except Exception as e:
print(f"处理 SSBO 模型根脚本失败 {node_path.getName()}: {e}")
return
if node_path.hasTag("scripts_info"):
try:
scripts_info = json.loads(node_path.getTag("scripts_info"))
self.process_scripts(node_path, scripts_info)
except Exception as e:
print(f"处理节点脚本失败 {node_path.getName()}: {e}")
if node_path.hasTag("light_type") and node_path not in processed_lights:
if node_path.hasTag("is_auxiliary_light") and node_path.getTag("is_auxiliary_light").lower() == "true":
return
light_type = node_path.getTag("light_type")
if light_type == "spot_light":
self._recreate_spot_light(node_path)
elif light_type == "point_light":
self._recreate_point_light(node_path)
processed_lights.add(node_path)
for child in node_path.getChildren():
walk(child)
walk(root_node)
def _apply_user_visibility(self, node_path):
if not node_path.hasTag("user_visible"):
return
user_visible = node_path.getTag("user_visible").lower() == "true"
node_path.setPythonTag("user_visible", user_visible)
if user_visible:
node_path.show()
else:
node_path.hide()
def _recreate_spot_light(self, light_node):
try:
from rpcore import SpotLight
light = SpotLight()
light.direction = Vec3(0, 0, -1)
light.fov = float(light_node.getTag("light_fov")) if light_node.hasTag("light_fov") else 70.0
light.energy = float(light_node.getTag("light_energy")) if light_node.hasTag("light_energy") else 5000.0
light.radius = float(light_node.getTag("light_radius")) if light_node.hasTag("light_radius") else 1000.0
light.casts_shadows = True
light.shadow_map_resolution = 256
light.setPos(light_node.getPos())
self.render_pipeline.add_light(light)
except Exception as e:
print(f"创建聚光灯失败 {light_node.getName()}: {e}")
def _recreate_point_light(self, light_node):
try:
from rpcore import PointLight
light = PointLight()
light.energy = float(light_node.getTag("light_energy")) if light_node.hasTag("light_energy") else 5000.0
light.radius = float(light_node.getTag("light_radius")) if light_node.hasTag("light_radius") else 1000.0
light.inner_radius = 0.4
light.casts_shadows = True
light.shadow_map_resolution = 256
light.setPos(light_node.getPos())
self.render_pipeline.add_light(light)
except Exception as e:
print(f"创建点光源失败 {light_node.getName()}: {e}")
def process_scripts(self, node_path, script_info_list):
if not self.script_manager:
return
for script_info in script_info_list or []:
script_name = str(script_info.get("name", "") or "").strip()
if not script_name:
continue
try:
if script_name not in self.script_manager.loader.script_classes:
script_path = ""
if hasattr(self.script_manager, "resolve_script_path"):
script_path = self.script_manager.resolve_script_path(script_info)
if script_path:
self.script_manager.load_script_from_file(script_path)
script_component = self.script_manager.add_script_to_object(node_path, script_name)
if script_component:
print(f"✓ 脚本 {script_name} 已挂载到 {node_path.getName()}")
else:
print(f"⚠ 脚本 {script_name} 挂载失败")
except Exception as e:
print(f"挂载脚本失败 {script_name}: {e}")
def load_gui_from_json(self):
gui_json_path = self.get_resource_path(os.path.join("gui", "gui_elements.json"))
if not os.path.exists(gui_json_path):
return
gui_data = self._load_json_file(gui_json_path, [])
if not gui_data:
return
self.create_gui_elements(gui_data)
def create_gui_elements(self, element_data):
processed_names = set()
created_elements = {}
pending_parent_links = []
for index, gui_info in enumerate(element_data or []):
try:
gui_type = gui_info.get("type", "unknown")
name = gui_info.get("name", f"gui_element_{index}")
if name in processed_names:
continue
processed_names.add(name)
position = list(gui_info.get("position", [0, 0, 0]))
scale = list(gui_info.get("scale", [1, 1, 1]))
parent_name = gui_info.get("parent_name")
text = gui_info.get("text", "")
raw_image_path = str(gui_info.get("image_path", "") or "")
raw_video_path = str(gui_info.get("video_path", "") or "")
image_path = self._resolve_media_path(raw_image_path)
video_path = self._resolve_media_path(raw_video_path)
new_element = None
if gui_type == "3d_text":
new_element = self.create_gui_3d_text(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "3d_image":
new_element = self.create_gui_3d_image(tuple(position), image_path, scale)
elif gui_type == "button":
new_element = self.create_gui_button(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "label":
new_element = self.create_gui_label(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "entry":
new_element = self.create_gui_entry(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "2d_image":
new_element = self.create_gui_2d_image(tuple(position), image_path, scale)
elif gui_type == "video_screen":
new_element = self.create_gui_video_screen(tuple(position), scale, video_path)
elif gui_type == "2d_video_screen":
new_element = self.create_gui_2d_video_screen(tuple(position), scale, video_path)
if not new_element:
continue
self._apply_gui_metadata(new_element, gui_info, gui_type, text, raw_image_path, raw_video_path)
self.gui_elements.append(new_element)
created_elements[name] = new_element
if parent_name:
pending_parent_links.append((new_element, parent_name))
if gui_info.get("scripts"):
self.process_scripts(new_element, gui_info["scripts"])
except Exception as e:
print(f"重建 GUI 元素失败: {e}")
traceback.print_exc()
for child, parent_name in pending_parent_links:
parent = created_elements.get(parent_name)
if not parent:
continue
try:
child.reparentTo(parent)
except Exception as e:
print(f"恢复 GUI 父子关系失败 {parent_name}: {e}")
def _apply_gui_metadata(self, node, gui_info, gui_type, text, image_path, video_path):
try:
name = gui_info.get("name")
if name and hasattr(node, "setName"):
node.setName(name)
except Exception:
pass
try:
position = gui_info.get("position", [])
if len(position) >= 3 and hasattr(node, "setPos"):
node.setPos(position[0], position[1], position[2])
except Exception:
pass
try:
rotation = gui_info.get("rotation", [])
if len(rotation) >= 3 and hasattr(node, "setHpr"):
node.setHpr(rotation[0], rotation[1], rotation[2])
except Exception:
pass
try:
scale = gui_info.get("scale", [])
if hasattr(node, "setScale"):
if isinstance(scale, (list, tuple)):
if len(scale) >= 3:
node.setScale(scale[0], scale[1], scale[2])
elif len(scale) == 1:
node.setScale(scale[0])
elif scale:
node.setScale(scale)
except Exception:
pass
if hasattr(node, "setTag"):
node.setTag("gui_type", gui_type)
node.setTag("saved_gui_type", gui_type)
node.setTag("is_gui_element", "true")
if text:
node.setTag("gui_text", str(text))
if image_path:
node.setTag("gui_image_path", str(image_path))
node.setTag("image_path", str(image_path))
if video_path:
node.setTag("video_path", str(video_path))
for key, value in (gui_info.get("tags") or {}).items():
try:
node.setTag(str(key), str(value))
except Exception:
continue
user_visible = bool(gui_info.get("user_visible", True))
if hasattr(node, "setPythonTag"):
node.setPythonTag("user_visible", user_visible)
if hasattr(node, "show") and hasattr(node, "hide"):
if user_visible:
node.show()
else:
node.hide()
def create_gui_button(self, pos=(0, 0, 0), text="按钮", size=0.1):
from direct.gui.DirectGui import DirectButton
return DirectButton(
text=text,
pos=(pos[0], pos[1], pos[2]),
scale=size,
frameColor=(0.2, 0.6, 0.8, 1),
text_font=self.get_chinese_font() or None,
rolloverSound=None,
clickSound=None,
parent=self.aspect2d,
command=None,
)
def create_gui_label(self, pos=(0, 0, 0), text="标签", size=0.08):
from direct.gui.DirectGui import DirectLabel
return DirectLabel(
text=text,
pos=(pos[0], pos[1], pos[2]),
scale=size,
frameColor=(0, 0, 0, 0),
text_fg=(1, 1, 1, 1),
text_font=self.get_chinese_font() or None,
text_align=TextNode.ACenter,
text_mayChange=True,
parent=self.aspect2d,
)
def create_gui_entry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
from direct.gui.DirectGui import DirectEntry
return DirectEntry(
text="",
pos=(pos[0], pos[1], pos[2]),
scale=size,
command=self.on_gui_entry_submit,
initialText=placeholder,
numLines=1,
width=12,
focus=0,
frameColor=(0, 0, 0, 0),
text_fg=(1, 1, 1, 1),
text_font=self.get_chinese_font() or None,
text_align=TextNode.ACenter,
text_mayChange=True,
parent=self.aspect2d,
rolloverSound=None,
clickSound=None,
suppressKeys=True,
suppressMouse=True,
)
def on_gui_entry_submit(self, text, *_args):
print(f"GUI 输入框提交: {text}")
def create_gui_2d_image(self, pos=(0, 0, 0), image_path="", size=(1, 1, 1)):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0]) * 0.2
height_scale = float(size[2]) * 0.2
else:
scalar = float(size) if isinstance(size, (int, float)) else 0.2
width_scale = scalar * 0.1
height_scale = width_scale
cm = CardMaker("gui_2d_image")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
image_node = self.aspect2d.attachNewNode(cm.generate())
image_node.setPos(pos)
image_node.setBin("fixed", 0)
image_node.setDepthWrite(False)
image_node.setDepthTest(False)
image_node.setTransparency(TransparencyAttrib.MAlpha)
if image_path:
texture = self.loader.loadTexture(image_path)
if texture:
image_node.setTexture(texture, 1)
return image_node
def create_gui_3d_text(self, pos=(0, 0, 0), text="3D文本", size=0.5):
text_node = TextNode("gui_3d_text")
text_node.setText(text)
text_node.setAlign(TextNode.ACenter)
if self.get_chinese_font():
text_node.setFont(self.get_chinese_font())
text_np = self.render.attachNewNode(text_node)
text_np.setPos(Vec3(pos[0], pos[1], pos[2]))
text_np.setScale(size)
text_np.setBin("fixed", 40)
text_np.setDepthWrite(False)
return text_np
def create_gui_3d_image(self, pos=(0, 0, 0), image_path="", size=(1, 1, 1)):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0])
height_scale = float(size[2])
else:
width_scale = float(size) if isinstance(size, (int, float)) else 1.0
height_scale = width_scale
cm = CardMaker("gui_3d_image")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
image_node = self.render.attachNewNode(cm.generate())
image_node.setPos(pos)
image_node.setTransparency(TransparencyAttrib.MAlpha)
if image_path:
texture = self.loader.loadTexture(image_path)
if texture:
image_node.setTexture(texture, 1)
return image_node
def _load_movie_texture(self, name, video_path):
if not video_path:
return None
if str(video_path).startswith(("http://", "https://")):
print(f"⚠ 当前运行时仅支持本地视频文件: {video_path}")
return None
movie_texture = MovieTexture(name)
if not movie_texture.read(Filename.fromOsSpecific(video_path)):
print(f"⚠ 无法加载视频: {video_path}")
return None
movie_texture.play()
return movie_texture
def create_gui_video_screen(self, pos=(0, 0, 0), size=(1, 1, 1), video_path=""):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0])
height_scale = float(size[2])
else:
width_scale = float(size) if isinstance(size, (int, float)) else 1.0
height_scale = width_scale
cm = CardMaker("gui_video_screen")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
video_node = self.render.attachNewNode(cm.generate())
video_node.setPos(pos)
video_node.setTransparency(TransparencyAttrib.MAlpha)
movie_texture = self._load_movie_texture("gui_video_texture_3d", video_path)
if movie_texture:
video_node.setTexture(movie_texture, 1)
video_node.setPythonTag("movie_texture", movie_texture)
self._movie_textures.append(movie_texture)
return video_node
def create_gui_2d_video_screen(self, pos=(0, 0, 0), size=(1, 1, 1), video_path=""):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0]) * 0.2
height_scale = float(size[2]) * 0.2
else:
scalar = float(size) if isinstance(size, (int, float)) else 0.2
width_scale = scalar * 0.1
height_scale = width_scale
cm = CardMaker("gui_2d_video_screen")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
video_node = self.aspect2d.attachNewNode(cm.generate())
video_node.setPos(pos)
video_node.setTransparency(TransparencyAttrib.MAlpha)
video_node.setDepthWrite(False)
video_node.setDepthTest(False)
movie_texture = self._load_movie_texture("gui_video_texture_2d", video_path)
if movie_texture:
video_node.setTexture(movie_texture, 1)
video_node.setPythonTag("movie_texture", movie_texture)
self._movie_textures.append(movie_texture)
return video_node
if __name__ == "__main__":
try:
app = MainApp()
app.run()
except Exception as e:
print(f"应用程序启动失败: {e}")
traceback.print_exc()