Moved Godot version to own repo for clarity

This commit is contained in:
Vasilis Valatsos 2024-05-22 08:44:04 +02:00
parent 5f0d5484bf
commit a730ca7a84
698 changed files with 0 additions and 9135 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -1,56 +0,0 @@
# TODO Manager
![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png)
## 简单而灵活
- 支持 GDScriptC# 和 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)

View file

@ -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

View file

@ -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"]

View file

@ -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"

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View file

@ -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"

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -1,9 +0,0 @@
@tool
extends RefCounted
var pattern : String
var title : String
var content : String
var script_path : String
var line_number : int

View file

@ -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

View file

@ -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

View file

@ -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")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

View file

@ -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();
}
}
}

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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"

View file

@ -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")

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

View file

@ -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"
# },
# }

View file

@ -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")

View file

@ -1,3 +0,0 @@
extends Label

View file

@ -1,4 +0,0 @@
extends Area2D
func _on_body_entered(body):
body.death.emit()

View file

@ -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()

View file

@ -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")

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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())

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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,
}

View file

@ -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

View file

@ -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"

View file

@ -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]

View file

@ -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"

View file

@ -1,4 +0,0 @@
[gd_scene format=3 uid="uid://c1i0nirxfagfh"]
[node name="Attack" type="Area2D"]
collision_mask = 2

View file

@ -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"]

View file

@ -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

View file

@ -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"]

File diff suppressed because one or more lines are too long

View file

@ -1,4 +0,0 @@
[gd_scene format=3 uid="uid://dxwjan054vgw0"]
[node name="Notice" type="Area2D"]
collision_mask = 2

View file

@ -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"]

View file

@ -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"]

View file

@ -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"
# },
# }

View file

@ -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
];
}

View file

@ -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()

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Some files were not shown because too many files have changed in this diff Show more