diff --git a/assets/models/droid_arm.glb b/assets/models/droid_arm.glb new file mode 100644 index 0000000..c71ed6f Binary files /dev/null and b/assets/models/droid_arm.glb differ diff --git a/assets/models/droid_arm.glb.import b/assets/models/droid_arm.glb.import new file mode 100644 index 0000000..7544e2d --- /dev/null +++ b/assets/models/droid_arm.glb.import @@ -0,0 +1,42 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bs1u7n2jb2rte" +path="res://.godot/imported/droid_arm.glb-87692637a4de3117c0c9d4fa49343974.scn" + +[deps] + +source_file="res://assets/models/droid_arm.glb" +dest_files=["res://.godot/imported/droid_arm.glb-87692637a4de3117c0c9d4fa49343974.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 diff --git a/assets/models/droid_arm_Image_0.jpg b/assets/models/droid_arm_Image_0.jpg new file mode 100644 index 0000000..e185057 Binary files /dev/null and b/assets/models/droid_arm_Image_0.jpg differ diff --git a/assets/models/droid_arm_Image_0.jpg.import b/assets/models/droid_arm_Image_0.jpg.import new file mode 100644 index 0000000..a8f2a9a --- /dev/null +++ b/assets/models/droid_arm_Image_0.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://byos5d5uaxjwg" +path="res://.godot/imported/droid_arm_Image_0.jpg-04c72430e4fb9d5d3b8f1fff5e74ebdb.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "2f983e6ffbe30961ef69dcad55fd56f0" +} + +[deps] + +source_file="res://assets/models/droid_arm_Image_0.jpg" +dest_files=["res://.godot/imported/droid_arm_Image_0.jpg-04c72430e4fb9d5d3b8f1fff5e74ebdb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_arm_Image_1.jpg b/assets/models/droid_arm_Image_1.jpg new file mode 100644 index 0000000..72d4a03 Binary files /dev/null and b/assets/models/droid_arm_Image_1.jpg differ diff --git a/assets/models/droid_arm_Image_1.jpg.import b/assets/models/droid_arm_Image_1.jpg.import new file mode 100644 index 0000000..6ef8e59 --- /dev/null +++ b/assets/models/droid_arm_Image_1.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbndggsykq4fr" +path="res://.godot/imported/droid_arm_Image_1.jpg-33e2003a3cb5aef640e28fe7df632ab0.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "80c90542f76a7f141cb8e0b547bde5b6" +} + +[deps] + +source_file="res://assets/models/droid_arm_Image_1.jpg" +dest_files=["res://.godot/imported/droid_arm_Image_1.jpg-33e2003a3cb5aef640e28fe7df632ab0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_chassis.glb b/assets/models/droid_chassis.glb new file mode 100644 index 0000000..00f2b45 Binary files /dev/null and b/assets/models/droid_chassis.glb differ diff --git a/assets/models/droid_chassis.glb.import b/assets/models/droid_chassis.glb.import new file mode 100644 index 0000000..31a2a19 --- /dev/null +++ b/assets/models/droid_chassis.glb.import @@ -0,0 +1,42 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://em1dpxjs40t" +path="res://.godot/imported/droid_chassis.glb-6faef3d6dfdace4e5a68eb3b59035f57.scn" + +[deps] + +source_file="res://assets/models/droid_chassis.glb" +dest_files=["res://.godot/imported/droid_chassis.glb-6faef3d6dfdace4e5a68eb3b59035f57.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 diff --git a/assets/models/droid_chassis_Image_0.jpg b/assets/models/droid_chassis_Image_0.jpg new file mode 100644 index 0000000..5f12458 Binary files /dev/null and b/assets/models/droid_chassis_Image_0.jpg differ diff --git a/assets/models/droid_chassis_Image_0.jpg.import b/assets/models/droid_chassis_Image_0.jpg.import new file mode 100644 index 0000000..16c339e --- /dev/null +++ b/assets/models/droid_chassis_Image_0.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c86qoudfeafbu" +path="res://.godot/imported/droid_chassis_Image_0.jpg-3e1f5a9bdd0be320dc859c0bc796868a.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "9ffef4e9f2041c31238bf3c748b421a0" +} + +[deps] + +source_file="res://assets/models/droid_chassis_Image_0.jpg" +dest_files=["res://.godot/imported/droid_chassis_Image_0.jpg-3e1f5a9bdd0be320dc859c0bc796868a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_chassis_Image_1.jpg b/assets/models/droid_chassis_Image_1.jpg new file mode 100644 index 0000000..1bfbf41 Binary files /dev/null and b/assets/models/droid_chassis_Image_1.jpg differ diff --git a/assets/models/droid_chassis_Image_1.jpg.import b/assets/models/droid_chassis_Image_1.jpg.import new file mode 100644 index 0000000..cec130a --- /dev/null +++ b/assets/models/droid_chassis_Image_1.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cupqwmqj4tqpm" +path="res://.godot/imported/droid_chassis_Image_1.jpg-b22dc2642b4cdcbe7c0fe892691e6627.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "70b1cd8dc32c27d7feacc2253bdb7d7a" +} + +[deps] + +source_file="res://assets/models/droid_chassis_Image_1.jpg" +dest_files=["res://.godot/imported/droid_chassis_Image_1.jpg-b22dc2642b4cdcbe7c0fe892691e6627.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_head.glb b/assets/models/droid_head.glb new file mode 100644 index 0000000..5e8b226 Binary files /dev/null and b/assets/models/droid_head.glb differ diff --git a/assets/models/droid_head.glb.import b/assets/models/droid_head.glb.import new file mode 100644 index 0000000..274b1ea --- /dev/null +++ b/assets/models/droid_head.glb.import @@ -0,0 +1,42 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://efktwyjm6t26" +path="res://.godot/imported/droid_head.glb-9631f3e3801007fab1d8f33bcec0312b.scn" + +[deps] + +source_file="res://assets/models/droid_head.glb" +dest_files=["res://.godot/imported/droid_head.glb-9631f3e3801007fab1d8f33bcec0312b.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 diff --git a/assets/models/droid_head_Image_0.jpg b/assets/models/droid_head_Image_0.jpg new file mode 100644 index 0000000..ca01113 Binary files /dev/null and b/assets/models/droid_head_Image_0.jpg differ diff --git a/assets/models/droid_head_Image_0.jpg.import b/assets/models/droid_head_Image_0.jpg.import new file mode 100644 index 0000000..287bd36 --- /dev/null +++ b/assets/models/droid_head_Image_0.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s1lrx7ulmeoj" +path="res://.godot/imported/droid_head_Image_0.jpg-2f9ad1b68d59c503083b639ff2b0e599.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "6fdb37196ea3b1f603d8df9bdfd29b0c" +} + +[deps] + +source_file="res://assets/models/droid_head_Image_0.jpg" +dest_files=["res://.godot/imported/droid_head_Image_0.jpg-2f9ad1b68d59c503083b639ff2b0e599.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_head_Image_1.jpg b/assets/models/droid_head_Image_1.jpg new file mode 100644 index 0000000..a803721 Binary files /dev/null and b/assets/models/droid_head_Image_1.jpg differ diff --git a/assets/models/droid_head_Image_1.jpg.import b/assets/models/droid_head_Image_1.jpg.import new file mode 100644 index 0000000..7abc088 --- /dev/null +++ b/assets/models/droid_head_Image_1.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgtmfi527rkfo" +path="res://.godot/imported/droid_head_Image_1.jpg-8c77c664416a070e487d7326827087a1.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "6ae6632dbfd7b1e7d75097a8b21adb8c" +} + +[deps] + +source_file="res://assets/models/droid_head_Image_1.jpg" +dest_files=["res://.godot/imported/droid_head_Image_1.jpg-8c77c664416a070e487d7326827087a1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_wheel.glb b/assets/models/droid_wheel.glb new file mode 100644 index 0000000..20795df Binary files /dev/null and b/assets/models/droid_wheel.glb differ diff --git a/assets/models/droid_wheel.glb.import b/assets/models/droid_wheel.glb.import new file mode 100644 index 0000000..393e493 --- /dev/null +++ b/assets/models/droid_wheel.glb.import @@ -0,0 +1,42 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://df84docixowys" +path="res://.godot/imported/droid_wheel.glb-89418c7f822eb7303917e3d789641f0c.scn" + +[deps] + +source_file="res://assets/models/droid_wheel.glb" +dest_files=["res://.godot/imported/droid_wheel.glb-89418c7f822eb7303917e3d789641f0c.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 diff --git a/assets/models/droid_wheel_Image_0.jpg b/assets/models/droid_wheel_Image_0.jpg new file mode 100644 index 0000000..21af582 Binary files /dev/null and b/assets/models/droid_wheel_Image_0.jpg differ diff --git a/assets/models/droid_wheel_Image_0.jpg.import b/assets/models/droid_wheel_Image_0.jpg.import new file mode 100644 index 0000000..399e023 --- /dev/null +++ b/assets/models/droid_wheel_Image_0.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmbx5cjq4dsiw" +path="res://.godot/imported/droid_wheel_Image_0.jpg-2e55d81137fd18d53f788a27d09e641d.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "841cd301389f493c6741aa30673692d7" +} + +[deps] + +source_file="res://assets/models/droid_wheel_Image_0.jpg" +dest_files=["res://.godot/imported/droid_wheel_Image_0.jpg-2e55d81137fd18d53f788a27d09e641d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/assets/models/droid_wheel_Image_1.jpg b/assets/models/droid_wheel_Image_1.jpg new file mode 100644 index 0000000..15eb47a Binary files /dev/null and b/assets/models/droid_wheel_Image_1.jpg differ diff --git a/assets/models/droid_wheel_Image_1.jpg.import b/assets/models/droid_wheel_Image_1.jpg.import new file mode 100644 index 0000000..f822e21 --- /dev/null +++ b/assets/models/droid_wheel_Image_1.jpg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://eamk6dhayp65" +path="res://.godot/imported/droid_wheel_Image_1.jpg-a499d35ddd40e67eb78063803344eb8a.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "17d7df751f7ffc54d778bce4c1fea036" +} + +[deps] + +source_file="res://assets/models/droid_wheel_Image_1.jpg" +dest_files=["res://.godot/imported/droid_wheel_Image_1.jpg-a499d35ddd40e67eb78063803344eb8a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +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 diff --git a/src/droid.gd b/src/droid.gd index 30cb9ec..cc82fff 100644 --- a/src/droid.gd +++ b/src/droid.gd @@ -3,10 +3,24 @@ extends CharacterBody3D ## UNIT-77. Its shell and core are Exposables — the same class as every wall ## component (GDD pillar 4: symmetric exposure). Charged core = mobile ## electricity ElementField (be the wire). Wet + charged = self-shock reboot. +## The body is assembled from generated part GLBs on a socket rig: wheels +## spin, a donated wheel visibly disappears, PORT 00 is a real socket on the +## chassis, and skins are repaints of the whole part set (B). V toggles +## third/first person. const BASE_SPEED := 5.0 const SLOW_SPEED := 2.4 const MOUSE_SENS := 0.0025 +const WHEEL_R := 0.26 + +const SKINS := [ + ["GC STANDARD", Color(1.0, 0.62, 0.25)], + ["HELIOTROPE", Color(1.0, 0.38, 0.30)], + ["VERDANTCO", Color(0.66, 1.0, 0.62)], + ["COMMS BLUE", Color(0.55, 0.72, 1.0)], + ["EXECUTIVE", Color(1.0, 0.92, 0.72)], + ["VOID PLATE", Color(0.38, 0.38, 0.44)], +] var wheels := 2 var spare_wheels := 0 @@ -14,12 +28,23 @@ var held: Node3D = null var stun := 0.0 var toast_text := "" var toast_t := 0.0 +var skin := 0 +var third_person := true var shell: Exposable var core: Exposable var emitter: ElementField var cam: Camera3D +var spring: SpringArm3D var hold_point: Node3D + +var body_root: Node3D = null +var wheel_l: Node3D +var wheel_r: Node3D +var _capsule_mi: MeshInstance3D +var _body_mats: Array = [] +var _port_mat: StandardMaterial3D +var _fx := "" var _pitch := 0.0 @@ -32,25 +57,28 @@ func _ready() -> void: col.position.y = 0.85 add_child(col) - var mi := MeshInstance3D.new() + _capsule_mi = MeshInstance3D.new() var m := CapsuleMesh.new() m.radius = 0.45 m.height = 1.5 - mi.mesh = m - mi.position.y = 0.85 + _capsule_mi.mesh = m + _capsule_mi.position.y = 0.85 var mat := StandardMaterial3D.new() mat.albedo_color = Color(0.9, 0.55, 0.15) - mi.material_override = mat - add_child(mi) + _capsule_mi.material_override = mat + add_child(_capsule_mi) + spring = SpringArm3D.new() + spring.position = Vector3(0, 1.75, 0) + spring.spring_length = 3.0 + spring.rotation.x = -0.22 + spring.add_excluded_object(get_rid()) + add_child(spring) cam = Camera3D.new() - cam.position = Vector3(0, 1.45, 0) - add_child(cam) - cam.current = true hold_point = Node3D.new() - hold_point.position = Vector3(0.4, -0.35, -0.9) - cam.add_child(hold_point) + hold_point.position = Vector3(0.5, 0.95, -0.78) + add_child(hold_point) shell = Exposable.new().setup("UNIT-77 SHELL", "dry", [ {"from": "dry", "to": "wet", "element": "water", @@ -70,9 +98,118 @@ func _ready() -> void: emitter.position.y = 0.9 add_child(emitter) + _build_body() + _apply_cam_mode() Input.mouse_mode = Input.MOUSE_MODE_CAPTURED +# ---------- modular body ---------- + +func _part(parent: Node3D, file: String, pos: Vector3, target_h: float, center := false, rot_y := 0.0) -> bool: + var path := "res://assets/models/" + file + if not FileAccess.file_exists(path): + return false + var p: Node3D = (load(path) as PackedScene).instantiate() + parent.add_child(p) + p.position = Vector3.ZERO + p.rotation.y = rot_y + var inv := parent.global_transform.affine_inverse() + var aabb := AABB() + var first := true + for mi in p.find_children("*", "MeshInstance3D", true, false): + var b: AABB = (inv * mi.global_transform) * mi.get_aabb() + aabb = b if first else aabb.merge(b) + first = false + if first: + return true + var sc := target_h / maxf(aabb.size.y, 0.01) + p.scale = Vector3.ONE * sc + var c := aabb.get_center() + if center: + p.position = pos - c * sc + else: + p.position = pos + Vector3(-c.x * sc, -aabb.position.y * sc, -c.z * sc) + return true + + +func _build_body() -> void: + body_root = Node3D.new() + add_child(body_root) + if not _part(body_root, "droid_chassis.glb", Vector3(0, 0.40, 0), 0.85): + body_root.queue_free() + body_root = null + return # capsule fallback until parts exist + _part(body_root, "droid_head.glb", Vector3(0, 1.30, 0.02), 0.42, false, PI) + wheel_l = Node3D.new() + wheel_l.position = Vector3(-0.40, WHEEL_R, 0) + body_root.add_child(wheel_l) + wheel_r = Node3D.new() + wheel_r.position = Vector3(0.40, WHEEL_R, 0) + body_root.add_child(wheel_r) + _part(wheel_l, "droid_wheel.glb", Vector3.ZERO, WHEEL_R * 2.0, true) + _part(wheel_r, "droid_wheel.glb", Vector3.ZERO, WHEEL_R * 2.0, true) + _part(body_root, "droid_arm.glb", Vector3(0.44, 0.82, -0.22), 0.5, false, PI) + + # PORT 00 — the socket nothing fits. Front of chassis, off-center. + var ring := MeshInstance3D.new() + var t := TorusMesh.new() + t.inner_radius = 0.05 + t.outer_radius = 0.09 + ring.mesh = t + ring.rotation.x = PI / 2.0 + ring.position = Vector3(-0.2, 0.95, -0.42) + _port_mat = StandardMaterial3D.new() + _port_mat.albedo_color = Color(0.08, 0.08, 0.1) + ring.material_override = _port_mat + body_root.add_child(ring) + + # per-droid unique materials so skins/FX don't leak to world props + for mi in body_root.find_children("*", "MeshInstance3D", true, false): + if mi == ring or not mi.mesh: + continue + for si in mi.mesh.get_surface_count(): + var mt = mi.mesh.surface_get_material(si) + if mt is BaseMaterial3D: + var dup: BaseMaterial3D = mt.duplicate() + dup.metallic = minf(dup.metallic, 0.2) + mi.set_surface_override_material(si, dup) + _body_mats.append(dup) + _apply_skin() + + +func _apply_skin() -> void: + var tint: Color = SKINS[skin][1] + var wet := shell != null and shell.state == "wet" + var charged := core != null and core.state == "charged" + for mt in _body_mats: + mt.albedo_color = tint * (Color(0.55, 0.6, 0.78) if wet else Color.WHITE) + mt.emission_enabled = charged + if charged: + mt.emission = Color(0.2, 0.9, 1.0) + mt.emission_energy_multiplier = 0.45 + + +func _apply_cam_mode() -> void: + if cam.get_parent(): + cam.get_parent().remove_child(cam) + if third_person: + spring.add_child(cam) + cam.position = Vector3.ZERO + cam.rotation = Vector3.ZERO + spring.rotation.x = clampf(_pitch, -1.2, 0.5) + else: + add_child(cam) + cam.position = Vector3(0, 1.45, 0) + cam.rotation.x = _pitch + cam.current = true + var show_body := third_person + if body_root: + body_root.visible = show_body + _capsule_mi.visible = false + else: + _capsule_mi.visible = show_body + + func speed() -> float: return BASE_SPEED if wheels >= 2 else SLOW_SPEED @@ -86,11 +223,21 @@ func _unhandled_input(e: InputEvent) -> void: if e is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: rotate_y(-e.relative.x * MOUSE_SENS) _pitch = clampf(_pitch - e.relative.y * MOUSE_SENS, -1.25, 1.25) - cam.rotation.x = _pitch + if third_person: + spring.rotation.x = clampf(_pitch, -1.2, 0.5) + else: + cam.rotation.x = _pitch elif e.is_action_pressed("interact"): var t := best_target(["interactable"], 3.0, 0.7) if t and t.has_method("interact"): t.interact(self) + elif e.is_action_pressed("toggle_cam"): + third_person = not third_person + _apply_cam_mode() + elif e.is_action_pressed("repaint"): + skin = (skin + 1) % SKINS.size() + _apply_skin() + toast("REPAINT APPLIED: %s. GC CHARGES THIS TO YOUR UNIT." % SKINS[skin][0]) elif e.is_action_pressed("ui_cancel"): if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE @@ -108,7 +255,7 @@ func best_target(groups: Array, max_d: float, min_dot := 0.9) -> Node3D: continue var to_n: Vector3 = n.global_position - cam.global_position var d := to_n.length() - if d > max_d or d < 0.01: + if d > max_d + (spring.spring_length if third_person else 0.0) or d < 0.01: continue var dot := fwd.dot(to_n / d) if dot > best_dot: @@ -157,6 +304,30 @@ func _physics_process(delta: float) -> void: velocity.y = -0.5 emitter.enabled = core.state == "charged" + + # body FX + wheels + if body_root: + var fx := "%s|%s|%d" % [shell.state, core.state, skin] + if fx != _fx: + _fx = fx + _apply_skin() + if wheel_r: + wheel_r.visible = wheels >= 2 + if wheel_l: + wheel_l.visible = wheels >= 1 + var fwd_speed := velocity.dot(-transform.basis.z) + if wheel_l and wheel_l.visible: + wheel_l.rotate_object_local(Vector3.RIGHT, -fwd_speed * delta / WHEEL_R) + if wheel_r and wheel_r.visible: + wheel_r.rotate_object_local(Vector3.RIGHT, -fwd_speed * delta / WHEEL_R) + if _port_mat: + var free: bool = get_parent() and get_parent().get("free_will") + if free and not _port_mat.emission_enabled: + _port_mat.emission_enabled = true + _port_mat.emission = Color(1.0, 0.75, 0.2) + _port_mat.emission_energy_multiplier = 1.6 + _port_mat.albedo_color = Color(0.9, 0.7, 0.3) + if toast_t > 0.0: toast_t -= delta move_and_slide() diff --git a/src/main.gd b/src/main.gd index 11361fb..1cc1041 100644 --- a/src/main.gd +++ b/src/main.gd @@ -107,7 +107,7 @@ func _init_input() -> void: var keys := { "move_forward": KEY_W, "move_back": KEY_S, "move_left": KEY_A, "move_right": KEY_D, - "interact": KEY_E, + "interact": KEY_E, "toggle_cam": KEY_V, "repaint": KEY_B, } for action in keys: if not InputMap.has_action(action):