Save / load advanced to usability

This commit is contained in:
effie 2025-07-12 19:32:01 +10:00
parent 1bb038d8b7
commit 949bc9f4ff
24 changed files with 807 additions and 135 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Charles Crete
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

@ -0,0 +1,63 @@
## Serializer to be used with Godot's built-in binary serialization ([method @GlobalScope.var_to_bytes] and [method @GlobalScope.bytes_to_var]).
## This serializes objects but leaves built-in Godot types as-is.
class_name BinarySerializer
## Serialize [param data] to value which can be passed to [method @GlobalScope.var_to_bytes].
static func serialize_var(value: Variant) -> Variant:
match typeof(value):
TYPE_OBJECT:
var name: StringName = value.get_script().get_global_name()
var object_entry := ObjectSerializer._get_entry(name, value.get_script())
if !object_entry:
assert(
false,
(
"Could not find type (%s) in registry\n%s"
% [name if name else "no name", value.get_script().source_code]
)
)
return object_entry.serialize(value, serialize_var)
TYPE_ARRAY:
return value.map(serialize_var)
TYPE_DICTIONARY:
var result := {}
for i: Variant in value:
result[i] = serialize_var(value[i])
return result
return value
## Serialize [param data] into bytes with [method BinaryObjectSerializer.serialize_var] and [method @GlobalScope.var_to_bytes].
static func serialize_bytes(value: Variant) -> PackedByteArray:
return var_to_bytes(serialize_var(value))
## Deserialize [param data] from [method @GlobalScope.bytes_to_var] to value.
static func deserialize_var(value: Variant) -> Variant:
match typeof(value):
TYPE_DICTIONARY:
if value.has(ObjectSerializer.type_field):
var type: String = value.get(ObjectSerializer.type_field)
if type.begins_with(ObjectSerializer.object_type_prefix):
var entry := ObjectSerializer._get_entry(type)
if !entry:
assert(false, "Could not find type (%s) in registry" % type)
return entry.deserialize(value, deserialize_var)
var result := {}
for i: Variant in value:
result[i] = deserialize_var(value[i])
return result
TYPE_ARRAY:
return value.map(deserialize_var)
return value
## Deserialize bytes [param data] to value with [method @GlobalScope.bytes_to_var] and [method BinaryObjectSerializer.deserialize_var].
static func deserialize_bytes(value: PackedByteArray) -> Variant:
return deserialize_var(bytes_to_var(value))

View File

@ -0,0 +1 @@
uid://82dofbvl1g6n

View File

@ -0,0 +1,101 @@
## Serializer to be used with JSON serialization ([method JSON.stringify] and [method JSON.parse_string]).
## This serializes objects and built-in Godot types.
class_name DictionarySerializer
# Types that can natively be represented in JSON
const _JSON_SERIALIZABLE_TYPES = [
TYPE_NIL, TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING, TYPE_STRING_NAME
]
## Controls if [PackedByteArray] should be serialized as base64 (instead of array of bytes as uint8).
## It's highly recommended to leave this enabled as it will result to smaller serialized payloads and should be faster.
## This can be changed, but must be configured before any serialization or deserialization.
static var bytes_as_base64 := true
## The type of the object for [PackedByteArray] when [member bytes_as_base64] is enabled.
## This should be set to something unlikely to clash with built-in type names or [member ObjectSerializer.object_type_prefix].
## This can be changed, but must be configured before any serialization or deserialization.
static var bytes_to_base64_type := "PackedByteArray_Base64"
## Serialize [param data] into value which can be passed to [method JSON.stringify].
static func serialize_var(value: Variant) -> Variant:
match typeof(value):
TYPE_OBJECT:
var name: StringName = value.get_script().get_global_name()
var object_entry := ObjectSerializer._get_entry(name, value.get_script())
if !object_entry:
assert(
false,
(
"Could not find type (%s) in registry\n%s"
% [name if name else "no name", value.get_script().source_code]
)
)
return object_entry.serialize(value, serialize_var)
TYPE_ARRAY:
return value.map(serialize_var)
TYPE_DICTIONARY:
var result := {}
for i: Variant in value:
result[i] = serialize_var(value[i])
return result
TYPE_PACKED_BYTE_ARRAY:
if bytes_as_base64:
return {
ObjectSerializer.type_field: bytes_to_base64_type,
ObjectSerializer.args_field:
Marshalls.raw_to_base64(value) if !value.is_empty() else ""
}
if _JSON_SERIALIZABLE_TYPES.has(typeof(value)):
return value
return {
ObjectSerializer.type_field: type_string(typeof(value)),
ObjectSerializer.args_field: JSON.from_native(value)["args"]
}
## Serialize [param data] into JSON string with [method DictionaryObjectSerializer.serialize_var] and [method JSON.stringify]. Supports same arguments as [method JSON.stringify]
static func serialize_json(
value: Variant, indent := "", sort_keys := true, full_precision := false
) -> String:
return JSON.stringify(serialize_var(value), indent, sort_keys, full_precision)
## Deserialize [data] from [JSON.parse_string] into value.
static func deserialize_var(value: Variant) -> Variant:
match typeof(value):
TYPE_DICTIONARY:
if value.has(ObjectSerializer.type_field):
var type: String = value.get(ObjectSerializer.type_field)
if bytes_as_base64 and type == bytes_to_base64_type:
return Marshalls.base64_to_raw(value[ObjectSerializer.args_field])
if type.begins_with(ObjectSerializer.object_type_prefix):
var entry := ObjectSerializer._get_entry(type)
if !entry:
assert(false, "Could not find type (%s) in registry" % type)
return entry.deserialize(value, deserialize_var, true)
return JSON.to_native({"type": type, "args": value[ObjectSerializer.args_field]})
var result := {}
for i: Variant in value:
result[i] = deserialize_var(value[i])
return result
TYPE_ARRAY:
return value.map(deserialize_var)
return value
## Deserialize JSON string [param data] to value with [method JSON.parse_string] and [method DictionaryObjectSerializer.deserialize_var].
static func deserialize_json(value: String) -> Variant:
return deserialize_var(JSON.parse_string(value))

