# Skin Creation Workflow How to author a new character skin, register it in the game's shop/gacha catalog, and ship it to players. --- ## Overview Each skin is defined in two places that must stay in sync: | Layer | File | What it stores | |---|---|---| | **Client (visual)** | `scripts/managers/skin_manager.gd` | Mesh slots, material paths, override/overlay mode | | **Server (shop)** | `server/nakama/lua/economy.lua` | Item ID, name, category, price (gold/star) | The item `id` in `economy.lua` must match the dictionary key in `skin_manager.gd` `SKIN_CATALOG` exactly -- the Godot client looks up the item ID from the wallet/inventory and applies the matching skin data. [Back to top](#top) --- ## Step 1: Create the Skin Material Open the **Skin Shader Generator** at `res://scenes/tools/skin_shader_generator.tscn`. 1. Run the scene (F6). 2. Import your base albedo and mask textures (PNG with color/alpha channels). 3. Use the UI to visualize UV overlays and adjust color channels (Red, Green, Blue, Alpha). 4. Export the configured material as a `.tres` file into `assets/materials/skins/` or a subfolder: - `assets/characters/skins/hat/` - `assets/characters/skins/clothing/` - `assets/characters/skins/gloves/` **Material path conventions (Oldpop character):** | Category | Example path | |---|---| | hat | `res://assets/characters/skins/hat/oldpop_mat_hat_blue.tres` | | costume/clothing | `res://assets/characters/skins/clothing/oldpop_mat_cloth_red_pant.tres` | | glove | `res://assets/characters/skins/gloves/oldpop_mat_gloves_blue.tres` | | accessory | `res://assets/characters/skins/accessory/` | [Back to top](#top) --- ## Step 2: Register the Skin in SkinManager (Client) Open `res://scripts/managers/skin_manager.gd` and add a new entry inside `SKIN_CATALOG` (between `[BEGIN_SKIN_CATALOG]` and `[END_SKIN_CATALOG]` markers). ### Entry format ```gdscript "item_id": { "category": "head", # head | costume | glove | accessory "character": "Oldpop", # node name under CharacterRoot "slots": [ { "mesh": "oldpop-hat1", # MeshInstance3D child name "mode": "override", # "override" | "overlay" "material": "res://path/to/material.tres" }, ] } ``` ### Slot modes - **`override`** -- `set_surface_override_material(0, mat)`. Replaces the base material entirely. Preserves the outline shader (`next_pass`) automatically. - **`overlay`** -- `material_overlay = mat`. Transparent layer on top of the base material. Good for costume/pant patterns. ### Multi-slot skins (costume example) Costumes typically touch 3 meshes: ```gdscript "oldpop-grey-pant": { "category": "costume", "character": "Oldpop", "slots": [ { "mesh": "oldpop-body", "mode": "overlay", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" }, { "mesh": "oldpop-bottom1", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" }, { "mesh": "oldpop-bottom2", "mode": "override", "material": "res://assets/characters/skins/clothing/oldpop_mat_cloth_grey_pant.tres" }, ] }, ``` ### Tips - Leave `"material"` as `""` if the `.tres` file is not ready yet. The slot is skipped gracefully. - Use the **Skin Catalog Editor** (`res://scenes/tools/skin_catalog_editor.tscn`) to avoid manual edits. Click **Save & Generate** to rewrite both `skin_manager.gd` and `economy.lua`. [Back to top](#top) --- ## Step 3: Register the Skin in Economy (Server) Open `server/nakama/lua/economy.lua` and add a new entry to `SHOP_CATALOG_DEFS`. ### Catalog entry format ```lua { id = "oldpop-blue-hat", name = "Oldpop Blue Hat", category = "head", gold = 100, star = 0, rarity = "Common", character = "Oldpop" }, ``` | Field | Type | Description | |---|---|---| | `id` | string | Must match the `SKIN_CATALOG` key in `skin_manager.gd` exactly | | `name` | string | Display name shown in shop | | `category` | string | `head` / `costume` / `glove` / `accessory` | | `gold` | number | Gold coin price (0 = not sold for gold) | | `star` | number | Star gem price (0 = not sold for stars) | | `rarity` | string | `"Common"` / `"Uncommon"` / `"Rare"` -- cosmetic label only | | `character` | string | Character this skin belongs to (e.g. `"Oldpop"`) | ### Existing catalog (12 items) ``` oldpop-blue-hat head 100 gold Common Oldpop oldpop-green-hat head 100 gold Common Oldpop oldpop-red-hat head 100 gold Common Oldpop oldpop-yellow-hat head 100 gold Common Oldpop oldpop-og-pant costume 0 gold Common Oldpop (free) oldpop-grey-pant costume 150 gold Common Oldpop oldpop-red-pant costume 150 gold Common Oldpop oldpop-yellow-pant costume 150 gold Common Oldpop oldpop-blue-gloves glove 75 gold Common Oldpop oldpop-green-gloves glove 75 gold Common Oldpop oldpop-red-gloves glove 75 gold Common Oldpop oldpop-yellow-gloves glove 75 gold Common Oldpop ``` [Back to top](#top) --- ## Step 4 (Optional): Add Skin as Gacha Prize Gacha-only skins are registered in `server/nakama/lua/gacha.lua` inside `GACHA_DATA.real_prize_catalog`. ### Existing gacha skins (4 items) ```lua skin_gacha_rainbow_suit = { name = "Rainbow Suit", category = "costume", rarity = "real_prize", character = "" } skin_gacha_dragon_hat = { name = "Dragon Hat", category = "head", rarity = "real_prize", character = "" } skin_gacha_phantom_gloves = { name = "Phantom Gloves", category = "glove", rarity = "real_prize", character = "" } skin_gacha_neon_acc = { name = "Neon Accessory", category = "accessory", rarity = "real_prize", character = "" } ``` Gacha skins also need a `skin_manager.gd` entry (same step 2 format) and a `skin_catalog_editor` entry so `SkinManager.apply_loadout()` can render them. The server catalog is optional -- gacha skins are not sold in the shop directly. The gacha pulls these IDs from `GACHA_DATA.pools.real_prize`, so add your item_id there too. ```lua pools = { common = {"frag_common"}, uncommon = {"frag_uncommon"}, rare = {"frag_rare"}, real_prize = { "skin_gacha_rainbow_suit", "skin_gacha_dragon_hat", "skin_gacha_phantom_gloves", "skin_gacha_neon_acc", -- add new skin here } }, ``` [Back to top](#top) --- ## Step 5: Deploy ### Hot patch (content only) -- recommended for skins 1. Commit changes to `experimental` branch: - `scripts/managers/skin_manager.gd` - `server/nakama/lua/economy.lua` (if shop item) - `server/nakama/lua/gacha.lua` (if gacha prize) - Material `.tres` files 2. Push to `experimental`. 3. Trigger `deploy_patch.yml` via Gitea UI workflow dispatch. - CI runs `--export-pack` to build `patch.pck`. - CI force-pushes `patch.pck` + `version.json` to `patches` branch. - Existing players auto-download on next boot via `GameUpdateManager`. ### Full binary release (if engine/templates changed) Tag a version (e.g. `v2.5.0`) and push. CI builds all platform binaries and uploads to the release. [Back to top](#top) --- ## Full Flow Diagram ``` ┌──────────────────────┐ │ 1. Create Material │ │ skin_shader_generator │ │ ────────────────── │ │ Export .tres file │ └────────┬─────────────┘ v ┌──────────────────────────────┐ │ 2. Register in SkinManager │ │ skin_manager.gd │ │ ────────────────── │ │ Add SKIN_CATALOG entry │ │ (mesh slots + material) │ └────────┬─────────────────────┘ v ┌──────────────────────────────┐ │ 3. Register in Economy │ │ economy.lua │ │ ────────────────── │ │ Add SHOP_CATALOG_DEFS │ │ (price, name, category) │ └────────┬─────────────────────┘ v (optional) ┌──────────────────────────────┐ │ 4. Gacha Prize │ │ gacha.lua │ │ ────────────────── │ │ real_prize_catalog + pools │ └────────┬─────────────────────┘ v ┌──────────────────────┐ │ 5. Git Push & CI │ │ deploy_patch.yml │ │ ────────────────── │ │ patch.pck → players │ └──────────────────────┘ ``` [Back to top](#top) --- ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| | Skin visible in editor but not in-game | Material path wrong or `.tres` not exported | Verify `res://` path in SKIN_CATALOG, run `--export-pack` | | Skin purchase fails: "NotEnoughFunds" | Wallet balance insufficient | Check gold/star prices in economy.lua | | Skin visible on all characters | `character` field wrong | Set correct character node name | | Skin purchase fails: "ItemNotFound" | Item ID not in SHOP_CATALOG_DEFS | Add entry matching SKIN_CATALOG key | | Player downloads patch but skin missing | econmy.lua change didn't reach server | Nakama hot-reload: restart Nakama container or wait for next restart | | Outline shader lost on skin | next_pass not preserved | SkinManager preserves it automatically -- verify with latest `skin_manager.gd` | [Back to top](#top)