Feat/14 movement overhaul #20
Binary file not shown.
@@ -163,6 +163,49 @@ func _ready() -> void:
|
|||||||
calf_r.cast_shadow = shadow_setting
|
calf_r.cast_shadow = shadow_setting
|
||||||
calf_r_pivot.add_child(calf_r)
|
calf_r_pivot.add_child(calf_r)
|
||||||
|
|
||||||
|
## Skin System
|
||||||
|
var current_skin: PlayerSkin
|
||||||
|
var skin_model: Node3D
|
||||||
|
|
||||||
|
func apply_skin(skin: PlayerSkin) -> void:
|
||||||
|
current_skin = skin
|
||||||
|
if skin.model_path != "" and skin.model_path != null:
|
||||||
|
var loaded = load(skin.model_path)
|
||||||
|
if loaded:
|
||||||
|
_set_procedural_visible(false)
|
||||||
|
if skin_model:
|
||||||
|
skin_model.queue_free()
|
||||||
|
skin_model = null
|
||||||
|
skin_model = loaded.instantiate()
|
||||||
|
if skin_model:
|
||||||
|
skin_model.name = "SkinModel"
|
||||||
|
add_child(skin_model)
|
||||||
|
print("HumanoidModel: applied skin '%s'" % skin.skin_name)
|
||||||
|
return
|
||||||
|
_set_procedural_visible(true)
|
||||||
|
if skin_model:
|
||||||
|
skin_model.queue_free()
|
||||||
|
skin_model = null
|
||||||
|
_apply_color(skin.color_tint)
|
||||||
|
|
||||||
|
func _set_procedural_visible(visible: bool) -> void:
|
||||||
|
for part in [torso, head, upper_arm_l, lower_arm_l, upper_arm_r, lower_arm_r, thigh_l, calf_l, thigh_r, calf_r]:
|
||||||
|
if part:
|
||||||
|
part.visible = visible
|
||||||
|
|
||||||
|
func _apply_color(col: Color) -> void:
|
||||||
|
var mat = StandardMaterial3D.new()
|
||||||
|
mat.albedo_color = col
|
||||||
|
mat.roughness = 0.8
|
||||||
|
for part in [torso, upper_arm_l, lower_arm_l, upper_arm_r, lower_arm_r, thigh_l, calf_l, thigh_r, calf_r]:
|
||||||
|
if part and part.mesh:
|
||||||
|
part.mesh.material = mat
|
||||||
|
if head and head.mesh:
|
||||||
|
var hmat = StandardMaterial3D.new()
|
||||||
|
hmat.albedo_color = Color(0.95, 0.85, 0.78)
|
||||||
|
hmat.roughness = 0.8
|
||||||
|
head.mesh.material = hmat
|
||||||
|
|
||||||
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
func update_state(state: String, speed: float, is_crouching: bool = false) -> void:
|
||||||
current_state = state
|
current_state = state
|
||||||
movement_speed = speed
|
movement_speed = speed
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
extends Resource
|
||||||
|
class_name PlayerSkin
|
||||||
|
|
||||||
|
## A player skin defines the visual appearance of the character.
|
||||||
|
## Can be either a color tint (for the default procedural model)
|
||||||
|
## or a full GLB model replacement.
|
||||||
|
|
||||||
|
@export var skin_name: String = "Default"
|
||||||
|
@export var description: String = ""
|
||||||
|
@export var model_path: String = "" # Path to .glb file, empty = use procedural
|
||||||
|
@export var color_tint: Color = Color(0.2, 0.4, 0.8) # For procedural model
|
||||||
|
@export var is_unlocked: bool = true
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
extends Node
|
||||||
|
class_name SkinManager
|
||||||
|
|
||||||
|
## Manages all available player skins.
|
||||||
|
## Add new skins here or load from a config file.
|
||||||
|
|
||||||
|
var skins: Dictionary = {}
|
||||||
|
var active_skin_id: String = "default"
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_register_default_skins()
|
||||||
|
|
||||||
|
func _register_default_skins() -> void:
|
||||||
|
# Default blue soldier
|
||||||
|
var default_skin = PlayerSkin.new()
|
||||||
|
default_skin.skin_name = "Default"
|
||||||
|
default_skin.description = "Standard issue"
|
||||||
|
default_skin.color_tint = Color(0.2, 0.4, 0.8)
|
||||||
|
default_skin.is_unlocked = true
|
||||||
|
skins["default"] = default_skin
|
||||||
|
|
||||||
|
# Red team
|
||||||
|
var red_skin = PlayerSkin.new()
|
||||||
|
red_skin.skin_name = "Red Team"
|
||||||
|
red_skin.description = "Red team colors"
|
||||||
|
red_skin.color_tint = Color(0.8, 0.2, 0.2)
|
||||||
|
red_skin.is_unlocked = true
|
||||||
|
skins["red_team"] = red_skin
|
||||||
|
|
||||||
|
# Green soldier
|
||||||
|
var green_skin = PlayerSkin.new()
|
||||||
|
green_skin.skin_name = "Forest"
|
||||||
|
green_skin.description = "Forest camouflage"
|
||||||
|
green_skin.color_tint = Color(0.2, 0.6, 0.2)
|
||||||
|
green_skin.is_unlocked = true
|
||||||
|
skins["forest"] = green_skin
|
||||||
|
|
||||||
|
# Hatsune Miku
|
||||||
|
var miku_skin = PlayerSkin.new()
|
||||||
|
miku_skin.skin_name = "Miku"
|
||||||
|
miku_skin.description = "Hatsune Miku — Virtual Idol"
|
||||||
|
miku_skin.model_path = "res://assets/characters/skins/miku.glb"
|
||||||
|
miku_skin.color_tint = Color(0.0, 0.75, 0.75)
|
||||||
|
miku_skin.is_unlocked = true
|
||||||
|
skins["miku"] = miku_skin
|
||||||
|
|
||||||
|
print("SkinManager: registered %d skins" % skins.size())
|
||||||
|
for id in skins:
|
||||||
|
print(" - %s: %s" % [id, skins[id].skin_name])
|
||||||
|
|
||||||
|
func get_skin(skin_id: String) -> PlayerSkin:
|
||||||
|
if skins.has(skin_id):
|
||||||
|
return skins[skin_id]
|
||||||
|
return skins["default"]
|
||||||
|
|
||||||
|
func get_active_skin() -> PlayerSkin:
|
||||||
|
return get_skin(active_skin_id)
|
||||||
|
|
||||||
|
func set_active_skin(skin_id: String) -> void:
|
||||||
|
if skins.has(skin_id) and skins[skin_id].is_unlocked:
|
||||||
|
active_skin_id = skin_id
|
||||||
|
print("SkinManager: active skin set to '%s'" % skins[skin_id].skin_name)
|
||||||
@@ -377,6 +377,14 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
humanoid.position = Vector3(0, -0.9, 0)
|
humanoid.position = Vector3(0, -0.9, 0)
|
||||||
player.add_child(humanoid)
|
player.add_child(humanoid)
|
||||||
|
|
||||||
|
# Apply skin (Miku for player 1, default for others)
|
||||||
|
if pid == 1:
|
||||||
|
var miku_skin = PlayerSkin.new()
|
||||||
|
miku_skin.skin_name = "Miku"
|
||||||
|
miku_skin.model_path = "res://assets/characters/skins/miku.glb"
|
||||||
|
miku_skin.color_tint = Color(0.0, 0.75, 0.75)
|
||||||
|
humanoid.apply_skin(miku_skin)
|
||||||
|
|
||||||
# Movement State Machine
|
# Movement State Machine
|
||||||
var sm := Node.new()
|
var sm := Node.new()
|
||||||
sm.name = "MovementStateMachine"
|
sm.name = "MovementStateMachine"
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# 3D Asset Pipeline for Papaya-Shooter
|
||||||
|
|
||||||
|
## Blender Setup
|
||||||
|
- Blender 5.0.1 installed system-wide
|
||||||
|
- Blender MCP addon at ~/.config/blender/5.0/scripts/addons/blender_mcp/
|
||||||
|
- Bridge: ~/blender-mcp/blender_bridge.py (socket port 9876)
|
||||||
|
- Start: xvfb-run -a blender --python ~/blender-mcp/blender_server.py &
|
||||||
|
|
||||||
|
## Export Workflow
|
||||||
|
1. Write Blender Python script (generate model, rig, animate)
|
||||||
|
2. Run: blender --background --python script.py
|
||||||
|
3. Export as .glb to assets/ subdirectory
|
||||||
|
4. Godot auto-imports on editor open
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
- assets/characters/skins/ — player skin models
|
||||||
|
- assets/characters/animations/ — animation clips
|
||||||
|
- assets/weapons/models/ — weapon meshes
|
||||||
|
- assets/weapons/skins/ — weapon skin textures
|
||||||
|
- assets/props/ — level props
|
||||||
|
|
||||||
|
## Character System
|
||||||
|
- HumanoidModel: characters/humanoid_model.gd
|
||||||
|
- Procedural rig: torso, head, 4 arms, 4 legs with pivot nodes
|
||||||
|
- States: idle, run, crouch, slide, air, wall_run, wall_cling, grapple, dash, death
|
||||||
|
- Color: @export var color (StandardMaterial3D)
|
||||||
|
- shadows_only for local player (don't see own model)
|
||||||
|
- Weapon attach: set_weapon(script_path) → root_pivot child
|
||||||
|
|
||||||
|
## Loadout System
|
||||||
|
- LoadoutManager: globals/loadout_manager.gd (autoload)
|
||||||
|
- 5 slots, each: primary_1, primary_2, special, melee
|
||||||
|
- Save: user://loadouts.json
|
||||||
|
|
||||||
|
## Weapon System
|
||||||
|
- WeaponManager: weapons/weapon_manager.gd
|
||||||
|
- 1st person: SubViewport + vm_camera (layer 20)
|
||||||
|
- 3rd person: HumanoidModel.set_weapon()
|
||||||
|
|
||||||
|
## Skin System (NEW)
|
||||||
|
- Store skin metadata in loadout or player settings
|
||||||
|
- Each skin: display_name, model_path, texture_path, color_tint
|
||||||
|
- Apply by replacing HumanoidModel mesh nodes or swapping material
|
||||||
|
- For full model replacement: load GLB scene, reparent to player
|
||||||
|
|
||||||
|
## Godot 4.2.1 Compatibility
|
||||||
|
- absf() → abs()
|
||||||
|
- fog_sky_affinity → remove
|
||||||
|
- is not Type → not (x is Type)
|
||||||
|
- lerpf → lerp
|
||||||
|
- := from untyped arrays → explicit type
|
||||||
|
- preload() on parse-error scripts → load()
|
||||||
Reference in New Issue
Block a user