View File

@ -0,0 +1 @@
uid://cejorwceoqbqe

View File

@ -0,0 +1,131 @@
extends SceneTree
# Example data class. Can extend any type, include Resource
class Data:
# Supports all primitive types (String, int, float, bool, null), including @export variables
@export var string: String
# Supports all extended built-in types (Vector2/3/4/i, Rect2/i, Transform2D/3D, Color, Packed*Array, etc)
var vector: Vector3
# Supports enum
var enum_state: State
# Supports arrays, including Array[Variant]
var array: Array[int]
# Supports dictionaries, including Dictionary[Variant, Variant]
var dictionary: Dictionary[String, Vector2]
# Supports efficient byte array serialization to base64
var packed_byte_array: PackedByteArray
# Supports nested data, either as a field or in array/dictionary
var nested: DataResource
class DataResource:
extends Resource
var name: String
enum State { OPENED, CLOSED }
# _static_init is used to register scripts without having to instanciate the script.
# It's recommended to either place all registrations in a single script, or have each script register itself.
static func _static_init() -> void:
# Required: Register possible object scripts
ObjectSerializer.register_script("Data", Data)
ObjectSerializer.register_script("DataResource", DataResource)
# Setup testing data
var data := Data.new()
func _init() -> void:
data.string = "Lorem ipsum"
data.vector = Vector3(1, 2, 3)
data.enum_state = State.CLOSED
data.array = [1, 2]
data.dictionary = {"position": Vector2(1, 2)}
data.packed_byte_array = PackedByteArray([1, 2, 3, 4, 5, 6, 7, 8])
var data_resource := DataResource.new()
data_resource.name = "dolor sit amet"
data.nested = data_resource
json_serialization()
binary_serialization()
func json_serialization() -> void:
# Serialize to JSON
# Alternative: DictionarySerializer.serialize_json(data)
var serialized: Variant = DictionarySerializer.serialize_var(data)
var json := JSON.stringify(serialized, "\t")
print(json)
""" Output:
{
"._type": "Object_Data",
"string": "Lorem ipsum",
"vector": {
"._type": "Vector3",
"._": [
1.0,
2.0,
3.0
]
},
"enum_state": 1,
"array": [
1,
2
],
"dictionary": {
"position": {
"._type": "Vector2",
"._": [
1.0,
2.0
]
}
},
"packed_byte_array": {
"._type": "PackedByteArray_Base64",
"._": "AQIDBAUGBwg="
},
"nested": {
"._type": "Object_DataResource",
"name": "dolor sit amet"
}
}
"""
# Verify after JSON deserialization
# Alternative: DictionarySerializer.deserialize_json(json)
var parsed_json = JSON.parse_string(json)
var deserialized: Data = DictionarySerializer.deserialize_var(parsed_json)
_assert_data(deserialized)
func binary_serialization() -> void:
# Serialize to bytes
# Alternative: BinarySerializer.serialize_bytes(data)
var serialized: Variant = BinarySerializer.serialize_var(data)
var bytes := var_to_bytes(serialized)
print(bytes)
# Output: List of bytes
# Verify after bytes deserialization.
# Alternative: BinarySerializer.deserialize_bytes(bytes)
var parsed_bytes = bytes_to_var(bytes)
var deserialized: Data = BinarySerializer.deserialize_var(parsed_bytes)
_assert_data(deserialized)
func _assert_data(deserialized: Data) -> void:
assert(data.string == deserialized.string, "string is different")
assert(data.vector == deserialized.vector, "vector is different")
assert(data.enum_state == deserialized.enum_state, "enum_state is different")
assert(data.array == deserialized.array, "array is different")
assert(data.dictionary == deserialized.dictionary, "dictionary is different")
assert(
data.packed_byte_array == deserialized.packed_byte_array, "packed_byte_array is different"
)
assert(data.nested.name == deserialized.nested.name, "nested.name is different")

View File

@ -0,0 +1 @@
uid://cnmoj6e2jq737

View File

