diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.json b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.json new file mode 100644 index 00000000..64d1e657 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.json @@ -0,0 +1,943 @@ +{ + "textures": [ + { + "image": "xingji-actors.webp", + "format": "RGBA8888", + "size": { + "w": 1104, + "h": 1130 + }, + "scale": 1, + "frames": [ + { + "filename": "hero-attack-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 224, + "h": 221 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 224, + "h": 219 + }, + "frame": { + "x": 3, + "y": 3, + "w": 224, + "h": 219 + } + }, + { + "filename": "fx-zhendi-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 190, + "h": 192 + }, + "frame": { + "x": 3, + "y": 228, + "w": 190, + "h": 192 + } + }, + { + "filename": "hero-hurt-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 187, + "h": 192 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 186, + "h": 192 + }, + "frame": { + "x": 199, + "y": 228, + "w": 186, + "h": 192 + } + }, + { + "filename": "hero-idle-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 140, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 140, + "h": 192 + }, + "frame": { + "x": 233, + "y": 3, + "w": 140, + "h": 192 + } + }, + { + "filename": "hero-idle-03", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 192, + "h": 180 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 192, + "h": 180 + }, + "frame": { + "x": 391, + "y": 3, + "w": 192, + "h": 180 + } + }, + { + "filename": "hero-idle-04", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 167, + "h": 192 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 166, + "h": 192 + }, + "frame": { + "x": 391, + "y": 189, + "w": 166, + "h": 192 + } + }, + { + "filename": "hero-run-05", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 154, + "h": 192 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 153, + "h": 192 + }, + "frame": { + "x": 3, + "y": 426, + "w": 153, + "h": 192 + } + }, + { + "filename": "hero-run-06", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 187 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 192, + "h": 186 + }, + "frame": { + "x": 162, + "y": 426, + "w": 192, + "h": 186 + } + }, + { + "filename": "hero-hurt-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 190, + "h": 192 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 188, + "h": 191 + }, + "frame": { + "x": 360, + "y": 426, + "w": 188, + "h": 191 + } + }, + { + "filename": "hero-run-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 171 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 191, + "h": 170 + }, + "frame": { + "x": 589, + "y": 3, + "w": 191, + "h": 170 + } + }, + { + "filename": "hero-run-04", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 191 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 191, + "h": 190 + }, + "frame": { + "x": 589, + "y": 179, + "w": 191, + "h": 190 + } + }, + { + "filename": "hero-idle-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 190 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 190, + "h": 190 + }, + "frame": { + "x": 554, + "y": 387, + "w": 190, + "h": 190 + } + }, + { + "filename": "hero-run-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 175 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 190, + "h": 173 + }, + "frame": { + "x": 3, + "y": 624, + "w": 190, + "h": 173 + } + }, + { + "filename": "hero-run-03", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 171 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 190, + "h": 171 + }, + "frame": { + "x": 199, + "y": 623, + "w": 190, + "h": 171 + } + }, + { + "filename": "fx-tianlei-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 117, + "h": 176 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 111, + "h": 174 + }, + "frame": { + "x": 395, + "y": 623, + "w": 111, + "h": 174 + } + }, + { + "filename": "fx-hit-fire-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 152, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 152, + "h": 160 + }, + "frame": { + "x": 512, + "y": 623, + "w": 152, + "h": 160 + } + }, + { + "filename": "fx-hit-ice-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 160, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 160, + "h": 160 + }, + "frame": { + "x": 3, + "y": 803, + "w": 160, + "h": 160 + } + }, + { + "filename": "fx-hit-thunder-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 160, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 160, + "h": 160 + }, + "frame": { + "x": 169, + "y": 803, + "w": 160, + "h": 160 + } + }, + { + "filename": "fx-icon-fuling-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 36, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 36, + "h": 160 + }, + "frame": { + "x": 335, + "y": 803, + "w": 36, + "h": 160 + } + }, + { + "filename": "fx-icon-hanyuan-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 154, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 154, + "h": 160 + }, + "frame": { + "x": 377, + "y": 803, + "w": 154, + "h": 160 + } + }, + { + "filename": "fx-icon-jinge-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 34, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 33, + "h": 160 + }, + "frame": { + "x": 537, + "y": 789, + "w": 33, + "h": 160 + } + }, + { + "filename": "fx-icon-shanzi-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 160, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 160, + "h": 160 + }, + "frame": { + "x": 576, + "y": 789, + "w": 160, + "h": 160 + } + }, + { + "filename": "fx-icon-zhendi-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 160, + "h": 145 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 160, + "h": 144 + }, + "frame": { + "x": 786, + "y": 3, + "w": 160, + "h": 144 + } + }, + { + "filename": "fx-icon-poyue-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 155, + "h": 160 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 153, + "h": 159 + }, + "frame": { + "x": 670, + "y": 583, + "w": 153, + "h": 159 + } + }, + { + "filename": "fx-icon-tianlei-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 53, + "h": 160 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 52, + "h": 159 + }, + "frame": { + "x": 750, + "y": 375, + "w": 52, + "h": 159 + } + }, + { + "filename": "fx-icon-xinghuo-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 140, + "h": 160 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 138, + "h": 159 + }, + "frame": { + "x": 829, + "y": 153, + "w": 138, + "h": 159 + } + }, + { + "filename": "fx-fuling-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 134, + "h": 160 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 133, + "h": 158 + }, + "frame": { + "x": 829, + "y": 318, + "w": 133, + "h": 158 + } + }, + { + "filename": "fx-icon-fengren-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 153, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 152, + "h": 158 + }, + "frame": { + "x": 742, + "y": 748, + "w": 152, + "h": 158 + } + }, + { + "filename": "fx-icon-fentian-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 160, + "h": 159 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 156, + "h": 158 + }, + "frame": { + "x": 3, + "y": 969, + "w": 156, + "h": 158 + } + }, + { + "filename": "fx-icon-leifu-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 159, + "h": 160 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 158, + "h": 158 + }, + "frame": { + "x": 165, + "y": 969, + "w": 158, + "h": 158 + } + }, + { + "filename": "fx-icon-xuanming-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 160, + "h": 157 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 154, + "h": 157 + }, + "frame": { + "x": 329, + "y": 969, + "w": 154, + "h": 157 + } + }, + { + "filename": "fx-fengren-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 115, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 115, + "h": 128 + }, + "frame": { + "x": 489, + "y": 969, + "w": 115, + "h": 128 + } + }, + { + "filename": "fx-hanyuan-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 120 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 120 + }, + "frame": { + "x": 829, + "y": 482, + "w": 128, + "h": 120 + } + }, + { + "filename": "fx-jinge-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 127, + "h": 128 + }, + "frame": { + "x": 829, + "y": 608, + "w": 127, + "h": 128 + } + }, + { + "filename": "fx-poyue-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 128, + "h": 56 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 128, + "h": 54 + }, + "frame": { + "x": 742, + "y": 912, + "w": 128, + "h": 54 + } + }, + { + "filename": "fx-shanzi-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 610, + "y": 972, + "w": 128, + "h": 128 + } + }, + { + "filename": "fx-xuanming-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 744, + "y": 972, + "w": 128, + "h": 128 + } + }, + { + "filename": "pickup-gem-02", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 973, + "y": 3, + "w": 128, + "h": 128 + } + }, + { + "filename": "fx-fentian-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 128, + "h": 121 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 127, + "h": 120 + }, + "frame": { + "x": 973, + "y": 137, + "w": 127, + "h": 120 + } + }, + { + "filename": "fx-leifu-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 128, + "h": 85 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 126, + "h": 81 + }, + "frame": { + "x": 973, + "y": 263, + "w": 126, + "h": 81 + } + }, + { + "filename": "pickup-orb-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 100, + "h": 112 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 99, + "h": 112 + }, + "frame": { + "x": 968, + "y": 350, + "w": 99, + "h": 112 + } + }, + { + "filename": "pickup-coin-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 84, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 83, + "h": 96 + }, + "frame": { + "x": 968, + "y": 468, + "w": 83, + "h": 96 + } + }, + { + "filename": "pickup-gem-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 95, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 95, + "h": 96 + }, + "frame": { + "x": 963, + "y": 570, + "w": 95, + "h": 96 + } + }, + { + "filename": "fx-xinghuo-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 96, + "h": 94 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 95, + "h": 93 + }, + "frame": { + "x": 962, + "y": 672, + "w": 95, + "h": 93 + } + } + ] + } + ], + "meta": { + "app": "http://github.com/odrick/free-tex-packer-cli", + "version": "0.3.0" + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.webp b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.webp new file mode 100644 index 00000000..855050fc Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-actors.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.json b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.json new file mode 100644 index 00000000..7f3ed3b4 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.json @@ -0,0 +1,628 @@ +{ + "textures": [ + { + "image": "xingji-beasts.webp", + "format": "RGBA8888", + "size": { + "w": 1051, + "h": 916 + }, + "scale": 1, + "frames": [ + { + "filename": "elite-taowu-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 224, + "h": 155 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 224, + "h": 155 + }, + "frame": { + "x": 3, + "y": 3, + "w": 224, + "h": 155 + } + }, + { + "filename": "elite-zheng-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 224, + "h": 185 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 224, + "h": 185 + }, + "frame": { + "x": 3, + "y": 164, + "w": 224, + "h": 185 + } + }, + { + "filename": "elite-qiongqi-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 224, + "h": 151 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 223, + "h": 150 + }, + "frame": { + "x": 233, + "y": 3, + "w": 223, + "h": 150 + } + }, + { + "filename": "qiongqi-attack-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 168 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 192, + "h": 167 + }, + "frame": { + "x": 233, + "y": 159, + "w": 192, + "h": 167 + } + }, + { + "filename": "qiongqi-move-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 192, + "h": 178 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 192, + "h": 178 + }, + "frame": { + "x": 3, + "y": 355, + "w": 192, + "h": 178 + } + }, + { + "filename": "qiongqi-move-02", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 191, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 191, + "h": 192 + }, + "frame": { + "x": 233, + "y": 332, + "w": 191, + "h": 192 + } + }, + { + "filename": "taowu-attack-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 192, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 192, + "h": 192 + }, + "frame": { + "x": 462, + "y": 3, + "w": 192, + "h": 192 + } + }, + { + "filename": "taowu-death-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 192, + "h": 165 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 192, + "h": 165 + }, + "frame": { + "x": 431, + "y": 201, + "w": 192, + "h": 165 + } + }, + { + "filename": "taowu-hurt-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 115 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 192, + "h": 113 + }, + "frame": { + "x": 430, + "y": 372, + "w": 192, + "h": 113 + } + }, + { + "filename": "taowu-idle-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 178 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 192, + "h": 177 + }, + "frame": { + "x": 3, + "y": 539, + "w": 192, + "h": 177 + } + }, + { + "filename": "taowu-move-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 186, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 186, + "h": 192 + }, + "frame": { + "x": 430, + "y": 491, + "w": 186, + "h": 192 + } + }, + { + "filename": "taowu-move-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 156 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 192, + "h": 155 + }, + "frame": { + "x": 201, + "y": 530, + "w": 192, + "h": 155 + } + }, + { + "filename": "zheng-death-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 130 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 192, + "h": 128 + }, + "frame": { + "x": 660, + "y": 3, + "w": 192, + "h": 128 + } + }, + { + "filename": "elite-aura-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 182, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 182, + "h": 191 + }, + "frame": { + "x": 660, + "y": 137, + "w": 182, + "h": 191 + } + }, + { + "filename": "elite-aura-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 188 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 191, + "h": 186 + }, + "frame": { + "x": 629, + "y": 334, + "w": 191, + "h": 186 + } + }, + { + "filename": "qiongqi-hurt-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 181, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 181, + "h": 191 + }, + "frame": { + "x": 3, + "y": 722, + "w": 181, + "h": 191 + } + }, + { + "filename": "qiongqi-idle-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 182, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 180, + "h": 191 + }, + "frame": { + "x": 190, + "y": 722, + "w": 180, + "h": 191 + } + }, + { + "filename": "zheng-hurt-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 164 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 191, + "h": 163 + }, + "frame": { + "x": 622, + "y": 526, + "w": 191, + "h": 163 + } + }, + { + "filename": "zheng-idle-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 176, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 176, + "h": 191 + }, + "frame": { + "x": 376, + "y": 695, + "w": 176, + "h": 191 + } + }, + { + "filename": "zheng-move-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 183, + "h": 192 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 183, + "h": 191 + }, + "frame": { + "x": 558, + "y": 695, + "w": 183, + "h": 191 + } + }, + { + "filename": "qiongqi-death-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 125 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 190, + "h": 123 + }, + "frame": { + "x": 858, + "y": 3, + "w": 190, + "h": 123 + } + }, + { + "filename": "zheng-attack-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 147 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 190, + "h": 146 + }, + "frame": { + "x": 858, + "y": 132, + "w": 190, + "h": 146 + } + }, + { + "filename": "zheng-move-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 192, + "h": 163 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 187, + "h": 162 + }, + "frame": { + "x": 848, + "y": 284, + "w": 187, + "h": 162 + } + }, + { + "filename": "affix-flame-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 91, + "h": 96 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 89, + "h": 96 + }, + "frame": { + "x": 747, + "y": 695, + "w": 89, + "h": 96 + } + }, + { + "filename": "affix-frost-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 89, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 89, + "h": 96 + }, + "frame": { + "x": 747, + "y": 797, + "w": 89, + "h": 96 + } + }, + { + "filename": "affix-swift-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 95, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 95, + "h": 96 + }, + "frame": { + "x": 819, + "y": 526, + "w": 95, + "h": 96 + } + }, + { + "filename": "affix-thunder-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 96, + "h": 96 + }, + "frame": { + "x": 920, + "y": 452, + "w": 96, + "h": 96 + } + }, + { + "filename": "affix-venom-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 95, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 95, + "h": 96 + }, + "frame": { + "x": 920, + "y": 554, + "w": 95, + "h": 96 + } + }, + { + "filename": "affix-armor-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 96, + "h": 94 + }, + "spriteSourceSize": { + "x": 6, + "y": 3, + "w": 90, + "h": 91 + }, + "frame": { + "x": 842, + "y": 656, + "w": 90, + "h": 91 + } + } + ] + } + ], + "meta": { + "app": "http://github.com/odrick/free-tex-packer-cli", + "version": "0.3.0" + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.webp b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.webp new file mode 100644 index 00000000..0cae2c62 Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-beasts.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.json b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.json new file mode 100644 index 00000000..2e485694 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.json @@ -0,0 +1,481 @@ +{ + "textures": [ + { + "image": "xingji-boss.webp", + "format": "RGBA8888", + "size": { + "w": 1692, + "h": 1557 + }, + "scale": 1, + "frames": [ + { + "filename": "luwu-portrait-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 384, + "h": 375 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 384, + "h": 373 + }, + "frame": { + "x": 3, + "y": 3, + "w": 384, + "h": 373 + } + }, + { + "filename": "paoxiao-portrait-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 384, + "h": 340 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 382, + "h": 338 + }, + "frame": { + "x": 3, + "y": 382, + "w": 382, + "h": 338 + } + }, + { + "filename": "luwu-attack-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 320 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 320 + }, + "frame": { + "x": 393, + "y": 3, + "w": 320, + "h": 320 + } + }, + { + "filename": "luwu-attack-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 320 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 320, + "h": 319 + }, + "frame": { + "x": 393, + "y": 329, + "w": 320, + "h": 319 + } + }, + { + "filename": "luwu-attack-03", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 292 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 320, + "h": 290 + }, + "frame": { + "x": 719, + "y": 3, + "w": 320, + "h": 290 + } + }, + { + "filename": "luwu-hurt-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 320 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 320 + }, + "frame": { + "x": 719, + "y": 299, + "w": 320, + "h": 320 + } + }, + { + "filename": "luwu-move-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 301 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 320, + "h": 300 + }, + "frame": { + "x": 719, + "y": 625, + "w": 320, + "h": 300 + } + }, + { + "filename": "luwu-move-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 311 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 309 + }, + "frame": { + "x": 391, + "y": 654, + "w": 320, + "h": 309 + } + }, + { + "filename": "luwu-roar-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 313, + "h": 320 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 313, + "h": 320 + }, + "frame": { + "x": 717, + "y": 931, + "w": 313, + "h": 320 + } + }, + { + "filename": "paoxiao-attack-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 316 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 316 + }, + "frame": { + "x": 3, + "y": 726, + "w": 320, + "h": 316 + } + }, + { + "filename": "paoxiao-attack-03", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 308 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 308 + }, + "frame": { + "x": 329, + "y": 969, + "w": 320, + "h": 308 + } + }, + { + "filename": "paoxiao-death-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 227 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 320, + "h": 224 + }, + "frame": { + "x": 3, + "y": 1048, + "w": 320, + "h": 224 + } + }, + { + "filename": "paoxiao-hurt-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 267 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 267 + }, + "frame": { + "x": 1045, + "y": 3, + "w": 320, + "h": 267 + } + }, + { + "filename": "paoxiao-move-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 316 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 316 + }, + "frame": { + "x": 1045, + "y": 276, + "w": 320, + "h": 316 + } + }, + { + "filename": "paoxiao-move-02", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 320, + "h": 312 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 320, + "h": 312 + }, + "frame": { + "x": 1045, + "y": 598, + "w": 320, + "h": 312 + } + }, + { + "filename": "paoxiao-roar-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 301 + }, + "spriteSourceSize": { + "x": 0, + "y": 2, + "w": 320, + "h": 299 + }, + "frame": { + "x": 1045, + "y": 916, + "w": 320, + "h": 299 + } + }, + { + "filename": "paoxiao-death-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 278 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 319, + "h": 276 + }, + "frame": { + "x": 3, + "y": 1278, + "w": 319, + "h": 276 + } + }, + { + "filename": "luwu-death-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 307 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 318, + "h": 307 + }, + "frame": { + "x": 1036, + "y": 1221, + "w": 318, + "h": 307 + } + }, + { + "filename": "luwu-death-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 273 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 318, + "h": 271 + }, + "frame": { + "x": 655, + "y": 1257, + "w": 318, + "h": 271 + } + }, + { + "filename": "luwu-idle-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 254 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 318, + "h": 254 + }, + "frame": { + "x": 328, + "y": 1283, + "w": 318, + "h": 254 + } + }, + { + "filename": "paoxiao-attack-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 300 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 318, + "h": 300 + }, + "frame": { + "x": 1371, + "y": 3, + "w": 318, + "h": 300 + } + }, + { + "filename": "paoxiao-idle-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 320, + "h": 289 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 318, + "h": 286 + }, + "frame": { + "x": 1371, + "y": 309, + "w": 318, + "h": 286 + } + } + ] + } + ], + "meta": { + "app": "http://github.com/odrick/free-tex-packer-cli", + "version": "0.3.0" + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.webp b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.webp new file mode 100644 index 00000000..f0a4e2eb Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.json b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.json new file mode 100644 index 00000000..965f1387 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.json @@ -0,0 +1,271 @@ +{ + "textures": [ + { + "image": "xingji-map1.webp", + "format": "RGBA8888", + "size": { + "w": 719, + "h": 738 + }, + "scale": 1, + "frames": [ + { + "filename": "event-rift-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 208, + "h": 204 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 208, + "h": 202 + }, + "frame": { + "x": 3, + "y": 3, + "w": 208, + "h": 202 + } + }, + { + "filename": "deco-bone-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 176, + "h": 167 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 176, + "h": 165 + }, + "frame": { + "x": 3, + "y": 211, + "w": 176, + "h": 165 + } + }, + { + "filename": "deco-rock-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 174, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 174, + "h": 176 + }, + "frame": { + "x": 217, + "y": 3, + "w": 174, + "h": 176 + } + }, + { + "filename": "deco-totem-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 129, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 176 + }, + "frame": { + "x": 217, + "y": 185, + "w": 128, + "h": 176 + } + }, + { + "filename": "deco-totem-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 171, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 170, + "h": 176 + }, + "frame": { + "x": 3, + "y": 382, + "w": 170, + "h": 176 + } + }, + { + "filename": "event-chest-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 157, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 157, + "h": 176 + }, + "frame": { + "x": 179, + "y": 382, + "w": 157, + "h": 176 + } + }, + { + "filename": "deco-reed-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 166, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 165, + "h": 175 + }, + "frame": { + "x": 397, + "y": 3, + "w": 165, + "h": 175 + } + }, + { + "filename": "deco-rock-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 174, + "h": 176 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 172, + "h": 175 + }, + "frame": { + "x": 342, + "y": 367, + "w": 172, + "h": 175 + } + }, + { + "filename": "deco-runestone-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 176, + "h": 169 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 175, + "h": 167 + }, + "frame": { + "x": 351, + "y": 185, + "w": 175, + "h": 167 + } + }, + { + "filename": "event-spring-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 176, + "h": 171 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 175, + "h": 171 + }, + "frame": { + "x": 3, + "y": 564, + "w": 175, + "h": 171 + } + }, + { + "filename": "deco-moss-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 171, + "h": 176 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 169, + "h": 174 + }, + "frame": { + "x": 342, + "y": 548, + "w": 169, + "h": 174 + } + }, + { + "filename": "event-shrine-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 148, + "h": 176 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 148, + "h": 174 + }, + "frame": { + "x": 568, + "y": 3, + "w": 148, + "h": 174 + } + } + ] + } + ], + "meta": { + "app": "http://github.com/odrick/free-tex-packer-cli", + "version": "0.3.0" + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.webp b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.webp new file mode 100644 index 00000000..b09427a2 Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.json b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.json new file mode 100644 index 00000000..c0558f89 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.json @@ -0,0 +1,271 @@ +{ + "textures": [ + { + "image": "xingji-ui.webp", + "format": "RGBA8888", + "size": { + "w": 911, + "h": 846 + }, + "scale": 1, + "frames": [ + { + "filename": "ui-bar-boss-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 512, + "h": 164 + }, + "spriteSourceSize": { + "x": 1, + "y": 2, + "w": 510, + "h": 161 + }, + "frame": { + "x": 3, + "y": 3, + "w": 510, + "h": 161 + } + }, + { + "filename": "ui-bar-frame-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 512, + "h": 105 + }, + "spriteSourceSize": { + "x": 2, + "y": 2, + "w": 508, + "h": 101 + }, + "frame": { + "x": 3, + "y": 170, + "w": 508, + "h": 101 + } + }, + { + "filename": "ui-card-frame-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 277, + "h": 384 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 277, + "h": 384 + }, + "frame": { + "x": 3, + "y": 277, + "w": 277, + "h": 384 + } + }, + { + "filename": "ui-card-frame-02", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 277, + "h": 384 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 277, + "h": 384 + }, + "frame": { + "x": 286, + "y": 277, + "w": 277, + "h": 384 + } + }, + { + "filename": "ui-btn-02", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 384, + "h": 177 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 383, + "h": 176 + }, + "frame": { + "x": 3, + "y": 667, + "w": 383, + "h": 176 + } + }, + { + "filename": "ui-btn-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 384, + "h": 173 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 382, + "h": 171 + }, + "frame": { + "x": 392, + "y": 667, + "w": 382, + "h": 171 + } + }, + { + "filename": "ui-joystick-base-01", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 184, + "h": 192 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 180, + "h": 192 + }, + "frame": { + "x": 569, + "y": 3, + "w": 180, + "h": 192 + } + }, + { + "filename": "ui-badge-tide-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 569, + "y": 201, + "w": 128, + "h": 128 + } + }, + { + "filename": "ui-badge-wave-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 569, + "y": 335, + "w": 128, + "h": 128 + } + }, + { + "filename": "ui-orb-hp-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 569, + "y": 469, + "w": 128, + "h": 128 + } + }, + { + "filename": "ui-slot-item-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 128, + "h": 128 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 128, + "h": 128 + }, + "frame": { + "x": 780, + "y": 3, + "w": 128, + "h": 128 + } + }, + { + "filename": "ui-joystick-knob-01", + "rotated": false, + "trimmed": false, + "sourceSize": { + "w": 96, + "h": 96 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 96, + "h": 96 + }, + "frame": { + "x": 780, + "y": 137, + "w": 96, + "h": 96 + } + } + ] + } + ], + "meta": { + "app": "http://github.com/odrick/free-tex-packer-cli", + "version": "0.3.0" + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.webp b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.webp new file mode 100644 index 00000000..13aff189 Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/bg/bg-map1-vista-01.webp b/game-runtime/games/shanhai-xingji/assets/bg/bg-map1-vista-01.webp new file mode 100644 index 00000000..4afa0c6a Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/bg/bg-map1-vista-01.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/bg/ui-panel-sortie-01.webp b/game-runtime/games/shanhai-xingji/assets/bg/ui-panel-sortie-01.webp new file mode 100644 index 00000000..33532c38 Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/bg/ui-panel-sortie-01.webp differ diff --git a/game-runtime/games/shanhai-xingji/assets/manifest.json b/game-runtime/games/shanhai-xingji/assets/manifest.json new file mode 100644 index 00000000..47491ba3 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/assets/manifest.json @@ -0,0 +1,77 @@ +{ + "batch": "xingji-a1", + "note": "《山海行纪》资产一期 manifest:图集/底图/tile 表与字节账。命名 role-state-frame;帧坐标在各图集同名 json(Phaser3 版式,LittleJS 按 frames 换算)。", + "anchor": "../../../docs-design/山海风格版-批次一/anchor-pack.json", + "styleDiscipline": "styleWords 单源=锚包 r1;生图台账(逐呼 prompt/seed/成败)=同目录 manifest.jsonl,batch=xingji-a1", + "generatedAt": "2026-07-07T07:31:00.125Z", + "atlases": { + "xingji-map1": [ + { + "page": "xingji-map1.webp", + "json": "xingji-map1.json", + "frames": 12, + "webpBytes": 142472 + } + ], + "xingji-beasts": [ + { + "page": "xingji-beasts.webp", + "json": "xingji-beasts.json", + "frames": 29, + "webpBytes": 443840 + } + ], + "xingji-boss": [ + { + "page": "xingji-boss.webp", + "json": "xingji-boss.json", + "frames": 22, + "webpBytes": 1057434 + } + ], + "xingji-ui": [ + { + "page": "xingji-ui.webp", + "json": "xingji-ui.json", + "frames": 12, + "webpBytes": 202748 + } + ], + "xingji-actors": [ + { + "page": "xingji-actors.webp", + "json": "xingji-actors.json", + "frames": 44, + "webpBytes": 470310 + } + ] + }, + "tiles": { + "file": "tiles/tiles-map1.webp", + "tileSize": 128, + "cols": 8, + "rows": 6, + "order": "a=0-15 基底 / b=16-31 暗变奏 / c=32-47 刻痕变奏(索引=行×8+列)", + "bytes": 240842 + }, + "bg": [ + { + "id": "ui-panel-sortie-01", + "file": "bg/ui-panel-sortie-01.webp", + "bytes": 135152, + "adopted": false + }, + { + "id": "bg-map1-vista-01", + "file": "bg/bg-map1-vista-01.webp", + "bytes": 164100, + "adopted": true + } + ], + "bytes": { + "atlasWebp": 2316804, + "tilesWebp": 240842, + "bgWebp": 299252, + "total": 2856898 + } +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/assets/tiles/tiles-map1.webp b/game-runtime/games/shanhai-xingji/assets/tiles/tiles-map1.webp new file mode 100644 index 00000000..bc538e77 Binary files /dev/null and b/game-runtime/games/shanhai-xingji/assets/tiles/tiles-map1.webp differ diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-atlas.mjs b/game-runtime/games/shanhai-xingji/scripts/asset-atlas.mjs new file mode 100644 index 00000000..057d7984 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-atlas.mjs @@ -0,0 +1,149 @@ +#!/usr/bin/env node +// 《山海行纪》资产批产 · 图集打包 + 底图转码 + 资产 manifest(迁移自批次一 atlas-pack.mjs) +// 职责: +// ① keyed/{group}/*.png 按消费分组打 2048² 图集(free-tex-packer,MaxRects,trim), +// png 中转 → webp q80 落 assets/atlas/xingji-{group}*.webp + 同名坐标 json(image 指 webp); +// ② bg 组镜头(整幅底图)不打包,直接转码 webp q80 落 assets/bg/{id}.webp; +// ③ 汇总写 assets/manifest.json:图集分页/帧数/字节 + tiles 几何 + bg 清单 + 锚包指针 +// ——图集字节数即两本账(契约包内账/首屏传输账)的实测输入。 +// 用法: node scripts/asset-atlas.mjs [--group beasts] (环境变量 ASSET_WORK 同 asset-gen.mjs) +import { readFileSync, writeFileSync, readdirSync, mkdirSync, statSync, existsSync, rmSync, copyFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const BATCH1REL = '../../../docs-design/山海风格版-批次一'; // 锚包单源(相对本游戏根,仅指针不复制) +const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work'); +const KEYED = join(WORK, 'keyed'); +const ATLAS_DIR = join(GAME, 'assets', 'atlas'); +const BG_DIR = join(GAME, 'assets', 'bg'); +const onlyGroup = process.argv.includes('--group') ? process.argv[process.argv.indexOf('--group') + 1] : null; + +const { shots, batch } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8')); +// boss 独立成组:双 Boss 10/20 分钟才登场,是天然的懒加载分包面(首屏账不背它) +const GROUPS = ['map1', 'beasts', 'boss', 'ui', 'actors']; +mkdirSync(ATLAS_DIR, { recursive: true }); +mkdirSync(BG_DIR, { recursive: true }); + +// PIL 转码 webp q80(与批次一同法:free-tex-packer 不直出 webp) +function toWebp(src, dest, quality = 80) { + execFileSync('python3', ['-c', + `from PIL import Image\nImage.open(${JSON.stringify(src)}).save(${JSON.stringify(dest)}, quality=${quality}, method=6)`, + ], { stdio: 'inherit', timeout: 60_000 }); +} + +const manifest = { + batch, + note: '《山海行纪》资产一期 manifest:图集/底图/tile 表与字节账。命名 role-state-frame;帧坐标在各图集同名 json(Phaser3 版式,LittleJS 按 frames 换算)。', + anchor: `${BATCH1REL}/anchor-pack.json`, + styleDiscipline: 'styleWords 单源=锚包 r1;生图台账(逐呼 prompt/seed/成败)=同目录 manifest.jsonl,batch=xingji-a1', + generatedAt: new Date().toISOString(), + atlases: {}, tiles: null, bg: [], bytes: { atlasWebp: 0, tilesWebp: 0, bgWebp: 0, total: 0 }, +}; + +// 断点续跑语义:局部重打包(--group / 工作区缺该组 keyed)时,未重打的组与缺源 bg 从既有 +// manifest 继承盘上产物,避免局部跑整写 manifest 时丢掉其余图集条目(工作区是临时目录, +// 断点恢复后常常只需重打单组;继承字节以实盘 statSync 为准,防账实漂移)。 +const mfPath = join(GAME, 'assets', 'manifest.json'); +const prevManifest = existsSync(mfPath) ? JSON.parse(readFileSync(mfPath, 'utf8')) : null; +function carryGroup(group, why) { + const name = `xingji-${group}`; + const prev = prevManifest?.atlases?.[name]; + if (!prev) { console.log(`[${group}] ${why},无既有 manifest 条目可继承,跳过`); return; } + const pages = prev.filter(p => existsSync(join(ATLAS_DIR, p.page)) && existsSync(join(ATLAS_DIR, p.json))); + if (pages.length < prev.length) console.warn(`[${group}] 继承时 ${prev.length - pages.length} 页盘上缺失,已丢弃(机检 C4 会兜)`); + if (!pages.length) return; + for (const p of pages) { p.webpBytes = statSync(join(ATLAS_DIR, p.page)).size; manifest.bytes.atlasWebp += p.webpBytes; } + manifest.atlases[name] = pages; + console.log(`[${group}] ${why},继承既有产物(${pages.length} 页,不重编码)`); +} + +// ---- ① 分组图集 ---- +for (const group of GROUPS) { + if (onlyGroup && group !== onlyGroup) { carryGroup(group, `--group=${onlyGroup} 局部跑`); continue; } + const inDir = join(KEYED, group); + if (!existsSync(inDir)) { carryGroup(group, '无 keyed 输入'); continue; } + const pngs = readdirSync(inDir).filter(f => f.endsWith('.png')); + if (!pngs.length) { carryGroup(group, 'keyed 目录为空'); continue; } + const name = `xingji-${group}`; + const packTmp = join(WORK, 'atlas-tmp', group); + rmSync(packTmp, { recursive: true, force: true }); + mkdirSync(packTmp, { recursive: true }); + // free-tex-packer 工程文件(schema 核自 free-tex-packer-cli@0.3.0,承批次一) + const project = { + savePath: packTmp, + images: pngs.map(f => ({ name: f, path: join(inDir, f) })), + folders: [], + packOptions: { + textureName: name, width: 2048, height: 2048, fixedSize: false, powerOfTwo: false, + padding: 2, extrude: 1, allowRotation: false, detectIdentical: true, + allowTrim: true, trimMode: 'trim', alphaThreshold: 0, + exporter: 'Phaser3', removeFileExtension: true, prependFolderName: false, + textureFormat: 'png', base64Export: false, scale: 1, packer: 'MaxRectsPacker', + }, + }; + const proj = join(packTmp, `${name}.ftpp`); + writeFileSync(proj, JSON.stringify(project, null, 2)); + execFileSync('free-tex-packer-cli', ['--project', proj, '--output', packTmp], { stdio: 'inherit', timeout: 120_000 }); + + const pages = readdirSync(packTmp).filter(f => f.startsWith(name) && f.endsWith('.png')); + const entry = []; + for (const png of pages.sort()) { + const base = png.replace(/\.png$/, ''); + const webp = join(ATLAS_DIR, `${base}.webp`); + toWebp(join(packTmp, png), webp); + const jsonSrc = join(packTmp, `${base}.json`); + const j = JSON.parse(readFileSync(jsonSrc, 'utf8')); + for (const t of j.textures || []) if (t.image) t.image = t.image.replace(/\.png$/, '.webp'); + const jsonDest = join(ATLAS_DIR, `${base}.json`); + writeFileSync(jsonDest, JSON.stringify(j, null, 2)); + const frames = (j.textures || []).reduce((s, t) => s + (t.frames?.length || 0), 0); + const bytes = statSync(webp).size; + manifest.bytes.atlasWebp += bytes; + entry.push({ page: `${base}.webp`, json: `${base}.json`, frames, webpBytes: bytes }); + console.log(`[${group}] 页 ${base}:${frames} 帧 | webp ${(bytes / 1024).toFixed(0)}KB`); + } + manifest.atlases[name] = entry; +} + +// ---- ② bg 底图直转码 ---- +const RAW = join(WORK, 'raw'); +const BATCH1 = resolve(GAME, BATCH1REL); +for (const s of shots.filter(x => x.group === 'bg')) { + let src = null; + if (s.adopt) src = join(BATCH1, s.adopt); + else for (const ext of ['.jpg', '.jpeg', '.png']) { const p = join(RAW, s.id + ext); if (existsSync(p)) { src = p; break; } } + if (!src || !existsSync(src)) { + // 缺源(gen 原片在已清工作区)时继承盘上既有 bg 产物,不整写丢账 + const prev = prevManifest?.bg?.find(b => b.id === s.id); + const kept = prev && existsSync(join(GAME, prev.file)); + if (kept) { + prev.bytes = statSync(join(GAME, prev.file)).size; + manifest.bytes.bgWebp += prev.bytes; + manifest.bg.push(prev); + console.log(`[bg] ${s.id} 缺源,继承既有产物(${(prev.bytes / 1024).toFixed(0)}KB)`); + } else console.log(`[bg] ${s.id} 缺源,跳过`); + continue; + } + const dest = join(BG_DIR, `${s.id}.webp`); + toWebp(src, dest, 72); // 整幅底图走 q72:全屏铺底件,细节容忍度高于精灵 + const bytes = statSync(dest).size; + manifest.bytes.bgWebp += bytes; + manifest.bg.push({ id: s.id, file: `bg/${s.id}.webp`, bytes, adopted: !!s.adopt }); + console.log(`[bg] ${s.id} → webp ${(bytes / 1024).toFixed(0)}KB${s.adopt ? '(收编批次一)' : ''}`); +} + +// ---- ③ tiles 几何登记(产物由 asset-tiles.py 生成)---- +const tilesWebp = join(GAME, 'assets', 'tiles', 'tiles-map1.webp'); +if (existsSync(tilesWebp)) { + const bytes = statSync(tilesWebp).size; + manifest.tiles = { file: 'tiles/tiles-map1.webp', tileSize: 128, cols: 8, rows: 6, + order: 'a=0-15 基底 / b=16-31 暗变奏 / c=32-47 刻痕变奏(索引=行×8+列)', bytes }; + manifest.bytes.tilesWebp = bytes; +} + +manifest.bytes.total = manifest.bytes.atlasWebp + manifest.bytes.tilesWebp + manifest.bytes.bgWebp; +writeFileSync(join(GAME, 'assets', 'manifest.json'), JSON.stringify(manifest, null, 2)); +console.log(`\nassets/manifest.json 已写:图集 ${(manifest.bytes.atlasWebp / 1024).toFixed(0)}KB + tiles ${(manifest.bytes.tilesWebp / 1024).toFixed(0)}KB + bg ${(manifest.bytes.bgWebp / 1024).toFixed(0)}KB = 合计 ${(manifest.bytes.total / 1024).toFixed(0)}KB(webp,两本账实测输入)`); diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-check.mjs b/game-runtime/games/shanhai-xingji/scripts/asset-check.mjs new file mode 100644 index 00000000..00141a76 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-check.mjs @@ -0,0 +1,109 @@ +#!/usr/bin/env node +// 《山海行纪》资产批产 · 机检门(纪律:每批机检 尺寸/透明底/命名,不过门不入图集账) +// 检查面: +// C1 命名:全部镜头 id 符合 role-state-frame(^[a-z0-9]+(-[a-z0-9]+)*-\d{2}$); +// C2 keyed 覆盖:sprite 镜头逐一有 keyed png,尺寸 ≤ pack 上限; +// C3 透明底:keyed png 为 RGBA 且边框 alpha≈0、内容非空(python/PIL 批查,单进程); +// C4 图集对账:每个 sprite 镜头的 id 在其分组图集 json 里恰有一帧;页尺寸 ≤2048²; +// C5 产物在位:tiles 表几何正确(1024×768)、bg webp 在位、assets/manifest.json 字节账与实盘一致。 +// 用法: node scripts/asset-check.mjs [--stage keyed|full](默认 full;keyed=生图后图集前的中检) +import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work'); +const KEYED = join(WORK, 'keyed'); +const stage = process.argv.includes('--stage') ? process.argv[process.argv.indexOf('--stage') + 1] : 'full'; +const { shots } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8')); + +const errs = [], warns = []; +const sprites = shots.filter(s => s.type === 'sprite'); + +// C1 命名纪律 +const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*-\d{2}$/; +for (const s of shots) if (!NAME_RE.test(s.id)) errs.push(`C1 命名违例:${s.id}`); + +// C2 keyed 覆盖 + 尺寸上限(PNG IHDR 直读宽高,无依赖) +function pngInfo(p) { + const b = readFileSync(p); + return { w: b.readUInt32BE(16), h: b.readUInt32BE(20), colorType: b[25] }; +} +// keyed 中间件只活在批产工作区(ASSET_WORK,不入仓):整目录缺席=从仓库态复跑(CI/验收), +// C2/C3 是生产期中检、无对象可查,降级为警示跳过;部分缺席才是真不一致,仍判错。 +const keyedPaths = {}; +const keyedAbsent = !existsSync(KEYED); +if (keyedAbsent) warns.push('C2/C3 跳过:keyed 工作区不在(生产期中检项;仓库态审计走 C1/C4/C5)'); +for (const s of keyedAbsent ? [] : sprites) { + const p = join(KEYED, s.group, `${s.id}.png`); + if (!existsSync(p)) { errs.push(`C2 缺 keyed:${s.group}/${s.id}.png`); continue; } + keyedPaths[s.id] = p; + const { w, h, colorType } = pngInfo(p); + if (s.pack && Math.max(w, h) > s.pack) errs.push(`C2 超 pack 上限:${s.id} ${w}x${h} > ${s.pack}`); + if (colorType !== 6) errs.push(`C3 非 RGBA(colorType=${colorType}):${s.id}`); +} + +// C3 透明底与内容非空(单个 python 进程批查全部 keyed) +if (Object.keys(keyedPaths).length) { + const py = ` +import sys, json +from PIL import Image +import numpy as np +out = {} +for sid, p in json.load(open(sys.argv[1])).items(): + a = np.asarray(Image.open(p).convert('RGBA'))[:, :, 3] + border = np.concatenate([a[0, :], a[-1, :], a[:, 0], a[:, -1]]).astype(float) + out[sid] = { 'borderMax': int(border.max()), 'opaquePct': float((a > 16).mean()) } +print(json.dumps(out))`; + const tmpJson = join(WORK, 'check-keyed.json'); + writeFileSync(tmpJson, JSON.stringify(keyedPaths)); + const res = JSON.parse(execFileSync('python3', ['-c', py, tmpJson], { encoding: 'utf8', timeout: 120_000 })); + for (const [sid, r] of Object.entries(res)) { + if (r.borderMax > 48) warns.push(`C3 边框 alpha 偏高(${r.borderMax}):${sid}(主体可能贴边/裁切)`); + if (r.opaquePct < 0.02) errs.push(`C3 内容近空(不透明占比 ${(r.opaquePct * 100).toFixed(1)}%):${sid}`); + if (r.opaquePct > 0.98) warns.push(`C3 几乎无透明(占比 ${(r.opaquePct * 100).toFixed(1)}%):${sid}(疑似抠图失败整幅保留)`); + } +} + +if (stage === 'full') { + // C4 图集对账 + const ATLAS_DIR = join(GAME, 'assets', 'atlas'); + const frameOwner = {}; + if (existsSync(ATLAS_DIR)) { + for (const f of readdirSync(ATLAS_DIR).filter(f => f.endsWith('.json'))) { + const j = JSON.parse(readFileSync(join(ATLAS_DIR, f), 'utf8')); + for (const t of j.textures || []) { + if (t.size && (t.size.w > 2048 || t.size.h > 2048)) errs.push(`C4 页超 2048²:${f} ${t.size.w}x${t.size.h}`); + for (const fr of t.frames || []) frameOwner[fr.filename] = (frameOwner[fr.filename] || 0) + 1; + } + } + } + for (const s of sprites) { + const n = frameOwner[s.id] || 0; + if (n === 0) errs.push(`C4 图集缺帧:${s.id}`); + else if (n > 1) errs.push(`C4 帧重复 ${n} 处:${s.id}`); + } + // C5 产物在位 + 字节账对账 + const tiles = join(GAME, 'assets', 'tiles', 'tiles-map1.webp'); + if (!existsSync(tiles)) errs.push('C5 缺 tiles-map1.webp'); + for (const s of shots.filter(x => x.group === 'bg')) { + if (!existsSync(join(GAME, 'assets', 'bg', `${s.id}.webp`))) errs.push(`C5 缺 bg:${s.id}.webp`); + } + const mfPath = join(GAME, 'assets', 'manifest.json'); + if (!existsSync(mfPath)) errs.push('C5 缺 assets/manifest.json'); + else { + const mf = JSON.parse(readFileSync(mfPath, 'utf8')); + let actual = 0; + for (const pages of Object.values(mf.atlases || {})) for (const p of pages) actual += statSync(join(ATLAS_DIR, p.page)).size; + if (mf.tiles && existsSync(tiles)) actual += statSync(tiles).size; + for (const b of mf.bg || []) actual += statSync(join(GAME, 'assets', b.file)).size; + if (actual !== mf.bytes.total) errs.push(`C5 字节账不符:manifest ${mf.bytes.total} vs 实盘 ${actual}`); + } +} + +for (const w of warns) console.log(`⚠ ${w}`); +for (const e of errs) console.error(`✗ ${e}`); +console.log(`\n机检(${stage}):${errs.length === 0 ? '全绿' : errs.length + ' 项不过'} / 警示 ${warns.length} 项 / sprite ${sprites.length} 镜头`); +process.exit(errs.length ? 1 : 0); diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-contact-sheet.py b/game-runtime/games/shanhai-xingji/scripts/asset-contact-sheet.py new file mode 100644 index 00000000..f61fb2fe --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-contact-sheet.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# 《山海行纪》资产批产 · 拼板图(迁移自批次一 contact-sheet.py;眼验入口) +# 用法: python3 scripts/asset-contact-sheet.py <输出.png> [--prio P1] [--group beasts] [--cols 8] [--title 标题] [--raw] +# python3 scripts/asset-contact-sheet.py --files <输出.png> <标题> <标签=路径>... +# 默认取 keyed 后精灵(所见即入图集帧);--raw 取生图原片;bleed 镜头(tiles/bg)始终取原片。 +# 中文标题字体:环境变量 FONT_TTF 或批次一同款霞鹜文楷(OFL)。 +import sys, os, json, tempfile +from PIL import Image, ImageDraw, ImageFont + +GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work') +BATCH1 = os.path.normpath(os.path.join(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一')) +FONT_TTF = os.environ.get('FONT_TTF') or '/private/tmp/claude-501/-Users-lili-Project-games-development-ai/69248bd4-16ac-4906-aea1-4157edf7b10f/scratchpad/deps/LXGWWenKai-Regular.ttf' + +def load_font(size): + try: return ImageFont.truetype(FONT_TTF, size) + except Exception: return ImageFont.load_default() + +def make_sheet(items, out_path, cols, title, tile_w=300, tile_h=330): + label_h, title_h, gap = 26, 52, 8 + rows = (len(items) + cols - 1) // cols + W = cols * (tile_w + gap) + gap + H = title_h + rows * (tile_h + label_h + gap) + gap + sheet = Image.new('RGB', (W, H), (30, 34, 32)) + d = ImageDraw.Draw(sheet) + d.text((gap + 4, 10), title, fill=(232, 197, 106), font=load_font(28)) + f_label = load_font(17) + for i, (label, path) in enumerate(items): + r, c = divmod(i, cols) + x = gap + c * (tile_w + gap) + y = title_h + r * (tile_h + label_h + gap) + try: + im = Image.open(path) + if im.mode == 'RGBA': # 透明底铺浅格,眼验抠图质量 + bgc = Image.new('RGB', im.size, (58, 62, 58)) + bgc.paste(im, (0, 0), im) + im = bgc + else: + im = im.convert('RGB') + except Exception as e: + d.text((x, y), f'{label}: 打开失败 {e}', fill=(200, 80, 80), font=f_label); continue + im.thumbnail((tile_w, tile_h)) + sheet.paste(im, (x + (tile_w - im.width) // 2, y + (tile_h - im.height) // 2)) + d.rectangle([x, y + tile_h, x + tile_w, y + tile_h + label_h], fill=(20, 23, 21)) + d.text((x + 5, y + tile_h + 3), label, fill=(220, 220, 210), font=f_label) + sheet.save(out_path) + print(f'拼板完成:{out_path} ({W}x{H}, {len(items)} 格, {os.path.getsize(out_path)//1024}KB)') + +def locate(shot, prefer_raw): + """镜头 → 现存图:keyed 优先(sprite),bleed/raw 回原片,adopt 回批次一源。""" + cands = [] + if shot['type'] == 'sprite' and not prefer_raw: + cands.append(os.path.join(WORK, 'keyed', shot['group'], shot['id'] + '.png')) + for ext in ('.jpg', '.jpeg', '.png', '.webp'): + cands.append(os.path.join(WORK, 'raw', shot['id'] + ext)) + if shot.get('adopt'): + cands.append(os.path.join(BATCH1, shot['adopt'])) + for p in cands: + if os.path.exists(p): return p + return None + +if __name__ == '__main__': + if len(sys.argv) > 1 and sys.argv[1] == '--files': + out, title = sys.argv[2], sys.argv[3] + items = [(kv.split('=', 1)[0], kv.split('=', 1)[1]) for kv in sys.argv[4:]] + make_sheet(items, out, cols=min(6, max(1, len(items))), title=title) + sys.exit(0) + out = sys.argv[1] + argv = sys.argv[2:] + prio = group = None; cols = 8; title = '山海行纪 · 资产一期'; raw = False + i = 0 + while i < len(argv): + if argv[i] == '--prio': prio = argv[i+1]; i += 2 + elif argv[i] == '--group': group = argv[i+1]; i += 2 + elif argv[i] == '--cols': cols = int(argv[i+1]); i += 2 + elif argv[i] == '--title': title = argv[i+1]; i += 2 + elif argv[i] == '--raw': raw = True; i += 1 + else: sys.exit(f'未知参数 {argv[i]}') + shots = json.load(open(os.path.join(GAME, 'scripts', 'asset-shots.json'), encoding='utf-8'))['shots'] + items, missing = [], [] + for s in shots: + if prio and s['prio'] != prio: continue + if group and s['group'] != group: continue + p = locate(s, raw) + if p: items.append((s['id'], p)) + else: missing.append(s['id']) + if missing: print(f"缺图 {len(missing)}:{','.join(missing)}") + make_sheet(items, out, cols=cols, title=title) diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-gen.mjs b/game-runtime/games/shanhai-xingji/scripts/asset-gen.mjs new file mode 100644 index 00000000..318c219a --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-gen.mjs @@ -0,0 +1,115 @@ +#!/usr/bin/env node +// 《山海行纪》资产批产 · 生图批跑(迁移自 docs-design/山海风格版-批次一/scripts/gen-batch.mjs) +// 用法: +// node scripts/asset-gen.mjs --prio P1 # 按优先级批跑(跳过已存在,可断点续跑) +// node scripts/asset-gen.mjs --group beasts # 只跑一个消费分组 +// node scripts/asset-gen.mjs --only zheng-move-01 --seed-bump 100000 --force # 单镜头重摇 +// node scripts/asset-gen.mjs --only tile-a-00 --subject-extra ",定向补词" --force +// 环境变量 ASSET_WORK=工作目录(默认系统临时目录 xingji-a1-work;raw/ 放生图原片) +// 纪律(承批次一,逐条生效): +// - styleWords 单源 = 批次一 anchor-pack.json(r1),运行时拼接,绝不在本仓复制一份锚; +// - prompt = promptPrefix + 镜头 subject + styleWordsShared + perGame.xingji(sprite|bleed) + 后缀,原样入账; +// - 每次外呼 150s 超时,失败退避重试 ≤3(内容过滤 exit=10 不重试,配额 exit=4 全批中止); +// - 每次调用成败都追加批次一 manifest.jsonl 一行(batch=xingji-a1),逐张 prompt/参数/耗时可审计; +// - adopt 收编镜头(批次一定稿样张直接入产线)不外呼,由 asset-key.py 直接取源; +// - 串行 + 1s 间隔防限流;--subject-ref 仅当镜头带 ref 字段(创始人拍的定妆帧参考图工艺)。 +import { readFileSync, existsSync, mkdirSync, renameSync, appendFileSync, readdirSync, rmSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve, extname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const BATCH1 = resolve(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一'); // 锚包与台账单源 +const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work'); +const RAW = join(WORK, 'raw'); +const MANIFEST = join(BATCH1, 'manifest.jsonl'); +const TMP = join(WORK, 'tmp-gen'); +mkdirSync(RAW, { recursive: true }); +mkdirSync(TMP, { recursive: true }); + +// ---- 参数解析 ---- +const args = process.argv.slice(2); +const getArg = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; }; +const onlyIds = getArg('--only')?.split(',').map(s => s.trim()).filter(Boolean); +const onlyGroup = getArg('--group'); +const onlyPrio = getArg('--prio'); +const seedBump = Number(getArg('--seed-bump') || 0); +const subjectExtra = getArg('--subject-extra') || ''; +const force = args.includes('--force'); + +// ---- 读锚包(单源)与镜头单 ---- +const anchor = JSON.parse(readFileSync(join(BATCH1, 'anchor-pack.json'), 'utf8')); +const { shots, batch } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8')); + +// prompt 拼装(锚包 r1 口径,与批次一 gen-batch 完全同构;行纪固定取 perGame.xingji) +function buildPrompt(shot) { + const kind = shot.type === 'sprite' ? 'sprite' : 'bleed'; + const per = anchor.perGame.xingji[kind]; + const suffix = shot.type === 'sprite' ? anchor.spriteSuffix : anchor.fullBleedSuffix; + return `${anchor.promptPrefix}${shot.subject}${subjectExtra}。${anchor.styleWordsShared}。${per}。${suffix}`; +} + +// 追加台账一行(逐次调用,成败都记;续写批次一 manifest.jsonl) +function ledger(entry) { + appendFileSync(MANIFEST, JSON.stringify({ ts: new Date().toISOString(), batch, ...entry }) + '\n'); +} + +// 调 mmx 一次(150s 超时);返回 { ok, file?, error?, ms, exit } +function callMmx(shot, prompt, seedUsed) { + for (const f of readdirSync(TMP)) if (f.startsWith(shot.id)) rmSync(join(TMP, f)); // 防串档 + const cli = ['image', 'generate', '--prompt', prompt, + '--width', String(shot.w), '--height', String(shot.h), + '--seed', String(seedUsed), '--out-dir', TMP, '--out-prefix', shot.id, + '--quiet', '--non-interactive']; + if (shot.ref) cli.push('--subject-ref', `type=character,image=${resolve(BATCH1, shot.ref)}`); + const t0 = Date.now(); + const r = spawnSync('mmx', cli, { timeout: 150_000, encoding: 'utf8' }); + const ms = Date.now() - t0; + if (r.error?.code === 'ETIMEDOUT' || r.signal) return { ok: false, error: 'timeout-150s', ms, exit: 5 }; + if (r.status !== 0) return { ok: false, error: (r.stderr || r.stdout || '').trim().slice(0, 300) || `exit=${r.status}`, ms, exit: r.status ?? 1 }; + const saved = (r.stdout || '').split('\n').map(s => s.trim()).find(s => s && existsSync(s)) + || readdirSync(TMP).filter(f => f.startsWith(shot.id)).map(f => join(TMP, f))[0]; + if (!saved) return { ok: false, error: 'exit=0 但未找到产物文件', ms, exit: 1 }; + return { ok: true, file: saved, ms, exit: 0 }; +} + +// ---- 主循环:串行 ---- +const todo = shots.filter(s => !s.adopt + && (!onlyIds || onlyIds.includes(s.id)) + && (!onlyGroup || s.group === onlyGroup) + && (!onlyPrio || s.prio === onlyPrio)); +let done = 0, skipped = 0, failed = 0, calls = 0; +for (const shot of todo) { + const existing = readdirSync(RAW).find(f => f.startsWith(shot.id + '.')); + if (existing && !force) { skipped++; continue; } + + const prompt = buildPrompt(shot); + const seedUsed = (shot.seed || 0) + seedBump; + let result = null; + for (let attempt = 1; attempt <= 3; attempt++) { + result = callMmx(shot, prompt, seedUsed); + calls++; + ledger({ kind: 'gen-call', id: shot.id, game: 'xingji', prio: shot.prio, group: shot.group, + type: shot.type, w: shot.w, h: shot.h, seed: seedUsed, seedBump, attempt, + model: 'image-01', ref: shot.ref || undefined, prompt, + status: result.ok ? 'ok' : 'fail', error: result.error, durationMs: result.ms }); + if (result.ok) break; + if (result.exit === 10) { console.error(`[${shot.id}] 内容过滤(exit=10),不重试`); break; } + if (result.exit === 4) { console.error(`[${shot.id}] 配额超限(exit=4),全批中止`); process.exit(4); } + if (attempt < 3) { const wait = attempt * 5000; console.error(`[${shot.id}] 第${attempt}次失败(${result.error}),${wait / 1000}s 后重试`); spawnSync('sleep', [String(wait / 1000)]); } + } + if (result.ok) { + const ext = extname(result.file) || '.png'; + const dest = join(RAW, shot.id + ext.toLowerCase()); + renameSync(result.file, dest); + done++; + console.log(`[${shot.id}] OK ${Math.round(result.ms / 1000)}s -> ${dest}`); + } else { + failed++; + console.error(`[${shot.id}] FAILED: ${result.error}`); + } + spawnSync('sleep', ['1']); // 限流间隔 +} +console.log(`\n批跑完成:成 ${done} / 跳过 ${skipped} / 败 ${failed};本次外呼 ${calls} 次(≈¥${(calls * 0.033).toFixed(2)} 仓内实测口径)`); +process.exit(failed > 0 ? 1 : 0); diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-key.py b/game-runtime/games/shanhai-xingji/scripts/asset-key.py new file mode 100644 index 00000000..3c1ea2e8 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-key.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# 《山海行纪》资产批产 · 色键抠图批处理(迁移自批次一 key-out.py,算法同源) +# 职责:镜头单里全部 sprite 镜头 → 透明底精灵 png,按消费分组落 keyed/{group}/{id}.png。 +# - gen 镜头取 ASSET_WORK/raw/{id}.jpg;adopt 收编镜头直接取批次一定稿样张(不外呼); +# - 每张:四角采样定底色 → 双阈值 alpha → 连通域除杂(印章/落款/尘点)→ 裁切留边 +# → 可选镜像归一(flip:统一面朝右)→ 内容盒缩到 pack 上限(入图集前定尺); +# - bleed 类(tiles/bg)不在本stage处理(不抠图),归 asset-tiles.py / asset-atlas.mjs。 +# 用法: python3 scripts/asset-key.py [--only id1,id2] [--prio P1] [--group beasts] [--force] +# [--tol 28] [--soft 52] [--pad 8] (环境变量 ASSET_WORK 同 asset-gen.mjs) +import sys, os, json, tempfile +from collections import deque +import numpy as np +from PIL import Image + +GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BATCH1 = os.path.normpath(os.path.join(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一')) +WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work') +RAW, KEYED = os.path.join(WORK, 'raw'), os.path.join(WORK, 'keyed') + +def parse_args(argv): + opt = {'only': None, 'prio': None, 'group': None, 'force': False, 'tol': 28.0, 'soft': 52.0, 'pad': 8} + i = 0 + while i < len(argv): + a = argv[i] + if a == '--only': opt['only'] = set(argv[i+1].split(',')); i += 2 + elif a == '--prio': opt['prio'] = argv[i+1]; i += 2 + elif a == '--group': opt['group'] = argv[i+1]; i += 2 + elif a == '--force': opt['force'] = True; i += 1 + elif a in ('--tol', '--soft'): opt[a[2:]] = float(argv[i+1]); i += 2 + elif a == '--pad': opt['pad'] = int(argv[i+1]); i += 2 + else: sys.exit(f'未知参数 {a}') + return opt + +def dilate(mask, it): + # 简易方块膨胀:把被丢弃域向外扩几圈,连羽化边一起清 + m = mask.copy() + for _ in range(it): + m = m | np.roll(m, 1, 0) | np.roll(m, -1, 0) | np.roll(m, 1, 1) | np.roll(m, -1, 1) + return m + +def key_one(src, tol, soft, pad): + """色键抠图核(与批次一 key-out.py 同源):返回 RGBA ndarray 或 None。""" + im = Image.open(src).convert('RGB') + a = np.asarray(im, dtype=np.float32) + h, w, _ = a.shape + c = 12 # ① 四角 12px 块中位数 = 背景色 + corners = np.concatenate([a[:c, :c].reshape(-1, 3), a[:c, -c:].reshape(-1, 3), + a[-c:, :c].reshape(-1, 3), a[-c:, -c:].reshape(-1, 3)]) + bg = np.median(corners, axis=0) + dist = np.sqrt(((a - bg) ** 2).sum(axis=2)) + alpha = np.clip((dist - tol) / max(soft - tol, 1e-6), 0.0, 1.0) # ② 双阈值羽化 + s = 4 # ③ 1/4 尺度连通域除杂 + fg = (dist > (tol + soft) / 2)[::s, ::s] + H, W = fg.shape + lab = np.zeros(fg.shape, dtype=np.int32) + sizes, boxes, nid = {}, {}, 0 + for y in range(H): + for x in range(W): + if fg[y, x] and lab[y, x] == 0: + nid += 1 + q = deque([(y, x)]); lab[y, x] = nid + n, y0, y1, x0, x1 = 0, y, y, x, x + while q: + cy, cx = q.popleft(); n += 1 + y0, y1, x0, x1 = min(y0, cy), max(y1, cy), min(x0, cx), max(x1, cx) + for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)): + ny, nx = cy + dy, cx + dx + if 0 <= ny < H and 0 <= nx < W and fg[ny, nx] and lab[ny, nx] == 0: + lab[ny, nx] = nid; q.append((ny, nx)) + sizes[nid], boxes[nid] = n, (y0, y1, x0, x1) + if not sizes: + return None, bg, 0 + main = max(sizes, key=sizes.get) + my0, my1, mx0, mx1 = boxes[main] + grow = max(H, W) // 20 # 主体框外扩 5%:贴身小件(飘带/火星/链梢)保留 + keep = set() + for i_, n in sizes.items(): + by0, by1, bx0, bx1 = boxes[i_] + near = not (bx1 < mx0 - grow or bx0 > mx1 + grow or by1 < my0 - grow or by0 > my1 + grow) + if i_ == main or (near and n >= 0.03 * sizes[main]) or n >= 0.20 * sizes[main]: + keep.add(i_) + dropped = len(sizes) - len(keep) + drop_small = fg & ~np.isin(lab, list(keep)) + drop_big = np.kron(dilate(drop_small, 2), np.ones((s, s), dtype=bool))[:h, :w] + alpha[drop_big] = 0.0 + ys, xs = np.nonzero(alpha > 0.02) # ④ 内容裁切 + 留边 + if len(ys) == 0: + return None, bg, dropped + y0, y1 = max(int(ys.min()) - pad, 0), min(int(ys.max()) + pad + 1, h) + x0, x1 = max(int(xs.min()) - pad, 0), min(int(xs.max()) + pad + 1, w) + rgba = np.dstack([a.astype(np.uint8), (alpha * 255).astype(np.uint8)])[y0:y1, x0:x1] + return rgba, bg, dropped + +def solid_interior(rgba): + """封闭内域回填:UI 框件的中央空白区在色键下会被啃成噪点。 + 从边界对「透明域」洪泛,凡不与边界连通的透明像素判为框内封闭区,回填原色为实心 + (卡面/槽面得到可承程序排字的实底)。""" + a = rgba[:, :, 3] + h, w = a.shape + outside = np.zeros((h, w), dtype=bool) + trans = a < 250 # 半透噪点一并纳入;外缘羽化与外界连通、不受回填影响 + q = deque() + for x in range(w): + for y in (0, h - 1): + if trans[y, x] and not outside[y, x]: outside[y, x] = True; q.append((y, x)) + for y in range(h): + for x in (0, w - 1): + if trans[y, x] and not outside[y, x]: outside[y, x] = True; q.append((y, x)) + while q: + cy, cx = q.popleft() + for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)): + ny, nx = cy + dy, cx + dx + if 0 <= ny < h and 0 <= nx < w and trans[ny, nx] and not outside[ny, nx]: + outside[ny, nx] = True; q.append((ny, nx)) + interior = trans & ~outside + rgba[:, :, 3] = np.where(interior, 255, a) + return rgba, int(interior.sum()) + +def find_src(shot): + """定位镜头源图:adopt→批次一定稿;gen→工作区 raw/。""" + if shot.get('adopt'): + p = os.path.join(BATCH1, shot['adopt']) + return p if os.path.exists(p) else None + for ext in ('.jpg', '.jpeg', '.png', '.webp'): + p = os.path.join(RAW, shot['id'] + ext) + if os.path.exists(p): return p + return None + +if __name__ == '__main__': + opt = parse_args(sys.argv[1:]) + shots = json.load(open(os.path.join(GAME, 'scripts', 'asset-shots.json'), encoding='utf-8'))['shots'] + todo = [s for s in shots if s['type'] == 'sprite' + and (not opt['only'] or s['id'] in opt['only']) + and (not opt['prio'] or s['prio'] == opt['prio']) + and (not opt['group'] or s['group'] == opt['group'])] + ok, miss, skip = 0, [], 0 + for s in todo: + out_dir = os.path.join(KEYED, s['group']) + out = os.path.join(out_dir, f"{s['id']}.png") + if os.path.exists(out) and not opt['force']: + skip += 1; continue + src = find_src(s) + if not src: + miss.append(s['id']); print(f"[{s['id']}] 缺源图(未生成或收编源不存在),跳过"); continue + rgba, bg, dropped = key_one(src, opt['tol'], opt['soft'], opt['pad']) + if rgba is None: + miss.append(s['id']); print(f"[{s['id']}] 警告:未检出前景,跳过"); continue + filled = 0 + if s.get('solidInterior'): # UI 框件:封闭内域回填为实心 + rgba, filled = solid_interior(rgba) + im = Image.fromarray(rgba, 'RGBA') + if s.get('flip'): # 镜像归一:帧集统一面朝画面右方 + im = im.transpose(Image.FLIP_LEFT_RIGHT) + cap = s.get('pack') + if cap and max(im.size) > cap: # 内容盒定尺:入图集前缩到消费尺寸 + r = cap / max(im.size) + im = im.resize((max(1, round(im.width * r)), max(1, round(im.height * r))), Image.LANCZOS) + os.makedirs(out_dir, exist_ok=True) + im.save(out) + ok += 1 + print(f"[{s['id']}] 底色 rgb({int(bg[0])},{int(bg[1])},{int(bg[2])}) 丢杂 {dropped}" + + (f" 内域回填 {filled}px" if filled else '') + + f" → {im.width}x{im.height}{'(flip)' if s.get('flip') else ''} {os.path.getsize(out)//1024}KB") + print(f"\n抠图完成:成 {ok} / 跳过 {skip} / 缺源 {len(miss)}" + (f":{','.join(miss)}" if miss else '')) + sys.exit(1 if miss else 0) diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-shots.json b/game-runtime/games/shanhai-xingji/scripts/asset-shots.json new file mode 100644 index 00000000..02c4421c --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-shots.json @@ -0,0 +1,1362 @@ +{ + "note": "《山海行纪》资产一期镜头单(锚=批次一 anchor-pack r1 单源,styleWords 不在此重复)。字段:id=role-state-frame 命名(生图请求即锁定);prio=P1(M3 阻塞面)/P2(M4 面);group=消费分组(tiles 切片图元/map1 图1图集/beasts 兽图集/ui 界面图集/actors 角色武器拾取图集/bg 整幅底图);type=sprite 纯浅底可抠 / bleed 满幅;pack=入图集前内容盒最大边(px);flip=true 抠图后镜像(朝向归一为右);adopt=直接收编批次一定稿样张(不外呼,路径相对批次一目录);seed 固定可复现。兽形语言一律出自兽谱(首批+增补-行纪),双 Boss 兽名不进 prompt(工艺发现4)。", + "batch": "xingji-a1", + "shots": [ + { + "id": "tile-a-00", + "prio": "P1", + "group": "tiles", + "type": "bleed", + "w": 1024, + "h": 1024, + "seed": 211001, + "subject": "俯视视角的上古山野地表纹理,青绿岩画风,石青石绿矿彩铺陈的岩面与苔斑交错,均匀无缝平铺纹理,无主体无焦点,纹理均匀布满整幅,四边纹理连续可拼接,低对比沉稳,画面中没有任何生物、兽形、人影与器物,无印章无落款" + }, + { + "id": "tile-b-00", + "prio": "P1", + "group": "tiles", + "type": "bleed", + "w": 1024, + "h": 1024, + "seed": 211002, + "subject": "俯视视角的上古山野地表纹理,暗色岩底变奏,深石青岩面带细碎裂纹与暗苔斑,岩画肌理均匀平铺,无主体无焦点,四边纹理连续可拼接,低对比,画面中没有任何生物、兽形、人影与器物,无印章无落款" + }, + { + "id": "tile-c-00", + "prio": "P1", + "group": "tiles", + "type": "bleed", + "w": 1024, + "h": 1024, + "seed": 211003, + "subject": "俯视视角的上古山野地表纹理,岩面浅刻残纹变奏,石青石绿岩底上散布浅刻的上古符点与磨蚀刻痕,纹样细弱不抢眼,均匀平铺无主体无焦点,四边纹理连续可拼接,画面中没有任何生物、兽形、人影与器物,无印章无落款" + }, + { + "id": "deco-rock-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212001, + "pack": 176, + "subject": "俯视微侧视角的一组青灰山岩石堆,石面苔点石绿,岩画风粗粝笔触,单个物件" + }, + { + "id": "deco-rock-02", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212002, + "pack": 176, + "subject": "俯视微侧视角的单块风化巨岩,石青色岩体带描金细裂纹,岩画风,单个物件" + }, + { + "id": "deco-totem-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212003, + "pack": 176, + "subject": "一根上古图腾石柱,柱身刻兽面纹与符文,石青石绿彩绘,顶端微泛描金光,俯视微侧视角,单个物件" + }, + { + "id": "deco-totem-02", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212004, + "pack": 176, + "subject": "一座半倾颓的上古石碑,碑面刻岩画兽形残纹,赭石苔斑,俯视微侧视角,单个物件" + }, + { + "id": "deco-bone-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212005, + "pack": 176, + "subject": "一副巨兽白骨残骸,弓起的肋骨半埋土中,骨色灰白带赭石土渍,岩画风,俯视微侧视角,单个物件" + }, + { + "id": "deco-moss-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212006, + "pack": 176, + "subject": "俯视视角的一片圆形苔斑地衣,石绿雌黄间杂,边缘自然散碎,平面装饰纹样,单个图元" + }, + { + "id": "deco-reed-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212007, + "pack": 176, + "subject": "一丛枯苇野草,赭石枯黄色,岩画风平面笔触,单个物件" + }, + { + "id": "deco-runestone-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 212008, + "pack": 176, + "subject": "一块半埋入地面的符文刻石,石面朱砂符纹微微发光,岩画风,俯视微侧视角,单个物件" + }, + { + "id": "event-shrine-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 218001, + "pack": 176, + "subject": "一座上古山神小祭坛,叠石为台,台上一盏描金灯焰,石青石绿彩绘,俯视微侧视角,单个物件" + }, + { + "id": "event-chest-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 218002, + "pack": 176, + "subject": "一只上古石匣宝匮,匣面刻兽面纹,描金锁扣,匣盖微开一缝透出金光,俯视微侧视角,单个物件" + }, + { + "id": "event-spring-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 218003, + "pack": 176, + "subject": "俯视视角的一眼圆形灵泉,青碧泉水泛石绿微光与描金涟漪,泉缘叠石,平面装饰风,单个图元" + }, + { + "id": "event-rift-01", + "prio": "P1", + "group": "map1", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 218004, + "pack": 208, + "subject": "俯视视角的一道地面裂谷豁口,岩缝中透出朱砂色凶光与雾气,裂缘带岩画纹理,平面图元,单个物件" + }, + { + "id": "zheng-idle-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213001, + "pack": 192, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴张开如火焰扇面,尾梢燃焰,四足立定蓄势昂首,侧面全身,面朝画面右方" + }, + { + "id": "zheng-move-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213002, + "pack": 192, + "flip": true, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴张开如火焰扇面,疾奔姿态,四足前后伸展腾空,五尾拖曳成焰,侧面全身,面朝画面右方" + }, + { + "id": "zheng-move-02", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213003, + "pack": 192, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴张开如火焰扇面,疾奔第二相,四足收拢蹬地身躯弓起,五尾扬起,侧面全身,面朝画面右方" + }, + { + "id": "zheng-attack-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213004, + "pack": 192, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴张开如火焰扇面,前扑扑击瞬间,前爪张开利爪,独角前指,侧面全身,面朝画面右方" + }, + { + "id": "zheng-hurt-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213005, + "pack": 192, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴张开如火焰扇面,受击瑟缩龇牙,身形低伏后缩,侧面全身,面朝画面右方" + }, + { + "id": "zheng-death-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213006, + "pack": 192, + "subject": "上古异兽狰,赤豹之躯通体朱砂赤红带深赭斑纹,额生独角如黑燧石,五条尾巴如火焰扇面垂散,倒地消散姿态,躯体边缘化作朱砂色碎屑与火星,侧面全身,面朝画面右方" + }, + { + "id": "taowu-idle-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213101, + "pack": 192, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,长尾曳地,四足立定蓄势,眉目顽愚而凶,侧面全身,面朝画面右方" + }, + { + "id": "taowu-move-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213102, + "pack": 192, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,长尾曳地,沉重奔冲姿态,四足交替扒地,尘土飞扬,侧面全身,面朝画面右方" + }, + { + "id": "taowu-move-02", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213103, + "pack": 192, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,奔冲第二相,肩背耸起前顶,长尾甩起,侧面全身,面朝画面右方" + }, + { + "id": "taowu-attack-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213104, + "pack": 192, + "flip": true, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,低头冲撞的攻击瞬间,如重锤破阵,前肩前顶,侧面全身,面朝画面右方" + }, + { + "id": "taowu-hurt-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213105, + "pack": 192, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,受击踉跄仍向前顶进,身形一沉,侧面全身,面朝画面右方" + }, + { + "id": "taowu-death-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213106, + "pack": 192, + "subject": "上古凶兽梼杌,虎躯而人面,通体赭石土黄,犬毛蓬乱如枯草鬃,倒地崩解姿态,躯体边缘化作赭石色岩屑,侧面全身,面朝画面右方" + }, + { + "id": "qiongqi-idle-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213201, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼羽刃泛雌黄金光,目色冷白,展翼立于地面蓄势,侧面全身,面朝画面右方" + }, + { + "id": "qiongqi-move-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213202, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼羽刃泛雌黄金光,低空掠行姿态,双翼半张四足虚点地面,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "qiongqi-move-02", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213203, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼羽刃泛雌黄金光,掠行第二相,双翼上挥躯体前倾,侧面全身,面朝画面右方" + }, + { + "id": "qiongqi-attack-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213204, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼羽刃泛雌黄金光,自空俯冲而下的攻击姿态,双翼收拢如箭,利爪前伸,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "qiongqi-hurt-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213205, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼羽刃泛雌黄金光,受击失衡半跪,单翼撑地,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "qiongqi-death-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 213206, + "pack": 192, + "subject": "上古凶兽穷奇,带翼巨虎,通体青白虎躯覆墨色虎纹,背生猬毛如短戟,双翼垂落,坠地消散姿态,羽刃散落化作雌黄金屑,侧面全身,面朝画面右方" + }, + { + "id": "elite-zheng-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "adopt": "images/xingji/xj-zheng-03.jpg", + "flip": true, + "pack": 224 + }, + { + "id": "elite-taowu-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "adopt": "images/xingji/xj-taowu-03.jpg", + "flip": true, + "pack": 224 + }, + { + "id": "elite-qiongqi-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "adopt": "images/xingji/xj-qiongqi-03.jpg", + "flip": true, + "pack": 224 + }, + { + "id": "elite-aura-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 214001, + "pack": 192, + "subject": "俯视视角的一圈圆形描金符阵光环,环带上刻图腾兽纹与符文,中心完全镂空,平面图元,单个物件" + }, + { + "id": "elite-aura-02", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 214002, + "pack": 192, + "subject": "俯视视角的一圈圆形朱砂凶纹光环,环缘飞绕点点火星,中心完全镂空,平面图元,单个物件" + }, + { + "id": "affix-flame-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215001, + "pack": 96, + "subject": "一枚圆形小徽记:朱砂火焰图腾纹,石质底描金细边,平面界面小物件,单个" + }, + { + "id": "affix-armor-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215002, + "pack": 96, + "subject": "一枚圆形小徽记:山字岩甲图腾纹,石青底描金细边,平面界面小物件,单个" + }, + { + "id": "affix-swift-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215003, + "pack": 96, + "subject": "一枚圆形小徽记:疾风流云图腾纹,石绿底描金细边,平面界面小物件,单个" + }, + { + "id": "affix-frost-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215004, + "pack": 96, + "subject": "一枚圆形小徽记:冰棱寒纹图腾,幽蓝底描金细边,平面界面小物件,单个" + }, + { + "id": "affix-thunder-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215005, + "pack": 96, + "subject": "一枚圆形小徽记:雷电符纹图腾,青白电弧纹描金细边,平面界面小物件,单个" + }, + { + "id": "affix-venom-01", + "prio": "P1", + "group": "beasts", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 215006, + "pack": 96, + "subject": "一枚圆形小徽记:蚀毒涡纹图腾,暗绿底描金细边,平面界面小物件,单个" + }, + { + "id": "luwu-portrait-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216001, + "pack": 384, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九条尾如仪仗屏展身后,人面威仪冷峻,双爪如钺,立姿俯瞰威压,全身像,细节最盛" + }, + { + "id": "luwu-idle-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216002, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九条尾如仪仗屏展身后,人面威仪冷峻,双爪如钺,四足立定九尾徐展,侧面全身,面朝画面右方" + }, + { + "id": "luwu-move-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216003, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九条尾屏展身后,人面威仪冷峻,沉步前行,一侧前爪抬起,九尾曳后,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "luwu-move-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216004, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九条尾屏展身后,人面威仪冷峻,沉步前行第二相,另一侧前爪抬起,身躯起伏,侧面全身,面朝画面右方" + }, + { + "id": "luwu-roar-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216005, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,人面威仪,仰首长啸,九尾全部张开如巨扇,周身描金符阵纹亮起,全身像" + }, + { + "id": "luwu-attack-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216006, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九尾屏展,人面怒目,挥爪如钺横扫的攻击瞬间,爪风撕裂空气,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "luwu-attack-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216007, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九尾屏展,人面沉静,双爪按地施法,足下荡开一圈描金符阵环,全身像" + }, + { + "id": "luwu-attack-03", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216008, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,人面凛然,九尾如鞭齐甩的攻击姿态,尾影展成大扇横扫,侧面全身,面朝画面右方" + }, + { + "id": "luwu-hurt-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216009, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九尾收拢,受击侧避低伏,人面现怒容,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "luwu-death-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216010, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽,虎躯石青毛色覆描金云雷纹,九尾垂落,跪伏欲倒,躯体裂出描金光缝,侧面全身,面朝画面右方" + }, + { + "id": "luwu-death-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 216011, + "pack": 320, + "subject": "山丘般巨大的上古山神巨兽躯体倒地崩解,石青虎躯化为岩屑与描金光屑四散,九尾如残焰消散,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-portrait-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217001, + "pack": 384, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,两侧腋下各生一只发光的眼睛,口列虎齿,前肢爪如人手,背覆青铜兽面纹岩甲,躯色赭石间雌黄,蹲踞正视巨口微张,威压全身像,细节最盛", + "flip": true + }, + { + "id": "paoxiao-idle-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217002, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下生发光的眼睛,口列虎齿,前肢爪如人手,背覆青铜兽面纹岩甲,躯色赭石间雌黄,四足立定蓄势,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-move-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217003, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下生发光的眼睛,口列虎齿,前肢爪如人手,背覆青铜兽面纹岩甲,躯色赭石间雌黄,扑奔前冲,爪手扒地,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "paoxiao-move-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217004, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下生发光的眼睛,口列虎齿,前肢爪如人手,背覆青铜兽面纹岩甲,躯色赭石间雌黄,扑奔第二相,后肢蹬地腾跃,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "paoxiao-roar-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217005, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,腋下的眼睛圆睁发光,仰首长鸣,巨口大张露出森列虎齿,背覆青铜兽面纹岩甲,躯色赭石间雌黄,全身像", + "flip": true + }, + { + "id": "paoxiao-attack-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217006, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下生发光的眼睛,巨口前咬的攻击瞬间,虎齿森列,背覆青铜兽面纹岩甲,躯色赭石间雌黄,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-attack-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217007, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下生发光的眼睛,人手巨爪前抓攫取,身躯前压,背覆青铜兽面纹岩甲,躯色赭石间雌黄,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-attack-03", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217008, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,腋下眼睛发光,吞噬姿态巨口大张,口中卷入一团暗色兽形剪影,背覆青铜兽面纹岩甲,躯色赭石间雌黄,侧面全身,面朝画面右方", + "flip": true + }, + { + "id": "paoxiao-hurt-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217009, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,头部人面双目紧闭,腋下眼睛眯起,受击缩身护住腋侧,背上青铜兽面纹岩甲崩裂一角,躯色赭石间雌黄,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-death-01", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217010, + "pack": 320, + "subject": "上古凶兽,羊躯巨兽而具人面,腋下眼睛黯淡熄灭,瘫伏欲倒,青铜兽面纹岩甲碎裂,躯色赭石间雌黄,侧面全身,面朝画面右方" + }, + { + "id": "paoxiao-death-02", + "prio": "P1", + "group": "boss", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 217011, + "pack": 320, + "subject": "上古凶兽巨躯倒地崩解,羊躯与青铜兽面纹岩甲化为赭石岩屑与雌黄金屑四散,人面安伏闭目,侧面全身,面朝画面右方" + }, + { + "id": "ui-panel-sortie-01", + "prio": "P1", + "group": "bg", + "type": "bleed", + "w": 768, + "h": 1360, + "seed": 219001, + "subject": "游戏竖屏界面背景底纹:暗色岩底,石青石绿的淡淡山影与岩画残纹,上部留一块横匾位置,中部大面积沉稳低对比留空供列表文字,底部渐暗,画面中没有任何生物、兽形、人影与器物,无印章无落款,界面背景图" + }, + { + "id": "ui-card-frame-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 768, + "h": 1064, + "seed": 219002, + "pack": 384, + "subject": "一张竖长方形符卡的空白卡框,朱漆描金边框刻云雷纹,卡面中央为干净的浅米色纸面完全空白无图案,边框外四周为干净的纯浅米色空白,没有任何灰色底纹与阴影,平面界面物件,单个", + "solidInterior": true + }, + { + "id": "ui-card-frame-02", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 768, + "h": 1064, + "seed": 219003, + "pack": 384, + "subject": "一张竖长方形石刻符卡的空白卡框,描金蟠纹重边框华丽庄重,卡面中央完全空白无图案,平面界面物件,单个", + "solidInterior": true + }, + { + "id": "ui-btn-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 1024, + "h": 512, + "seed": 219004, + "pack": 384, + "subject": "一枚横长圆角石牌按钮,石青底描金边框,中央完全空白,平面界面物件,单个" + }, + { + "id": "ui-btn-02", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 1024, + "h": 512, + "seed": 219005, + "pack": 384, + "subject": "一枚横长圆角石牌大按钮,朱砂红底描金重框,中央完全空白,庄重醒目,平面界面物件,单个" + }, + { + "id": "ui-orb-hp-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219006, + "pack": 128, + "subject": "一颗圆形血量玉珠界面件,朱砂红玉质感温润,描金细环镶边,平面界面物件,单个" + }, + { + "id": "ui-bar-frame-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 1024, + "h": 512, + "seed": 219007, + "pack": 512, + "subject": "一条横长的进度条空槽界面件,石质槽体描金边线,内里空槽干净,两端云纹收头,平面界面物件,单个" + }, + { + "id": "ui-bar-boss-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 1024, + "h": 512, + "seed": 219008, + "pack": 512, + "subject": "一条横长的血条空槽界面件,槽体如青铜器形制,两端兽面纹收头,描金边,内里空槽干净,平面界面物件,单个" + }, + { + "id": "ui-badge-wave-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219009, + "pack": 128, + "subject": "一枚圆形图腾计数徽记,石青底描金兽角纹环绕,中央完全空白,平面界面物件,单个", + "solidInterior": true + }, + { + "id": "ui-joystick-base-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219010, + "pack": 192, + "subject": "一枚圆形符阵盘界面件,石青石绿刻五行符环与细描金线,朴素克制,平面界面物件,单个" + }, + { + "id": "ui-joystick-knob-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219011, + "pack": 96, + "subject": "一颗圆形石珠界面件,石青玉质,描金细环,平面界面物件,单个", + "solidInterior": true + }, + { + "id": "ui-slot-item-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219012, + "pack": 128, + "subject": "一枚方形圆角道具槽界面框,石质刻纹描金边,中央完全空白,平面界面物件,单个", + "solidInterior": true + }, + { + "id": "ui-badge-tide-01", + "prio": "P1", + "group": "ui", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 219013, + "pack": 128, + "subject": "一枚圆形水潮纹徽记,石青底浪纹翻卷,描金细边,平面界面物件,单个" + }, + { + "id": "bg-map1-vista-01", + "prio": "P1", + "group": "bg", + "type": "bleed", + "adopt": "images/xingji/xj-scene-01.jpg" + }, + { + "id": "hero-idle-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-hero-01.jpg", + "pack": 192 + }, + { + "id": "hero-run-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-hero-02.jpg", + "flip": true, + "pack": 192 + }, + { + "id": "hero-attack-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-hero-03.jpg", + "flip": true, + "pack": 224 + }, + { + "id": "hero-hurt-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-hero-04.jpg", + "flip": true, + "pack": 192 + }, + { + "id": "hero-idle-02", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221001, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,手持缠绕雷电的符箓锁链,持链侧立警戒,目视前方,符链垂缠于臂,全身,面朝画面右方" + }, + { + "id": "hero-idle-03", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221002, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,手持缠绕雷电的符箓锁链,收链而立微微喘息,服饰与参考图完全一致,衣摆轻扬,全身,面朝画面右方" + }, + { + "id": "hero-idle-04", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221003, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,单手提起符箓锁链绕臂一圈蓄势,重心微沉,短褐及膝绝非长袍长裙,服饰与参考图完全一致,全身,面朝画面右方" + }, + { + "id": "hero-run-02", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221004, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,疾奔相位:右脚前伸落地,左脚后蹬离地,身体前倾,雷电符链后曳,侧身全身,面朝画面右方" + }, + { + "id": "hero-run-03", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221005, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,疾奔相位:双脚交错腾空瞬间,衣袂扬起,雷电符链横曳,侧身全身,面朝画面右方" + }, + { + "id": "hero-run-04", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221006, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,疾奔相位:左脚前伸落地,右脚后蹬,双臂摆动相反,符链拖曳,侧身全身,面朝画面右方" + }, + { + "id": "hero-run-05", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221007, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,疾奔相位:身体重心下沉蹬地发力,前臂前摆,符链绷直,侧身全身,面朝画面右方" + }, + { + "id": "hero-run-06", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221008, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,疾奔相位:腾空换步,符链在身侧画出弧光,侧身全身,面朝画面右方" + }, + { + "id": "hero-hurt-02", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 221009, + "ref": "images/xingji/xj-hero-01.jpg", + "pack": 192, + "subject": "上古巡山使,青年道人形象,着石青短褐与赭石护甲,红巾束发髻,受击踉跄侧倾,单膝点地支撑,咬牙持链,全身,面朝画面右方" + }, + { + "id": "fx-icon-leifu-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-weapon-01.jpg", + "pack": 160 + }, + { + "id": "fx-icon-shanzi-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-weapon-02.jpg", + "pack": 160 + }, + { + "id": "fx-icon-fentian-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-weapon-03.jpg", + "pack": 160 + }, + { + "id": "fx-icon-jinge-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222001, + "pack": 160, + "subject": "法器展示:一支金气凝成的戈形符箭,箭体刻符,雌黄金光流转,笔直悬浮,单个物件" + }, + { + "id": "fx-icon-hanyuan-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222002, + "pack": 160, + "subject": "法器展示:一支寒渊水气凝成的冰晶符箭,箭身结霜,幽蓝泛石青,悬浮,单个物件" + }, + { + "id": "fx-icon-xinghuo-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222003, + "pack": 160, + "subject": "法器展示:一张燃边符纸周围环绕数点朱砂星火,火星飞舞,单个物件" + }, + { + "id": "fx-icon-tianlei-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222004, + "pack": 160, + "subject": "法器展示:一张雷符之上一道竖直落雷,青白电弧盘绕描金符文,单个物件" + }, + { + "id": "fx-icon-fengren-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222005, + "pack": 160, + "subject": "法器展示:一片风气凝成的弧月形风刃,刃缘石绿流光,轻盈悬浮,单个物件" + }, + { + "id": "fx-icon-poyue-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222006, + "pack": 160, + "subject": "法器展示:一杆沉重的石质戟形符器,戟头如山岳斧钺,赭石描金,压迫感,单个物件" + }, + { + "id": "fx-icon-xuanming-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222007, + "pack": 160, + "subject": "法器展示:一具旋转的玄色水轮法器,轮缘生水刃,幽蓝玄青流转,单个物件" + }, + { + "id": "fx-icon-fuling-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222008, + "pack": 160, + "subject": "法器展示:一根缚灵木桩法器,桩身缠符纸与细雷电锁链,顶端符印发光,单个物件" + }, + { + "id": "fx-icon-zhendi-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 1024, + "h": 1024, + "seed": 222009, + "pack": 160, + "subject": "法器展示:一张按入地面的震地符,符纸周围地面荡开石青同心震纹,单个物件" + }, + { + "id": "fx-leifu-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222101, + "pack": 128, + "subject": "一枚飞行中的细长雷符,符纸缠绕青白电弧,尾曳雷光,平面小物件,单个" + }, + { + "id": "fx-jinge-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222102, + "pack": 128, + "subject": "一支飞行中的金色戈形符箭,笔直破空,雌黄金光,平面小物件,单个" + }, + { + "id": "fx-hanyuan-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222103, + "pack": 128, + "subject": "一支飞行中的冰晶符箭,霜雾拖尾,幽蓝石青,平面小物件,单个" + }, + { + "id": "fx-fentian-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222104, + "pack": 128, + "subject": "一根燃烧的朱砂火羽箭,焰尾拖曳,俯冲姿态,平面小物件,单个" + }, + { + "id": "fx-xinghuo-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222105, + "pack": 96, + "subject": "一点飞溅的朱砂星火小火球,拖着火星尾迹,平面小物件,单个" + }, + { + "id": "fx-tianlei-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222106, + "pack": 176, + "subject": "一道竖直的落雷光柱,青白电弧缠绕微微描金,平面特效件,单个" + }, + { + "id": "fx-fengren-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222107, + "pack": 128, + "subject": "一片飞旋的弧月形风刃,石绿流光刃缘,平面小物件,单个" + }, + { + "id": "fx-poyue-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222108, + "pack": 128, + "subject": "一杆横飞的石质戟形符器,戟头前指,赭石描金,平面小物件,单个" + }, + { + "id": "fx-shanzi-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222109, + "pack": 128, + "subject": "一面小型石盾,盾面刻三峰山形浮雕纹,石青石绿描金边,正面朝前,平面小物件,单个" + }, + { + "id": "fx-xuanming-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222110, + "pack": 128, + "subject": "一具旋转中的玄色小水轮,轮缘水刃泛幽蓝,平面小物件,单个" + }, + { + "id": "fx-fuling-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222111, + "pack": 160, + "subject": "一根立地的缚灵木桩,桩身缠符纸,顶端放射细电弧,平面物件,单个" + }, + { + "id": "fx-zhendi-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222112, + "pack": 192, + "subject": "俯视视角的圆形地脉震波,石青同心圆环涟漪,描金细纹,平面特效图元,单个" + }, + { + "id": "fx-hit-thunder-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222201, + "pack": 160, + "subject": "一团雷光炸裂特效,放射状青白电弧星芒,平面特效图元,单个" + }, + { + "id": "fx-hit-fire-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222202, + "pack": 160, + "subject": "一团火焰爆裂特效,朱砂焰瓣放射绽开,平面特效图元,单个" + }, + { + "id": "fx-hit-ice-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 222203, + "pack": 160, + "subject": "一蓬冰晶碎裂特效,幽蓝棱片放射飞溅,平面特效图元,单个" + }, + { + "id": "pickup-gem-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "adopt": "images/xingji/xj-pickup-01.jpg", + "pack": 96 + }, + { + "id": "pickup-gem-02", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 223001, + "pack": 128, + "subject": "一簇大颗灵晶矿石,多面切割的石绿晶体簇生,描金棱边,微光,单个物件" + }, + { + "id": "pickup-coin-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 223002, + "pack": 96, + "subject": "一枚上古描金贝币,贝形金泽温润,刻细纹,平面小物件,单个" + }, + { + "id": "pickup-orb-01", + "prio": "P2", + "group": "actors", + "type": "sprite", + "w": 512, + "h": 512, + "seed": 223003, + "pack": 112, + "subject": "一颗圆形的聚灵珠,石绿色核心流转微光,珠身中环一圈描金饰带,单个物件" + } + ] +} \ No newline at end of file diff --git a/game-runtime/games/shanhai-xingji/scripts/asset-tiles.py b/game-runtime/games/shanhai-xingji/scripts/asset-tiles.py new file mode 100644 index 00000000..c84fe466 --- /dev/null +++ b/game-runtime/games/shanhai-xingji/scripts/asset-tiles.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# 《山海行纪》资产批产 · 图1 tileset 合成(生图满幅地表 → 定格网格 tile 表) +# 思路:三张 1024² 满幅地表纹理(a 基底/b 暗变奏/c 刻痕变奏)各缩到 512² 后切 4×4=16 块 +# 128px tile,按 a→b→c 顺序排进 8 列 × 6 行的单张 1024×768 tile 表——同表切块天然可 +# 互拼(纹理连续来自同一幅画),tile 索引 = 行×8+列,LittleJS tile(index,128) 直接消费。 +# 用法: python3 scripts/asset-tiles.py (环境变量 ASSET_WORK 同 asset-gen.mjs) +# 产物: assets/tiles/tiles-map1.webp(q80,进包体账)+ 工作区 png 开发查看副本。 +import os, sys, tempfile +import numpy as np +from PIL import Image + +GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work') +RAW = os.path.join(WORK, 'raw') +OUT_DIR = os.path.join(GAME, 'assets', 'tiles') +TILE, COLS = 128, 8 +SHEETS = ['tile-a-00', 'tile-b-00', 'tile-c-00'] # 与 asset-shots.json 的 tiles 组对齐 +INSET = 24 # 内缩裁切:去掉生图四周细框线,再整幅重铺 + + +def patch_seals(im): + """印章修补:mmx 国风生图系统性伴生红印(批次一工艺发现3)。 + tiles 不走连通域除杂,这里按朱砂色域逐连通域检出印章块(一图可能多枚), + 每枚用画面中心区域同尺寸纹理覆盖;大面积红(画面本身偏红)不动。""" + a = np.asarray(im, dtype=np.int16) + r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2] + seal = (r > 110) & (r - g > 60) & (r - b > 55) & (g < 90) # 朱砂印泥红:R 显著高、G 压低(区别于赭金斑) + if seal.mean() > 0.05: + return im, 0 + # 1/4 尺度 BFS 分岛:多枚印章逐一修,避免并框误判 + s = 4 + m4 = seal[::s, ::s].copy() + H, W = m4.shape + patched = 0 + from collections import deque + for y in range(H): + for x in range(W): + if not m4[y, x]: + continue + q = deque([(y, x)]); m4[y, x] = False + n, y0, y1, x0, x1 = 0, y, y, x, x + while q: + cy, cx = q.popleft(); n += 1 + y0, y1, x0, x1 = min(y0, cy), max(y1, cy), min(x0, cx), max(x1, cx) + for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)): + ny, nx = cy + dy, cx + dx + if 0 <= ny < H and 0 <= nx < W and m4[ny, nx]: + m4[ny, nx] = False; q.append((ny, nx)) + if n < 12: # 岛太小(<~200 全尺寸像素)= 画面固有红点,不动 + continue + gy0, gy1 = max(0, y0 * s - 12), min(im.height, (y1 + 1) * s + 12) + gx0, gx1 = max(0, x0 * s - 12), min(im.width, (x1 + 1) * s + 12) + if (gy1 - gy0) > im.height // 3 or (gx1 - gx0) > im.width // 3: + continue # 异常大块不自动动,留人检 + ph, pw = gy1 - gy0, gx1 - gx0 + cy0, cx0 = im.height // 2 - ph // 2, im.width // 2 - pw // 2 + im.paste(im.crop((cx0, cy0, cx0 + pw, cy0 + ph)), (gx0, gy0)) + patched += 1 + return im, patched + +def find_raw(sid): + for ext in ('.jpg', '.jpeg', '.png', '.webp'): + p = os.path.join(RAW, sid + ext) + if os.path.exists(p): return p + return None + +if __name__ == '__main__': + srcs = [] + for sid in SHEETS: + p = find_raw(sid) + if not p: sys.exit(f'缺 tile 源图 {sid}(先跑 asset-gen.mjs --group tiles)') + srcs.append(p) + rows_per_sheet = 16 // COLS # 每张源图贡献 16 块 → 2 行 + sheet = Image.new('RGB', (COLS * TILE, len(SHEETS) * rows_per_sheet * TILE)) + for si, p in enumerate(srcs): + im = Image.open(p).convert('RGB') + im = im.crop((INSET, INSET, im.width - INSET, im.height - INSET)) # 去边框线 + im, patched = patch_seals(im) + if patched: print(f'[{SHEETS[si]}] 检出印章 {patched} 枚,已用中心纹理修补') + im = im.resize((512, 512), Image.LANCZOS) + for i in range(16): # 源图 4×4 切块,按序排入 8 列网格 + sx, sy = (i % 4) * TILE, (i // 4) * TILE + gi = si * 16 + i + dx, dy = (gi % COLS) * TILE, (gi // COLS) * TILE + sheet.paste(im.crop((sx, sy, sx + TILE, sy + TILE)), (dx, dy)) + os.makedirs(OUT_DIR, exist_ok=True) + webp = os.path.join(OUT_DIR, 'tiles-map1.webp') + png_dev = os.path.join(WORK, 'tiles-map1.png') + sheet.save(webp, quality=80, method=6) + sheet.save(png_dev) + kb = os.path.getsize(webp) / 1024 + print(f'tile 表完成:{sheet.width}x{sheet.height},{len(SHEETS) * 16} tiles @{TILE}px,' + f'索引=行×{COLS}+列(a=0-15/b=16-31/c=32-47) → {webp}({kb:.0f}KB webp)')