Save workspace artifacts: character-controller, gdextension scaffold, network scripts, map-pipeline, server config
Includes code from task workspaces that was never pushed: - client/characters/ — fps_character_controller.gd (400 lines), fps_camera.gd, input_handler.gd - gdextension/simulation/ — C++ GDExtension scaffold: entity, movement, hit detection, simulation server, state serializer, bitstream (14 files) - scripts/network/ — ENet-based network_manager.gd, server/client_main.gd, player.gd - scripts/map_packaging/ — PCK pipeline: pack_map.gd, map_downloader.gd, map_registry_server.py, README - scripts/config/ — server_config.gd (480 lines) - scenes/ — client_main, server_main, player, test_range - project.godot, export_presets.cfg
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
## pack_map.gd — Map PCK Exporter
|
||||
##
|
||||
## Editor tool script that exports a map scene (.tscn) as a standalone
|
||||
## .pck resource pack. The .pck contains only the map scene and its
|
||||
## direct dependencies (meshes, textures, materials) — no game logic.
|
||||
##
|
||||
## Usage:
|
||||
## 1. Open the map template project (client/map_template/) in Godot.
|
||||
## 2. Build your map in the scenes/maps/ directory.
|
||||
## 3. Run this script from the Editor > Tools menu, or via the CLI:
|
||||
## godot --headless --script scripts/map_packaging/pack_map.gd --map=res://scenes/maps/my_map.tscn
|
||||
##
|
||||
## Output:
|
||||
## user://packed_maps/my_map.pck (ready to upload to the registry server)
|
||||
##
|
||||
## The exported .pck can be loaded in-game via:
|
||||
## ProjectSettings.load_resource_pack("user://maps/my_map.pck")
|
||||
##
|
||||
## Requirements:
|
||||
## - Godot 4.2+
|
||||
## - The map scene must use only local resources (relative paths within the project)
|
||||
## - External dependencies (e.g. shared game assets) should be excluded — the
|
||||
## .pck is meant to be additive content, not a full replacement.
|
||||
|
||||
tool
|
||||
extends EditorScript
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Output directory relative to user://
|
||||
const OUTPUT_DIR: String = "packed_maps"
|
||||
|
||||
## Godot export mode — packs all dependencies recursively.
|
||||
const EXPORT_MODE: int = PackedScene.GEN_FLAG_SAVE_NONE
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _run() -> void:
|
||||
# Determine the map scene path
|
||||
var scene_path: String = _resolve_scene_path()
|
||||
if scene_path.is_empty():
|
||||
print("Usage: godot --headless --script pack_map.gd --map=res://scenes/maps/<map_name>.tscn")
|
||||
print(" Or run from Editor > Tools > Pack Current Map")
|
||||
return
|
||||
|
||||
# Validate the scene exists
|
||||
if not ResourceLoader.exists(scene_path):
|
||||
push_error("[PackMap] Scene not found: %s" % scene_path)
|
||||
return
|
||||
|
||||
# Derive map name from file name
|
||||
var map_name: String = scene_path.get_file().trim_suffix(".tscn")
|
||||
var output_path: String = "user://%s/%s.pck" % [OUTPUT_DIR, map_name]
|
||||
|
||||
# Ensure output directory exists
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("user://%s" % OUTPUT_DIR))
|
||||
|
||||
print("[PackMap] Packaging: %s" % scene_path)
|
||||
print("[PackMap] Output: %s" % output_path)
|
||||
|
||||
# Load the scene
|
||||
var scene: PackedScene = load(scene_path)
|
||||
if scene == null:
|
||||
push_error("[PackMap] Failed to load scene: %s" % scene_path)
|
||||
return
|
||||
|
||||
# Generate the .pck via Godot's built-in packer.
|
||||
# We do this by creating a temporary PackedScene that encapsulates the map,
|
||||
# then using ProjectSettings.save_resource_pack to extract only the needed resources.
|
||||
var err: Error = _export_pck(scene, scene_path, output_path)
|
||||
if err != OK:
|
||||
push_error("[PackMap] Export failed: %s" % error_string(err))
|
||||
return
|
||||
|
||||
print("[PackMap] Successfully packed: %s (→ %s)" % [map_name, output_path])
|
||||
print("[PackMap] File size: %d bytes" % FileAccess.get_size(output_path))
|
||||
|
||||
# Write a sidecar JSON with metadata
|
||||
_write_metadata(map_name, output_path, scene_path)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Resolve the --map CLI argument or use the currently open scene.
|
||||
func _resolve_scene_path() -> String:
|
||||
# CLI argument takes precedence
|
||||
for arg in OS.get_cmdline_args():
|
||||
if arg.begins_with("--map="):
|
||||
return arg.trim_prefix("--map=")
|
||||
|
||||
# Fallback: use the currently open scene in the editor
|
||||
var current_scene_path: String = ""
|
||||
if Engine.is_editor_hint():
|
||||
# In editor mode, try to get the current scene from the editor interface
|
||||
var editor_interface: EditorInterface = get_editor_interface()
|
||||
if editor_interface:
|
||||
current_scene_path = editor_interface.get_current_scene().scene_file_path
|
||||
|
||||
return current_scene_path
|
||||
|
||||
|
||||
## Export the scene as a .pck file containing only its direct dependencies.
|
||||
func _export_pck(scene: PackedScene, scene_path: String, output_path: String) -> Error:
|
||||
# Strategy: pack the scene + all its dependencies into a .pck using
|
||||
# ResourceSaver. We create a packaging helper that simulates what
|
||||
# Godot's export process does for resource packs.
|
||||
|
||||
# Create a collection of all dependencies we need to include
|
||||
var deps: Array[String] = _collect_dependencies(scene_path)
|
||||
|
||||
# Add the scene itself
|
||||
deps.append(scene_path)
|
||||
|
||||
# We need to strip the "res://" prefix and map to correct paths
|
||||
# Create a PackedScene copy with packed resources
|
||||
var packed: PackedScene = _build_packed_map(scene_path, deps)
|
||||
if packed == null:
|
||||
return ERR_FILE_CORRUPT
|
||||
|
||||
# Write the .pck using ProjectSettings.save_resource_pack()
|
||||
var files_to_pack: PackedStringArray = PackedStringArray()
|
||||
for dep in deps:
|
||||
var global_path: String = ProjectSettings.globalize_path(dep)
|
||||
if FileAccess.file_exists(global_path):
|
||||
files_to_pack.append(dep)
|
||||
|
||||
if files_to_pack.is_empty():
|
||||
return ERR_FILE_NOT_FOUND
|
||||
|
||||
var err: Error = ProjectSettings.save_resource_pack(output_path, files_to_pack)
|
||||
return err
|
||||
|
||||
|
||||
## Collect all dependencies of a scene recursively.
|
||||
func _collect_dependencies(scene_path: String) -> Array[String]:
|
||||
var deps: Array[String] = []
|
||||
var visited: Dictionary = {}
|
||||
var pending: Array[String] = [scene_path]
|
||||
|
||||
while not pending.is_empty():
|
||||
var current: String = pending.pop_front()
|
||||
if current in visited:
|
||||
continue
|
||||
visited[current] = true
|
||||
|
||||
# Skip external / built-in resources
|
||||
if current.begins_with("builtin://") or current.begins_with("uid://"):
|
||||
continue
|
||||
|
||||
# Get dependencies from ResourceLoader
|
||||
var dep_list: PackedStringArray = ResourceLoader.get_dependencies(current)
|
||||
for dep in dep_list:
|
||||
if dep not in visited and dep.begins_with("res://"):
|
||||
deps.append(dep)
|
||||
pending.append(dep)
|
||||
|
||||
return deps
|
||||
|
||||
|
||||
## Build a PackedScene from the map and its dependencies.
|
||||
func _build_packed_map(scene_path: String, deps: Array[String]) -> PackedScene:
|
||||
# Load the scene and pack it
|
||||
var scene: PackedScene = load(scene_path)
|
||||
if scene == null:
|
||||
return null
|
||||
return scene
|
||||
|
||||
|
||||
## Write a JSON metadata file alongside the .pck.
|
||||
func _write_metadata(map_name: String, pck_path: String, scene_path: String) -> void:
|
||||
var meta := {
|
||||
map_name = map_name,
|
||||
pck_path = pck_path,
|
||||
source_scene = scene_path,
|
||||
godot_version = Engine.get_version_info(),
|
||||
packed_at = Time.get_datetime_string_from_system(),
|
||||
}
|
||||
|
||||
var meta_path: String = pck_path.trim_suffix(".pck") + ".json"
|
||||
var f := FileAccess.open(meta_path, FileAccess.WRITE)
|
||||
if f:
|
||||
f.store_string(JSON.stringify(meta, "\t", false))
|
||||
f.close()
|
||||
print("[PackMap] Metadata written: %s" % meta_path)
|
||||
Reference in New Issue
Block a user