@ -0,0 +1,185 @@
## Main godot-object-serializer class. Stores the script registry.
## [url]https://github.com/Cretezy/godot-object-serializer[/url]
class_name ObjectSerializer
## The field containing the type in serialized object values. Not recommended to change.
##
## This should be set to something unlikely to clash with keys in objects/dictionaries.
##
## This can be changed, but must be configured before any serialization or deserialization.
static var type_field := "._type"
## The field containing the constructor arguments in serialized object values. Not recommended to change.
##
## This should be set to something unlikely to clash with keys in objects.
##
## This can be changed, but must be configured before any serialization or deserialization.
static var args_field := "._"
## The prefix for object types stored in [member type_field]. Not recommended to change.
##
## This should be set to something unlikely to clash with built-in type names.
##
## This can be changed, but must be configured before any serialization or deserialization.
static var object_type_prefix := "Object_"
## By default, variables with [@GlobalScope.PROPERTY_USAGE_SCRIPT_VARIABLE] are serialized (all variables have this by default).
## When [member require_export_storage] is true, variables will also require [@GlobalScope.PROPERTY_USAGE_STORAGE] to be serialized.
## This can be set on variables using [annotation @GDScript.@export_storage]. Example: [code]@export_storage var name: String[/code]
static var require_export_storage := false
## Registry of object types
static var _script_registry: Dictionary[String, _ScriptRegistryEntry]
## Registers a script (an object type) to be serialized/deserialized. All custom types (including nested types) must be registered [b]before[/b] using this library.
## [param name] can be empty if script uses [code]class_name[/code] (e.g [code]ObjectSerializer.register_script("", Data)[/code]), but it's generally better to set the name.
static func register_script(name: StringName, script: Script) -> void:
var script_name := _get_script_name(script, name)
assert(script_name, "Script must have name\n" + script.source_code)
var entry := _ScriptRegistryEntry.new()
entry.script_type = script
entry.type = object_type_prefix + script_name
_script_registry[entry.type] = entry
## Registers multiple scripts (object types) to be serialized/deserialized from a dictionary.
## See [method ObjectSerializer.register_script]
static func register_scripts(scripts: Dictionary[String, Script]) -> void:
for name in scripts:
register_script(name, scripts[name])
static func _get_script_name(script: Script, name: StringName = "") -> StringName:
if name:
return name
if script.resource_name:
return script.resource_name
if script.get_global_name():
return script.get_global_name()
return ""
static func _get_entry(name: StringName = "", script: Script = null) -> _ScriptRegistryEntry:
if name:
var entry: _ScriptRegistryEntry = _script_registry.get(name)
if entry:
return entry
if script:
for i: String in _script_registry:
var entry: _ScriptRegistryEntry = _script_registry.get(i)
if entry:
if script == entry.script_type:
return entry
return null
class _ScriptRegistryEntry:
var type: String
var script_type: Script
func serialize(value: Variant, next: Callable) -> Variant:
if value.has_method("_serialize"):
var result: Dictionary = value._serialize(next)
result[ObjectSerializer.type_field] = type
return result
var result := {ObjectSerializer.type_field: type}
var excluded_properties: Array[String] = []
if value.has_method("_get_excluded_properties"):
excluded_properties = value._get_excluded_properties()
var partial: Dictionary = {}
if value.has_method("_serialize_partial"):
partial = value._serialize_partial(next)
for property: Dictionary in value.get_property_list():
if (
property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE
and (
!ObjectSerializer.require_export_storage
or property.usage & PROPERTY_USAGE_STORAGE
)
and !excluded_properties.has(property.name)
and !partial.has(property.name)
):
result[property.name] = next.call(value.get(property.name))
for key in partial:
result[key] = partial[key]
if value.has_method("_get_constructor_args"):
var args: Array = value._get_constructor_args()
result[ObjectSerializer.args_field] = args
return result
## When [param json_keys] is enabled, attempt to convert int/float/bool string keys into values
func deserialize(value: Variant, next: Callable, json_keys := false) -> Variant:
if script_type.has_method("_deserialize"):
return script_type._deserialize(value, next)
var instance: Variant
if value.has(ObjectSerializer.args_field):
instance = script_type.new.callv(value[ObjectSerializer.args_field])
else:
instance = script_type.new()
var excluded_properties: Array[String] = []
if instance.has_method("_get_excluded_properties"):
excluded_properties = instance._get_excluded_properties()
var partial: Dictionary = {}
if instance.has_method("_deserialize_partial"):
partial = instance._deserialize_partial(value, next)
for key: String in value:
if (
key == ObjectSerializer.type_field
or key == ObjectSerializer.args_field
or excluded_properties.has(key)
or partial.has(key)
):
continue
var key_value: Variant = next.call(value[key])
match typeof(key_value):
TYPE_DICTIONARY:
if json_keys and instance[key].is_typed_key():
match instance[key].get_typed_key_builtin():
TYPE_STRING:
instance[key].assign(key_value)
TYPE_BOOL:
var dict: Dictionary[bool, Variant] = {}
for i in key_value:
dict[i == "true"] = key_value[i]
instance[key].assign(dict)
TYPE_INT:
var dict: Dictionary[int, Variant] = {}
for i in key_value:
dict[int(i)] = key_value[i]
instance[key].assign(dict)
TYPE_FLOAT:
var dict: Dictionary[float, Variant] = {}
for i in key_value:
dict[float(i)] = key_value[i]
instance[key].assign(dict)
_:
assert(
false,
"Trying to deserialize from JSON to a dictionary with non-primitive (String/int/float/bool) keys"
)
else:
instance[key].assign(key_value)
TYPE_ARRAY:
instance[key].assign(key_value)
_:
instance[key] = key_value
for key in partial:
instance[key] = partial[key]
return instance

View File

@ -0,0 +1 @@
uid://dn0k1lmeff72p

View File

