e9dc05983c
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
191 lines
6.6 KiB
GDScript
191 lines
6.6 KiB
GDScript
## 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)
|