Open squiddingme opened 6 months ago
Some thoughts about this proposal:
BTNewScope
and BTSubtree
are creating new scopes, and each instance of LimboState
creates a scope which has a non-empty blackboard_data
property set.target
is a common pattern)? What about the non-Node variety, like extended Object
classes and Ref<Resource>
?ResourceSaver
for behavior tree serialization:
PROPERTY_USAGE_STORAGE
flag set. This way, the user has full control over which property should be serialized.
serialize()
, deserialize()
, because it's a lot of unnecessary work.Example of a blackboard chain:
LimboHSM -- new blackboard scope
-- LimboState -- inherits scope (but can also define new blackboard)
-- BTState -- new blackboard scope
---- Sequence -- inherits scope
------ Action -- inherits scope
------ BTSubtree -- new blackboard scope
-------- SubtreeRootTask -- inherits scope
In the example, SubtreeRootTask
has the following scope chain: BTSubtree's Blackboard -> BTState's Blackboard -> LimboHSM's Blackboard
.
In my own behavior tree implementation (which isn't very good, and LimboAI is looking much better), I've just been serialising node references to node paths and back. It... works for what I need, but may not necessarily make sense for a generic serialiser.
Godot's built-in serialisers (JSON.parse_string and var_to_bytes) will just use a basic string representation for types it doesn't support, which doesn't correctly serialise and deserialise back (for example, bizarrely, the JSON serialiser does not support Vector types, but you can use var_to_string and string_to_var to get around this). I wouldn't say full serialisation is necessary -- though warnings when a type can't be serialised would help developers design the way they use LimboAI around their serialisation requirements (and would already be a step up from the zero feedback Godot's serialisers give you).
This issue was mentioned in a discussion in the discord channel.
One key outcome of the discussion was about serializing object references. @limbonaut suggested using a strategy pattern with a Serializer
class, taking an ObjectSerializer
instance, which by default would ignore object references, and not deserialize any objects.
API draft:
// de/serialize tasks and their attributes - can be subclassed to add support for user-defined types
class ObjectSerializer : public RefCounted {
// serialize fields
Variant serialize_object_reference(Ref<Object> object, ObjectID obj_id);
Ref<Object> deserialize_object_reference(data: Variant, ObjectID obj_id);
// serialize tasks
Variant serialize_task(Ref<BTTask> task);
Ref<BTTask> deserialize_task(Variant task_data);
};
// walk the tree and aggregates the ObjectSerializer's outputs
class Serializer : public RefCounted {
// serialize (json) a task and its children
String serialize(Ref<BTTask> task, Ref<ObjectSerializer> object_serializer)
Ref<BTtask> deserialize(String tree_payload)
};
This should include all blackboard variables and the current running indexes of any composite nodes, as well as the current running state of any HSMs. This would allow developers to dump the state of the entire behavior tree to a save file, which is useful for allowing players to save and load at any time (including mid-behavior tree execution, so an AI agent can remember what it was doing).