@ -0,0 +1,7 @@
[plugin]
name="Godot Object Serializer"
description="Safely serialize/deserialize objects and built-in types in Godot"
author="Cretezy"
version="0.3.0"
script=""

View File

@ -1,7 +1 @@
extends Node extends Node
func save_game():
var save_data = {}

View File

@ -41,6 +41,11 @@ offset_right = 1142.0
offset_bottom = 604.0 offset_bottom = 604.0
text = "Load Map" text = "Load Map"
[node name="Button" type="Button" parent="InterfaceLayer"]
offset_right = 8.0
offset_bottom = 8.0
text = "Init Grid"
[node name="GameCam" type="Camera3D" parent="."] [node name="GameCam" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.182236, 0.983255, 0, -0.983255, 0.182236, 24.5127, 23.2849, 20.9667) transform = Transform3D(1, 0, 0, 0, 0.182236, 0.983255, 0, -0.983255, 0.182236, 24.5127, 23.2849, 20.9667)
size = 31.34 size = 31.34
@ -55,4 +60,5 @@ script = ExtResource("5_7jktm")
[connection signal="pressed" from="InterfaceLayer/BuildButton" to="PlaceManager" method="_on_confirm_button_pressed"] [connection signal="pressed" from="InterfaceLayer/BuildButton" to="PlaceManager" method="_on_confirm_button_pressed"]
[connection signal="pressed" from="InterfaceLayer/SaveButton" to="SaveLoad" method="_on_save_button_pressed"] [connection signal="pressed" from="InterfaceLayer/SaveButton" to="SaveLoad" method="_on_save_button_pressed"]
[connection signal="pressed" from="InterfaceLayer/LoadButton" to="SaveLoad" method="_on_load_button_pressed"] [connection signal="pressed" from="InterfaceLayer/LoadButton" to="SaveLoad" method="_on_load_button_pressed"]
[connection signal="pressed" from="InterfaceLayer/Button" to="PlaceManager" method="_on_init_grid_button_pressed"]
[connection signal="room_built" from="PlaceManager" to="InterfaceLayer/BuildToggle" method="_on_room_built"] [connection signal="room_built" from="PlaceManager" to="InterfaceLayer/BuildToggle" method="_on_room_built"]

View File

@ -1,5 +1,29 @@
extends Node extends Node
class SaveData:
var type: String
var position: Vector3
var parent: String
var scene_file_path: String
var id: int
#Connections
var place_id: int
var room_array: Array
#Tile data
var grid_pos: Vector3i
var face_dict: Array
var construction_mode: int
func _init():
ObjectSerializer.register_scripts({
"SaveData": SaveData,
"Tile": Tile,
"Room": Room,
"Wall": Wall,
})
func _on_save_button_pressed() -> void: func _on_save_button_pressed() -> void:
var save_nodes = get_tree().get_nodes_in_group("SaveObjects") var save_nodes = get_tree().get_nodes_in_group("SaveObjects")
var save_file = FileAccess.open("user://savegame.save", FileAccess.WRITE) var save_file = FileAccess.open("user://savegame.save", FileAccess.WRITE)
@ -10,8 +34,15 @@ func _on_save_button_pressed() -> void:
continue continue
var node_data = node.save() var node_data = node.save()
var save_data = SaveData.new()
for i in node_data:
save_data.set(i, node_data[i])
save_file.store_var(node_data) var serialized_save_data = BinarySerializer.serialize_var(save_data)
var byte_data = var_to_bytes(serialized_save_data)
save_file.store_var(byte_data)
pass # Replace with function body. pass # Replace with function body.
@ -21,18 +52,83 @@ func _on_load_button_pressed():
print("no save to load") print("no save to load")
return return
#var save_nodes = get_tree().get_nodes_in_group("SaveObjects") var save_nodes = get_tree().get_nodes_in_group("SaveObjects")
#for i in save_nodes: for i in save_nodes:
#print(i) if i:
#i.free() i.free()
get_node("/root/Game/PlaceManager/BasePlace").free() #if get_node("/root/Game/PlaceManager/BasePlace"):
#get_node("/root/Game/PlaceManager/BasePlace").free()
var save_file = FileAccess.open("user://savegame.save", FileAccess.READ) var save_file = FileAccess.open("user://savegame.save", FileAccess.READ)
var place_id_dict: Dictionary = {}
var room_id_dict: Dictionary = {}
var load_place_room_array: Dictionary = {}
var load_place_tile_dict: Dictionary = {}
var load_room_tile_array: Dictionary = {}
while save_file.get_position() < save_file.get_length(): while save_file.get_position() < save_file.get_length():
var next_load = save_file.get_var()
var next_load_bytes = save_file.get_var()
var next_load = bytes_to_var(next_load_bytes)
var load_object = load(next_load["scene_file_path"]).instantiate() var load_object = load(next_load["scene_file_path"]).instantiate()
for i in next_load.keys(): var id = next_load["id"]
load_object.set(i, next_load[i])
get_node(next_load["parent"]).add_child(load_object) match next_load["type"]:
"Place":
var new_place_id = load_object.get_instance_id()
place_id_dict[id] = new_place_id
load_place_room_array[load_object] = []
load_place_tile_dict[load_object] = {}
for j in next_load.keys():
load_object.set(j, next_load[j])
get_node(next_load["parent"]).add_child(load_object)
"Room":
room_id_dict[id] = load_object.get_instance_id()
load_room_tile_array[load_object] = []
for j in next_load.keys():
if j == "place_id":
var new_place_id = place_id_dict[next_load[j]]
var place = instance_from_id(new_place_id)
load_place_room_array[place].append(load_object)
load_object.set(j, new_place_id)
else:
load_object.set(j, next_load[j])
get_node(next_load["parent"]).add_child(load_object)
"Tile":
for j in next_load.keys():
if j == "place_id":
var new_place_id = place_id_dict[next_load[j]]
var place = instance_from_id(new_place_id)
load_place_tile_dict[place].merge({next_load["grid_pos"]: load_object})
load_object.set(j, new_place_id)
elif j == "room_array":
for k in next_load["room_array"]:
var new_room_id = room_id_dict[k]
var room = instance_from_id(new_room_id)
load_room_tile_array[room].append(load_object)
load_object[j].append(room)
else:
load_object.set(j, next_load[j])
get_node(next_load["parent"]).add_child(load_object)
_:
for j in next_load.keys():
load_object.set(j, next_load[j])
get_node(next_load["parent"]).add_child(load_object)
for i in load_place_room_array.keys():
i.place_room_array = load_place_room_array[i]
for i in load_place_tile_dict.keys():
i.place_tile_dict.merge(load_place_tile_dict[i])
for i in load_room_tile_array.keys():
i.room_tile_array = load_room_tile_array[i]

