feat: add level editor addon
This commit is contained in:
parent
a2213dcce5
commit
fefc3e8a5c
18 changed files with 588 additions and 33 deletions
1
addons/LevelEditor/Draggable.gd
Normal file
1
addons/LevelEditor/Draggable.gd
Normal file
|
|
@ -0,0 +1 @@
|
|||
extends Node
|
||||
1
addons/LevelEditor/Draggable.gd.uid
Normal file
1
addons/LevelEditor/Draggable.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dno7nph2s6ad0
|
||||
32
addons/LevelEditor/LevelEditor.gd
Normal file
32
addons/LevelEditor/LevelEditor.gd
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
# Replace this value with a PascalCase autoload name, as per the GDScript style guide.
|
||||
#const AUTOLOAD_NAME = "LevelEditorAutoload"
|
||||
const ui = preload("res://addons/LevelEditor/wave_maker.tscn")
|
||||
|
||||
var main_panel_instance
|
||||
|
||||
func _enter_tree():
|
||||
main_panel_instance = ui.instantiate()
|
||||
# Add the main panel to the editor's main viewport.
|
||||
EditorInterface.get_editor_main_screen().add_child(main_panel_instance)
|
||||
# Hide the main panel. Very much required.
|
||||
_make_visible(false)
|
||||
|
||||
|
||||
func _has_main_screen():
|
||||
return true
|
||||
|
||||
|
||||
func _make_visible(visible):
|
||||
if main_panel_instance:
|
||||
main_panel_instance.visible = visible
|
||||
|
||||
|
||||
func _get_plugin_name():
|
||||
return "Level Editor"
|
||||
|
||||
|
||||
func _get_plugin_icon():
|
||||
return EditorInterface.get_editor_theme().get_icon("Node", "EditorIcons")
|
||||
1
addons/LevelEditor/LevelEditor.gd.uid
Normal file
1
addons/LevelEditor/LevelEditor.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://ppf3jmjydns0
|
||||
51
addons/LevelEditor/UI/CustomLineEdit.gd
Normal file
51
addons/LevelEditor/UI/CustomLineEdit.gd
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
@tool
|
||||
extends LineEdit
|
||||
|
||||
class_name CustomLineEdit
|
||||
|
||||
enum TYPE { INT, TEXT, FLOAT }
|
||||
|
||||
@export var inputType : TYPE
|
||||
@export var step : float = 1
|
||||
var oldValue : String = ""
|
||||
|
||||
var value:
|
||||
get:
|
||||
return getTypedValue(text)
|
||||
|
||||
func _init() -> void:
|
||||
text_changed.connect(valueUpdated)
|
||||
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
if !has_focus() || event is not InputEventKey || !event.pressed:
|
||||
return
|
||||
|
||||
processKeyInput(event)
|
||||
|
||||
|
||||
func processKeyInput(event : InputEventKey) -> void:
|
||||
if inputType in [TYPE.INT, TYPE.FLOAT]:
|
||||
if event.keycode == KEY_UP:
|
||||
text = str(getTypedValue(str(value + step)))
|
||||
valueUpdated(text)
|
||||
elif event.keycode == KEY_DOWN:
|
||||
text = str(getTypedValue(str(value - step)))
|
||||
valueUpdated(text)
|
||||
|
||||
|
||||
func valueUpdated(newText : String) -> void:
|
||||
valueHasChanged.emit(value)
|
||||
|
||||
|
||||
func setValue(value) -> void:
|
||||
text = str(getTypedValue(str(value)))
|
||||
|
||||
|
||||
func getTypedValue(valueToType: String):
|
||||
match inputType:
|
||||
TYPE.INT: return int(valueToType)
|
||||
TYPE.FLOAT: return float(valueToType)
|
||||
_: return valueToType
|
||||
|
||||
signal valueHasChanged(newValue)
|
||||
1
addons/LevelEditor/UI/CustomLineEdit.gd.uid
Normal file
1
addons/LevelEditor/UI/CustomLineEdit.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bpv75ucqoy446
|
||||
47
addons/LevelEditor/UI/CustomOptionButton.gd
Normal file
47
addons/LevelEditor/UI/CustomOptionButton.gd
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
@tool
|
||||
extends OptionButton
|
||||
class_name CustomOptionButton
|
||||
|
||||
@export_dir var resourcePath : String
|
||||
@export var regexPattern : String
|
||||
@export var reloadOnOpen : bool = false
|
||||
|
||||
var selectedValue : String
|
||||
|
||||
func _ready() -> void:
|
||||
loadData(true)
|
||||
item_selected.connect(itemHasBeenSelected)
|
||||
pressed.connect(loadData)
|
||||
allow_reselect = true
|
||||
|
||||
func loadData(force : bool = false) -> void:
|
||||
if !force && !reloadOnOpen:
|
||||
return
|
||||
|
||||
var regex := RegEx.create_from_string(regexPattern)
|
||||
var dir := DirAccess.open(resourcePath)
|
||||
clear()
|
||||
for file in dir.get_files():
|
||||
var fileMatch := regex.search(file)
|
||||
if fileMatch:
|
||||
add_item(fileMatch.strings[1])
|
||||
if selectedValue == fileMatch.strings[1]:
|
||||
selected = item_count - 1
|
||||
|
||||
if !selectedValue && item_count > 0:
|
||||
selected = 0
|
||||
|
||||
func itemHasBeenSelected(index : int) -> void:
|
||||
if selectedValue != get_item_text(index):
|
||||
selectedValue = get_item_text(index)
|
||||
onValueChanged.emit(selectedValue)
|
||||
|
||||
|
||||
func selectItemByName(name : String) -> void:
|
||||
for i in item_count:
|
||||
if get_item_text(i) == name:
|
||||
selected = i
|
||||
return
|
||||
|
||||
|
||||
signal onValueChanged(value : String)
|
||||
1
addons/LevelEditor/UI/CustomOptionButton.gd.uid
Normal file
1
addons/LevelEditor/UI/CustomOptionButton.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b47p2u458hsn0
|
||||
7
addons/LevelEditor/UI/baseLabel.tres
Normal file
7
addons/LevelEditor/UI/baseLabel.tres
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://s1lfc81j20la"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://dv7ow5e7jj355" path="res://Assets/Fonts/Grandstander/static/Grandstander-Light.ttf" id="1_r6cgw"]
|
||||
|
||||
[resource]
|
||||
font = ExtResource("1_r6cgw")
|
||||
font_size = 24
|
||||
4
addons/LevelEditor/UI/theme.tres
Normal file
4
addons/LevelEditor/UI/theme.tres
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[gd_resource type="Theme" format=3 uid="uid://bvji8e8p2d72y"]
|
||||
|
||||
[resource]
|
||||
default_font_size = 20
|
||||
7
addons/LevelEditor/UI/troopLabel.tres
Normal file
7
addons/LevelEditor/UI/troopLabel.tres
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://bee458c1kc0j7"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://ctmfgwv1dwdyg" path="res://Assets/Fonts/Grandstander/static/Grandstander-BoldItalic.ttf" id="1_kyxue"]
|
||||
|
||||
[resource]
|
||||
font = ExtResource("1_kyxue")
|
||||
font_size = 24
|
||||
7
addons/LevelEditor/UI/waveLabel.tres
Normal file
7
addons/LevelEditor/UI/waveLabel.tres
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[gd_resource type="LabelSettings" load_steps=2 format=3 uid="uid://cafoo04y1t31t"]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://byqqml5g6dwil" path="res://Assets/Fonts/Grandstander/static/Grandstander-BlackItalic.ttf" id="1_m52f8"]
|
||||
|
||||
[resource]
|
||||
font = ExtResource("1_m52f8")
|
||||
font_size = 32
|
||||
243
addons/LevelEditor/WaveMaker.gd
Normal file
243
addons/LevelEditor/WaveMaker.gd
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
@tool
|
||||
extends Control
|
||||
|
||||
const LEVEL_PATH : String = "res://Levels"
|
||||
const LEVEL_NAME_PATERN : String = "level_{id}.tres"
|
||||
const LEVEL_NAME_PATH_PATERN : String = LEVEL_PATH + "/{name}.tres"
|
||||
|
||||
const ENEMY_PATH : String = "res://enemies"
|
||||
const ENEMY_REGEX_PATERN : String = "(enemy.*)\\.tscn"
|
||||
|
||||
const BASE_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/baseLabel.tres")
|
||||
const WAVE_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/waveLabel.tres")
|
||||
const TROOP_LABEL_SETTINGS = preload("res://addons/LevelEditor/UI/troopLabel.tres")
|
||||
|
||||
const space_multiplicator : int = 10
|
||||
enum DIRECTION { UP, DOWN, TOP, LEFT, VERTICAL, HORIZONTAL}
|
||||
|
||||
|
||||
@onready var autoLaunchLevel := $VBoxContainer2/ButtonContainer2/AutoLaunchLevel
|
||||
@onready var waitForKill := $VBoxContainer2/ButtonContainer4/WaitForKill
|
||||
@onready var levelSelect := $VBoxContainer2/HBoxContainer/LevelSelect
|
||||
@onready var waveTabContainer := $VBoxContainer2/ScrollContainer/WaveContainer
|
||||
@onready var waveTimerInput := $VBoxContainer2/ButtonContainer3/WaveTimer
|
||||
var level : Level
|
||||
var currentWave : int = -1
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if !has_focus() || event is not InputEventKey || !event.pressed:
|
||||
return
|
||||
|
||||
if event.keycode == KEY_RIGHT && waveTabContainer.get_tab_count() > waveTabContainer.current_tab:
|
||||
waveTabContainer.current_tab += 1
|
||||
elif event.keycode == KEY_LEFT && waveTabContainer.current_tab > 0:
|
||||
waveTabContainer.current_tab -= 1
|
||||
|
||||
|
||||
func buildTree() -> void:
|
||||
if !level:
|
||||
return
|
||||
|
||||
autoLaunchLevel.button_pressed = level.auto_start
|
||||
for i in level.waves.size():
|
||||
var troopContainer := VBoxContainer.new()
|
||||
waitForKill.button_pressed = level.waves[i].wait_for_enemy_kills
|
||||
buildWave(level.waves[i], troopContainer)
|
||||
waveTabContainer.add_child(troopContainer)
|
||||
waveTabContainer.set_tab_title(i, "Vague N°" + str(i + 1))
|
||||
|
||||
|
||||
func buildWave(wave : Wave, troopContainer : VBoxContainer) -> void:
|
||||
for i in wave.troops.size():
|
||||
var troop : Troop = wave.troops[i]
|
||||
|
||||
if i > 0 && !troopContainer.get_child(troopContainer.get_child_count() - 1).has_meta("troop_group") \
|
||||
|| troop.spawn_delay > 0:
|
||||
troopContainer.add_child(HSeparator.new())
|
||||
|
||||
if troop.spawn_delay:
|
||||
var timeSeparator := buildInputLabel(
|
||||
func(newValue):
|
||||
troop.spawn_delay = newValue
|
||||
if newValue == 0:
|
||||
cleanAndBuildMenu(),
|
||||
troop.spawn_delay,
|
||||
CustomLineEdit.TYPE.FLOAT,
|
||||
"sec."
|
||||
)
|
||||
troopContainer.add_child(timeSeparator)
|
||||
|
||||
var nodeToAppend : BoxContainer = troopContainer
|
||||
if i < wave.troops.size() -1 && wave.troops[i + 1].spawn_delay == 0:
|
||||
if i == 0 || !troopContainer.get_child(troopContainer.get_child_count() - 1).has_meta("troop_group"):
|
||||
nodeToAppend = HBoxContainer.new()
|
||||
nodeToAppend.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
nodeToAppend.set_meta("troop_group", true)
|
||||
troopContainer.add_child(nodeToAppend)
|
||||
|
||||
if i > 0 && troop.spawn_delay == 0:
|
||||
nodeToAppend = troopContainer.get_child(troopContainer.get_child_count() - 1)
|
||||
nodeToAppend.add_child(VSeparator.new())
|
||||
|
||||
var ennemyContainer := VBoxContainer.new()
|
||||
ennemyContainer.add_child(createSection("Troop N°" + str(i + 1), removeTroop.bind(troop, wave), TROOP_LABEL_SETTINGS))
|
||||
|
||||
if troop.spawn_delay == 0:
|
||||
var button := Button.new()
|
||||
button.text = "Séparer"
|
||||
button.pressed.connect(
|
||||
func():
|
||||
troop.spawn_delay = 1
|
||||
cleanAndBuildMenu()
|
||||
)
|
||||
ennemyContainer.add_child(button)
|
||||
|
||||
buildTroop(troop, ennemyContainer)
|
||||
nodeToAppend.add_child(ennemyContainer)
|
||||
|
||||
var addTroopBtn := Button.new()
|
||||
addTroopBtn.text = "Ajouter une troupe"
|
||||
addTroopBtn.pressed.connect(addTroop.bind(wave))
|
||||
troopContainer.add_child(addTroopBtn)
|
||||
|
||||
|
||||
|
||||
func buildTroop(troop : Troop, ennemyContainer : VBoxContainer) -> void:
|
||||
var qtyEdit := buildInputLabel(
|
||||
func(newValue): troop.number_to_spawn = newValue,
|
||||
troop.number_to_spawn,
|
||||
CustomLineEdit.TYPE.INT,
|
||||
"x"
|
||||
)
|
||||
|
||||
var enemySelector := CustomOptionButton.new()
|
||||
enemySelector.resourcePath = ENEMY_PATH
|
||||
enemySelector.regexPattern = ENEMY_REGEX_PATERN
|
||||
var regex = RegEx.create_from_string(ENEMY_REGEX_PATERN)
|
||||
enemySelector.onValueChanged.connect(func(enemyFileName): addEnemy(troop, enemyFileName))
|
||||
if troop.enemy:
|
||||
enemySelector.selectItemByName(regex.search(troop.enemy.resource_path).strings[0])
|
||||
|
||||
qtyEdit.add_child(enemySelector)
|
||||
ennemyContainer.add_child(qtyEdit)
|
||||
|
||||
|
||||
func buildInputLabel(updateCallback : Callable, delay : float, type : CustomLineEdit.TYPE, text : String = "") -> HBoxContainer:
|
||||
var container := HBoxContainer.new()
|
||||
container.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
||||
container.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
|
||||
var timeEdit := CustomLineEdit.new()
|
||||
timeEdit.inputType = type
|
||||
timeEdit.setValue(delay)
|
||||
timeEdit.valueHasChanged.connect(updateCallback)
|
||||
container.add_child(timeEdit)
|
||||
|
||||
if text:
|
||||
var label := Label.new()
|
||||
label.text = text
|
||||
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
label.label_settings = BASE_LABEL_SETTINGS
|
||||
container.add_child(label)
|
||||
|
||||
return container
|
||||
|
||||
func createSection(sectionName : String, BtnCallback : Callable, settings : LabelSettings = BASE_LABEL_SETTINGS) -> HSplitContainer :
|
||||
var container := HSplitContainer.new()
|
||||
|
||||
var label := Label.new()
|
||||
label.text = sectionName
|
||||
label.label_settings = settings
|
||||
|
||||
var button := Button.new()
|
||||
button.text = "Supprimer"
|
||||
button.pressed.connect(BtnCallback)
|
||||
|
||||
container.add_child(label)
|
||||
container.add_child(button)
|
||||
|
||||
return container
|
||||
|
||||
|
||||
func cleanMenu() -> void:
|
||||
if waveTabContainer.get_child_count() > 0:
|
||||
for child in waveTabContainer.get_children():
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func removeWave() -> void:
|
||||
level.waves.remove_at(currentWave)
|
||||
waveTabContainer.get_child(currentWave).queue_free()
|
||||
|
||||
|
||||
func addWave() -> void:
|
||||
level.waves.append(Wave.new())
|
||||
cleanAndBuildMenu()
|
||||
|
||||
|
||||
func changeWaveOrder(newPos : int) -> void:
|
||||
var newWaveOrder : Array[Wave]
|
||||
var waveToMove = level.waves[currentWave]
|
||||
for i in level.waves.size():
|
||||
if i == 0 && newPos == 0:
|
||||
newWaveOrder.append(waveToMove)
|
||||
if i != currentWave:
|
||||
newWaveOrder.append(level.waves[i])
|
||||
if i == newPos && newPos != 0:
|
||||
newWaveOrder.append(waveToMove)
|
||||
currentWave = newPos
|
||||
level.waves = newWaveOrder
|
||||
|
||||
|
||||
|
||||
func addTroop(toWave : Wave) -> void:
|
||||
toWave.troops.append(Troop.new())
|
||||
cleanAndBuildMenu()
|
||||
|
||||
|
||||
func addEnemy(toTroop : Troop, enemyResourcePath : String) -> void:
|
||||
var enemy = load(enemyResourcePath)
|
||||
toTroop.enemy = enemy
|
||||
|
||||
|
||||
func removeTroop(troop : Troop, fromWave : Wave) -> void:
|
||||
fromWave.troops.erase(troop)
|
||||
cleanAndBuildMenu()
|
||||
|
||||
|
||||
func selectLevel(levelName : String) -> void :
|
||||
level = load(LEVEL_NAME_PATH_PATERN.format([["name", levelName]]))
|
||||
|
||||
|
||||
func changeWaveCooldown(duration : float) -> void:
|
||||
level.waves[currentWave].wait_time_before_launch_wave = duration
|
||||
|
||||
|
||||
func tabFocusHaschanged(idx : int) -> void:
|
||||
waveTimerInput.setValue(level.waves[idx].wait_time_before_launch_wave)
|
||||
currentWave = idx
|
||||
|
||||
|
||||
func cleanAndBuildMenu() -> void:
|
||||
cleanMenu()
|
||||
buildTree()
|
||||
|
||||
|
||||
func _on_auto_launch_wave_toggled(toggled_on: bool) -> void:
|
||||
level.auto_start = toggled_on
|
||||
|
||||
|
||||
func _on_wait_for_kill_toggled(toggled_on: bool) -> void:
|
||||
level.waves[currentWave].wait_for_enemy_kills = toggled_on
|
||||
|
||||
|
||||
func _on_new_level_pressed() -> void:
|
||||
level = Level.new()
|
||||
levelSelect.add_item(LEVEL_NAME_PATERN.format([["id", levelSelect.item_count + 1]]))
|
||||
levelSelect.select(levelSelect.item_count - 1)
|
||||
_on_save_pressed()
|
||||
|
||||
|
||||
func _on_save_pressed() -> void:
|
||||
ResourceSaver.save(level, LEVEL_PATH + "/" + levelSelect.selectedValue, ResourceSaver.FLAG_BUNDLE_RESOURCES)
|
||||
1
addons/LevelEditor/WaveMaker.gd.uid
Normal file
1
addons/LevelEditor/WaveMaker.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://27y0jliv6ckx
|
||||
7
addons/LevelEditor/plugin.cfg
Normal file
7
addons/LevelEditor/plugin.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="LevelEditor"
|
||||
description=""
|
||||
author="Varylios"
|
||||
version="0.3"
|
||||
script="LevelEditor.gd"
|
||||
144
addons/LevelEditor/wave_maker.tscn
Normal file
144
addons/LevelEditor/wave_maker.tscn
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://dh24t8804isms"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://27y0jliv6ckx" path="res://addons/LevelEditor/WaveMaker.gd" id="1_usfft"]
|
||||
[ext_resource type="Script" uid="uid://b47p2u458hsn0" path="res://addons/LevelEditor/UI/CustomOptionButton.gd" id="2_xjxpq"]
|
||||
[ext_resource type="Script" uid="uid://bpv75ucqoy446" path="res://addons/LevelEditor/UI/CustomLineEdit.gd" id="3_qw7ts"]
|
||||
|
||||
[node name="Menu" type="HBoxContainer"]
|
||||
offset_right = 885.0
|
||||
offset_bottom = 574.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_usfft")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||
custom_minimum_size = Vector2(20, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer2" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer2"]
|
||||
custom_minimum_size = Vector2(0, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LevelSelect" type="OptionButton" parent="VBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
selected = 0
|
||||
allow_reselect = true
|
||||
item_count = 2
|
||||
popup/item_0/text = "level_1"
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "level_2"
|
||||
popup/item_1/id = 1
|
||||
script = ExtResource("2_xjxpq")
|
||||
resourcePath = "res://Levels"
|
||||
regexPattern = "(level_.*)\\.tres"
|
||||
reloadOnOpen = true
|
||||
metadata/_custom_type_script = "uid://b47p2u458hsn0"
|
||||
|
||||
[node name="NewLevel" type="Button" parent="VBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Nouveau Niveau"
|
||||
|
||||
[node name="Show" type="Button" parent="VBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Afficher"
|
||||
|
||||
[node name="Clean" type="Button" parent="VBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Effacer"
|
||||
|
||||
[node name="Save" type="Button" parent="VBoxContainer2/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Sauvegarder"
|
||||
|
||||
[node name="Test" type="Button" parent="VBoxContainer2/HBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "TESTER !!!!"
|
||||
|
||||
[node name="ButtonContainer" type="HBoxContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Add wave" type="Button" parent="VBoxContainer2/ButtonContainer"]
|
||||
layout_mode = 2
|
||||
text = "Ajouter une vague"
|
||||
|
||||
[node name="ButtonContainer2" type="HBoxContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="AutoLaunchLevel" type="CheckButton" parent="VBoxContainer2/ButtonContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer2/ButtonContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Lancer le niveau auto. "
|
||||
|
||||
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer2/ButtonContainer2"]
|
||||
custom_minimum_size = Vector2(20, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="RemoveWave" type="Button" parent="VBoxContainer2/ButtonContainer2"]
|
||||
layout_mode = 2
|
||||
text = "Suprimer la vague"
|
||||
|
||||
[node name="ButtonContainer4" type="HBoxContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WaitForKill" type="CheckButton" parent="VBoxContainer2/ButtonContainer4"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer2/ButtonContainer4"]
|
||||
layout_mode = 2
|
||||
text = "Attendre la mort des enemies pour lancer la vague suivante"
|
||||
|
||||
[node name="ButtonContainer3" type="HBoxContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="WaveTimer" type="LineEdit" parent="VBoxContainer2/ButtonContainer3"]
|
||||
custom_minimum_size = Vector2(55, 55)
|
||||
layout_mode = 2
|
||||
script = ExtResource("3_qw7ts")
|
||||
inputType = 2
|
||||
step = 0.2
|
||||
metadata/_custom_type_script = "uid://bpv75ucqoy446"
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer2/ButtonContainer3"]
|
||||
custom_minimum_size = Vector2(10, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VBoxContainer2/ButtonContainer3"]
|
||||
layout_mode = 2
|
||||
text = "Timer avant lancement de la vague"
|
||||
|
||||
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer2"]
|
||||
custom_minimum_size = Vector2(0, 30)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer2"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="WaveContainer" type="TabContainer" parent="VBoxContainer2/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
clip_tabs = false
|
||||
drag_to_rearrange_enabled = true
|
||||
|
||||
[connection signal="onValueChanged" from="VBoxContainer2/HBoxContainer/LevelSelect" to="." method="selectLevel"]
|
||||
[connection signal="pressed" from="VBoxContainer2/HBoxContainer/NewLevel" to="." method="_on_new_level_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer2/HBoxContainer/Show" to="." method="cleanAndBuildMenu"]
|
||||
[connection signal="pressed" from="VBoxContainer2/HBoxContainer/Clean" to="." method="cleanMenu"]
|
||||
[connection signal="pressed" from="VBoxContainer2/HBoxContainer/Save" to="." method="_on_save_pressed"]
|
||||
[connection signal="pressed" from="VBoxContainer2/ButtonContainer/Add wave" to="." method="addWave"]
|
||||
[connection signal="toggled" from="VBoxContainer2/ButtonContainer2/AutoLaunchLevel" to="." method="_on_auto_launch_wave_toggled"]
|
||||
[connection signal="pressed" from="VBoxContainer2/ButtonContainer2/RemoveWave" to="." method="removeWave"]
|
||||
[connection signal="toggled" from="VBoxContainer2/ButtonContainer4/WaitForKill" to="." method="_on_wait_for_kill_toggled"]
|
||||
[connection signal="valueHasChanged" from="VBoxContainer2/ButtonContainer3/WaveTimer" to="." method="changeWaveCooldown"]
|
||||
[connection signal="active_tab_rearranged" from="VBoxContainer2/ScrollContainer/WaveContainer" to="." method="changeWaveOrder"]
|
||||
[connection signal="tab_changed" from="VBoxContainer2/ScrollContainer/WaveContainer" to="." method="tabFocusHaschanged"]
|
||||
|
|
@ -17,7 +17,7 @@ const DEFAULT_EMISSION_DURATION: float = 1.0
|
|||
## TODO: This could be a user setting
|
||||
const DEFAULT_CONNECTION_OPACITY: float = 0.3
|
||||
|
||||
## This enum is used to set up the graph node's ports
|
||||
## This enum is used to set up the graph node's ports
|
||||
## in a way that provides more legibility in the code
|
||||
enum Direction {LEFT, RIGHT}
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ var block_new_inspections: bool = false
|
|||
## If true, all incoming signal emissions will be drawn and won't fade out
|
||||
var keep_emissions: bool = false
|
||||
|
||||
## Multiplier that increases or decreases emission drawing speed
|
||||
## Multiplier that increases or decreases emission drawing speed
|
||||
## Acquired from slider in scene
|
||||
var emission_speed_multiplier: float = 1.0
|
||||
|
||||
|
|
@ -61,10 +61,10 @@ var settings: Dictionary = {
|
|||
}
|
||||
|
||||
# Scene references
|
||||
@export var graph_edit: GraphEdit
|
||||
@export var graph_edit: GraphEdit
|
||||
@export var logger_button: Button
|
||||
@export var node_path_line_edit: LineEdit
|
||||
@export var refresh_button: Button
|
||||
@export var node_path_line_edit: LineEdit
|
||||
@export var refresh_button: Button
|
||||
@export var options_button: MenuButton
|
||||
@onready var options_popup: PopupMenu = options_button.get_popup()
|
||||
@export var clear_button: Button
|
||||
|
|
@ -96,7 +96,7 @@ func _ready() -> void:
|
|||
graph_edit.get_menu_hbox().hide()
|
||||
#graph_edit.get_menu_hbox().hide()
|
||||
repo_button.icon = EditorInterface.get_base_control().get_theme_icon("ExternalLink", "EditorIcons")
|
||||
|
||||
|
||||
@onready var repo_button: Button = $EditorPanel/MainButtonsContainer/HBoxContainer2/RepoButton
|
||||
|
||||
@onready var panel_container: PanelContainer = $EditorPanel/PanelContainer
|
||||
|
|
@ -143,13 +143,13 @@ func stop_session():
|
|||
func assign_node_path(target_node: NodePath):
|
||||
# If locked button is toggled, don't change the current node
|
||||
if block_new_inspections: return
|
||||
|
||||
|
||||
# If incoming node is invalid, disable refreshing to avoid null nodes
|
||||
refresh_button.disabled = target_node.is_empty()
|
||||
|
||||
|
||||
# Assign incoming node as the current one
|
||||
current_node = target_node
|
||||
|
||||
|
||||
# Update line edit
|
||||
node_path_line_edit.text = current_node
|
||||
node_path_line_edit.caret_column = node_path_line_edit.text.length()
|
||||
|
|
@ -167,7 +167,7 @@ func clear_graph():
|
|||
clean_connection_activity()
|
||||
# Frees child nodes
|
||||
for child: Node in graph_edit.get_children():
|
||||
# This seems to be necessary as per Godot 4.3
|
||||
# This seems to be necessary as per Godot 4.3
|
||||
# because this child, despite being internal,
|
||||
# is iterated in get_children() and if it is
|
||||
# destroyed, the editor crashed
|
||||
|
|
@ -187,20 +187,20 @@ func clear_graph():
|
|||
func draw_node_data(data: Array):
|
||||
# If lock button toggled on, don't draw incoming data
|
||||
if block_new_inspections: return
|
||||
|
||||
|
||||
# Clear graph to avoid drawing over old data
|
||||
clear_graph()
|
||||
logger.clear()
|
||||
|
||||
|
||||
# This line is super important to avoid random rendering errors
|
||||
# It seems we need to give a small breathing room for the graph edit
|
||||
# to fully cleanup, otherwise, artifacts from a previously rendered
|
||||
# graph edit may appear and mess up the new drawing
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
|
||||
|
||||
# Retrieve the targeted node from the data array, which is always index 0
|
||||
var target_node_name = data[0]
|
||||
|
||||
|
||||
# Handle root node inspection edge case
|
||||
if target_node_name == "Root":
|
||||
warning_text.show()
|
||||
|
|
@ -211,19 +211,19 @@ func draw_node_data(data: Array):
|
|||
|
||||
# Retrieve the targeted node signal data, which is always index 1
|
||||
var target_node_signal_data: Array = data[1]
|
||||
|
||||
|
||||
# Create main node from which connections will be created
|
||||
# and add it to the graph
|
||||
var target_node: SignalLensGraphNode = create_node(target_node_name, "(Signals)")
|
||||
graph_edit.add_child(target_node)
|
||||
|
||||
|
||||
var current_signal_index = 0
|
||||
|
||||
|
||||
# Start iterating signal by signal
|
||||
for signal_data in target_node_signal_data:
|
||||
# Check signal connections and skip not connected signals (based on settings)
|
||||
if settings[Options.HIDE_SIGNALS_WITHOUT_CONNECTIONS] and signal_data["callables"].size() == 0: continue
|
||||
|
||||
|
||||
# Check signal connections and skip if signal is built-in (based on settings)
|
||||
if settings[Options.HIDE_BUILT_IN_SIGNALS]:
|
||||
var class_signals: Array = []
|
||||
|
|
@ -231,13 +231,13 @@ func draw_node_data(data: Array):
|
|||
class_signals.append(class_signal["name"])
|
||||
if signal_data["signal"] in class_signals:
|
||||
continue
|
||||
|
||||
|
||||
# Get the color based on the index so we can have the rainbow vibes
|
||||
var slot_color = get_slot_color(current_signal_index, target_node_signal_data.size())
|
||||
|
||||
|
||||
# Create the slot button with the signal's name
|
||||
create_button_slot(signal_data["signal"], target_node, Direction.RIGHT, slot_color)
|
||||
|
||||
|
||||
# Start iterating each callable in the signal
|
||||
var callables_for_current_signal = signal_data["callables"]
|
||||
for callable_index in range(callables_for_current_signal.size()):
|
||||
|
|
@ -306,8 +306,8 @@ func create_button_slot(button_text: String, parent_node: GraphNode, slot_direct
|
|||
parent_node.set_slot(signal_button.get_index(), slot_direction == Direction.LEFT, 0, slot_color, slot_direction == Direction.RIGHT, 0, slot_color)
|
||||
|
||||
func get_slot_color(slot_index, signal_amount) -> Color:
|
||||
var hue = float(slot_index) / float(signal_amount)
|
||||
return Color.from_hsv(hue, 1.0, 0.5, connection_opacity)
|
||||
var hue = float(slot_index) / float(signal_amount)
|
||||
return Color.from_hsv(hue, 1.0, 0.5, connection_opacity)
|
||||
|
||||
func clean_connection_activity():
|
||||
for connection in graph_edit.get_connection_list():
|
||||
|
|
@ -331,13 +331,13 @@ func draw_signal_emission(data: Array):
|
|||
|
||||
func pulse_connection(connection: Dictionary) -> void:
|
||||
if connection not in pulsing_connections: pulsing_connections.append(connection)
|
||||
|
||||
|
||||
var from_node = connection["from_node"]
|
||||
var from_port = connection["from_port"]
|
||||
var to_node = connection["to_node"]
|
||||
var to_port = connection["to_port"]
|
||||
|
||||
if keep_emissions:
|
||||
|
||||
if keep_emissions:
|
||||
graph_edit.set_connection_activity(from_node, from_port, to_node, to_port, 1.0)
|
||||
else:
|
||||
fade_out_connection(connection)
|
||||
|
|
@ -349,11 +349,11 @@ func fade_out_connection(connection: Dictionary):
|
|||
var from_port = connection["from_port"]
|
||||
var to_node = connection["to_node"]
|
||||
var to_port = connection["to_port"]
|
||||
|
||||
|
||||
tween.tween_method(
|
||||
func(value): graph_edit.set_connection_activity(from_node, from_port, to_node, to_port, value), 1.0, 0.0, fade_out_duration
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN)
|
||||
|
||||
|
||||
tween.tween_callback(func(): pulsing_connections.erase(connection))
|
||||
|
||||
func get_port_index_from_signal_name(signal_name: String):
|
||||
|
|
@ -407,7 +407,7 @@ func _resize_panel(new_size: float):
|
|||
func _can_resize_panel() -> bool:
|
||||
# If user wants to resize panel on open
|
||||
if not ProjectSettings.get_setting("addons/Signal Lens/resize_panel_on_open"): return false
|
||||
|
||||
|
||||
# If editor dock reference has been acquired
|
||||
if not _editor_dock: return false
|
||||
return true
|
||||
|
|
@ -417,7 +417,7 @@ func _on_visibility_changed() -> void:
|
|||
# Only resize bottom panel if both visible and visible in editor
|
||||
if visible and is_visible_in_tree():
|
||||
_resize_panel(-ProjectSettings.get_setting("addons/Signal Lens/height_to_resize_to"))
|
||||
else:
|
||||
else:
|
||||
_resize_panel(_original_panel_size)
|
||||
|
||||
func _open_project_settings():
|
||||
|
|
@ -517,12 +517,12 @@ func _on_keep_emissions_checkbox_toggled(toggled_on: bool) -> void:
|
|||
|
||||
func _on_logger_button_toggled(toggled_on: bool) -> void:
|
||||
logger.visible = toggled_on
|
||||
|
||||
|
||||
func _on_options_index_pressed(option_index: int) -> void:
|
||||
if options_popup.is_item_checkable(option_index):
|
||||
settings[option_index] = not options_popup.is_item_checked(option_index) # Change state
|
||||
options_popup.set_item_checked(option_index, settings[option_index]) # Apply state
|
||||
|
||||
|
||||
if option_index in [Options.HIDE_SIGNALS_WITHOUT_CONNECTIONS, Options.HIDE_BUILT_IN_SIGNALS]:
|
||||
refresh_button.pressed.emit()
|
||||
elif option_index == Options.SHOW_GRAPH_TOOLBAR:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ window/size/viewport_height=1080
|
|||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/signal_lens/plugin.cfg")
|
||||
enabled=PackedStringArray("res://addons/LevelEditor/plugin.cfg", "res://addons/signal_lens/plugin.cfg")
|
||||
|
||||
[file_customization]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue