Moved Godot version to own repo for clarity
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Peter DV
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,23 +0,0 @@
|
||||||
## Contributing to TODO Manager
|
|
||||||
Firstly, thank you for being interested in contributing to the Godot TODO Manager plugin!
|
|
||||||
TODO Manager has benefitted greatly from enthusiastic users who have suggested new features, noticed bugs, and contributed code to the plugin.
|
|
||||||
|
|
||||||
### Code Style Guide
|
|
||||||
For the sake of clarity, TODO Manager takes advantage of GDScripts optional static typing in most circumstances.
|
|
||||||
In particular, when declaring variables use colons to infer the type where possible:
|
|
||||||
|
|
||||||
`todo := "#TODO"`
|
|
||||||
|
|
||||||
If the type is not obvious then explicit typing is desirable:
|
|
||||||
|
|
||||||
`items : PoolStringArray = todo.split()`
|
|
||||||
|
|
||||||
Typed arguments and return values for functions are required:
|
|
||||||
```
|
|
||||||
func example(name: String, amount: int) -> Array:
|
|
||||||
# code
|
|
||||||
return array_of_names
|
|
||||||
```
|
|
||||||
|
|
||||||
For more info on static typing in Godot please refer to the documentation.
|
|
||||||
https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/static_typing.html
|
|
|
@ -1,17 +0,0 @@
|
||||||
@tool
|
|
||||||
extends HBoxContainer
|
|
||||||
|
|
||||||
var colour : Color
|
|
||||||
var title : String:
|
|
||||||
set = set_title
|
|
||||||
var index : int
|
|
||||||
|
|
||||||
@onready var colour_picker := $TODOColourPickerButton
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
$TODOColourPickerButton.color = colour
|
|
||||||
$Label.text = title
|
|
||||||
|
|
||||||
func set_title(value: String) -> void:
|
|
||||||
title = value
|
|
||||||
$Label.text = value
|
|
|
@ -1,44 +0,0 @@
|
||||||
@tool
|
|
||||||
extends Panel
|
|
||||||
|
|
||||||
signal tree_built # used for debugging
|
|
||||||
|
|
||||||
const Todo := preload("res://addons/Todo_Manager/todo_class.gd")
|
|
||||||
const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd")
|
|
||||||
|
|
||||||
var _sort_alphabetical := true
|
|
||||||
|
|
||||||
@onready var tree := $Tree as Tree
|
|
||||||
|
|
||||||
func build_tree(todo_item : TodoItem, patterns : Array, cased_patterns : Array[String]) -> void:
|
|
||||||
tree.clear()
|
|
||||||
var root := tree.create_item()
|
|
||||||
root.set_text(0, "Scripts")
|
|
||||||
var script := tree.create_item(root)
|
|
||||||
script.set_text(0, todo_item.get_short_path() + " -------")
|
|
||||||
script.set_metadata(0, todo_item)
|
|
||||||
for todo in todo_item.todos:
|
|
||||||
var item := tree.create_item(script)
|
|
||||||
var content_header : String = todo.content
|
|
||||||
if "\n" in todo.content:
|
|
||||||
content_header = content_header.split("\n")[0] + "..."
|
|
||||||
item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_"))
|
|
||||||
item.set_tooltip_text(0, todo.content)
|
|
||||||
item.set_metadata(0, todo)
|
|
||||||
for i in range(0, len(cased_patterns)):
|
|
||||||
if cased_patterns[i] == todo.pattern:
|
|
||||||
item.set_custom_color(0, patterns[i][1])
|
|
||||||
emit_signal("tree_built")
|
|
||||||
|
|
||||||
|
|
||||||
func sort_alphabetical(a, b) -> bool:
|
|
||||||
if a.script_path > b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
|
|
||||||
func sort_backwards(a, b) -> bool:
|
|
||||||
if a.script_path < b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
|
@ -1,297 +0,0 @@
|
||||||
@tool
|
|
||||||
extends Control
|
|
||||||
|
|
||||||
#signal tree_built # used for debugging
|
|
||||||
enum { CASE_INSENSITIVE, CASE_SENSITIVE }
|
|
||||||
|
|
||||||
const Project := preload("res://addons/Todo_Manager/Project.gd")
|
|
||||||
const Current := preload("res://addons/Todo_Manager/Current.gd")
|
|
||||||
|
|
||||||
const Todo := preload("res://addons/Todo_Manager/todo_class.gd")
|
|
||||||
const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd")
|
|
||||||
const ColourPicker := preload("res://addons/Todo_Manager/UI/ColourPicker.tscn")
|
|
||||||
const Pattern := preload("res://addons/Todo_Manager/UI/Pattern.tscn")
|
|
||||||
const DEFAULT_PATTERNS := [["\\bTODO\\b", Color("96f1ad"), CASE_INSENSITIVE], ["\\bHACK\\b", Color("d5bc70"), CASE_INSENSITIVE], ["\\bFIXME\\b", Color("d57070"), CASE_INSENSITIVE]]
|
|
||||||
const DEFAULT_SCRIPT_COLOUR := Color("ccced3")
|
|
||||||
const DEFAULT_SCRIPT_NAME := false
|
|
||||||
const DEFAULT_SORT := true
|
|
||||||
|
|
||||||
var plugin : EditorPlugin
|
|
||||||
|
|
||||||
var todo_items : Array
|
|
||||||
|
|
||||||
var script_colour := Color("ccced3")
|
|
||||||
var ignore_paths : Array[String] = []
|
|
||||||
var full_path := false
|
|
||||||
var auto_refresh := true
|
|
||||||
var builtin_enabled := false
|
|
||||||
var _sort_alphabetical := true
|
|
||||||
|
|
||||||
var patterns := [["\\bTODO\\b", Color("96f1ad"), CASE_INSENSITIVE], ["\\bHACK\\b", Color("d5bc70"), CASE_INSENSITIVE], ["\\bFIXME\\b", Color("d57070"), CASE_INSENSITIVE]]
|
|
||||||
|
|
||||||
|
|
||||||
@onready var tabs := $VBoxContainer/TabContainer as TabContainer
|
|
||||||
@onready var project := $VBoxContainer/TabContainer/Project as Project
|
|
||||||
@onready var current := $VBoxContainer/TabContainer/Current as Current
|
|
||||||
@onready var project_tree := $VBoxContainer/TabContainer/Project/Tree as Tree
|
|
||||||
@onready var current_tree := $VBoxContainer/TabContainer/Current/Tree as Tree
|
|
||||||
@onready var settings_panel := $VBoxContainer/TabContainer/Settings as Panel
|
|
||||||
@onready var colours_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3/Colours as VBoxContainer
|
|
||||||
@onready var pattern_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns as VBoxContainer
|
|
||||||
@onready var ignore_textbox := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths/TextEdit as LineEdit
|
|
||||||
@onready var auto_refresh_button := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/RefreshCheckButton as CheckButton
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
load_config()
|
|
||||||
populate_settings()
|
|
||||||
|
|
||||||
|
|
||||||
func build_tree() -> void:
|
|
||||||
if tabs:
|
|
||||||
match tabs.current_tab:
|
|
||||||
0:
|
|
||||||
project.build_tree(todo_items, ignore_paths, patterns, plugin.cased_patterns, _sort_alphabetical, full_path)
|
|
||||||
create_config_file()
|
|
||||||
1:
|
|
||||||
current.build_tree(get_active_script(), patterns, plugin.cased_patterns)
|
|
||||||
create_config_file()
|
|
||||||
2:
|
|
||||||
pass
|
|
||||||
_:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func get_active_script() -> TodoItem:
|
|
||||||
var current_script : Script = plugin.get_editor_interface().get_script_editor().get_current_script()
|
|
||||||
if current_script:
|
|
||||||
var script_path = current_script.resource_path
|
|
||||||
for todo_item in todo_items:
|
|
||||||
if todo_item.script_path == script_path:
|
|
||||||
return todo_item
|
|
||||||
|
|
||||||
# nothing found
|
|
||||||
var todo_item := TodoItem.new(script_path, [])
|
|
||||||
return todo_item
|
|
||||||
else:
|
|
||||||
# not a script
|
|
||||||
var todo_item := TodoItem.new("res://Documentation", [])
|
|
||||||
return todo_item
|
|
||||||
|
|
||||||
|
|
||||||
func go_to_script(script_path: String, line_number : int = 0) -> void:
|
|
||||||
if plugin.get_editor_interface().get_editor_settings().get_setting("text_editor/external/use_external_editor"):
|
|
||||||
var exec_path = plugin.get_editor_interface().get_editor_settings().get_setting("text_editor/external/exec_path")
|
|
||||||
var args := get_exec_flags(exec_path, script_path, line_number)
|
|
||||||
OS.execute(exec_path, args)
|
|
||||||
else:
|
|
||||||
var script := load(script_path)
|
|
||||||
plugin.get_editor_interface().edit_resource(script)
|
|
||||||
plugin.get_editor_interface().get_script_editor().goto_line(line_number - 1)
|
|
||||||
|
|
||||||
func get_exec_flags(editor_path : String, script_path : String, line_number : int) -> PackedStringArray:
|
|
||||||
var args : PackedStringArray
|
|
||||||
var script_global_path = ProjectSettings.globalize_path(script_path)
|
|
||||||
|
|
||||||
if editor_path.ends_with("code.cmd") or editor_path.ends_with("code"): ## VS Code
|
|
||||||
args.append(ProjectSettings.globalize_path("res://"))
|
|
||||||
args.append("--goto")
|
|
||||||
args.append(script_global_path + ":" + str(line_number))
|
|
||||||
|
|
||||||
elif editor_path.ends_with("rider64.exe") or editor_path.ends_with("rider"): ## Rider
|
|
||||||
args.append("--line")
|
|
||||||
args.append(str(line_number))
|
|
||||||
args.append(script_global_path)
|
|
||||||
|
|
||||||
else: ## Atom / Sublime
|
|
||||||
args.append(script_global_path + ":" + str(line_number))
|
|
||||||
|
|
||||||
return args
|
|
||||||
|
|
||||||
func sort_alphabetical(a, b) -> bool:
|
|
||||||
if a.script_path > b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
|
|
||||||
func sort_backwards(a, b) -> bool:
|
|
||||||
if a.script_path < b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
func populate_settings() -> void:
|
|
||||||
for i in patterns.size():
|
|
||||||
## Create Colour Pickers
|
|
||||||
var colour_picker: Variant = ColourPicker.instantiate()
|
|
||||||
colour_picker.colour = patterns[i][1]
|
|
||||||
colour_picker.title = patterns[i][0]
|
|
||||||
colour_picker.index = i
|
|
||||||
colours_container.add_child(colour_picker)
|
|
||||||
colour_picker.colour_picker.color_changed.connect(change_colour.bind(i))
|
|
||||||
|
|
||||||
## Create Patterns
|
|
||||||
var pattern_edit: Variant = Pattern.instantiate()
|
|
||||||
pattern_edit.text = patterns[i][0]
|
|
||||||
pattern_edit.index = i
|
|
||||||
pattern_container.add_child(pattern_edit)
|
|
||||||
pattern_edit.line_edit.text_changed.connect(change_pattern.bind(i,
|
|
||||||
colour_picker))
|
|
||||||
pattern_edit.remove_button.pressed.connect(remove_pattern.bind(i,
|
|
||||||
pattern_edit, colour_picker))
|
|
||||||
pattern_edit.case_checkbox.button_pressed = patterns[i][2]
|
|
||||||
pattern_edit.case_checkbox.toggled.connect(case_sensitive_pattern.bind(i))
|
|
||||||
|
|
||||||
var pattern_button := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton
|
|
||||||
$VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns.move_child(pattern_button, 0)
|
|
||||||
|
|
||||||
# path filtering
|
|
||||||
var ignore_paths_field := ignore_textbox
|
|
||||||
if not ignore_paths_field.is_connected("text_changed", _on_ignore_paths_changed):
|
|
||||||
ignore_paths_field.connect("text_changed", _on_ignore_paths_changed)
|
|
||||||
var ignore_paths_text := ""
|
|
||||||
for path in ignore_paths:
|
|
||||||
ignore_paths_text += path + ", "
|
|
||||||
ignore_paths_text = ignore_paths_text.trim_suffix(", ")
|
|
||||||
ignore_paths_field.text = ignore_paths_text
|
|
||||||
|
|
||||||
auto_refresh_button.button_pressed = auto_refresh
|
|
||||||
|
|
||||||
|
|
||||||
func rebuild_settings() -> void:
|
|
||||||
for node in colours_container.get_children():
|
|
||||||
node.queue_free()
|
|
||||||
for node in pattern_container.get_children():
|
|
||||||
if node is Button:
|
|
||||||
continue
|
|
||||||
node.queue_free()
|
|
||||||
populate_settings()
|
|
||||||
|
|
||||||
|
|
||||||
#### CONFIG FILE ####
|
|
||||||
func create_config_file() -> void:
|
|
||||||
var config = ConfigFile.new()
|
|
||||||
config.set_value("scripts", "full_path", full_path)
|
|
||||||
config.set_value("scripts", "sort_alphabetical", _sort_alphabetical)
|
|
||||||
config.set_value("scripts", "script_colour", script_colour)
|
|
||||||
config.set_value("scripts", "ignore_paths", ignore_paths)
|
|
||||||
|
|
||||||
config.set_value("patterns", "patterns", patterns)
|
|
||||||
|
|
||||||
config.set_value("config", "auto_refresh", auto_refresh)
|
|
||||||
config.set_value("config", "builtin_enabled", builtin_enabled)
|
|
||||||
|
|
||||||
var err = config.save("res://addons/Todo_Manager/todo.cfg")
|
|
||||||
|
|
||||||
|
|
||||||
func load_config() -> void:
|
|
||||||
var config := ConfigFile.new()
|
|
||||||
if config.load("res://addons/Todo_Manager/todo.cfg") == OK:
|
|
||||||
full_path = config.get_value("scripts", "full_path", DEFAULT_SCRIPT_NAME)
|
|
||||||
_sort_alphabetical = config.get_value("scripts", "sort_alphabetical", DEFAULT_SORT)
|
|
||||||
script_colour = config.get_value("scripts", "script_colour", DEFAULT_SCRIPT_COLOUR)
|
|
||||||
ignore_paths = config.get_value("scripts", "ignore_paths", [] as Array[String])
|
|
||||||
patterns = config.get_value("patterns", "patterns", DEFAULT_PATTERNS)
|
|
||||||
auto_refresh = config.get_value("config", "auto_refresh", true)
|
|
||||||
builtin_enabled = config.get_value("config", "builtin_enabled", false)
|
|
||||||
else:
|
|
||||||
create_config_file()
|
|
||||||
|
|
||||||
|
|
||||||
#### Events ####
|
|
||||||
func _on_SettingsButton_toggled(button_pressed: bool) -> void:
|
|
||||||
settings_panel.visible = button_pressed
|
|
||||||
if button_pressed == false:
|
|
||||||
create_config_file()
|
|
||||||
# plugin.find_tokens_from_path(plugin.script_cache)
|
|
||||||
if auto_refresh:
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func _on_Tree_item_activated() -> void:
|
|
||||||
var item : TreeItem
|
|
||||||
match tabs.current_tab:
|
|
||||||
0:
|
|
||||||
item = project_tree.get_selected()
|
|
||||||
1:
|
|
||||||
item = current_tree.get_selected()
|
|
||||||
if item.get_metadata(0) is Todo:
|
|
||||||
var todo : Todo = item.get_metadata(0)
|
|
||||||
call_deferred("go_to_script", todo.script_path, todo.line_number)
|
|
||||||
else:
|
|
||||||
var todo_item = item.get_metadata(0)
|
|
||||||
call_deferred("go_to_script", todo_item.script_path)
|
|
||||||
|
|
||||||
func _on_FullPathCheckBox_toggled(button_pressed: bool) -> void:
|
|
||||||
full_path = button_pressed
|
|
||||||
|
|
||||||
func _on_ScriptColourPickerButton_color_changed(color: Color) -> void:
|
|
||||||
script_colour = color
|
|
||||||
|
|
||||||
func _on_RescanButton_pressed() -> void:
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func change_colour(colour: Color, index: int) -> void:
|
|
||||||
patterns[index][1] = colour
|
|
||||||
|
|
||||||
func change_pattern(value: String, index: int, this_colour: Node) -> void:
|
|
||||||
patterns[index][0] = value
|
|
||||||
this_colour.title = value
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func remove_pattern(index: int, this: Node, this_colour: Node) -> void:
|
|
||||||
patterns.remove_at(index)
|
|
||||||
this.queue_free()
|
|
||||||
this_colour.queue_free()
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func case_sensitive_pattern(active: bool, index: int) -> void:
|
|
||||||
if active:
|
|
||||||
patterns[index][2] = CASE_SENSITIVE
|
|
||||||
else:
|
|
||||||
patterns[index][2] = CASE_INSENSITIVE
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func _on_DefaultButton_pressed() -> void:
|
|
||||||
patterns = DEFAULT_PATTERNS.duplicate(true)
|
|
||||||
_sort_alphabetical = DEFAULT_SORT
|
|
||||||
script_colour = DEFAULT_SCRIPT_COLOUR
|
|
||||||
full_path = DEFAULT_SCRIPT_NAME
|
|
||||||
rebuild_settings()
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func _on_AlphSortCheckBox_toggled(button_pressed: bool) -> void:
|
|
||||||
_sort_alphabetical = button_pressed
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func _on_AddPatternButton_pressed() -> void:
|
|
||||||
patterns.append(["\\bplaceholder\\b", Color.WHITE, CASE_INSENSITIVE])
|
|
||||||
rebuild_settings()
|
|
||||||
|
|
||||||
func _on_RefreshCheckButton_toggled(button_pressed: bool) -> void:
|
|
||||||
auto_refresh = button_pressed
|
|
||||||
|
|
||||||
func _on_Timer_timeout() -> void:
|
|
||||||
plugin.refresh_lock = false
|
|
||||||
|
|
||||||
func _on_ignore_paths_changed(new_text: String) -> void:
|
|
||||||
var text = ignore_textbox.text
|
|
||||||
var split: Array = text.split(',')
|
|
||||||
ignore_paths.clear()
|
|
||||||
for elem in split:
|
|
||||||
if elem == " " || elem == "":
|
|
||||||
continue
|
|
||||||
ignore_paths.push_front(elem.lstrip(' ').rstrip(' '))
|
|
||||||
# validate so no empty string slips through (all paths ignored)
|
|
||||||
var i := 0
|
|
||||||
for path in ignore_paths:
|
|
||||||
if (path == "" || path == " "):
|
|
||||||
ignore_paths.remove_at(i)
|
|
||||||
i += 1
|
|
||||||
plugin.rescan_files(true)
|
|
||||||
|
|
||||||
func _on_TabContainer_tab_changed(tab: int) -> void:
|
|
||||||
build_tree()
|
|
||||||
|
|
||||||
func _on_BuiltInCheckButton_toggled(button_pressed: bool) -> void:
|
|
||||||
builtin_enabled = button_pressed
|
|
||||||
plugin.rescan_files(true)
|
|
|
@ -1,21 +0,0 @@
|
||||||
@tool
|
|
||||||
extends HBoxContainer
|
|
||||||
|
|
||||||
|
|
||||||
var text : String : set = set_text
|
|
||||||
var disabled : bool
|
|
||||||
var index : int
|
|
||||||
|
|
||||||
@onready var line_edit := $LineEdit as LineEdit
|
|
||||||
@onready var remove_button := $RemoveButton as Button
|
|
||||||
@onready var case_checkbox := %CaseSensativeCheckbox as CheckBox
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
line_edit.text = text
|
|
||||||
remove_button.disabled = disabled
|
|
||||||
|
|
||||||
|
|
||||||
func set_text(value: String) -> void:
|
|
||||||
text = value
|
|
||||||
if line_edit:
|
|
||||||
line_edit.text = value
|
|
|
@ -1,73 +0,0 @@
|
||||||
@tool
|
|
||||||
extends Panel
|
|
||||||
|
|
||||||
signal tree_built # used for debugging
|
|
||||||
|
|
||||||
const Todo := preload("res://addons/Todo_Manager/todo_class.gd")
|
|
||||||
|
|
||||||
var _sort_alphabetical := true
|
|
||||||
var _full_path := false
|
|
||||||
|
|
||||||
@onready var tree := $Tree as Tree
|
|
||||||
|
|
||||||
func build_tree(todo_items : Array, ignore_paths : Array, patterns : Array, cased_patterns: Array[String], sort_alphabetical : bool, full_path : bool) -> void:
|
|
||||||
_full_path = full_path
|
|
||||||
tree.clear()
|
|
||||||
if sort_alphabetical:
|
|
||||||
todo_items.sort_custom(Callable(self, "sort_alphabetical"))
|
|
||||||
else:
|
|
||||||
todo_items.sort_custom(Callable(self, "sort_backwards"))
|
|
||||||
var root := tree.create_item()
|
|
||||||
root.set_text(0, "Scripts")
|
|
||||||
for todo_item in todo_items:
|
|
||||||
var ignore := false
|
|
||||||
for ignore_path in ignore_paths:
|
|
||||||
var script_path : String = todo_item.script_path
|
|
||||||
if script_path.begins_with(ignore_path) or script_path.begins_with("res://" + ignore_path) or script_path.begins_with("res:///" + ignore_path):
|
|
||||||
ignore = true
|
|
||||||
break
|
|
||||||
if ignore:
|
|
||||||
continue
|
|
||||||
var script := tree.create_item(root)
|
|
||||||
if full_path:
|
|
||||||
script.set_text(0, todo_item.script_path + " -------")
|
|
||||||
else:
|
|
||||||
script.set_text(0, todo_item.get_short_path() + " -------")
|
|
||||||
script.set_metadata(0, todo_item)
|
|
||||||
for todo in todo_item.todos:
|
|
||||||
var item := tree.create_item(script)
|
|
||||||
var content_header : String = todo.content
|
|
||||||
if "\n" in todo.content:
|
|
||||||
content_header = content_header.split("\n")[0] + "..."
|
|
||||||
item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_"))
|
|
||||||
item.set_tooltip_text(0, todo.content)
|
|
||||||
item.set_metadata(0, todo)
|
|
||||||
for i in range(0, len(cased_patterns)):
|
|
||||||
if cased_patterns[i] == todo.pattern:
|
|
||||||
item.set_custom_color(0, patterns[i][1])
|
|
||||||
emit_signal("tree_built")
|
|
||||||
|
|
||||||
|
|
||||||
func sort_alphabetical(a, b) -> bool:
|
|
||||||
if _full_path:
|
|
||||||
if a.script_path < b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
else:
|
|
||||||
if a.get_short_path() < b.get_short_path():
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
|
|
||||||
func sort_backwards(a, b) -> bool:
|
|
||||||
if _full_path:
|
|
||||||
if a.script_path > b.script_path:
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
||||||
else:
|
|
||||||
if a.get_short_path() > b.get_short_path():
|
|
||||||
return true
|
|
||||||
else:
|
|
||||||
return false
|
|
|
@ -1,60 +0,0 @@
|
||||||
|
|
||||||
### Localised READMEs
|
|
||||||
- [简体中文](READMECN.md) (Simplified Chinese)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Manager
|
|
||||||
|
|
||||||
![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png)
|
|
||||||
|
|
||||||
## Simple and flexible
|
|
||||||
|
|
||||||
- Supports GDScript, C# and GDNative
|
|
||||||
- Seamlessly integrated into the Godot dock
|
|
||||||
- Lenient syntax. Write TODOs that suit your style
|
|
||||||
- Quickly jump to lines and launch external editors
|
|
||||||
|
|
||||||
## Customizable
|
|
||||||
|
|
||||||
![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png)
|
|
||||||
|
|
||||||
- Add your own RegEx patterns
|
|
||||||
- Set colours to your liking
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Method 1 (Godot Asset Library)
|
|
||||||
|
|
||||||
The most simple way to get started using TODO Manager is to use Godot's inbuilt Asset Library to install the plugin into your project.
|
|
||||||
|
|
||||||
#### Step 1
|
|
||||||
|
|
||||||
Find TODO Manager in the Godot Asset Library.
|
|
||||||
![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png)
|
|
||||||
|
|
||||||
#### Step 2
|
|
||||||
|
|
||||||
Install the package. You may want to untick the /doc folder at this point as it is not necessary for the functions of the plugin.
|
|
||||||
![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png)
|
|
||||||
|
|
||||||
#### Step 4
|
|
||||||
|
|
||||||
Enable the plugin in the project settings.
|
|
||||||
![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png)
|
|
||||||
|
|
||||||
### Method 2 (GitHub)
|
|
||||||
|
|
||||||
#### Step 1
|
|
||||||
|
|
||||||
Click Download ZIP from the 'Code' dropdown.
|
|
||||||
![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png)
|
|
||||||
|
|
||||||
#### Step 2
|
|
||||||
|
|
||||||
- Unzip the file and add it into your project folder. Make sure 'addons' is a subdirectory of res://
|
|
||||||
- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings.
|
|
||||||
|
|
||||||
#### Step 3
|
|
||||||
|
|
||||||
Enable the plugin in the project settings.
|
|
||||||
![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png)
|
|
|
@ -1,56 +0,0 @@
|
||||||
# TODO Manager
|
|
||||||
|
|
||||||
![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png)
|
|
||||||
|
|
||||||
## 简单而灵活
|
|
||||||
|
|
||||||
- 支持 GDScript,C# 和 GDNative。
|
|
||||||
- 无缝集成到 Godot dock 栏。
|
|
||||||
- 宽松的语法,用适合你自己的风格写TODOs。
|
|
||||||
- 快速跳转到某一行并启用外部编辑器。
|
|
||||||
|
|
||||||
## 可定制
|
|
||||||
|
|
||||||
![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png)
|
|
||||||
|
|
||||||
- 添加你自己的正则表达式。
|
|
||||||
- 设置你喜欢的颜色。
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
### 方法一 (Godot Asset Library)
|
|
||||||
|
|
||||||
最简单的使用 TODO Manager 的方法,使用 Godot 内置的资源商店(Asset Library)来安装这个插件到你的项目。
|
|
||||||
|
|
||||||
#### 第一步
|
|
||||||
|
|
||||||
在资源商店搜索 TODO Manager。
|
|
||||||
![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png)
|
|
||||||
|
|
||||||
#### 第二步
|
|
||||||
|
|
||||||
安装下载的插件,你可能需要取消勾选 /doc 文件夹,因为插件的功能不需要。
|
|
||||||
![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png)
|
|
||||||
|
|
||||||
#### 第三步
|
|
||||||
|
|
||||||
在项目设置里启用插件。
|
|
||||||
![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png)
|
|
||||||
|
|
||||||
### 方法二 (GitHub)
|
|
||||||
|
|
||||||
#### 第一步
|
|
||||||
|
|
||||||
点击 Download ZIP。
|
|
||||||
![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png)
|
|
||||||
|
|
||||||
#### 第二步
|
|
||||||
|
|
||||||
- 解压文件并且放到你的项目文件夹。确保 “addons” 是 res:// 的子文件夹。
|
|
||||||
- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings.
|
|
||||||
- 不要更改 “addons” 或 “Todo_Manager” 文件夹的名称,因为这会打破预设的保存和加载。
|
|
||||||
|
|
||||||
#### 第三步
|
|
||||||
|
|
||||||
在项目设置里启用这个插件。
|
|
||||||
![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png)
|
|
|
@ -1,21 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://bie1xn8v1kd66"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/Todo_Manager/ColourPicker.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="TODOColour" type="HBoxContainer"]
|
|
||||||
offset_right = 105.0
|
|
||||||
offset_bottom = 31.0
|
|
||||||
script = ExtResource("1")
|
|
||||||
metadata/_edit_use_custom_anchors = false
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="."]
|
|
||||||
offset_top = 4.0
|
|
||||||
offset_right = 1.0
|
|
||||||
offset_bottom = 27.0
|
|
||||||
|
|
||||||
[node name="TODOColourPickerButton" type="ColorPickerButton" parent="."]
|
|
||||||
custom_minimum_size = Vector2(40, 0)
|
|
||||||
offset_left = 65.0
|
|
||||||
offset_right = 105.0
|
|
||||||
offset_bottom = 31.0
|
|
||||||
size_flags_horizontal = 10
|
|
|
@ -1,315 +0,0 @@
|
||||||
[gd_scene load_steps=6 format=3 uid="uid://b6k0dtftankcx"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/Todo_Manager/Dock.gd" id="1"]
|
|
||||||
[ext_resource type="Script" path="res://addons/Todo_Manager/Project.gd" id="2"]
|
|
||||||
[ext_resource type="Script" path="res://addons/Todo_Manager/Current.gd" id="3"]
|
|
||||||
|
|
||||||
[sub_resource type="ButtonGroup" id="ButtonGroup_kqxcu"]
|
|
||||||
|
|
||||||
[sub_resource type="ButtonGroup" id="ButtonGroup_kltg3"]
|
|
||||||
|
|
||||||
[node name="Dock" type="Control"]
|
|
||||||
custom_minimum_size = Vector2(0, 200)
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_top = 4.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
metadata/_edit_layout_mode = 1
|
|
||||||
|
|
||||||
[node name="Header" type="HBoxContainer" parent="VBoxContainer"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="HeaderLeft" type="HBoxContainer" parent="VBoxContainer/Header"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="Title" type="Label" parent="VBoxContainer/Header/HeaderLeft"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Todo Dock:"
|
|
||||||
|
|
||||||
[node name="HeaderRight" type="HBoxContainer" parent="VBoxContainer/Header"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
alignment = 2
|
|
||||||
|
|
||||||
[node name="SettingsButton" type="Button" parent="VBoxContainer/Header/HeaderRight"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
toggle_mode = true
|
|
||||||
text = "Settings"
|
|
||||||
|
|
||||||
[node name="TabContainer" type="TabContainer" parent="VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 3
|
|
||||||
|
|
||||||
[node name="Project" type="Panel" parent="VBoxContainer/TabContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
size_flags_vertical = 3
|
|
||||||
script = ExtResource("2")
|
|
||||||
|
|
||||||
[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Project"]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
hide_root = true
|
|
||||||
|
|
||||||
[node name="Current" type="Panel" parent="VBoxContainer/TabContainer"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
size_flags_vertical = 3
|
|
||||||
script = ExtResource("3")
|
|
||||||
|
|
||||||
[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Current"]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
hide_folding = true
|
|
||||||
hide_root = true
|
|
||||||
|
|
||||||
[node name="Settings" type="Panel" parent="VBoxContainer/TabContainer"]
|
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/TabContainer/Settings"]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
size_flags_vertical = 3
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
size_flags_vertical = 3
|
|
||||||
|
|
||||||
[node name="Scripts" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Scripts:"
|
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 5
|
|
||||||
|
|
||||||
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Scripts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="ScriptName" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Script Name:"
|
|
||||||
|
|
||||||
[node name="FullPathCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"]
|
|
||||||
layout_mode = 2
|
|
||||||
button_group = SubResource("ButtonGroup_kqxcu")
|
|
||||||
text = "Full path"
|
|
||||||
|
|
||||||
[node name="ShortNameCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"]
|
|
||||||
layout_mode = 2
|
|
||||||
button_pressed = true
|
|
||||||
button_group = SubResource("ButtonGroup_kqxcu")
|
|
||||||
text = "Short name"
|
|
||||||
|
|
||||||
[node name="ScriptSort" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Sort Order:"
|
|
||||||
|
|
||||||
[node name="AlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"]
|
|
||||||
layout_mode = 2
|
|
||||||
button_pressed = true
|
|
||||||
button_group = SubResource("ButtonGroup_kltg3")
|
|
||||||
text = "Alphabetical"
|
|
||||||
|
|
||||||
[node name="RAlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"]
|
|
||||||
layout_mode = 2
|
|
||||||
button_group = SubResource("ButtonGroup_kltg3")
|
|
||||||
text = "Reverse Alphabetical"
|
|
||||||
|
|
||||||
[node name="ScriptColour" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Script Colour:"
|
|
||||||
|
|
||||||
[node name="ScriptColourPickerButton" type="ColorPickerButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"]
|
|
||||||
custom_minimum_size = Vector2(40, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
color = Color(0.8, 0.807843, 0.827451, 1)
|
|
||||||
|
|
||||||
[node name="IgnorePaths" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Ignore Paths:"
|
|
||||||
|
|
||||||
[node name="TextEdit" type="LineEdit" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"]
|
|
||||||
custom_minimum_size = Vector2(100, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
expand_to_text_length = true
|
|
||||||
|
|
||||||
[node name="Label3" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "(Separated by commas)"
|
|
||||||
|
|
||||||
[node name="TODOColours" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "TODO Colours:"
|
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Colours" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Patterns" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Patterns:"
|
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="HBoxContainer4" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="AddPatternButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
text = "Add"
|
|
||||||
|
|
||||||
[node name="Config" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Config:"
|
|
||||||
|
|
||||||
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="HBoxContainer5" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="RefreshCheckButton" type="CheckButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
button_pressed = true
|
|
||||||
text = "Auto Refresh"
|
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="BuiltInCheckButton" type="CheckButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Scan Built-in Scripts"
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="DefaultButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
text = "Reset to default"
|
|
||||||
|
|
||||||
[node name="Timer" type="Timer" parent="."]
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[node name="RescanButton" type="Button" parent="."]
|
|
||||||
layout_mode = 1
|
|
||||||
anchors_preset = 1
|
|
||||||
anchor_left = 1.0
|
|
||||||
anchor_right = 1.0
|
|
||||||
offset_left = -102.0
|
|
||||||
offset_top = 3.0
|
|
||||||
offset_bottom = 34.0
|
|
||||||
grow_horizontal = 0
|
|
||||||
text = "Rescan Files"
|
|
||||||
flat = true
|
|
||||||
|
|
||||||
[connection signal="toggled" from="VBoxContainer/Header/HeaderRight/SettingsButton" to="." method="_on_SettingsButton_toggled"]
|
|
||||||
[connection signal="tab_changed" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_tab_changed"]
|
|
||||||
[connection signal="item_activated" from="VBoxContainer/TabContainer/Project/Tree" to="." method="_on_Tree_item_activated"]
|
|
||||||
[connection signal="item_activated" from="VBoxContainer/TabContainer/Current/Tree" to="." method="_on_Tree_item_activated"]
|
|
||||||
[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName/FullPathCheckBox" to="." method="_on_FullPathCheckBox_toggled"]
|
|
||||||
[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort/AlphSortCheckBox" to="." method="_on_AlphSortCheckBox_toggled"]
|
|
||||||
[connection signal="color_changed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour/ScriptColourPickerButton" to="." method="_on_ScriptColourPickerButton_color_changed"]
|
|
||||||
[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton" to="." method="_on_AddPatternButton_pressed"]
|
|
||||||
[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/RefreshCheckButton" to="." method="_on_RefreshCheckButton_toggled"]
|
|
||||||
[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer/BuiltInCheckButton" to="." method="_on_BuiltInCheckButton_toggled"]
|
|
||||||
[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/DefaultButton" to="." method="_on_DefaultButton_pressed"]
|
|
||||||
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]
|
|
||||||
[connection signal="pressed" from="RescanButton" to="." method="_on_RescanButton_pressed"]
|
|
|
@ -1,26 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://bx11sel2q5wli"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/Todo_Manager/Pattern.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="Pattern" type="HBoxContainer"]
|
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="LineEdit" type="LineEdit" parent="."]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
expand_to_text_length = true
|
|
||||||
|
|
||||||
[node name="RemoveButton" type="Button" parent="."]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "-"
|
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
|
||||||
custom_minimum_size = Vector2(20, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
|
|
||||||
[node name="CaseSensativeCheckbox" type="CheckBox" parent="."]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 0
|
|
||||||
text = "Case Sensitive"
|
|
|
@ -1,7 +0,0 @@
|
||||||
extends Node
|
|
||||||
|
|
||||||
# TODO: this is a TODO
|
|
||||||
# HACK: this is a HACK
|
|
||||||
# FIXME: this is a FIXME
|
|
||||||
# TODO this works too
|
|
||||||
#Hack any format will do
|
|
Before Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB |
|
@ -1,7 +0,0 @@
|
||||||
[plugin]
|
|
||||||
|
|
||||||
name="Todo Manager"
|
|
||||||
description="Dock for housing TODO messages."
|
|
||||||
author="Peter de Vroom"
|
|
||||||
version="2.3.1"
|
|
||||||
script="plugin.gd"
|
|
|
@ -1,286 +0,0 @@
|
||||||
@tool
|
|
||||||
extends EditorPlugin
|
|
||||||
|
|
||||||
const DockScene := preload("res://addons/Todo_Manager/UI/Dock.tscn")
|
|
||||||
const Dock := preload("res://addons/Todo_Manager/Dock.gd")
|
|
||||||
const Todo := preload("res://addons/Todo_Manager/todo_class.gd")
|
|
||||||
const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd")
|
|
||||||
|
|
||||||
var _dockUI : Dock
|
|
||||||
|
|
||||||
class TodoCacheValue:
|
|
||||||
var todos: Array
|
|
||||||
var last_modified_time: int
|
|
||||||
|
|
||||||
func _init(todos: Array, last_modified_time: int):
|
|
||||||
self.todos = todos
|
|
||||||
self.last_modified_time = last_modified_time
|
|
||||||
|
|
||||||
var todo_cache : Dictionary # { key: script_path, value: TodoCacheValue }
|
|
||||||
var remove_queue : Array
|
|
||||||
var combined_pattern : String
|
|
||||||
var cased_patterns : Array[String]
|
|
||||||
|
|
||||||
var refresh_lock := false # makes sure _on_filesystem_changed only triggers once
|
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree() -> void:
|
|
||||||
_dockUI = DockScene.instantiate() as Control
|
|
||||||
add_control_to_bottom_panel(_dockUI, "TODO")
|
|
||||||
get_editor_interface().get_resource_filesystem().connect("filesystem_changed",
|
|
||||||
_on_filesystem_changed)
|
|
||||||
get_editor_interface().get_file_system_dock().connect("file_removed", queue_remove)
|
|
||||||
get_editor_interface().get_script_editor().connect("editor_script_changed",
|
|
||||||
_on_active_script_changed)
|
|
||||||
_dockUI.plugin = self
|
|
||||||
|
|
||||||
combined_pattern = combine_patterns(_dockUI.patterns)
|
|
||||||
find_tokens_from_path(find_scripts())
|
|
||||||
_dockUI.build_tree()
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
|
||||||
_dockUI.create_config_file()
|
|
||||||
remove_control_from_bottom_panel(_dockUI)
|
|
||||||
_dockUI.free()
|
|
||||||
|
|
||||||
|
|
||||||
func queue_remove(file: String):
|
|
||||||
for i in _dockUI.todo_items.size() - 1:
|
|
||||||
if _dockUI.todo_items[i].script_path == file:
|
|
||||||
_dockUI.todo_items.remove_at(i)
|
|
||||||
|
|
||||||
|
|
||||||
func find_tokens_from_path(scripts: Array[String]) -> void:
|
|
||||||
for script_path in scripts:
|
|
||||||
var file := FileAccess.open(script_path, FileAccess.READ)
|
|
||||||
var contents := file.get_as_text()
|
|
||||||
if script_path.ends_with(".tscn"):
|
|
||||||
handle_built_in_scripts(contents, script_path)
|
|
||||||
else:
|
|
||||||
find_tokens(contents, script_path)
|
|
||||||
|
|
||||||
|
|
||||||
func handle_built_in_scripts(contents: String, resource_path: String):
|
|
||||||
var s := contents.split("sub_resource type=\"GDScript\"")
|
|
||||||
if s.size() <= 1:
|
|
||||||
return
|
|
||||||
for i in range(1, s.size()):
|
|
||||||
var script_components := s[i].split("script/source")
|
|
||||||
var script_name = script_components[0].substr(5, 14)
|
|
||||||
find_tokens(script_components[1], resource_path + "::" + script_name)
|
|
||||||
|
|
||||||
|
|
||||||
func find_tokens(text: String, script_path: String) -> void:
|
|
||||||
var cached_todos = get_cached_todos(script_path)
|
|
||||||
if cached_todos.size() != 0:
|
|
||||||
# var i := 0
|
|
||||||
# for todo_item in _dockUI.todo_items:
|
|
||||||
# if todo_item.script_path == script_path:
|
|
||||||
# _dockUI.todo_items.remove_at(i)
|
|
||||||
# i += 1
|
|
||||||
var todo_item := TodoItem.new(script_path, cached_todos)
|
|
||||||
_dockUI.todo_items.append(todo_item)
|
|
||||||
else:
|
|
||||||
var regex = RegEx.new()
|
|
||||||
# if regex.compile("#\\s*\\bTODO\\b.*|#\\s*\\bHACK\\b.*") == OK:
|
|
||||||
if regex.compile(combined_pattern) == OK:
|
|
||||||
var result : Array[RegExMatch] = regex.search_all(text)
|
|
||||||
if result.is_empty():
|
|
||||||
for i in _dockUI.todo_items.size():
|
|
||||||
if _dockUI.todo_items[i].script_path == script_path:
|
|
||||||
_dockUI.todo_items.remove_at(i)
|
|
||||||
return # No tokens found
|
|
||||||
var match_found : bool
|
|
||||||
var i := 0
|
|
||||||
for todo_item in _dockUI.todo_items:
|
|
||||||
if todo_item.script_path == script_path:
|
|
||||||
match_found = true
|
|
||||||
var updated_todo_item := update_todo_item(todo_item, result, text, script_path)
|
|
||||||
_dockUI.todo_items.remove_at(i)
|
|
||||||
_dockUI.todo_items.insert(i, updated_todo_item)
|
|
||||||
break
|
|
||||||
i += 1
|
|
||||||
if !match_found:
|
|
||||||
_dockUI.todo_items.append(create_todo_item(result, text, script_path))
|
|
||||||
|
|
||||||
|
|
||||||
func create_todo_item(regex_results: Array[RegExMatch], text: String, script_path: String) -> TodoItem:
|
|
||||||
var todo_item = TodoItem.new(script_path, [])
|
|
||||||
todo_item.script_path = script_path
|
|
||||||
var last_line_number := 0
|
|
||||||
var lines := text.split("\n")
|
|
||||||
for r in regex_results:
|
|
||||||
var new_todo : Todo = create_todo(r.get_string(), script_path)
|
|
||||||
new_todo.line_number = get_line_number(r.get_string(), text, last_line_number)
|
|
||||||
# GD Multiline comment
|
|
||||||
var trailing_line := new_todo.line_number
|
|
||||||
var should_break = false
|
|
||||||
while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"):
|
|
||||||
for other_r in regex_results:
|
|
||||||
if lines[trailing_line] in other_r.get_string():
|
|
||||||
should_break = true
|
|
||||||
break
|
|
||||||
if should_break:
|
|
||||||
break
|
|
||||||
|
|
||||||
new_todo.content += "\n" + lines[trailing_line]
|
|
||||||
trailing_line += 1
|
|
||||||
|
|
||||||
last_line_number = new_todo.line_number
|
|
||||||
todo_item.todos.append(new_todo)
|
|
||||||
cache_todos(todo_item.todos, script_path)
|
|
||||||
return todo_item
|
|
||||||
|
|
||||||
|
|
||||||
func update_todo_item(todo_item: TodoItem, regex_results: Array[RegExMatch], text: String, script_path: String) -> TodoItem:
|
|
||||||
todo_item.todos.clear()
|
|
||||||
var lines := text.split("\n")
|
|
||||||
for r in regex_results:
|
|
||||||
var new_todo : Todo = create_todo(r.get_string(), script_path)
|
|
||||||
new_todo.line_number = get_line_number(r.get_string(), text)
|
|
||||||
# GD Multiline comment
|
|
||||||
var trailing_line := new_todo.line_number
|
|
||||||
var should_break = false
|
|
||||||
while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"):
|
|
||||||
for other_r in regex_results:
|
|
||||||
if lines[trailing_line] in other_r.get_string():
|
|
||||||
should_break = true
|
|
||||||
break
|
|
||||||
if should_break:
|
|
||||||
break
|
|
||||||
|
|
||||||
new_todo.content += "\n" + lines[trailing_line]
|
|
||||||
trailing_line += 1
|
|
||||||
todo_item.todos.append(new_todo)
|
|
||||||
return todo_item
|
|
||||||
|
|
||||||
|
|
||||||
func get_line_number(what: String, from: String, start := 0) -> int:
|
|
||||||
what = what.split('\n')[0] # Match first line of multiline C# comments
|
|
||||||
var temp_array := from.split('\n')
|
|
||||||
var lines := Array(temp_array)
|
|
||||||
var line_number# = lines.find(what) + 1
|
|
||||||
for i in range(start, lines.size()):
|
|
||||||
if what in lines[i]:
|
|
||||||
line_number = i + 1 # +1 to account of 0-based array vs 1-based line numbers
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
line_number = 0 # This is an error
|
|
||||||
return line_number
|
|
||||||
|
|
||||||
|
|
||||||
func _on_filesystem_changed() -> void:
|
|
||||||
if !refresh_lock:
|
|
||||||
if _dockUI.auto_refresh:
|
|
||||||
refresh_lock = true
|
|
||||||
_dockUI.get_node("Timer").start()
|
|
||||||
rescan_files(false)
|
|
||||||
|
|
||||||
|
|
||||||
func find_scripts() -> Array[String]:
|
|
||||||
var scripts : Array[String]
|
|
||||||
var directory_queue : Array[String]
|
|
||||||
var dir := DirAccess.open("res://")
|
|
||||||
if dir.get_open_error() == OK:
|
|
||||||
get_dir_contents(dir, scripts, directory_queue)
|
|
||||||
else:
|
|
||||||
printerr("TODO_Manager: There was an error during find_scripts()")
|
|
||||||
|
|
||||||
while not directory_queue.is_empty():
|
|
||||||
if dir.change_dir(directory_queue[0]) == OK:
|
|
||||||
get_dir_contents(dir, scripts, directory_queue)
|
|
||||||
else:
|
|
||||||
printerr("TODO_Manager: There was an error at: " + directory_queue[0])
|
|
||||||
directory_queue.pop_front()
|
|
||||||
|
|
||||||
return scripts
|
|
||||||
|
|
||||||
|
|
||||||
func cache_todos(todos: Array, script_path: String) -> void:
|
|
||||||
var last_modified_time = FileAccess.get_modified_time(script_path)
|
|
||||||
todo_cache[script_path] = TodoCacheValue.new(todos, last_modified_time)
|
|
||||||
|
|
||||||
|
|
||||||
func get_cached_todos(script_path: String) -> Array:
|
|
||||||
if todo_cache.has(script_path) and !script_path.contains("tscn::"):
|
|
||||||
var cached_value: TodoCacheValue = todo_cache[script_path]
|
|
||||||
if cached_value.last_modified_time == FileAccess.get_modified_time(script_path):
|
|
||||||
|
|
||||||
return cached_value.todos
|
|
||||||
return []
|
|
||||||
|
|
||||||
func get_dir_contents(dir: DirAccess, scripts: Array[String], directory_queue: Array[String]) -> void:
|
|
||||||
dir.include_navigational = false
|
|
||||||
dir.include_hidden = false
|
|
||||||
dir.list_dir_begin()
|
|
||||||
var file_name : String = dir.get_next()
|
|
||||||
|
|
||||||
while file_name != "":
|
|
||||||
if dir.current_is_dir():
|
|
||||||
if file_name == ".import" or file_name == ".mono": # Skip .import folder which should never have scripts
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
directory_queue.append(dir.get_current_dir().path_join(file_name))
|
|
||||||
else:
|
|
||||||
if file_name.ends_with(".gd") or file_name.ends_with(".cs") \
|
|
||||||
or file_name.ends_with(".c") or file_name.ends_with(".cpp") or file_name.ends_with(".h") \
|
|
||||||
or ((file_name.ends_with(".tscn") and _dockUI.builtin_enabled)):
|
|
||||||
scripts.append(dir.get_current_dir().path_join(file_name))
|
|
||||||
file_name = dir.get_next()
|
|
||||||
|
|
||||||
|
|
||||||
func rescan_files(clear_cache: bool) -> void:
|
|
||||||
_dockUI.todo_items.clear()
|
|
||||||
if clear_cache:
|
|
||||||
todo_cache.clear()
|
|
||||||
combined_pattern = combine_patterns(_dockUI.patterns)
|
|
||||||
find_tokens_from_path(find_scripts())
|
|
||||||
_dockUI.build_tree()
|
|
||||||
|
|
||||||
|
|
||||||
func combine_patterns(patterns: Array) -> String:
|
|
||||||
# Case Sensitivity
|
|
||||||
cased_patterns = []
|
|
||||||
for pattern in patterns:
|
|
||||||
if pattern[2] == _dockUI.CASE_INSENSITIVE:
|
|
||||||
cased_patterns.append(pattern[0].insert(0, "((?i)") + ")")
|
|
||||||
else:
|
|
||||||
cased_patterns.append("(" + pattern[0] + ")")
|
|
||||||
|
|
||||||
if patterns.size() == 1:
|
|
||||||
return cased_patterns[0]
|
|
||||||
else:
|
|
||||||
var pattern_string := "((\\/\\*)|(#|\\/\\/))\\s*("
|
|
||||||
for i in range(patterns.size()):
|
|
||||||
if i == 0:
|
|
||||||
pattern_string += cased_patterns[i]
|
|
||||||
else:
|
|
||||||
pattern_string += "|" + cased_patterns[i]
|
|
||||||
pattern_string += ")(?(2)[\\s\\S]*?\\*\\/|.*)"
|
|
||||||
return pattern_string
|
|
||||||
|
|
||||||
|
|
||||||
func create_todo(todo_string: String, script_path: String) -> Todo:
|
|
||||||
var todo := Todo.new()
|
|
||||||
var regex = RegEx.new()
|
|
||||||
for pattern in cased_patterns:
|
|
||||||
if regex.compile(pattern) == OK:
|
|
||||||
var result : RegExMatch = regex.search(todo_string)
|
|
||||||
if result:
|
|
||||||
todo.pattern = pattern
|
|
||||||
todo.title = result.strings[0]
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
printerr("Error compiling " + pattern)
|
|
||||||
|
|
||||||
todo.content = todo_string
|
|
||||||
todo.script_path = script_path
|
|
||||||
return todo
|
|
||||||
|
|
||||||
|
|
||||||
func _on_active_script_changed(script) -> void:
|
|
||||||
if _dockUI:
|
|
||||||
if _dockUI.tabs.current_tab == 1:
|
|
||||||
_dockUI.build_tree()
|
|
|
@ -1,15 +0,0 @@
|
||||||
[scripts]
|
|
||||||
|
|
||||||
full_path=false
|
|
||||||
sort_alphabetical=true
|
|
||||||
script_colour=Color(0.8, 0.807843, 0.827451, 1)
|
|
||||||
ignore_paths=Array[String]([])
|
|
||||||
|
|
||||||
[patterns]
|
|
||||||
|
|
||||||
patterns=[["\\bTODO\\b", Color(0.588235, 0.945098, 0.678431, 1), 0], ["\\bHACK\\b", Color(0.835294, 0.737255, 0.439216, 1), 0], ["\\bFIXME\\b", Color(0.835294, 0.439216, 0.439216, 1), 0]]
|
|
||||||
|
|
||||||
[config]
|
|
||||||
|
|
||||||
auto_refresh=true
|
|
||||||
builtin_enabled=false
|
|
|
@ -1,18 +0,0 @@
|
||||||
@tool
|
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
var script_path : String
|
|
||||||
var todos : Array
|
|
||||||
|
|
||||||
func _init(script_path: String, todos: Array):
|
|
||||||
self.script_path = script_path
|
|
||||||
self.todos = todos
|
|
||||||
|
|
||||||
func get_short_path() -> String:
|
|
||||||
var temp_array := script_path.rsplit('/', false, 1)
|
|
||||||
var short_path : String
|
|
||||||
if not temp_array.size() > 1:
|
|
||||||
short_path = "(!)" + temp_array[0]
|
|
||||||
else:
|
|
||||||
short_path = temp_array[1]
|
|
||||||
return short_path
|
|
|
@ -1,9 +0,0 @@
|
||||||
@tool
|
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
|
|
||||||
var pattern : String
|
|
||||||
var title : String
|
|
||||||
var content : String
|
|
||||||
var script_path : String
|
|
||||||
var line_number : int
|
|
|
@ -1,119 +0,0 @@
|
||||||
extends Node2D
|
|
||||||
class_name AIController2D
|
|
||||||
|
|
||||||
enum ControlModes { INHERIT_FROM_SYNC, HUMAN, TRAINING, ONNX_INFERENCE, RECORD_EXPERT_DEMOS }
|
|
||||||
@export var control_mode: ControlModes = ControlModes.INHERIT_FROM_SYNC
|
|
||||||
@export var onnx_model_path := ""
|
|
||||||
@export var reset_after := 1000
|
|
||||||
|
|
||||||
@export_group("Record expert demos mode options")
|
|
||||||
## Path where the demos will be saved. The file can later be used for imitation learning.
|
|
||||||
@export var expert_demo_save_path: String
|
|
||||||
## The action that erases the last recorded episode from the currently recorded data.
|
|
||||||
@export var remove_last_episode_key: InputEvent
|
|
||||||
## Action will be repeated for n frames. Will introduce control lag if larger than 1.
|
|
||||||
## Can be used to ensure that action_repeat on inference and training matches
|
|
||||||
## the recorded demonstrations.
|
|
||||||
@export var action_repeat: int = 1
|
|
||||||
|
|
||||||
@export_group("Multi-policy mode options")
|
|
||||||
## Allows you to set certain agents to use different policies.
|
|
||||||
## Changing has no effect with default SB3 training. Works with Rllib example.
|
|
||||||
## Tutorial: https://github.com/edbeeching/godot_rl_agents/blob/main/docs/TRAINING_MULTIPLE_POLICIES.md
|
|
||||||
@export var policy_name: String = "shared_policy"
|
|
||||||
|
|
||||||
var onnx_model: ONNXModel
|
|
||||||
|
|
||||||
var heuristic := "human"
|
|
||||||
var done := false
|
|
||||||
var reward := 0.0
|
|
||||||
var n_steps := 0
|
|
||||||
var needs_reset := false
|
|
||||||
|
|
||||||
var _player: Node2D
|
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
add_to_group("AGENT")
|
|
||||||
|
|
||||||
|
|
||||||
func init(player: Node2D):
|
|
||||||
_player = player
|
|
||||||
|
|
||||||
|
|
||||||
#-- Methods that need implementing using the "extend script" option in Godot --#
|
|
||||||
func get_obs() -> Dictionary:
|
|
||||||
assert(false, "the get_obs method is not implemented when extending from ai_controller")
|
|
||||||
return {"obs": []}
|
|
||||||
|
|
||||||
|
|
||||||
func get_reward() -> float:
|
|
||||||
assert(false, "the get_reward method is not implemented when extending from ai_controller")
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
|
|
||||||
func get_action_space() -> Dictionary:
|
|
||||||
assert(
|
|
||||||
false,
|
|
||||||
"the get get_action_space method is not implemented when extending from ai_controller"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"example_actions_continous": {"size": 2, "action_type": "continuous"},
|
|
||||||
"example_actions_discrete": {"size": 2, "action_type": "discrete"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func set_action(action) -> void:
|
|
||||||
assert(false, "the set_action method is not implemented when extending from ai_controller")
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
|
|
||||||
#-- Methods that sometimes need implementing using the "extend script" option in Godot --#
|
|
||||||
# Only needed if you are recording expert demos with this AIController
|
|
||||||
func get_action() -> Array:
|
|
||||||
assert(false, "the get_action method is not implemented in extended AIController but demo_recorder is used")
|
|
||||||
return []
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
func _physics_process(delta):
|
|
||||||
n_steps += 1
|
|
||||||
if n_steps > reset_after:
|
|
||||||
needs_reset = true
|
|
||||||
|
|
||||||
|
|
||||||
func get_obs_space():
|
|
||||||
# may need overriding if the obs space is complex
|
|
||||||
var obs = get_obs()
|
|
||||||
return {
|
|
||||||
"obs": {"size": [len(obs["obs"])], "space": "box"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
n_steps = 0
|
|
||||||
needs_reset = false
|
|
||||||
|
|
||||||
|
|
||||||
func reset_if_done():
|
|
||||||
if done:
|
|
||||||
reset()
|
|
||||||
|
|
||||||
|
|
||||||
func set_heuristic(h):
|
|
||||||
# sets the heuristic from "human" or "model" nothing to change here
|
|
||||||
heuristic = h
|
|
||||||
|
|
||||||
|
|
||||||
func get_done():
|
|
||||||
return done
|
|
||||||
|
|
||||||
|
|
||||||
func set_done_false():
|
|
||||||
done = false
|
|
||||||
|
|
||||||
|
|
||||||
func zero_reward():
|
|
||||||
reward = 0.0
|
|
|
@ -1,120 +0,0 @@
|
||||||
extends Node3D
|
|
||||||
class_name AIController3D
|
|
||||||
|
|
||||||
enum ControlModes { INHERIT_FROM_SYNC, HUMAN, TRAINING, ONNX_INFERENCE, RECORD_EXPERT_DEMOS }
|
|
||||||
@export var control_mode: ControlModes = ControlModes.INHERIT_FROM_SYNC
|
|
||||||
@export var onnx_model_path := ""
|
|
||||||
@export var reset_after := 1000
|
|
||||||
|
|
||||||
@export_group("Record expert demos mode options")
|
|
||||||
## Path where the demos will be saved. The file can later be used for imitation learning.
|
|
||||||
@export var expert_demo_save_path: String
|
|
||||||
## The action that erases the last recorded episode from the currently recorded data.
|
|
||||||
@export var remove_last_episode_key: InputEvent
|
|
||||||
## Action will be repeated for n frames. Will introduce control lag if larger than 1.
|
|
||||||
## Can be used to ensure that action_repeat on inference and training matches
|
|
||||||
## the recorded demonstrations.
|
|
||||||
@export var action_repeat: int = 1
|
|
||||||
|
|
||||||
@export_group("Multi-policy mode options")
|
|
||||||
## Allows you to set certain agents to use different policies.
|
|
||||||
## Changing has no effect with default SB3 training. Works with Rllib example.
|
|
||||||
## Tutorial: https://github.com/edbeeching/godot_rl_agents/blob/main/docs/TRAINING_MULTIPLE_POLICIES.md
|
|
||||||
@export var policy_name: String = "shared_policy"
|
|
||||||
|
|
||||||
var onnx_model: ONNXModel
|
|
||||||
|
|
||||||
var heuristic := "human"
|
|
||||||
var done := false
|
|
||||||
var reward := 0.0
|
|
||||||
var n_steps := 0
|
|
||||||
var needs_reset := false
|
|
||||||
|
|
||||||
var _player: Node3D
|
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
add_to_group("AGENT")
|
|
||||||
|
|
||||||
|
|
||||||
func init(player: Node3D):
|
|
||||||
_player = player
|
|
||||||
|
|
||||||
|
|
||||||
#-- Methods that need implementing using the "extend script" option in Godot --#
|
|
||||||
func get_obs() -> Dictionary:
|
|
||||||
assert(false, "the get_obs method is not implemented when extending from ai_controller")
|
|
||||||
return {"obs": []}
|
|
||||||
|
|
||||||
|
|
||||||
func get_reward() -> float:
|
|
||||||
assert(false, "the get_reward method is not implemented when extending from ai_controller")
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
|
|
||||||
func get_action_space() -> Dictionary:
|
|
||||||
assert(
|
|
||||||
false,
|
|
||||||
"the get_action_space method is not implemented when extending from ai_controller"
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"example_actions_continous": {"size": 2, "action_type": "continuous"},
|
|
||||||
"example_actions_discrete": {"size": 2, "action_type": "discrete"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func set_action(action) -> void:
|
|
||||||
assert(false, "the set_action method is not implemented when extending from ai_controller")
|
|
||||||
|
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
|
|
||||||
#-- Methods that sometimes need implementing using the "extend script" option in Godot --#
|
|
||||||
# Only needed if you are recording expert demos with this AIController
|
|
||||||
func get_action() -> Array:
|
|
||||||
assert(false, "the get_action method is not implemented in extended AIController but demo_recorder is used")
|
|
||||||
return []
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta):
|
|
||||||
n_steps += 1
|
|
||||||
if n_steps > reset_after:
|
|
||||||
needs_reset = true
|
|
||||||
|
|
||||||
|
|
||||||
func get_obs_space():
|
|
||||||
# may need overriding if the obs space is complex
|
|
||||||
var obs = get_obs()
|
|
||||||
return {
|
|
||||||
"obs": {"size": [len(obs["obs"])], "space": "box"},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
n_steps = 0
|
|
||||||
needs_reset = false
|
|
||||||
|
|
||||||
|
|
||||||
func reset_if_done():
|
|
||||||
if done:
|
|
||||||
reset()
|
|
||||||
|
|
||||||
|
|
||||||
func set_heuristic(h):
|
|
||||||
# sets the heuristic from "human" or "model" nothing to change here
|
|
||||||
heuristic = h
|
|
||||||
|
|
||||||
|
|
||||||
func get_done():
|
|
||||||
return done
|
|
||||||
|
|
||||||
|
|
||||||
func set_done_false():
|
|
||||||
done = false
|
|
||||||
|
|
||||||
|
|
||||||
func zero_reward():
|
|
||||||
reward = 0.0
|
|
|
@ -1,16 +0,0 @@
|
||||||
@tool
|
|
||||||
extends EditorPlugin
|
|
||||||
|
|
||||||
|
|
||||||
func _enter_tree():
|
|
||||||
# Initialization of the plugin goes here.
|
|
||||||
# Add the new type with a name, a parent type, a script and an icon.
|
|
||||||
add_custom_type("Sync", "Node", preload("sync.gd"), preload("icon.png"))
|
|
||||||
#add_custom_type("RaycastSensor2D2", "Node", preload("raycast_sensor_2d.gd"), preload("icon.png"))
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree():
|
|
||||||
# Clean-up of the plugin goes here.
|
|
||||||
# Always remember to remove it from the engine when deactivated.
|
|
||||||
remove_custom_type("Sync")
|
|
||||||
#remove_custom_type("RaycastSensor2D2")
|
|
Before Width: | Height: | Size: 198 B |
|
@ -1,109 +0,0 @@
|
||||||
using Godot;
|
|
||||||
using Microsoft.ML.OnnxRuntime;
|
|
||||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GodotONNX
|
|
||||||
{
|
|
||||||
/// <include file='docs/ONNXInference.xml' path='docs/members[@name="ONNXInference"]/ONNXInference/*'/>
|
|
||||||
public partial class ONNXInference : GodotObject
|
|
||||||
{
|
|
||||||
|
|
||||||
private InferenceSession session;
|
|
||||||
/// <summary>
|
|
||||||
/// Path to the ONNX model. Use Initialize to change it.
|
|
||||||
/// </summary>
|
|
||||||
private string modelPath;
|
|
||||||
private int batchSize;
|
|
||||||
|
|
||||||
private SessionOptions SessionOpt;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// init function
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Path"></param>
|
|
||||||
/// <param name="BatchSize"></param>
|
|
||||||
/// <returns>Returns the output size of the model</returns>
|
|
||||||
public int Initialize(string Path, int BatchSize)
|
|
||||||
{
|
|
||||||
modelPath = Path;
|
|
||||||
batchSize = BatchSize;
|
|
||||||
SessionOpt = SessionConfigurator.MakeConfiguredSessionOptions();
|
|
||||||
session = LoadModel(modelPath);
|
|
||||||
return session.OutputMetadata["output"].Dimensions[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <include file='docs/ONNXInference.xml' path='docs/members[@name="ONNXInference"]/Run/*'/>
|
|
||||||
public Godot.Collections.Dictionary<string, Godot.Collections.Array<float>> RunInference(Godot.Collections.Array<float> obs, int state_ins)
|
|
||||||
{
|
|
||||||
//Current model: Any (Godot Rl Agents)
|
|
||||||
//Expects a tensor of shape [batch_size, input_size] type float named obs and a tensor of shape [batch_size] type float named state_ins
|
|
||||||
|
|
||||||
//Fill the input tensors
|
|
||||||
// create span from inputSize
|
|
||||||
var span = new float[obs.Count]; //There's probably a better way to do this
|
|
||||||
for (int i = 0; i < obs.Count; i++)
|
|
||||||
{
|
|
||||||
span[i] = obs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
IReadOnlyCollection<NamedOnnxValue> inputs = new List<NamedOnnxValue>
|
|
||||||
{
|
|
||||||
NamedOnnxValue.CreateFromTensor("obs", new DenseTensor<float>(span, new int[] { batchSize, obs.Count })),
|
|
||||||
NamedOnnxValue.CreateFromTensor("state_ins", new DenseTensor<float>(new float[] { state_ins }, new int[] { batchSize }))
|
|
||||||
};
|
|
||||||
IReadOnlyCollection<string> outputNames = new List<string> { "output", "state_outs" }; //ONNX is sensible to these names, as well as the input names
|
|
||||||
|
|
||||||
IDisposableReadOnlyCollection<DisposableNamedOnnxValue> results;
|
|
||||||
//We do not use "using" here so we get a better exception explaination later
|
|
||||||
try
|
|
||||||
{
|
|
||||||
results = session.Run(inputs, outputNames);
|
|
||||||
}
|
|
||||||
catch (OnnxRuntimeException e)
|
|
||||||
{
|
|
||||||
//This error usually means that the model is not compatible with the input, beacause of the input shape (size)
|
|
||||||
GD.Print("Error at inference: ", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
//Can't convert IEnumerable<float> to Variant, so we have to convert it to an array or something
|
|
||||||
Godot.Collections.Dictionary<string, Godot.Collections.Array<float>> output = new Godot.Collections.Dictionary<string, Godot.Collections.Array<float>>();
|
|
||||||
DisposableNamedOnnxValue output1 = results.First();
|
|
||||||
DisposableNamedOnnxValue output2 = results.Last();
|
|
||||||
Godot.Collections.Array<float> output1Array = new Godot.Collections.Array<float>();
|
|
||||||
Godot.Collections.Array<float> output2Array = new Godot.Collections.Array<float>();
|
|
||||||
|
|
||||||
foreach (float f in output1.AsEnumerable<float>())
|
|
||||||
{
|
|
||||||
output1Array.Add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (float f in output2.AsEnumerable<float>())
|
|
||||||
{
|
|
||||||
output2Array.Add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Add(output1.Name, output1Array);
|
|
||||||
output.Add(output2.Name, output2Array);
|
|
||||||
|
|
||||||
//Output is a dictionary of arrays, ex: { "output" : [0.1, 0.2, 0.3, 0.4, ...], "state_outs" : [0.5, ...]}
|
|
||||||
results.Dispose();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
/// <include file='docs/ONNXInference.xml' path='docs/members[@name="ONNXInference"]/Load/*'/>
|
|
||||||
public InferenceSession LoadModel(string Path)
|
|
||||||
{
|
|
||||||
using Godot.FileAccess file = FileAccess.Open(Path, Godot.FileAccess.ModeFlags.Read);
|
|
||||||
byte[] model = file.GetBuffer((int)file.GetLength());
|
|
||||||
//file.Close(); file.Dispose(); //Close the file, then dispose the reference.
|
|
||||||
return new InferenceSession(model, SessionOpt); //Load the model
|
|
||||||
}
|
|
||||||
public void FreeDisposables()
|
|
||||||
{
|
|
||||||
session.Dispose();
|
|
||||||
SessionOpt.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
using Godot;
|
|
||||||
using Microsoft.ML.OnnxRuntime;
|
|
||||||
|
|
||||||
namespace GodotONNX
|
|
||||||
{
|
|
||||||
/// <include file='docs/SessionConfigurator.xml' path='docs/members[@name="SessionConfigurator"]/SessionConfigurator/*'/>
|
|
||||||
|
|
||||||
public static class SessionConfigurator
|
|
||||||
{
|
|
||||||
public enum ComputeName
|
|
||||||
{
|
|
||||||
CUDA,
|
|
||||||
ROCm,
|
|
||||||
DirectML,
|
|
||||||
CoreML,
|
|
||||||
CPU
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <include file='docs/SessionConfigurator.xml' path='docs/members[@name="SessionConfigurator"]/GetSessionOptions/*'/>
|
|
||||||
public static SessionOptions MakeConfiguredSessionOptions()
|
|
||||||
{
|
|
||||||
SessionOptions sessionOptions = new();
|
|
||||||
SetOptions(sessionOptions);
|
|
||||||
return sessionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetOptions(SessionOptions sessionOptions)
|
|
||||||
{
|
|
||||||
sessionOptions.LogSeverityLevel = OrtLoggingLevel.ORT_LOGGING_LEVEL_WARNING;
|
|
||||||
ApplySystemSpecificOptions(sessionOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <include file='docs/SessionConfigurator.xml' path='docs/members[@name="SessionConfigurator"]/SystemCheck/*'/>
|
|
||||||
static public void ApplySystemSpecificOptions(SessionOptions sessionOptions)
|
|
||||||
{
|
|
||||||
//Most code for this function is verbose only, the only reason it exists is to track
|
|
||||||
//implementation progress of the different compute APIs.
|
|
||||||
|
|
||||||
//December 2022: CUDA is not working.
|
|
||||||
|
|
||||||
string OSName = OS.GetName(); //Get OS Name
|
|
||||||
|
|
||||||
//ComputeName ComputeAPI = ComputeCheck(); //Get Compute API
|
|
||||||
// //TODO: Get CPU architecture
|
|
||||||
|
|
||||||
//Linux can use OpenVINO (C#) on x64 and ROCm on x86 (GDNative/C++)
|
|
||||||
//Windows can use OpenVINO (C#) on x64
|
|
||||||
//TODO: try TensorRT instead of CUDA
|
|
||||||
//TODO: Use OpenVINO for Intel Graphics
|
|
||||||
|
|
||||||
// Temporarily using CPU on all platforms to avoid errors detected with DML
|
|
||||||
ComputeName ComputeAPI = ComputeName.CPU;
|
|
||||||
|
|
||||||
//match OS and Compute API
|
|
||||||
GD.Print($"OS: {OSName} Compute API: {ComputeAPI}");
|
|
||||||
|
|
||||||
// CPU is set by default without appending necessary
|
|
||||||
// sessionOptions.AppendExecutionProvider_CPU(0);
|
|
||||||
|
|
||||||
/*
|
|
||||||
switch (OSName)
|
|
||||||
{
|
|
||||||
case "Windows": //Can use CUDA, DirectML
|
|
||||||
if (ComputeAPI is ComputeName.CUDA)
|
|
||||||
{
|
|
||||||
//CUDA
|
|
||||||
//sessionOptions.AppendExecutionProvider_CUDA(0);
|
|
||||||
//sessionOptions.AppendExecutionProvider_DML(0);
|
|
||||||
}
|
|
||||||
else if (ComputeAPI is ComputeName.DirectML)
|
|
||||||
{
|
|
||||||
//DirectML
|
|
||||||
//sessionOptions.AppendExecutionProvider_DML(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "X11": //Can use CUDA, ROCm
|
|
||||||
if (ComputeAPI is ComputeName.CUDA)
|
|
||||||
{
|
|
||||||
//CUDA
|
|
||||||
//sessionOptions.AppendExecutionProvider_CUDA(0);
|
|
||||||
}
|
|
||||||
if (ComputeAPI is ComputeName.ROCm)
|
|
||||||
{
|
|
||||||
//ROCm, only works on x86
|
|
||||||
//Research indicates that this has to be compiled as a GDNative plugin
|
|
||||||
//GD.Print("ROCm not supported yet, using CPU.");
|
|
||||||
//sessionOptions.AppendExecutionProvider_CPU(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "macOS": //Can use CoreML
|
|
||||||
if (ComputeAPI is ComputeName.CoreML)
|
|
||||||
{ //CoreML
|
|
||||||
//TODO: Needs testing
|
|
||||||
//sessionOptions.AppendExecutionProvider_CoreML(0);
|
|
||||||
//CoreML on ARM64, out of the box, on x64 needs .tar file from GitHub
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
GD.Print("OS not Supported.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <include file='docs/SessionConfigurator.xml' path='docs/members[@name="SessionConfigurator"]/ComputeCheck/*'/>
|
|
||||||
public static ComputeName ComputeCheck()
|
|
||||||
{
|
|
||||||
string adapterName = Godot.RenderingServer.GetVideoAdapterName();
|
|
||||||
//string adapterVendor = Godot.RenderingServer.GetVideoAdapterVendor();
|
|
||||||
adapterName = adapterName.ToUpper(new System.Globalization.CultureInfo(""));
|
|
||||||
//TODO: GPU vendors for MacOS, what do they even use these days?
|
|
||||||
|
|
||||||
if (adapterName.Contains("INTEL"))
|
|
||||||
{
|
|
||||||
return ComputeName.DirectML;
|
|
||||||
}
|
|
||||||
if (adapterName.Contains("AMD") || adapterName.Contains("RADEON"))
|
|
||||||
{
|
|
||||||
return ComputeName.DirectML;
|
|
||||||
}
|
|
||||||
if (adapterName.Contains("NVIDIA"))
|
|
||||||
{
|
|
||||||
return ComputeName.CUDA;
|
|
||||||
}
|
|
||||||
|
|
||||||
GD.Print("Graphics Card not recognized."); //Should use CPU
|
|
||||||
return ComputeName.CPU;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
<docs>
|
|
||||||
<members name="ONNXInference">
|
|
||||||
<ONNXInference>
|
|
||||||
<summary>
|
|
||||||
The main <c>ONNXInference</c> Class that handles the inference process.
|
|
||||||
</summary>
|
|
||||||
</ONNXInference>
|
|
||||||
<Initialize>
|
|
||||||
<summary>
|
|
||||||
Starts the inference process.
|
|
||||||
</summary>
|
|
||||||
<param name="Path">Path to the ONNX model, expects a path inside resources.</param>
|
|
||||||
<param name="BatchSize">How many observations will the model recieve.</param>
|
|
||||||
</Initialize>
|
|
||||||
<Run>
|
|
||||||
<summary>
|
|
||||||
Runs the given input through the model and returns the output.
|
|
||||||
</summary>
|
|
||||||
<param name="obs">Dictionary containing all observations.</param>
|
|
||||||
<param name="state_ins">How many different agents are creating these observations.</param>
|
|
||||||
<returns>A Dictionary of arrays, containing instructions based on the observations.</returns>
|
|
||||||
</Run>
|
|
||||||
<Load>
|
|
||||||
<summary>
|
|
||||||
Loads the given model into the inference process, using the best Execution provider available.
|
|
||||||
</summary>
|
|
||||||
<param name="Path">Path to the ONNX model, expects a path inside resources.</param>
|
|
||||||
<returns>InferenceSession ready to run.</returns>
|
|
||||||
</Load>
|
|
||||||
</members>
|
|
||||||
</docs>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<docs>
|
|
||||||
<members name="SessionConfigurator">
|
|
||||||
<SessionConfigurator>
|
|
||||||
<summary>
|
|
||||||
The main <c>SessionConfigurator</c> Class that handles the execution options and providers for the inference process.
|
|
||||||
</summary>
|
|
||||||
</SessionConfigurator>
|
|
||||||
<GetSessionOptions>
|
|
||||||
<summary>
|
|
||||||
Creates a SessionOptions with all available execution providers.
|
|
||||||
</summary>
|
|
||||||
<returns>SessionOptions with all available execution providers.</returns>
|
|
||||||
</GetSessionOptions>
|
|
||||||
<SystemCheck>
|
|
||||||
<summary>
|
|
||||||
Appends any execution provider available in the current system.
|
|
||||||
</summary>
|
|
||||||
<remarks>
|
|
||||||
This function is mainly verbose for tracking implementation progress of different compute APIs.
|
|
||||||
</remarks>
|
|
||||||
</SystemCheck>
|
|
||||||
<ComputeCheck>
|
|
||||||
<summary>
|
|
||||||
Checks for available GPUs.
|
|
||||||
</summary>
|
|
||||||
<returns>An integer identifier for each compute platform.</returns>
|
|
||||||
</ComputeCheck>
|
|
||||||
</members>
|
|
||||||
</docs>
|
|
|
@ -1,51 +0,0 @@
|
||||||
extends Resource
|
|
||||||
class_name ONNXModel
|
|
||||||
var inferencer_script = load("res://addons/godot_rl_agents/onnx/csharp/ONNXInference.cs")
|
|
||||||
|
|
||||||
var inferencer = null
|
|
||||||
|
|
||||||
## How many action values the model outputs
|
|
||||||
var action_output_size: int
|
|
||||||
|
|
||||||
## Used to differentiate models
|
|
||||||
## that only output continuous action mean (e.g. sb3, cleanrl export)
|
|
||||||
## versus models that output mean and logstd (e.g. rllib export)
|
|
||||||
var action_means_only: bool
|
|
||||||
|
|
||||||
## Whether action_means_value has been set already for this model
|
|
||||||
var action_means_only_set: bool
|
|
||||||
|
|
||||||
# Must provide the path to the model and the batch size
|
|
||||||
func _init(model_path, batch_size):
|
|
||||||
inferencer = inferencer_script.new()
|
|
||||||
action_output_size = inferencer.Initialize(model_path, batch_size)
|
|
||||||
|
|
||||||
# This function is the one that will be called from the game,
|
|
||||||
# requires the observation as an array and the state_ins as an int
|
|
||||||
# returns an Array containing the action the model takes.
|
|
||||||
func run_inference(obs: Array, state_ins: int) -> Dictionary:
|
|
||||||
if inferencer == null:
|
|
||||||
printerr("Inferencer not initialized")
|
|
||||||
return {}
|
|
||||||
return inferencer.RunInference(obs, state_ins)
|
|
||||||
|
|
||||||
|
|
||||||
func _notification(what):
|
|
||||||
if what == NOTIFICATION_PREDELETE:
|
|
||||||
inferencer.FreeDisposables()
|
|
||||||
inferencer.free()
|
|
||||||
|
|
||||||
# Check whether agent uses a continuous actions model with only action means or not
|
|
||||||
func set_action_means_only(agent_action_space):
|
|
||||||
action_means_only_set = true
|
|
||||||
var continuous_only: bool = true
|
|
||||||
var continuous_actions: int
|
|
||||||
for action in agent_action_space:
|
|
||||||
if not agent_action_space[action]["action_type"] == "continuous":
|
|
||||||
continuous_only = false
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continuous_actions += agent_action_space[action]["size"]
|
|
||||||
if continuous_only:
|
|
||||||
if continuous_actions == action_output_size:
|
|
||||||
action_means_only = true
|
|
|
@ -1,7 +0,0 @@
|
||||||
[plugin]
|
|
||||||
|
|
||||||
name="GodotRLAgents"
|
|
||||||
description="Custom nodes for the godot rl agents toolkit "
|
|
||||||
author="Edward Beeching"
|
|
||||||
version="0.1"
|
|
||||||
script="godot_rl_agents.gd"
|
|
|
@ -1,48 +0,0 @@
|
||||||
[gd_scene load_steps=5 format=3 uid="uid://ddeq7mn1ealyc"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/godot_rl_agents/sensors/sensors_2d/RaycastSensor2D.gd" id="1"]
|
|
||||||
|
|
||||||
[sub_resource type="GDScript" id="2"]
|
|
||||||
script/source = "extends Node2D
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
|
||||||
print(\"step start\")
|
|
||||||
|
|
||||||
"
|
|
||||||
|
|
||||||
[sub_resource type="GDScript" id="1"]
|
|
||||||
script/source = "extends RayCast2D
|
|
||||||
|
|
||||||
var steps = 1
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
|
||||||
print(\"processing raycast\")
|
|
||||||
steps += 1
|
|
||||||
if steps % 2:
|
|
||||||
force_raycast_update()
|
|
||||||
|
|
||||||
print(is_colliding())
|
|
||||||
"
|
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="3"]
|
|
||||||
|
|
||||||
[node name="ExampleRaycastSensor2D" type="Node2D"]
|
|
||||||
script = SubResource("2")
|
|
||||||
|
|
||||||
[node name="ExampleAgent" type="Node2D" parent="."]
|
|
||||||
position = Vector2(573, 314)
|
|
||||||
rotation = 0.286234
|
|
||||||
|
|
||||||
[node name="RaycastSensor2D" type="Node2D" parent="ExampleAgent"]
|
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="TestRayCast2D" type="RayCast2D" parent="."]
|
|
||||||
script = SubResource("1")
|
|
||||||
|
|
||||||
[node name="StaticBody2D" type="StaticBody2D" parent="."]
|
|
||||||
position = Vector2(1, 52)
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"]
|
|
||||||
shape = SubResource("3")
|
|
|
@ -1,235 +0,0 @@
|
||||||
@tool
|
|
||||||
extends ISensor2D
|
|
||||||
class_name GridSensor2D
|
|
||||||
|
|
||||||
@export var debug_view := false:
|
|
||||||
get:
|
|
||||||
return debug_view
|
|
||||||
set(value):
|
|
||||||
debug_view = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_flags_2d_physics var detection_mask := 0:
|
|
||||||
get:
|
|
||||||
return detection_mask
|
|
||||||
set(value):
|
|
||||||
detection_mask = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_areas := false:
|
|
||||||
get:
|
|
||||||
return collide_with_areas
|
|
||||||
set(value):
|
|
||||||
collide_with_areas = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_bodies := true:
|
|
||||||
get:
|
|
||||||
return collide_with_bodies
|
|
||||||
set(value):
|
|
||||||
collide_with_bodies = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 200, 0.1) var cell_width := 20.0:
|
|
||||||
get:
|
|
||||||
return cell_width
|
|
||||||
set(value):
|
|
||||||
cell_width = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 200, 0.1) var cell_height := 20.0:
|
|
||||||
get:
|
|
||||||
return cell_height
|
|
||||||
set(value):
|
|
||||||
cell_height = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 21, 2, "or_greater") var grid_size_x := 3:
|
|
||||||
get:
|
|
||||||
return grid_size_x
|
|
||||||
set(value):
|
|
||||||
grid_size_x = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 21, 2, "or_greater") var grid_size_y := 3:
|
|
||||||
get:
|
|
||||||
return grid_size_y
|
|
||||||
set(value):
|
|
||||||
grid_size_y = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
var _obs_buffer: PackedFloat64Array
|
|
||||||
var _rectangle_shape: RectangleShape2D
|
|
||||||
var _collision_mapping: Dictionary
|
|
||||||
var _n_layers_per_cell: int
|
|
||||||
|
|
||||||
var _highlighted_cell_color: Color
|
|
||||||
var _standard_cell_color: Color
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation():
|
|
||||||
return _obs_buffer
|
|
||||||
|
|
||||||
|
|
||||||
func _update():
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if is_node_ready():
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_set_colors()
|
|
||||||
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if get_child_count() == 0:
|
|
||||||
_spawn_nodes()
|
|
||||||
else:
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _set_colors() -> void:
|
|
||||||
_standard_cell_color = Color(100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0)
|
|
||||||
_highlighted_cell_color = Color(255.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0)
|
|
||||||
|
|
||||||
|
|
||||||
func _get_collision_mapping() -> Dictionary:
|
|
||||||
# defines which layer is mapped to which cell obs index
|
|
||||||
var total_bits = 0
|
|
||||||
var collision_mapping = {}
|
|
||||||
for i in 32:
|
|
||||||
var bit_mask = 2 ** i
|
|
||||||
if (detection_mask & bit_mask) > 0:
|
|
||||||
collision_mapping[i] = total_bits
|
|
||||||
total_bits += 1
|
|
||||||
|
|
||||||
return collision_mapping
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_nodes():
|
|
||||||
for cell in get_children():
|
|
||||||
cell.name = "_%s" % cell.name # Otherwise naming below will fail
|
|
||||||
cell.queue_free()
|
|
||||||
|
|
||||||
_collision_mapping = _get_collision_mapping()
|
|
||||||
#prints("collision_mapping", _collision_mapping, len(_collision_mapping))
|
|
||||||
# allocate memory for the observations
|
|
||||||
_n_layers_per_cell = len(_collision_mapping)
|
|
||||||
_obs_buffer = PackedFloat64Array()
|
|
||||||
_obs_buffer.resize(grid_size_x * grid_size_y * _n_layers_per_cell)
|
|
||||||
_obs_buffer.fill(0)
|
|
||||||
#prints(len(_obs_buffer), _obs_buffer )
|
|
||||||
|
|
||||||
_rectangle_shape = RectangleShape2D.new()
|
|
||||||
_rectangle_shape.set_size(Vector2(cell_width, cell_height))
|
|
||||||
|
|
||||||
var shift := Vector2(
|
|
||||||
-(grid_size_x / 2) * cell_width,
|
|
||||||
-(grid_size_y / 2) * cell_height,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in grid_size_x:
|
|
||||||
for j in grid_size_y:
|
|
||||||
var cell_position = Vector2(i * cell_width, j * cell_height) + shift
|
|
||||||
_create_cell(i, j, cell_position)
|
|
||||||
|
|
||||||
|
|
||||||
func _create_cell(i: int, j: int, position: Vector2):
|
|
||||||
var cell := Area2D.new()
|
|
||||||
cell.position = position
|
|
||||||
cell.name = "GridCell %s %s" % [i, j]
|
|
||||||
cell.modulate = _standard_cell_color
|
|
||||||
|
|
||||||
if collide_with_areas:
|
|
||||||
cell.area_entered.connect(_on_cell_area_entered.bind(i, j))
|
|
||||||
cell.area_exited.connect(_on_cell_area_exited.bind(i, j))
|
|
||||||
|
|
||||||
if collide_with_bodies:
|
|
||||||
cell.body_entered.connect(_on_cell_body_entered.bind(i, j))
|
|
||||||
cell.body_exited.connect(_on_cell_body_exited.bind(i, j))
|
|
||||||
|
|
||||||
cell.collision_layer = 0
|
|
||||||
cell.collision_mask = detection_mask
|
|
||||||
cell.monitorable = true
|
|
||||||
add_child(cell)
|
|
||||||
cell.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
var col_shape := CollisionShape2D.new()
|
|
||||||
col_shape.shape = _rectangle_shape
|
|
||||||
col_shape.name = "CollisionShape2D"
|
|
||||||
cell.add_child(col_shape)
|
|
||||||
col_shape.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
if debug_view:
|
|
||||||
var quad = MeshInstance2D.new()
|
|
||||||
quad.name = "MeshInstance2D"
|
|
||||||
var quad_mesh = QuadMesh.new()
|
|
||||||
|
|
||||||
quad_mesh.set_size(Vector2(cell_width, cell_height))
|
|
||||||
|
|
||||||
quad.mesh = quad_mesh
|
|
||||||
cell.add_child(quad)
|
|
||||||
quad.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_obs(cell_i: int, cell_j: int, collision_layer: int, entered: bool):
|
|
||||||
for key in _collision_mapping:
|
|
||||||
var bit_mask = 2 ** key
|
|
||||||
if (collision_layer & bit_mask) > 0:
|
|
||||||
var collison_map_index = _collision_mapping[key]
|
|
||||||
|
|
||||||
var obs_index = (
|
|
||||||
(cell_i * grid_size_x * _n_layers_per_cell)
|
|
||||||
+ (cell_j * _n_layers_per_cell)
|
|
||||||
+ collison_map_index
|
|
||||||
)
|
|
||||||
#prints(obs_index, cell_i, cell_j)
|
|
||||||
if entered:
|
|
||||||
_obs_buffer[obs_index] += 1
|
|
||||||
else:
|
|
||||||
_obs_buffer[obs_index] -= 1
|
|
||||||
|
|
||||||
|
|
||||||
func _toggle_cell(cell_i: int, cell_j: int):
|
|
||||||
var cell = get_node_or_null("GridCell %s %s" % [cell_i, cell_j])
|
|
||||||
|
|
||||||
if cell == null:
|
|
||||||
print("cell not found, returning")
|
|
||||||
|
|
||||||
var n_hits = 0
|
|
||||||
var start_index = (cell_i * grid_size_x * _n_layers_per_cell) + (cell_j * _n_layers_per_cell)
|
|
||||||
for i in _n_layers_per_cell:
|
|
||||||
n_hits += _obs_buffer[start_index + i]
|
|
||||||
|
|
||||||
if n_hits > 0:
|
|
||||||
cell.modulate = _highlighted_cell_color
|
|
||||||
else:
|
|
||||||
cell.modulate = _standard_cell_color
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_area_entered(area: Area2D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_area_entered", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, area.collision_layer, true)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
#print(_obs_buffer)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_area_exited(area: Area2D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_area_exited", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, area.collision_layer, false)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_body_entered(body: Node2D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_body_entered", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, body.collision_layer, true)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_body_exited(body: Node2D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_body_exited", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, body.collision_layer, false)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
|
@ -1,25 +0,0 @@
|
||||||
extends Node2D
|
|
||||||
class_name ISensor2D
|
|
||||||
|
|
||||||
var _obs: Array = []
|
|
||||||
var _active := false
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func activate():
|
|
||||||
_active = true
|
|
||||||
|
|
||||||
|
|
||||||
func deactivate():
|
|
||||||
_active = false
|
|
||||||
|
|
||||||
|
|
||||||
func _update_observation():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
pass
|
|
|
@ -1,118 +0,0 @@
|
||||||
@tool
|
|
||||||
extends ISensor2D
|
|
||||||
class_name RaycastSensor2D
|
|
||||||
|
|
||||||
@export_flags_2d_physics var collision_mask := 1:
|
|
||||||
get:
|
|
||||||
return collision_mask
|
|
||||||
set(value):
|
|
||||||
collision_mask = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_areas := false:
|
|
||||||
get:
|
|
||||||
return collide_with_areas
|
|
||||||
set(value):
|
|
||||||
collide_with_areas = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_bodies := true:
|
|
||||||
get:
|
|
||||||
return collide_with_bodies
|
|
||||||
set(value):
|
|
||||||
collide_with_bodies = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var n_rays := 16.0:
|
|
||||||
get:
|
|
||||||
return n_rays
|
|
||||||
set(value):
|
|
||||||
n_rays = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(5, 3000, 5.0) var ray_length := 200:
|
|
||||||
get:
|
|
||||||
return ray_length
|
|
||||||
set(value):
|
|
||||||
ray_length = value
|
|
||||||
_update()
|
|
||||||
@export_range(5, 360, 5.0) var cone_width := 360.0:
|
|
||||||
get:
|
|
||||||
return cone_width
|
|
||||||
set(value):
|
|
||||||
cone_width = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var debug_draw := true:
|
|
||||||
get:
|
|
||||||
return debug_draw
|
|
||||||
set(value):
|
|
||||||
debug_draw = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
var _angles = []
|
|
||||||
var rays := []
|
|
||||||
|
|
||||||
|
|
||||||
func _update():
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if debug_draw:
|
|
||||||
_spawn_nodes()
|
|
||||||
else:
|
|
||||||
for ray in get_children():
|
|
||||||
if ray is RayCast2D:
|
|
||||||
remove_child(ray)
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_nodes():
|
|
||||||
for ray in rays:
|
|
||||||
ray.queue_free()
|
|
||||||
rays = []
|
|
||||||
|
|
||||||
_angles = []
|
|
||||||
var step = cone_width / (n_rays)
|
|
||||||
var start = step / 2 - cone_width / 2
|
|
||||||
|
|
||||||
for i in n_rays:
|
|
||||||
var angle = start + i * step
|
|
||||||
var ray = RayCast2D.new()
|
|
||||||
ray.set_target_position(
|
|
||||||
Vector2(ray_length * cos(deg_to_rad(angle)), ray_length * sin(deg_to_rad(angle)))
|
|
||||||
)
|
|
||||||
ray.set_name("node_" + str(i))
|
|
||||||
ray.enabled = false
|
|
||||||
ray.collide_with_areas = collide_with_areas
|
|
||||||
ray.collide_with_bodies = collide_with_bodies
|
|
||||||
ray.collision_mask = collision_mask
|
|
||||||
add_child(ray)
|
|
||||||
rays.append(ray)
|
|
||||||
|
|
||||||
_angles.append(start + i * step)
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation() -> Array:
|
|
||||||
return self.calculate_raycasts()
|
|
||||||
|
|
||||||
|
|
||||||
func calculate_raycasts() -> Array:
|
|
||||||
var result = []
|
|
||||||
for ray in rays:
|
|
||||||
ray.enabled = true
|
|
||||||
ray.force_raycast_update()
|
|
||||||
var distance = _get_raycast_distance(ray)
|
|
||||||
result.append(distance)
|
|
||||||
ray.enabled = false
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
func _get_raycast_distance(ray: RayCast2D) -> float:
|
|
||||||
if !ray.is_colliding():
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
var distance = (global_position - ray.get_collision_point()).length()
|
|
||||||
distance = clamp(distance, 0.0, ray_length)
|
|
||||||
return (ray_length - distance) / ray_length
|
|
|
@ -1,7 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://drvfihk5esgmv"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/godot_rl_agents/sensors/sensors_2d/RaycastSensor2D.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="RaycastSensor2D" type="Node2D"]
|
|
||||||
script = ExtResource("1")
|
|
||||||
n_rays = 17.0
|
|
|
@ -1,6 +0,0 @@
|
||||||
[gd_scene format=3 uid="uid://biu787qh4woik"]
|
|
||||||
|
|
||||||
[node name="ExampleRaycastSensor3D" type="Node3D"]
|
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="."]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.804183, 0, 2.70146)
|
|
|
@ -1,258 +0,0 @@
|
||||||
@tool
|
|
||||||
extends ISensor3D
|
|
||||||
class_name GridSensor3D
|
|
||||||
|
|
||||||
@export var debug_view := false:
|
|
||||||
get:
|
|
||||||
return debug_view
|
|
||||||
set(value):
|
|
||||||
debug_view = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_flags_3d_physics var detection_mask := 0:
|
|
||||||
get:
|
|
||||||
return detection_mask
|
|
||||||
set(value):
|
|
||||||
detection_mask = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_areas := false:
|
|
||||||
get:
|
|
||||||
return collide_with_areas
|
|
||||||
set(value):
|
|
||||||
collide_with_areas = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_bodies := false:
|
|
||||||
# NOTE! The sensor will not detect StaticBody3D, add an area to static bodies to detect them
|
|
||||||
get:
|
|
||||||
return collide_with_bodies
|
|
||||||
set(value):
|
|
||||||
collide_with_bodies = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(0.1, 2, 0.1) var cell_width := 1.0:
|
|
||||||
get:
|
|
||||||
return cell_width
|
|
||||||
set(value):
|
|
||||||
cell_width = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(0.1, 2, 0.1) var cell_height := 1.0:
|
|
||||||
get:
|
|
||||||
return cell_height
|
|
||||||
set(value):
|
|
||||||
cell_height = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 21, 2, "or_greater") var grid_size_x := 3:
|
|
||||||
get:
|
|
||||||
return grid_size_x
|
|
||||||
set(value):
|
|
||||||
grid_size_x = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export_range(1, 21, 2, "or_greater") var grid_size_z := 3:
|
|
||||||
get:
|
|
||||||
return grid_size_z
|
|
||||||
set(value):
|
|
||||||
grid_size_z = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
var _obs_buffer: PackedFloat64Array
|
|
||||||
var _box_shape: BoxShape3D
|
|
||||||
var _collision_mapping: Dictionary
|
|
||||||
var _n_layers_per_cell: int
|
|
||||||
|
|
||||||
var _highlighted_box_material: StandardMaterial3D
|
|
||||||
var _standard_box_material: StandardMaterial3D
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation():
|
|
||||||
return _obs_buffer
|
|
||||||
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
_obs_buffer.fill(0)
|
|
||||||
|
|
||||||
|
|
||||||
func _update():
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if is_node_ready():
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
_make_materials()
|
|
||||||
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if get_child_count() == 0:
|
|
||||||
_spawn_nodes()
|
|
||||||
else:
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _make_materials() -> void:
|
|
||||||
if _highlighted_box_material != null and _standard_box_material != null:
|
|
||||||
return
|
|
||||||
|
|
||||||
_standard_box_material = StandardMaterial3D.new()
|
|
||||||
_standard_box_material.set_transparency(1) # ALPHA
|
|
||||||
_standard_box_material.albedo_color = Color(
|
|
||||||
100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0
|
|
||||||
)
|
|
||||||
|
|
||||||
_highlighted_box_material = StandardMaterial3D.new()
|
|
||||||
_highlighted_box_material.set_transparency(1) # ALPHA
|
|
||||||
_highlighted_box_material.albedo_color = Color(
|
|
||||||
255.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0, 100.0 / 255.0
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _get_collision_mapping() -> Dictionary:
|
|
||||||
# defines which layer is mapped to which cell obs index
|
|
||||||
var total_bits = 0
|
|
||||||
var collision_mapping = {}
|
|
||||||
for i in 32:
|
|
||||||
var bit_mask = 2 ** i
|
|
||||||
if (detection_mask & bit_mask) > 0:
|
|
||||||
collision_mapping[i] = total_bits
|
|
||||||
total_bits += 1
|
|
||||||
|
|
||||||
return collision_mapping
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_nodes():
|
|
||||||
for cell in get_children():
|
|
||||||
cell.name = "_%s" % cell.name # Otherwise naming below will fail
|
|
||||||
cell.queue_free()
|
|
||||||
|
|
||||||
_collision_mapping = _get_collision_mapping()
|
|
||||||
#prints("collision_mapping", _collision_mapping, len(_collision_mapping))
|
|
||||||
# allocate memory for the observations
|
|
||||||
_n_layers_per_cell = len(_collision_mapping)
|
|
||||||
_obs_buffer = PackedFloat64Array()
|
|
||||||
_obs_buffer.resize(grid_size_x * grid_size_z * _n_layers_per_cell)
|
|
||||||
_obs_buffer.fill(0)
|
|
||||||
#prints(len(_obs_buffer), _obs_buffer )
|
|
||||||
|
|
||||||
_box_shape = BoxShape3D.new()
|
|
||||||
_box_shape.set_size(Vector3(cell_width, cell_height, cell_width))
|
|
||||||
|
|
||||||
var shift := Vector3(
|
|
||||||
-(grid_size_x / 2) * cell_width,
|
|
||||||
0,
|
|
||||||
-(grid_size_z / 2) * cell_width,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in grid_size_x:
|
|
||||||
for j in grid_size_z:
|
|
||||||
var cell_position = Vector3(i * cell_width, 0.0, j * cell_width) + shift
|
|
||||||
_create_cell(i, j, cell_position)
|
|
||||||
|
|
||||||
|
|
||||||
func _create_cell(i: int, j: int, position: Vector3):
|
|
||||||
var cell := Area3D.new()
|
|
||||||
cell.position = position
|
|
||||||
cell.name = "GridCell %s %s" % [i, j]
|
|
||||||
|
|
||||||
if collide_with_areas:
|
|
||||||
cell.area_entered.connect(_on_cell_area_entered.bind(i, j))
|
|
||||||
cell.area_exited.connect(_on_cell_area_exited.bind(i, j))
|
|
||||||
|
|
||||||
if collide_with_bodies:
|
|
||||||
cell.body_entered.connect(_on_cell_body_entered.bind(i, j))
|
|
||||||
cell.body_exited.connect(_on_cell_body_exited.bind(i, j))
|
|
||||||
|
|
||||||
# cell.body_shape_entered.connect(_on_cell_body_shape_entered.bind(i, j))
|
|
||||||
# cell.body_shape_exited.connect(_on_cell_body_shape_exited.bind(i, j))
|
|
||||||
|
|
||||||
cell.collision_layer = 0
|
|
||||||
cell.collision_mask = detection_mask
|
|
||||||
cell.monitorable = true
|
|
||||||
cell.input_ray_pickable = false
|
|
||||||
add_child(cell)
|
|
||||||
cell.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
var col_shape := CollisionShape3D.new()
|
|
||||||
col_shape.shape = _box_shape
|
|
||||||
col_shape.name = "CollisionShape3D"
|
|
||||||
cell.add_child(col_shape)
|
|
||||||
col_shape.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
if debug_view:
|
|
||||||
var box = MeshInstance3D.new()
|
|
||||||
box.name = "MeshInstance3D"
|
|
||||||
var box_mesh = BoxMesh.new()
|
|
||||||
|
|
||||||
box_mesh.set_size(Vector3(cell_width, cell_height, cell_width))
|
|
||||||
box_mesh.material = _standard_box_material
|
|
||||||
|
|
||||||
box.mesh = box_mesh
|
|
||||||
cell.add_child(box)
|
|
||||||
box.set_owner(get_tree().edited_scene_root)
|
|
||||||
|
|
||||||
|
|
||||||
func _update_obs(cell_i: int, cell_j: int, collision_layer: int, entered: bool):
|
|
||||||
for key in _collision_mapping:
|
|
||||||
var bit_mask = 2 ** key
|
|
||||||
if (collision_layer & bit_mask) > 0:
|
|
||||||
var collison_map_index = _collision_mapping[key]
|
|
||||||
|
|
||||||
var obs_index = (
|
|
||||||
(cell_i * grid_size_x * _n_layers_per_cell)
|
|
||||||
+ (cell_j * _n_layers_per_cell)
|
|
||||||
+ collison_map_index
|
|
||||||
)
|
|
||||||
#prints(obs_index, cell_i, cell_j)
|
|
||||||
if entered:
|
|
||||||
_obs_buffer[obs_index] += 1
|
|
||||||
else:
|
|
||||||
_obs_buffer[obs_index] -= 1
|
|
||||||
|
|
||||||
|
|
||||||
func _toggle_cell(cell_i: int, cell_j: int):
|
|
||||||
var cell = get_node_or_null("GridCell %s %s" % [cell_i, cell_j])
|
|
||||||
|
|
||||||
if cell == null:
|
|
||||||
print("cell not found, returning")
|
|
||||||
|
|
||||||
var n_hits = 0
|
|
||||||
var start_index = (cell_i * grid_size_x * _n_layers_per_cell) + (cell_j * _n_layers_per_cell)
|
|
||||||
for i in _n_layers_per_cell:
|
|
||||||
n_hits += _obs_buffer[start_index + i]
|
|
||||||
|
|
||||||
var cell_mesh = cell.get_node_or_null("MeshInstance3D")
|
|
||||||
if n_hits > 0:
|
|
||||||
cell_mesh.mesh.material = _highlighted_box_material
|
|
||||||
else:
|
|
||||||
cell_mesh.mesh.material = _standard_box_material
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_area_entered(area: Area3D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_area_entered", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, area.collision_layer, true)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
#print(_obs_buffer)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_area_exited(area: Area3D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_area_exited", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, area.collision_layer, false)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_body_entered(body: Node3D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_body_entered", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, body.collision_layer, true)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_cell_body_exited(body: Node3D, cell_i: int, cell_j: int):
|
|
||||||
#prints("_on_cell_body_exited", cell_i, cell_j)
|
|
||||||
_update_obs(cell_i, cell_j, body.collision_layer, false)
|
|
||||||
if debug_view:
|
|
||||||
_toggle_cell(cell_i, cell_j)
|
|
|
@ -1,25 +0,0 @@
|
||||||
extends Node3D
|
|
||||||
class_name ISensor3D
|
|
||||||
|
|
||||||
var _obs: Array = []
|
|
||||||
var _active := false
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func activate():
|
|
||||||
_active = true
|
|
||||||
|
|
||||||
|
|
||||||
func deactivate():
|
|
||||||
_active = false
|
|
||||||
|
|
||||||
|
|
||||||
func _update_observation():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
func reset():
|
|
||||||
pass
|
|
|
@ -1,21 +0,0 @@
|
||||||
extends Node3D
|
|
||||||
class_name RGBCameraSensor3D
|
|
||||||
var camera_pixels = null
|
|
||||||
|
|
||||||
@onready var camera_texture := $Control/TextureRect/CameraTexture as Sprite2D
|
|
||||||
@onready var sub_viewport := $SubViewport as SubViewport
|
|
||||||
|
|
||||||
|
|
||||||
func get_camera_pixel_encoding():
|
|
||||||
return camera_texture.get_texture().get_image().get_data().hex_encode()
|
|
||||||
|
|
||||||
|
|
||||||
func get_camera_shape() -> Array:
|
|
||||||
assert(
|
|
||||||
sub_viewport.size.x >= 36 and sub_viewport.size.y >= 36,
|
|
||||||
"SubViewport size must be 36x36 or larger."
|
|
||||||
)
|
|
||||||
if sub_viewport.transparent_bg:
|
|
||||||
return [4, sub_viewport.size.y, sub_viewport.size.x]
|
|
||||||
else:
|
|
||||||
return [3, sub_viewport.size.y, sub_viewport.size.x]
|
|
|
@ -1,41 +0,0 @@
|
||||||
[gd_scene load_steps=3 format=3 uid="uid://baaywi3arsl2m"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/godot_rl_agents/sensors/sensors_3d/RGBCameraSensor3D.gd" id="1"]
|
|
||||||
|
|
||||||
[sub_resource type="ViewportTexture" id="1"]
|
|
||||||
viewport_path = NodePath("SubViewport")
|
|
||||||
|
|
||||||
[node name="RGBCameraSensor3D" type="Node3D"]
|
|
||||||
script = ExtResource("1")
|
|
||||||
|
|
||||||
[node name="RemoteTransform3D" type="RemoteTransform3D" parent="."]
|
|
||||||
remote_path = NodePath("../SubViewport/Camera3D")
|
|
||||||
|
|
||||||
[node name="SubViewport" type="SubViewport" parent="."]
|
|
||||||
size = Vector2i(32, 32)
|
|
||||||
render_target_update_mode = 3
|
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="SubViewport"]
|
|
||||||
near = 0.5
|
|
||||||
|
|
||||||
[node name="Control" type="Control" parent="."]
|
|
||||||
layout_mode = 3
|
|
||||||
anchors_preset = 15
|
|
||||||
anchor_right = 1.0
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 2
|
|
||||||
|
|
||||||
[node name="TextureRect" type="ColorRect" parent="Control"]
|
|
||||||
layout_mode = 0
|
|
||||||
offset_left = 1096.0
|
|
||||||
offset_top = 534.0
|
|
||||||
offset_right = 1114.0
|
|
||||||
offset_bottom = 552.0
|
|
||||||
scale = Vector2(10, 10)
|
|
||||||
color = Color(0.00784314, 0.00784314, 0.00784314, 1)
|
|
||||||
|
|
||||||
[node name="CameraTexture" type="Sprite2D" parent="Control/TextureRect"]
|
|
||||||
texture = SubResource("1")
|
|
||||||
offset = Vector2(9, 9)
|
|
||||||
flip_v = true
|
|
|
@ -1,185 +0,0 @@
|
||||||
@tool
|
|
||||||
extends ISensor3D
|
|
||||||
class_name RayCastSensor3D
|
|
||||||
@export_flags_3d_physics var collision_mask = 1:
|
|
||||||
get:
|
|
||||||
return collision_mask
|
|
||||||
set(value):
|
|
||||||
collision_mask = value
|
|
||||||
_update()
|
|
||||||
@export_flags_3d_physics var boolean_class_mask = 1:
|
|
||||||
get:
|
|
||||||
return boolean_class_mask
|
|
||||||
set(value):
|
|
||||||
boolean_class_mask = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var n_rays_width := 6.0:
|
|
||||||
get:
|
|
||||||
return n_rays_width
|
|
||||||
set(value):
|
|
||||||
n_rays_width = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var n_rays_height := 6.0:
|
|
||||||
get:
|
|
||||||
return n_rays_height
|
|
||||||
set(value):
|
|
||||||
n_rays_height = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var ray_length := 10.0:
|
|
||||||
get:
|
|
||||||
return ray_length
|
|
||||||
set(value):
|
|
||||||
ray_length = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var cone_width := 60.0:
|
|
||||||
get:
|
|
||||||
return cone_width
|
|
||||||
set(value):
|
|
||||||
cone_width = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var cone_height := 60.0:
|
|
||||||
get:
|
|
||||||
return cone_height
|
|
||||||
set(value):
|
|
||||||
cone_height = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_areas := false:
|
|
||||||
get:
|
|
||||||
return collide_with_areas
|
|
||||||
set(value):
|
|
||||||
collide_with_areas = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var collide_with_bodies := true:
|
|
||||||
get:
|
|
||||||
return collide_with_bodies
|
|
||||||
set(value):
|
|
||||||
collide_with_bodies = value
|
|
||||||
_update()
|
|
||||||
|
|
||||||
@export var class_sensor := false
|
|
||||||
|
|
||||||
var rays := []
|
|
||||||
var geo = null
|
|
||||||
|
|
||||||
|
|
||||||
func _update():
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if is_node_ready():
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
if Engine.is_editor_hint():
|
|
||||||
if get_child_count() == 0:
|
|
||||||
_spawn_nodes()
|
|
||||||
else:
|
|
||||||
_spawn_nodes()
|
|
||||||
|
|
||||||
|
|
||||||
func _spawn_nodes():
|
|
||||||
print("spawning nodes")
|
|
||||||
for ray in get_children():
|
|
||||||
ray.queue_free()
|
|
||||||
if geo:
|
|
||||||
geo.clear()
|
|
||||||
#$Lines.remove_points()
|
|
||||||
rays = []
|
|
||||||
|
|
||||||
var horizontal_step = cone_width / (n_rays_width)
|
|
||||||
var vertical_step = cone_height / (n_rays_height)
|
|
||||||
|
|
||||||
var horizontal_start = horizontal_step / 2 - cone_width / 2
|
|
||||||
var vertical_start = vertical_step / 2 - cone_height / 2
|
|
||||||
|
|
||||||
var points = []
|
|
||||||
|
|
||||||
for i in n_rays_width:
|
|
||||||
for j in n_rays_height:
|
|
||||||
var angle_w = horizontal_start + i * horizontal_step
|
|
||||||
var angle_h = vertical_start + j * vertical_step
|
|
||||||
#angle_h = 0.0
|
|
||||||
var ray = RayCast3D.new()
|
|
||||||
var cast_to = to_spherical_coords(ray_length, angle_w, angle_h)
|
|
||||||
ray.set_target_position(cast_to)
|
|
||||||
|
|
||||||
points.append(cast_to)
|
|
||||||
|
|
||||||
ray.set_name("node_" + str(i) + " " + str(j))
|
|
||||||
ray.enabled = true
|
|
||||||
ray.collide_with_bodies = collide_with_bodies
|
|
||||||
ray.collide_with_areas = collide_with_areas
|
|
||||||
ray.collision_mask = collision_mask
|
|
||||||
add_child(ray)
|
|
||||||
ray.set_owner(get_tree().edited_scene_root)
|
|
||||||
rays.append(ray)
|
|
||||||
ray.force_raycast_update()
|
|
||||||
|
|
||||||
|
|
||||||
# if Engine.editor_hint:
|
|
||||||
# _create_debug_lines(points)
|
|
||||||
|
|
||||||
|
|
||||||
func _create_debug_lines(points):
|
|
||||||
if not geo:
|
|
||||||
geo = ImmediateMesh.new()
|
|
||||||
add_child(geo)
|
|
||||||
|
|
||||||
geo.clear()
|
|
||||||
geo.begin(Mesh.PRIMITIVE_LINES)
|
|
||||||
for point in points:
|
|
||||||
geo.set_color(Color.AQUA)
|
|
||||||
geo.add_vertex(Vector3.ZERO)
|
|
||||||
geo.add_vertex(point)
|
|
||||||
geo.end()
|
|
||||||
|
|
||||||
|
|
||||||
func display():
|
|
||||||
if geo:
|
|
||||||
geo.display()
|
|
||||||
|
|
||||||
|
|
||||||
func to_spherical_coords(r, inc, azimuth) -> Vector3:
|
|
||||||
return Vector3(
|
|
||||||
r * sin(deg_to_rad(inc)) * cos(deg_to_rad(azimuth)),
|
|
||||||
r * sin(deg_to_rad(azimuth)),
|
|
||||||
r * cos(deg_to_rad(inc)) * cos(deg_to_rad(azimuth))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func get_observation() -> Array:
|
|
||||||
return self.calculate_raycasts()
|
|
||||||
|
|
||||||
|
|
||||||
func calculate_raycasts() -> Array:
|
|
||||||
var result = []
|
|
||||||
for ray in rays:
|
|
||||||
ray.set_enabled(true)
|
|
||||||
ray.force_raycast_update()
|
|
||||||
var distance = _get_raycast_distance(ray)
|
|
||||||
|
|
||||||
result.append(distance)
|
|
||||||
if class_sensor:
|
|
||||||
var hit_class: float = 0
|
|
||||||
if ray.get_collider():
|
|
||||||
var hit_collision_layer = ray.get_collider().collision_layer
|
|
||||||
hit_collision_layer = hit_collision_layer & collision_mask
|
|
||||||
hit_class = (hit_collision_layer & boolean_class_mask) > 0
|
|
||||||
result.append(float(hit_class))
|
|
||||||
ray.set_enabled(false)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
func _get_raycast_distance(ray: RayCast3D) -> float:
|
|
||||||
if !ray.is_colliding():
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
var distance = (global_transform.origin - ray.get_collision_point()).length()
|
|
||||||
distance = clamp(distance, 0.0, ray_length)
|
|
||||||
return (ray_length - distance) / ray_length
|
|
|
@ -1,27 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://b803cbh1fmy66"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://addons/godot_rl_agents/sensors/sensors_3d/RaycastSensor3D.gd" id="1"]
|
|
||||||
|
|
||||||
[node name="RaycastSensor3D" type="Node3D"]
|
|
||||||
script = ExtResource("1")
|
|
||||||
n_rays_width = 4.0
|
|
||||||
n_rays_height = 2.0
|
|
||||||
ray_length = 11.0
|
|
||||||
|
|
||||||
[node name="node_1 0" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(-1.38686, -2.84701, 10.5343)
|
|
||||||
|
|
||||||
[node name="node_1 1" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(-1.38686, 2.84701, 10.5343)
|
|
||||||
|
|
||||||
[node name="node_2 0" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(1.38686, -2.84701, 10.5343)
|
|
||||||
|
|
||||||
[node name="node_2 1" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(1.38686, 2.84701, 10.5343)
|
|
||||||
|
|
||||||
[node name="node_3 0" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(4.06608, -2.84701, 9.81639)
|
|
||||||
|
|
||||||
[node name="node_3 1" type="RayCast3D" parent="."]
|
|
||||||
target_position = Vector3(4.06608, 2.84701, 9.81639)
|
|
|
@ -1,579 +0,0 @@
|
||||||
extends Node
|
|
||||||
|
|
||||||
# --fixed-fps 2000 --disable-render-loop
|
|
||||||
|
|
||||||
enum ControlModes { HUMAN, TRAINING, ONNX_INFERENCE }
|
|
||||||
@export var control_mode: ControlModes = ControlModes.TRAINING
|
|
||||||
@export_range(1, 10, 1, "or_greater") var action_repeat := 8
|
|
||||||
@export_range(0, 10, 0.1, "or_greater") var speed_up := 1.0
|
|
||||||
@export var onnx_model_path := ""
|
|
||||||
|
|
||||||
# Onnx model stored for each requested path
|
|
||||||
var onnx_models: Dictionary
|
|
||||||
|
|
||||||
@onready var start_time = Time.get_ticks_msec()
|
|
||||||
|
|
||||||
const MAJOR_VERSION := "0"
|
|
||||||
const MINOR_VERSION := "7"
|
|
||||||
const DEFAULT_PORT := "11008"
|
|
||||||
const DEFAULT_SEED := "1"
|
|
||||||
var stream: StreamPeerTCP = null
|
|
||||||
var connected = false
|
|
||||||
var message_center
|
|
||||||
var should_connect = true
|
|
||||||
|
|
||||||
var all_agents: Array
|
|
||||||
var agents_training: Array
|
|
||||||
## Policy name of each agent, for use with multi-policy multi-agent RL cases
|
|
||||||
var agents_training_policy_names: Array[String] = ["shared_policy"]
|
|
||||||
var agents_inference: Array
|
|
||||||
var agents_heuristic: Array
|
|
||||||
|
|
||||||
## For recording expert demos
|
|
||||||
var agent_demo_record: Node
|
|
||||||
## File path for writing recorded trajectories
|
|
||||||
var expert_demo_save_path: String
|
|
||||||
## Stores recorded trajectories
|
|
||||||
var demo_trajectories: Array
|
|
||||||
## A trajectory includes obs: Array, acts: Array, terminal (set in Python env instead)
|
|
||||||
var current_demo_trajectory: Array
|
|
||||||
|
|
||||||
var need_to_send_obs = false
|
|
||||||
var args = null
|
|
||||||
var initialized = false
|
|
||||||
var just_reset = false
|
|
||||||
var onnx_model = null
|
|
||||||
var n_action_steps = 0
|
|
||||||
|
|
||||||
var _action_space_training: Array[Dictionary] = []
|
|
||||||
var _action_space_inference: Array[Dictionary] = []
|
|
||||||
var _obs_space_training: Array[Dictionary] = []
|
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
|
||||||
func _ready():
|
|
||||||
await get_tree().root.ready
|
|
||||||
get_tree().set_pause(true)
|
|
||||||
_initialize()
|
|
||||||
await get_tree().create_timer(1.0).timeout
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize():
|
|
||||||
_get_agents()
|
|
||||||
args = _get_args()
|
|
||||||
Engine.physics_ticks_per_second = _get_speedup() * 60 # Replace with function body.
|
|
||||||
Engine.time_scale = _get_speedup() * 1.0
|
|
||||||
prints(
|
|
||||||
"physics ticks",
|
|
||||||
Engine.physics_ticks_per_second,
|
|
||||||
Engine.time_scale,
|
|
||||||
_get_speedup(),
|
|
||||||
speed_up
|
|
||||||
)
|
|
||||||
|
|
||||||
_set_heuristic("human", all_agents)
|
|
||||||
|
|
||||||
_initialize_training_agents()
|
|
||||||
_initialize_inference_agents()
|
|
||||||
_initialize_demo_recording()
|
|
||||||
|
|
||||||
_set_seed()
|
|
||||||
_set_action_repeat()
|
|
||||||
initialized = true
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize_training_agents():
|
|
||||||
if agents_training.size() > 0:
|
|
||||||
_obs_space_training.resize(agents_training.size())
|
|
||||||
_action_space_training.resize(agents_training.size())
|
|
||||||
for agent_idx in range(0, agents_training.size()):
|
|
||||||
_obs_space_training[agent_idx] = agents_training[agent_idx].get_obs_space()
|
|
||||||
_action_space_training[agent_idx] = agents_training[agent_idx].get_action_space()
|
|
||||||
connected = connect_to_server()
|
|
||||||
if connected:
|
|
||||||
_set_heuristic("model", agents_training)
|
|
||||||
_handshake()
|
|
||||||
_send_env_info()
|
|
||||||
else:
|
|
||||||
push_warning(
|
|
||||||
"Couldn't connect to Python server, using human controls instead. ",
|
|
||||||
"Did you start the training server using e.g. `gdrl` from the console?"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize_inference_agents():
|
|
||||||
if agents_inference.size() > 0:
|
|
||||||
if control_mode == ControlModes.ONNX_INFERENCE:
|
|
||||||
assert(
|
|
||||||
FileAccess.file_exists(onnx_model_path),
|
|
||||||
"Onnx Model Path set on Sync node does not exist: %s" % onnx_model_path
|
|
||||||
)
|
|
||||||
onnx_models[onnx_model_path] = ONNXModel.new(onnx_model_path, 1)
|
|
||||||
|
|
||||||
for agent in agents_inference:
|
|
||||||
var action_space = agent.get_action_space()
|
|
||||||
_action_space_inference.append(action_space)
|
|
||||||
|
|
||||||
var agent_onnx_model: ONNXModel
|
|
||||||
if agent.onnx_model_path.is_empty():
|
|
||||||
assert(
|
|
||||||
onnx_models.has(onnx_model_path),
|
|
||||||
(
|
|
||||||
"Node %s has no onnx model path set " % agent.get_path()
|
|
||||||
+ "and sync node's control mode is not set to OnnxInference. "
|
|
||||||
+ "Either add the path to the AIController, "
|
|
||||||
+ "or if you want to use the path set on sync node instead, "
|
|
||||||
+ "set control mode to OnnxInference."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
prints(
|
|
||||||
"Info: AIController %s" % agent.get_path(),
|
|
||||||
"has no onnx model path set.",
|
|
||||||
"Using path set on the sync node instead."
|
|
||||||
)
|
|
||||||
agent_onnx_model = onnx_models[onnx_model_path]
|
|
||||||
else:
|
|
||||||
if not onnx_models.has(agent.onnx_model_path):
|
|
||||||
assert(
|
|
||||||
FileAccess.file_exists(agent.onnx_model_path),
|
|
||||||
(
|
|
||||||
"Onnx Model Path set on %s node does not exist: %s"
|
|
||||||
% [agent.get_path(), agent.onnx_model_path]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
onnx_models[agent.onnx_model_path] = ONNXModel.new(agent.onnx_model_path, 1)
|
|
||||||
agent_onnx_model = onnx_models[agent.onnx_model_path]
|
|
||||||
|
|
||||||
agent.onnx_model = agent_onnx_model
|
|
||||||
if not agent_onnx_model.action_means_only_set:
|
|
||||||
agent_onnx_model.set_action_means_only(action_space)
|
|
||||||
|
|
||||||
_set_heuristic("model", agents_inference)
|
|
||||||
|
|
||||||
|
|
||||||
func _initialize_demo_recording():
|
|
||||||
if agent_demo_record:
|
|
||||||
expert_demo_save_path = agent_demo_record.expert_demo_save_path
|
|
||||||
assert(
|
|
||||||
not expert_demo_save_path.is_empty(),
|
|
||||||
"Expert demo save path set in %s is empty." % agent_demo_record.get_path()
|
|
||||||
)
|
|
||||||
|
|
||||||
InputMap.add_action("RemoveLastDemoEpisode")
|
|
||||||
InputMap.action_add_event(
|
|
||||||
"RemoveLastDemoEpisode", agent_demo_record.remove_last_episode_key
|
|
||||||
)
|
|
||||||
current_demo_trajectory.resize(2)
|
|
||||||
current_demo_trajectory[0] = []
|
|
||||||
current_demo_trajectory[1] = []
|
|
||||||
agent_demo_record.heuristic = "demo_record"
|
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(_delta):
|
|
||||||
# two modes, human control, agent control
|
|
||||||
# pause tree, send obs, get actions, set actions, unpause tree
|
|
||||||
|
|
||||||
_demo_record_process()
|
|
||||||
|
|
||||||
if n_action_steps % action_repeat != 0:
|
|
||||||
n_action_steps += 1
|
|
||||||
return
|
|
||||||
|
|
||||||
n_action_steps += 1
|
|
||||||
|
|
||||||
_training_process()
|
|
||||||
_inference_process()
|
|
||||||
_heuristic_process()
|
|
||||||
|
|
||||||
|
|
||||||
func _training_process():
|
|
||||||
if connected:
|
|
||||||
get_tree().set_pause(true)
|
|
||||||
|
|
||||||
if just_reset:
|
|
||||||
just_reset = false
|
|
||||||
var obs = _get_obs_from_agents(agents_training)
|
|
||||||
|
|
||||||
var reply = {"type": "reset", "obs": obs}
|
|
||||||
_send_dict_as_json_message(reply)
|
|
||||||
# this should go straight to getting the action and setting it checked the agent, no need to perform one phyics tick
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
return
|
|
||||||
|
|
||||||
if need_to_send_obs:
|
|
||||||
need_to_send_obs = false
|
|
||||||
var reward = _get_reward_from_agents()
|
|
||||||
var done = _get_done_from_agents()
|
|
||||||
#_reset_agents_if_done() # this ensures the new observation is from the next env instance : NEEDS REFACTOR
|
|
||||||
|
|
||||||
var obs = _get_obs_from_agents(agents_training)
|
|
||||||
|
|
||||||
var reply = {"type": "step", "obs": obs, "reward": reward, "done": done}
|
|
||||||
_send_dict_as_json_message(reply)
|
|
||||||
|
|
||||||
var handled = handle_message()
|
|
||||||
|
|
||||||
|
|
||||||
func _inference_process():
|
|
||||||
if agents_inference.size() > 0:
|
|
||||||
var obs: Array = _get_obs_from_agents(agents_inference)
|
|
||||||
var actions = []
|
|
||||||
|
|
||||||
for agent_id in range(0, agents_inference.size()):
|
|
||||||
var model: ONNXModel = agents_inference[agent_id].onnx_model
|
|
||||||
var action = model.run_inference(
|
|
||||||
obs[agent_id]["obs"], 1.0
|
|
||||||
)
|
|
||||||
var action_dict = _extract_action_dict(
|
|
||||||
action["output"], _action_space_inference[agent_id], model.action_means_only
|
|
||||||
)
|
|
||||||
actions.append(action_dict)
|
|
||||||
|
|
||||||
_set_agent_actions(actions, agents_inference)
|
|
||||||
_reset_agents_if_done(agents_inference)
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
|
|
||||||
|
|
||||||
func _demo_record_process():
|
|
||||||
if not agent_demo_record:
|
|
||||||
return
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed("RemoveLastDemoEpisode"):
|
|
||||||
print("[Sync script][Demo recorder] Removing last recorded episode.")
|
|
||||||
demo_trajectories.remove_at(demo_trajectories.size() - 1)
|
|
||||||
print("Remaining episode count: %d" % demo_trajectories.size())
|
|
||||||
|
|
||||||
if n_action_steps % agent_demo_record.action_repeat != 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
var obs_dict: Dictionary = agent_demo_record.get_obs()
|
|
||||||
|
|
||||||
# Get the current obs from the agent
|
|
||||||
assert(
|
|
||||||
obs_dict.has("obs"),
|
|
||||||
"Demo recorder needs an 'obs' key in get_obs() returned dictionary to record obs from."
|
|
||||||
)
|
|
||||||
current_demo_trajectory[0].append(obs_dict.obs)
|
|
||||||
|
|
||||||
# Get the action applied for the current obs from the agent
|
|
||||||
agent_demo_record.set_action()
|
|
||||||
var acts = agent_demo_record.get_action()
|
|
||||||
|
|
||||||
var terminal = agent_demo_record.get_done()
|
|
||||||
# Record actions only for non-terminal states
|
|
||||||
if terminal:
|
|
||||||
agent_demo_record.set_done_false()
|
|
||||||
else:
|
|
||||||
current_demo_trajectory[1].append(acts)
|
|
||||||
|
|
||||||
if terminal:
|
|
||||||
#current_demo_trajectory[2].append(true)
|
|
||||||
demo_trajectories.append(current_demo_trajectory.duplicate(true))
|
|
||||||
print("[Sync script][Demo recorder] Recorded episode count: %d" % demo_trajectories.size())
|
|
||||||
current_demo_trajectory[0].clear()
|
|
||||||
current_demo_trajectory[1].clear()
|
|
||||||
|
|
||||||
|
|
||||||
func _heuristic_process():
|
|
||||||
for agent in agents_heuristic:
|
|
||||||
_reset_agents_if_done(agents_heuristic)
|
|
||||||
|
|
||||||
|
|
||||||
func _extract_action_dict(action_array: Array, action_space: Dictionary, action_means_only: bool):
|
|
||||||
var index = 0
|
|
||||||
var result = {}
|
|
||||||
for key in action_space.keys():
|
|
||||||
var size = action_space[key]["size"]
|
|
||||||
var action_type = action_space[key]["action_type"]
|
|
||||||
if action_type == "discrete":
|
|
||||||
var largest_logit: float # Value of the largest logit for this action in the actions array
|
|
||||||
var largest_logit_idx: int # Index of the largest logit for this action in the actions array
|
|
||||||
for logit_idx in range(0, size):
|
|
||||||
var logit_value = action_array[index + logit_idx]
|
|
||||||
if logit_value > largest_logit:
|
|
||||||
largest_logit = logit_value
|
|
||||||
largest_logit_idx = logit_idx
|
|
||||||
result[key] = largest_logit_idx # Index of the largest logit is the discrete action value
|
|
||||||
index += size
|
|
||||||
elif action_type == "continuous":
|
|
||||||
# For continous actions, we only take the action mean values
|
|
||||||
result[key] = clamp_array(action_array.slice(index, index + size), -1.0, 1.0)
|
|
||||||
if action_means_only:
|
|
||||||
index += size # model only outputs action means, so we move index by size
|
|
||||||
else:
|
|
||||||
index += size * 2 # model outputs logstd after action mean, we skip the logstd part
|
|
||||||
|
|
||||||
else:
|
|
||||||
assert(false, 'Only "discrete" and "continuous" action types supported. Found: %s action type set.' % action_type)
|
|
||||||
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
## For AIControllers that inherit mode from sync, sets the correct mode.
|
|
||||||
func _set_agent_mode(agent: Node):
|
|
||||||
var agent_inherits_mode: bool = agent.control_mode == agent.ControlModes.INHERIT_FROM_SYNC
|
|
||||||
|
|
||||||
if agent_inherits_mode:
|
|
||||||
match control_mode:
|
|
||||||
ControlModes.HUMAN:
|
|
||||||
agent.control_mode = agent.ControlModes.HUMAN
|
|
||||||
ControlModes.TRAINING:
|
|
||||||
agent.control_mode = agent.ControlModes.TRAINING
|
|
||||||
ControlModes.ONNX_INFERENCE:
|
|
||||||
agent.control_mode = agent.ControlModes.ONNX_INFERENCE
|
|
||||||
|
|
||||||
|
|
||||||
func _get_agents():
|
|
||||||
all_agents = get_tree().get_nodes_in_group("AGENT")
|
|
||||||
for agent in all_agents:
|
|
||||||
_set_agent_mode(agent)
|
|
||||||
|
|
||||||
if agent.control_mode == agent.ControlModes.TRAINING:
|
|
||||||
agents_training.append(agent)
|
|
||||||
elif agent.control_mode == agent.ControlModes.ONNX_INFERENCE:
|
|
||||||
agents_inference.append(agent)
|
|
||||||
elif agent.control_mode == agent.ControlModes.HUMAN:
|
|
||||||
agents_heuristic.append(agent)
|
|
||||||
elif agent.control_mode == agent.ControlModes.RECORD_EXPERT_DEMOS:
|
|
||||||
assert(
|
|
||||||
not agent_demo_record,
|
|
||||||
"Currently only a single AIController can be used for recording expert demos."
|
|
||||||
)
|
|
||||||
agent_demo_record = agent
|
|
||||||
|
|
||||||
var training_agent_count = agents_training.size()
|
|
||||||
agents_training_policy_names.resize(training_agent_count)
|
|
||||||
for i in range(0, training_agent_count):
|
|
||||||
agents_training_policy_names[i] = agents_training[i].policy_name
|
|
||||||
|
|
||||||
|
|
||||||
func _set_heuristic(heuristic, agents: Array):
|
|
||||||
for agent in agents:
|
|
||||||
agent.set_heuristic(heuristic)
|
|
||||||
|
|
||||||
|
|
||||||
func _handshake():
|
|
||||||
print("performing handshake")
|
|
||||||
|
|
||||||
var json_dict = _get_dict_json_message()
|
|
||||||
assert(json_dict["type"] == "handshake")
|
|
||||||
var major_version = json_dict["major_version"]
|
|
||||||
var minor_version = json_dict["minor_version"]
|
|
||||||
if major_version != MAJOR_VERSION:
|
|
||||||
print("WARNING: major verison mismatch ", major_version, " ", MAJOR_VERSION)
|
|
||||||
if minor_version != MINOR_VERSION:
|
|
||||||
print("WARNING: minor verison mismatch ", minor_version, " ", MINOR_VERSION)
|
|
||||||
|
|
||||||
print("handshake complete")
|
|
||||||
|
|
||||||
|
|
||||||
func _get_dict_json_message():
|
|
||||||
# returns a dictionary from of the most recent message
|
|
||||||
# this is not waiting
|
|
||||||
while stream.get_available_bytes() == 0:
|
|
||||||
stream.poll()
|
|
||||||
if stream.get_status() != 2:
|
|
||||||
print("server disconnected status, closing")
|
|
||||||
get_tree().quit()
|
|
||||||
return null
|
|
||||||
|
|
||||||
OS.delay_usec(10)
|
|
||||||
|
|
||||||
var message = stream.get_string()
|
|
||||||
var json_data = JSON.parse_string(message)
|
|
||||||
|
|
||||||
return json_data
|
|
||||||
|
|
||||||
|
|
||||||
func _send_dict_as_json_message(dict):
|
|
||||||
stream.put_string(JSON.stringify(dict, "", false))
|
|
||||||
|
|
||||||
|
|
||||||
func _send_env_info():
|
|
||||||
var json_dict = _get_dict_json_message()
|
|
||||||
assert(json_dict["type"] == "env_info")
|
|
||||||
|
|
||||||
var message = {
|
|
||||||
"type": "env_info",
|
|
||||||
"observation_space": _obs_space_training,
|
|
||||||
"action_space": _action_space_training,
|
|
||||||
"n_agents": len(agents_training),
|
|
||||||
"agent_policy_names": agents_training_policy_names
|
|
||||||
}
|
|
||||||
_send_dict_as_json_message(message)
|
|
||||||
|
|
||||||
|
|
||||||
func connect_to_server():
|
|
||||||
print("Waiting for one second to allow server to start")
|
|
||||||
OS.delay_msec(1000)
|
|
||||||
print("trying to connect to server")
|
|
||||||
stream = StreamPeerTCP.new()
|
|
||||||
|
|
||||||
# "localhost" was not working on windows VM, had to use the IP
|
|
||||||
var ip = "127.0.0.1"
|
|
||||||
var port = _get_port()
|
|
||||||
var connect = stream.connect_to_host(ip, port)
|
|
||||||
stream.set_no_delay(true) # TODO check if this improves performance or not
|
|
||||||
stream.poll()
|
|
||||||
# Fetch the status until it is either connected (2) or failed to connect (3)
|
|
||||||
while stream.get_status() < 2:
|
|
||||||
stream.poll()
|
|
||||||
return stream.get_status() == 2
|
|
||||||
|
|
||||||
|
|
||||||
func _get_args():
|
|
||||||
print("getting command line arguments")
|
|
||||||
var arguments = {}
|
|
||||||
for argument in OS.get_cmdline_args():
|
|
||||||
print(argument)
|
|
||||||
if argument.find("=") > -1:
|
|
||||||
var key_value = argument.split("=")
|
|
||||||
arguments[key_value[0].lstrip("--")] = key_value[1]
|
|
||||||
else:
|
|
||||||
# Options without an argument will be present in the dictionary,
|
|
||||||
# with the value set to an empty string.
|
|
||||||
arguments[argument.lstrip("--")] = ""
|
|
||||||
|
|
||||||
return arguments
|
|
||||||
|
|
||||||
|
|
||||||
func _get_speedup():
|
|
||||||
print(args)
|
|
||||||
return args.get("speedup", str(speed_up)).to_float()
|
|
||||||
|
|
||||||
|
|
||||||
func _get_port():
|
|
||||||
return args.get("port", DEFAULT_PORT).to_int()
|
|
||||||
|
|
||||||
|
|
||||||
func _set_seed():
|
|
||||||
var _seed = args.get("env_seed", DEFAULT_SEED).to_int()
|
|
||||||
seed(_seed)
|
|
||||||
|
|
||||||
|
|
||||||
func _set_action_repeat():
|
|
||||||
action_repeat = args.get("action_repeat", str(action_repeat)).to_int()
|
|
||||||
|
|
||||||
|
|
||||||
func disconnect_from_server():
|
|
||||||
stream.disconnect_from_host()
|
|
||||||
|
|
||||||
|
|
||||||
func handle_message() -> bool:
|
|
||||||
# get json message: reset, step, close
|
|
||||||
var message = _get_dict_json_message()
|
|
||||||
if message["type"] == "close":
|
|
||||||
print("received close message, closing game")
|
|
||||||
get_tree().quit()
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
return true
|
|
||||||
|
|
||||||
if message["type"] == "reset":
|
|
||||||
print("resetting all agents")
|
|
||||||
_reset_agents()
|
|
||||||
just_reset = true
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
#print("resetting forcing draw")
|
|
||||||
# RenderingServer.force_draw()
|
|
||||||
# var obs = _get_obs_from_agents()
|
|
||||||
# print("obs ", obs)
|
|
||||||
# var reply = {
|
|
||||||
# "type": "reset",
|
|
||||||
# "obs": obs
|
|
||||||
# }
|
|
||||||
# _send_dict_as_json_message(reply)
|
|
||||||
return true
|
|
||||||
|
|
||||||
if message["type"] == "call":
|
|
||||||
var method = message["method"]
|
|
||||||
var returns = _call_method_on_agents(method)
|
|
||||||
var reply = {"type": "call", "returns": returns}
|
|
||||||
print("calling method from Python")
|
|
||||||
_send_dict_as_json_message(reply)
|
|
||||||
return handle_message()
|
|
||||||
|
|
||||||
if message["type"] == "action":
|
|
||||||
var action = message["action"]
|
|
||||||
_set_agent_actions(action, agents_training)
|
|
||||||
need_to_send_obs = true
|
|
||||||
get_tree().set_pause(false)
|
|
||||||
return true
|
|
||||||
|
|
||||||
print("message was not handled")
|
|
||||||
return false
|
|
||||||
|
|
||||||
|
|
||||||
func _call_method_on_agents(method):
|
|
||||||
var returns = []
|
|
||||||
for agent in all_agents:
|
|
||||||
returns.append(agent.call(method))
|
|
||||||
|
|
||||||
return returns
|
|
||||||
|
|
||||||
|
|
||||||
func _reset_agents_if_done(agents = all_agents):
|
|
||||||
for agent in agents:
|
|
||||||
if agent.get_done():
|
|
||||||
agent.set_done_false()
|
|
||||||
|
|
||||||
|
|
||||||
func _reset_agents(agents = all_agents):
|
|
||||||
for agent in agents:
|
|
||||||
agent.needs_reset = true
|
|
||||||
#agent.reset()
|
|
||||||
|
|
||||||
|
|
||||||
func _get_obs_from_agents(agents: Array = all_agents):
|
|
||||||
var obs = []
|
|
||||||
for agent in agents:
|
|
||||||
obs.append(agent.get_obs())
|
|
||||||
return obs
|
|
||||||
|
|
||||||
|
|
||||||
func _get_reward_from_agents(agents: Array = agents_training):
|
|
||||||
var rewards = []
|
|
||||||
for agent in agents:
|
|
||||||
rewards.append(agent.get_reward())
|
|
||||||
agent.zero_reward()
|
|
||||||
return rewards
|
|
||||||
|
|
||||||
|
|
||||||
func _get_done_from_agents(agents: Array = agents_training):
|
|
||||||
var dones = []
|
|
||||||
for agent in agents:
|
|
||||||
var done = agent.get_done()
|
|
||||||
if done:
|
|
||||||
agent.set_done_false()
|
|
||||||
dones.append(done)
|
|
||||||
return dones
|
|
||||||
|
|
||||||
|
|
||||||
func _set_agent_actions(actions, agents: Array = all_agents):
|
|
||||||
for i in range(len(actions)):
|
|
||||||
agents[i].set_action(actions[i])
|
|
||||||
|
|
||||||
|
|
||||||
func clamp_array(arr: Array, min: float, max: float):
|
|
||||||
var output: Array = []
|
|
||||||
for a in arr:
|
|
||||||
output.append(clamp(a, min, max))
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
## Save recorded export demos on window exit (Close game window instead of "Stop" button in Godot Editor)
|
|
||||||
func _notification(what):
|
|
||||||
if demo_trajectories.size() == 0 or expert_demo_save_path.is_empty():
|
|
||||||
return
|
|
||||||
|
|
||||||
if what == NOTIFICATION_PREDELETE:
|
|
||||||
var json_string = JSON.stringify(demo_trajectories, "", false)
|
|
||||||
var file = FileAccess.open(expert_demo_save_path, FileAccess.WRITE)
|
|
||||||
|
|
||||||
if not file:
|
|
||||||
var error: Error = FileAccess.get_open_error()
|
|
||||||
assert(not error, "There was an error opening the file: %d" % error)
|
|
||||||
|
|
||||||
file.store_line(json_string)
|
|
||||||
var error = file.get_error()
|
|
||||||
assert(not error, "There was an error after trying to write to the file: %d" % error)
|
|
159
Godot/agent.py
|
@ -1,159 +0,0 @@
|
||||||
import args
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
import torch as T
|
|
||||||
import torch.nn as nn
|
|
||||||
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from stable_baselines3 import PPO
|
|
||||||
from stable_baselines3.common.callbacks import CheckpointCallback
|
|
||||||
from stable_baselines3.common.vec_env.vec_monitor import VecMonitor
|
|
||||||
|
|
||||||
from godot_rl.core.utils import can_import
|
|
||||||
from godot_rl.wrappers.onnx.stable_baselines_export import export_ppo_model_as_onnx
|
|
||||||
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
|
|
||||||
|
|
||||||
def main(policy_name=None, policy=None, parseargs=None):
|
|
||||||
if can_import("ray"):
|
|
||||||
print("WARNING: SB3 and ray[rllib] are not compatible.")
|
|
||||||
|
|
||||||
args, extras = parseargs
|
|
||||||
# args, extras = args.parse_args()
|
|
||||||
|
|
||||||
def handle_onnx_export():
|
|
||||||
'''
|
|
||||||
Enforces the onnx and zip extentions when saving models.
|
|
||||||
This avoids potential conflicts in case of identical names and extentions
|
|
||||||
'''
|
|
||||||
if args.onnx_export_path is not None:
|
|
||||||
path_onnx = pathlib.Path(args.onnx_export_path).with_suffix(".onnx")
|
|
||||||
print(f"Exporting onnx to: {os.path.abspath(path_onnx)}")
|
|
||||||
export_ppo_model_as_onnx(model, str(path_onnx))
|
|
||||||
|
|
||||||
def handle_model_save():
|
|
||||||
if args.save_model_path is not None:
|
|
||||||
zip_save_path = pathlib.Path(Args.save_model_path).with_suffix(".zip")
|
|
||||||
print(f"Saving model to: {os.path.abspath(zip_save_path)}")
|
|
||||||
model.save(zip_save_path)
|
|
||||||
|
|
||||||
def close_env():
|
|
||||||
try:
|
|
||||||
print("Closing env...")
|
|
||||||
env.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Exception while closing env: {e}")
|
|
||||||
|
|
||||||
if policy_name is None:
|
|
||||||
path_checkpoint = os.path.join(args.exper_dir, f"{args.exper_name}_checkpoints")
|
|
||||||
else:
|
|
||||||
path_checkpoint = os.path.join(args.exper_dir, f"{policy_name}_checkpoints")
|
|
||||||
|
|
||||||
abs_path_checkpoint = os.path.abspath(path_checkpoint)
|
|
||||||
|
|
||||||
if args.save_checkpoint_frequency is not None and os.path.isdir(path_checkpoint):
|
|
||||||
raise RuntimeError(
|
|
||||||
f"{abs_path_checkpoint} already exists."
|
|
||||||
"Use a different directory or different name."
|
|
||||||
"If you want to override previous checkpoints you have to delete them manually."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.inference and args.resume_model_path is None:
|
|
||||||
raise parser.error(
|
|
||||||
"Using --inference requires --resume_model_path to be set."
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.env_path is None and args.viz:
|
|
||||||
print("Info: using --viz without --env_path set has no effect.")
|
|
||||||
print("\nIn editor training will always render.")
|
|
||||||
|
|
||||||
env = StableBaselinesGodotEnv(
|
|
||||||
env_path=args.env_path,
|
|
||||||
show_window=args.viz,
|
|
||||||
seed=args.seed,
|
|
||||||
n_parallel=args.n_parallel,
|
|
||||||
speedup=args.speedup
|
|
||||||
)
|
|
||||||
env = VecMonitor(env)
|
|
||||||
|
|
||||||
# LR schedule code snippet from:
|
|
||||||
# https://stable-baselines3.readthedocs.io/en/master/guide/examples.html#learning-rate-schedule
|
|
||||||
def linear_schedule(initial_value: float) -> Callable[[float], float]:
|
|
||||||
"""
|
|
||||||
Linear learning rate schedule.
|
|
||||||
|
|
||||||
:param initial_value: Initial learning rate.
|
|
||||||
:return: schedule that computes
|
|
||||||
current learning rate depending on remaining progress
|
|
||||||
"""
|
|
||||||
|
|
||||||
def func(progress_remaining: float) -> float:
|
|
||||||
"""
|
|
||||||
Progress will decrease from 1 (beginning) to 0.
|
|
||||||
|
|
||||||
:param progress_remaining:
|
|
||||||
:return: current learning rate
|
|
||||||
"""
|
|
||||||
return progress_remaining * initial_value
|
|
||||||
|
|
||||||
return func
|
|
||||||
|
|
||||||
if args.resume_model_path is None:
|
|
||||||
if not args.linear_lr_schedule:
|
|
||||||
learning_rate = 0.0003
|
|
||||||
else:
|
|
||||||
linear_schedule(0.0003)
|
|
||||||
|
|
||||||
model: PPO = PPO(
|
|
||||||
# 'MultiInputPolicy' serves as an alias for MultiInputActorCriticPolicy
|
|
||||||
"MultiInputPolicy",
|
|
||||||
env,
|
|
||||||
batch_size=64,
|
|
||||||
ent_coef=0.01,
|
|
||||||
verbose=2,
|
|
||||||
n_steps=256,
|
|
||||||
tensorboard_log=args.exper_dir,
|
|
||||||
learning_rate=learning_rate,
|
|
||||||
policy_kwargs=policy,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
path_zip = pathlib.Path(args.resume_model_path)
|
|
||||||
print(f"Loading model: {os.path.abspath(pathzip)}")
|
|
||||||
model: PPO = PPO.load(
|
|
||||||
path_zip,
|
|
||||||
env=env,
|
|
||||||
tensorboard_log=args.exper_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.inference:
|
|
||||||
obs = env.reset()
|
|
||||||
for i in range(args.timesteps):
|
|
||||||
action, _state = model.predict(obs, deterministic=True)
|
|
||||||
obs, reward, done, info = env.step(action)
|
|
||||||
else:
|
|
||||||
learn_arguments = dict(
|
|
||||||
total_timesteps=args.timesteps,
|
|
||||||
tb_log_name=policy_name
|
|
||||||
)
|
|
||||||
if args.save_checkpoint_frequency:
|
|
||||||
print("Checkpoint saving enabled.")
|
|
||||||
print(f"\nCheckpoints will be saved to {abs_path_checkpoint}")
|
|
||||||
checkpoint_callback = CheckpointCallback(
|
|
||||||
save_freq=(args.save_checkpoint_frequency // env.num_envs),
|
|
||||||
save_path=path_checkpoint,
|
|
||||||
name_prefix=policy_name
|
|
||||||
)
|
|
||||||
learn_arguments["callback"] = checkpoint_callback
|
|
||||||
try:
|
|
||||||
model.learn(**learn_arguments)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(
|
|
||||||
"""
|
|
||||||
Training interrupted by user. Will save if --save_model_path was set and/or export if --onnx_export was set.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
close_env()
|
|
||||||
handle_onnx_export()
|
|
||||||
handle_model_save()
|
|
109
Godot/args.py
|
@ -1,109 +0,0 @@
|
||||||
import argparse
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
prog='Pneuma',
|
|
||||||
allow_abbrev=False,
|
|
||||||
description='A Reinforcement Learning platform made with Godot',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--env_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The Godot binary to use, do not include for in editor training",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--exper_dir",
|
|
||||||
default="logs/sb3",
|
|
||||||
type=str,
|
|
||||||
help="The name of the experiment directory, in which the tensorboard logs and checkpoints (if enabled) are "
|
|
||||||
"getting stored.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--exper_name",
|
|
||||||
default="experiment",
|
|
||||||
type=str,
|
|
||||||
help="The name of the experiment, which will be displayed in tensorboard and "
|
|
||||||
"for checkpoint directory and name (if enabled).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--seed",
|
|
||||||
type=int,
|
|
||||||
default=1,
|
|
||||||
help="seed of the experiment"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--resume_model_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The path to a model file previously saved using --save_model_path or a checkpoint saved using "
|
|
||||||
"--save_checkpoints_frequency. Use this to resume training or infer from a saved model.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--save_model_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The path to use for saving the trained sb3 model after training is complete. Saved model can be used later "
|
|
||||||
"to resume training. Extension will be set to .zip",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--save_checkpoint_frequency",
|
|
||||||
default=None,
|
|
||||||
type=int,
|
|
||||||
help=(
|
|
||||||
"If set, will save checkpoints every 'frequency' environment steps. "
|
|
||||||
"Requires a unique --experiment_name or --experiment_dir for each run. "
|
|
||||||
"Does not need --save_model_path to be set. "
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--onnx_export_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="If included, will export onnx file after training to the path specified.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--timesteps",
|
|
||||||
default=1_000_000,
|
|
||||||
type=int,
|
|
||||||
help="The number of environment steps to train for, default is 1_000_000. If resuming from a saved model, "
|
|
||||||
"it will continue training for this amount of steps from the saved state without counting previously trained "
|
|
||||||
"steps",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--inference",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="Instead of training, it will run inference on a loaded model for --timesteps steps. "
|
|
||||||
"Requires --resume_model_path to be set.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--linear_lr_schedule",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="Use a linear LR schedule for training. If set, learning rate will decrease until it reaches 0 at "
|
|
||||||
"--timesteps"
|
|
||||||
"value. Note: On resuming training, the schedule will reset. If disabled, constant LR will be used.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--viz",
|
|
||||||
action="store_true",
|
|
||||||
help="If set, the simulation will be displayed in a window during training. Otherwise "
|
|
||||||
"training will run without rendering the simulation. This setting does not apply to in-editor training.",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--speedup",
|
|
||||||
default=1,
|
|
||||||
type=int,
|
|
||||||
help="Whether to speed up the physics in the env"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--n_parallel",
|
|
||||||
default=1,
|
|
||||||
type=int,
|
|
||||||
help="How many instances of the environment executable to " "launch - requires --env_path to be set if > 1.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser.parse_known_args()
|
|
Before Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 495 B |
|
@ -1,53 +0,0 @@
|
||||||
extends AIController2D
|
|
||||||
|
|
||||||
# meta-name: AI Controller Logic
|
|
||||||
# meta-description: Methods that need implementing for AI controllers
|
|
||||||
# meta-default: true
|
|
||||||
|
|
||||||
#-- Methods that need implementing using the "extend script" option in Godot --#
|
|
||||||
|
|
||||||
@onready var player = $".."
|
|
||||||
@onready var bamboos = $"../../../Bamboos"
|
|
||||||
@onready var move: int
|
|
||||||
|
|
||||||
func get_obs() -> Dictionary:
|
|
||||||
var dict = {"obs":[
|
|
||||||
player.position.x,
|
|
||||||
player.position.y,
|
|
||||||
player.health,
|
|
||||||
player.experience,
|
|
||||||
]}
|
|
||||||
for bamboo in bamboos.get_children():
|
|
||||||
dict["obs"].append(bamboo.position.x)
|
|
||||||
dict["obs"].append(bamboo.position.y)
|
|
||||||
dict["obs"].append(bamboo.health)
|
|
||||||
dict["obs"].append(bamboo.position.direction_to(player.position).x)
|
|
||||||
dict["obs"].append(bamboo.position.direction_to(player.position).y)
|
|
||||||
return dict
|
|
||||||
|
|
||||||
func get_reward() -> float:
|
|
||||||
return reward
|
|
||||||
|
|
||||||
func get_action_space() -> Dictionary:
|
|
||||||
return {
|
|
||||||
"move" : {
|
|
||||||
"size": 5,
|
|
||||||
"action_type": "discrete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func set_action(action) -> void:
|
|
||||||
move = action["move"]
|
|
||||||
# -----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
#-- Methods that can be overridden if needed --#
|
|
||||||
|
|
||||||
#func get_obs_space() -> Dictionary:
|
|
||||||
# May need overriding if the obs space is complex
|
|
||||||
# var obs = get_obs()
|
|
||||||
# return {
|
|
||||||
# "obs": {
|
|
||||||
# "size": [len(obs["obs"])],
|
|
||||||
# "space": "box"
|
|
||||||
# },
|
|
||||||
# }
|
|
|
@ -1,98 +0,0 @@
|
||||||
extends CharacterBody2D
|
|
||||||
|
|
||||||
const SPEED = 150
|
|
||||||
const DAMAGE = 7
|
|
||||||
const EXP_AMOUNT = 1
|
|
||||||
|
|
||||||
@onready var knockback_timer = $KnockbackTimer
|
|
||||||
@onready var attack_timer = $AttackTimer
|
|
||||||
@onready var notice = $Notice
|
|
||||||
@onready var attack = $Attack
|
|
||||||
|
|
||||||
@onready var animation_player = $AnimationPlayer
|
|
||||||
|
|
||||||
@export var health = 100
|
|
||||||
|
|
||||||
var knockback = Vector2.ZERO
|
|
||||||
var near_player = false
|
|
||||||
var is_attacking = false
|
|
||||||
var is_knocked = false
|
|
||||||
var is_dead = false
|
|
||||||
var can_move = true
|
|
||||||
|
|
||||||
signal death
|
|
||||||
|
|
||||||
|
|
||||||
func change_hp(dmg):
|
|
||||||
if not is_dead:
|
|
||||||
health += dmg
|
|
||||||
if health <= 0:
|
|
||||||
health = 0
|
|
||||||
for body in attack.get_overlapping_bodies():
|
|
||||||
body.change_hp(DAMAGE)
|
|
||||||
body.add_exp(EXP_AMOUNT)
|
|
||||||
is_dead = true
|
|
||||||
death.emit()
|
|
||||||
|
|
||||||
func _on_notice_body_entered(body):
|
|
||||||
near_player = true
|
|
||||||
body.ai_controller.reward += 1
|
|
||||||
|
|
||||||
|
|
||||||
func _on_notice_body_exited(body):
|
|
||||||
near_player = false
|
|
||||||
|
|
||||||
func _on_attack_body_entered(body):
|
|
||||||
is_attacking = false
|
|
||||||
body.ai_controller.reward += 1
|
|
||||||
|
|
||||||
func _on_attack_body_exited(body):
|
|
||||||
is_attacking = true
|
|
||||||
|
|
||||||
func _physics_process(delta):
|
|
||||||
if near_player and not is_dead:
|
|
||||||
if not is_knocked:
|
|
||||||
for body in notice.get_overlapping_bodies():
|
|
||||||
if self.to_local(body.global_position).x > 30:
|
|
||||||
velocity.x = SPEED
|
|
||||||
elif self.to_local(body.global_position).x < -30:
|
|
||||||
velocity.x = -SPEED
|
|
||||||
else:
|
|
||||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
|
|
||||||
if self.to_local(body.global_position).y > 30:
|
|
||||||
velocity.y = SPEED
|
|
||||||
elif self.to_local(body.global_position).y < -30:
|
|
||||||
velocity.y = -SPEED
|
|
||||||
else:
|
|
||||||
velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
|
|
||||||
if not is_attacking:
|
|
||||||
for enemy in attack.get_overlapping_bodies():
|
|
||||||
enemy.change_hp(-DAMAGE)
|
|
||||||
attack_timer.start()
|
|
||||||
is_attacking = true
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
if can_move:
|
|
||||||
self.velocity = knockback
|
|
||||||
knockback_timer.start()
|
|
||||||
can_move = false
|
|
||||||
else:
|
|
||||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
|
|
||||||
move_and_slide()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_attack_timer_timeout():
|
|
||||||
is_attacking = false
|
|
||||||
|
|
||||||
func _on_knockback_timer_timeout():
|
|
||||||
knockback = Vector2.ZERO
|
|
||||||
is_knocked = false
|
|
||||||
can_move = true
|
|
||||||
|
|
||||||
func _on_death():
|
|
||||||
animation_player.play("death")
|
|
|
@ -1,3 +0,0 @@
|
||||||
extends Label
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
extends Area2D
|
|
||||||
|
|
||||||
func _on_body_entered(body):
|
|
||||||
body.death.emit()
|
|
|
@ -1,62 +0,0 @@
|
||||||
extends Node2D
|
|
||||||
|
|
||||||
@onready var main_camera = %Overworld
|
|
||||||
@onready var timer = %WorldTimer
|
|
||||||
|
|
||||||
@onready var players = $Players
|
|
||||||
#TODO: Fix camera
|
|
||||||
@onready var player_camera = $Players/Player/Camera
|
|
||||||
@onready var bamboos = $Bamboos
|
|
||||||
|
|
||||||
@onready var player_starting_pos = []
|
|
||||||
@onready var bamboo_starting_pos = []
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
|
||||||
func _ready():
|
|
||||||
main_camera.make_current()
|
|
||||||
for player in players.get_children():
|
|
||||||
player_starting_pos.append(player.position)
|
|
||||||
for bamboo in bamboos.get_children():
|
|
||||||
bamboo_starting_pos.append(bamboo.position)
|
|
||||||
timer.start()
|
|
||||||
|
|
||||||
func _input(event):
|
|
||||||
if event.is_action_pressed("reset_camera"):
|
|
||||||
main_camera.make_current()
|
|
||||||
player_camera.visible = false
|
|
||||||
|
|
||||||
func _process(delta):
|
|
||||||
var dead_state = 0
|
|
||||||
var i=0
|
|
||||||
|
|
||||||
for bamboo in bamboos.get_children():
|
|
||||||
if bamboo.is_dead:
|
|
||||||
dead_state += 1
|
|
||||||
|
|
||||||
if dead_state == bamboos.get_children().size():
|
|
||||||
for player in players.get_children():
|
|
||||||
player.change_hp(-1000)
|
|
||||||
|
|
||||||
func _on_player_death():
|
|
||||||
var i = 0
|
|
||||||
for player in players.get_children():
|
|
||||||
player.position = player_starting_pos[i]
|
|
||||||
player.health = 100
|
|
||||||
player.ai_controller.done = true
|
|
||||||
player.ai_controller.reset()
|
|
||||||
i += 1
|
|
||||||
var j = 0
|
|
||||||
for bamboo in bamboos.get_children():
|
|
||||||
bamboo.position = bamboo_starting_pos[j]
|
|
||||||
bamboo.health = 40
|
|
||||||
bamboo.is_dead = false
|
|
||||||
bamboo.animation_player.play("RESET")
|
|
||||||
j += 1
|
|
||||||
|
|
||||||
func _on_timer_timeout():
|
|
||||||
for player in players.get_children():
|
|
||||||
player.change_hp(-1000)
|
|
||||||
timer.start()
|
|
||||||
|
|
|
@ -1,190 +0,0 @@
|
||||||
extends CharacterBody2D
|
|
||||||
|
|
||||||
const SPEED = 300.0
|
|
||||||
|
|
||||||
var is_attacking = false
|
|
||||||
var last_action = 0
|
|
||||||
var cooldown_start = false
|
|
||||||
|
|
||||||
var zoomed_in = false
|
|
||||||
|
|
||||||
var starting_position = self.position
|
|
||||||
|
|
||||||
signal death
|
|
||||||
|
|
||||||
@onready var ai_controller = $AIController2D
|
|
||||||
|
|
||||||
@onready var animated_sprite = $AnimatedSprite2D
|
|
||||||
|
|
||||||
@onready var overworld = %Overworld
|
|
||||||
|
|
||||||
@onready var camera = $Camera
|
|
||||||
@onready var exp_label = $Camera/ExpPanel/ExpLabel
|
|
||||||
@onready var hp_label = $Camera/HPPanel/HPLabel
|
|
||||||
|
|
||||||
@onready var attack_timer = $AttackTimer
|
|
||||||
@onready var weapon = $Weapon
|
|
||||||
@onready var weapon_player = $Weapon/AnimationPlayer
|
|
||||||
|
|
||||||
|
|
||||||
@export var health = 100
|
|
||||||
@export var experience = 0
|
|
||||||
|
|
||||||
|
|
||||||
func _ready():
|
|
||||||
exp_label.text = "Experience\n"+str(experience)
|
|
||||||
hp_label.text = "Health\n"+str(health)
|
|
||||||
|
|
||||||
func add_exp(exp_amount):
|
|
||||||
experience += exp_amount
|
|
||||||
ai_controller.reward = experience
|
|
||||||
exp_label.text = "Experience\n"+str(experience)
|
|
||||||
|
|
||||||
|
|
||||||
func change_hp(dmg):
|
|
||||||
health += dmg
|
|
||||||
#ai_controller.reward += dmg/10
|
|
||||||
if health <= 0:
|
|
||||||
health = 0
|
|
||||||
add_exp(-1)
|
|
||||||
death.emit()
|
|
||||||
hp_label.text = "Health\n"+str(health)
|
|
||||||
|
|
||||||
func move_left():
|
|
||||||
last_action = 1
|
|
||||||
velocity.x = -SPEED
|
|
||||||
func move_right():
|
|
||||||
last_action = 3
|
|
||||||
velocity.x = SPEED
|
|
||||||
func move_up():
|
|
||||||
last_action = 2
|
|
||||||
velocity.y = -SPEED
|
|
||||||
func move_down():
|
|
||||||
last_action = 0
|
|
||||||
velocity.y = SPEED
|
|
||||||
|
|
||||||
func _physics_process(_delta):
|
|
||||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
|
|
||||||
# Get the input direction and handle the movement/deceleration.
|
|
||||||
if not is_attacking:
|
|
||||||
# Handle and movement
|
|
||||||
## X Axis
|
|
||||||
if Input.is_action_pressed("move_right") or ai_controller.move == 3:
|
|
||||||
move_right()
|
|
||||||
if Input.is_action_pressed("move_left") or ai_controller.move == 1:
|
|
||||||
move_left()
|
|
||||||
|
|
||||||
## Y Axis
|
|
||||||
if Input.is_action_pressed("move_up") or ai_controller.move == 2:
|
|
||||||
move_up()
|
|
||||||
if Input.is_action_pressed("move_down") or ai_controller.move == 0:
|
|
||||||
move_down()
|
|
||||||
|
|
||||||
# Handle animations
|
|
||||||
if velocity.x > 0:
|
|
||||||
animated_sprite.play("move_right")
|
|
||||||
elif velocity.x < 0:
|
|
||||||
animated_sprite.play("move_left")
|
|
||||||
elif velocity.y > 0 :
|
|
||||||
animated_sprite.play("move_down")
|
|
||||||
elif velocity.y < 0:
|
|
||||||
animated_sprite.play("move_up")
|
|
||||||
|
|
||||||
# Stop movement or change direction (left/right or up/down)
|
|
||||||
if Input.is_action_just_released("move_right"):
|
|
||||||
if Input.is_action_pressed("move_left"):
|
|
||||||
move_left()
|
|
||||||
else:
|
|
||||||
last_action = 3
|
|
||||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
animated_sprite.play("idle_right")
|
|
||||||
if Input.is_action_just_released("move_left"):
|
|
||||||
if Input.is_action_pressed("move_right"):
|
|
||||||
move_right()
|
|
||||||
else:
|
|
||||||
last_action = 1
|
|
||||||
velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
animated_sprite.play("idle_left")
|
|
||||||
|
|
||||||
if Input.is_action_just_released("move_up"):
|
|
||||||
if Input.is_action_pressed("move_down"):
|
|
||||||
move_down()
|
|
||||||
else:
|
|
||||||
last_action = 2
|
|
||||||
velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
animated_sprite.play("idle_up")
|
|
||||||
if Input.is_action_just_released("move_down"):
|
|
||||||
if Input.is_action_pressed("move_up"):
|
|
||||||
move_up()
|
|
||||||
else:
|
|
||||||
last_action = 0
|
|
||||||
velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
animated_sprite.play("idle_down")
|
|
||||||
|
|
||||||
# Handle attacking and magic
|
|
||||||
if Input.is_action_just_pressed("attack") or ai_controller.move == 4:
|
|
||||||
|
|
||||||
is_attacking = true
|
|
||||||
if last_action == 1:
|
|
||||||
weapon.position = Vector2i(-54,14)
|
|
||||||
weapon.rotation_degrees = 90*last_action
|
|
||||||
animated_sprite.play("attack_left")
|
|
||||||
weapon_player.play("attack")
|
|
||||||
elif last_action == 2:
|
|
||||||
weapon.position = Vector2i(8,-44)
|
|
||||||
animated_sprite.play("attack_up")
|
|
||||||
weapon.rotation_degrees = 90*last_action
|
|
||||||
weapon_player.play("attack")
|
|
||||||
elif last_action == 3:
|
|
||||||
weapon.rotation_degrees = 90*last_action
|
|
||||||
weapon.position = Vector2i(54,14)
|
|
||||||
animated_sprite.play("attack_right")
|
|
||||||
weapon_player.play("attack")
|
|
||||||
else:
|
|
||||||
weapon.position = Vector2i(-12,52)
|
|
||||||
animated_sprite.play("attack_down")
|
|
||||||
weapon.rotation_degrees = 90*last_action
|
|
||||||
weapon_player.play("attack")
|
|
||||||
#
|
|
||||||
## TODO: Fix magic
|
|
||||||
#elif Input.is_action_just_pressed("cast_magic"):
|
|
||||||
#is_attacking = true
|
|
||||||
#velocity.x = move_toward(velocity.x, 0, SPEED)
|
|
||||||
#velocity.y = move_toward(velocity.y, 0, SPEED)
|
|
||||||
#if last_action == 1:
|
|
||||||
#animated_sprite.play("attack_right")
|
|
||||||
#elif last_action == 2:
|
|
||||||
#animated_sprite.play("attack_left")
|
|
||||||
#elif last_action == 3:
|
|
||||||
#animated_sprite.play("attack_up")
|
|
||||||
#else:
|
|
||||||
#animated_sprite.play("attack_down")
|
|
||||||
|
|
||||||
move_and_slide()
|
|
||||||
|
|
||||||
else:
|
|
||||||
attack_cooldown()
|
|
||||||
|
|
||||||
func attack_cooldown():
|
|
||||||
if cooldown_start == false:
|
|
||||||
attack_timer.start()
|
|
||||||
cooldown_start = true
|
|
||||||
|
|
||||||
# TODO: Find more elegant way to go back
|
|
||||||
func _on_button_pressed():
|
|
||||||
if not zoomed_in:
|
|
||||||
camera.visible = true
|
|
||||||
camera.make_current()
|
|
||||||
zoomed_in = true
|
|
||||||
else:
|
|
||||||
camera.visible = false
|
|
||||||
overworld.make_current()
|
|
||||||
zoomed_in = false
|
|
||||||
|
|
||||||
|
|
||||||
func _on_attack_timer_timeout():
|
|
||||||
is_attacking = false
|
|
||||||
cooldown_start = false
|
|
||||||
weapon_player.play("RESET")
|
|
|
@ -1,12 +0,0 @@
|
||||||
extends Area2D
|
|
||||||
|
|
||||||
const DAMAGE = 20
|
|
||||||
const KNOCKBACK_STR = 120
|
|
||||||
|
|
||||||
func _on_body_entered(body):
|
|
||||||
var direction = self.global_position.direction_to(body.global_position)
|
|
||||||
var knockback_force = direction * KNOCKBACK_STR
|
|
||||||
if not body.is_dead:
|
|
||||||
body.change_hp(-DAMAGE)
|
|
||||||
body.knockback = knockback_force
|
|
||||||
body.is_knocked = true
|
|
|
@ -1,9 +0,0 @@
|
||||||
[gd_resource type="AudioBusLayout" format=3 uid="uid://crne71jxj5jib"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
bus/1/name = &"SFX"
|
|
||||||
bus/1/solo = false
|
|
||||||
bus/1/mute = false
|
|
||||||
bus/1/bypass_fx = false
|
|
||||||
bus/1/volume_db = -5.32994
|
|
||||||
bus/1/send = &"Master"
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
|
|
||||||
<desc>Created with Fabric.js 5.2.4</desc>
|
|
||||||
<defs>
|
|
||||||
</defs>
|
|
||||||
<g transform="matrix(1 0 0 1 540 540)" id="bbbd9393-16c7-44f0-8b5c-16bfd131ab9a" >
|
|
||||||
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(Infinity NaN NaN Infinity 0 0)" id="2c258675-4de0-49f7-91dd-b55f919563be" >
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(2.5 0 0 2.5 540 540)" >
|
|
||||||
<g style="" vector-effect="non-scaling-stroke" >
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 0 0)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1440.01, -1440.49)" d="M 1265 2856 C 926 2805 665 2676 434 2446 C -52 1959 -125 1222 257 646 C 389 447 618 249 831 152 C 1232 -33 1648 -33 2049 152 C 2330 281 2599 550 2728 831 C 2913 1232 2913 1648 2728 2049 C 2599 2331 2331 2599 2049 2728 C 1798 2844 1504 2892 1265 2856 z M 1745 2816 C 1800 2803 1879 2779 1920 2764 C 1993 2738 2170 2654 2170 2646 C 2170 2643 2137 2657 2096 2675 C 1920 2757 1776 2790 1564 2797 C 1374 2803 1256 2788 1094 2735 C 722 2613 394 2328 244 1994 C 223 1949 202 1914 197 1917 C 191 1921 189 1914 192 1902 C 196 1890 194 1882 190 1885 C 168 1898 120 1586 120 1435 C 121 1339 143 1172 166 1090 C 170 1076 170 1068 166 1072 C 153 1084 112 1246 100 1339 C 83 1460 93 1696 119 1810 C 130 1857 153 1936 170 1985 C 290 2321 507 2557 840 2713 C 971 2775 1006 2781 890 2723 C 742 2648 645 2577 525 2456 C 411 2341 344 2250 278 2120 C 235 2036 197 1940 204 1933 C 206 1931 214 1946 220 1967 C 272 2124 396 2318 534 2455 C 730 2649 954 2769 1238 2831 C 1364 2859 1604 2852 1745 2816 z M 1795 2765 C 1890 2744 2010 2704 2099 2664 C 2139 2645 2177 2633 2183 2637 C 2189 2640 2190 2638 2186 2632 C 2182 2625 2193 2614 2212 2604 C 2408 2503 2595 2293 2713 2040 C 2775 1909 2781 1874 2723 1990 C 2648 2138 2577 2235 2456 2355 C 2341 2469 2250 2536 2120 2602 C 2036 2645 1940 2683 1933 2676 C 1931 2674 1946 2666 1967 2660 C 2124 2608 2318 2484 2455 2346 C 2654 2146 2781 1902 2835 1615 C 2870 1431 2843 1177 2764 960 C 2737 883 2654 710 2645 710 C 2642 710 2649 728 2660 749 C 2687 803 2740 938 2740 953 C 2739 959 2724 926 2706 879 C 2687 832 2662 774 2651 751 C 2639 728 2632 704 2636 697 C 2640 690 2640 688 2635 692 C 2631 696 2600 659 2568 609 C 2448 423 2271 275 2040 167 C 1931 116 1873 96 1930 130 C 1970 154 1968 153 1883 121 C 1847 108 1823 93 1827 87 C 1830 81 1828 80 1822 84 C 1815 88 1762 78 1703 63 C 1604 37 1581 35 1420 36 C 1229 36 1148 50 970 112 C 887 142 710 225 710 235 C 710 238 736 227 768 211 C 877 156 1096 89 1160 91 C 1174 91 1144 100 1093 111 C 922 147 741 222 596 318 C 424 431 270 619 167 840 C 105 971 99 1006 157 890 C 230 745 303 645 419 529 C 636 312 882 190 1205 140 C 1467 100 1700 124 1940 217 C 1948 220 1952 219 1948 215 C 1944 211 1895 190 1838 169 C 1781 148 1740 131 1747 130 C 1759 130 1895 179 1942 200 C 1957 207 1967 217 1963 223 C 1960 229 1962 230 1968 226 C 1985 216 2098 282 2210 366 C 2393 504 2536 677 2631 872 C 2676 965 2674 942 2628 835 C 2513 568 2318 355 2044 194 C 2001 169 1974 151 1985 154 C 2016 164 2152 248 2223 301 C 2312 368 2456 522 2519 616 C 2634 790 2705 981 2740 1205 C 2774 1426 2762 1631 2705 1817 C 2692 1859 2683 1896 2685 1898 C 2692 1905 2759 1682 2774 1604 C 2781 1560 2788 1536 2789 1550 C 2792 1631 2692 1949 2669 1934 C 2664 1931 2663 1936 2666 1944 C 2669 1952 2657 1985 2639 2017 C 2480 2300 2249 2521 1994 2636 C 1949 2657 1914 2678 1917 2683 C 1921 2689 1914 2691 1902 2688 C 1890 2684 1882 2686 1885 2690 C 1891 2700 1785 2724 1645 2745 C 1459 2773 1245 2759 1080 2710 C 1045 2699 1014 2692 1012 2695 C 1001 2705 1184 2753 1350 2784 C 1405 2794 1727 2781 1795 2765 z M 1710 2712 C 2345 2564 2767 2018 2737 1380 C 2703 661 2098 110 1380 143 C 661 177 110 782 143 1500 C 167 2001 469 2438 930 2635 C 1019 2673 1163 2714 1265 2730 C 1361 2744 1612 2734 1710 2712 z M 211 2113 C 174 2039 126 1894 103 1788 C 89 1724 85 1661 85 1510 C 86 1333 88 1305 112 1210 C 181 935 305 714 505 514 C 622 395 728 319 872 249 C 965 204 942 206 835 252 C 425 429 132 805 45 1265 C 10 1449 37 1703 116 1920 C 143 1997 226 2170 235 2170 C 238 2170 227 2144 211 2113 z M 1880 100 C 1872 95 1858 91 1850 91 C 1840 91 1842 94 1855 100 C 1882 112 1898 112 1880 100 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 180.83 9.86)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-2796.25, -1366.51)" d="M 2794 1365 C 2794 1277 2796 1242 2797 1288 C 2799 1334 2799 1406 2797 1448 C 2796 1490 2794 1453 2794 1365 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 179.37 36.04)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-2785.25, -1170.18)" d="M 2782 1170 C 2782 1151 2784 1143 2787 1153 C 2789 1162 2789 1178 2787 1188 C 2784 1197 2782 1189 2782 1170 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 176.14 52.51)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-2761.06, -1046.69)" d="M 2758 1053 C 2748 1011 2742 975 2744 973 C 2749 967 2783 1107 2778 1120 C 2777 1125 2767 1096 2758 1053 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 27.49 177.37)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1646.21, -110.19)" d="M 1625 109 C 1561 94 1555 91 1596 95 C 1624 98 1666 106 1691 114 C 1752 133 1721 131 1625 109 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 -27.12 180.83)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1236.6, -84.25)" d="M 1213 83 C 1228 81 1250 81 1263 83 C 1275 85 1263 87 1235 87 C 1208 87 1197 85 1213 83 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 -3.33 181.4)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1415, -80)" d="M 1365 80 L 1285 74 L 1355 73 C 1394 73 1452 76 1485 80 L 1545 87 L 1495 87 C 1468 86 1409 83 1365 80 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 51.06 -127.27)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1822.97, -2395)" d="M 1773 2453 L 1820 2405 L 1763 2348 C 1731 2316 1711 2290 1717 2290 C 1723 2290 1751 2314 1779 2342 L 1829 2395 L 1880 2345 C 1907 2318 1930 2300 1930 2306 C 1930 2318 1748 2500 1735 2500 C 1730 2500 1747 2479 1773 2453 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.13 0 0 -0.13 3.17 11.33)" >
|
|
||||||
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-1463.77, -1355.51)" d="M 1800 1848 L 1475 1527 L 1470 1821 L 1465 2115 L 1440 2115 L 1415 2115 L 1412 1790 L 1410 1465 L 1343 1398 L 1276 1330 L 1039 1566 C 903 1701 796 1800 788 1798 C 748 1784 773 1752 1016 1509 C 1153 1372 1270 1260 1275 1260 C 1281 1260 1312 1287 1345 1320 L 1405 1381 L 1410 963 L 1415 545 L 1440 545 L 1465 545 L 1470 995 L 1475 1445 L 1820 1787 C 2115 2080 2163 2131 2154 2146 C 2148 2156 2139 2165 2134 2166 C 2129 2168 1979 2025 1800 1848 z" stroke-linecap="round" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 9.1 KiB |
|
@ -1,6 +0,0 @@
|
||||||
from policy import policies
|
|
||||||
from agent import main
|
|
||||||
import args
|
|
||||||
|
|
||||||
for policy_name, policy in policies.items():
|
|
||||||
main(policy_name=policy_name, policy=policy, parseargs=args.parse_args())
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
echo -ne '\033c\033]0;Pneuma\a'
|
|
||||||
base_path="$(dirname "$(realpath "$0")")"
|
|
||||||
"$base_path/pneuma.x86_64" "$@"
|
|
2207
Godot/poetry.lock
generated
131
Godot/policy.py
|
@ -1,131 +0,0 @@
|
||||||
import torch as T
|
|
||||||
import torch.nn as nn
|
|
||||||
|
|
||||||
policy_small=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[256],
|
|
||||||
vf=[256]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_small_optim=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[256],
|
|
||||||
vf=[256]
|
|
||||||
),
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9, 0.9),
|
|
||||||
eps=1e-5,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_small_tanh=dict(
|
|
||||||
activation_fn=nn.Tanh,
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[256],
|
|
||||||
vf=[256]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_small_optim_tanh=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[256],
|
|
||||||
vf=[256]
|
|
||||||
),
|
|
||||||
optimizer_class=T.optim.Adam,
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9, 0.9),
|
|
||||||
eps=1e-5,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_mid=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[512],
|
|
||||||
vf=[2048, 2048]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_mid_tanh=dict(
|
|
||||||
activation_fn=nn.Tanh,
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[512],
|
|
||||||
vf=[2048, 2048]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_mid_optim=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[512],
|
|
||||||
vf=[2048, 2048]
|
|
||||||
),
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9,0.9),
|
|
||||||
eps=1e-5
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_mid_optim_tanh=dict(
|
|
||||||
activation_fn=nn.Tanh,
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[512],
|
|
||||||
vf=[2048, 2048]
|
|
||||||
),
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9,0.9),
|
|
||||||
eps=1e-5
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_big=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[1024, 1024],
|
|
||||||
vf=[4096, 4096, 4096, 4096]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_big_tanh=dict(
|
|
||||||
activation_fn=nn.Tanh,
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[1024, 1024],
|
|
||||||
vf=[4096, 4096, 4096, 4096]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_big_optim=dict(
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[1024, 1024],
|
|
||||||
vf=[4096, 4096, 4096, 4096]
|
|
||||||
),
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9, 0.9),
|
|
||||||
eps=1e-5,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
policy_big_optim_tanh = dict(
|
|
||||||
activation_fn=nn.Tanh,
|
|
||||||
net_arch=dict(
|
|
||||||
pi=[1024, 1024],
|
|
||||||
vf=[4096, 4096, 4096, 4096],
|
|
||||||
),
|
|
||||||
optimizer_kwargs=dict(
|
|
||||||
betas=(0.9, 0.9),
|
|
||||||
eps=1e-5,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
policies={
|
|
||||||
"policy_small": policy_small,
|
|
||||||
"policy_small_optim": policy_small_optim,
|
|
||||||
"policy_small_tanh": policy_small_tanh,
|
|
||||||
"policy_small_optim_tanh": policy_small_optim_tanh,
|
|
||||||
"policy_mid": policy_mid,
|
|
||||||
"policy_mid_optim": policy_mid_optim,
|
|
||||||
"policy_mid_tanh": policy_mid_tanh,
|
|
||||||
"policy_mid_optim_tanh": policy_mid_optim_tanh,
|
|
||||||
"policy_big": policy_big,
|
|
||||||
"policy_big_optim": policy_big_optim,
|
|
||||||
"policy_big_tanh": policy_big_tanh,
|
|
||||||
"policy_big_optim_tanh": policy_big_optim_tanh,
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
; Engine configuration file.
|
|
||||||
; It's best edited using the editor UI and not directly,
|
|
||||||
; since the parameters that go here are not all obvious.
|
|
||||||
;
|
|
||||||
; Format:
|
|
||||||
; [section] ; section goes between []
|
|
||||||
; param=value ; assign values to parameters
|
|
||||||
|
|
||||||
config_version=5
|
|
||||||
|
|
||||||
[application]
|
|
||||||
|
|
||||||
config/name="Pneuma"
|
|
||||||
run/main_scene="res://scenes/main.tscn"
|
|
||||||
config/features=PackedStringArray("4.2", "Forward Plus")
|
|
||||||
config/icon="res://icon.svg"
|
|
||||||
|
|
||||||
[dotnet]
|
|
||||||
|
|
||||||
project/assembly_name="Pneuma"
|
|
||||||
|
|
||||||
[editor_plugins]
|
|
||||||
|
|
||||||
enabled=PackedStringArray("res://addons/Todo_Manager/plugin.cfg", "res://addons/godot_rl_agents/plugin.cfg")
|
|
||||||
|
|
||||||
[input]
|
|
||||||
|
|
||||||
move_left={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
|
|
||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
move_right={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
|
|
||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
move_up={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
|
|
||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
move_down={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
|
|
||||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
attack={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
cast_magic={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
reset_camera={
|
|
||||||
"deadzone": 0.5,
|
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
[rendering]
|
|
||||||
|
|
||||||
textures/canvas_textures/default_texture_filter=0
|
|
|
@ -1,22 +0,0 @@
|
||||||
[tool.poetry]
|
|
||||||
name = "pneumarl"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Godot training env for MARL"
|
|
||||||
authors = ["Vasilis Valatsos <vasilvalat@gmail.com>"]
|
|
||||||
readme = "README.md"
|
|
||||||
package-mode = false
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
|
||||||
python = "^3.11"
|
|
||||||
dm-tree = "^0.1.8"
|
|
||||||
typer = "^0.12.3"
|
|
||||||
scikit-image = "^0.23.2"
|
|
||||||
lz4 = "^4.3.3"
|
|
||||||
godot-rl = {url = "https://github.com/edbeeching/godot_rl_agents/archive/refs/heads/main.zip"}
|
|
||||||
pettingzoo = "^1.24.3"
|
|
||||||
tensorboard = "^2.16.2"
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
|
@ -1,60 +0,0 @@
|
||||||
algorithm: PPO
|
|
||||||
|
|
||||||
# Multi-agent-env setting:
|
|
||||||
# If true:
|
|
||||||
# - Any AIController with done = true will receive zeroes as action values until all AIControllers are done, an episode ends at that point.
|
|
||||||
# - ai_controller.needs_reset will also be set to true every time a new episode begins (but you can ignore it in your env if needed).
|
|
||||||
# If false:
|
|
||||||
# - AIControllers auto-reset in Godot and will receive actions after setting done = true.
|
|
||||||
# - Each AIController has its own episodes that can end/reset at any point.
|
|
||||||
# Set to false if you have a single policy name for all agents set in AIControllers
|
|
||||||
env_is_multiagent: false
|
|
||||||
|
|
||||||
checkpoint_frequency: 20
|
|
||||||
|
|
||||||
# You can set one or more stopping criteria
|
|
||||||
stop:
|
|
||||||
#episode_reward_mean: 0
|
|
||||||
#training_iteration: 1000
|
|
||||||
#timesteps_total: 10000
|
|
||||||
time_total_s: 10000000
|
|
||||||
|
|
||||||
config:
|
|
||||||
env: godot
|
|
||||||
env_config:
|
|
||||||
env_path: '/home/valapeos/Projects/pneumarl/pneuma.x86_64' # Set your env path here (exported executable from Godot) - e.g. env_path: 'env_path.exe' on Windows
|
|
||||||
action_repeat: null # Doesn't need to be set here, you can set this in sync node in Godot editor as well
|
|
||||||
show_window: true # Displays game window while training. Might be faster when false in some cases, turning off also reduces GPU usage if you don't need rendering.
|
|
||||||
speedup: 30 # Speeds up Godot physics
|
|
||||||
|
|
||||||
framework: torch # ONNX models exported with torch are compatible with the current Godot RL Agents Plugin
|
|
||||||
|
|
||||||
lr: 0.0003
|
|
||||||
lambda: 0.95
|
|
||||||
gamma: 0.99
|
|
||||||
|
|
||||||
vf_loss_coeff: 0.5
|
|
||||||
vf_clip_param: .inf
|
|
||||||
#clip_param: 0.2
|
|
||||||
entropy_coeff: 0.0001
|
|
||||||
entropy_coeff_schedule: null
|
|
||||||
#grad_clip: 0.5
|
|
||||||
|
|
||||||
normalize_actions: False
|
|
||||||
clip_actions: True # During onnx inference we simply clip the actions to [-1.0, 1.0] range, set here to match
|
|
||||||
|
|
||||||
rollout_fragment_length: 32
|
|
||||||
sgd_minibatch_size: 128
|
|
||||||
num_workers: 4
|
|
||||||
num_envs_per_worker: 1 # This will be set automatically if not multi-agent. If multi-agent, changing this changes how many envs to launch per worker.
|
|
||||||
# The value below needs changing per env
|
|
||||||
# Basic calculation for this value can be rollout_fragment_length * num_workers * num_envs_per_worker (how many AIControllers you have if not multi_agent, otherwise the value you set)
|
|
||||||
train_batch_size: 2048
|
|
||||||
|
|
||||||
num_sgd_iter: 4
|
|
||||||
batch_mode: truncate_episodes
|
|
||||||
|
|
||||||
num_gpus: 0
|
|
||||||
model:
|
|
||||||
vf_share_layers: False
|
|
||||||
fcnet_hiddens: [64, 64]
|
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
python main.py --env_path="/home/valapeos/Projects/thesis/Godot/pneuma.x86_64" --speedup=200 --n_parallel=4 --exper_dir="logs/sb3_full_1" &&
|
|
||||||
|
|
||||||
python main.py --env_path="/home/valapeos/Projects/thesis/Godot/pneuma.x86_64" --speedup=200 --n_parallel=4 --exper_dir="logs/sb3_full_2" &&
|
|
||||||
|
|
||||||
python main.py --env_path="/home/valapeos/Projects/thesis/Godot/pneuma.x86_64" --speedup=200 --n_parallel=4 --exper_dir="logs/sb3_full_3" &&
|
|
||||||
|
|
||||||
python main.py --env_path="/home/valapeos/Projects/thesis/Godot/pneuma.x86_64" --speedup=200 --n_parallel=4 --exper_dir="logs/sb3_full_4"
|
|
|
@ -1,4 +0,0 @@
|
||||||
[gd_scene format=3 uid="uid://c1i0nirxfagfh"]
|
|
||||||
|
|
||||||
[node name="Attack" type="Area2D"]
|
|
||||||
collision_mask = 2
|
|
|
@ -1,249 +0,0 @@
|
||||||
[gd_scene load_steps=20 format=3 uid="uid://bsjy4oejfrg81"]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://d4d34das0e3j0" path="res://assets/graphics/monsters/bamboo/idle/0.png" id="1_em175"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://l0bk7j8xiqur" path="res://assets/graphics/monsters/bamboo/idle/1.png" id="2_i1h6v"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bwe7pi6shcc2x" path="res://assets/graphics/monsters/bamboo/attack/0.png" id="2_tihvn"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://damt0mr2bvcjv" path="res://assets/graphics/monsters/bamboo/idle/2.png" id="3_5ekod"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://b11yb3x6h5oas" path="res://assets/graphics/monsters/bamboo/idle/3.png" id="4_0fhpn"]
|
|
||||||
[ext_resource type="Script" path="res://code/bamboo.gd" id="5_ib85v"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://q8yev438ec6r" path="res://assets/graphics/monsters/bamboo/move/0.png" id="7_8n247"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dqr075ilm3tu8" path="res://assets/graphics/monsters/bamboo/move/1.png" id="8_c5akp"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://hcm2s3v12pkw" path="res://assets/graphics/monsters/bamboo/move/2.png" id="9_t18yx"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dqa5hvprf3kdp" path="res://assets/graphics/monsters/bamboo/move/3.png" id="10_gsrg3"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dxwjan054vgw0" path="res://scenes/notice.tscn" id="11_d502u"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://c1i0nirxfagfh" path="res://scenes/attack.tscn" id="12_27c4n"]
|
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_iyhjt"]
|
|
||||||
size = Vector2(24, 50)
|
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_wx6w8"]
|
|
||||||
radius = 300.0
|
|
||||||
|
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_crbjt"]
|
|
||||||
radius = 40.0
|
|
||||||
|
|
||||||
[sub_resource type="SpriteFrames" id="SpriteFrames_2w0pn"]
|
|
||||||
animations = [{
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("2_tihvn")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"attack",
|
|
||||||
"speed": 7.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("1_em175")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("2_i1h6v")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("3_5ekod")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("4_0fhpn")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"idle",
|
|
||||||
"speed": 7.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("7_8n247")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("8_c5akp")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("9_t18yx")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("10_gsrg3")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"move",
|
|
||||||
"speed": 7.0
|
|
||||||
}]
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_lrur3"]
|
|
||||||
length = 0.001
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("CollisionShape2D:disabled")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath(".:visible")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
tracks/2/type = "value"
|
|
||||||
tracks/2/imported = false
|
|
||||||
tracks/2/enabled = true
|
|
||||||
tracks/2/path = NodePath("Attack:monitoring")
|
|
||||||
tracks/2/interp = 1
|
|
||||||
tracks/2/loop_wrap = true
|
|
||||||
tracks/2/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
tracks/3/type = "value"
|
|
||||||
tracks/3/imported = false
|
|
||||||
tracks/3/enabled = true
|
|
||||||
tracks/3/path = NodePath("Notice:monitoring")
|
|
||||||
tracks/3/interp = 1
|
|
||||||
tracks/3/loop_wrap = true
|
|
||||||
tracks/3/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
tracks/4/type = "value"
|
|
||||||
tracks/4/imported = false
|
|
||||||
tracks/4/enabled = true
|
|
||||||
tracks/4/path = NodePath(".:collision_mask")
|
|
||||||
tracks/4/interp = 1
|
|
||||||
tracks/4/loop_wrap = true
|
|
||||||
tracks/4/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [6]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_3xlxe"]
|
|
||||||
resource_name = "death"
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("CollisionShape2D:disabled")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath(".:visible")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
tracks/2/type = "value"
|
|
||||||
tracks/2/imported = false
|
|
||||||
tracks/2/enabled = true
|
|
||||||
tracks/2/path = NodePath("Attack:monitoring")
|
|
||||||
tracks/2/interp = 1
|
|
||||||
tracks/2/loop_wrap = true
|
|
||||||
tracks/2/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
tracks/3/type = "value"
|
|
||||||
tracks/3/imported = false
|
|
||||||
tracks/3/enabled = true
|
|
||||||
tracks/3/path = NodePath("Notice:monitoring")
|
|
||||||
tracks/3/interp = 1
|
|
||||||
tracks/3/loop_wrap = true
|
|
||||||
tracks/3/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
tracks/4/type = "value"
|
|
||||||
tracks/4/imported = false
|
|
||||||
tracks/4/enabled = true
|
|
||||||
tracks/4/path = NodePath(".:collision_mask")
|
|
||||||
tracks/4/interp = 1
|
|
||||||
tracks/4/loop_wrap = true
|
|
||||||
tracks/4/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [0]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ftwl3"]
|
|
||||||
_data = {
|
|
||||||
"RESET": SubResource("Animation_lrur3"),
|
|
||||||
"death": SubResource("Animation_3xlxe")
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="Bamboo" type="CharacterBody2D"]
|
|
||||||
z_index = 5
|
|
||||||
y_sort_enabled = true
|
|
||||||
position = Vector2(0, -33)
|
|
||||||
collision_layer = 4
|
|
||||||
collision_mask = 6
|
|
||||||
script = ExtResource("5_ib85v")
|
|
||||||
health = 40
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
|
||||||
position = Vector2(0, 2)
|
|
||||||
shape = SubResource("RectangleShape2D_iyhjt")
|
|
||||||
|
|
||||||
[node name="Notice" parent="." instance=ExtResource("11_d502u")]
|
|
||||||
position = Vector2(0, 7)
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Notice"]
|
|
||||||
shape = SubResource("CircleShape2D_wx6w8")
|
|
||||||
|
|
||||||
[node name="Attack" parent="." instance=ExtResource("12_27c4n")]
|
|
||||||
position = Vector2(0, 5)
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="Attack"]
|
|
||||||
shape = SubResource("CircleShape2D_crbjt")
|
|
||||||
|
|
||||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
|
||||||
sprite_frames = SubResource("SpriteFrames_2w0pn")
|
|
||||||
animation = &"idle"
|
|
||||||
autoplay = "idle"
|
|
||||||
|
|
||||||
[node name="AttackTimer" type="Timer" parent="."]
|
|
||||||
|
|
||||||
[node name="KnockbackTimer" type="Timer" parent="."]
|
|
||||||
wait_time = 0.5
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
|
||||||
libraries = {
|
|
||||||
"": SubResource("AnimationLibrary_ftwl3")
|
|
||||||
}
|
|
||||||
|
|
||||||
[connection signal="death" from="." to="." method="_on_death"]
|
|
||||||
[connection signal="body_entered" from="Notice" to="." method="_on_notice_body_entered"]
|
|
||||||
[connection signal="body_exited" from="Notice" to="." method="_on_notice_body_exited"]
|
|
||||||
[connection signal="body_entered" from="Attack" to="." method="_on_attack_body_entered"]
|
|
||||||
[connection signal="body_exited" from="Attack" to="." method="_on_attack_body_exited"]
|
|
||||||
[connection signal="timeout" from="AttackTimer" to="." method="_on_attack_timer_timeout"]
|
|
||||||
[connection signal="timeout" from="KnockbackTimer" to="." method="_on_knockback_timer_timeout"]
|
|
|
@ -1,51 +0,0 @@
|
||||||
[gd_scene load_steps=3 format=3 uid="uid://bj4ap7bw0imhy"]
|
|
||||||
|
|
||||||
[ext_resource type="FontFile" uid="uid://bbe5csaxy5g3c" path="res://assets/graphics/font/joystix.ttf" id="1_kqyoj"]
|
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0lqnv"]
|
|
||||||
bg_color = Color(0, 0, 0, 1)
|
|
||||||
border_width_left = 4
|
|
||||||
border_width_top = 4
|
|
||||||
border_width_right = 4
|
|
||||||
border_width_bottom = 4
|
|
||||||
border_color = Color(1, 1, 1, 1)
|
|
||||||
|
|
||||||
[node name="Camera" type="Camera2D"]
|
|
||||||
|
|
||||||
[node name="ExpPanel" type="PanelContainer" parent="."]
|
|
||||||
offset_left = 187.0
|
|
||||||
offset_top = 227.0
|
|
||||||
offset_right = 568.0
|
|
||||||
offset_bottom = 316.0
|
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_0lqnv")
|
|
||||||
|
|
||||||
[node name="ExpLabel" type="Label" parent="ExpPanel"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
|
||||||
theme_override_constants/outline_size = 8
|
|
||||||
theme_override_fonts/font = ExtResource("1_kqyoj")
|
|
||||||
theme_override_font_sizes/font_size = 32
|
|
||||||
text = "EXP POINTS
|
|
||||||
"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
vertical_alignment = 1
|
|
||||||
max_lines_visible = 2
|
|
||||||
|
|
||||||
[node name="HPPanel" type="PanelContainer" parent="."]
|
|
||||||
offset_left = -569.0
|
|
||||||
offset_top = 223.0
|
|
||||||
offset_right = -188.0
|
|
||||||
offset_bottom = 318.0
|
|
||||||
theme_override_styles/panel = SubResource("StyleBoxFlat_0lqnv")
|
|
||||||
|
|
||||||
[node name="HPLabel" type="Label" parent="HPPanel"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
|
||||||
theme_override_constants/outline_size = 8
|
|
||||||
theme_override_fonts/font = ExtResource("1_kqyoj")
|
|
||||||
theme_override_font_sizes/font_size = 32
|
|
||||||
text = "HEALTH
|
|
||||||
"
|
|
||||||
horizontal_alignment = 1
|
|
||||||
vertical_alignment = 1
|
|
||||||
max_lines_visible = 2
|
|
|
@ -1,13 +0,0 @@
|
||||||
[gd_scene load_steps=2 format=3 uid="uid://cuvs5tobahf0x"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://code/killzone.gd" id="1_nmw5v"]
|
|
||||||
|
|
||||||
[node name="Killzone" type="Area2D"]
|
|
||||||
script = ExtResource("1_nmw5v")
|
|
||||||
|
|
||||||
[node name="Timer" type="Timer" parent="."]
|
|
||||||
wait_time = 0.6
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
|
|
||||||
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
|
|
|
@ -1,4 +0,0 @@
|
||||||
[gd_scene format=3 uid="uid://dxwjan054vgw0"]
|
|
||||||
|
|
||||||
[node name="Notice" type="Area2D"]
|
|
||||||
collision_mask = 2
|
|
|
@ -1,252 +0,0 @@
|
||||||
[gd_scene load_steps=41 format=3 uid="uid://c7rps714eqdg5"]
|
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://code/player.gd" id="1_iepw4"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://b30lhx6f0uy74" path="res://assets/graphics/player/down_attack/attack_down.png" id="2_nre2m"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dxtdtufshr3tw" path="res://assets/graphics/player/left_attack/attack_left.png" id="3_byxsk"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bsocx4vc2sb6s" path="res://assets/graphics/player/right_attack/attack_right.png" id="4_8jqr7"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://spquud6cue2l" path="res://assets/graphics/player/up_attack/attack_up.png" id="5_jmjp6"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dij0wyugh24b" path="res://assets/graphics/player/down_idle/idle_down.png" id="6_1snvs"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://s31ghurbke8i" path="res://assets/graphics/player/left_idle/idle_left.png" id="7_5uysv"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://dh3tuae2rueb" path="res://assets/graphics/player/right_idle/idle_right.png" id="8_i2d7d"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://clmyffqm3bmo7" path="res://assets/graphics/player/up_idle/idle_up.png" id="9_vygid"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://cs8hfdfrcj3ym" path="res://assets/graphics/player/down/down_0.png" id="10_ix4yj"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://5sv3w4si1nrt" path="res://assets/graphics/player/down/down_1.png" id="11_n0p7r"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bmijyqm6i1c1c" path="res://assets/graphics/player/down/down_2.png" id="12_ewi2q"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://c81kxx6dryb36" path="res://assets/graphics/player/down/down_3.png" id="13_uvjce"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bdbd32x12d60" path="res://assets/graphics/player/left/left_0.png" id="14_1ggki"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://8jvqhpphj6np" path="res://assets/graphics/player/left/left_1.png" id="15_0b7dq"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://cam3122hb8vfa" path="res://assets/graphics/player/left/left_2.png" id="16_4yut5"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://b801fr00dpw8u" path="res://assets/graphics/player/left/left_3.png" id="17_xafwe"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://3bsrlfk4poo1" path="res://assets/graphics/player/right/right_0.png" id="18_g74n4"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bjjcymlv4lkps" path="res://assets/graphics/player/right/right_1.png" id="19_r3xtt"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://cfo77c6bv322j" path="res://assets/graphics/player/right/right_2.png" id="20_5b8or"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://debleuxlf6kdt" path="res://assets/graphics/player/right/right_3.png" id="21_qn80v"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://cbou2pxybkt4d" path="res://assets/graphics/player/up/up_0.png" id="22_5nuot"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://o78bod3x5qss" path="res://assets/graphics/player/up/up_1.png" id="23_vkm2w"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://crspttjaijn4g" path="res://assets/graphics/player/up/up_2.png" id="24_j1lfm"]
|
|
||||||
[ext_resource type="Texture2D" uid="uid://b20c4l52ey54t" path="res://assets/graphics/player/up/up_3.png" id="25_mui4y"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://dh6xtqap2c2j4" path="res://scenes/weapon.tscn" id="26_5p1ew"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://bj4ap7bw0imhy" path="res://scenes/camera.tscn" id="27_dsoxo"]
|
|
||||||
[ext_resource type="Script" path="res://code/AIController2D.gd" id="28_cl3w8"]
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_n5xny"]
|
|
||||||
atlas = ExtResource("6_1snvs")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_1pcxv"]
|
|
||||||
atlas = ExtResource("10_ix4yj")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_hc3sy"]
|
|
||||||
atlas = ExtResource("11_n0p7r")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_ae5o2"]
|
|
||||||
atlas = ExtResource("12_ewi2q")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_dvqaa"]
|
|
||||||
atlas = ExtResource("13_uvjce")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_i6fhi"]
|
|
||||||
atlas = ExtResource("14_1ggki")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_b6l7h"]
|
|
||||||
atlas = ExtResource("22_5nuot")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_43afb"]
|
|
||||||
atlas = ExtResource("23_vkm2w")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_kib0g"]
|
|
||||||
atlas = ExtResource("24_j1lfm")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="AtlasTexture" id="AtlasTexture_3nifw"]
|
|
||||||
atlas = ExtResource("25_mui4y")
|
|
||||||
region = Rect2(0, 0, 64, 64)
|
|
||||||
|
|
||||||
[sub_resource type="SpriteFrames" id="SpriteFrames_bjuky"]
|
|
||||||
animations = [{
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("2_nre2m")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"attack_down",
|
|
||||||
"speed": 2.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("3_byxsk")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"attack_left",
|
|
||||||
"speed": 2.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("4_8jqr7")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"attack_right",
|
|
||||||
"speed": 2.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("5_jmjp6")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"attack_up",
|
|
||||||
"speed": 2.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_n5xny")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"idle_down",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("7_5uysv")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"idle_left",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("8_i2d7d")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"idle_right",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("9_vygid")
|
|
||||||
}],
|
|
||||||
"loop": false,
|
|
||||||
"name": &"idle_up",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_1pcxv")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_hc3sy")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_ae5o2")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_dvqaa")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"move_down",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_i6fhi")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("15_0b7dq")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("16_4yut5")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("17_xafwe")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"move_left",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("18_g74n4")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("19_r3xtt")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("20_5b8or")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("21_qn80v")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"move_right",
|
|
||||||
"speed": 10.0
|
|
||||||
}, {
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_b6l7h")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_43afb")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_kib0g")
|
|
||||||
}, {
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": SubResource("AtlasTexture_3nifw")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"move_up",
|
|
||||||
"speed": 10.0
|
|
||||||
}]
|
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_ilpv0"]
|
|
||||||
size = Vector2(47, 48)
|
|
||||||
|
|
||||||
[node name="Player" type="CharacterBody2D"]
|
|
||||||
z_index = 5
|
|
||||||
position = Vector2(0, -31)
|
|
||||||
collision_layer = 2
|
|
||||||
collision_mask = 6
|
|
||||||
script = ExtResource("1_iepw4")
|
|
||||||
|
|
||||||
[node name="Button" type="Button" parent="."]
|
|
||||||
modulate = Color(1, 1, 1, 0)
|
|
||||||
self_modulate = Color(1, 1, 1, 0)
|
|
||||||
offset_left = -85.0
|
|
||||||
offset_top = -86.0
|
|
||||||
offset_right = 85.0
|
|
||||||
offset_bottom = 93.0
|
|
||||||
icon_alignment = 1
|
|
||||||
|
|
||||||
[node name="Weapon" parent="." instance=ExtResource("26_5p1ew")]
|
|
||||||
position = Vector2(-10, 48)
|
|
||||||
|
|
||||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
|
||||||
position = Vector2(0, -1)
|
|
||||||
sprite_frames = SubResource("SpriteFrames_bjuky")
|
|
||||||
animation = &"idle_down"
|
|
||||||
autoplay = "idle_down"
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
|
||||||
position = Vector2(-1.5, 2)
|
|
||||||
shape = SubResource("RectangleShape2D_ilpv0")
|
|
||||||
|
|
||||||
[node name="AttackTimer" type="Timer" parent="."]
|
|
||||||
wait_time = 0.4
|
|
||||||
one_shot = true
|
|
||||||
|
|
||||||
[node name="Camera" parent="." instance=ExtResource("27_dsoxo")]
|
|
||||||
visible = false
|
|
||||||
position = Vector2(-2, 18)
|
|
||||||
|
|
||||||
[node name="AIController2D" type="Node2D" parent="."]
|
|
||||||
script = ExtResource("28_cl3w8")
|
|
||||||
|
|
||||||
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
|
|
||||||
[connection signal="toggled" from="Button" to="." method="_on_button_toggled"]
|
|
||||||
[connection signal="timeout" from="AttackTimer" to="." method="_on_attack_timer_timeout"]
|
|
|
@ -1,100 +0,0 @@
|
||||||
[gd_scene load_steps=8 format=3 uid="uid://dh6xtqap2c2j4"]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://bpobfwslfc3qy" path="res://assets/graphics/weapons/sword/down.png" id="1_1yw4v"]
|
|
||||||
[ext_resource type="Script" path="res://code/weapon.gd" id="1_utwve"]
|
|
||||||
|
|
||||||
[sub_resource type="SpriteFrames" id="SpriteFrames_4n6kd"]
|
|
||||||
animations = [{
|
|
||||||
"frames": [{
|
|
||||||
"duration": 1.0,
|
|
||||||
"texture": ExtResource("1_1yw4v")
|
|
||||||
}],
|
|
||||||
"loop": true,
|
|
||||||
"name": &"down",
|
|
||||||
"speed": 5.0
|
|
||||||
}]
|
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_iedax"]
|
|
||||||
size = Vector2(51, 79)
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_0n3f6"]
|
|
||||||
length = 0.001
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("AnimatedSprite2D:visible")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath("CollisionShape2D:disabled")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="Animation" id="Animation_vwacy"]
|
|
||||||
resource_name = "attack"
|
|
||||||
tracks/0/type = "value"
|
|
||||||
tracks/0/imported = false
|
|
||||||
tracks/0/enabled = true
|
|
||||||
tracks/0/path = NodePath("AnimatedSprite2D:visible")
|
|
||||||
tracks/0/interp = 1
|
|
||||||
tracks/0/loop_wrap = true
|
|
||||||
tracks/0/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [true]
|
|
||||||
}
|
|
||||||
tracks/1/type = "value"
|
|
||||||
tracks/1/imported = false
|
|
||||||
tracks/1/enabled = true
|
|
||||||
tracks/1/path = NodePath("CollisionShape2D:disabled")
|
|
||||||
tracks/1/interp = 1
|
|
||||||
tracks/1/loop_wrap = true
|
|
||||||
tracks/1/keys = {
|
|
||||||
"times": PackedFloat32Array(0),
|
|
||||||
"transitions": PackedFloat32Array(1),
|
|
||||||
"update": 1,
|
|
||||||
"values": [false]
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_sb3pq"]
|
|
||||||
_data = {
|
|
||||||
"RESET": SubResource("Animation_0n3f6"),
|
|
||||||
"attack": SubResource("Animation_vwacy")
|
|
||||||
}
|
|
||||||
|
|
||||||
[node name="Weapon" type="Area2D"]
|
|
||||||
collision_mask = 4
|
|
||||||
script = ExtResource("1_utwve")
|
|
||||||
|
|
||||||
[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
|
|
||||||
visible = false
|
|
||||||
sprite_frames = SubResource("SpriteFrames_4n6kd")
|
|
||||||
animation = &"down"
|
|
||||||
flip_h = true
|
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
|
||||||
position = Vector2(10.5, -15.5)
|
|
||||||
shape = SubResource("RectangleShape2D_iedax")
|
|
||||||
disabled = true
|
|
||||||
|
|
||||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
|
||||||
libraries = {
|
|
||||||
"": SubResource("AnimationLibrary_sb3pq")
|
|
||||||
}
|
|
||||||
|
|
||||||
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
|
|
|
@ -1,43 +0,0 @@
|
||||||
# meta-name: AI Controller Logic
|
|
||||||
# meta-description: Methods that need implementing for AI controllers
|
|
||||||
# meta-default: true
|
|
||||||
extends _BASE_
|
|
||||||
|
|
||||||
#-- Methods that need implementing using the "extend script" option in Godot --#
|
|
||||||
|
|
||||||
func get_obs() -> Dictionary:
|
|
||||||
assert(false, "the get_obs method is not implemented when extending from ai_controller")
|
|
||||||
return {"obs":[]}
|
|
||||||
|
|
||||||
func get_reward() -> float:
|
|
||||||
assert(false, "the get_reward method is not implemented when extending from ai_controller")
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
func get_action_space() -> Dictionary:
|
|
||||||
assert(false, "the get get_action_space method is not implemented when extending from ai_controller")
|
|
||||||
return {
|
|
||||||
"example_actions_continous" : {
|
|
||||||
"size": 2,
|
|
||||||
"action_type": "continuous"
|
|
||||||
},
|
|
||||||
"example_actions_discrete" : {
|
|
||||||
"size": 2,
|
|
||||||
"action_type": "discrete"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func set_action(action) -> void:
|
|
||||||
assert(false, "the get set_action method is not implemented when extending from ai_controller")
|
|
||||||
# -----------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
#-- Methods that can be overridden if needed --#
|
|
||||||
|
|
||||||
#func get_obs_space() -> Dictionary:
|
|
||||||
# May need overriding if the obs space is complex
|
|
||||||
# var obs = get_obs()
|
|
||||||
# return {
|
|
||||||
# "obs": {
|
|
||||||
# "size": [len(obs["obs"])],
|
|
||||||
# "space": "box"
|
|
||||||
# },
|
|
||||||
# }
|
|
|
@ -1,33 +0,0 @@
|
||||||
with import <nixpkgs> {};
|
|
||||||
|
|
||||||
pkgs.mkShell rec {
|
|
||||||
|
|
||||||
dotnetPkg = (with dotnetCorePackages; combinePackages [
|
|
||||||
sdk_9_0
|
|
||||||
]);
|
|
||||||
|
|
||||||
NIX_LD_LIBRARY_PATH = lib.makeLibraryPath ([
|
|
||||||
stdenv.cc.cc
|
|
||||||
] ++ deps);
|
|
||||||
|
|
||||||
NIX_LD = "${pkgs.stdenv.cc.libc_bin}/bin/ld.so";
|
|
||||||
|
|
||||||
nativeBuildInputs = [] ++ deps;
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
|
|
||||||
DOTNET_ROOT="${dotnetPkg}"
|
|
||||||
'';
|
|
||||||
|
|
||||||
packages = [
|
|
||||||
dotnet-sdk
|
|
||||||
xorg.libX11
|
|
||||||
];
|
|
||||||
|
|
||||||
deps = [
|
|
||||||
zlib
|
|
||||||
zlib.dev
|
|
||||||
openssl
|
|
||||||
dotnetPkg
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from stable_baselines3 import PPO
|
|
||||||
from stable_baselines3.common.callbacks import CheckpointCallback
|
|
||||||
from stable_baselines3.common.vec_env.vec_monitor import VecMonitor
|
|
||||||
|
|
||||||
from godot_rl.core.utils import can_import
|
|
||||||
from godot_rl.wrappers.onnx.stable_baselines_export import export_ppo_model_as_onnx
|
|
||||||
from godot_rl.wrappers.stable_baselines_wrapper import StableBaselinesGodotEnv
|
|
||||||
|
|
||||||
# To download the env source and binary:
|
|
||||||
# 1. gdrl.env_from_hub -r edbeeching/godot_rl_BallChase
|
|
||||||
# 2. chmod +x examples/godot_rl_BallChase/bin/BallChase.x86_64
|
|
||||||
if can_import("ray"):
|
|
||||||
print("WARNING, stable baselines and ray[rllib] are not compatible")
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
|
||||||
parser.add_argument(
|
|
||||||
"--env_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The Godot binary to use, do not include for in editor training",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--experiment_dir",
|
|
||||||
default="logs/sb3",
|
|
||||||
type=str,
|
|
||||||
help="The name of the experiment directory, in which the tensorboard logs and checkpoints (if enabled) are "
|
|
||||||
"getting stored.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--experiment_name",
|
|
||||||
default="experiment",
|
|
||||||
type=str,
|
|
||||||
help="The name of the experiment, which will be displayed in tensorboard and "
|
|
||||||
"for checkpoint directory and name (if enabled).",
|
|
||||||
)
|
|
||||||
parser.add_argument("--seed", type=int, default=0, help="seed of the experiment")
|
|
||||||
parser.add_argument(
|
|
||||||
"--resume_model_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The path to a model file previously saved using --save_model_path or a checkpoint saved using "
|
|
||||||
"--save_checkpoints_frequency. Use this to resume training or infer from a saved model.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--save_model_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="The path to use for saving the trained sb3 model after training is complete. Saved model can be used later "
|
|
||||||
"to resume training. Extension will be set to .zip",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--save_checkpoint_frequency",
|
|
||||||
default=None,
|
|
||||||
type=int,
|
|
||||||
help=(
|
|
||||||
"If set, will save checkpoints every 'frequency' environment steps. "
|
|
||||||
"Requires a unique --experiment_name or --experiment_dir for each run. "
|
|
||||||
"Does not need --save_model_path to be set. "
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--onnx_export_path",
|
|
||||||
default=None,
|
|
||||||
type=str,
|
|
||||||
help="If included, will export onnx file after training to the path specified.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--timesteps",
|
|
||||||
default=1_000_000,
|
|
||||||
type=int,
|
|
||||||
help="The number of environment steps to train for, default is 1_000_000. If resuming from a saved model, "
|
|
||||||
"it will continue training for this amount of steps from the saved state without counting previously trained "
|
|
||||||
"steps",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--inference",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="Instead of training, it will run inference on a loaded model for --timesteps steps. "
|
|
||||||
"Requires --resume_model_path to be set.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--linear_lr_schedule",
|
|
||||||
default=False,
|
|
||||||
action="store_true",
|
|
||||||
help="Use a linear LR schedule for training. If set, learning rate will decrease until it reaches 0 at "
|
|
||||||
"--timesteps"
|
|
||||||
"value. Note: On resuming training, the schedule will reset. If disabled, constant LR will be used.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--viz",
|
|
||||||
action="store_true",
|
|
||||||
help="If set, the simulation will be displayed in a window during training. Otherwise "
|
|
||||||
"training will run without rendering the simulation. This setting does not apply to in-editor training.",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
parser.add_argument("--speedup", default=1, type=int, help="Whether to speed up the physics in the env")
|
|
||||||
parser.add_argument(
|
|
||||||
"--n_parallel",
|
|
||||||
default=1,
|
|
||||||
type=int,
|
|
||||||
help="How many instances of the environment executable to " "launch - requires --env_path to be set if > 1.",
|
|
||||||
)
|
|
||||||
args, extras = parser.parse_known_args()
|
|
||||||
|
|
||||||
|
|
||||||
def handle_onnx_export():
|
|
||||||
# Enforce the extension of onnx and zip when saving model to avoid potential conflicts in case of same name
|
|
||||||
# and extension used for both
|
|
||||||
if args.onnx_export_path is not None:
|
|
||||||
path_onnx = pathlib.Path(args.onnx_export_path).with_suffix(".onnx")
|
|
||||||
print("Exporting onnx to: " + os.path.abspath(path_onnx))
|
|
||||||
export_ppo_model_as_onnx(model, str(path_onnx))
|
|
||||||
|
|
||||||
|
|
||||||
def handle_model_save():
|
|
||||||
if args.save_model_path is not None:
|
|
||||||
zip_save_path = pathlib.Path(args.save_model_path).with_suffix(".zip")
|
|
||||||
print("Saving model to: " + os.path.abspath(zip_save_path))
|
|
||||||
model.save(zip_save_path)
|
|
||||||
|
|
||||||
|
|
||||||
def close_env():
|
|
||||||
try:
|
|
||||||
print("closing env")
|
|
||||||
env.close()
|
|
||||||
except Exception as e:
|
|
||||||
print("Exception while closing env: ", e)
|
|
||||||
|
|
||||||
|
|
||||||
path_checkpoint = os.path.join(args.experiment_dir, args.experiment_name + "_checkpoints")
|
|
||||||
abs_path_checkpoint = os.path.abspath(path_checkpoint)
|
|
||||||
|
|
||||||
# Prevent overwriting existing checkpoints when starting a new experiment if checkpoint saving is enabled
|
|
||||||
if args.save_checkpoint_frequency is not None and os.path.isdir(path_checkpoint):
|
|
||||||
raise RuntimeError(
|
|
||||||
abs_path_checkpoint + " folder already exists. "
|
|
||||||
"Use a different --experiment_dir, or --experiment_name,"
|
|
||||||
"or if previous checkpoints are not needed anymore, "
|
|
||||||
"remove the folder containing the checkpoints. "
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.inference and args.resume_model_path is None:
|
|
||||||
raise parser.error("Using --inference requires --resume_model_path to be set.")
|
|
||||||
|
|
||||||
if args.env_path is None and args.viz:
|
|
||||||
print("Info: Using --viz without --env_path set has no effect, in-editor training will always render.")
|
|
||||||
|
|
||||||
env = StableBaselinesGodotEnv(
|
|
||||||
env_path=args.env_path, show_window=args.viz, seed=args.seed, n_parallel=args.n_parallel, speedup=args.speedup
|
|
||||||
)
|
|
||||||
env = VecMonitor(env)
|
|
||||||
|
|
||||||
|
|
||||||
# LR schedule code snippet from:
|
|
||||||
# https://stable-baselines3.readthedocs.io/en/master/guide/examples.html#learning-rate-schedule
|
|
||||||
def linear_schedule(initial_value: float) -> Callable[[float], float]:
|
|
||||||
"""
|
|
||||||
Linear learning rate schedule.
|
|
||||||
|
|
||||||
:param initial_value: Initial learning rate.
|
|
||||||
:return: schedule that computes
|
|
||||||
current learning rate depending on remaining progress
|
|
||||||
"""
|
|
||||||
|
|
||||||
def func(progress_remaining: float) -> float:
|
|
||||||
"""
|
|
||||||
Progress will decrease from 1 (beginning) to 0.
|
|
||||||
|
|
||||||
:param progress_remaining:
|
|
||||||
:return: current learning rate
|
|
||||||
"""
|
|
||||||
return progress_remaining * initial_value
|
|
||||||
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
if args.resume_model_path is None:
|
|
||||||
learning_rate = 0.0003 if not args.linear_lr_schedule else linear_schedule(0.0003)
|
|
||||||
model: PPO = PPO(
|
|
||||||
"MultiInputPolicy",
|
|
||||||
env,
|
|
||||||
batch_size=128,
|
|
||||||
ent_coef=0.001,
|
|
||||||
verbose=2,
|
|
||||||
n_steps=32,
|
|
||||||
tensorboard_log=args.experiment_dir,
|
|
||||||
learning_rate=learning_rate,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
path_zip = pathlib.Path(args.resume_model_path)
|
|
||||||
print("Loading model: " + os.path.abspath(path_zip))
|
|
||||||
model = PPO.load(path_zip, env=env, tensorboard_log=args.experiment_dir)
|
|
||||||
|
|
||||||
if args.inference:
|
|
||||||
obs = env.reset()
|
|
||||||
for i in range(args.timesteps):
|
|
||||||
action, _state = model.predict(obs, deterministic=True)
|
|
||||||
obs, reward, done, info = env.step(action)
|
|
||||||
else:
|
|
||||||
learn_arguments = dict(total_timesteps=args.timesteps, tb_log_name=args.experiment_name)
|
|
||||||
if args.save_checkpoint_frequency:
|
|
||||||
print("Checkpoint saving enabled. Checkpoints will be saved to: " + abs_path_checkpoint)
|
|
||||||
checkpoint_callback = CheckpointCallback(
|
|
||||||
save_freq=(args.save_checkpoint_frequency // env.num_envs),
|
|
||||||
save_path=path_checkpoint,
|
|
||||||
name_prefix=args.experiment_name,
|
|
||||||
)
|
|
||||||
learn_arguments["callback"] = checkpoint_callback
|
|
||||||
try:
|
|
||||||
model.learn(**learn_arguments)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(
|
|
||||||
"""Training interrupted by user. Will save if --save_model_path was
|
|
||||||
used and/or export if --onnx_export_path was used."""
|
|
||||||
)
|
|
||||||
|
|
||||||
close_env()
|
|
||||||
handle_onnx_export()
|
|
||||||
handle_model_save()
|
|
Before Width: | Height: | Size: 494 B |