View File

@ -1,5 +1,7 @@
extends Node3D extends Node3D
class_name Place
var tile_grid_size: int = 8 var tile_grid_size: int = 8
var load_tile = preload("res://tiles/base_tile/base_tile.tscn") var load_tile = preload("res://tiles/base_tile/base_tile.tscn")
@ -15,7 +17,7 @@ var selection_drag_dict: Dictionary = {}
var selection_dict: Dictionary = {} var selection_dict: Dictionary = {}
#Contains all rooms in a workplace. #Contains all rooms in a workplace.
var room_dict: Dictionary = {} var place_room_array: Array = []
#Tracks the previous amount of tiles counted in a selection drac #Tracks the previous amount of tiles counted in a selection drac
var tile_count_x_hist: int = 0 var tile_count_x_hist: int = 0
@ -30,7 +32,19 @@ var current_room_walls: Array = []
var current_door: Object = null var current_door: Object = null
func _ready(): func _ready():
self.name = "BasePlace"
pass
func place_tile_check(new_dict):
place_tile_dict = new_dict
#if type_string(typeof(new_dict)) == "Array":
#for i in new_dict:
#
#place_tile_dict[]
func init_grid():
#TEMP: Sets up a simple 2D grid of blank tiles. #TEMP: Sets up a simple 2D grid of blank tiles.
for x in range(tile_grid_size): for x in range(tile_grid_size):
@ -42,40 +56,39 @@ func _ready():
tile.grid_pos = pos tile.grid_pos = pos
tile.name = str("Tile", tile.get_instance_id()) tile.name = str("Tile", tile.get_instance_id())
tile.place_id = self.get_instance_id()
tile.selection_mode = Tile.SEL_MODE.NONE tile.selection_mode = Tile.SEL_MODE.NONE
tile.construction_mode = Tile.CON_MODE.CLOSED tile.construction_mode = Tile.CON_MODE.CLOSED
place_tile_dict[pos] = tile place_tile_dict[pos] = tile
add_child(tile) $TileContainer.add_child(tile)
tile.connect("neighbor_request", give_neighbors)
var pos = Vector3i(1, 0, 1) #var pos = Vector3i(1, 0, 1)
#
var tile = place_tile_dict[pos] #var tile = place_tile_dict[pos]
#
var trickydict = {pos: tile} #var trickydict = {pos: tile}
#
tile.room = create_room(trickydict) #tile.room = create_room(trickydict)
#
tile.construction_mode = Tile.CON_MODE.BUILT #tile.construction_mode = Tile.CON_MODE.BUILT
#
#selection_drag_dict[pos] = tile #selection_drag_dict[pos] = tile
#end_select_drag() #end_select_drag()
#build_selection() #build_selection()
func save(): func save():
var save_data = { var save_data = {
"test": "test", # Basics
"name": name,
"type": "Place",
"id": self.get_instance_id(),
"scene_file_path": scene_file_path, "scene_file_path": scene_file_path,
"parent": get_parent().get_path(), "parent": get_parent().get_path(),
"place_tile_dict": place_tile_dict, # Connections
"room_dict": room_dict # Data
} }
return save_data return save_data
func draw_tile_click(click_pos): func draw_tile_click(click_pos):
@ -234,16 +247,17 @@ func create_room(selection):
room.position = (selection.keys().min()) room.position = (selection.keys().min())
room.room_tile_dict = selection.duplicate() room.room_tile_array = selection.values()
room.place_id = self.get_instance_id()
room.name = str("Room", room.get_instance_id()) room.name = str("Room", room.get_instance_id())
add_child(room) $RoomContainer.add_child(room)
for i in room.room_tile_dict: for i in room.room_tile_array:
place_tile_dict[i].room = room i.room_array.append(room)
current_room = room place_room_array.append(room)
return room return room
@ -280,44 +294,44 @@ func hover_door(mouse_pos):
current_door.position = closest current_door.position = closest
current_door.rotation_degrees = Vector3(0, 180 * fmod(closest.z, 1), 0) current_door.rotation_degrees = Vector3(0, 180 * fmod(closest.z, 1), 0)
func confirm_door(): #func confirm_door():
#Builds the door at the hovered location ##Builds the door at the hovered location
#
var tile_1: Object #var tile_1: Object
var tile_2: Object #var tile_2: Object
#
if fmod(current_door.position.x, 1) == 0: #if fmod(current_door.position.x, 1) == 0:
tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] #tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)]
tile_2 = place_tile_dict[Vector3i(current_door.position.x - 1, 0, current_door.position.z)] #tile_2 = place_tile_dict[Vector3i(current_door.position.x - 1, 0, current_door.position.z)]
else: #else:
tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)] #tile_1 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z)]
tile_2 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z - 1)] #tile_2 = place_tile_dict[Vector3i(current_door.position.x, 0, current_door.position.z - 1)]
#
var tile_1_door_face: Vector3i = tile_2.position - tile_1.position #var tile_1_door_face: Vector3i = tile_2.position - tile_1.position
#
var tile_1_door_face_direction: int = tile_1.direction_vector_array.find(tile_1_door_face) #var tile_1_door_face_direction: int = tile_1.direction_vector_array.find(tile_1_door_face)
#
tile_1.update_face(tile_1_door_face_direction, Tile.FACE_MODE.DOOR, current_door) #tile_1.update_face(tile_1_door_face_direction, Tile.FACE_MODE.DOOR, current_door)
#
var tile_1_room: Object = tile_1.room_id #var tile_1_room: Object = tile_1.room_id
#
tile_1_room.room_door_array.append(current_door) #tile_1_room.room_door_array.append(current_door)
#
var tile_2_door_face: Vector3i = tile_1.position - tile_2.position #var tile_2_door_face: Vector3i = tile_1.position - tile_2.position
#
var tile_2_door_face_direction: int = tile_2.direction_vector_array.find(tile_2_door_face) #var tile_2_door_face_direction: int = tile_2.direction_vector_array.find(tile_2_door_face)
#
tile_2.update_face(tile_2_door_face_direction, Tile.FACE_MODE.DOOR, current_door) #tile_2.update_face(tile_2_door_face_direction, Tile.FACE_MODE.DOOR, current_door)
#
var tile_2_room: Object = tile_2.room_id #var tile_2_room: Object = tile_2.room_id
#
tile_2_room.room_door_array.append(current_door) #tile_2_room.room_door_array.append(current_door)
#
current_door.door_room_array = [tile_1_room, tile_2_room] #current_door.door_room_array = [tile_1_room, tile_2_room]
#
current_room = null #current_room = null
current_room_walls = [] #current_room_walls = []
current_door = null #current_door = null
func give_neighbors(tile, grid_pos, directions): func give_neighbors(tile, grid_pos, directions):
var neighbor_dict = {} var neighbor_dict = {}
@ -325,3 +339,7 @@ func give_neighbors(tile, grid_pos, directions):
if place_tile_dict.has(directions[i] + grid_pos): if place_tile_dict.has(directions[i] + grid_pos):
neighbor_dict[directions[i]] = place_tile_dict[directions[i] + grid_pos] neighbor_dict[directions[i]] = place_tile_dict[directions[i] + grid_pos]
tile.neighbor_dict = neighbor_dict tile.neighbor_dict = neighbor_dict
func _on_tile_container_child_entered_tree(node: Node) -> void:
node.connect("neighbor_request", give_neighbors)

