feat(studio,stacking,animations,collections): M16 ModBison Studio, object stacking, model animations, and poly collections for MVP 2
checkpoint / checkpoint (push) Successful in 6m32s
checkpoint / checkpoint (push) Successful in 6m32s
This commit is contained in:
@@ -35,6 +35,27 @@ var _scale_y: SpinBox
|
||||
var _scale_z: SpinBox
|
||||
var _delete_button: Button
|
||||
|
||||
# Animation & Motion fields
|
||||
var _anim_clip_option: OptionButton
|
||||
var _anim_autoplay_check: CheckBox
|
||||
var _anim_loop_check: CheckBox
|
||||
var _anim_speed_spin: SpinBox
|
||||
|
||||
var _motion_type_option: OptionButton
|
||||
var _motion_trigger_option: OptionButton
|
||||
var _motion_rotate_box: VBoxContainer
|
||||
var _motion_speed_spin: SpinBox
|
||||
var _motion_bob_box: VBoxContainer
|
||||
var _motion_bob_amp_spin: SpinBox
|
||||
var _motion_bob_freq_spin: SpinBox
|
||||
var _motion_waypoint_box: VBoxContainer
|
||||
var _motion_wp_speed_spin: SpinBox
|
||||
var _motion_wp_pause_spin: SpinBox
|
||||
var _motion_wp_count_label: Label
|
||||
var _motion_add_wp_btn: Button
|
||||
var _motion_clear_wp_btn: Button
|
||||
var _current_waypoints: Array[Vector3] = []
|
||||
|
||||
# Behavior & Rules fields
|
||||
var _rules_vbox: VBoxContainer
|
||||
var _add_rule_btn: Button
|
||||
@@ -140,9 +161,220 @@ func _build_ui() -> void:
|
||||
_delete_button.pressed.connect(_on_delete_pressed)
|
||||
_fields_box.add_child(_delete_button)
|
||||
|
||||
_setup_animation_and_motion_ui()
|
||||
_setup_behavior_ui()
|
||||
|
||||
|
||||
func _setup_animation_and_motion_ui() -> void:
|
||||
var sep := HSeparator.new()
|
||||
sep.custom_minimum_size = Vector2(0, 16)
|
||||
_fields_box.add_child(sep)
|
||||
|
||||
var header := Label.new()
|
||||
header.text = "Animation & Movement"
|
||||
header.add_theme_font_size_override("font_size", 14)
|
||||
header.add_theme_color_override("font_color", Color(0.4, 0.9, 0.5))
|
||||
_fields_box.add_child(header)
|
||||
|
||||
var desc := Label.new()
|
||||
desc.text = "Configure 3D clip playback and procedural motion."
|
||||
desc.add_theme_font_size_override("font_size", 11)
|
||||
desc.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
||||
_fields_box.add_child(desc)
|
||||
|
||||
# --- Animation Section ---
|
||||
var anim_panel := PanelContainer.new()
|
||||
var anim_vbox := VBoxContainer.new()
|
||||
anim_panel.add_child(anim_vbox)
|
||||
_fields_box.add_child(anim_panel)
|
||||
|
||||
var anim_lbl := Label.new()
|
||||
anim_lbl.text = "Clip Animation"
|
||||
anim_lbl.add_theme_font_size_override("font_size", 12)
|
||||
anim_lbl.add_theme_color_override("font_color", Color(1.0, 0.9, 0.4))
|
||||
anim_vbox.add_child(anim_lbl)
|
||||
|
||||
var clip_row := HBoxContainer.new()
|
||||
var clip_lbl := Label.new()
|
||||
clip_lbl.text = "Clip:"
|
||||
clip_lbl.custom_minimum_size = Vector2(60, 0)
|
||||
clip_row.add_child(clip_lbl)
|
||||
_anim_clip_option = OptionButton.new()
|
||||
_anim_clip_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_anim_clip_option.item_selected.connect(_on_animation_changed)
|
||||
clip_row.add_child(_anim_clip_option)
|
||||
anim_vbox.add_child(clip_row)
|
||||
|
||||
var anim_opts_row := HBoxContainer.new()
|
||||
_anim_autoplay_check = CheckBox.new()
|
||||
_anim_autoplay_check.text = "Autoplay"
|
||||
_anim_autoplay_check.toggled.connect(func(_val): _on_animation_changed(0))
|
||||
anim_opts_row.add_child(_anim_autoplay_check)
|
||||
|
||||
_anim_loop_check = CheckBox.new()
|
||||
_anim_loop_check.text = "Loop"
|
||||
_anim_loop_check.toggled.connect(func(_val): _on_animation_changed(0))
|
||||
anim_opts_row.add_child(_anim_loop_check)
|
||||
anim_vbox.add_child(anim_opts_row)
|
||||
|
||||
var speed_row := HBoxContainer.new()
|
||||
var spd_lbl := Label.new()
|
||||
spd_lbl.text = "Speed:"
|
||||
spd_lbl.custom_minimum_size = Vector2(60, 0)
|
||||
speed_row.add_child(spd_lbl)
|
||||
_anim_speed_spin = SpinBox.new()
|
||||
_anim_speed_spin.min_value = 0.1
|
||||
_anim_speed_spin.max_value = 10.0
|
||||
_anim_speed_spin.step = 0.1
|
||||
_anim_speed_spin.value = 1.0
|
||||
_anim_speed_spin.value_changed.connect(func(_val): _on_animation_changed(0))
|
||||
speed_row.add_child(_anim_speed_spin)
|
||||
anim_vbox.add_child(speed_row)
|
||||
|
||||
# --- Motion Section ---
|
||||
var motion_panel := PanelContainer.new()
|
||||
var motion_vbox := VBoxContainer.new()
|
||||
motion_panel.add_child(motion_vbox)
|
||||
_fields_box.add_child(motion_panel)
|
||||
|
||||
var motion_lbl := Label.new()
|
||||
motion_lbl.text = "Procedural Motion"
|
||||
motion_lbl.add_theme_font_size_override("font_size", 12)
|
||||
motion_lbl.add_theme_color_override("font_color", Color(0.35, 0.85, 1.0))
|
||||
motion_vbox.add_child(motion_lbl)
|
||||
|
||||
var type_row := HBoxContainer.new()
|
||||
var type_lbl := Label.new()
|
||||
type_lbl.text = "Type:"
|
||||
type_lbl.custom_minimum_size = Vector2(60, 0)
|
||||
type_row.add_child(type_lbl)
|
||||
_motion_type_option = OptionButton.new()
|
||||
_motion_type_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_motion_type_option.add_item("None (Static)", 0)
|
||||
_motion_type_option.set_item_metadata(0, "none")
|
||||
_motion_type_option.add_item("Rotate (Spin)", 1)
|
||||
_motion_type_option.set_item_metadata(1, "rotate")
|
||||
_motion_type_option.add_item("Bob / Float", 2)
|
||||
_motion_type_option.set_item_metadata(2, "bob")
|
||||
_motion_type_option.add_item("Waypoint Path", 3)
|
||||
_motion_type_option.set_item_metadata(3, "waypoint")
|
||||
_motion_type_option.item_selected.connect(_on_motion_type_selected)
|
||||
type_row.add_child(_motion_type_option)
|
||||
motion_vbox.add_child(type_row)
|
||||
|
||||
var trig_row := HBoxContainer.new()
|
||||
var trig_lbl := Label.new()
|
||||
trig_lbl.text = "Trigger:"
|
||||
trig_lbl.custom_minimum_size = Vector2(60, 0)
|
||||
trig_row.add_child(trig_lbl)
|
||||
_motion_trigger_option = OptionButton.new()
|
||||
_motion_trigger_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_motion_trigger_option.add_item("Always (Active)", 0)
|
||||
_motion_trigger_option.set_item_metadata(0, "always")
|
||||
_motion_trigger_option.add_item("Proximity (Player near)", 1)
|
||||
_motion_trigger_option.set_item_metadata(1, "proximity")
|
||||
_motion_trigger_option.add_item("Interact (Click)", 2)
|
||||
_motion_trigger_option.set_item_metadata(2, "interact")
|
||||
_motion_trigger_option.add_item("Signal (Script)", 3)
|
||||
_motion_trigger_option.set_item_metadata(3, "signal")
|
||||
_motion_trigger_option.item_selected.connect(func(_idx): _on_motion_param_changed())
|
||||
trig_row.add_child(_motion_trigger_option)
|
||||
motion_vbox.add_child(trig_row)
|
||||
|
||||
# Rotate parameters
|
||||
_motion_rotate_box = VBoxContainer.new()
|
||||
var rot_speed_row := HBoxContainer.new()
|
||||
var rot_spd_lbl := Label.new()
|
||||
rot_spd_lbl.text = "Speed (deg/s):"
|
||||
rot_spd_lbl.custom_minimum_size = Vector2(100, 0)
|
||||
rot_speed_row.add_child(rot_spd_lbl)
|
||||
_motion_speed_spin = SpinBox.new()
|
||||
_motion_speed_spin.min_value = -720.0
|
||||
_motion_speed_spin.max_value = 720.0
|
||||
_motion_speed_spin.value = 45.0
|
||||
_motion_speed_spin.value_changed.connect(func(_v): _on_motion_param_changed())
|
||||
rot_speed_row.add_child(_motion_speed_spin)
|
||||
_motion_rotate_box.add_child(rot_speed_row)
|
||||
motion_vbox.add_child(_motion_rotate_box)
|
||||
|
||||
# Bob parameters
|
||||
_motion_bob_box = VBoxContainer.new()
|
||||
var bob_amp_row := HBoxContainer.new()
|
||||
var bob_amp_lbl := Label.new()
|
||||
bob_amp_lbl.text = "Amplitude:"
|
||||
bob_amp_lbl.custom_minimum_size = Vector2(80, 0)
|
||||
bob_amp_row.add_child(bob_amp_lbl)
|
||||
_motion_bob_amp_spin = SpinBox.new()
|
||||
_motion_bob_amp_spin.min_value = 0.05
|
||||
_motion_bob_amp_spin.max_value = 20.0
|
||||
_motion_bob_amp_spin.step = 0.1
|
||||
_motion_bob_amp_spin.value = 0.5
|
||||
_motion_bob_amp_spin.value_changed.connect(func(_v): _on_motion_param_changed())
|
||||
bob_amp_row.add_child(_motion_bob_amp_spin)
|
||||
_motion_bob_box.add_child(bob_amp_row)
|
||||
|
||||
var bob_freq_row := HBoxContainer.new()
|
||||
var bob_freq_lbl := Label.new()
|
||||
bob_freq_lbl.text = "Frequency:"
|
||||
bob_freq_lbl.custom_minimum_size = Vector2(80, 0)
|
||||
bob_freq_row.add_child(bob_freq_lbl)
|
||||
_motion_bob_freq_spin = SpinBox.new()
|
||||
_motion_bob_freq_spin.min_value = 0.1
|
||||
_motion_bob_freq_spin.max_value = 10.0
|
||||
_motion_bob_freq_spin.step = 0.1
|
||||
_motion_bob_freq_spin.value = 1.0
|
||||
_motion_bob_freq_spin.value_changed.connect(func(_v): _on_motion_param_changed())
|
||||
bob_freq_row.add_child(_motion_bob_freq_spin)
|
||||
_motion_bob_box.add_child(bob_freq_row)
|
||||
motion_vbox.add_child(_motion_bob_box)
|
||||
|
||||
# Waypoint parameters
|
||||
_motion_waypoint_box = VBoxContainer.new()
|
||||
var wp_spd_row := HBoxContainer.new()
|
||||
var wp_spd_lbl := Label.new()
|
||||
wp_spd_lbl.text = "Move Speed:"
|
||||
wp_spd_lbl.custom_minimum_size = Vector2(80, 0)
|
||||
wp_spd_row.add_child(wp_spd_lbl)
|
||||
_motion_wp_speed_spin = SpinBox.new()
|
||||
_motion_wp_speed_spin.min_value = 0.1
|
||||
_motion_wp_speed_spin.max_value = 50.0
|
||||
_motion_wp_speed_spin.value = 2.0
|
||||
_motion_wp_speed_spin.value_changed.connect(func(_v): _on_motion_param_changed())
|
||||
wp_spd_row.add_child(_motion_wp_speed_spin)
|
||||
_motion_waypoint_box.add_child(wp_spd_row)
|
||||
|
||||
var wp_pause_row := HBoxContainer.new()
|
||||
var wp_pause_lbl := Label.new()
|
||||
wp_pause_lbl.text = "Pause (s):"
|
||||
wp_pause_lbl.custom_minimum_size = Vector2(80, 0)
|
||||
wp_pause_row.add_child(wp_pause_lbl)
|
||||
_motion_wp_pause_spin = SpinBox.new()
|
||||
_motion_wp_pause_spin.min_value = 0.0
|
||||
_motion_wp_pause_spin.max_value = 60.0
|
||||
_motion_wp_pause_spin.value = 0.0
|
||||
_motion_wp_pause_spin.value_changed.connect(func(_v): _on_motion_param_changed())
|
||||
wp_pause_row.add_child(_motion_wp_pause_spin)
|
||||
_motion_waypoint_box.add_child(wp_pause_row)
|
||||
|
||||
_motion_wp_count_label = Label.new()
|
||||
_motion_wp_count_label.text = "Waypoints: 0"
|
||||
_motion_waypoint_box.add_child(_motion_wp_count_label)
|
||||
|
||||
var wp_btn_row := HBoxContainer.new()
|
||||
_motion_add_wp_btn = Button.new()
|
||||
_motion_add_wp_btn.text = "+ Add Current Pos"
|
||||
_motion_add_wp_btn.pressed.connect(_on_add_current_pos_waypoint)
|
||||
wp_btn_row.add_child(_motion_add_wp_btn)
|
||||
|
||||
_motion_clear_wp_btn = Button.new()
|
||||
_motion_clear_wp_btn.text = "Clear"
|
||||
_motion_clear_wp_btn.pressed.connect(_on_clear_waypoints)
|
||||
wp_btn_row.add_child(_motion_clear_wp_btn)
|
||||
_motion_waypoint_box.add_child(wp_btn_row)
|
||||
|
||||
motion_vbox.add_child(_motion_waypoint_box)
|
||||
|
||||
|
||||
func _setup_behavior_ui() -> void:
|
||||
var sep := HSeparator.new()
|
||||
sep.custom_minimum_size = Vector2(0, 16)
|
||||
@@ -516,9 +748,141 @@ func show_object(object_id: String) -> void:
|
||||
_scale_z.value = obj.scale.z
|
||||
_updating = false
|
||||
|
||||
_refresh_animation_and_motion()
|
||||
_refresh_rules()
|
||||
|
||||
|
||||
func _refresh_animation_and_motion() -> void:
|
||||
if _core == null or _object_id == "":
|
||||
return
|
||||
var obj := _core.model.find_object(_object_id)
|
||||
if obj == null:
|
||||
return
|
||||
|
||||
_updating = true
|
||||
|
||||
# Refresh Animation
|
||||
_anim_clip_option.clear()
|
||||
_anim_clip_option.add_item("(None)", 0)
|
||||
_anim_clip_option.set_item_metadata(0, "")
|
||||
var avail: Array = obj.properties.get("available_animations", [])
|
||||
var anim_cfg: Dictionary = obj.properties.get("animation", {})
|
||||
var current_clip: String = str(anim_cfg.get("clip", obj.properties.get("active_animation", "")))
|
||||
|
||||
var selected_idx := 0
|
||||
for i in range(avail.size()):
|
||||
var clip_name := str(avail[i])
|
||||
_anim_clip_option.add_item(clip_name, i + 1)
|
||||
_anim_clip_option.set_item_metadata(i + 1, clip_name)
|
||||
if clip_name == current_clip:
|
||||
selected_idx = i + 1
|
||||
_anim_clip_option.selected = selected_idx
|
||||
_anim_autoplay_check.button_pressed = bool(anim_cfg.get("autoplay", true))
|
||||
_anim_loop_check.button_pressed = bool(anim_cfg.get("loop", true))
|
||||
_anim_speed_spin.value = float(anim_cfg.get("speed", 1.0))
|
||||
|
||||
# Refresh Motion
|
||||
var motion_cfg: Dictionary = obj.properties.get("motion", {})
|
||||
var m_type := str(motion_cfg.get("motion_type", "none")).to_lower()
|
||||
match m_type:
|
||||
"rotate": _motion_type_option.selected = 1
|
||||
"bob", "oscillate": _motion_type_option.selected = 2
|
||||
"waypoint", "patrol": _motion_type_option.selected = 3
|
||||
_: _motion_type_option.selected = 0
|
||||
|
||||
var m_trig := str(motion_cfg.get("trigger_mode", "always")).to_lower()
|
||||
match m_trig:
|
||||
"proximity": _motion_trigger_option.selected = 1
|
||||
"interact": _motion_trigger_option.selected = 2
|
||||
"signal": _motion_trigger_option.selected = 3
|
||||
_: _motion_trigger_option.selected = 0
|
||||
|
||||
_motion_speed_spin.value = float(motion_cfg.get("rotation_speed", 45.0))
|
||||
_motion_bob_amp_spin.value = float(motion_cfg.get("bob_amplitude", 0.5))
|
||||
_motion_bob_freq_spin.value = float(motion_cfg.get("bob_frequency", 1.0))
|
||||
_motion_wp_speed_spin.value = float(motion_cfg.get("move_speed", 2.0))
|
||||
_motion_wp_pause_spin.value = float(motion_cfg.get("pause_time", 0.0))
|
||||
|
||||
_current_waypoints.clear()
|
||||
var raw_wps = motion_cfg.get("waypoints", [])
|
||||
if raw_wps is Array:
|
||||
for wp in raw_wps:
|
||||
if wp is Array and wp.size() >= 3:
|
||||
_current_waypoints.append(Vector3(wp[0], wp[1], wp[2]))
|
||||
elif wp is Vector3:
|
||||
_current_waypoints.append(wp)
|
||||
_motion_wp_count_label.text = "Waypoints: %d" % _current_waypoints.size()
|
||||
|
||||
_on_motion_type_selected(_motion_type_option.selected)
|
||||
_updating = false
|
||||
|
||||
|
||||
func _on_animation_changed(_idx: int) -> void:
|
||||
if _updating or _core == null or _object_id == "":
|
||||
return
|
||||
var clip_meta = _anim_clip_option.get_item_metadata(_anim_clip_option.selected)
|
||||
var clip_name := str(clip_meta) if clip_meta != null else ""
|
||||
var anim_dict := {
|
||||
"clip": clip_name,
|
||||
"autoplay": _anim_autoplay_check.button_pressed,
|
||||
"loop": _anim_loop_check.button_pressed,
|
||||
"speed": _anim_speed_spin.value
|
||||
}
|
||||
_core.set_object_property(_object_id, "animation", anim_dict)
|
||||
|
||||
|
||||
func _on_motion_type_selected(idx: int) -> void:
|
||||
var m_type = _motion_type_option.get_item_metadata(idx)
|
||||
if m_type == null:
|
||||
m_type = "none"
|
||||
_motion_rotate_box.visible = (m_type == "rotate")
|
||||
_motion_bob_box.visible = (m_type == "bob")
|
||||
_motion_waypoint_box.visible = (m_type == "waypoint")
|
||||
if not _updating:
|
||||
_on_motion_param_changed()
|
||||
|
||||
|
||||
func _on_motion_param_changed() -> void:
|
||||
if _updating or _core == null or _object_id == "":
|
||||
return
|
||||
var type_meta = _motion_type_option.get_item_metadata(_motion_type_option.selected)
|
||||
var trig_meta = _motion_trigger_option.get_item_metadata(_motion_trigger_option.selected)
|
||||
var m_type := str(type_meta) if type_meta != null else "none"
|
||||
var m_trig := str(trig_meta) if trig_meta != null else "always"
|
||||
|
||||
var wps_ser: Array = []
|
||||
for wp in _current_waypoints:
|
||||
wps_ser.append([wp.x, wp.y, wp.z])
|
||||
|
||||
var motion_dict := {
|
||||
"motion_type": m_type,
|
||||
"trigger_mode": m_trig,
|
||||
"rotation_speed": _motion_speed_spin.value,
|
||||
"bob_amplitude": _motion_bob_amp_spin.value,
|
||||
"bob_frequency": _motion_bob_freq_spin.value,
|
||||
"move_speed": _motion_wp_speed_spin.value,
|
||||
"pause_time": _motion_wp_pause_spin.value,
|
||||
"waypoints": wps_ser
|
||||
}
|
||||
_core.set_object_property(_object_id, "motion", motion_dict)
|
||||
|
||||
|
||||
func _on_add_current_pos_waypoint() -> void:
|
||||
if _core == null or _object_id == "":
|
||||
return
|
||||
var obj := _core.model.find_object(_object_id)
|
||||
if obj != null:
|
||||
_current_waypoints.append(obj.position)
|
||||
_motion_wp_count_label.text = "Waypoints: %d" % _current_waypoints.size()
|
||||
_on_motion_param_changed()
|
||||
|
||||
|
||||
func _on_clear_waypoints() -> void:
|
||||
_current_waypoints.clear()
|
||||
_motion_wp_count_label.text = "Waypoints: 0"
|
||||
_on_motion_param_changed()
|
||||
|
||||
|
||||
func _refresh_rules() -> void:
|
||||
for child in _rules_vbox.get_children():
|
||||
child.queue_free()
|
||||
|
||||
Reference in New Issue
Block a user