add addons and swarm engine skeleton
This commit is contained in:
157
addons/godot_resource_groups/csharp/ResourceGroup.cs
Normal file
157
addons/godot_resource_groups/csharp/ResourceGroup.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace GodotResourceGroups
|
||||
{
|
||||
using Godot;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
public class ResourceGroup
|
||||
{
|
||||
private readonly Resource _wrapped;
|
||||
|
||||
private ResourceGroup(Resource wrapped)
|
||||
{
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource group from the given path.
|
||||
/// </summary>
|
||||
public static ResourceGroup Of(string path)
|
||||
{
|
||||
var wrapped = GD.Load<Resource>(path);
|
||||
return Of(wrapped);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a typesafe wrapper for the given resource group.
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static ResourceGroup Of(Resource wrapped)
|
||||
{
|
||||
if (wrapped == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(wrapped));
|
||||
}
|
||||
|
||||
if (wrapped.GetScript().As<Script>() is not GDScript gdScript ||
|
||||
!gdScript.ResourcePath.EndsWith("resource_group.gd"))
|
||||
{
|
||||
throw new ArgumentException("Resource is not a resource group");
|
||||
}
|
||||
|
||||
return new ResourceGroup(wrapped);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns all include patterns in this resource group.
|
||||
/// </summary>
|
||||
public List<string> Includes => new(_wrapped.Get("includes").AsStringArray());
|
||||
|
||||
/// <summary>
|
||||
/// Returns all exclude patterns in this resource group.
|
||||
/// </summary>
|
||||
public List<string> Excludes => new(_wrapped.Get("excludes").AsStringArray());
|
||||
|
||||
/// <summary>
|
||||
/// Returns all paths in this resource group.
|
||||
/// </summary>
|
||||
public List<string> Paths => new(_wrapped.Get("paths").AsStringArray());
|
||||
|
||||
/// <summary>
|
||||
/// Loads all resources in this resource group.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Resource> LoadAll() => new(_wrapped.Call("load_all").AsGodotObjectArray<Resource>());
|
||||
|
||||
/// <summary>
|
||||
/// Loads all resources in this resource group in the background. The given callback will be called
|
||||
/// for each resource that is loaded. The callback will be called from the main thread.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ResourceGroupBackgroundLoader"/> which can be used to cancel the background loading.</returns>
|
||||
public ResourceGroupBackgroundLoader LoadAllInBackground(
|
||||
Action<ResourceGroupBackgroundLoader.ResourceLoadingInfo> callback)
|
||||
{
|
||||
var godotCallback =
|
||||
Callable.From<GodotObject>(it => callback(new ResourceGroupBackgroundLoader.ResourceLoadingInfo(it)));
|
||||
var wrapped = _wrapped.Call("load_all_in_background", godotCallback);
|
||||
return new ResourceGroupBackgroundLoader(wrapped.AsGodotObject());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all resources that match the given include and exclude specification from this resource group in the background.
|
||||
/// The given callback will be called for each resource that is loaded. The callback will be called from the main thread.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ResourceGroupBackgroundLoader"/> which can be used to cancel the background loading.</returns>
|
||||
public ResourceGroupBackgroundLoader LoadMatchingInBackground(IEnumerable<string> includes,
|
||||
IEnumerable<string> excludes,
|
||||
Action<ResourceGroupBackgroundLoader.ResourceLoadingInfo> callback)
|
||||
{
|
||||
var godotCallback =
|
||||
Callable.From<GodotObject>(it => callback(new ResourceGroupBackgroundLoader.ResourceLoadingInfo(it)));
|
||||
var wrapped = _wrapped.Call("__csharp_load_matching_in_background", ToArray(includes), ToArray(excludes),
|
||||
godotCallback);
|
||||
return new ResourceGroupBackgroundLoader(wrapped.AsGodotObject());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads all items of the group into the given collection. If an item is of the wrong
|
||||
/// type it will be skipped and an error is printed.
|
||||
/// </summary>
|
||||
public void LoadAllInto<T>(ICollection<T> destination)
|
||||
{
|
||||
var items = _wrapped.Call("load_all").AsGodotObjectArray<Resource>();
|
||||
PushInto(items, destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all paths in this resource group that match the given patterns.
|
||||
/// </summary>
|
||||
public List<string> GetMatchingPaths(IEnumerable<string> includes, IEnumerable<string> excludes)
|
||||
=> new(_wrapped.Call("__csharp_get_matching_paths", ToArray(includes), ToArray(excludes)).AsStringArray());
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns all resources in this resource group that match the given patterns.
|
||||
/// </summary>
|
||||
public List<Resource> LoadMatching(IEnumerable<string> includes, IEnumerable<string> excludes)
|
||||
=> new(_wrapped.Call("__csharp_load_matching", ToArray(includes), ToArray(excludes))
|
||||
.AsGodotObjectArray<Resource>());
|
||||
|
||||
/// <summary>
|
||||
/// Loads all resources in this resource group that match the given patterns and stores them
|
||||
/// into the given collection. If an item is of the wrong type it will be skipped and an
|
||||
/// error is printed.
|
||||
/// </summary>
|
||||
public void LoadMatchingInto<T>(ICollection<T> destination, IEnumerable<string> includes,
|
||||
IEnumerable<string> excludes)
|
||||
{
|
||||
var items = _wrapped.Call("__csharp_load_matching", ToArray(includes), ToArray(excludes))
|
||||
.AsGodotObjectArray<Resource>();
|
||||
PushInto(items, destination);
|
||||
}
|
||||
|
||||
|
||||
private static string[] ToArray(IEnumerable<string> enumerable)
|
||||
{
|
||||
return enumerable as string[] ?? new List<string>(enumerable).ToArray();
|
||||
}
|
||||
|
||||
private static void PushInto<T>(IEnumerable<Resource> items, ICollection<T> destination)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is T t)
|
||||
{
|
||||
destination.Add(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
GD.PushError("Item ", item, " is not of required type ", typeof(T).Namespace, ". Skipping.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Godot;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace GodotResourceGroups;
|
||||
|
||||
/// <summary>
|
||||
/// A background loader for resource groups. Use <see cref="ResourceGroup.LoadAllInBackground"/> or
|
||||
/// <see cref="ResourceGroup.LoadMatchingInBackground"/>to create an instance of this class.
|
||||
/// </summary>
|
||||
public class ResourceGroupBackgroundLoader
|
||||
{
|
||||
private readonly GodotObject _wrapped;
|
||||
|
||||
internal ResourceGroupBackgroundLoader(GodotObject wrapped)
|
||||
{
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the background loading. The callback will not be called anymore after this method returns.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
_wrapped.Call("cancel");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the background loading is done. Will also return true if the loading was cancelled.
|
||||
/// </summary>
|
||||
public bool IsDone()
|
||||
{
|
||||
return _wrapped.Call("is_done").As<bool>();
|
||||
}
|
||||
|
||||
public readonly struct ResourceLoadingInfo
|
||||
{
|
||||
private readonly GodotObject _wrapped;
|
||||
|
||||
public ResourceLoadingInfo(GodotObject wrapped)
|
||||
{
|
||||
_wrapped = wrapped;
|
||||
}
|
||||
|
||||
public bool Success => _wrapped.Get("success").As<bool>();
|
||||
public string Path => _wrapped.Get("path").As<string>();
|
||||
public Resource Resource => _wrapped.Get("resource").As<Resource>();
|
||||
public float Progress => _wrapped.Get("progress").As<float>();
|
||||
public bool Last => _wrapped.Get("last").As<bool>();
|
||||
}
|
||||
}
|
||||
62
addons/godot_resource_groups/godot_resource_groups.gd
Normal file
62
addons/godot_resource_groups/godot_resource_groups.gd
Normal file
@@ -0,0 +1,62 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const ResourceScanner = preload("resource_scanner.gd")
|
||||
const ResourceGroupScanner = preload("resource_group_scanner.gd")
|
||||
const ResourceGroupsExportPlugin = preload("godot_resource_groups_export_plugin.gd")
|
||||
const REBUILD_SETTING: StringName = "godot_resource_groups/auto_rebuild"
|
||||
|
||||
var _group_scanner: ResourceGroupScanner
|
||||
var _export_plugin: ResourceGroupsExportPlugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
add_tool_menu_item("Rebuild project resource groups", _rebuild_resource_groups)
|
||||
_group_scanner = ResourceGroupScanner.new(get_editor_interface().get_resource_filesystem())
|
||||
|
||||
# try to get the setting, if it doesn't exist, set it to true
|
||||
var auto_rebuild: bool = ProjectSettings.get_setting(REBUILD_SETTING, true)
|
||||
|
||||
# make sure it is there
|
||||
ProjectSettings.set_setting("godot_resource_groups/auto_rebuild", auto_rebuild)
|
||||
# add property info so it shows up in the editor
|
||||
ProjectSettings.add_property_info({
|
||||
"name": "godot_resource_groups/auto_rebuild",
|
||||
"description": "Automatically rebuild resource groups when the project is built.",
|
||||
"type": TYPE_BOOL,
|
||||
})
|
||||
|
||||
# register the export plugin
|
||||
_export_plugin = ResourceGroupsExportPlugin.new(_rebuild_resource_groups)
|
||||
add_export_plugin(_export_plugin)
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_tool_menu_item("Rebuild project resource groups")
|
||||
remove_export_plugin(_export_plugin)
|
||||
|
||||
|
||||
func _build() -> bool:
|
||||
# always read the setting to make sure it is up to date
|
||||
var auto_rebuild: bool = ProjectSettings.get_setting(REBUILD_SETTING, true)
|
||||
if auto_rebuild:
|
||||
_rebuild_resource_groups()
|
||||
return true
|
||||
|
||||
|
||||
func _rebuild_resource_groups():
|
||||
var start = Time.get_unix_time_from_system()
|
||||
var groups = _group_scanner.scan()
|
||||
|
||||
for group in groups:
|
||||
var resource_scanner = ResourceScanner.new(group, get_editor_interface().get_resource_filesystem())
|
||||
var resource_paths = resource_scanner.scan()
|
||||
|
||||
group.paths = resource_paths
|
||||
|
||||
|
||||
ResourceSaver.save(group)
|
||||
get_editor_interface().get_resource_filesystem().update_file(group.resource_path)
|
||||
|
||||
var end = Time.get_unix_time_from_system()
|
||||
print("Rebuilt resource groups in %.2f seconds." % [end-start])
|
||||
@@ -0,0 +1,14 @@
|
||||
@tool
|
||||
extends EditorExportPlugin
|
||||
|
||||
var _on_export:Callable
|
||||
|
||||
func _init(on_export:Callable):
|
||||
_on_export = on_export
|
||||
|
||||
func _export_begin(features, is_debug, path, flags):
|
||||
_on_export.call()
|
||||
|
||||
|
||||
func _get_name() -> String:
|
||||
return "Godot Resource Groups Export Plugin"
|
||||
140
addons/godot_resource_groups/path_verifier.gd
Normal file
140
addons/godot_resource_groups/path_verifier.gd
Normal file
@@ -0,0 +1,140 @@
|
||||
@tool
|
||||
|
||||
var _include_regexes:Array[RegEx]
|
||||
var _exclude_regexes:Array[RegEx]
|
||||
|
||||
func _init(base_folder:String, include_patterns:Array[String], exclude_patterns:Array[String]):
|
||||
# compile the include and exclude patterns to regular expressions, so we don't
|
||||
# have to do it for each file
|
||||
_include_regexes = []
|
||||
_exclude_regexes = []
|
||||
|
||||
|
||||
for pattern in include_patterns:
|
||||
if pattern == "" or pattern == null:
|
||||
continue
|
||||
_include_regexes.append(_compile_pattern(base_folder, pattern))
|
||||
|
||||
for pattern in exclude_patterns:
|
||||
if pattern == "" or pattern == null:
|
||||
continue
|
||||
_exclude_regexes.append(_compile_pattern(base_folder, pattern))
|
||||
|
||||
|
||||
|
||||
## Compiles the given pattern to a regular expression.
|
||||
func _compile_pattern(base_folder:String, pattern:String) -> RegEx:
|
||||
# ** - matches zero or more characters (including "/")
|
||||
# * - matches zero or more characters (excluding "/")
|
||||
# ? - matches one character
|
||||
|
||||
# we convert the pattern to a regular expression
|
||||
# ** becomes .*
|
||||
# * becomes [^/]* (any number of characters except /)
|
||||
# ? becomes [^/] (any character except /)
|
||||
# all other characters are escaped
|
||||
# the pattern is anchored at the beginning and end of the string
|
||||
# the pattern is case-sensitive
|
||||
|
||||
var regex = "^" + _escape_string(base_folder) + "/"
|
||||
var i = 0
|
||||
var len = pattern.length()
|
||||
|
||||
while i < len:
|
||||
var c = pattern[i]
|
||||
if c == "*":
|
||||
if i + 1 < len and pattern[i + 1] == "*":
|
||||
# ** - matches zero or more characters (including "/")
|
||||
regex += ".*"
|
||||
i += 2
|
||||
else:
|
||||
# * - matches zero or more characters (excluding "/")
|
||||
regex += "[^\\/]*"
|
||||
i += 1
|
||||
elif c == "?":
|
||||
# ? - matches one character
|
||||
regex += "[^\\/]"
|
||||
i += 1
|
||||
else:
|
||||
# escape all other characters
|
||||
regex += _escape_character(c)
|
||||
i += 1
|
||||
|
||||
regex += "$"
|
||||
|
||||
var result = RegEx.new()
|
||||
result.compile(regex)
|
||||
return result
|
||||
|
||||
|
||||
func _escape_string(c:String) -> String:
|
||||
var result = ""
|
||||
for i in len(c):
|
||||
result += _escape_character(c[i])
|
||||
return result
|
||||
|
||||
## Escapes the given character for use in a regular expression.
|
||||
## No clue why this is not built-in.
|
||||
func _escape_character(c:String) -> String:
|
||||
if c == "\\":
|
||||
return "\\\\"
|
||||
elif c == "^":
|
||||
return "\\^"
|
||||
elif c == "$":
|
||||
return "\\$"
|
||||
elif c == ".":
|
||||
return "\\."
|
||||
elif c == "|":
|
||||
return "\\|"
|
||||
elif c == "?":
|
||||
return "\\?"
|
||||
elif c == "*":
|
||||
return "\\*"
|
||||
elif c == "+":
|
||||
return "\\+"
|
||||
elif c == "(":
|
||||
return "\\("
|
||||
elif c == ")":
|
||||
return "\\)"
|
||||
elif c == "{":
|
||||
return "\\{"
|
||||
elif c == "}":
|
||||
return "\\}"
|
||||
elif c == "[":
|
||||
return "\\["
|
||||
elif c == "]":
|
||||
return "\\]"
|
||||
elif c == "/":
|
||||
return "\\/"
|
||||
else:
|
||||
return c
|
||||
|
||||
|
||||
func matches(file:String) -> bool:
|
||||
# the group definition has a list of include and exclude patterns
|
||||
# if the list of include patterns is empty, all files match
|
||||
# any file that matches an exclude pattern is excluded
|
||||
# we allow * as a wildcard for a single path segment
|
||||
# we allow ** as a wildcard for multiple path segments
|
||||
|
||||
if _include_regexes.size() > 0:
|
||||
var found = false
|
||||
# the file must match at least one include pattern
|
||||
for item in _include_regexes:
|
||||
if item.search(file) != null:
|
||||
found = true
|
||||
break
|
||||
|
||||
if not found:
|
||||
if file.contains(".txt"):
|
||||
print("file ", file , " did not match any regex")
|
||||
return false
|
||||
|
||||
# the file must not match any exclude pattern
|
||||
for item in _exclude_regexes:
|
||||
if item.search(file) != null:
|
||||
if file.contains(".txt"):
|
||||
print("file ", file , " was excluded ")
|
||||
return false
|
||||
|
||||
return true
|
||||
7
addons/godot_resource_groups/plugin.cfg
Normal file
7
addons/godot_resource_groups/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Godot Resource Groups"
|
||||
description="A plugin which helps you to conveniently access all resources in your project without having to hardcode any paths."
|
||||
author="Jan Thomä"
|
||||
version="0.4.1"
|
||||
script="godot_resource_groups.gd"
|
||||
103
addons/godot_resource_groups/resource_group.gd
Normal file
103
addons/godot_resource_groups/resource_group.gd
Normal file
@@ -0,0 +1,103 @@
|
||||
@tool
|
||||
## A resource group is a set of resources in the project
|
||||
## that you want to access at runtime.
|
||||
@icon("resource_group.svg")
|
||||
class_name ResourceGroup
|
||||
extends Resource
|
||||
|
||||
const PathVerifier = preload("path_verifier.gd")
|
||||
|
||||
## The base folder for locating files in this resource group.
|
||||
@export_dir var base_folder:String = ""
|
||||
## Files that should be included. Can contain ant-style wildcards:
|
||||
## ** - matches zero or more characters (including "/")
|
||||
## * - matches zero or more characters (excluding "/")
|
||||
## ? - matches one character
|
||||
@export var includes:Array[String] = []
|
||||
|
||||
## Files which should be excluded. Is applied after the include filter.
|
||||
## Can also contain ant-style wildcards.
|
||||
@export var excludes:Array[String] = []
|
||||
|
||||
## The paths of the project that match this resource group.
|
||||
@export var paths:Array[String] = []
|
||||
|
||||
|
||||
## Loads all resources in this resource group and returns them.
|
||||
func load_all() -> Array[Resource]:
|
||||
var result:Array[Resource] = []
|
||||
load_all_into(result)
|
||||
return result
|
||||
|
||||
## Loads all resources and stores them into the given array. Allows
|
||||
## to load resources into typed arrays for better type safety. If
|
||||
## the item is not of the required type, an error will be printed and
|
||||
## the item is skipped.
|
||||
func load_all_into(destination:Array):
|
||||
for path in paths:
|
||||
destination.append(load(path))
|
||||
|
||||
## Gets all paths of resources inside of this resource group that
|
||||
## match the given include and exclude criteria
|
||||
func get_matching_paths(includes:Array[String], excludes:Array[String]) -> Array[String]:
|
||||
var path_verifier = PathVerifier.new(base_folder, includes, excludes)
|
||||
return paths.filter(func(it): return path_verifier.matches(it))
|
||||
|
||||
## Loads all resources in this resource group that match the given
|
||||
## include and exclude criteria
|
||||
func load_matching(includes:Array[String], excludes:Array[String]) -> Array[Resource]:
|
||||
var result:Array[Resource] = []
|
||||
load_matching_into(result, includes, excludes)
|
||||
return result
|
||||
|
||||
|
||||
## Loads all resources in this resource group that match the given
|
||||
## include and exclude criteria and stores them into the given array.
|
||||
## Allows to load resources into typed arrays for better type safety. If
|
||||
## the item is not of the required type, an error will be printed and
|
||||
## the item is skipped.
|
||||
func load_matching_into(destination:Array, includes:Array[String], excludes:Array[String]):
|
||||
var matching_paths = get_matching_paths(includes, excludes)
|
||||
for path in matching_paths:
|
||||
destination.append(load(path))
|
||||
|
||||
|
||||
## Loads all resources in this resource group in background. Returns
|
||||
## a ResourceGroupBackgroundLoader object which can be used to check the
|
||||
## status and collect the results. Will call the on_resource_loaded callable for each
|
||||
## loaded resource.
|
||||
func load_all_in_background(on_resource_loaded:Callable) -> ResourceGroupBackgroundLoader:
|
||||
return ResourceGroupBackgroundLoader.new(paths, on_resource_loaded)
|
||||
|
||||
## Loads all resources in this resource group that match the given
|
||||
## include and exclude criteria in background. ResourceGroupBackgroundLoader object which can be used to check the
|
||||
## status and collect the results. Will call the on_resource_loaded callable for each
|
||||
## loaded resource.
|
||||
func load_matching_in_background(includes:Array[String], excludes:Array[String], on_resource_loaded:Callable) \
|
||||
-> ResourceGroupBackgroundLoader:
|
||||
return ResourceGroupBackgroundLoader.new(get_matching_paths(includes, excludes), on_resource_loaded)
|
||||
|
||||
#### CSHARP Specifics ####
|
||||
|
||||
# Workaround for C# interop not being able to properly convert arrays into Godot land.
|
||||
func __csharp_get_matching_paths(includes:Array, excludes:Array) -> Array[String]:
|
||||
return get_matching_paths(__to_string_array(includes), __to_string_array(excludes))
|
||||
|
||||
|
||||
# Workaround for C# interop not being able to properly convert arrays into Godot land.
|
||||
func __csharp_load_matching(includes:Array, excludes:Array) -> Array[Resource]:
|
||||
return load_matching(__to_string_array(includes), __to_string_array(excludes))
|
||||
|
||||
|
||||
# Workaround for C# interop not being able to properly convert arrays into Godot land.
|
||||
func __csharp_load_matching_in_background(includes:Array, excludes:Array, on_resource_loaded:Callable) -> ResourceGroupBackgroundLoader:
|
||||
return load_matching_in_background(__to_string_array(includes), __to_string_array(excludes), on_resource_loaded)
|
||||
|
||||
|
||||
# Converts an untyped array to a string array.
|
||||
func __to_string_array(array:Array) -> Array[String]:
|
||||
var result:Array[String] = []
|
||||
for item in array:
|
||||
result.append(item)
|
||||
|
||||
return result
|
||||
32
addons/godot_resource_groups/resource_group.svg
Normal file
32
addons/godot_resource_groups/resource_group.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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 width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g id="Cube" transform="matrix(1,0,0,1,-25.7158,-7.96899)">
|
||||
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
</g>
|
||||
<g id="Cube1" serif:id="Cube" transform="matrix(1,0,0,1,16.124,-7.96899)">
|
||||
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
</g>
|
||||
<g id="Cube2" serif:id="Cube" transform="matrix(1,0,0,1,-4.32539,4.5168)">
|
||||
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
</g>
|
||||
<g id="Cube3" serif:id="Cube" transform="matrix(1,0,0,1,-25.7158,16.7339)">
|
||||
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
</g>
|
||||
<g id="Cube4" serif:id="Cube" transform="matrix(1,0,0,1,18.0258,16.7339)">
|
||||
<path d="M68.796,39.855L86.269,49.736L68.796,59.618L51.323,49.736L68.796,39.855" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M51.323,49.736L51.323,69.499L68.796,79.38L68.796,59.618L51.323,49.736" style="fill:rgb(149,136,223);stroke:black;stroke-width:0.15px;"/>
|
||||
<path d="M86.269,49.736L68.796,59.618L68.796,79.38L86.269,69.499L86.269,49.736Z" style="fill:rgb(193,185,236);stroke:black;stroke-width:0.15px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1.05859,-2.81137,-0.554868)">
|
||||
<path d="M118.078,27.093C118.078,19.076 111.188,12.568 102.703,12.568L30.92,12.568C22.435,12.568 15.545,19.076 15.545,27.093L15.545,94.871C15.545,102.887 22.435,109.395 30.92,109.395L102.703,109.395C111.188,109.395 118.078,102.887 118.078,94.871L118.078,27.093Z" style="fill:none;stroke:rgb(193,185,236);stroke-width:2.04px;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
37
addons/godot_resource_groups/resource_group.svg.import
Normal file
37
addons/godot_resource_groups/resource_group.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bkc024iv5jdeu"
|
||||
path="res://.godot/imported/resource_group.svg-4ab15486a9423c0a5fbcfc8f79bd7fc4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot_resource_groups/resource_group.svg"
|
||||
dest_files=["res://.godot/imported/resource_group.svg-4ab15486a9423c0a5fbcfc8f79bd7fc4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
126
addons/godot_resource_groups/resource_group_background_loader.gd
Normal file
126
addons/godot_resource_groups/resource_group_background_loader.gd
Normal file
@@ -0,0 +1,126 @@
|
||||
class_name ResourceGroupBackgroundLoader
|
||||
|
||||
var _open:Array[String] = []
|
||||
var _callback:Callable
|
||||
var _cancelled:bool = false
|
||||
var _total:int = 0
|
||||
var _finished:int = 0
|
||||
|
||||
|
||||
## A loaded resource. This will be given as argument
|
||||
## to the callback function
|
||||
class ResourceLoadingInfo:
|
||||
## Whether loading of this resource was successful
|
||||
var success:bool
|
||||
## The path from which the resource was loaded
|
||||
var path:String
|
||||
## The loaded resource. Is null when success is false
|
||||
var resource:Resource
|
||||
## The overall progress of the background loading, can
|
||||
## be used to drive a progress indicator
|
||||
var progress:float
|
||||
## Whether this has been the last resource
|
||||
## of the
|
||||
var last:bool
|
||||
|
||||
## Ctor. Takes an array of paths to load.
|
||||
func _init(paths:Array[String], callback:Callable):
|
||||
_callback = callback
|
||||
|
||||
# copy all into the open array and reverse the order
|
||||
# so we can use the array as a stack
|
||||
_open.append_array(paths)
|
||||
_open.reverse()
|
||||
|
||||
# make note of the total amount of things
|
||||
_total = _open.size()
|
||||
|
||||
# start loading
|
||||
_call_deferred(_next)
|
||||
|
||||
|
||||
## Cancels the currently running background loading process
|
||||
## as soon as possible. If the process is already finished,
|
||||
## nothing will happen. This function will immediately return.
|
||||
func cancel():
|
||||
_cancelled = true
|
||||
|
||||
|
||||
## Checks if the background loading process is done.
|
||||
func is_done() -> bool:
|
||||
return _open.is_empty() or _cancelled
|
||||
|
||||
|
||||
## Looks like call_deferred does not work properly when not in a node
|
||||
## context (it doesn't seem to defer there...), so we roll our own here.
|
||||
func _call_deferred(callable:Callable, args:Array = []):
|
||||
await Engine.get_main_loop().process_frame
|
||||
callable.callv(args)
|
||||
|
||||
## Starts the loading process for the next file in background.
|
||||
func _next():
|
||||
# if we're done or the process was cancelled, stop it here.
|
||||
if _open.is_empty() or _cancelled:
|
||||
return
|
||||
|
||||
# fetch the next path and ask the resource loader to load it.
|
||||
var path = _open.pop_back()
|
||||
var result = ResourceLoader.load_threaded_request(path)
|
||||
if result != OK:
|
||||
push_warning("Unable to load path ", path , " return code ", result)
|
||||
|
||||
|
||||
_call_deferred(_fetch, [path])
|
||||
|
||||
## Tries to fetch a file currently being loaded in background.
|
||||
func _fetch(path:String):
|
||||
var status = ResourceLoader.load_threaded_get_status(path)
|
||||
match (status):
|
||||
ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
|
||||
push_warning("Loading resource at ", path , " failed. Invalid resource. Ignoring and moving on.")
|
||||
_failed(path)
|
||||
|
||||
ResourceLoader.THREAD_LOAD_FAILED:
|
||||
push_warning("Loading resource at ", path , " failed. Ignoring and moving on.")
|
||||
_failed(path)
|
||||
|
||||
# if its ready, ship it to the callback and move on
|
||||
ResourceLoader.THREAD_LOAD_LOADED:
|
||||
var resource = ResourceLoader.load_threaded_get(path)
|
||||
_succeeded(path, resource)
|
||||
|
||||
# we're still loading, so try again next frame
|
||||
ResourceLoader.THREAD_LOAD_IN_PROGRESS:
|
||||
_call_deferred(_fetch, [path])
|
||||
|
||||
|
||||
# Called when a path loading ultimately failed. Informs the callback
|
||||
# and moves on.
|
||||
func _failed(path:String):
|
||||
_finished += 1
|
||||
_call_callback(false, path, null)
|
||||
_call_deferred(_next)
|
||||
|
||||
## Called when a path loading ultimately succeeded. Informs the callback
|
||||
## and moves on.
|
||||
func _succeeded(path:String, resource:Resource):
|
||||
_finished += 1
|
||||
_call_callback(true, path, resource)
|
||||
_call_deferred(_next)
|
||||
|
||||
## Called when an operation is finished. Will invoke the callback.
|
||||
func _call_callback(success:bool, path:String, resource:Resource):
|
||||
# don't call the callback anymore if the process was cancelled
|
||||
if _cancelled:
|
||||
return
|
||||
|
||||
var progress:float = 1.0 if _total == 0 else float(_finished) / float(_total)
|
||||
|
||||
var result = ResourceLoadingInfo.new()
|
||||
result.success = success
|
||||
result.path = path
|
||||
result.resource = resource
|
||||
result.progress = progress
|
||||
result.last = _open.is_empty()
|
||||
|
||||
_callback.call(result)
|
||||
33
addons/godot_resource_groups/resource_group_scanner.gd
Normal file
33
addons/godot_resource_groups/resource_group_scanner.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
@tool
|
||||
|
||||
var _file_system:EditorFileSystem
|
||||
|
||||
func _init(file_system:EditorFileSystem) -> void:
|
||||
_file_system = file_system
|
||||
|
||||
## The resource group scanner finds all resource groups currently
|
||||
## in the project.
|
||||
|
||||
## Scans the whole project for resources that match the
|
||||
## group definition.
|
||||
func scan() -> Array[Resource]:
|
||||
var result:Array[Resource] = []
|
||||
_scan(_file_system.get_filesystem(), result)
|
||||
return result
|
||||
|
||||
|
||||
func _scan(folder:EditorFileSystemDirectory, results:Array[Resource]):
|
||||
# get all files in the folder
|
||||
for i in folder.get_file_count():
|
||||
if folder.get_file_type(i) == "Resource":
|
||||
var path = folder.get_file_path(i)
|
||||
if path.ends_with(".tres"):
|
||||
var resource = ResourceLoader.load(path)
|
||||
if resource is ResourceGroup:
|
||||
results.append(resource)
|
||||
|
||||
# for each file first check if it matches the group definition, before trying to load it
|
||||
for j in folder.get_subdir_count():
|
||||
_scan(folder.get_subdir(j), results)
|
||||
|
||||
|
||||
61
addons/godot_resource_groups/resource_scanner.gd
Normal file
61
addons/godot_resource_groups/resource_scanner.gd
Normal file
@@ -0,0 +1,61 @@
|
||||
@tool
|
||||
|
||||
|
||||
const PathVerifier = preload("path_verifier.gd")
|
||||
|
||||
## The resource scanner scans the project for resources matching the given
|
||||
## definition and returns a list of matching resources.
|
||||
|
||||
var _group:ResourceGroup
|
||||
var _verifier:PathVerifier
|
||||
var _file_system:EditorFileSystem
|
||||
|
||||
## Ctor.
|
||||
func _init(group:ResourceGroup, file_system:EditorFileSystem):
|
||||
_group = group
|
||||
_verifier = PathVerifier.new(_group.base_folder, _group.includes, _group.excludes)
|
||||
_file_system = file_system
|
||||
|
||||
## Scans the whole project for resources that match the
|
||||
## group definition.
|
||||
func scan() -> Array[String]:
|
||||
var result:Array[String] = []
|
||||
var folder = _group.base_folder
|
||||
|
||||
if folder == "":
|
||||
push_warning("In resource group '" + _group.resource_path + "': Base folder is not set. Resource group will be empty.")
|
||||
return result
|
||||
|
||||
var root = _file_system.get_filesystem_path(folder)
|
||||
|
||||
if root == null:
|
||||
push_warning("In resource group '" + _group.resource_path + "': Base folder '" + folder + "' does not exist. Resource group will be empty.")
|
||||
return result
|
||||
|
||||
_scan(root, result)
|
||||
return result
|
||||
|
||||
|
||||
func _scan(folder:EditorFileSystemDirectory, results:Array[String]):
|
||||
# get all files in the folder
|
||||
# for each file first check if it matches the group definition, before trying to load it
|
||||
for i in folder.get_file_count():
|
||||
var full_name = folder.get_file_path(i)
|
||||
if _matches_group_definition(full_name):
|
||||
if ResourceLoader.exists(full_name):
|
||||
results.append(full_name)
|
||||
else:
|
||||
push_warning("In resource group '" + _group.resource_path + "': File '" + full_name + "' exists, but is not a supported Godot resource. It will be ignored.")
|
||||
|
||||
# recurse into subfolders
|
||||
for j in folder.get_subdir_count():
|
||||
_scan(folder.get_subdir(j), results)
|
||||
|
||||
|
||||
func _matches_group_definition(file:String) -> bool:
|
||||
# Skip import files
|
||||
if file.ends_with(".import"):
|
||||
return false
|
||||
|
||||
return _verifier.matches(file)
|
||||
|
||||
Reference in New Issue
Block a user