View File

@ -7,3 +7,11 @@ script = ExtResource("1_uq3a8")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.345446, 0.938439, 0, -0.938439, 0.345446, 41.393, 18.7409, 34.4224) transform = Transform3D(1, 0, 0, 0, 0.345446, 0.938439, 0, -0.938439, 0.345446, 41.393, 18.7409, 34.4224)
[node name="RoomContainer" type="Node" parent="."]
[node name="TileContainer" type="Node" parent="."]
[node name="Objects" type="Node" parent="."]
[connection signal="child_entered_tree" from="TileContainer" to="." method="_on_tile_container_child_entered_tree"]

View File

@ -1,11 +1,14 @@
extends Node extends Node
class_name Room
var room_tile_dict = {} var room_tile_array = []: set = setup_path_grid
var path_grid = AStar3D.new() var path_grid = AStar3D.new()
var room_door_array = [] var room_door_array = []
var place_id: int = 0
#TODO: replace with global dict? #TODO: replace with global dict?
var direction_array = [ var direction_array = [
Vector3i(0, 0, -1), Vector3i(0, 0, -1),
@ -14,32 +17,36 @@ var direction_array = [
Vector3i(-1, 0, 0), Vector3i(-1, 0, 0),
] ]
func _ready():
self.name = str("Room", self.get_instance_id())
init_path_grid()
pass
func save(): func save():
var save_data = { var save_data = {
"test": "test", # Basics
"name": name,
"type": "Room",
"id": self.get_instance_id(),
"scene_file_path": scene_file_path, "scene_file_path": scene_file_path,
"parent": get_parent().get_path(), "parent": get_parent().get_path(),
"room_tile_dict": room_tile_dict, # Connections
"room_door_array": room_door_array, "place_id": place_id,
"path_grid": path_grid # Data
} }
return save_data return save_data
func init_path_grid(): func init_room(new_dict):
setup_path_grid(new_dict)
for i in new_dict.values():
i.room = self
func setup_path_grid(new_array):
for i in room_tile_dict.keys(): room_tile_array = new_array
for i in room_tile_array:
var id = path_grid.get_available_point_id() var id = path_grid.get_available_point_id()
path_grid.add_point(id, i) path_grid.add_point(id, i.grid_pos)
for n in direction_array: for n in direction_array:
if room_tile_dict.has(i + n): if room_tile_array.has(i.grid_pos + n):
var closest = path_grid.get_closest_point(i + n) var closest = path_grid.get_closest_point(i + n)
if (i + n) == Vector3i(path_grid.get_point_position(closest)): if (i + n) == Vector3i(path_grid.get_point_position(closest)):
path_grid.connect_points(id, closest) path_grid.connect_points(id, closest)

View File

@ -2,5 +2,5 @@
[ext_resource type="Script" uid="uid://c2neixe7kpvma" path="res://places/base_place/base_room.gd" id="1_3l4mp"] [ext_resource type="Script" uid="uid://c2neixe7kpvma" path="res://places/base_place/base_room.gd" id="1_3l4mp"]
[node name="BaseRoom" type="Node3D"] [node name="BaseRoom" type="Node3D" groups=["SaveObjects"]]
script = ExtResource("1_3l4mp") script = ExtResource("1_3l4mp")

View File

@ -1,7 +1,7 @@
extends Node3D extends Node3D
var load_place = preload("res://places/base_place/base_place.tscn") var load_place = preload("res://places/base_place/base_place.tscn")
var place = load_place.instantiate() var place = null
enum ROOM_BUILD_STATE {NONE, ALLOWED, BUILDING, IS_PLACING_DOOR} enum ROOM_BUILD_STATE {NONE, ALLOWED, BUILDING, IS_PLACING_DOOR}
@ -16,10 +16,6 @@ var build_drag_start_pos = null
signal room_built signal room_built
func _ready():
#TEMP loads in a workplace.
add_child(place)
func _on_build_toggle(toggled_on): func _on_build_toggle(toggled_on):
#Responds to the 'Build A Room' toggle #Responds to the 'Build A Room' toggle
if toggled_on: if toggled_on:
@ -63,3 +59,15 @@ func _on_confirm_button_pressed() -> void:
else: else:
pass pass
func _on_init_grid_button_pressed() -> void:
if place:
place.queue_free()
var new_place = load_place.instantiate()
add_child(new_place)
place.init_grid()
func _on_child_entered_tree(node: Node) -> void:
if node is Place:
place = node

View File

@ -14,4 +14,5 @@ script = ExtResource("1_rcbs8")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 128, 0, 128) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 128, 0, 128)
shape = SubResource("BoxShape3D_rcbs8") shape = SubResource("BoxShape3D_rcbs8")
[connection signal="child_entered_tree" from="." to="." method="_on_child_entered_tree"]
[connection signal="input_event" from="FloorPlane" to="." method="_on_area_3d_input_event"] [connection signal="input_event" from="FloorPlane" to="." method="_on_area_3d_input_event"]

