打包功能

This commit is contained in:
ayuan9957 2026-03-18 13:58:53 +08:00
parent 6524f8306b
commit da629e508d
78 changed files with 7276 additions and 1854 deletions

BIN
111/111.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

8
111/project.json Normal file
View File

@ -0,0 +1,8 @@
{
"name": "111",
"path": "D:\\IMGUI\\EG\\111",
"last_modified": "2026-03-18 10:56:00",
"scene_file": "scenes\\scene.bam",
"created_at": "2026-03-18 09:46:18",
"version": "1.0"
}

BIN
111/scenes/scene.bam Normal file

Binary file not shown.

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跳跃脚本 - 让对象产生上下跳跃效果
"""
from core.script_system import ScriptBase
import math
class BouncerScript(ScriptBase):
"""跳跃脚本类"""
def __init__(self):
super().__init__()
# 跳跃参数
self.jump_height = 2.0 # 跳跃高度
self.jump_speed = 3.0 # 跳跃速度 (跳跃/秒)
self.bounce_type = "sine" # 跳跃类型: "sine", "abs_sine", "square"
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_y = None # 原始Y位置
self.is_bouncing = True # 是否正在跳跃
self.bounce_direction = 1 # 跳跃方向
def start(self):
"""脚本开始时调用"""
self.log("跳跃脚本启动!")
self.log(f"跳跃参数: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
# 记录原始Y位置
self.original_y = self.gameObject.getZ() # Z轴是高度
self.log(f"原始高度: {self.original_y}")
def update(self, dt):
"""每帧更新"""
if not self.is_bouncing:
return
# 累积时间
self.time_accumulator += dt * self.bounce_direction
# 根据类型计算跳跃高度
if self.bounce_type == "sine":
# 标准正弦波跳跃
height_offset = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi) * self.jump_height
elif self.bounce_type == "abs_sine":
# 绝对值正弦波(始终向上)
height_offset = abs(math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)) * self.jump_height
elif self.bounce_type == "square":
# 方波跳跃(突然跳起落下)
sine_val = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)
height_offset = self.jump_height if sine_val > 0 else 0
else:
height_offset = 0
# 应用跳跃
current_pos = self.gameObject.getPos()
new_z = self.original_y + height_offset
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), new_z)
def set_bounce_parameters(self, height=None, speed=None, bounce_type=None):
"""设置跳跃参数"""
if height is not None:
self.jump_height = height
if speed is not None:
self.jump_speed = speed
if bounce_type is not None and bounce_type in ["sine", "abs_sine", "square"]:
self.bounce_type = bounce_type
self.log(f"跳跃参数更新: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
def toggle_bouncing(self):
"""切换跳跃状态"""
self.is_bouncing = not self.is_bouncing
status = "恢复" if self.is_bouncing else "暂停"
self.log(f"跳跃{status}")
def reverse_direction(self):
"""反转跳跃方向"""
self.bounce_direction *= -1
direction = "正向" if self.bounce_direction > 0 else "反向"
self.log(f"跳跃方向改为{direction}")
def reset_position(self):
"""重置到原始高度"""
if self.original_y is not None:
current_pos = self.gameObject.getPos()
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), self.original_y)
self.time_accumulator = 0.0
self.log("位置已重置到原始高度")
def jump_once(self):
"""执行一次跳跃"""
self.time_accumulator = 0.0
self.log("执行单次跳跃")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跳跃脚本停止")

View File

@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
颜色变化脚本 - 让对象颜色产生循环变化
"""
from core.script_system import ScriptBase
from panda3d.core import Vec4
import math
class ColorChangerScript(ScriptBase):
"""颜色变化脚本类"""
def __init__(self):
super().__init__()
# 颜色参数
self.color_speed = 1.0 # 颜色变化速度 (周期/秒)
self.color_mode = "rainbow" # 颜色模式: "rainbow", "pulse", "fade", "strobe"
self.base_color = Vec4(1, 1, 1, 1) # 基础颜色
self.intensity = 1.0 # 颜色强度
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_color = None # 原始颜色
self.is_changing = True # 是否正在变化
self.strobe_state = False # 闪烁状态
def start(self):
"""脚本开始时调用"""
self.log("颜色变化脚本启动!")
self.log(f"颜色参数: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
# 记录原始颜色
self.original_color = self.gameObject.getColor()
self.log(f"原始颜色: {self.original_color}")
def update(self, dt):
"""每帧更新"""
if not self.is_changing:
return
# 累积时间
self.time_accumulator += dt
# 根据模式计算新颜色
if self.color_mode == "rainbow":
new_color = self._calculate_rainbow_color()
elif self.color_mode == "pulse":
new_color = self._calculate_pulse_color()
elif self.color_mode == "fade":
new_color = self._calculate_fade_color()
elif self.color_mode == "strobe":
new_color = self._calculate_strobe_color()
else:
new_color = self.base_color
# 应用颜色
self.gameObject.setColor(new_color)
def _calculate_rainbow_color(self):
"""计算彩虹颜色"""
# 使用HSV到RGB的转换创建彩虹效果
hue = (self.time_accumulator * self.color_speed) % 1.0
# 简单的HSV到RGB转换
i = int(hue * 6.0)
f = (hue * 6.0) - i
p = 0.0
q = 1.0 - f
t = f
if i % 6 == 0:
r, g, b = 1.0, t, p
elif i % 6 == 1:
r, g, b = q, 1.0, p
elif i % 6 == 2:
r, g, b = p, 1.0, t
elif i % 6 == 3:
r, g, b = p, q, 1.0
elif i % 6 == 4:
r, g, b = t, p, 1.0
else:
r, g, b = 1.0, p, q
return Vec4(r * self.intensity, g * self.intensity, b * self.intensity, 1.0)
def _calculate_pulse_color(self):
"""计算脉冲颜色"""
pulse = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
multiplier = pulse * self.intensity
return Vec4(
self.base_color.getX() * multiplier,
self.base_color.getY() * multiplier,
self.base_color.getZ() * multiplier,
self.base_color.getW()
)
def _calculate_fade_color(self):
"""计算淡入淡出颜色"""
fade = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
alpha = fade * self.intensity
return Vec4(
self.base_color.getX(),
self.base_color.getY(),
self.base_color.getZ(),
alpha
)
def _calculate_strobe_color(self):
"""计算闪烁颜色"""
# 根据时间间隔切换状态
interval = 1.0 / (self.color_speed * 2) # 闪烁间隔
if int(self.time_accumulator / interval) % 2 == 0:
return Vec4(
self.base_color.getX() * self.intensity,
self.base_color.getY() * self.intensity,
self.base_color.getZ() * self.intensity,
self.base_color.getW()
)
else:
return Vec4(0.1, 0.1, 0.1, self.base_color.getW()) # 暗色状态
def set_color_parameters(self, speed=None, mode=None, base_color=None, intensity=None):
"""设置颜色参数"""
if speed is not None:
self.color_speed = speed
if mode is not None and mode in ["rainbow", "pulse", "fade", "strobe"]:
self.color_mode = mode
if base_color is not None:
self.base_color = base_color
if intensity is not None:
self.intensity = intensity
self.log(f"颜色参数更新: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
def toggle_color_change(self):
"""切换颜色变化状态"""
self.is_changing = not self.is_changing
status = "恢复" if self.is_changing else "暂停"
self.log(f"颜色变化{status}")
def reset_color(self):
"""重置到原始颜色"""
if self.original_color:
self.gameObject.setColor(self.original_color)
self.time_accumulator = 0.0
self.log("颜色已重置到原始值")
def set_solid_color(self, r=1.0, g=1.0, b=1.0, a=1.0):
"""设置固定颜色"""
color = Vec4(r, g, b, a)
self.gameObject.setColor(color)
self.base_color = color
self.log(f"设置固定颜色: {color}")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("颜色变化脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
复合动画脚本 - 结合旋转和跳跃效果
"""
from core.script_system import ScriptBase
import math
class ComboAnimatorScript(ScriptBase):
def __init__(self):
super().__init__()
self.time = 0.0
self.original_pos = None
self.is_active = True
def start(self):
self.log("复合动画脚本启动!")
self.original_pos = self.gameObject.getPos()
def update(self, dt):
if not self.is_active:
return
self.time += dt
# 旋转效果
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + 45.0 * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
# 跳跃效果
if self.original_pos:
bounce_offset = abs(math.sin(self.time * 3.0)) * 1.0
self.gameObject.setZ(self.original_pos.getZ() + bounce_offset)
def on_destroy(self):
self.log("复合动画脚本停止")

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跟随脚本 - 让对象跟随指定的目标对象
"""
from core.script_system import ScriptBase
from panda3d.core import Vec3
class FollowerScript(ScriptBase):
"""跟随脚本类"""
def __init__(self):
super().__init__()
self.target = None # 跟随目标
self.follow_speed = 5.0 # 跟随速度
self.follow_distance = 2.0 # 跟随距离
self.is_following = True # 是否正在跟随
def start(self):
"""脚本开始时调用"""
self.log("跟随脚本启动!")
self.log(f"跟随参数: 速度={self.follow_speed}, 距离={self.follow_distance}")
def update(self, dt):
"""每帧更新"""
if not self.is_following or self.target is None:
return
target_pos = self.target.getPos()
current_pos = self.gameObject.getPos()
# 计算目标方向
direction = target_pos - current_pos
distance = direction.length()
# 如果距离大于跟随距离,则移动
if distance > self.follow_distance:
if distance > 0:
direction.normalize()
# 计算目标位置(保持跟随距离)
target_follow_pos = target_pos - direction * self.follow_distance
# 平滑移动到目标位置
move_direction = target_follow_pos - current_pos
move_distance = move_direction.length()
if move_distance > 0:
move_direction.normalize()
move_amount = min(self.follow_speed * dt, move_distance)
new_pos = current_pos + move_direction * move_amount
self.gameObject.setPos(new_pos)
# 朝向目标
self.gameObject.lookAt(target_pos)
def set_target(self, target):
"""设置跟随目标"""
self.target = target
if target:
self.log(f"设置跟随目标: {target.getName()}")
else:
self.log("清除跟随目标")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跟随脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
移动脚本 - 让对象在指定方向上来回移动
"""
from core.script_system import ScriptBase
import math
class MoverScript(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
# 移动参数
self.move_distance = 5.0 # 移动距离
self.move_speed = 2.0 # 移动速度 (单位/秒)
self.move_axis = "x" # 移动轴: "x", "y", "z"
# 内部变量
self.start_position = None # 起始位置
self.current_direction = 1 # 当前移动方向: 1或-1
self.current_distance = 0.0 # 当前移动距离
self.is_moving = True # 是否正在移动
def start(self):
"""脚本开始时调用"""
self.log("移动脚本启动!")
self.log(f"移动参数: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
# 记录起始位置
self.start_position = self.gameObject.getPos()
self.log(f"起始位置: {self.start_position}")
def update(self, dt):
"""每帧更新"""
if not self.is_moving or self.start_position is None:
return
# 计算移动增量
move_delta = self.move_speed * dt * self.current_direction
self.current_distance += abs(move_delta)
# 检查是否需要改变方向
if self.current_distance >= self.move_distance:
self.current_direction *= -1
self.current_distance = 0.0
# 应用移动
current_pos = self.gameObject.getPos()
new_pos = [current_pos.getX(), current_pos.getY(), current_pos.getZ()]
if self.move_axis == "x":
new_pos[0] += move_delta
elif self.move_axis == "y":
new_pos[1] += move_delta
elif self.move_axis == "z":
new_pos[2] += move_delta
self.gameObject.setPos(new_pos[0], new_pos[1], new_pos[2])
def set_move_parameters(self, distance=None, speed=None, axis=None):
"""设置移动参数"""
if distance is not None:
self.move_distance = distance
if speed is not None:
self.move_speed = speed
if axis is not None and axis in ["x", "y", "z"]:
self.move_axis = axis
self.log(f"移动参数更新: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
def toggle_movement(self):
"""切换移动状态"""
self.is_moving = not self.is_moving
status = "恢复" if self.is_moving else "暂停"
self.log(f"移动{status}")
def reset_position(self):
"""重置到起始位置"""
if self.start_position:
self.gameObject.setPos(self.start_position)
self.current_distance = 0.0
self.current_direction = 1
self.log("位置已重置到起始点")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本停止")

133
111/scripts/R_P.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

133
111/scripts/R_R.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), final_angle)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(final_angle, current_hpr.getY(), current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 25.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 15.0
self.direction = 1
self.current_angle = 0.0
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
"""每帧更新"""
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_angle += delta_angle
# 如果超出角度范围,则反向
if self.current_angle > self.max_angle:
self.current_angle = self.max_angle
self.direction *= -1
elif self.current_angle < -self.max_angle:
self.current_angle = -self.max_angle
self.direction *= -1
# 设置新的旋转只改变Z轴保持其他不变
base_hpr = self.gameObject.getHpr()
new_r = self.current_angle
self.gameObject.setHpr(base_hpr.getX(), base_hpr.getY(), new_r)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
# 检查 gameObject 是否存在且不为空
if not self.gameObject or self.gameObject.isEmpty():
print("RotatorScript: gameObject is empty or None, skipping update")
return
"""每帧更新"""
if not self.is_rotating:
return
# 获取当前旋转并应用增量
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + self.rotation_speed_y * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
缩放脚本 - 让对象产生呼吸般的缩放效果
"""
from core.script_system import ScriptBase
import math
class ScalerScript(ScriptBase):
"""缩放脚本类"""
def __init__(self):
super().__init__()
# 缩放参数
self.base_scale = 1.0 # 基础缩放
self.scale_amplitude = 0.3 # 缩放幅度
self.scale_speed = 2.0 # 缩放速度 (周期/秒)
self.uniform_scale = True # 是否统一缩放(所有轴)
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_scale = None # 原始缩放
self.is_scaling = True # 是否正在缩放
def start(self):
"""脚本开始时调用"""
self.log("缩放脚本启动!")
self.log(f"缩放参数: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
# 记录原始缩放
self.original_scale = self.gameObject.getScale()
self.log(f"原始缩放: {self.original_scale}")
def update(self, dt):
"""每帧更新"""
if not self.is_scaling:
return
# 累积时间
self.time_accumulator += dt
# 计算正弦波缩放值
sine_value = math.sin(self.time_accumulator * self.scale_speed * 2 * math.pi)
scale_factor = self.base_scale + (self.scale_amplitude * sine_value)
# 应用缩放
if self.uniform_scale:
# 统一缩放
self.gameObject.setScale(scale_factor)
else:
# 非统一缩放仅Z轴
current_scale = self.gameObject.getScale()
self.gameObject.setScale(current_scale.getX(), current_scale.getY(), scale_factor)
def set_scale_parameters(self, base=None, amplitude=None, speed=None, uniform=None):
"""设置缩放参数"""
if base is not None:
self.base_scale = base
if amplitude is not None:
self.scale_amplitude = amplitude
if speed is not None:
self.scale_speed = speed
if uniform is not None:
self.uniform_scale = uniform
self.log(f"缩放参数更新: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
def toggle_scaling(self):
"""切换缩放状态"""
self.is_scaling = not self.is_scaling
status = "恢复" if self.is_scaling else "暂停"
self.log(f"缩放{status}")
def reset_scale(self):
"""重置到原始缩放"""
if self.original_scale:
self.gameObject.setScale(self.original_scale)
self.time_accumulator = 0.0
self.log("缩放已重置到原始值")
def pulse_once(self):
"""执行一次脉冲缩放"""
self.time_accumulator = 0.0
self.log("执行脉冲缩放")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("缩放脚本停止")

41
111/scripts/TestMover.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestMover - 移动脚本
"""
from core.script_system import ScriptBase
class Testmover(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
self.speed = 5.0 # 移动速度
self.direction = [1, 0, 0] # 移动方向
def start(self):
"""脚本开始时调用"""
self.log("移动脚本开始运行!")
def update(self, dt):
"""每帧更新"""
if self.transform:
# 计算移动偏移
offset_x = self.direction[0] * self.speed * dt
offset_y = self.direction[1] * self.speed * dt
offset_z = self.direction[2] * self.speed * dt
# 更新位置
current_pos = self.transform.getPos()
new_pos = (
current_pos.x + offset_x,
current_pos.y + offset_y,
current_pos.z + offset_z
)
self.transform.setPos(*new_pos)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestRotator - 自定义脚本
"""
from core.script_system import ScriptBase
class Testrotator(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

28
111/scripts/TestScaler.py Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestScaler - 自定义脚本
"""
from core.script_system import ScriptBase
class Testscaler(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

28
111/scripts/a.py Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
a - 自定义脚本
"""
from core.script_system import ScriptBase
class A(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
示例脚本 - 演示如何编写脚本
"""
from core.script_system import ScriptBase
class ExampleScript(ScriptBase):
"""示例脚本类"""
def __init__(self):
super().__init__()
self.counter = 0
self.rotation_speed = 30.0 # 度/秒
def start(self):
"""脚本开始时调用"""
self.log("示例脚本开始运行!")
self.log(f"挂载到对象: {self.gameObject.getName()}")
def update(self, dt):
"""每帧更新"""
self.counter += 1
# 每60帧输出一次信息
if self.counter % 60 == 0:
self.log(f"运行了 {self.counter}")
# 让对象旋转
if self.transform:
current_h = self.transform.getH()
new_h = current_h + self.rotation_speed * dt
self.transform.setH(new_h)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("示例脚本被销毁")
def on_enable(self):
"""脚本启用时调用"""
self.log("示例脚本被启用")
def on_disable(self):
"""脚本禁用时调用"""
self.log("示例脚本被禁用")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_quick_script - 自定义脚本
"""
from core.script_system import ScriptBase
class TestQuickScript(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,8 @@
{
"name": "新项目",
"path": "D:\\IMGUI\\EG\\111\\新项目",
"last_modified": "2026-03-18 12:12:50",
"scene_file": "scenes\\scene.bam",
"created_at": "2026-03-18 11:31:59",
"version": "1.0"
}

Binary file not shown.

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跳跃脚本 - 让对象产生上下跳跃效果
"""
from core.script_system import ScriptBase
import math
class BouncerScript(ScriptBase):
"""跳跃脚本类"""
def __init__(self):
super().__init__()
# 跳跃参数
self.jump_height = 2.0 # 跳跃高度
self.jump_speed = 3.0 # 跳跃速度 (跳跃/秒)
self.bounce_type = "sine" # 跳跃类型: "sine", "abs_sine", "square"
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_y = None # 原始Y位置
self.is_bouncing = True # 是否正在跳跃
self.bounce_direction = 1 # 跳跃方向
def start(self):
"""脚本开始时调用"""
self.log("跳跃脚本启动!")
self.log(f"跳跃参数: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
# 记录原始Y位置
self.original_y = self.gameObject.getZ() # Z轴是高度
self.log(f"原始高度: {self.original_y}")
def update(self, dt):
"""每帧更新"""
if not self.is_bouncing:
return
# 累积时间
self.time_accumulator += dt * self.bounce_direction
# 根据类型计算跳跃高度
if self.bounce_type == "sine":
# 标准正弦波跳跃
height_offset = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi) * self.jump_height
elif self.bounce_type == "abs_sine":
# 绝对值正弦波(始终向上)
height_offset = abs(math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)) * self.jump_height
elif self.bounce_type == "square":
# 方波跳跃(突然跳起落下)
sine_val = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)
height_offset = self.jump_height if sine_val > 0 else 0
else:
height_offset = 0
# 应用跳跃
current_pos = self.gameObject.getPos()
new_z = self.original_y + height_offset
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), new_z)
def set_bounce_parameters(self, height=None, speed=None, bounce_type=None):
"""设置跳跃参数"""
if height is not None:
self.jump_height = height
if speed is not None:
self.jump_speed = speed
if bounce_type is not None and bounce_type in ["sine", "abs_sine", "square"]:
self.bounce_type = bounce_type
self.log(f"跳跃参数更新: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
def toggle_bouncing(self):
"""切换跳跃状态"""
self.is_bouncing = not self.is_bouncing
status = "恢复" if self.is_bouncing else "暂停"
self.log(f"跳跃{status}")
def reverse_direction(self):
"""反转跳跃方向"""
self.bounce_direction *= -1
direction = "正向" if self.bounce_direction > 0 else "反向"
self.log(f"跳跃方向改为{direction}")
def reset_position(self):
"""重置到原始高度"""
if self.original_y is not None:
current_pos = self.gameObject.getPos()
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), self.original_y)
self.time_accumulator = 0.0
self.log("位置已重置到原始高度")
def jump_once(self):
"""执行一次跳跃"""
self.time_accumulator = 0.0
self.log("执行单次跳跃")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跳跃脚本停止")

View File

@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
颜色变化脚本 - 让对象颜色产生循环变化
"""
from core.script_system import ScriptBase
from panda3d.core import Vec4
import math
class ColorChangerScript(ScriptBase):
"""颜色变化脚本类"""
def __init__(self):
super().__init__()
# 颜色参数
self.color_speed = 1.0 # 颜色变化速度 (周期/秒)
self.color_mode = "rainbow" # 颜色模式: "rainbow", "pulse", "fade", "strobe"
self.base_color = Vec4(1, 1, 1, 1) # 基础颜色
self.intensity = 1.0 # 颜色强度
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_color = None # 原始颜色
self.is_changing = True # 是否正在变化
self.strobe_state = False # 闪烁状态
def start(self):
"""脚本开始时调用"""
self.log("颜色变化脚本启动!")
self.log(f"颜色参数: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
# 记录原始颜色
self.original_color = self.gameObject.getColor()
self.log(f"原始颜色: {self.original_color}")
def update(self, dt):
"""每帧更新"""
if not self.is_changing:
return
# 累积时间
self.time_accumulator += dt
# 根据模式计算新颜色
if self.color_mode == "rainbow":
new_color = self._calculate_rainbow_color()
elif self.color_mode == "pulse":
new_color = self._calculate_pulse_color()
elif self.color_mode == "fade":
new_color = self._calculate_fade_color()
elif self.color_mode == "strobe":
new_color = self._calculate_strobe_color()
else:
new_color = self.base_color
# 应用颜色
self.gameObject.setColor(new_color)
def _calculate_rainbow_color(self):
"""计算彩虹颜色"""
# 使用HSV到RGB的转换创建彩虹效果
hue = (self.time_accumulator * self.color_speed) % 1.0
# 简单的HSV到RGB转换
i = int(hue * 6.0)
f = (hue * 6.0) - i
p = 0.0
q = 1.0 - f
t = f
if i % 6 == 0:
r, g, b = 1.0, t, p
elif i % 6 == 1:
r, g, b = q, 1.0, p
elif i % 6 == 2:
r, g, b = p, 1.0, t
elif i % 6 == 3:
r, g, b = p, q, 1.0
elif i % 6 == 4:
r, g, b = t, p, 1.0
else:
r, g, b = 1.0, p, q
return Vec4(r * self.intensity, g * self.intensity, b * self.intensity, 1.0)
def _calculate_pulse_color(self):
"""计算脉冲颜色"""
pulse = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
multiplier = pulse * self.intensity
return Vec4(
self.base_color.getX() * multiplier,
self.base_color.getY() * multiplier,
self.base_color.getZ() * multiplier,
self.base_color.getW()
)
def _calculate_fade_color(self):
"""计算淡入淡出颜色"""
fade = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
alpha = fade * self.intensity
return Vec4(
self.base_color.getX(),
self.base_color.getY(),
self.base_color.getZ(),
alpha
)
def _calculate_strobe_color(self):
"""计算闪烁颜色"""
# 根据时间间隔切换状态
interval = 1.0 / (self.color_speed * 2) # 闪烁间隔
if int(self.time_accumulator / interval) % 2 == 0:
return Vec4(
self.base_color.getX() * self.intensity,
self.base_color.getY() * self.intensity,
self.base_color.getZ() * self.intensity,
self.base_color.getW()
)
else:
return Vec4(0.1, 0.1, 0.1, self.base_color.getW()) # 暗色状态
def set_color_parameters(self, speed=None, mode=None, base_color=None, intensity=None):
"""设置颜色参数"""
if speed is not None:
self.color_speed = speed
if mode is not None and mode in ["rainbow", "pulse", "fade", "strobe"]:
self.color_mode = mode
if base_color is not None:
self.base_color = base_color
if intensity is not None:
self.intensity = intensity
self.log(f"颜色参数更新: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
def toggle_color_change(self):
"""切换颜色变化状态"""
self.is_changing = not self.is_changing
status = "恢复" if self.is_changing else "暂停"
self.log(f"颜色变化{status}")
def reset_color(self):
"""重置到原始颜色"""
if self.original_color:
self.gameObject.setColor(self.original_color)
self.time_accumulator = 0.0
self.log("颜色已重置到原始值")
def set_solid_color(self, r=1.0, g=1.0, b=1.0, a=1.0):
"""设置固定颜色"""
color = Vec4(r, g, b, a)
self.gameObject.setColor(color)
self.base_color = color
self.log(f"设置固定颜色: {color}")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("颜色变化脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
复合动画脚本 - 结合旋转和跳跃效果
"""
from core.script_system import ScriptBase
import math
class ComboAnimatorScript(ScriptBase):
def __init__(self):
super().__init__()
self.time = 0.0
self.original_pos = None
self.is_active = True
def start(self):
self.log("复合动画脚本启动!")
self.original_pos = self.gameObject.getPos()
def update(self, dt):
if not self.is_active:
return
self.time += dt
# 旋转效果
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + 45.0 * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
# 跳跃效果
if self.original_pos:
bounce_offset = abs(math.sin(self.time * 3.0)) * 1.0
self.gameObject.setZ(self.original_pos.getZ() + bounce_offset)
def on_destroy(self):
self.log("复合动画脚本停止")

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跟随脚本 - 让对象跟随指定的目标对象
"""
from core.script_system import ScriptBase
from panda3d.core import Vec3
class FollowerScript(ScriptBase):
"""跟随脚本类"""
def __init__(self):
super().__init__()
self.target = None # 跟随目标
self.follow_speed = 5.0 # 跟随速度
self.follow_distance = 2.0 # 跟随距离
self.is_following = True # 是否正在跟随
def start(self):
"""脚本开始时调用"""
self.log("跟随脚本启动!")
self.log(f"跟随参数: 速度={self.follow_speed}, 距离={self.follow_distance}")
def update(self, dt):
"""每帧更新"""
if not self.is_following or self.target is None:
return
target_pos = self.target.getPos()
current_pos = self.gameObject.getPos()
# 计算目标方向
direction = target_pos - current_pos
distance = direction.length()
# 如果距离大于跟随距离,则移动
if distance > self.follow_distance:
if distance > 0:
direction.normalize()
# 计算目标位置(保持跟随距离)
target_follow_pos = target_pos - direction * self.follow_distance
# 平滑移动到目标位置
move_direction = target_follow_pos - current_pos
move_distance = move_direction.length()
if move_distance > 0:
move_direction.normalize()
move_amount = min(self.follow_speed * dt, move_distance)
new_pos = current_pos + move_direction * move_amount
self.gameObject.setPos(new_pos)
# 朝向目标
self.gameObject.lookAt(target_pos)
def set_target(self, target):
"""设置跟随目标"""
self.target = target
if target:
self.log(f"设置跟随目标: {target.getName()}")
else:
self.log("清除跟随目标")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跟随脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
移动脚本 - 让对象在指定方向上来回移动
"""
from core.script_system import ScriptBase
import math
class MoverScript(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
# 移动参数
self.move_distance = 5.0 # 移动距离
self.move_speed = 2.0 # 移动速度 (单位/秒)
self.move_axis = "x" # 移动轴: "x", "y", "z"
# 内部变量
self.start_position = None # 起始位置
self.current_direction = 1 # 当前移动方向: 1或-1
self.current_distance = 0.0 # 当前移动距离
self.is_moving = True # 是否正在移动
def start(self):
"""脚本开始时调用"""
self.log("移动脚本启动!")
self.log(f"移动参数: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
# 记录起始位置
self.start_position = self.gameObject.getPos()
self.log(f"起始位置: {self.start_position}")
def update(self, dt):
"""每帧更新"""
if not self.is_moving or self.start_position is None:
return
# 计算移动增量
move_delta = self.move_speed * dt * self.current_direction
self.current_distance += abs(move_delta)
# 检查是否需要改变方向
if self.current_distance >= self.move_distance:
self.current_direction *= -1
self.current_distance = 0.0
# 应用移动
current_pos = self.gameObject.getPos()
new_pos = [current_pos.getX(), current_pos.getY(), current_pos.getZ()]
if self.move_axis == "x":
new_pos[0] += move_delta
elif self.move_axis == "y":
new_pos[1] += move_delta
elif self.move_axis == "z":
new_pos[2] += move_delta
self.gameObject.setPos(new_pos[0], new_pos[1], new_pos[2])
def set_move_parameters(self, distance=None, speed=None, axis=None):
"""设置移动参数"""
if distance is not None:
self.move_distance = distance
if speed is not None:
self.move_speed = speed
if axis is not None and axis in ["x", "y", "z"]:
self.move_axis = axis
self.log(f"移动参数更新: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
def toggle_movement(self):
"""切换移动状态"""
self.is_moving = not self.is_moving
status = "恢复" if self.is_moving else "暂停"
self.log(f"移动{status}")
def reset_position(self):
"""重置到起始位置"""
if self.start_position:
self.gameObject.setPos(self.start_position)
self.current_distance = 0.0
self.current_direction = 1
self.log("位置已重置到起始点")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本停止")

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), final_angle)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(final_angle, current_hpr.getY(), current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 25.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 15.0
self.direction = 1
self.current_angle = 0.0
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
"""每帧更新"""
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_angle += delta_angle
# 如果超出角度范围,则反向
if self.current_angle > self.max_angle:
self.current_angle = self.max_angle
self.direction *= -1
elif self.current_angle < -self.max_angle:
self.current_angle = -self.max_angle
self.direction *= -1
# 设置新的旋转只改变Z轴保持其他不变
base_hpr = self.gameObject.getHpr()
new_r = self.current_angle
self.gameObject.setHpr(base_hpr.getX(), base_hpr.getY(), new_r)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
# 检查 gameObject 是否存在且不为空
if not self.gameObject or self.gameObject.isEmpty():
print("RotatorScript: gameObject is empty or None, skipping update")
return
"""每帧更新"""
if not self.is_rotating:
return
# 获取当前旋转并应用增量
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + self.rotation_speed_y * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
缩放脚本 - 让对象产生呼吸般的缩放效果
"""
from core.script_system import ScriptBase
import math
class ScalerScript(ScriptBase):
"""缩放脚本类"""
def __init__(self):
super().__init__()
# 缩放参数
self.base_scale = 1.0 # 基础缩放
self.scale_amplitude = 0.3 # 缩放幅度
self.scale_speed = 2.0 # 缩放速度 (周期/秒)
self.uniform_scale = True # 是否统一缩放(所有轴)
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_scale = None # 原始缩放
self.is_scaling = True # 是否正在缩放
def start(self):
"""脚本开始时调用"""
self.log("缩放脚本启动!")
self.log(f"缩放参数: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
# 记录原始缩放
self.original_scale = self.gameObject.getScale()
self.log(f"原始缩放: {self.original_scale}")
def update(self, dt):
"""每帧更新"""
if not self.is_scaling:
return
# 累积时间
self.time_accumulator += dt
# 计算正弦波缩放值
sine_value = math.sin(self.time_accumulator * self.scale_speed * 2 * math.pi)
scale_factor = self.base_scale + (self.scale_amplitude * sine_value)
# 应用缩放
if self.uniform_scale:
# 统一缩放
self.gameObject.setScale(scale_factor)
else:
# 非统一缩放仅Z轴
current_scale = self.gameObject.getScale()
self.gameObject.setScale(current_scale.getX(), current_scale.getY(), scale_factor)
def set_scale_parameters(self, base=None, amplitude=None, speed=None, uniform=None):
"""设置缩放参数"""
if base is not None:
self.base_scale = base
if amplitude is not None:
self.scale_amplitude = amplitude
if speed is not None:
self.scale_speed = speed
if uniform is not None:
self.uniform_scale = uniform
self.log(f"缩放参数更新: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
def toggle_scaling(self):
"""切换缩放状态"""
self.is_scaling = not self.is_scaling
status = "恢复" if self.is_scaling else "暂停"
self.log(f"缩放{status}")
def reset_scale(self):
"""重置到原始缩放"""
if self.original_scale:
self.gameObject.setScale(self.original_scale)
self.time_accumulator = 0.0
self.log("缩放已重置到原始值")
def pulse_once(self):
"""执行一次脉冲缩放"""
self.time_accumulator = 0.0
self.log("执行脉冲缩放")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("缩放脚本停止")

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestMover - 移动脚本
"""
from core.script_system import ScriptBase
class Testmover(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
self.speed = 5.0 # 移动速度
self.direction = [1, 0, 0] # 移动方向
def start(self):
"""脚本开始时调用"""
self.log("移动脚本开始运行!")
def update(self, dt):
"""每帧更新"""
if self.transform:
# 计算移动偏移
offset_x = self.direction[0] * self.speed * dt
offset_y = self.direction[1] * self.speed * dt
offset_z = self.direction[2] * self.speed * dt
# 更新位置
current_pos = self.transform.getPos()
new_pos = (
current_pos.x + offset_x,
current_pos.y + offset_y,
current_pos.z + offset_z
)
self.transform.setPos(*new_pos)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestRotator - 自定义脚本
"""
from core.script_system import ScriptBase
class Testrotator(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestScaler - 自定义脚本
"""
from core.script_system import ScriptBase
class Testscaler(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
a - 自定义脚本
"""
from core.script_system import ScriptBase
class A(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
示例脚本 - 演示如何编写脚本
"""
from core.script_system import ScriptBase
class ExampleScript(ScriptBase):
"""示例脚本类"""
def __init__(self):
super().__init__()
self.counter = 0
self.rotation_speed = 30.0 # 度/秒
def start(self):
"""脚本开始时调用"""
self.log("示例脚本开始运行!")
self.log(f"挂载到对象: {self.gameObject.getName()}")
def update(self, dt):
"""每帧更新"""
self.counter += 1
# 每60帧输出一次信息
if self.counter % 60 == 0:
self.log(f"运行了 {self.counter}")
# 让对象旋转
if self.transform:
current_h = self.transform.getH()
new_h = current_h + self.rotation_speed * dt
self.transform.setH(new_h)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("示例脚本被销毁")
def on_enable(self):
"""脚本启用时调用"""
self.log("示例脚本被启用")
def on_disable(self):
"""脚本禁用时调用"""
self.log("示例脚本被禁用")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_quick_script - 自定义脚本
"""
from core.script_system import ScriptBase
class TestQuickScript(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

BIN
111/新项目/新项目.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
Resources/models/jyc.glb Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
[]

Binary file not shown.

View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跳跃脚本 - 让对象产生上下跳跃效果
"""
from core.script_system import ScriptBase
import math
class BouncerScript(ScriptBase):
"""跳跃脚本类"""
def __init__(self):
super().__init__()
# 跳跃参数
self.jump_height = 2.0 # 跳跃高度
self.jump_speed = 3.0 # 跳跃速度 (跳跃/秒)
self.bounce_type = "sine" # 跳跃类型: "sine", "abs_sine", "square"
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_y = None # 原始Y位置
self.is_bouncing = True # 是否正在跳跃
self.bounce_direction = 1 # 跳跃方向
def start(self):
"""脚本开始时调用"""
self.log("跳跃脚本启动!")
self.log(f"跳跃参数: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
# 记录原始Y位置
self.original_y = self.gameObject.getZ() # Z轴是高度
self.log(f"原始高度: {self.original_y}")
def update(self, dt):
"""每帧更新"""
if not self.is_bouncing:
return
# 累积时间
self.time_accumulator += dt * self.bounce_direction
# 根据类型计算跳跃高度
if self.bounce_type == "sine":
# 标准正弦波跳跃
height_offset = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi) * self.jump_height
elif self.bounce_type == "abs_sine":
# 绝对值正弦波(始终向上)
height_offset = abs(math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)) * self.jump_height
elif self.bounce_type == "square":
# 方波跳跃(突然跳起落下)
sine_val = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)
height_offset = self.jump_height if sine_val > 0 else 0
else:
height_offset = 0
# 应用跳跃
current_pos = self.gameObject.getPos()
new_z = self.original_y + height_offset
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), new_z)
def set_bounce_parameters(self, height=None, speed=None, bounce_type=None):
"""设置跳跃参数"""
if height is not None:
self.jump_height = height
if speed is not None:
self.jump_speed = speed
if bounce_type is not None and bounce_type in ["sine", "abs_sine", "square"]:
self.bounce_type = bounce_type
self.log(f"跳跃参数更新: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
def toggle_bouncing(self):
"""切换跳跃状态"""
self.is_bouncing = not self.is_bouncing
status = "恢复" if self.is_bouncing else "暂停"
self.log(f"跳跃{status}")
def reverse_direction(self):
"""反转跳跃方向"""
self.bounce_direction *= -1
direction = "正向" if self.bounce_direction > 0 else "反向"
self.log(f"跳跃方向改为{direction}")
def reset_position(self):
"""重置到原始高度"""
if self.original_y is not None:
current_pos = self.gameObject.getPos()
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), self.original_y)
self.time_accumulator = 0.0
self.log("位置已重置到原始高度")
def jump_once(self):
"""执行一次跳跃"""
self.time_accumulator = 0.0
self.log("执行单次跳跃")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跳跃脚本停止")

View File

@ -0,0 +1,160 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
颜色变化脚本 - 让对象颜色产生循环变化
"""
from core.script_system import ScriptBase
from panda3d.core import Vec4
import math
class ColorChangerScript(ScriptBase):
"""颜色变化脚本类"""
def __init__(self):
super().__init__()
# 颜色参数
self.color_speed = 1.0 # 颜色变化速度 (周期/秒)
self.color_mode = "rainbow" # 颜色模式: "rainbow", "pulse", "fade", "strobe"
self.base_color = Vec4(1, 1, 1, 1) # 基础颜色
self.intensity = 1.0 # 颜色强度
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_color = None # 原始颜色
self.is_changing = True # 是否正在变化
self.strobe_state = False # 闪烁状态
def start(self):
"""脚本开始时调用"""
self.log("颜色变化脚本启动!")
self.log(f"颜色参数: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
# 记录原始颜色
self.original_color = self.gameObject.getColor()
self.log(f"原始颜色: {self.original_color}")
def update(self, dt):
"""每帧更新"""
if not self.is_changing:
return
# 累积时间
self.time_accumulator += dt
# 根据模式计算新颜色
if self.color_mode == "rainbow":
new_color = self._calculate_rainbow_color()
elif self.color_mode == "pulse":
new_color = self._calculate_pulse_color()
elif self.color_mode == "fade":
new_color = self._calculate_fade_color()
elif self.color_mode == "strobe":
new_color = self._calculate_strobe_color()
else:
new_color = self.base_color
# 应用颜色
self.gameObject.setColor(new_color)
def _calculate_rainbow_color(self):
"""计算彩虹颜色"""
# 使用HSV到RGB的转换创建彩虹效果
hue = (self.time_accumulator * self.color_speed) % 1.0
# 简单的HSV到RGB转换
i = int(hue * 6.0)
f = (hue * 6.0) - i
p = 0.0
q = 1.0 - f
t = f
if i % 6 == 0:
r, g, b = 1.0, t, p
elif i % 6 == 1:
r, g, b = q, 1.0, p
elif i % 6 == 2:
r, g, b = p, 1.0, t
elif i % 6 == 3:
r, g, b = p, q, 1.0
elif i % 6 == 4:
r, g, b = t, p, 1.0
else:
r, g, b = 1.0, p, q
return Vec4(r * self.intensity, g * self.intensity, b * self.intensity, 1.0)
def _calculate_pulse_color(self):
"""计算脉冲颜色"""
pulse = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
multiplier = pulse * self.intensity
return Vec4(
self.base_color.getX() * multiplier,
self.base_color.getY() * multiplier,
self.base_color.getZ() * multiplier,
self.base_color.getW()
)
def _calculate_fade_color(self):
"""计算淡入淡出颜色"""
fade = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
alpha = fade * self.intensity
return Vec4(
self.base_color.getX(),
self.base_color.getY(),
self.base_color.getZ(),
alpha
)
def _calculate_strobe_color(self):
"""计算闪烁颜色"""
# 根据时间间隔切换状态
interval = 1.0 / (self.color_speed * 2) # 闪烁间隔
if int(self.time_accumulator / interval) % 2 == 0:
return Vec4(
self.base_color.getX() * self.intensity,
self.base_color.getY() * self.intensity,
self.base_color.getZ() * self.intensity,
self.base_color.getW()
)
else:
return Vec4(0.1, 0.1, 0.1, self.base_color.getW()) # 暗色状态
def set_color_parameters(self, speed=None, mode=None, base_color=None, intensity=None):
"""设置颜色参数"""
if speed is not None:
self.color_speed = speed
if mode is not None and mode in ["rainbow", "pulse", "fade", "strobe"]:
self.color_mode = mode
if base_color is not None:
self.base_color = base_color
if intensity is not None:
self.intensity = intensity
self.log(f"颜色参数更新: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
def toggle_color_change(self):
"""切换颜色变化状态"""
self.is_changing = not self.is_changing
status = "恢复" if self.is_changing else "暂停"
self.log(f"颜色变化{status}")
def reset_color(self):
"""重置到原始颜色"""
if self.original_color:
self.gameObject.setColor(self.original_color)
self.time_accumulator = 0.0
self.log("颜色已重置到原始值")
def set_solid_color(self, r=1.0, g=1.0, b=1.0, a=1.0):
"""设置固定颜色"""
color = Vec4(r, g, b, a)
self.gameObject.setColor(color)
self.base_color = color
self.log(f"设置固定颜色: {color}")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("颜色变化脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
复合动画脚本 - 结合旋转和跳跃效果
"""
from core.script_system import ScriptBase
import math
class ComboAnimatorScript(ScriptBase):
def __init__(self):
super().__init__()
self.time = 0.0
self.original_pos = None
self.is_active = True
def start(self):
self.log("复合动画脚本启动!")
self.original_pos = self.gameObject.getPos()
def update(self, dt):
if not self.is_active:
return
self.time += dt
# 旋转效果
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + 45.0 * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
# 跳跃效果
if self.original_pos:
bounce_offset = abs(math.sin(self.time * 3.0)) * 1.0
self.gameObject.setZ(self.original_pos.getZ() + bounce_offset)
def on_destroy(self):
self.log("复合动画脚本停止")

View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跟随脚本 - 让对象跟随指定的目标对象
"""
from core.script_system import ScriptBase
from panda3d.core import Vec3
class FollowerScript(ScriptBase):
"""跟随脚本类"""
def __init__(self):
super().__init__()
self.target = None # 跟随目标
self.follow_speed = 5.0 # 跟随速度
self.follow_distance = 2.0 # 跟随距离
self.is_following = True # 是否正在跟随
def start(self):
"""脚本开始时调用"""
self.log("跟随脚本启动!")
self.log(f"跟随参数: 速度={self.follow_speed}, 距离={self.follow_distance}")
def update(self, dt):
"""每帧更新"""
if not self.is_following or self.target is None:
return
target_pos = self.target.getPos()
current_pos = self.gameObject.getPos()
# 计算目标方向
direction = target_pos - current_pos
distance = direction.length()
# 如果距离大于跟随距离,则移动
if distance > self.follow_distance:
if distance > 0:
direction.normalize()
# 计算目标位置(保持跟随距离)
target_follow_pos = target_pos - direction * self.follow_distance
# 平滑移动到目标位置
move_direction = target_follow_pos - current_pos
move_distance = move_direction.length()
if move_distance > 0:
move_direction.normalize()
move_amount = min(self.follow_speed * dt, move_distance)
new_pos = current_pos + move_direction * move_amount
self.gameObject.setPos(new_pos)
# 朝向目标
self.gameObject.lookAt(target_pos)
def set_target(self, target):
"""设置跟随目标"""
self.target = target
if target:
self.log(f"设置跟随目标: {target.getName()}")
else:
self.log("清除跟随目标")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跟随脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
移动脚本 - 让对象在指定方向上来回移动
"""
from core.script_system import ScriptBase
import math
class MoverScript(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
# 移动参数
self.move_distance = 5.0 # 移动距离
self.move_speed = 2.0 # 移动速度 (单位/秒)
self.move_axis = "x" # 移动轴: "x", "y", "z"
# 内部变量
self.start_position = None # 起始位置
self.current_direction = 1 # 当前移动方向: 1或-1
self.current_distance = 0.0 # 当前移动距离
self.is_moving = True # 是否正在移动
def start(self):
"""脚本开始时调用"""
self.log("移动脚本启动!")
self.log(f"移动参数: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
# 记录起始位置
self.start_position = self.gameObject.getPos()
self.log(f"起始位置: {self.start_position}")
def update(self, dt):
"""每帧更新"""
if not self.is_moving or self.start_position is None:
return
# 计算移动增量
move_delta = self.move_speed * dt * self.current_direction
self.current_distance += abs(move_delta)
# 检查是否需要改变方向
if self.current_distance >= self.move_distance:
self.current_direction *= -1
self.current_distance = 0.0
# 应用移动
current_pos = self.gameObject.getPos()
new_pos = [current_pos.getX(), current_pos.getY(), current_pos.getZ()]
if self.move_axis == "x":
new_pos[0] += move_delta
elif self.move_axis == "y":
new_pos[1] += move_delta
elif self.move_axis == "z":
new_pos[2] += move_delta
self.gameObject.setPos(new_pos[0], new_pos[1], new_pos[2])
def set_move_parameters(self, distance=None, speed=None, axis=None):
"""设置移动参数"""
if distance is not None:
self.move_distance = distance
if speed is not None:
self.move_speed = speed
if axis is not None and axis in ["x", "y", "z"]:
self.move_axis = axis
self.log(f"移动参数更新: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
def toggle_movement(self):
"""切换移动状态"""
self.is_moving = not self.is_moving
status = "恢复" if self.is_moving else "暂停"
self.log(f"移动{status}")
def reset_position(self):
"""重置到起始位置"""
if self.start_position:
self.gameObject.setPos(self.start_position)
self.current_distance = 0.0
self.current_direction = 1
self.log("位置已重置到起始点")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本停止")

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), final_angle)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(final_angle, current_hpr.getY(), current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,215 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 25.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 15.0
self.direction = 1
self.current_angle = 0.0
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
"""每帧更新"""
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_angle += delta_angle
# 如果超出角度范围,则反向
if self.current_angle > self.max_angle:
self.current_angle = self.max_angle
self.direction *= -1
elif self.current_angle < -self.max_angle:
self.current_angle = -self.max_angle
self.direction *= -1
# 设置新的旋转只改变Z轴保持其他不变
base_hpr = self.gameObject.getHpr()
new_r = self.current_angle
self.gameObject.setHpr(base_hpr.getX(), base_hpr.getY(), new_r)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
# 检查 gameObject 是否存在且不为空
if not self.gameObject or self.gameObject.isEmpty():
print("RotatorScript: gameObject is empty or None, skipping update")
return
"""每帧更新"""
if not self.is_rotating:
return
# 获取当前旋转并应用增量
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + self.rotation_speed_y * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
缩放脚本 - 让对象产生呼吸般的缩放效果
"""
from core.script_system import ScriptBase
import math
class ScalerScript(ScriptBase):
"""缩放脚本类"""
def __init__(self):
super().__init__()
# 缩放参数
self.base_scale = 1.0 # 基础缩放
self.scale_amplitude = 0.3 # 缩放幅度
self.scale_speed = 2.0 # 缩放速度 (周期/秒)
self.uniform_scale = True # 是否统一缩放(所有轴)
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_scale = None # 原始缩放
self.is_scaling = True # 是否正在缩放
def start(self):
"""脚本开始时调用"""
self.log("缩放脚本启动!")
self.log(f"缩放参数: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
# 记录原始缩放
self.original_scale = self.gameObject.getScale()
self.log(f"原始缩放: {self.original_scale}")
def update(self, dt):
"""每帧更新"""
if not self.is_scaling:
return
# 累积时间
self.time_accumulator += dt
# 计算正弦波缩放值
sine_value = math.sin(self.time_accumulator * self.scale_speed * 2 * math.pi)
scale_factor = self.base_scale + (self.scale_amplitude * sine_value)
# 应用缩放
if self.uniform_scale:
# 统一缩放
self.gameObject.setScale(scale_factor)
else:
# 非统一缩放仅Z轴
current_scale = self.gameObject.getScale()
self.gameObject.setScale(current_scale.getX(), current_scale.getY(), scale_factor)
def set_scale_parameters(self, base=None, amplitude=None, speed=None, uniform=None):
"""设置缩放参数"""
if base is not None:
self.base_scale = base
if amplitude is not None:
self.scale_amplitude = amplitude
if speed is not None:
self.scale_speed = speed
if uniform is not None:
self.uniform_scale = uniform
self.log(f"缩放参数更新: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
def toggle_scaling(self):
"""切换缩放状态"""
self.is_scaling = not self.is_scaling
status = "恢复" if self.is_scaling else "暂停"
self.log(f"缩放{status}")
def reset_scale(self):
"""重置到原始缩放"""
if self.original_scale:
self.gameObject.setScale(self.original_scale)
self.time_accumulator = 0.0
self.log("缩放已重置到原始值")
def pulse_once(self):
"""执行一次脉冲缩放"""
self.time_accumulator = 0.0
self.log("执行脉冲缩放")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("缩放脚本停止")

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestMover - 移动脚本
"""
from core.script_system import ScriptBase
class Testmover(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
self.speed = 5.0 # 移动速度
self.direction = [1, 0, 0] # 移动方向
def start(self):
"""脚本开始时调用"""
self.log("移动脚本开始运行!")
def update(self, dt):
"""每帧更新"""
if self.transform:
# 计算移动偏移
offset_x = self.direction[0] * self.speed * dt
offset_y = self.direction[1] * self.speed * dt
offset_z = self.direction[2] * self.speed * dt
# 更新位置
current_pos = self.transform.getPos()
new_pos = (
current_pos.x + offset_x,
current_pos.y + offset_y,
current_pos.z + offset_z
)
self.transform.setPos(*new_pos)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestRotator - 自定义脚本
"""
from core.script_system import ScriptBase
class Testrotator(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestScaler - 自定义脚本
"""
from core.script_system import ScriptBase
class Testscaler(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
a - 自定义脚本
"""
from core.script_system import ScriptBase
class A(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
示例脚本 - 演示如何编写脚本
"""
from core.script_system import ScriptBase
class ExampleScript(ScriptBase):
"""示例脚本类"""
def __init__(self):
super().__init__()
self.counter = 0
self.rotation_speed = 30.0 # 度/秒
def start(self):
"""脚本开始时调用"""
self.log("示例脚本开始运行!")
self.log(f"挂载到对象: {self.gameObject.getName()}")
def update(self, dt):
"""每帧更新"""
self.counter += 1
# 每60帧输出一次信息
if self.counter % 60 == 0:
self.log(f"运行了 {self.counter}")
# 让对象旋转
if self.transform:
current_h = self.transform.getH()
new_h = current_h + self.rotation_speed * dt
self.transform.setH(new_h)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("示例脚本被销毁")
def on_enable(self):
"""脚本启用时调用"""
self.log("示例脚本被启用")
def on_disable(self):
"""脚本禁用时调用"""
self.log("示例脚本被禁用")

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_quick_script - 自定义脚本
"""
from core.script_system import ScriptBase
class TestQuickScript(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -0,0 +1,534 @@
#!/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.actor.Actor import Actor
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
CardMaker,
Filename,
MovieTexture,
Point3,
TextNode,
Texture,
TransparencyAttrib,
Vec3,
load_prc_file_data,
)
PROJECT_NAME = "111"
def _bootstrap_paths():
if getattr(sys, "frozen", False):
project_root = os.path.dirname(sys.executable)
else:
project_root = os.path.dirname(os.path.abspath(__file__))
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
load_prc_file_data(
"",
f"""
win-size 1380 750
window-title {PROJECT_NAME}
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.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 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 _resolve_media_path(self, relative_path):
if not relative_path:
return ""
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
scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
if not scene:
print("⚠ 场景文件加载失败")
return
scene.reparentTo(self.render)
self.render_pipeline.prepare_scene(scene)
self.process_scene_elements(scene)
print("✓ 场景加载完成")
def process_scene_elements(self, root_node):
processed_lights = set()
def walk(node_path):
self._apply_user_visibility(node_path)
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
with open(gui_json_path, "r", encoding="utf-8") as f:
content = f.read().strip()
if not content:
return
gui_data = json.loads(content)
self.create_gui_elements(gui_data)
def create_gui_elements(self, element_data):
processed_names = set()
element_original_data = {}
for index, gui_info in enumerate(element_data or []):
name = gui_info.get("name", f"gui_element_{index}")
element_original_data[name] = {
"scale": gui_info.get("scale", [1, 1, 1]),
"position": gui_info.get("position", [0, 0, 0]),
"parent_name": gui_info.get("parent_name"),
}
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")
if parent_name and parent_name in element_original_data:
parent_scale = element_original_data[parent_name]["scale"]
for i in range(min(len(position), len(parent_scale))):
position[i] *= parent_scale[i]
for i in range(min(len(scale), len(parent_scale))):
scale[i] *= parent_scale[i]
text = gui_info.get("text", "")
image_path = self._resolve_media_path(gui_info.get("image_path", ""))
video_path = self._resolve_media_path(gui_info.get("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, image_path, video_path)
self.gui_elements.append(new_element)
if gui_info.get("scripts"):
self.process_scripts(new_element, gui_info["scripts"])
except Exception as e:
print(f"重建 GUI 元素失败: {e}")
traceback.print_exc()
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
if hasattr(node, "setTag"):
node.setTag("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))
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
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)
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)
return video_node
def play_model_animation(self):
actors = self.render.findAllMatches("**/+ActorNode")
for actor_np in actors:
actor_node = actor_np.node()
if isinstance(actor_node, Actor):
anim_names = actor_node.getAnimNames()
if anim_names:
actor_node.loop(anim_names[0])
if __name__ == "__main__":
try:
app = MainApp()
app.run()
except Exception as e:
print(f"应用程序启动失败: {e}")
traceback.print_exc()

View File

@ -1,26 +1,40 @@
"""
Core package - 核心功能模块
包含引擎的核心功能
- world.py: 基础世界功能相机光照地板等
- selection.py: 选择和变换系统
- event_handler.py: 事件处理系统
- tool_manager.py: 工具管理系统
- script_system.py: 脚本系统
"""
from .world import CoreWorld
from .selection import SelectionSystem
from .event_handler import EventHandler
from .tool_manager import ToolManager
from .script_system import ScriptManager, ScriptBase, ScriptComponent
__all__ = [
'CoreWorld',
'SelectionSystem',
'EventHandler',
'ToolManager',
'ScriptManager',
'ScriptBase',
'ScriptComponent'
]
"""
Core package - 核心功能模块
Keep package imports lazy so lightweight runtime contexts can import
`core.script_system` without eagerly pulling in the editor stack.
"""
from importlib import import_module
__all__ = [
"CoreWorld",
"SelectionSystem",
"EventHandler",
"ToolManager",
"ScriptManager",
"ScriptBase",
"ScriptComponent",
]
_LAZY_IMPORTS = {
"CoreWorld": ("core.world", "CoreWorld"),
"SelectionSystem": ("core.selection", "SelectionSystem"),
"EventHandler": ("core.event_handler", "EventHandler"),
"ToolManager": ("core.tool_manager", "ToolManager"),
"ScriptManager": ("core.script_system", "ScriptManager"),
"ScriptBase": ("core.script_system", "ScriptBase"),
"ScriptComponent": ("core.script_system", "ScriptComponent"),
}
def __getattr__(name):
if name not in _LAZY_IMPORTS:
raise AttributeError(f"module 'core' has no attribute {name!r}")
module_name, attr_name = _LAZY_IMPORTS[name]
module = import_module(module_name)
value = getattr(module, attr_name)
globals()[name] = value
return value

View File

@ -266,6 +266,24 @@ class ScriptLoader:
del self.script_classes[script_name]
print(f"✓ 脚本已卸载: {script_name}")
def clear(self, unload_components: bool = False):
"""清空当前加载的脚本缓存。"""
if unload_components:
for script_name in list(self.loaded_modules.keys()):
try:
self.unload_script(script_name)
except Exception as e:
print(f"卸载脚本失败 {script_name}: {e}")
else:
for module in list(self.loaded_modules.values()):
module_name = getattr(module, "__name__", "")
if module_name and module_name in sys.modules:
del sys.modules[module_name]
self.loaded_modules.clear()
self.script_classes.clear()
self.file_mtimes.clear()
def reload_script(self, script_path: str) -> Optional[type]:
"""重新加载脚本(热重载)"""
@ -313,7 +331,7 @@ class ScriptLoader:
scripts_dir = self.script_manager.scripts_directory
if os.path.exists(scripts_dir):
for file_name in os.listdir(scripts_dir):
if file_name.endswith('.py'):
if file_name.endswith(('.py', '.pyc')):
base_name = os.path.splitext(file_name)[0]
if base_name == script_name:
return os.path.join(scripts_dir, file_name)
@ -418,7 +436,7 @@ class ScriptManager:
self.script_templates: Dict[str, type] = {} # 脚本名 -> 脚本类
# 脚本目录
self.scripts_directory = "scripts"
self.scripts_directory = self._normalize_scripts_directory("scripts")
self._ensure_scripts_directory()
# 热重载监控
@ -426,6 +444,137 @@ class ScriptManager:
self.hot_reload_task = None
print("✓ 脚本管理系统初始化完成")
def _normalize_scripts_directory(self, directory: str) -> str:
directory = directory or "scripts"
return os.path.normpath(os.path.abspath(directory))
def get_project_path(self) -> Optional[str]:
project_manager = getattr(self.world, "project_manager", None)
project_path = getattr(project_manager, "current_project_path", None)
if project_path:
return os.path.normpath(project_path)
project_path = getattr(self.world, "project_path", None)
if project_path:
return os.path.normpath(project_path)
return None
def get_project_scripts_directory(self, project_path: Optional[str] = None) -> Optional[str]:
project_path = project_path or self.get_project_path()
if not project_path:
return None
return os.path.normpath(os.path.join(project_path, "scripts"))
def get_script_relative_path(self, script_path: str) -> str:
if not script_path:
return ""
script_path = os.path.normpath(os.path.abspath(script_path))
project_path = self.get_project_path()
if not project_path:
return ""
try:
relative_path = os.path.relpath(script_path, project_path)
except ValueError:
return ""
if relative_path.startswith(".."):
return ""
return relative_path.replace("\\", "/")
def resolve_script_path(self, script_info: Dict[str, Any]) -> str:
if not isinstance(script_info, dict):
return ""
project_path = self.get_project_path()
candidates = []
for key in ("project_relative_path", "relative_path", "path", "file"):
raw_value = str(script_info.get(key, "") or "").strip()
if not raw_value:
continue
normalized_value = raw_value.replace("/", os.sep)
if os.path.isabs(normalized_value):
candidates.append(os.path.normpath(normalized_value))
continue
if project_path:
candidates.append(os.path.normpath(os.path.join(project_path, normalized_value)))
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, normalized_value)))
lower_value = normalized_value.lower()
if not lower_value.endswith((".py", ".pyc")):
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{normalized_value}.py")))
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{normalized_value}.pyc")))
elif lower_value.endswith(".py"):
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{os.path.splitext(normalized_value)[0]}.pyc")))
elif lower_value.endswith(".pyc"):
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{os.path.splitext(normalized_value)[0]}.py")))
script_name = str(script_info.get("name", "") or "").strip()
if script_name:
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{script_name}.py")))
candidates.append(os.path.normpath(os.path.join(self.scripts_directory, f"{script_name}.pyc")))
seen = set()
for candidate in candidates:
if not candidate or candidate in seen:
continue
seen.add(candidate)
if os.path.exists(candidate):
return candidate
if script_name:
return self.loader.find_script_file(script_name) or ""
return ""
def build_script_reference(self, script_name: str, script_file: str = "") -> Dict[str, Any]:
reference = {"name": script_name}
resolved_script_path = script_file or self.loader.find_script_file(script_name) or ""
if resolved_script_path:
resolved_script_path = os.path.normpath(os.path.abspath(resolved_script_path))
relative_path = self.get_script_relative_path(resolved_script_path)
if relative_path:
reference["project_relative_path"] = relative_path
reference["file"] = relative_path
else:
reference["file"] = resolved_script_path
return reference
def set_scripts_directory(
self,
directory: str,
*,
create: bool = True,
reload_scripts: bool = True,
) -> str:
normalized_directory = self._normalize_scripts_directory(directory)
if normalized_directory == self.scripts_directory and (
os.path.exists(normalized_directory) or not create
):
return self.scripts_directory
self.scripts_directory = normalized_directory
if create:
self._ensure_scripts_directory()
self.loader.clear(unload_components=False)
self.script_templates.clear()
if reload_scripts and os.path.exists(self.scripts_directory):
self.load_all_scripts_from_directory()
print(f"✓ 当前脚本目录已切换到: {self.scripts_directory}")
return self.scripts_directory
def _ensure_scripts_directory(self):
"""确保脚本目录存在"""
@ -550,6 +699,9 @@ class ExampleScript(ScriptBase):
"""创建新的脚本文件"""
script_base_name = os.path.splitext(script_name.strip())[0]
script_path = os.path.join(self.scripts_directory, f"{script_base_name}.py")
if not os.path.exists(self.scripts_directory):
os.makedirs(self.scripts_directory)
if os.path.exists(script_path):
print(f"脚本文件已存在: {script_path}")
@ -670,19 +822,32 @@ class {class_name}(ScriptBase):
"""从目录加载所有脚本"""
if directory is None:
directory = self.scripts_directory
else:
directory = self._normalize_scripts_directory(directory)
if not os.path.exists(directory):
print(f"脚本目录不存在: {directory}")
return []
loaded_scripts = []
for filename in os.listdir(directory):
if filename.endswith('.py') and not filename.startswith('__'):
script_path = os.path.join(directory, filename)
script_class = self.load_script_from_file(script_path)
if script_class:
script_name = os.path.splitext(filename)[0]
loaded_scripts.append(script_name)
seen_script_names = set()
for filename in sorted(os.listdir(directory)):
if filename.startswith('__') or not filename.endswith(('.py', '.pyc')):
continue
script_name = os.path.splitext(filename)[0]
if script_name in seen_script_names:
continue
preferred_path = os.path.join(directory, f"{script_name}.py")
script_path = preferred_path if os.path.exists(preferred_path) else os.path.join(directory, filename)
if not os.path.exists(script_path):
continue
script_class = self.load_script_from_file(script_path)
if script_class:
loaded_scripts.append(script_name)
seen_script_names.add(script_name)
print(f"✓ 从目录 {directory} 加载了 {len(loaded_scripts)} 个脚本")
return loaded_scripts
@ -773,13 +938,8 @@ class {class_name}(ScriptBase):
script_info_list = []
for script_component in remaining_scripts:
script_name = script_component.script_name
script_class = script_component.script_instance.__class__
script_file = self.loader.find_script_file(script_name) or ""
script_info_list.append({
"name": script_name,
"file": script_file
})
script_info_list.append(self.build_script_reference(script_name, script_file))
import json
game_object.setTag("has_scripts", "true")

View File

@ -31,19 +31,19 @@ DockId=0x0000000D,0
[Window][场景树]
Pos=0,20
Size=274,1012
Size=274,1084
Collapsed=0
DockId=0x00000007,0
[Window][属性面板]
Pos=1527,20
Size=393,1012
Pos=1655,20
Size=393,1084
Collapsed=0
DockId=0x00000002,0
[Window][控制台]
Pos=276,632
Size=1249,400
Pos=276,704
Size=1377,400
Collapsed=0
DockId=0x00000006,1
@ -60,7 +60,7 @@ Collapsed=0
[Window][WindowOverViewport_11111111]
Pos=0,20
Size=1920,1012
Size=2048,1084
Collapsed=0
[Window][测试窗口1]
@ -84,12 +84,12 @@ Size=400,300
Collapsed=0
[Window][选择路径]
Pos=660,254
Pos=724,302
Size=600,500
Collapsed=0
[Window][打开项目]
Pos=710,304
Pos=774,352
Size=500,400
Collapsed=0
@ -99,8 +99,8 @@ Size=600,500
Collapsed=0
[Window][资源管理器]
Pos=276,632
Size=1249,400
Pos=276,704
Size=1377,400
Collapsed=0
DockId=0x00000006,0
@ -150,10 +150,9 @@ Size=101,226
Collapsed=0
[Window][LUI编辑器]
Pos=1690,20
Size=358,748
Pos=1675,295
Size=358,569
Collapsed=0
DockId=0x00000005,2
[Window][LUI测试控制面板]
Pos=6,10
@ -207,18 +206,23 @@ Collapsed=0
DockId=0x00000005,2
[Window][项目另存为]
Pos=794,432
Pos=1050,555
Size=460,240
Collapsed=0
[Window][打包项目]
Pos=784,442
Size=480,220
Collapsed=0
[Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,1012 Split=X
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2048,1084 Split=X
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1525,1012 Split=X
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=274,1084 Selected=0xE0015051
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1249,1084 Split=Y
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,610 Split=Y
DockNode ID=0x0000000D Parent=0x00000005 SizeRef=1318,383 HiddenTabBar=1 Selected=0x43A39006
DockNode ID=0x0000000E Parent=0x00000005 SizeRef=1318,363 CentralNode=1 Selected=0xE0015051
DockNode ID=0x00000006 Parent=0x00000008 SizeRef=2048,400 Selected=0x3A2E05C3
DockNode ID=0x00000006 Parent=0x00000008 SizeRef=2048,400 Selected=0x5428E753
DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=393,1012 Selected=0x5DB6FF37

1
index_name.txt Normal file
View File

@ -0,0 +1 @@
index-b2d982.boo

View File

@ -383,9 +383,11 @@ class MyWorld(PanelDelegates, CoreWorld):
self.show_new_project_dialog = False
self.show_open_project_dialog = False
self.show_save_as_dialog = False
self.show_build_project_dialog = False
self.show_about_dialog = False
self.save_as_project_name = ""
self.save_as_project_path = ""
self.build_output_path = ""
# 路径选择对话框状态
self.show_path_browser = False
@ -1003,6 +1005,7 @@ class MyWorld(PanelDelegates, CoreWorld):
self._draw_new_project_dialog()
self._draw_open_project_dialog()
self._draw_save_as_project_dialog()
self._draw_build_project_dialog()
self._draw_path_browser()
self._draw_import_dialog()

View File

@ -4,6 +4,8 @@ import json
import datetime
import subprocess
import shutil
import importlib.util
import py_compile
class ProjectManager:
@ -13,6 +15,58 @@ class ProjectManager:
self.project_config = None
print("✓ 项目管理系统初始化完成")
def _get_repo_root(self):
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def get_project_scripts_dir(self, project_path=None):
project_path = os.path.normpath(project_path or self.current_project_path or "")
if not project_path:
return ""
return os.path.join(project_path, "scripts")
def _ensure_project_scripts_dir(self, project_path):
scripts_dir = self.get_project_scripts_dir(project_path)
if scripts_dir:
os.makedirs(scripts_dir, exist_ok=True)
return scripts_dir
def _sync_project_script_manager(self, project_path, reload_scripts=True):
script_manager = getattr(self.world, "script_manager", None)
if not script_manager:
return ""
scripts_dir = self._ensure_project_scripts_dir(project_path)
script_manager.set_scripts_directory(
scripts_dir,
create=True,
reload_scripts=reload_scripts,
)
return scripts_dir
def _restore_project_context(self, project_path, project_config, reload_scripts=True):
normalized_project_path = os.path.normpath(project_path) if project_path else None
self.current_project_path = normalized_project_path
self.project_config = project_config
script_manager = getattr(self.world, "script_manager", None)
if not script_manager:
return
if normalized_project_path:
self._sync_project_script_manager(normalized_project_path, reload_scripts=reload_scripts)
else:
script_manager.set_scripts_directory(
"scripts",
create=True,
reload_scripts=reload_scripts,
)
def _sanitize_build_name(self, project_name):
invalid_chars = '<>:"/\\|?*'
safe_name = "".join("_" if char in invalid_chars else char for char in str(project_name or "").strip())
safe_name = safe_name.rstrip(". ")
return safe_name or "EGProject"
# ==================== 项目生命周期管理 ====================
@ -41,6 +95,7 @@ class ProjectManager:
os.makedirs(os.path.join(full_project_path, "textures")) # 贴图文件夹
scenes_path = os.path.join(full_project_path, "scenes") # 场景文件夹
os.makedirs(scenes_path)
self._ensure_project_scripts_dir(full_project_path)
# 创建项目配置文件
project_config = {
@ -61,9 +116,14 @@ class ProjectManager:
# 清空当前场景
self._clearCurrentScene()
# 更新项目状态,确保后续保存脚本元数据时能拿到项目路径
self.current_project_path = full_project_path
self.project_config = project_config
self._sync_project_script_manager(full_project_path, reload_scripts=True)
# 自动保存初始场景
scene_file = os.path.join(scenes_path, "scene.bam")
if self.world.scene_manager.saveScene(scene_file, project_path):
if self.world.scene_manager.saveScene(scene_file, full_project_path):
# 更新配置文件中的场景路径
project_config["scene_file"] = os.path.relpath(scene_file, full_project_path)
with open(config_file, "w", encoding="utf-8") as f:
@ -71,10 +131,6 @@ class ProjectManager:
print(f"初始场景已保存到: {scene_file}")
else:
print("初始场景保存失败")
# 更新项目状态
self.current_project_path = full_project_path
self.project_config = project_config
print(f"项目 '{project_name}' 创建成功!")
return True
@ -113,6 +169,13 @@ class ProjectManager:
project_config = json.load(f)
# print(f"[DEBUG] 项目配置读取成功: {project_config.get('name', 'Unknown')}")
previous_project_path = self.current_project_path
previous_project_config = self.project_config
self.current_project_path = os.path.normpath(project_path)
self.project_config = project_config
self._sync_project_script_manager(self.current_project_path, reload_scripts=True)
# 检查场景文件
scene_file = os.path.join(project_path, "scenes", "scene.bam")
# print(f"[DEBUG] 场景文件路径: {scene_file}")
@ -129,10 +192,20 @@ class ProjectManager:
# 检查world的基本状态
if not hasattr(self.world, 'render') or self.world.render is None:
# print(f"[DEBUG] 错误: world.render不存在或为None")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
if not hasattr(self.world, 'loader') or self.world.loader is None:
# print(f"[DEBUG] 错误: world.loader不存在或为None")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
# 清理可能的残留状态
@ -154,17 +227,32 @@ class ProjectManager:
# print(f"[DEBUG] 验证BAM文件完整性...")
if os.path.getsize(scene_file) == 0:
# print(f"[DEBUG] 错误: BAM文件为空")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
# 检查文件权限
if not os.access(scene_file, os.R_OK):
# print(f"[DEBUG] 错误: BAM文件不可读")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
# print(f"[DEBUG] BAM文件验证通过")
except Exception as e:
# print(f"[DEBUG] 清理状态时发生异常: {e}")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
if self.world.scene_manager.loadScene(scene_file):
@ -173,6 +261,11 @@ class ProjectManager:
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
else:
# print(f"[DEBUG] 场景加载失败")
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
else:
# print(f"[DEBUG] 场景文件不存在,跳过场景加载")
@ -183,7 +276,7 @@ class ProjectManager:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 更新项目状态
self.current_project_path = project_path
self.current_project_path = os.path.normpath(project_path)
self.project_config = project_config
# print(f"[DEBUG] ===== 项目打开成功: {project_path} =====")
@ -191,6 +284,11 @@ class ProjectManager:
return True
except Exception as e:
self._restore_project_context(
locals().get("previous_project_path"),
locals().get("previous_project_config"),
reload_scripts=True,
)
# print(f"[DEBUG] ===== 项目打开失败 =====")
print(f"加载项目时发生错误:{str(e)}")
import traceback
@ -209,6 +307,10 @@ class ProjectManager:
try:
if not project_path:
return False
previous_project_path = self.current_project_path
previous_project_config = self.project_config
# 检查是否是有效的项目文件夹
config_file = os.path.join(project_path, "project.json")
if not os.path.exists(config_file):
@ -219,6 +321,10 @@ class ProjectManager:
with open(config_file, "r", encoding="utf-8") as f:
project_config = json.load(f)
self.current_project_path = os.path.normpath(project_path)
self.project_config = project_config
self._sync_project_script_manager(self.current_project_path, reload_scripts=True)
# 检查场景文件
scene_file = os.path.join(project_path, "scenes", "scene.bam")
if os.path.exists(scene_file):
@ -226,19 +332,31 @@ class ProjectManager:
if self.world.scene_manager.loadScene(scene_file):
# 更新项目配置
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
else:
self._restore_project_context(
previous_project_path,
previous_project_config,
reload_scripts=True,
)
return False
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 更新项目状态
self.current_project_path = project_path
self.current_project_path = os.path.normpath(project_path)
self.project_config = project_config
print(f"项目 '{project_path}' 加载成功!")
return True
except Exception as e:
self._restore_project_context(
locals().get("previous_project_path"),
locals().get("previous_project_config"),
reload_scripts=True,
)
error_msg = f"加载项目时发生错误:{str(e)}"
print(error_msg)
return False
@ -331,104 +449,270 @@ class ProjectManager:
# ==================== 项目打包功能 ====================
def buildPackage(self, build_dir):
"""打包项目为可执行文件 - 按照Panda3D官方标准方法
Args:
build_dir: 打包输出目录
Returns:
bool: 打包是否成功
"""
"""将当前项目打包为最终运行程序。"""
try:
# 检查是否有当前项目路径
if not self.current_project_path:
print("错误: 请先创建或打开一个项目!")
return False
project_path = self.current_project_path
scenes_path = os.path.join(project_path, "scenes")
scene_file = os.path.join(scenes_path, "scene.bam")
# 检查场景文件是否存在
if not os.path.exists(scene_file):
print("错误: 请先保存场景!")
return False
if hasattr(self.world,'selection') and self.world.selection:
self.world.selection.clearSelection()
print("已取消场景中的物体选中状态")
if self.world.scene_manager.saveScene(scene_file, project_path):
print("场景保存成功")
config_file = os.path.join(project_path,"project.json")
if os.path.exists(config_file):
with open(config_file,"r",encoding="utf-8") as f:
project_config = json.load(f)
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
project_config["scene_file"] = os.path.relpath(scene_file,project_path)
with open(config_file,"w",encoding = "utf-8") as f:
json.dump(project_config,f,ensure_ascii = False,indent=4)
self.project_config = project_config
project_name = os.path.basename(project_path)
else:
print("错误: 保存场景失败!")
return False
project_path = os.path.normpath(self.current_project_path)
project_display_name = (
(self.project_config or {}).get("name")
or os.path.basename(project_path)
or "EGProject"
)
project_name = self._sanitize_build_name(project_display_name)
if not build_dir:
print("错误: 请指定打包输出目录!")
return False
# 创建构建目录
build_dir = os.path.join(build_dir, "build")
if not os.path.exists(build_dir):
os.makedirs(build_dir)
# 创建标准的打包文件
self._createStandardBuildFiles(build_dir, project_path, scene_file)
# 执行打包命令
success = self._executeStandardBuild(build_dir)
if success:
# 根据操作系统创建对应的启动脚本文件
try:
import stat
# 检查操作系统类型
if os.name == 'nt': # Windows系统
run_bat_path = os.path.join(build_dir, "run.bat")
with open(run_bat_path, "w") as f:
f.write("@echo off\n")
f.write("python Start_Run.py %*\n")
else: # Unix-like系统 (Linux, macOS等)
run_sh_path = os.path.join(build_dir, "run.sh")
with open(run_sh_path, "w") as f:
f.write("#!/bin/bash\n")
f.write("python3.11 Start_Run.py \"$@\"\n")
# 为Unix-like系统添加执行权限
st = os.stat(run_sh_path)
os.chmod(run_sh_path, st.st_mode | stat.S_IEXEC)
except Exception as e:
print(f"创建启动脚本文件失败: {str(e)}")
print("打包完成!可执行文件在 build 目录中。")
return True
else:
scene_file = os.path.join(project_path, "scenes", "scene.bam")
if not os.path.exists(scene_file):
print("错误: 请先保存场景!")
return False
if hasattr(self.world, "selection") and self.world.selection:
self.world.selection.clearSelection()
print("已取消场景中的物体选中状态")
self._sync_project_script_manager(project_path, reload_scripts=True)
if not self.saveProject():
print("错误: 保存场景失败!")
return False
output_root = os.path.normpath(build_dir)
staging_root = os.path.join(output_root, f".{project_name}_staging")
source_root = os.path.join(staging_root, "source")
assets_root = os.path.join(staging_root, "assets")
dist_dir = os.path.join(output_root, project_name)
if os.path.exists(staging_root):
shutil.rmtree(staging_root)
os.makedirs(source_root, exist_ok=True)
os.makedirs(assets_root, exist_ok=True)
if os.path.exists(dist_dir):
shutil.rmtree(dist_dir)
shutil.copy2(scene_file, os.path.join(assets_root, "scene.bam"))
self.copy_folder(os.path.join(project_path, "scenes", "resources"), assets_root)
self._merge_folder_contents(
os.path.join(project_path, "Resources"),
os.path.join(assets_root, "resources"),
)
if not self._saveGUIElementsToJSON(assets_root, project_path):
print("错误: 导出 GUI 数据失败!")
return False
self._saveCameraStateToJSON(assets_root)
self._copyScriptsToBuild(assets_root, project_path)
self._copyRenderPipelineRuntime(assets_root)
runtime_entry = self._createProjectRuntimeEntry(source_root, project_display_name)
if not runtime_entry:
print("错误: 生成项目运行时入口失败")
return False
success = self._executeProjectBuild(
source_root=source_root,
assets_root=assets_root,
build_root=output_root,
project_name=project_name,
)
if not success:
return False
if os.path.exists(staging_root):
shutil.rmtree(staging_root, ignore_errors=True)
exe_path = os.path.join(dist_dir, f"{project_name}.exe")
print(f"打包完成!项目运行程序: {exe_path}")
return True
except Exception as e:
print(f"打包过程出错:{str(e)}")
return False
def _merge_folder_contents(self, source_folder, destination_folder):
"""将 source_folder 的内容合并到 destination_folder。"""
if not source_folder or not os.path.exists(source_folder):
return False
os.makedirs(destination_folder, exist_ok=True)
for root, _, files in os.walk(source_folder):
rel_root = os.path.relpath(root, source_folder)
target_root = destination_folder if rel_root == "." else os.path.join(destination_folder, rel_root)
os.makedirs(target_root, exist_ok=True)
for file_name in files:
source_file = os.path.join(root, file_name)
target_file = os.path.join(target_root, file_name)
shutil.copy2(source_file, target_file)
return True
def _load_runtime_template(self):
template_path = os.path.join(self._get_repo_root(), "templates", "main_template.py")
with open(template_path, "r", encoding="utf-8") as f:
return f.read()
def _createProjectRuntimeEntry(self, source_root, project_name):
try:
template_content = self._load_runtime_template()
safe_project_name = project_name.replace("\\", "\\\\").replace('"', '\\"')
runtime_source = template_content.replace("__EG_PROJECT_NAME__", safe_project_name)
runtime_entry = os.path.join(source_root, "main.py")
with open(runtime_entry, "w", encoding="utf-8") as f:
f.write(runtime_source)
return runtime_entry
except Exception as e:
print(f"创建项目运行时入口失败: {e}")
return ""
def _executeProjectBuild(self, source_root, assets_root, build_root, project_name):
repo_root = self._get_repo_root()
work_root = os.path.join(build_root, f".{project_name}_work")
spec_root = os.path.join(build_root, f".{project_name}_spec")
for directory in (work_root, spec_root):
if os.path.exists(directory):
shutil.rmtree(directory)
os.makedirs(directory, exist_ok=True)
pyinstaller_runner = None
pyinstaller_candidates = [
[sys.executable, "-m", "PyInstaller"],
[sys.executable, "-m", "pyinstaller"],
["pyinstaller"],
]
for candidate in pyinstaller_candidates:
pyinstaller_probe = subprocess.run(
[*candidate, "--version"],
capture_output=True,
text=True,
check=False,
)
if pyinstaller_probe.returncode == 0:
pyinstaller_runner = candidate
break
if not pyinstaller_runner:
print("错误: 当前环境未安装 PyInstaller无法执行项目打包。")
return False
sep = ";" if os.name == "nt" else ":"
command = [
*pyinstaller_runner,
"--noconfirm",
"--clean",
"--windowed",
"--name",
project_name,
"--distpath",
build_root,
"--workpath",
work_root,
"--specpath",
spec_root,
"--paths",
source_root,
"--paths",
repo_root,
"--paths",
os.path.join(repo_root, "RenderPipelineFile"),
"--exclude-module",
"imgui_bundle",
"--exclude-module",
"p3dimgui",
"--hidden-import",
"core",
"--hidden-import",
"core.script_system",
"--hidden-import",
"core.CustomMouseController",
"--hidden-import",
"ssbo_component.runtime_importer",
"--hidden-import",
"ssbo_component.ssbo_controller",
"--hidden-import",
"rpcore",
"--collect-submodules",
"rpcore",
"--collect-submodules",
"rpplugins",
"--collect-submodules",
"rplibs",
]
panda_binary_dir = self._get_panda3d_binary_dir()
panda_display_binaries = [
"libpandagl.dll",
"libp3windisplay.dll",
"libpandadx9.dll",
"libp3tinydisplay.dll",
"cgGL.dll",
"cgD3D9.dll",
"d3dx9_43.dll",
]
if panda_binary_dir:
for binary_name in panda_display_binaries:
binary_path = os.path.join(panda_binary_dir, binary_name)
if os.path.exists(binary_path):
command.extend(["--add-binary", f"{binary_path}{sep}."])
data_mappings = [
(os.path.join(assets_root, "scene.bam"), "."),
(os.path.join(assets_root, "camera_state.json"), "."),
(os.path.join(assets_root, "gui"), "gui"),
(os.path.join(assets_root, "resources"), "resources"),
(os.path.join(assets_root, "scripts"), "scripts"),
(os.path.join(assets_root, "RenderPipelineFile"), "RenderPipelineFile"),
]
for source_path, target_path in data_mappings:
if os.path.exists(source_path):
command.extend(["--add-data", f"{source_path}{sep}{target_path}"])
command.append(os.path.join(source_root, "main.py"))
result = subprocess.run(
command,
cwd=source_root,
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
print("项目打包失败。")
output = (result.stdout or "") + "\n" + (result.stderr or "")
print(output[-4000:])
return False
for directory in (work_root, spec_root):
if os.path.exists(directory):
shutil.rmtree(directory, ignore_errors=True)
print(f"✓ 项目已打包到: {os.path.join(build_root, project_name)}")
return True
def _get_panda3d_binary_dir(self):
try:
panda3d_spec = importlib.util.find_spec("panda3d.core")
if panda3d_spec and panda3d_spec.origin:
panda_package_dir = os.path.dirname(os.path.dirname(os.path.abspath(panda3d_spec.origin)))
candidate_dirs = [
os.path.join(panda_package_dir, "bin"),
os.path.join(os.path.dirname(panda_package_dir), "bin"),
]
for candidate_dir in candidate_dirs:
if os.path.isdir(candidate_dir):
return candidate_dir
except Exception as e:
print(f"⚠️ 获取 Panda3D 二进制目录失败: {e}")
return ""
def _createStandardBuildFiles(self, build_dir, project_path, scene_file):
"""创建标准的Panda3D打包文件"""
project_name = os.path.basename(project_path)
# 确保构建目录存在
if not os.path.exists(build_dir):
os.makedirs(build_dir)
@ -472,38 +756,108 @@ class ProjectManager:
self._createRequirementsFile(build_dir)
def _copyScriptsToBuild(self, build_dir, project_path):
"""复制脚本文件到构建目录的scripts文件夹"""
"""将项目脚本编译后复制到构建目录,避免直接暴露源码。"""
try:
# 创建目标scripts目录
scripts_dest = os.path.join(build_dir, "scripts")
# 正确的源scripts目录路径
scripts_src = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts")
# 如果上面的路径不存在尝试项目路径下的scripts目录
if not os.path.exists(scripts_src):
scripts_src = os.path.join(project_path, "scripts")
scripts_src = self.get_project_scripts_dir(project_path)
if os.path.exists(scripts_dest):
shutil.rmtree(scripts_dest)
os.makedirs(scripts_dest, exist_ok=True)
if os.path.exists(scripts_src):
# 直接复制整个scripts目录
if os.path.exists(scripts_dest):
shutil.rmtree(scripts_dest)
compiled_count = 0
fallback_count = 0
for root, _, files in os.walk(scripts_src):
rel_root = os.path.relpath(root, scripts_src)
target_root = scripts_dest if rel_root == "." else os.path.join(scripts_dest, rel_root)
os.makedirs(target_root, exist_ok=True)
shutil.copytree(
scripts_src,
scripts_dest,
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
)
print("✓ Scripts目录已复制到build/scripts")
for file_name in files:
if file_name.startswith("__"):
continue
source_file = os.path.join(root, file_name)
if file_name.endswith(".py"):
compiled_file = os.path.join(
target_root,
f"{os.path.splitext(file_name)[0]}.pyc",
)
try:
py_compile.compile(source_file, cfile=compiled_file, doraise=True)
compiled_count += 1
except py_compile.PyCompileError as e:
fallback_file = os.path.join(target_root, file_name)
shutil.copy2(source_file, fallback_file)
fallback_count += 1
print(f"⚠️ 编译脚本失败,已回退复制源码: {source_file} -> {fallback_file} ({e})")
elif not file_name.endswith((".pyc", ".pyo", ".log")):
shutil.copy2(source_file, os.path.join(target_root, file_name))
print(f"✓ Scripts已导出到 build/scripts编译 {compiled_count} 个脚本")
if fallback_count:
print(f"⚠️ {fallback_count} 个脚本因编译失败保留为 .py 源码")
else:
# 创建空的scripts目录
if not os.path.exists(scripts_dest):
os.makedirs(scripts_dest)
print("⚠️ 项目中没有scripts目录")
except Exception as e:
print(f"⚠️ 复制脚本文件时出错: {str(e)}")
def _copyRenderPipelineRuntime(self, build_dir):
"""复制运行时需要的 RenderPipeline 资源,并将源码编译为 pyc。"""
try:
source_root = os.path.join(self._get_repo_root(), "RenderPipelineFile")
target_root = os.path.join(build_dir, "RenderPipelineFile")
if os.path.exists(target_root):
shutil.rmtree(target_root)
if not os.path.exists(source_root):
print("⚠️ RenderPipelineFile 文件夹未找到")
return False
blocked_dirs = {"__pycache__", ".git", ".vscode", "samples", "toolkit"}
blocked_root_files = {"setup.py", "start_daytime_editor.py", "start_plugin_configurator.py"}
blocked_relative_dirs = {
os.path.normpath(os.path.join("rplibs", "yaml", "yaml_py2")),
}
for root, dirs, files in os.walk(source_root):
relative_root = os.path.relpath(root, source_root)
normalized_relative_root = os.path.normpath(relative_root)
dirs[:] = [
name for name in dirs
if name not in blocked_dirs
and os.path.normpath(os.path.join(normalized_relative_root, name)) not in blocked_relative_dirs
]
target_dir = target_root if relative_root == "." else os.path.join(target_root, relative_root)
os.makedirs(target_dir, exist_ok=True)
for file_name in files:
if file_name.endswith((".pyc", ".pyo", ".log")):
continue
if relative_root == "." and file_name in blocked_root_files:
continue
source_file = os.path.join(root, file_name)
if file_name.endswith(".py"):
compiled_file = os.path.join(
target_dir,
f"{os.path.splitext(file_name)[0]}.pyc",
)
try:
py_compile.compile(source_file, cfile=compiled_file, doraise=True)
except py_compile.PyCompileError as e:
print(f"⚠️ RenderPipeline 脚本编译失败,保留源码: {source_file} ({e})")
shutil.copy2(source_file, os.path.join(target_dir, file_name))
else:
shutil.copy2(source_file, os.path.join(target_dir, file_name))
print("✓ RenderPipeline 运行时资源已导出")
return True
except Exception as e:
print(f"⚠️ 导出 RenderPipeline 运行时资源失败: {e}")
return False
def _copyScriptSystemToBuild(self,build_dir):
core_files = [
"script_system.py",
@ -559,22 +913,90 @@ class ProjectManager:
traceback.print_exc()
return False
def _saveCameraStateToJSON(self, build_dir):
"""保存当前主相机状态,供打包运行时恢复。"""
try:
camera = getattr(self.world, "camera", None) or getattr(self.world, "cam", None)
if not camera or camera.isEmpty():
return False
camera_state = {
"position": list(camera.getPos()),
"rotation": list(camera.getHpr()),
"camera_control_enabled": bool(getattr(self.world, "camera_control_enabled", True)),
}
camera_state_path = os.path.join(build_dir, "camera_state.json")
with open(camera_state_path, "w", encoding="utf-8") as f:
json.dump(camera_state, f, ensure_ascii=False, indent=4)
print(f"✓ 主相机状态已保存到 {camera_state_path}")
return True
except Exception as e:
print(f"⚠️ 保存主相机状态失败: {e}")
return False
def _updateResourcePaths(self,gui_info,project_path,build_dir):
project_path = os.path.normpath(project_path)
project_resources_dir = os.path.join(project_path, "Resources")
scene_resources_dir = os.path.join(project_path, "scenes", "resources")
def normalize_resource_path(old_path):
if not old_path:
return old_path
if old_path.startswith(("http://","https://")):
old_path = str(old_path).strip()
if not old_path:
return old_path
if os.path.isabs(old_path):
try:
rel_path = os.path.relpath(old_path,project_path)
return os.path.join("resources",os.path.basename(rel_path)).replace("\\","/")
except Exception:
return os.path.join("resources",os.path.basename(old_path)).replace("\\","/")
if old_path.startswith(("http://", "https://")):
return old_path
normalized_path = os.path.normpath(old_path.replace("/", os.sep))
if normalized_path == "resources" or normalized_path.startswith(f"resources{os.sep}"):
return normalized_path.replace("\\", "/")
candidate_paths = []
if os.path.isabs(normalized_path):
candidate_paths.append(normalized_path)
else:
return os.path.join("resources",os.path.basename(old_path)).replace("\\","/")
candidate_paths.extend(
[
os.path.join(project_resources_dir, normalized_path),
os.path.join(scene_resources_dir, normalized_path),
os.path.join(project_path, normalized_path),
]
)
resolved_path = ""
for candidate in candidate_paths:
if os.path.exists(candidate):
resolved_path = os.path.normpath(candidate)
break
if not resolved_path and os.path.isabs(normalized_path):
resolved_path = normalized_path
if resolved_path:
for base_dir in (project_resources_dir, scene_resources_dir):
try:
if os.path.exists(base_dir) and os.path.commonpath([resolved_path, base_dir]) == os.path.normpath(base_dir):
relative_path = os.path.relpath(resolved_path, base_dir)
return os.path.join("resources", relative_path).replace("\\", "/")
except ValueError:
continue
try:
relative_path = os.path.relpath(resolved_path, project_path)
if not relative_path.startswith(".."):
return os.path.join("resources", relative_path).replace("\\", "/")
except ValueError:
pass
return os.path.join("resources", os.path.basename(resolved_path)).replace("\\", "/")
return os.path.join("resources", os.path.basename(normalized_path)).replace("\\", "/")
if "image_path" in gui_info and gui_info["image_path"]:
gui_info["image_path"] = normalize_resource_path(gui_info["image_path"])
if "video_path" in gui_info and gui_info["video_path"]:

View File

@ -191,16 +191,12 @@ class SceneManagerIOMixin:
for script_component in scripts:
try:
script_name = script_component.script_name
# 获取脚本路径
script_class = script_component.script_instance.__class__
script_file = self._get_script_file_path(script_class, script_name)
script_file = script_manager.loader.find_script_file(script_name) or ""
script_reference = script_manager.build_script_reference(script_name, script_file)
# 只有当脚本文件存在时才保存
if script_file and os.path.exists(script_file):
gui_info["scripts"].append({
"name": script_name,
"file": script_file
})
print(f"收集脚本信息: {script_name} from {script_file}")
if script_reference.get("file") or script_reference.get("project_relative_path"):
gui_info["scripts"].append(script_reference)
print(f"收集脚本信息: {script_name} from {script_reference.get('file', '')}")
else:
print(f"警告: 脚本文件不存在: {script_file}")
except Exception as e:
@ -419,15 +415,10 @@ class SceneManagerIOMixin:
for script_component in scripts:
script_name = script_component.script_name
print(f"保存脚本信息: {script_name}")
# 获取脚本类的文件路径
script_class = script_component.script_instance.__class__
script_file = self._get_script_file_path(script_class, script_name)
script_info_list.append({
"name": script_name,
"file": script_file
})
script_file = script_manager.loader.find_script_file(script_name) or ""
script_info_list.append(
script_manager.build_script_reference(script_name, script_file)
)
# 将脚本信息保存为JSON字符串
import json
@ -955,7 +946,7 @@ class SceneManagerIOMixin:
script_manager = self.world.script_manager
for script_info in scripts_info:
script_name = script_info["name"]
script_file = script_info.get("file","")
script_file = script_manager.resolve_script_path(script_info)
print(f"尝试重新挂载脚本{script_name}from {script_file}")
@ -1272,14 +1263,14 @@ class SceneManagerIOMixin:
if os.path.exists(scripts_dir):
# 首先精确匹配
for file_name in os.listdir(scripts_dir):
if file_name.endswith('.py'):
if file_name.endswith(('.py', '.pyc')):
base_name = os.path.splitext(file_name)[0]
if base_name == script_name:
return os.path.join(scripts_dir, file_name)
# 如果没有精确匹配,尝试模糊匹配
for file_name in os.listdir(scripts_dir):
if file_name.endswith('.py'):
if file_name.endswith(('.py', '.pyc')):
base_name = os.path.splitext(file_name)[0]
if script_name.lower() in base_name.lower() or base_name.lower() in script_name.lower():
return os.path.join(scripts_dir, file_name)

View File

@ -0,0 +1,2 @@
"""SSBO runtime helpers and editor components."""

View File

@ -0,0 +1,356 @@
import os
from panda3d.core import Filename, GeomNode, Material, MaterialAttrib, NodePath
from .ssbo_controller import ObjectController
class RuntimeSSBOSceneImporter:
"""Minimal runtime-only SSBO scene importer without editor UI dependencies."""
def __init__(self, base_app):
self.base = base_app
self.controller = None
self.model = None
self.source_model = None
self.source_model_root = None
self.static_scene_root = None
self.interactive_scene_root = None
self.interactive_nodes = {}
def load_scene(self, scene_path, interactive_root_names=None):
source_model = self._load_source_model_from_path(scene_path)
self._clear_runtime_state()
source_root = self._ensure_source_model_root()
imported_root = source_model.copyTo(source_root)
self._set_node_name(imported_root, os.path.basename(scene_path) or "scene.bam")
self.source_model = imported_root
interactive_root_names = {str(name).strip() for name in (interactive_root_names or []) if str(name).strip()}
top_level_children = self._get_scene_top_level_children(imported_root)
if interactive_root_names and top_level_children:
self._build_release_split_scene(top_level_children, interactive_root_names)
return self.interactive_scene_root or self.static_scene_root
working_holder = NodePath("ssbo_source_scene_work")
working_root = source_root.copyTo(working_holder)
self.controller = ObjectController()
self.controller.bake_ids_and_collect(working_root)
self.model = self.controller.model
self.model.reparentTo(self.base.render)
try:
if not working_holder.isEmpty():
working_holder.removeNode()
except Exception:
pass
return self.model
def _clear_runtime_state(self):
for node in (self.model, self.static_scene_root, self.interactive_scene_root, self.source_model_root):
try:
if node and not node.isEmpty():
node.removeNode()
except Exception:
continue
self.controller = None
self.model = None
self.source_model = None
self.source_model_root = None
self.static_scene_root = None
self.interactive_scene_root = None
self.interactive_nodes = {}
def get_runtime_node_for_name(self, node_name):
return self.interactive_nodes.get(str(node_name or "").strip())
def _get_scene_top_level_children(self, imported_root):
children = []
try:
for child in imported_root.getChildren():
if child and not child.isEmpty():
children.append(child)
except Exception:
children = []
if children:
return children
return [imported_root] if imported_root and not imported_root.isEmpty() else []
def _build_release_split_scene(self, top_level_children, interactive_root_names):
static_holder = NodePath("runtime_static_scene")
interactive_holder = NodePath("runtime_interactive_scene")
interactive_count = 0
static_count = 0
for child in top_level_children:
child_name = self._get_node_name(child, "")
if child_name in interactive_root_names:
runtime_child = child.copyTo(interactive_holder)
self._set_node_name(runtime_child, child_name)
self.interactive_nodes[child_name] = runtime_child
interactive_count += 1
else:
child.copyTo(static_holder)
static_count += 1
if static_count:
self.static_scene_root = static_holder
self.static_scene_root.reparentTo(self.base.render)
try:
self.static_scene_root.flattenStrong()
except Exception as e:
print(f"静态场景合批失败,继续使用未合批结果: {e}")
else:
try:
if not static_holder.isEmpty():
static_holder.removeNode()
except Exception:
pass
if interactive_count:
self.interactive_scene_root = interactive_holder
self.interactive_scene_root.reparentTo(self.base.render)
else:
try:
if not interactive_holder.isEmpty():
interactive_holder.removeNode()
except Exception:
pass
print(f"[RuntimeSSBO] 运行时分层导入完成: 交互对象 {interactive_count} 个, 静态合批对象 {static_count}")
def _load_source_model_from_path(self, model_path):
source_model = None
last_error = None
for fn in self._build_filename_candidates(model_path):
try:
source_model = self.base.loader.loadModel(fn)
if source_model and not source_model.isEmpty():
break
except Exception as e:
last_error = e
source_model = None
if not source_model or source_model.isEmpty():
if last_error:
raise RuntimeError(f"Failed to load model '{model_path}': {last_error}")
raise RuntimeError(f"Failed to load model '{model_path}'")
self._fix_black_materials(source_model)
self._repair_missing_textures(source_model, model_path)
return source_model
def _build_filename_candidates(self, path_text):
candidates = []
seen = set()
for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
ctor = getattr(Filename, ctor_name, None)
if not ctor:
continue
try:
fn = ctor(path_text)
key = fn.getFullpath() if hasattr(fn, "getFullpath") else fn.get_fullpath()
if key in seen:
continue
seen.add(key)
candidates.append(fn)
except Exception:
continue
if not candidates:
try:
candidates.append(Filename(path_text))
except Exception:
pass
return candidates
def _ensure_source_model_root(self):
root = self.source_model_root
if root and not root.isEmpty():
return root
self.source_model_root = NodePath("ssbo_source_scene_root")
return self.source_model_root
def _set_node_name(self, node, name):
if not node:
return
try:
node.set_name(name)
return
except Exception:
pass
try:
node.setName(name)
except Exception:
pass
def _get_node_name(self, node, default_name=None):
if not node:
return default_name
try:
return node.get_name()
except Exception:
pass
try:
return node.getName()
except Exception:
return default_name
def _fix_black_materials(self, model):
try:
for geom_path in model.findAllMatches("**/+GeomNode"):
geom_node = geom_path.node()
node_state = geom_path.getState()
if node_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat and self._is_dark_material(mat):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_path.setState(node_state.setAttrib(MaterialAttrib.make(new_mat)))
for i in range(geom_node.getNumGeoms()):
geom_state = geom_node.getGeomState(i)
if geom_state.hasAttrib(MaterialAttrib.getClassType()):
mat_attrib = geom_state.getAttrib(MaterialAttrib.getClassType())
mat = mat_attrib.getMaterial()
if mat and self._is_dark_material(mat):
new_mat = Material(mat)
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
geom_node.setGeomState(i, geom_state.setAttrib(MaterialAttrib.make(new_mat)))
else:
new_mat = Material()
new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
new_mat.setSpecular((0.2, 0.2, 0.2, 1.0))
new_mat.setRoughness(0.8)
geom_node.setGeomState(i, geom_state.addAttrib(MaterialAttrib.make(new_mat)))
model.clearColor()
except Exception as e:
print(f"修复黑色模型材质时出错: {e}")
def _is_dark_material(self, material):
try:
if material.hasBaseColor():
c = material.getBaseColor()
elif material.hasDiffuse():
c = material.getDiffuse()
else:
return True
return c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05
except Exception:
return False
def _repair_missing_textures(self, model_np, model_path):
if not model_np or model_np.isEmpty():
return
search_dirs = self._build_texture_search_dirs(model_path)
texture_index = self._index_texture_files(search_dirs)
for node in [model_np] + list(model_np.findAllMatches("**")):
if not node or node.isEmpty():
continue
try:
stages = node.findAllTextureStages()
stage_count = stages.getNumTextureStages()
stage_at = stages.getTextureStage
except Exception:
continue
for i in range(stage_count):
stage = stage_at(i)
if not stage:
continue
try:
tex = node.getTexture(stage)
except Exception:
tex = None
if not tex or self._texture_is_valid(tex):
continue
basename = self._extract_texture_basename(tex)
if not basename:
continue
replacement = texture_index.get(basename.lower())
if replacement:
new_tex = self._load_texture_from_path(replacement)
if new_tex:
try:
node.setTexture(stage, new_tex, 1)
continue
except Exception:
pass
try:
node.clearTexture(stage)
except Exception:
pass
def _build_texture_search_dirs(self, model_path):
dirs = []
model_dir = os.path.dirname(os.path.abspath(model_path))
project_root = getattr(self.base, "project_path", "") or os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def add_dir(path):
if path and os.path.isdir(path):
normalized = os.path.normpath(path)
if normalized not in dirs:
dirs.append(normalized)
add_dir(model_dir)
for sub in ("textures", "texture", "tex", "assets", "materials"):
add_dir(os.path.join(model_dir, sub))
add_dir(os.path.join(project_root, "resources", sub))
add_dir(os.path.join(project_root, "resources"))
return dirs
def _index_texture_files(self, dirs, limit=30000):
texture_exts = {".png", ".jpg", ".jpeg", ".tga", ".bmp", ".dds", ".ktx", ".ktx2", ".webp"}
index = {}
scanned = 0
for root_dir in dirs:
try:
for root, _, files in os.walk(root_dir):
for filename in files:
if os.path.splitext(filename)[1].lower() not in texture_exts:
continue
index.setdefault(filename.lower(), os.path.join(root, filename))
scanned += 1
if scanned >= limit:
return index
except Exception:
continue
return index
def _load_texture_from_path(self, texture_path):
for fn in self._build_filename_candidates(texture_path):
try:
tex = self.base.loader.loadTexture(fn)
if tex:
return tex
except Exception:
continue
return None
def _texture_is_valid(self, texture):
try:
x_size = texture.getXSize()
y_size = texture.getYSize()
return bool(x_size and y_size)
except Exception:
return False
def _extract_texture_basename(self, texture):
try:
path = texture.getFilename().toOsSpecific()
except Exception:
path = ""
if not path:
return ""
return os.path.basename(path)

File diff suppressed because it is too large Load Diff

View File

@ -270,6 +270,38 @@ class AppActions:
self.add_info_message("打开另存为项目对话框")
self.show_save_as_dialog = True
def _on_build_project(self):
"""处理打包项目菜单项。"""
current_project_path = getattr(getattr(self, "project_manager", None), "current_project_path", None)
if current_project_path:
self.build_output_path = os.path.dirname(current_project_path)
else:
self.build_output_path = os.getcwd()
self.add_info_message("打开打包项目对话框")
self.show_build_project_dialog = True
def _build_project_impl(self, output_path):
"""执行项目打包。"""
if not hasattr(self, "project_manager") or not self.project_manager:
self.add_error_message("项目管理器未初始化")
return False
if not output_path or not output_path.strip():
self.add_warning_message("请选择打包输出目录")
return False
try:
build_success = self.project_manager.buildPackage(output_path.strip())
if build_success:
self.add_success_message("项目打包成功")
return True
self.add_error_message("项目打包失败")
return False
except Exception as e:
self.add_error_message(f"项目打包失败: {e}")
return False
def _show_about_dialog(self):
"""显示关于对话框。"""
@ -1033,71 +1065,16 @@ class AppActions:
def _open_project_impl(self, project_path):
"""打开项目的具体实现不依赖Qt"""
import json
import datetime
try:
# 检查项目管理器是否已初始化
if not hasattr(self, 'project_manager') or not self.project_manager:
print("✗ 项目管理器未初始化")
self.add_error_message("项目管理器未初始化")
return False
# 检查场景管理器是否已初始化
if not hasattr(self, 'scene_manager') or not self.scene_manager:
print("✗ 场景管理器未初始化")
self.add_error_message("场景管理器未初始化")
return False
# 检查是否是有效的项目文件夹
config_file = os.path.join(project_path, "project.json")
if not os.path.exists(config_file):
print(f"⚠ 选择的不是有效的项目文件夹: {project_path}")
self.add_warning_message(f"选择的不是有效的项目文件夹: {project_path}")
if not self.project_manager.openProject(project_path):
self.add_error_message(f"项目打开失败: {project_path}")
return False
# 读取项目配置
try:
with open(config_file, "r", encoding="utf-8") as f:
project_config = json.load(f)
except Exception as e:
print(f"✗ 读取项目配置文件失败: {e}")
self.add_error_message(f"读取项目配置文件失败: {e}")
return False
# 检查场景文件
scene_file = os.path.join(project_path, "scenes", "scene.bam")
if os.path.exists(scene_file):
if getattr(self, "use_ssbo_mouse_picking", False) and callable(getattr(self, "_import_model_for_runtime", None)):
self.use_ssbo_scene_import = True
# 加载场景
try:
if self.scene_manager.loadScene(scene_file):
# 更新项目配置
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
print(f"✓ 场景加载成功: {scene_file}")
else:
print(f"⚠ 场景加载失败: {scene_file}")
self.add_warning_message(f"场景加载失败: {scene_file}")
except Exception as e:
print(f"✗ 加载场景时发生错误: {e}")
self.add_error_message(f"加载场景时发生错误: {e}")
# 继续执行,不阻止项目打开
# 更新项目配置
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"✗ 保存项目配置失败: {e}")
self.add_error_message(f"保存项目配置失败: {e}")
# 更新项目状态
self.project_manager.current_project_path = project_path
self.project_manager.project_config = project_config
# 更新窗口标题
project_name = os.path.basename(project_path)
self._update_window_title(project_name)
@ -1113,45 +1090,12 @@ class AppActions:
def _create_new_project_impl(self, name, path):
"""创建新项目的具体实现不依赖Qt"""
full_project_path = os.path.normpath(os.path.join(path, name))
print(f"创建项目路径: {full_project_path}")
try:
# 创建项目文件夹结构
os.makedirs(full_project_path)
os.makedirs(os.path.join(full_project_path, "models")) # 模型文件夹
os.makedirs(os.path.join(full_project_path, "textures")) # 贴图文件夹
scenes_path = os.path.join(full_project_path, "scenes") # 场景文件夹
os.makedirs(scenes_path)
# 创建项目配置文件
project_config = {
"name": name,
"path": full_project_path,
"created": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"last_modified": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"version": "1.0",
"scene_file": "scenes/scene.bam"
}
# 保存项目配置
config_file = os.path.join(full_project_path, "project.json")
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
# 保存初始场景
scene_file = os.path.join(scenes_path, "scene.bam")
self.scene_manager.saveScene(scene_file, full_project_path)
# 更新项目管理器状态
self.project_manager.current_project_path = full_project_path
self.project_manager.project_config = project_config
# 更新窗口标题
if not self.project_manager.createNewProject(path, name):
return False
self._update_window_title(name)
return True
except Exception as e:
print(f"创建项目失败: {e}")
return False
@ -1192,9 +1136,11 @@ class AppActions:
os.makedirs(os.path.join(full_project_path, "models"), exist_ok=True)
os.makedirs(os.path.join(full_project_path, "textures"), exist_ok=True)
os.makedirs(os.path.join(full_project_path, "scenes"), exist_ok=True)
os.makedirs(os.path.join(full_project_path, "scripts"), exist_ok=True)
previous_project_path = self.project_manager.current_project_path
previous_project_config = dict(self.project_manager.project_config or {})
previous_scripts_dir = getattr(getattr(self, "script_manager", None), "scripts_directory", "")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
project_config = dict(previous_project_config)
@ -1217,12 +1163,25 @@ class AppActions:
with open(config_file, "w", encoding="utf-8") as f:
json.dump(project_config, f, ensure_ascii=False, indent=4)
source_scripts_dir = ""
if previous_project_path:
source_scripts_dir = self.project_manager.get_project_scripts_dir(previous_project_path)
if not source_scripts_dir:
source_scripts_dir = previous_scripts_dir
target_scripts_dir = self.project_manager.get_project_scripts_dir(full_project_path)
if source_scripts_dir and os.path.exists(source_scripts_dir):
self._copy_directory_contents(source_scripts_dir, target_scripts_dir)
self.project_manager.current_project_path = full_project_path
self.project_manager.project_config = project_config
self.project_manager._sync_project_script_manager(full_project_path, reload_scripts=True)
if not self.project_manager.saveProject():
self.project_manager.current_project_path = previous_project_path
self.project_manager.project_config = previous_project_config
if previous_project_path:
self.project_manager._sync_project_script_manager(previous_project_path, reload_scripts=True)
self._update_window_title(os.path.basename(previous_project_path) if previous_project_path else "未命名项目")
self.add_error_message("项目另存为失败")
return False
@ -1233,6 +1192,23 @@ class AppActions:
except Exception as e:
self.add_error_message(f"项目另存为失败: {e}")
return False
def _copy_directory_contents(self, source_dir, target_dir):
"""Copy a directory tree into target_dir, replacing existing files."""
import shutil
if not source_dir or not os.path.exists(source_dir):
return
os.makedirs(target_dir, exist_ok=True)
for root, _, files in os.walk(source_dir):
rel_root = os.path.relpath(root, source_dir)
destination_root = target_dir if rel_root == "." else os.path.join(target_dir, rel_root)
os.makedirs(destination_root, exist_ok=True)
for file_name in files:
source_file = os.path.join(root, file_name)
target_file = os.path.join(destination_root, file_name)
shutil.copy2(source_file, target_file)
def _update_window_title(self, project_name):

View File

@ -203,6 +203,51 @@ class DialogPanels:
imgui.same_line()
if imgui.button("取消"):
self.show_save_as_dialog = False
def _draw_build_project_dialog(self):
"""绘制项目打包对话框。"""
if not getattr(self, "show_build_project_dialog", False):
return
if not getattr(self, "build_output_path", "").strip():
current_project_path = getattr(getattr(self, "project_manager", None), "current_project_path", None)
self.build_output_path = os.path.dirname(current_project_path) if current_project_path else os.getcwd()
flags = (
imgui.WindowFlags_.no_resize
| imgui.WindowFlags_.no_collapse
| imgui.WindowFlags_.modal
)
self.style_manager.prepare_centered_dialog(480, 220)
with imgui_ctx.begin("打包项目", True, flags) as window:
if not window.opened:
self.show_build_project_dialog = False
return
imgui.text("生成当前项目的最终运行程序")
imgui.separator()
changed, output_path = imgui.input_text("输出目录", self.build_output_path, 512)
if changed:
self.build_output_path = output_path
imgui.same_line()
if imgui.button("浏览..."):
self.path_browser_mode = "build_project"
self.path_browser_current_path = self.build_output_path if self.build_output_path else os.getcwd()
self.show_path_browser = True
self._refresh_path_browser()
imgui.separator()
if imgui.button("开始打包"):
if self._build_project_impl(self.build_output_path):
self.show_build_project_dialog = False
imgui.same_line()
if imgui.button("取消"):
self.show_build_project_dialog = False
def _draw_path_browser(self):
@ -465,6 +510,9 @@ class DialogPanels:
# 另存为项目模式:使用当前路径作为目标目录
self.save_as_project_path = self.path_browser_current_path
self.add_info_message(f"已选择保存位置: {self.save_as_project_path}")
elif self.path_browser_mode == "build_project":
self.build_output_path = self.path_browser_current_path
self.add_info_message(f"已选择打包位置: {self.build_output_path}")
elif self.path_browser_mode == "import_model":
# 导入模型模式:使用选择的文件路径
self.import_file_path = self.path_browser_selected_path

View File

@ -187,6 +187,8 @@ class EditorPanelsTopMixin:
self.app._on_save_project()
if imgui.menu_item("另存为", "", False, True)[1]:
self.app._on_save_as_project()
if imgui.menu_item("打包项目", "", False, True)[1]:
self.app._on_build_project()
imgui.separator()
if imgui.menu_item("退出", "Alt+F4", False, True)[1]:
self.app._on_exit()

View File

@ -459,6 +459,12 @@ class PanelDelegates:
def _on_save_as_project(self, *args, **kwargs):
return self.app_actions._on_save_as_project(*args, **kwargs)
def _on_build_project(self, *args, **kwargs):
return self.app_actions._on_build_project(*args, **kwargs)
def _build_project_impl(self, *args, **kwargs):
return self.app_actions._build_project_impl(*args, **kwargs)
def _show_about_dialog(self, *args, **kwargs):
return self.app_actions._show_about_dialog(*args, **kwargs)
@ -588,6 +594,9 @@ class PanelDelegates:
def _draw_save_as_project_dialog(self, *args, **kwargs):
return self.dialog_panels._draw_save_as_project_dialog(*args, **kwargs)
def _draw_build_project_dialog(self, *args, **kwargs):
return self.dialog_panels._draw_build_project_dialog(*args, **kwargs)
def _draw_path_browser(self, *args, **kwargs):
return self.dialog_panels._draw_path_browser(*args, **kwargs)