View File

@ -1,79 +1,91 @@
extends Node3D extends Node3D
class_name Tile class_name Tile
var id = null var place_id: int = 0
#This tile's current construction and selection modes. #This tile's current construction and selection modes.
@export var construction_mode = 0: set = update_construction @export var construction_mode = 0: set = update_construction
@export var selection_mode = 0: set = update_selection @export var selection_mode = 0: set = update_selection
var orange = preload("res://tiles/base_tile/orange.tres") const orange = preload("res://tiles/base_tile/orange.tres")
var gray = preload("res://tiles/base_tile/gray.tres") const gray = preload("res://tiles/base_tile/gray.tres")
var red = preload("res://tiles/base_tile/red.tres") const red = preload("res://tiles/base_tile/red.tres")
var blue = preload("res://tiles/base_tile/blue.tres") const blue = preload("res://tiles/base_tile/blue.tres")
var lightblue = preload("res://tiles/base_tile/lightblue.tres") const lightblue = preload("res://tiles/base_tile/lightblue.tres")
#Lists of possible states for various modes. #Lists of possible states for various modes.
enum SEL_MODE {NONE, ROOM, BUILD, INVALID} enum SEL_MODE {NONE, ROOM, BUILD, INVALID}
enum CON_MODE {NONE, NON_INTERACTABLE, CLOSED, OPEN, BUILT, REINFORCED, CONSTRUCTION} enum CON_MODE {NONE, NON_INTERACTABLE, CLOSED, OPEN, BUILT, REINFORCED, CONSTRUCTION}
enum FACE_MODE {NONE, FULL, PARTIAL, DOOR} #enum FACE_MODE {NONE, FULL, PARTIAL, DOOR}
var direction_vector_dict = { const direction_vector_dict = {
"North": Vector3i(0, 0, -1), "North": Vector3i(0, 0, -1),
"East": Vector3i(1, 0, 0), "East": Vector3i(1, 0, 0),
"South": Vector3i(0, 0, 1), "South": Vector3i(0, 0, 1),
"West": Vector3i(-1, 0, 0), "West": Vector3i(-1, 0, 0),
} }
var wall_position_dict = { const wall_position_dict = {
"North": Vector3i(0, 0, 0), "North": Vector3i(0, 0, 0),
"East": Vector3i(1, 0, 0), "East": Vector3i(1, 0, 0),
"South": Vector3i(1, 0, 1), "South": Vector3i(1, 0, 1),
"West": Vector3i(0, 0, 1), "West": Vector3i(0, 0, 1),
} }
var load_wall = preload("res://tiles/base_tile/base_wall.tscn") const load_wall = preload("res://tiles/base_tile/base_wall.tscn")
@export var grid_pos = null @export var grid_pos = null
@export var face_dict = {}: set = update_faces @export var face_dict = {}: set = update_faces
@export var room = null @export var room_array = []
var neighbor_dict = [] var neighbor_dict = {}
signal wall_built
signal neighbor_request signal neighbor_request
func _ready(): func _ready():
pass pass
func save(): func save():
var room_id_array: Array
for i in room_array:
room_id_array.append(i.get_instance_id())
var save_data = { var save_data = {
"test": "test", # Basics
"position": position, "name": name,
"type": "Tile",
"id": self.get_instance_id(),
"scene_file_path": scene_file_path, "scene_file_path": scene_file_path,
"parent": get_parent().get_path(), "parent": get_parent().get_path(),
# Connections
"place_id": place_id,
"room_array": room_id_array,
# Data
"position": position,
"grid_pos": grid_pos, "grid_pos": grid_pos,
"face_dict": face_dict, "face_dict": face_dict.keys(),
"room": room "construction_mode": construction_mode,
} }
return save_data return save_data
func update_faces(new_dict): func update_faces(new_dict):
face_dict = new_dict #for i in $Walls.get_children():
#i.queue_free()
for i in $Walls.get_children():
i.queue_free() if type_string(typeof(new_dict)) == "Array":
var temp_new_dict: Dictionary
for i in new_dict:
temp_new_dict[i] = null
new_dict = temp_new_dict.duplicate()
for i in new_dict.keys(): for i in new_dict.keys():
var direction: String = direction_vector_dict.find_key(i) var direction: String = direction_vector_dict.find_key(i)
var wall_pos = wall_position_dict[direction] var wall_pos = wall_position_dict[direction]
@ -88,6 +100,7 @@ func update_faces(new_dict):
"West": "West":
wall.rotation_degrees = Vector3(0, -270, 0) wall.rotation_degrees = Vector3(0, -270, 0)
$Walls.add_child(wall) $Walls.add_child(wall)
face_dict[i] = wall
func update_neighbors(): func update_neighbors():
neighbor_dict = {} neighbor_dict = {}
@ -120,12 +133,10 @@ func update_construction(mode):
var temp_face_dict = {} var temp_face_dict = {}
for i in neighbor_dict.keys(): for i in neighbor_dict.keys():
if not neighbor_dict[i].room == room: if not neighbor_dict[i].room_array == room_array:
#print(neighbor_dict[i].room)
temp_face_dict[i] = null temp_face_dict[i] = null
face_dict = temp_face_dict.duplicate() face_dict = temp_face_dict.duplicate()
print(face_dict)
CON_MODE.REINFORCED: CON_MODE.REINFORCED:
face_dict = {} face_dict = {}

View File

@ -3,7 +3,7 @@
[ext_resource type="Script" uid="uid://jqjcr7dxjnbt" path="res://tiles/base_tile/base_tile.gd" id="1_yoisu"] [ext_resource type="Script" uid="uid://jqjcr7dxjnbt" path="res://tiles/base_tile/base_tile.gd" id="1_yoisu"]
[ext_resource type="PlaneMesh" uid="uid://bis4hdushjnjm" path="res://tiles/base_tile/base_floor.tres" id="2_wxg2y"] [ext_resource type="PlaneMesh" uid="uid://bis4hdushjnjm" path="res://tiles/base_tile/base_floor.tres" id="2_wxg2y"]
[node name="Tile" type="Node3D" groups=["SaveObjects"]] [node name="BaseTile" type="Node3D" groups=["SaveObjects"]]
script = ExtResource("1_yoisu") script = ExtResource("1_yoisu")
[node name="Floor" type="Node3D" parent="."] [node name="Floor" type="Node3D" parent="."]

View File

@ -0,0 +1,3 @@
extends Node3D
class_name Wall

View File

@ -0,0 +1 @@
uid://cx6bmej4pcjlt

View File

@ -1,9 +1,16 @@
[gd_scene load_steps=2 format=3 uid="uid://diovc4myisuow"] [gd_scene load_steps=4 format=3 uid="uid://diovc4myisuow"]
[ext_resource type="Script" uid="uid://cx6bmej4pcjlt" path="res://tiles/base_tile/base_wall.gd" id="1_lirtl"]
[sub_resource type="BoxMesh" id="BoxMesh_xdfmn"] [sub_resource type="BoxMesh" id="BoxMesh_xdfmn"]
[node name="Node3D" type="Node3D"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_lirtl"]
albedo_color = Color(0.0574887, 2.24891e-05, 0.240434, 1)
[node name="BaseWall" type="Node3D"]
script = ExtResource("1_lirtl")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."] [node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 0.1, 0.5, 0.5, 0.05) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 0.1, 0.5, 0.5, 0.05)
mesh = SubResource("BoxMesh_xdfmn") mesh = SubResource("BoxMesh_xdfmn")
surface_material_override/0 = SubResource("StandardMaterial3D_lirtl")