cocos-farm/tools/generate_creator_project.py

2419 lines
88 KiB
Python

#!/usr/bin/env python3
"""Generate the Cocos Creator 3.8 Stage 1 project assets.
All layout numbers come from source_export/stage1_world_camera_source.json.
Stage 1B land nodes also read source_export/stage1_visible_lands_source.json.
Stage 1C empty-land interaction nodes read
source_export/stage1_land_interaction_source.json.
Stage 1D top HUD nodes read source_export/stage1_top_hud_source.json.
The script writes Cocos Creator assets under CocosFarm/assets and leaves
Creator cache folders alone so the reconstruction remains reproducible.
"""
from __future__ import annotations
import copy
import json
import shutil
import uuid
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
PROJECT = ROOT / "CocosFarm"
ASSETS = PROJECT / "assets"
SPINE_DIR = ASSETS / "spine" / "stage1"
SOURCE_JSON = ROOT / "source_export" / "stage1_world_camera_source.json"
LAND_SOURCE_JSON = ROOT / "source_export" / "stage1_visible_lands_source.json"
INTERACTION_SOURCE_JSON = ROOT / "source_export" / "stage1_land_interaction_source.json"
DECOR_SOURCE_JSON = ROOT / "source_export" / "stage1_scene_decor_source.json"
TOP_HUD_SOURCE_JSON = ROOT / "source_export" / "stage1_top_hud_source.json"
RULES_PATH = ROOT / "source_export" / "cocos_creator38_conversion_rules.md"
SCENE_TEMPLATE = Path(
"/Applications/Cocos/Creator/3.8.8/CocosCreator.app/Contents/Resources/"
"resources/3d/engine/editor/assets/default_file_content/scene/scene-2d.scene"
)
SCENE_UUID = "02bb8b9c-d1f7-40d3-b9d3-3568d015c285"
SOURCE_WORLD_LAYER = 1073741824
SOURCE_UI_LAYER = 33554432
CAMERA_VISIBILITY = 1083179010
NAMESPACE = uuid.UUID("4799205f-8960-4807-a28e-9e32ad821936")
COCOS_BASE64_KEYS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def stable_uuid(name: str) -> str:
return str(uuid.uuid5(NAMESPACE, f"stage1-world-camera:{name}"))
def compress_uuid(uuid_text: str) -> str:
hex_text = uuid_text.replace("-", "")
result = hex_text[:5]
index = 5
while index < len(hex_text):
first = int(hex_text[index], 16)
second = int(hex_text[index + 1], 16) if index + 1 < len(hex_text) else 0
third = int(hex_text[index + 2], 16) if index + 2 < len(hex_text) else 0
result += COCOS_BASE64_KEYS[(first << 2) | (second >> 2)]
result += COCOS_BASE64_KEYS[((second & 3) << 4) | third]
index += 3
return result
def script_class_id(relative_asset_path: str) -> str:
return compress_uuid(stable_uuid(f"script:assets/{relative_asset_path}"))
def read_json(path: Path) -> dict | list:
return json.loads(path.read_text(encoding="utf-8"))
def write_json(path: Path, data: dict | list) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def ensure_directory_meta(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
if path == ASSETS:
return
meta = {
"ver": "1.2.0",
"importer": "directory",
"imported": True,
"uuid": stable_uuid(f"dir:{path.relative_to(PROJECT)}"),
"files": [],
"subMetas": {},
"userData": {"compressionType": {}, "isRemoteBundle": {}},
}
write_json(path.with_suffix(path.suffix + ".meta"), meta)
def vec2(x: float, y: float) -> dict:
return {"__type__": "cc.Vec2", "x": x, "y": y}
def vec3(x: float, y: float, z: float) -> dict:
return {"__type__": "cc.Vec3", "x": x, "y": y, "z": z}
def quat_identity() -> dict:
return {"__type__": "cc.Quat", "x": 0, "y": 0, "z": 0, "w": 1}
def color(r: int, g: int, b: int, a: int) -> dict:
return {"__type__": "cc.Color", "r": r, "g": g, "b": b, "a": a}
def size(width: float, height: float) -> dict:
return {"__type__": "cc.Size", "width": width, "height": height}
def ref(index: int) -> dict:
return {"__id__": index}
def uuid_ref(asset_uuid: str, expected_type: str | None = None) -> dict:
result = {"__uuid__": asset_uuid}
if expected_type:
result["__expectedType__"] = expected_type
return result
def node(
name: str,
parent: int | None,
children: list[int],
components: list[int],
position: dict,
active: bool = True,
layer: int = SOURCE_WORLD_LAYER,
id_key: str | None = None,
) -> dict:
return {
"__type__": "cc.Node",
"_name": name,
"_objFlags": 0,
"_parent": None if parent is None else ref(parent),
"_children": [ref(item) for item in children],
"_active": active,
"_components": [ref(item) for item in components],
"_prefab": None,
"_lpos": position,
"_lrot": quat_identity(),
"_lscale": vec3(1, 1, 1),
"_layer": layer,
"_euler": vec3(0, 0, 0),
"_id": stable_uuid(f"node:{id_key or name}:{position['x']}:{position['y']}:{position['z']}"),
}
def ui_transform(node_index: int, width: float, height: float, anchor_x: float = 0.5, anchor_y: float = 0.5) -> dict:
return {
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_priority": 0,
"_contentSize": size(width, height),
"_anchorPoint": vec2(anchor_x, anchor_y),
"_id": stable_uuid(f"uitransform:{node_index}:{width}:{height}:{anchor_x}:{anchor_y}"),
}
def camera_component(
node_index: int,
source_camera: dict,
*,
priority: int | None = None,
clear_flags: int | None = None,
component_id: str = "component:main-camera",
) -> dict:
return {
"__type__": "cc.Camera",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_projection": int(source_camera["projection"]),
"_priority": int(source_camera.get("priority", priority if priority is not None else 0)),
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": float(source_camera["orthographicHeight"]),
"_near": float(source_camera["near"]),
"_far": float(source_camera["far"]),
"_color": color(0, 0, 0, 255),
"_depth": 1,
"_stencil": 0,
"_clearFlags": int(source_camera.get("clearFlags", clear_flags if clear_flags is not None else 7)),
"_rect": {"__type__": "cc.Rect", "x": 0, "y": 0, "width": 1, "height": 1},
"_aperture": 19,
"_shutter": 7,
"_iso": 0,
"_screenScale": 1,
"_visibility": int(source_camera["visibility"]),
"_targetTexture": None,
"_id": stable_uuid(component_id),
}
def canvas_component(node_index: int, camera_index: int) -> dict:
return {
"__type__": "cc.Canvas",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_cameraComponent": ref(camera_index),
"_alignCanvasWithScreen": True,
"_id": stable_uuid("component:canvas"),
}
def widget_component(node_index: int) -> dict:
return {
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_alignFlags": 45,
"_target": None,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": True,
"_isAbsRight": True,
"_isAbsTop": True,
"_isAbsBottom": True,
"_isAbsHorizontalCenter": True,
"_isAbsVerticalCenter": True,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": stable_uuid("component:root-widget"),
}
def source_widget_component(node_index: int, widget_source: dict, id_key: str) -> dict:
# Cocos compact scenes omit fields that equal engine defaults. Widget's
# omitted `_alignMode` defaults to ON_WINDOW_RESIZE in Cocos Creator 3.8.
return {
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_alignFlags": int(widget_source["alignFlags"]),
"_target": None,
"_left": float(widget_source.get("left") or 0),
"_right": float(widget_source.get("right") or 0),
"_top": float(widget_source.get("top") or 0),
"_bottom": float(widget_source.get("bottom") or 0),
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": bool(widget_source.get("isAbsLeft", True)),
"_isAbsRight": bool(widget_source.get("isAbsRight", True)),
"_isAbsTop": bool(widget_source.get("isAbsTop", True)),
"_isAbsBottom": bool(widget_source.get("isAbsBottom", True)),
"_isAbsHorizontalCenter": bool(widget_source.get("isAbsHorizontalCenter", True)),
"_isAbsVerticalCenter": bool(widget_source.get("isAbsVerticalCenter", True)),
"_originalWidth": float(widget_source.get("originalWidth") or 0),
"_originalHeight": float(widget_source.get("originalHeight") or 0),
"_alignMode": int(widget_source.get("alignMode", 2)),
"_lockFlags": 0,
"_id": stable_uuid(f"widget:{id_key}:{widget_source['alignFlags']}:{widget_source.get('sourceTemplate')}"),
}
def sprite_component(
node_index: int,
sprite_frame_uuid: str,
sprite_type: int = 0,
size_mode: int = 0,
) -> dict:
return {
"__type__": "cc.Sprite",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": color(255, 255, 255, 255),
"_sharedMaterial": None,
"_spriteFrame": {"__uuid__": sprite_frame_uuid},
"_type": sprite_type,
"_fillType": 0,
"_sizeMode": size_mode,
"_fillCenter": vec2(0, 0),
"_fillStart": 0,
"_fillRange": 0,
"_isTrimmedMode": True,
"_useGrayscale": False,
"_atlas": None,
"_id": stable_uuid(f"component:sprite:{node_index}:{sprite_frame_uuid}"),
}
def uint32_to_color(value: int) -> dict:
# Cocos compact JSON stores label colors in the same packed order as Color.toUint32.
return color(value & 255, (value >> 8) & 255, (value >> 16) & 255, (value >> 24) & 255)
def label_component(node_index: int, label: dict) -> dict:
return {
"__type__": "cc.Label",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_customMaterial": None,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": uint32_to_color(int(label["colorUint32"])),
"_string": str(label["string"]),
"_horizontalAlign": int(label.get("horizontalAlign", 1)),
"_verticalAlign": int(label.get("verticalAlign", 1)),
"_actualFontSize": float(label["actualFontSize"]),
"_fontSize": float(label["fontSize"]),
"_fontFamily": "Arial",
"_lineHeight": float(label["lineHeight"]),
"_overflow": int(label.get("overflow", 0)),
"_enableWrapText": bool(label.get("enableWrapText", True)),
"_font": None,
"_isSystemFontUsed": True,
"_spacingX": 0,
"_isBold": bool(label["bold"]),
"_isItalic": False,
"_isUnderline": False,
"_underlineHeight": 2,
"_cacheMode": 0,
"_id": stable_uuid(f"component:label:{node_index}:{label['string']}"),
}
def mask_component(node_index: int, mask: dict) -> dict:
return {
"__type__": "cc.Mask",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": color(255, 255, 255, 255),
"_sharedMaterial": None,
"_type": int(mask["type"]),
"_segments": int(mask["segments"]),
"_id": stable_uuid(f"component:mask:{node_index}:{mask['type']}:{mask['segments']}"),
"__prefab": None,
}
def graphics_component(node_index: int, graphics: dict) -> dict:
fill = uint32_to_color(int(graphics["fillColorUint32"]))
return {
"__type__": "cc.Graphics",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_visFlags": 0,
"_customMaterial": None,
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": color(255, 255, 255, 255),
"_lineWidth": 1,
"_strokeColor": color(0, 0, 0, 255),
"_lineJoin": 2,
"_lineCap": 0,
"_fillColor": fill,
"_miterLimit": 10,
"_id": stable_uuid(f"component:graphics:{node_index}:{graphics['fillColorUint32']}"),
}
def progress_bar_component(node_index: int, progress_bar: dict, progress: float) -> dict:
return {
"__type__": "cc.ProgressBar",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"_barSprite": None,
"_mode": 0,
"_totalLength": float(progress_bar["totalLength"]),
"_progress": float(progress),
"_reverse": False,
"_id": stable_uuid(f"component:progress-bar:{node_index}:{progress_bar['totalLength']}"),
"__prefab": None,
}
def layout_component(node_index: int, layout: dict) -> dict:
return {
"__type__": "cc.Layout",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_resizeMode": int(layout.get("resizeMode", 0)),
"_layoutType": int(layout.get("layoutType", 0)),
"_cellSize": size(40, 40),
"_startAxis": 0,
"_paddingLeft": float(layout.get("paddingLeft", 0)),
"_paddingRight": float(layout.get("paddingRight", 0)),
"_paddingTop": float(layout.get("paddingTop", 0)),
"_paddingBottom": float(layout.get("paddingBottom", 0)),
"_spacingX": float(layout.get("spacingX", 0)),
"_spacingY": 0,
"_verticalDirection": 1,
"_horizontalDirection": int(layout.get("horizontalDirection", 0)),
"_constraint": 0,
"_constraintNum": 2,
"_affectedByScale": bool(layout.get("affectedByScale", False)),
"_isAlign": bool(layout.get("isAlign", False)),
"_id": stable_uuid(f"component:layout:{node_index}:{layout.get('sourceNode', '')}"),
}
def stage1_land_interaction_component(node_index: int) -> dict:
return {
"__type__": script_class_id("scripts/stage1/Stage1LandInteraction.ts"),
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_id": stable_uuid("component:stage1-land-interaction"),
}
def stage1_world_camera_component(node_index: int, camera_component_index: int) -> dict:
return {
"__type__": script_class_id("scripts/stage1/Stage1WorldCamera.ts"),
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"mainCamera": ref(camera_component_index),
"_id": stable_uuid("component:stage1-world-camera"),
}
def stage1_top_hud_component(node_index: int) -> dict:
return {
"__type__": script_class_id("scripts/stage1/Stage1TopHud.ts"),
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_id": stable_uuid("component:stage1-top-hud"),
}
def stage1_ui_camera_viewport_component(node_index: int) -> dict:
return {
"__type__": script_class_id("scripts/stage1/Stage1UiCameraViewport.ts"),
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_id": stable_uuid("component:stage1-ui-camera-viewport"),
}
def scene_land_spine_uuids() -> dict[str, str]:
return {
"skeletonDataUuid": stable_uuid("spine:scene_land.json"),
"atlasUuid": stable_uuid("spine:scene_land.atlas"),
"imageUuid": stable_uuid("spine:scene_land.png"),
}
def decor_spine_uuids(asset_id: str) -> dict[str, str]:
return {
"skeletonDataUuid": stable_uuid(f"spine:{asset_id}.skel"),
"atlasUuid": stable_uuid(f"spine:{asset_id}.atlas"),
"imageUuid": stable_uuid(f"spine:{asset_id}.png"),
}
def spine_skeleton_component(node_index: int, skeleton_data_uuid: str, default_skin: str, default_animation: str) -> dict:
return {
"__type__": "sp.Skeleton",
"_name": "",
"_objFlags": 0,
"node": ref(node_index),
"_enabled": True,
"__prefab": None,
"_materials": [],
"_srcBlendFactor": 2,
"_dstBlendFactor": 4,
"_color": color(255, 255, 255, 255),
"_skeletonData": uuid_ref(skeleton_data_uuid, "sp.SkeletonData"),
"defaultSkin": default_skin,
"defaultAnimation": default_animation,
"_premultipliedAlpha": True,
"loop": False,
"_timeScale": 1,
"_preCacheMode": 1,
"_cacheMode": 1,
"_sockets": [],
"_useTint": False,
"_debugMesh": False,
"_debugBones": False,
"_debugSlots": False,
"_enableBatch": True,
"_id": stable_uuid(f"component:spine-skeleton:{node_index}:{default_skin}:{default_animation}"),
}
def remap_ids(value: object, mapping: dict[int, int]) -> object:
if isinstance(value, dict):
if set(value.keys()) == {"__id__"} and value["__id__"] in mapping:
return {"__id__": mapping[value["__id__"]]}
return {key: remap_ids(item, mapping) for key, item in value.items()}
if isinstance(value, list):
return [remap_ids(item, mapping) for item in value]
return value
def image_meta(name: str, image_uuid: str, width: int, height: int, sprite_frame: dict | None = None) -> dict:
texture_uuid = f"{image_uuid}@6c48a"
sprite_uuid = f"{image_uuid}@f9941"
if sprite_frame:
frame_rect = sprite_frame["rect"]
original_size = sprite_frame["originalSize"]
offset = sprite_frame.get("offset", {"x": 0, "y": 0})
pivot = sprite_frame.get("pivot", {"x": 0.5, "y": 0.5})
cap = sprite_frame.get("capInsets", [0, 0, 0, 0])
frame_width = int(frame_rect["width"])
frame_height = int(frame_rect["height"])
raw_width = int(original_size["width"])
raw_height = int(original_size["height"])
else:
offset = {"x": 0, "y": 0}
pivot = {"x": 0.5, "y": 0.5}
cap = [0, 0, 0, 0]
frame_width = width
frame_height = height
raw_width = width
raw_height = height
return {
"ver": "1.0.27",
"importer": "image",
"imported": True,
"uuid": image_uuid,
"files": [".json", ".png"],
"subMetas": {
"6c48a": {
"ver": "1.0.22",
"importer": "texture",
"uuid": texture_uuid,
"imported": True,
"files": [".json"],
"subMetas": {},
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"premultiplyAlpha": False,
"anisotropy": 0,
"isUuid": True,
"imageUuidOrDatabaseUri": image_uuid,
"visible": False,
},
"displayName": name,
"id": "6c48a",
"name": "texture",
},
"f9941": {
"ver": "1.0.12",
"importer": "sprite-frame",
"uuid": sprite_uuid,
"imported": True,
"files": [".json"],
"subMetas": {},
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"minfilter": "linear",
"magfilter": "linear",
"premultiplyAlpha": False,
"generateMipmap": False,
"anisotropy": 1,
"trimType": "custom",
"trimThreshold": 1,
"rotated": False,
"offsetX": float(offset["x"]),
"offsetY": float(offset["y"]),
"trimX": 0,
"trimY": 0,
"width": frame_width,
"height": frame_height,
"rawWidth": raw_width,
"rawHeight": raw_height,
"borderTop": int(cap[0]),
"borderBottom": int(cap[2]),
"borderLeft": int(cap[3]),
"borderRight": int(cap[1]),
"isUuid": True,
"imageUuidOrDatabaseUri": texture_uuid,
"atlasUuid": "",
"mipfilter": "none",
"packable": True,
"vertices": {
"rawPosition": [
-frame_width / 2,
-frame_height / 2,
0,
frame_width / 2,
-frame_height / 2,
0,
-frame_width / 2,
frame_height / 2,
0,
frame_width / 2,
frame_height / 2,
0,
],
"indexes": [0, 1, 2, 2, 1, 3],
"uv": [0, frame_height, frame_width, frame_height, 0, 0, frame_width, 0],
"nuv": [0, 0, 1, 0, 0, 1, 1, 1],
"minPos": [-frame_width / 2, -frame_height / 2, 0],
"maxPos": [frame_width / 2, frame_height / 2, 0],
},
"pixelsToUnit": 100,
"pivotX": float(pivot["x"]),
"pivotY": float(pivot["y"]),
"meshType": 0,
},
"displayName": name,
"id": "f9941",
"name": "spriteFrame",
},
},
"userData": {
"type": "sprite-frame",
"redirect": texture_uuid,
"hasAlpha": True,
"fixAlphaTransparencyArtifacts": False,
},
}
def write_script_assets(land_evidence: dict, interaction_evidence: dict, top_hud_evidence: dict) -> None:
core_dir = ASSETS / "scripts" / "core"
stage_dir = ASSETS / "scripts" / "stage1"
mock_dir = ASSETS / "scripts" / "mock"
ensure_directory_meta(ASSETS / "scripts")
ensure_directory_meta(core_dir)
ensure_directory_meta(stage_dir)
ensure_directory_meta(mock_dir)
source_space = """export interface SourceVec2 {
x: number;
y: number;
}
export interface SourceVec3 extends SourceVec2 {
z: number;
}
export interface SourceNodeTransform {
position: SourceVec3;
scale: SourceVec3;
euler: SourceVec3;
size: SourceVec2;
pivot: SourceVec2;
}
export interface CreatorNodeTransform extends SourceNodeTransform {
layer: number;
}
export interface SourceCameraState {
position: SourceVec3;
orthographicHeight: number;
near: number;
far: number;
visibility: number;
}
export const SOURCE_DESIGN_WIDTH = 720;
export const SOURCE_DESIGN_HEIGHT = 1280;
export const SOURCE_WORLD_LAYER = 1073741824;
export const SOURCE_CAMERA_VISIBILITY = 1083179010;
export const SOURCE_CAMERA_ORTHO_HEIGHT = 640;
export const SOURCE_CAMERA_MIN_ZOOM = 0.8;
export const SOURCE_CAMERA_MAX_ZOOM = 2.4;
export const SOURCE_CAMERA_MAX_VISIBLE_WIDTH = 2300;
export function sourceCameraInitialZoom(): number {
return SOURCE_CAMERA_MIN_ZOOM;
}
export function sourceCameraInitialOrthoHeight(): number {
return SOURCE_CAMERA_ORTHO_HEIGHT / sourceCameraInitialZoom();
}
export function toCreatorNodeTransform(source: SourceNodeTransform): CreatorNodeTransform {
return {
position: { ...source.position },
scale: { ...source.scale },
euler: { ...source.euler },
size: { ...source.size },
pivot: { ...source.pivot },
layer: SOURCE_WORLD_LAYER,
};
}
export function toCreatorCameraState(source: SourceCameraState): SourceCameraState {
return {
position: { ...source.position },
orthographicHeight: source.orthographicHeight,
near: source.near,
far: source.far,
visibility: source.visibility,
};
}
"""
source_visible_land = """export interface SourceLandAssetRule {
landLevel: number;
levelName: string;
landRes: string;
landDryRes: string;
}
export interface SourceVisibleLand {
landId: number;
gridName: string;
assetId: string;
landLevel: number;
state: string;
}
export function resolveLandAssetId(rule: SourceLandAssetRule, dry: boolean): string {
const resource = dry ? rule.landDryRes : rule.landRes;
const match = resource.match(/model\\/v3\\/(.+?)\\/spriteFrame/);
return match ? match[1] : resource;
}
"""
source_land_interaction = """export interface SourceSeedMock {
seedItemId: number;
cropConfigId: number;
name: string;
assetId: string;
count: number;
bundle: string;
resource: string;
}
export interface SourceLandInteractionRule {
selectableState: string;
noResponseStates: string[];
}
"""
source_top_hud = """export interface Stage1UserMock {
nickname: string;
level: number;
exp: number;
expMax: number;
gold: number;
goldDisplay: string;
diamond: number;
avatarAssetId: string;
}
export function stage1ExpProgress(user: Stage1UserMock): number {
if (user.expMax <= 0) {
return 0;
}
return Math.max(0, Math.min(1, user.exp / user.expMax));
}
"""
stage_script = """import { _decorator, Camera, Component } from 'cc';
import { SOURCE_CAMERA_VISIBILITY, SOURCE_DESIGN_HEIGHT, SOURCE_DESIGN_WIDTH, sourceCameraInitialOrthoHeight, sourceCameraInitialZoom } from '../core/SourceSpace';
const { ccclass, property } = _decorator;
@ccclass('Stage1WorldCamera')
export class Stage1WorldCamera extends Component {
@property(Camera)
public mainCamera: Camera | null = null;
start() {
if (!this.mainCamera) {
return;
}
this.mainCamera.orthoHeight = sourceCameraInitialOrthoHeight();
this.mainCamera.visibility = SOURCE_CAMERA_VISIBILITY;
this.mainCamera.camera.update(true);
}
public sourceDesignSize() {
return {
width: SOURCE_DESIGN_WIDTH,
height: SOURCE_DESIGN_HEIGHT,
initialZoom: sourceCameraInitialZoom(),
initialOrthoHeight: sourceCameraInitialOrthoHeight(),
};
}
}
"""
ui_camera_viewport_script = """import { _decorator, Camera, Component, view } from 'cc';
import { SOURCE_DESIGN_HEIGHT } from '../core/SourceSpace';
const { ccclass } = _decorator;
@ccclass('Stage1UiCameraViewport')
export class Stage1UiCameraViewport extends Component {
private camera: Camera | null = null;
onLoad() {
this.camera = this.getComponent(Camera);
}
onEnable() {
this.syncToSourceDesignHeight();
view.on('canvas-resize', this.syncToSourceDesignHeight, this);
view.on('design-resolution-changed', this.syncToSourceDesignHeight, this);
}
start() {
this.syncToSourceDesignHeight();
}
onDisable() {
view.off('canvas-resize', this.syncToSourceDesignHeight, this);
view.off('design-resolution-changed', this.syncToSourceDesignHeight, this);
}
private syncToSourceDesignHeight() {
if (!this.camera) {
return;
}
// Keep the recovered UI camera in the source Canvas coordinate system.
this.camera.orthoHeight = SOURCE_DESIGN_HEIGHT / 2;
this.camera.camera.update(true);
}
}
"""
first_land_icon = land_evidence["visibleLands"][0]["icon"]
source_land_icon_x = float(first_land_icon["localPosition"]["x"])
source_land_icon_y = float(first_land_icon["localPosition"]["y"])
source_land_icon_width = float(first_land_icon["size"]["x"])
source_land_icon_height = float(first_land_icon["size"]["y"])
slot_layout = interaction_evidence["slotLayout"]
layout_component_evidence = slot_layout["layoutComponent"]
seed_slot_width = float(slot_layout["slotSize"]["x"])
seed_layout_spacing_x = float(layout_component_evidence["spacingX"])
seed_layout_padding_right = float(layout_component_evidence["paddingRight"])
interaction_script = """import { _decorator, Camera, Component, EventTouch, Label, Layout, Node, Sprite, UITransform, Vec3 } from 'cc';
import { STAGE1_VISIBLE_LAND_STATE } from '../mock/Stage1LandMock';
import { STAGE1_SEED_MOCK } from '../mock/Stage1SeedMock';
const { ccclass } = _decorator;
@ccclass('Stage1LandInteraction')
export class Stage1LandInteraction extends Component {
private gridOrigin: Node | null = null;
private uiRoot: Node | null = null;
private interactionRoot: Node | null = null;
private mainCamera: Camera | null = null;
private uiCamera: Camera | null = null;
private readonly selectableState = 'unlocked_normal';
private readonly sourceLandIconX = __SOURCE_LAND_ICON_X__;
private readonly sourceLandIconY = __SOURCE_LAND_ICON_Y__;
private readonly sourceLandIconWidth = __SOURCE_LAND_ICON_WIDTH__;
private readonly sourceLandIconHeight = __SOURCE_LAND_ICON_HEIGHT__;
private readonly seedSlotWidth = __SEED_SLOT_WIDTH__;
private readonly seedLayoutSpacingX = __SEED_LAYOUT_SPACING_X__;
private readonly seedLayoutPaddingRight = __SEED_LAYOUT_PADDING_RIGHT__;
start() {
this.gridOrigin = this.findDescendant(this.node, 'GridOrigin');
this.uiRoot = this.findDescendant(this.node, 'ui');
this.interactionRoot = this.findDescendant(this.node, 'plant_interactive_v2');
this.mainCamera = this.findDescendant(this.node, 'Camera')?.getComponent(Camera) ?? null;
this.uiCamera = this.findDescendant(this.node, 'uiCamera')?.getComponent(Camera) ?? null;
if (!this.gridOrigin || !this.uiRoot || !this.interactionRoot || !this.mainCamera || !this.uiCamera) {
return;
}
this.interactionRoot.active = false;
this.bindSeedMock();
this.mountLandDetailBubble();
this.setSelectedLandVisual(false);
this.node.off(Node.EventType.TOUCH_END, this.onRootTouchEnd, this);
this.node.on(Node.EventType.TOUCH_END, this.onRootTouchEnd, this);
for (const land of STAGE1_VISIBLE_LAND_STATE) {
const grid = this.gridOrigin.getChildByName(land.gridName);
if (!grid) {
continue;
}
grid.off(Node.EventType.TOUCH_END);
grid.on(Node.EventType.TOUCH_END, (event: EventTouch) => {
this.onLandClick(land.gridName, event);
}, this);
}
}
private onLandClick(gridName: string, event: EventTouch) {
const resolvedGridName = this.resolveGridNameFromTouch(event, true);
if (!resolvedGridName) {
return;
}
const land = STAGE1_VISIBLE_LAND_STATE.find((item) => item.gridName === resolvedGridName);
if (!land || land.state !== this.selectableState || !this.gridOrigin || !this.interactionRoot) {
return;
}
event.propagationStopped = true;
const grid = this.gridOrigin.getChildByName(resolvedGridName);
if (!grid) {
return;
}
const targetPosition = this.gridIconPosition(grid);
this.interactionRoot.setPosition(0, 0, 0);
this.applySourceTargetPositions(targetPosition);
this.interactionRoot.active = true;
this.setSelectedLandVisual(true);
const mountedLandBubble = this.landBubbleNode();
if (mountedLandBubble) {
mountedLandBubble.active = true;
}
}
private onRootTouchEnd(event: EventTouch) {
if (!this.interactionRoot?.active) {
return;
}
if (this.resolveGridNameFromTouch(event, true)) {
return;
}
if (this.isTouchInsideInteraction(event)) {
return;
}
this.hideInteraction();
}
private hideInteraction() {
if (!this.interactionRoot) {
return;
}
this.interactionRoot.active = false;
this.setSelectedLandVisual(false);
const mountedLandBubble = this.landBubbleNode();
if (mountedLandBubble) {
mountedLandBubble.active = false;
}
}
private applySourceTargetPositions(targetPosition: Vec3) {
if (!this.interactionRoot) {
return;
}
const activeSeedCount = this.activeSeedCount();
const followNode = this.findByPath(this.interactionRoot, 'followNode');
const landTarget = this.findByPath(this.interactionRoot, 'landTarget');
const guideNode = this.findByPath(this.interactionRoot, 'guide');
const followPosition = targetPosition.clone();
if (activeSeedCount > 3) {
followPosition.x = 0;
}
if (followNode) {
followNode.setPosition(followPosition);
}
if (landTarget) {
landTarget.setPosition(targetPosition);
}
if (guideNode) {
guideNode.setPosition(targetPosition);
}
}
private activeSeedCount(): number {
if (!this.interactionRoot) {
return STAGE1_SEED_MOCK.length;
}
const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5');
if (!group) {
return STAGE1_SEED_MOCK.length;
}
return group.children.filter((child) => child.active && /^node\d+$/.test(child.name)).length;
}
private resolveGridNameFromTouch(event: EventTouch, requireInsideIcon: boolean): string | null {
if (!this.gridOrigin) {
return null;
}
const location = event.getUILocation();
const halfWidth = this.sourceLandIconWidth / 2;
const halfHeight = this.sourceLandIconHeight / 2;
let bestGridName: string | null = null;
let bestScore = Number.POSITIVE_INFINITY;
for (const land of STAGE1_VISIBLE_LAND_STATE) {
const grid = this.gridOrigin.getChildByName(land.gridName);
const icon = grid?.getChildByName('icon');
if (!grid || !icon) {
continue;
}
const center = icon.worldPosition;
const dx = Math.abs(location.x - center.x);
const dy = Math.abs(location.y - center.y);
const insideIcon = dx <= halfWidth && dy <= halfHeight;
if (requireInsideIcon && !insideIcon) {
continue;
}
const score = dx / halfWidth + dy / halfHeight;
if (score < bestScore) {
bestScore = score;
bestGridName = land.gridName;
}
}
return bestGridName;
}
private gridIconPosition(grid: Node): Vec3 {
const icon = grid.getChildByName('icon');
if (icon && this.mainCamera && this.uiCamera && this.interactionRoot?.parent) {
const screenPosition = this.mainCamera.worldToScreen(icon.worldPosition, new Vec3());
const uiWorldPosition = this.uiCamera.screenToWorld(screenPosition, new Vec3());
const parentTransform = this.interactionRoot.parent.getComponent(UITransform);
if (parentTransform) {
return parentTransform.convertToNodeSpaceAR(uiWorldPosition);
}
return uiWorldPosition;
}
const iconPosition = icon ? icon.position : new Vec3(this.sourceLandIconX, this.sourceLandIconY, 0);
return new Vec3(grid.position.x + iconPosition.x, grid.position.y + iconPosition.y, 0);
}
private setSelectedLandVisual(active: boolean) {
if (!this.interactionRoot) {
return;
}
const selected = this.findByPath(this.interactionRoot, 'landTarget/land_valid_selected');
if (selected) {
selected.active = active;
}
}
private bindSeedMock() {
if (!this.interactionRoot) {
return;
}
const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5');
if (!group) {
return;
}
for (let index = 1; index <= 5; index += 1) {
const slot = group.getChildByName(`node${index}`);
if (!slot) {
continue;
}
const seed = STAGE1_SEED_MOCK[index - 1];
slot.active = !!seed;
const countLabel = this.findByPath(slot, 'count')?.getComponent(Label);
if (seed && countLabel) {
countLabel.string = String(seed.count);
}
const icon = this.findByPath(slot, 'icon');
const iconSprite = icon?.getComponent(Sprite);
if (iconSprite) {
iconSprite.enabled = !!seed;
}
}
this.updateSeedBackgroundVariant();
this.updateSeedLayout(group);
}
private mountLandDetailBubble() {
if (!this.interactionRoot) {
return;
}
const detailNode = this.findByPath(this.interactionRoot, 'followNode/detailNode');
const landBubble = this.findByPath(this.interactionRoot, 'land_detail/land');
if (detailNode && landBubble && landBubble.parent !== detailNode) {
landBubble.setParent(detailNode, false);
}
}
private landBubbleNode(): Node | null {
if (!this.interactionRoot) {
return null;
}
return this.findByPath(this.interactionRoot, 'followNode/detailNode/land')
?? this.findByPath(this.interactionRoot, 'land_detail/land');
}
private updateSeedBackgroundVariant() {
if (!this.interactionRoot) {
return;
}
const compactBg = this.findByPath(this.interactionRoot, 'followNode/seedGroup/bg_node3');
const wideBg = this.findByPath(this.interactionRoot, 'followNode/seedGroup/bg_node5');
const group = this.findByPath(this.interactionRoot, 'followNode/seedGroup/group_5');
const activeSlotCount = this.activeSeedCount();
const useWideBackground = activeSlotCount > 3;
if (compactBg) {
compactBg.active = !useWideBackground;
}
if (wideBg) {
wideBg.active = useWideBackground;
}
}
private isTouchInsideInteraction(event: EventTouch): boolean {
if (!this.interactionRoot) {
return false;
}
for (const path of ['followNode/detailNode/land', 'land_detail/land', 'followNode/seedGroup', 'landTarget/land_valid_selected']) {
const node = this.findByPath(this.interactionRoot, path);
const transform = node?.getComponent(UITransform);
if (node?.activeInHierarchy && transform?.hitTest(event.getUILocation())) {
return true;
}
}
return false;
}
private updateSeedLayout(group: Node) {
const layout = group.getComponent(Layout);
if (layout) {
layout.updateLayout(true);
return;
}
const activeSlots = group.children.filter((child) => child.active && /^node\\d+$/.test(child.name));
const totalWidth = activeSlots.length * this.seedSlotWidth
+ Math.max(activeSlots.length - 1, 0) * this.seedLayoutSpacingX
+ this.seedLayoutPaddingRight;
const firstX = -totalWidth / 2 + this.seedSlotWidth / 2;
activeSlots.forEach((slot, index) => {
slot.setPosition(firstX + index * (this.seedSlotWidth + this.seedLayoutSpacingX), slot.position.y, slot.position.z);
});
}
private findDescendant(root: Node, name: string): Node | null {
if (root.name === name) {
return root;
}
for (const child of root.children) {
const match = this.findDescendant(child, name);
if (match) {
return match;
}
}
return null;
}
private findByPath(root: Node, path: string): Node | null {
return path.split('/').reduce<Node | null>((current, name) => {
return current ? current.getChildByName(name) : null;
}, root);
}
}
"""
interaction_script = (
interaction_script
.replace("__SOURCE_LAND_ICON_X__", f"{source_land_icon_x:.3f}")
.replace("__SOURCE_LAND_ICON_Y__", f"{source_land_icon_y:.3f}")
.replace("__SOURCE_LAND_ICON_WIDTH__", f"{source_land_icon_width:.3f}")
.replace("__SOURCE_LAND_ICON_HEIGHT__", f"{source_land_icon_height:.3f}")
.replace("__SEED_SLOT_WIDTH__", f"{seed_slot_width:.3f}")
.replace("__SEED_LAYOUT_SPACING_X__", f"{seed_layout_spacing_x:.3f}")
.replace("__SEED_LAYOUT_PADDING_RIGHT__", f"{seed_layout_padding_right:.3f}")
)
land_level_rules = [
{
"landLevel": item["landLevel"],
"levelName": item["levelName"],
"landRes": item["landRes"],
"landDryRes": item["landDryRes"],
}
for item in land_evidence["levelConfig"]["landLevelAssetRules"]
]
visible_land_state = [
{
"landId": item["landId"],
"gridName": item["gridName"],
"assetId": item["mockRender"]["assetId"],
"landLevel": item["mockRender"]["landLevel"],
"state": item["mockRender"]["state"],
}
for item in land_evidence["visibleLands"]
]
land_mock_script = f"""import type {{ SourceLandAssetRule, SourceVisibleLand }} from '../core/SourceVisibleLand';
export const STAGE1_LAND_LEVEL_ASSET_RULES: SourceLandAssetRule[] = {json.dumps(land_level_rules, ensure_ascii=False, indent=2)};
export const STAGE1_VISIBLE_LAND_STATE: SourceVisibleLand[] = {json.dumps(visible_land_state, ensure_ascii=False, indent=2)};
"""
seed_mock_script = f"""import type {{ SourceSeedMock }} from '../core/SourceLandInteraction';
export const STAGE1_SEED_MOCK: SourceSeedMock[] = {json.dumps(interaction_evidence["mockSeeds"], ensure_ascii=False, indent=2)};
"""
user_mock_script = f"""import type {{ Stage1UserMock }} from '../core/SourceTopHud';
export const STAGE1_USER_MOCK: Stage1UserMock = {json.dumps(top_hud_evidence["mockUser"], ensure_ascii=False, indent=2)};
"""
top_hud_script = """import { _decorator, Component, Label, Node, ProgressBar } from 'cc';
import { stage1ExpProgress } from '../core/SourceTopHud';
import { STAGE1_USER_MOCK } from '../mock/Stage1UserMock';
const { ccclass } = _decorator;
@ccclass('Stage1TopHud')
export class Stage1TopHud extends Component {
start() {
this.bindMockUser();
}
private bindMockUser() {
this.setLabel('HeadInfo/SubHeadInfo/txtName', STAGE1_USER_MOCK.nickname);
this.setLabel('HeadInfo/SubHeadInfo/txtLevel', ` ${STAGE1_USER_MOCK.level} `);
this.setLabel('HeadInfo/SubHeadInfo/txtExp', `${STAGE1_USER_MOCK.exp}/${STAGE1_USER_MOCK.expMax}`);
this.setLabel('Source/root/txtGold', STAGE1_USER_MOCK.goldDisplay);
this.setLabel('Source/root/txtDiamond', String(STAGE1_USER_MOCK.diamond));
const progress = this.findByPath(this.node, 'HeadInfo/SubHeadInfo/expBar')?.getComponent(ProgressBar);
if (progress) {
progress.progress = stage1ExpProgress(STAGE1_USER_MOCK);
}
}
private setLabel(path: string, value: string) {
const label = this.findByPath(this.node, path)?.getComponent(Label);
if (label) {
label.string = value;
}
}
private findByPath(root: Node, path: string): Node | null {
return path.split('/').reduce<Node | null>((current, name) => {
return current ? current.getChildByName(name) : null;
}, root);
}
}
"""
write_text(core_dir / "SourceSpace.ts", source_space)
write_text(core_dir / "SourceVisibleLand.ts", source_visible_land)
write_text(core_dir / "SourceLandInteraction.ts", source_land_interaction)
write_text(core_dir / "SourceTopHud.ts", source_top_hud)
write_text(stage_dir / "Stage1WorldCamera.ts", stage_script)
write_text(stage_dir / "Stage1UiCameraViewport.ts", ui_camera_viewport_script)
write_text(stage_dir / "Stage1LandInteraction.ts", interaction_script)
write_text(stage_dir / "Stage1TopHud.ts", top_hud_script)
write_text(mock_dir / "Stage1LandMock.ts", land_mock_script)
write_text(mock_dir / "Stage1SeedMock.ts", seed_mock_script)
write_text(mock_dir / "Stage1UserMock.ts", user_mock_script)
for path in [
core_dir / "SourceSpace.ts",
core_dir / "SourceVisibleLand.ts",
core_dir / "SourceLandInteraction.ts",
core_dir / "SourceTopHud.ts",
stage_dir / "Stage1WorldCamera.ts",
stage_dir / "Stage1UiCameraViewport.ts",
stage_dir / "Stage1LandInteraction.ts",
stage_dir / "Stage1TopHud.ts",
mock_dir / "Stage1LandMock.ts",
mock_dir / "Stage1SeedMock.ts",
mock_dir / "Stage1UserMock.ts",
]:
meta = {
"ver": "1.0.0",
"importer": "*",
"imported": True,
"uuid": stable_uuid(f"script:{path.relative_to(PROJECT)}"),
"files": ["", ".json"],
"subMetas": {},
"userData": {},
}
write_json(path.with_suffix(path.suffix + ".meta"), meta)
def write_config_assets(evidence: dict, land_evidence: dict, interaction_evidence: dict, decor_evidence: dict, top_hud_evidence: dict) -> None:
config_dir = ASSETS / "configs"
ensure_directory_meta(config_dir)
stage_config = copy.deepcopy(evidence)
stage_config["conversionRulesPath"] = str(RULES_PATH)
manifest = {
"module": "stage1_world_camera_visible_lands",
"sourceEvidence": "source_export/stage1_world_camera_source.json",
"landSourceEvidence": "source_export/stage1_visible_lands_source.json",
"interactionSourceEvidence": "source_export/stage1_land_interaction_source.json",
"decorSourceEvidence": "source_export/stage1_scene_decor_source.json",
"topHudSourceEvidence": "source_export/stage1_top_hud_source.json",
"conversionRules": "source_export/cocos_creator38_conversion_rules.md",
"creatorScene": "assets/scenes/stage1_world_camera.scene",
"designResolution": evidence["canvas"],
"camera": evidence["camera"],
"assets": evidence["assets"],
"landAssets": land_evidence["assets"],
"interactionAssets": interaction_evidence["assets"],
"topHudAssets": top_hud_evidence["assets"],
"decorSpineAssets": decor_evidence["decorSpineAssets"],
"decorNodes": decor_evidence["generatedNodes"],
"mockSeeds": interaction_evidence["mockSeeds"],
"mockUser": top_hud_evidence["mockUser"],
"topHudNodeCount": len(top_hud_evidence["nodes"]),
"visibleLandCount": len(land_evidence["visibleLands"]),
"excludedStaticGrid": land_evidence["excludedStaticGrid"],
"landLevelAssetRules": land_evidence["levelConfig"]["landLevelAssetRules"],
"nodes": evidence["nodes"],
"layerRule": {
"sourceCameraVisibility": evidence["camera"]["visibility"],
"sourceUiCameraVisibility": evidence["uiCamera"]["visibility"],
"creatorWorldLayer": SOURCE_WORLD_LAYER,
"creatorUiLayer": SOURCE_UI_LAYER,
"reason": "Source world camera includes bit 30 but not UI_2D; source uiCamera visibility includes UI_2D and renders interaction prefabs.",
},
}
write_json(config_dir / "stage1_world_camera.source.json", stage_config)
write_json(config_dir / "stage1_visible_lands.source.json", land_evidence)
write_json(config_dir / "stage1_land_interaction.source.json", interaction_evidence)
write_json(config_dir / "stage1_scene_decor.source.json", decor_evidence)
write_json(config_dir / "stage1_top_hud.source.json", top_hud_evidence)
write_json(config_dir / "stage1_world_camera.manifest.json", manifest)
for name in [
"stage1_world_camera.source.json",
"stage1_visible_lands.source.json",
"stage1_land_interaction.source.json",
"stage1_scene_decor.source.json",
"stage1_top_hud.source.json",
"stage1_world_camera.manifest.json",
]:
path = config_dir / name
meta = {
"ver": "1.0.0",
"importer": "json",
"imported": True,
"uuid": stable_uuid(f"json:{path.relative_to(PROJECT)}"),
"files": [".json"],
"subMetas": {},
"userData": {},
}
write_json(path.with_suffix(path.suffix + ".meta"), meta)
def write_project_settings(evidence: dict) -> None:
settings_path = PROJECT / "settings" / "v2" / "packages" / "project.json"
settings = read_json(settings_path) if settings_path.exists() else {"__version__": "1.0.6"}
settings.setdefault("general", {})
settings["general"]["designResolution"] = {
"width": int(evidence["canvas"]["width"]),
"height": int(evidence["canvas"]["height"]),
"policy": 2,
}
write_json(settings_path, settings)
builder_profile_path = PROJECT / "profiles" / "v2" / "packages" / "builder.json"
builder_profile = read_json(builder_profile_path) if builder_profile_path.exists() else {"__version__": "1.3.9"}
builder_profile["useSplashScreen"] = True
builder_profile["splashScreen"] = {
"displayRatio": 0.756,
"totalTime": 0,
"logo": {"type": "none"},
"background": {
"type": "color",
"color": {"x": 1, "y": 1, "z": 1, "w": 1},
},
"watermarkLocation": "default",
"autoFit": True,
}
write_json(builder_profile_path, builder_profile)
def write_texture_assets(evidence: dict, land_evidence: dict, interaction_evidence: dict, top_hud_evidence: dict) -> dict[str, dict]:
texture_dir = ASSETS / "textures" / "stage1"
ensure_directory_meta(ASSETS / "textures")
ensure_directory_meta(texture_dir)
by_id: dict[str, dict] = {}
scene_land_atlas = {
"id": "scene_land_atlas",
"sourcePath": land_evidence["sceneLandSkeletonData"]["atlasImagePath"],
}
for asset in [*evidence["assets"], *land_evidence["assets"], *interaction_evidence["assets"], *top_hud_evidence["assets"], scene_land_atlas]:
asset_id = asset["id"]
src = Path(asset["sourcePath"])
dst = texture_dir / f"{asset_id}.png"
shutil.copy2(src, dst)
with Image.open(dst) as image:
width, height = image.size
image_uuid = stable_uuid(f"image:{asset_id}")
write_json(
dst.with_suffix(dst.suffix + ".meta"),
image_meta(asset_id, image_uuid, width, height, asset.get("spriteFrame")),
)
by_id[asset_id] = {
"path": str(dst),
"uuid": image_uuid,
"textureUuid": f"{image_uuid}@6c48a",
"spriteFrameUuid": f"{image_uuid}@f9941",
"pixelWidth": width,
"pixelHeight": height,
}
return by_id
def write_spine_assets(land_evidence: dict) -> dict[str, str]:
scene_land = land_evidence["sceneLandSkeletonData"]
uuids = scene_land_spine_uuids()
ensure_directory_meta(ASSETS / "spine")
ensure_directory_meta(SPINE_DIR)
json_path = SPINE_DIR / "scene_land.json"
atlas_path = SPINE_DIR / "scene_land.atlas"
png_path = SPINE_DIR / "scene_land.png"
write_text(json_path, json.dumps(scene_land["skeletonJson"], ensure_ascii=False, separators=(",", ":")) + "\n")
write_text(atlas_path, scene_land["atlasText"].lstrip("\n"))
shutil.copy2(scene_land["atlasImagePath"], png_path)
with Image.open(png_path) as image:
width, height = image.size
write_json(png_path.with_suffix(png_path.suffix + ".meta"), image_meta("scene_land", uuids["imageUuid"], width, height))
write_json(
atlas_path.with_suffix(atlas_path.suffix + ".meta"),
{
"ver": "1.0.0",
"importer": "*",
"imported": True,
"uuid": uuids["atlasUuid"],
"files": [".atlas", ".json"],
"subMetas": {},
"userData": {},
},
)
write_json(
json_path.with_suffix(json_path.suffix + ".meta"),
{
"ver": "1.2.7",
"importer": "spine-data",
"imported": True,
"uuid": uuids["skeletonDataUuid"],
"files": [".json"],
"subMetas": {},
"userData": {"atlasUuid": uuids["atlasUuid"]},
},
)
return {
**uuids,
"jsonPath": str(json_path),
"atlasPath": str(atlas_path),
"pngPath": str(png_path),
"textureUuid": f"{uuids['imageUuid']}@6c48a",
}
def write_decor_spine_assets(decor_evidence: dict) -> dict[str, dict]:
ensure_directory_meta(ASSETS / "spine")
ensure_directory_meta(SPINE_DIR)
written: dict[str, dict] = {}
for asset in decor_evidence["decorSpineAssets"]:
asset_id = asset["id"]
skin_cfg_id = str(asset["skinCfgId"])
uuids = decor_spine_uuids(asset_id)
asset_dir = SPINE_DIR / asset_id
ensure_directory_meta(asset_dir)
skel_path = asset_dir / f"{skin_cfg_id}.skel"
atlas_path = asset_dir / f"{skin_cfg_id}.atlas"
png_path = asset_dir / asset["atlasImageName"]
shutil.copy2(asset["skeletonBinaryPath"], skel_path)
shutil.copy2(asset["atlasPath"], atlas_path)
shutil.copy2(asset["atlasImagePath"], png_path)
with Image.open(png_path) as image:
width, height = image.size
write_json(png_path.with_suffix(png_path.suffix + ".meta"), image_meta(asset_id, uuids["imageUuid"], width, height))
write_json(
atlas_path.with_suffix(atlas_path.suffix + ".meta"),
{
"ver": "1.0.0",
"importer": "*",
"imported": True,
"uuid": uuids["atlasUuid"],
"files": [".atlas", ".json"],
"subMetas": {},
"userData": {},
},
)
write_json(
skel_path.with_suffix(skel_path.suffix + ".meta"),
{
"ver": "1.2.7",
"importer": "spine-data",
"imported": True,
"uuid": uuids["skeletonDataUuid"],
"files": [".skel"],
"subMetas": {},
"userData": {"atlasUuid": uuids["atlasUuid"]},
},
)
written[asset_id] = {
**uuids,
"skeletonPath": str(skel_path),
"atlasPath": str(atlas_path),
"pngPath": str(png_path),
"textureUuid": f"{uuids['imageUuid']}@6c48a",
}
return written
def source_node(evidence: dict, path: str) -> dict:
for item in evidence["nodes"]:
if item["path"] == path:
return item
raise KeyError(path)
def interaction_source_node(interaction_evidence: dict, path: str) -> dict:
for item in interaction_evidence["nodes"]:
if item["path"] == path:
return item
raise KeyError(path)
def top_hud_source_node(top_hud_evidence: dict, path: str) -> dict:
for item in top_hud_evidence["nodes"]:
if item["path"] == path:
return item
raise KeyError(path)
def top_hud_mock_label(top_hud_evidence: dict, source: dict) -> dict:
label = copy.deepcopy(source["label"])
mock_user = top_hud_evidence["mockUser"]
mock_field = label.get("mockField")
if mock_field == "nickname":
label["string"] = str(mock_user["nickname"])
elif mock_field == "level":
label["string"] = f" {mock_user['level']} "
elif mock_field == "experience":
label["string"] = f"{mock_user['exp']}/{mock_user['expMax']}"
elif mock_field == "goldDisplay":
label["string"] = str(mock_user["goldDisplay"])
elif mock_field == "diamond":
label["string"] = str(mock_user["diamond"])
return label
def top_hud_mock_progress(top_hud_evidence: dict) -> float:
mock_user = top_hud_evidence["mockUser"]
exp_max = float(mock_user["expMax"])
if exp_max <= 0:
return 0.0
return max(0.0, min(1.0, float(mock_user["exp"]) / exp_max))
def add_generated_node(
objects: list[dict],
name: str,
parent_index: int,
source: dict,
textures: dict[str, dict],
id_key: str,
sprite_type: int = 0,
layer: int = SOURCE_WORLD_LAYER,
label_override: dict | None = None,
) -> int:
node_index = len(objects)
position = source["localPosition"]
objects.append(
node(
name,
parent_index,
[],
[],
vec3(position["x"], position["y"], position["z"]),
active=bool(source.get("active", True)),
layer=layer,
id_key=id_key,
)
)
components: list[int] = []
size_source = source.get("size")
if size_source:
ui_index = len(objects)
anchor = source.get("anchor", {"x": 0.5, "y": 0.5})
objects.append(ui_transform(node_index, size_source["x"], size_source["y"], anchor["x"], anchor["y"]))
components.append(ui_index)
if source.get("widget"):
widget_index = len(objects)
objects.append(source_widget_component(node_index, source["widget"], id_key))
components.append(widget_index)
sprite_asset_id = source.get("spriteAssetId")
if sprite_asset_id:
sprite_index = len(objects)
objects.append(
sprite_component(
node_index,
textures[sprite_asset_id]["spriteFrameUuid"],
sprite_type=sprite_type,
size_mode=0,
)
)
components.append(sprite_index)
label_source = label_override if label_override is not None else source.get("label")
if label_source:
label_index = len(objects)
objects.append(label_component(node_index, label_source))
components.append(label_index)
objects[node_index]["_components"] = [ref(item) for item in components]
return node_index
def build_top_hud_nodes(
objects: list[dict],
ui_root_index: int,
top_hud_evidence: dict,
textures: dict[str, dict],
) -> int:
node_indexes: dict[str, int] = {}
def add(path: str, parent_index: int, sprite_type: int = 0) -> int:
source = top_hud_source_node(top_hud_evidence, path)
label_override = top_hud_mock_label(top_hud_evidence, source) if source.get("label") else None
node_index = add_generated_node(
objects,
path.rsplit("/", 1)[-1],
parent_index,
source,
textures,
path,
sprite_type=sprite_type,
layer=SOURCE_UI_LAYER,
label_override=label_override,
)
if source.get("mask"):
mask_index = len(objects)
objects.append(mask_component(node_index, source["mask"]))
objects[node_index]["_components"].append(ref(mask_index))
if source["mask"].get("fillColorUint32") is not None:
graphics_index = len(objects)
objects.append(graphics_component(node_index, {"fillColorUint32": source["mask"]["fillColorUint32"]}))
objects[node_index]["_components"].append(ref(graphics_index))
if source.get("progressBar"):
progress_index = len(objects)
objects.append(progress_bar_component(node_index, source["progressBar"], top_hud_mock_progress(top_hud_evidence)))
objects[node_index]["_components"].append(ref(progress_index))
node_indexes[path] = node_index
return node_index
main_index = add("main_ui_v2", ui_root_index)
top_hud_component_index = len(objects)
objects.append(stage1_top_hud_component(main_index))
objects[main_index]["_components"].append(ref(top_hud_component_index))
head_info = add("main_ui_v2/HeadInfo", main_index)
source_root = add("main_ui_v2/Source", main_index)
sub_head = add("main_ui_v2/HeadInfo/SubHeadInfo", head_info)
btn_exit = add("main_ui_v2/HeadInfo/btnExit", head_info)
head_di = add("main_ui_v2/HeadInfo/SubHeadInfo/head_di", sub_head, sprite_type=1)
img_head_di1 = add("main_ui_v2/HeadInfo/SubHeadInfo/img_head_di1", sub_head)
head_mask = add("main_ui_v2/HeadInfo/SubHeadInfo/headMask", sub_head)
head = add("main_ui_v2/HeadInfo/SubHeadInfo/headMask/head", head_mask)
node_head_frame = add("main_ui_v2/HeadInfo/SubHeadInfo/node_head_frame", sub_head)
exp_bar = add("main_ui_v2/HeadInfo/SubHeadInfo/expBar", sub_head, sprite_type=1)
bar = add("main_ui_v2/HeadInfo/SubHeadInfo/expBar/Bar", exp_bar)
img_level = add("main_ui_v2/HeadInfo/SubHeadInfo/img_level", sub_head)
txt_name = add("main_ui_v2/HeadInfo/SubHeadInfo/txtName", sub_head)
txt_level = add("main_ui_v2/HeadInfo/SubHeadInfo/txtLevel", sub_head)
txt_exp = add("main_ui_v2/HeadInfo/SubHeadInfo/txtExp", sub_head)
source_root_child = add("main_ui_v2/Source/root", source_root)
img_source_di = add("main_ui_v2/Source/root/img_source_di", source_root_child, sprite_type=1)
img_gold = add("main_ui_v2/Source/root/img_gold", source_root_child)
txt_gold = add("main_ui_v2/Source/root/txtGold", source_root_child)
img_source_di1 = add("main_ui_v2/Source/root/img_source_di1", source_root_child, sprite_type=1)
img_diamond = add("main_ui_v2/Source/root/img_diamond", source_root_child)
txt_diamond = add("main_ui_v2/Source/root/txtDiamond", source_root_child)
objects[main_index]["_children"] = [ref(head_info), ref(source_root)]
objects[head_info]["_children"] = [ref(sub_head), ref(btn_exit)]
objects[sub_head]["_children"] = [
ref(head_di),
ref(img_head_di1),
ref(head_mask),
ref(node_head_frame),
ref(exp_bar),
ref(img_level),
ref(txt_name),
ref(txt_level),
ref(txt_exp),
]
objects[head_mask]["_children"] = [ref(head)]
objects[exp_bar]["_children"] = [ref(bar)]
objects[source_root]["_children"] = [ref(source_root_child)]
objects[source_root_child]["_children"] = [
ref(img_source_di),
ref(img_gold),
ref(txt_gold),
ref(img_source_di1),
ref(img_diamond),
ref(txt_diamond),
]
return main_index
def build_land_interaction_nodes(
objects: list[dict],
ui_root_index: int,
interaction_evidence: dict,
textures: dict[str, dict],
) -> int:
root_source = interaction_source_node(interaction_evidence, "plant_interactive_v2")
interaction_index = add_generated_node(
objects,
"plant_interactive_v2",
ui_root_index,
root_source,
textures,
"plant_interactive_v2",
layer=SOURCE_UI_LAYER,
)
land_detail = add_generated_node(
objects,
"land_detail",
interaction_index,
interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail"),
textures,
"plant_interactive_v2/land_detail",
layer=SOURCE_UI_LAYER,
)
land = add_generated_node(
objects,
"land",
land_detail,
interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land"),
textures,
"plant_interactive_v2/land_detail/land",
sprite_type=1,
layer=SOURCE_UI_LAYER,
)
title = add_generated_node(
objects,
"title",
land,
interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land/title"),
textures,
"plant_interactive_v2/land_detail/land/title",
layer=SOURCE_UI_LAYER,
)
land_icon = add_generated_node(
objects,
"icon",
land,
interaction_source_node(interaction_evidence, "plant_interactive_v2/land_detail/land/icon"),
textures,
"plant_interactive_v2/land_detail/land/icon",
layer=SOURCE_UI_LAYER,
)
objects[land]["_children"] = [ref(title), ref(land_icon)]
objects[land_detail]["_children"] = [ref(land)]
follow_node = add_generated_node(
objects,
"followNode",
interaction_index,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode"),
textures,
"plant_interactive_v2/followNode",
layer=SOURCE_UI_LAYER,
)
detail_node = add_generated_node(
objects,
"detailNode",
follow_node,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/detailNode"),
textures,
"plant_interactive_v2/followNode/detailNode",
layer=SOURCE_UI_LAYER,
)
seed_group = add_generated_node(
objects,
"seedGroup",
follow_node,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup"),
textures,
"plant_interactive_v2/followNode/seedGroup",
layer=SOURCE_UI_LAYER,
)
bg_node3 = add_generated_node(
objects,
"bg_node3",
seed_group,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/bg_node3"),
textures,
"plant_interactive_v2/followNode/seedGroup/bg_node3",
sprite_type=1,
layer=SOURCE_UI_LAYER,
)
bg_node5 = add_generated_node(
objects,
"bg_node5",
seed_group,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/bg_node5"),
textures,
"plant_interactive_v2/followNode/seedGroup/bg_node5",
sprite_type=1,
layer=SOURCE_UI_LAYER,
)
group_5 = add_generated_node(
objects,
"group_5",
seed_group,
interaction_source_node(interaction_evidence, "plant_interactive_v2/followNode/seedGroup/group_5"),
textures,
"plant_interactive_v2/followNode/seedGroup/group_5",
layer=SOURCE_UI_LAYER,
)
group_layout_index = len(objects)
objects.append(layout_component(group_5, interaction_evidence["slotLayout"]["layoutComponent"]))
objects[group_5]["_components"].append(ref(group_layout_index))
slot_indices: list[int] = []
for slot_number in range(1, 6):
slot_path = f"plant_interactive_v2/followNode/seedGroup/group_5/node{slot_number}"
slot = add_generated_node(objects, f"node{slot_number}", group_5, interaction_source_node(interaction_evidence, slot_path), textures, slot_path, layer=SOURCE_UI_LAYER)
seed_bg = add_generated_node(objects, "seed_bg", slot, interaction_source_node(interaction_evidence, f"{slot_path}/seed_bg"), textures, f"{slot_path}/seed_bg", layer=SOURCE_UI_LAYER)
icon = add_generated_node(objects, "icon", slot, interaction_source_node(interaction_evidence, f"{slot_path}/icon"), textures, f"{slot_path}/icon", layer=SOURCE_UI_LAYER)
bg_txt = add_generated_node(
objects,
"bg_txt",
slot,
interaction_source_node(interaction_evidence, f"{slot_path}/bg_txt"),
textures,
f"{slot_path}/bg_txt",
sprite_type=1,
layer=SOURCE_UI_LAYER,
)
color_block = add_generated_node(
objects,
"color_block",
bg_txt,
interaction_source_node(interaction_evidence, f"{slot_path}/bg_txt/color_block"),
textures,
f"{slot_path}/bg_txt/color_block",
sprite_type=1,
layer=SOURCE_UI_LAYER,
)
count = add_generated_node(objects, "count", slot, interaction_source_node(interaction_evidence, f"{slot_path}/count"), textures, f"{slot_path}/count", layer=SOURCE_UI_LAYER)
objects[bg_txt]["_children"] = [ref(color_block)]
objects[slot]["_children"] = [ref(seed_bg), ref(icon), ref(bg_txt), ref(count)]
slot_indices.append(slot)
objects[group_5]["_children"] = [ref(item) for item in slot_indices]
objects[seed_group]["_children"] = [ref(bg_node3), ref(bg_node5), ref(group_5)]
objects[follow_node]["_children"] = [ref(detail_node), ref(seed_group)]
land_target = add_generated_node(
objects,
"landTarget",
interaction_index,
interaction_source_node(interaction_evidence, "plant_interactive_v2/landTarget"),
textures,
"plant_interactive_v2/landTarget",
layer=SOURCE_UI_LAYER,
)
selected_land = add_generated_node(
objects,
"land_valid_selected",
land_target,
interaction_source_node(interaction_evidence, "plant_interactive_v2/landTarget/land_valid_selected"),
textures,
"plant_interactive_v2/landTarget/land_valid_selected",
layer=SOURCE_UI_LAYER,
)
objects[land_target]["_children"] = [ref(selected_land)]
objects[interaction_index]["_children"] = [ref(land_target), ref(land_detail), ref(follow_node)]
return interaction_index
def build_scene(
evidence: dict,
land_evidence: dict,
interaction_evidence: dict,
decor_evidence: dict,
top_hud_evidence: dict,
textures: dict[str, dict],
decor_spines: dict[str, dict],
) -> list[dict]:
root = source_node(evidence, "root")
scene_mount = source_node(evidence, "root/scene")
ui_root_source = evidence.get(
"uiRoot",
{
"localPosition": {"x": 0.0, "y": 0.0, "z": 0.0},
"size": {"x": root["size"]["x"], "y": root["size"]["y"]},
"pivot": root["pivot"],
"layer": SOURCE_UI_LAYER,
},
)
farm_root = source_node(evidence, "root/scene/farm_scene_v3")
map_root = source_node(evidence, "root/scene/farm_scene_v3/Scene")
bg_layer = source_node(evidence, "root/scene/farm_scene_v3/Scene/BgLayer")
bg = source_node(evidence, "root/scene/farm_scene_v3/Scene/BgLayer/defaultbg")
fg_layer = source_node(evidence, "root/scene/farm_scene_v3/Scene/FgLayer")
fg = source_node(evidence, "root/scene/farm_scene_v3/Scene/FgLayer/default")
source_camera = evidence["camera"]
root_widget_source = root.get(
"widget",
{
"alignFlags": 45,
"originalWidth": root["size"]["x"],
"originalHeight": root["size"]["y"],
"sourceTemplate": "cc.Widget compact root default",
},
)
ui_widget_source = ui_root_source.get(
"widget",
{
"alignFlags": 45,
"originalWidth": ui_root_source["size"]["x"],
"originalHeight": ui_root_source["size"]["y"],
"sourceTemplate": "cc.Widget compact ui default",
},
)
objects: list[dict] = [
{
"__type__": "cc.SceneAsset",
"_name": "stage1_world_camera",
"_objFlags": 0,
"_native": "",
"scene": ref(1),
},
{
"__type__": "cc.Scene",
"_name": "stage1_world_camera",
"_objFlags": 0,
"_parent": None,
"_children": [ref(2)],
"_active": True,
"_components": [],
"_prefab": None,
"autoReleaseAssets": False,
"_globals": ref(20),
"_id": SCENE_UUID,
},
node("root", 1, [3], [6, 7, 8], vec3(root["localPosition"]["x"], root["localPosition"]["y"], root["localPosition"]["z"]), id_key="root"),
node("scene", 2, [4, 10], [9], vec3(scene_mount["localPosition"]["x"], scene_mount["localPosition"]["y"], scene_mount["localPosition"]["z"]), id_key="root/scene"),
node("Camera", 3, [], [5], vec3(source_camera["sourcePosition"]["x"], source_camera["sourcePosition"]["y"], source_camera["sourcePosition"]["z"]), id_key="root/scene/Camera"),
camera_component(4, source_camera),
ui_transform(2, root["size"]["x"], root["size"]["y"], root["pivot"]["x"], root["pivot"]["y"]),
canvas_component(2, 5),
source_widget_component(2, root_widget_source, "root"),
ui_transform(3, scene_mount["size"]["x"], scene_mount["size"]["y"], scene_mount["pivot"]["x"], scene_mount["pivot"]["y"]),
node("farm_scene_v3", 3, [11], [], vec3(farm_root["localPosition"]["x"], farm_root["localPosition"]["y"], farm_root["localPosition"]["z"]), id_key="root/scene/farm_scene_v3"),
node("Scene", 10, [12, 16], [], vec3(map_root["localPosition"]["x"], map_root["localPosition"]["y"], map_root["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene"),
node("BgLayer", 11, [13], [], vec3(bg_layer["localPosition"]["x"], bg_layer["localPosition"]["y"], bg_layer["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/BgLayer"),
node("defaultbg", 12, [], [14, 15], vec3(bg["localPosition"]["x"], bg["localPosition"]["y"], bg["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/BgLayer/defaultbg"),
ui_transform(13, bg["size"]["x"], bg["size"]["y"], bg["pivot"]["x"], bg["pivot"]["y"]),
sprite_component(13, textures["scene_bg"]["spriteFrameUuid"]),
node("FgLayer", 11, [17], [], vec3(fg_layer["localPosition"]["x"], fg_layer["localPosition"]["y"], fg_layer["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/FgLayer"),
node("default", 16, [], [18, 19], vec3(fg["localPosition"]["x"], fg["localPosition"]["y"], fg["localPosition"]["z"]), id_key="root/scene/farm_scene_v3/Scene/FgLayer/default"),
ui_transform(17, fg["size"]["x"], fg["size"]["y"], fg["pivot"]["x"], fg["pivot"]["y"]),
sprite_component(17, textures["scene_fg"]["spriteFrameUuid"]),
]
world_camera_component_index = len(objects)
objects.append(stage1_world_camera_component(4, 5))
objects[4]["_components"].append(ref(world_camera_component_index))
if scene_mount.get("widget"):
scene_widget_index = len(objects)
objects.append(source_widget_component(3, scene_mount["widget"], "root/scene"))
objects[3]["_components"].append(ref(scene_widget_index))
decor_nodes = {item["path"]: item for item in decor_evidence["generatedNodes"]}
decor_assets = {item["id"]: item for item in decor_evidence["decorSpineAssets"]}
decor_parents = decor_evidence["sourceParentNodes"]
fg_child_indices_by_name: dict[str, int] = {"default": 17}
farm_skin_source = decor_parents["farmSkin"]
farm_skin_index = len(objects)
fg_child_indices_by_name["FarmSkin"] = farm_skin_index
objects.append(
node(
"FarmSkin",
16,
[],
[],
vec3(
farm_skin_source["localPosition"]["x"],
farm_skin_source["localPosition"]["y"],
farm_skin_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scene/FgLayer/FarmSkin",
)
)
farm_skin_layer_indices: dict[str, int] = {}
for layer_source in decor_parents["farmSkinLayers"]:
layer_index = len(objects)
farm_skin_layer_indices[layer_source["name"]] = layer_index
objects.append(
node(
layer_source["name"],
farm_skin_index,
[],
[],
vec3(
layer_source["localPosition"]["x"],
layer_source["localPosition"]["y"],
layer_source["localPosition"]["z"],
),
id_key=f"root/scene/{layer_source['path']}",
)
)
objects[farm_skin_index]["_children"] = [ref(farm_skin_layer_indices[item["name"]]) for item in decor_parents["farmSkinLayers"]]
house_source = decor_nodes["farm_scene_v3/Scene/FgLayer/FarmSkin/Layer2/house_201001"]
house_asset = decor_assets[house_source["spineAssetId"]]
house_index = len(objects)
house_skeleton_index = house_index + 1
layer2_index = farm_skin_layer_indices["Layer2"]
objects.extend(
[
node(
house_source["name"],
layer2_index,
[],
[house_skeleton_index],
vec3(
house_source["localPosition"]["x"],
house_source["localPosition"]["y"],
house_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scene/FgLayer/FarmSkin/Layer2/house_201001",
),
spine_skeleton_component(
house_index,
decor_spines[house_source["spineAssetId"]]["skeletonDataUuid"],
str(house_asset["defaultSkin"]),
str(house_asset["defaultAnimation"]),
),
]
)
objects[layer2_index]["_children"] = [ref(house_index)]
dog_layer_source = decor_parents["dogLayer"]
dog_layer_index = len(objects)
fg_child_indices_by_name["DogLayer"] = dog_layer_index
objects.append(
node(
"DogLayer",
16,
[],
[],
vec3(
dog_layer_source["localPosition"]["x"],
dog_layer_source["localPosition"]["y"],
dog_layer_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scene/FgLayer/DogLayer",
)
)
doghouse_source = decor_nodes["farm_scene_v3/Scene/FgLayer/DogLayer/dogHouse"]
doghouse_asset = decor_assets[doghouse_source["spineAssetId"]]
doghouse_index = len(objects)
doghouse_skeleton_index = doghouse_index + 1
objects.extend(
[
node(
doghouse_source["name"],
dog_layer_index,
[],
[doghouse_skeleton_index],
vec3(
doghouse_source["localPosition"]["x"],
doghouse_source["localPosition"]["y"],
doghouse_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scene/FgLayer/DogLayer/dogHouse",
),
spine_skeleton_component(
doghouse_index,
decor_spines[doghouse_source["spineAssetId"]]["skeletonDataUuid"],
str(doghouse_asset["defaultSkin"]),
str(doghouse_asset["defaultAnimation"]),
),
]
)
objects[dog_layer_index]["_children"] = [ref(doghouse_index)]
for layer_source in decor_parents["fgLayerChildrenOrder"]:
layer_name = layer_source["name"]
if layer_name in fg_child_indices_by_name:
continue
layer_index = len(objects)
fg_child_indices_by_name[layer_name] = layer_index
objects.append(
node(
layer_name,
16,
[],
[],
vec3(
layer_source["localPosition"]["x"],
layer_source["localPosition"]["y"],
layer_source["localPosition"]["z"],
),
id_key=f"root/scene/{layer_source['path']}",
)
)
objects[16]["_children"] = [
ref(fg_child_indices_by_name[layer_source["name"]])
for layer_source in decor_parents["fgLayerChildrenOrder"]
if layer_source["name"] in fg_child_indices_by_name
]
ui_index = len(objects)
ui_camera_index = ui_index + 1
ui_transform_index = ui_index + 2
ui_widget_index = ui_index + 3
ui_camera_component_index = ui_index + 4
ui_camera_viewport_component_index = ui_index + 5
ui_camera_source = evidence["uiCamera"]
objects[2]["_children"] = [ref(3), ref(ui_index)]
objects.extend(
[
node(
"ui",
2,
[ui_camera_index],
[ui_transform_index, ui_widget_index],
vec3(
ui_root_source["localPosition"]["x"],
ui_root_source["localPosition"]["y"],
ui_root_source["localPosition"]["z"],
),
layer=int(ui_root_source.get("layer", SOURCE_UI_LAYER)),
id_key="root/ui",
),
node(
"uiCamera",
ui_index,
[],
[ui_camera_component_index, ui_camera_viewport_component_index],
vec3(
ui_camera_source["sourcePosition"]["x"],
ui_camera_source["sourcePosition"]["y"],
ui_camera_source["sourcePosition"]["z"],
),
layer=SOURCE_UI_LAYER,
id_key="root/ui/uiCamera",
),
ui_transform(ui_index, ui_root_source["size"]["x"], ui_root_source["size"]["y"], ui_root_source["pivot"]["x"], ui_root_source["pivot"]["y"]),
source_widget_component(ui_index, ui_widget_source, "root/ui"),
camera_component(ui_camera_index, ui_camera_source, component_id="component:ui-camera"),
stage1_ui_camera_viewport_component(ui_camera_index),
]
)
# Source startup.scene Canvas serializes `_cameraComponent=-5`; Cocos
# compiled-json refs resolve that to instance 7, the `root/ui/uiCamera`
# Camera component. Keep the world camera independent from Canvas resize.
objects[7]["_cameraComponent"] = ref(ui_camera_component_index)
pre_plant_source = interaction_evidence["farmGridGraphics"]["prePlantNode"]
post_plant_source = interaction_evidence["farmGridGraphics"]["postPlantNode"]
pre_plant_index = len(objects)
post_plant_index = pre_plant_index + 1
objects.extend(
[
node(
"PrePlant",
10,
[],
[],
vec3(
pre_plant_source["localPosition"]["x"],
pre_plant_source["localPosition"]["y"],
pre_plant_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/PrePlant",
),
node(
"PostPlant",
10,
[],
[],
vec3(
post_plant_source["localPosition"]["x"],
post_plant_source["localPosition"]["y"],
post_plant_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/PostPlant",
),
]
)
transform_nodes = land_evidence["gridRoot"]["transformNodes"]
scaled_source, rotate_source, grid_origin_source = transform_nodes
scaled_index = len(objects)
rotate_index = scaled_index + 1
grid_origin_index = scaled_index + 2
objects[10]["_children"] = [ref(11), ref(scaled_index), ref(pre_plant_index), ref(post_plant_index)]
objects.extend(
[
node(
"Scaled",
10,
[rotate_index],
[],
vec3(
scaled_source["localPosition"]["x"],
scaled_source["localPosition"]["y"],
scaled_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scaled",
),
node(
"Rotate",
scaled_index,
[grid_origin_index],
[],
vec3(
rotate_source["localPosition"]["x"],
rotate_source["localPosition"]["y"],
rotate_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scaled/Rotate",
),
node(
"GridOrigin",
rotate_index,
[],
[],
vec3(
grid_origin_source["localPosition"]["x"],
grid_origin_source["localPosition"]["y"],
grid_origin_source["localPosition"]["z"],
),
id_key="root/scene/farm_scene_v3/Scaled/Rotate/GridOrigin",
),
]
)
grid_indices: list[int] = []
icon_skeleton_sources = land_evidence["iconSkeletonComponents"]
scene_land_skeleton_uuid = scene_land_spine_uuids()["skeletonDataUuid"]
for source_order, land in enumerate(sorted(land_evidence["visibleLands"], key=lambda item: item["cell"]["sourceIndex"])):
cell = land["cell"]
icon = land["icon"]
asset_id = land["mockRender"]["assetId"]
skeleton_source = icon_skeleton_sources[source_order]
default_animation = "land_locked" if asset_id == "land_extend" else asset_id
has_expand_board = asset_id == "land_extend"
grid_index = len(objects)
icon_index = grid_index + 1
grid_ui_index = grid_index + 2
icon_ui_index = grid_index + 3
icon_skeleton_index = grid_index + 4
board_index = grid_index + 5
board_ui_index = grid_index + 6
board_sprite_index = grid_index + 7
grid_indices.append(grid_index)
land_objects = [
node(
cell["name"],
grid_origin_index,
[board_index, icon_index] if has_expand_board else [icon_index],
[grid_ui_index],
vec3(cell["localPosition"]["x"], cell["localPosition"]["y"], cell["localPosition"]["z"]),
id_key=f"root/scene/{cell['path']}",
),
node(
"icon",
grid_index,
[],
[icon_ui_index, icon_skeleton_index],
vec3(icon["localPosition"]["x"], icon["localPosition"]["y"], icon["localPosition"]["z"]),
id_key=f"root/scene/{icon['path']}",
),
ui_transform(grid_index, cell["size"]["x"], cell["size"]["y"], cell["anchor"]["x"], cell["anchor"]["y"]),
ui_transform(icon_index, icon["size"]["x"], icon["size"]["y"], icon["anchor"]["x"], icon["anchor"]["y"]),
spine_skeleton_component(
icon_index,
scene_land_skeleton_uuid,
str(skeleton_source["defaultSkin"]),
default_animation,
),
]
if has_expand_board:
board_texture = textures["land_extend"]
land_objects.extend(
[
node(
"expand_board",
grid_index,
[],
[board_ui_index, board_sprite_index],
vec3(icon["localPosition"]["x"], icon["localPosition"]["y"], icon["localPosition"]["z"]),
id_key=f"root/scene/{icon['path']}/expand_board",
),
ui_transform(
board_index,
board_texture["pixelWidth"],
board_texture["pixelHeight"],
0.5,
0.5,
),
sprite_component(board_index, board_texture["spriteFrameUuid"]),
]
)
objects.extend(land_objects)
top_hud_index = build_top_hud_nodes(objects, ui_index, top_hud_evidence, textures)
interaction_index = build_land_interaction_nodes(objects, ui_index, interaction_evidence, textures)
objects[grid_origin_index]["_children"] = [ref(index) for index in grid_indices]
objects[ui_index]["_children"] = [ref(ui_camera_index), ref(top_hud_index), ref(interaction_index)]
interaction_component_index = len(objects)
objects.append(stage1_land_interaction_component(2))
objects[2]["_components"].append(ref(interaction_component_index))
template = read_json(SCENE_TEMPLATE)
globals_start = len(objects)
mapping = {old: globals_start + (old - 8) for old in range(8, 15)}
objects.extend(remap_ids(copy.deepcopy(template[8:15]), mapping))
objects[1]["_globals"] = ref(globals_start)
return objects
def write_scene_assets(
evidence: dict,
land_evidence: dict,
interaction_evidence: dict,
decor_evidence: dict,
top_hud_evidence: dict,
textures: dict[str, dict],
decor_spines: dict[str, dict],
) -> None:
scene_dir = ASSETS / "scenes"
ensure_directory_meta(scene_dir)
scene_path = scene_dir / "stage1_world_camera.scene"
write_json(scene_path, build_scene(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence, textures, decor_spines))
write_json(
scene_path.with_suffix(scene_path.suffix + ".meta"),
{
"ver": "1.1.50",
"importer": "scene",
"imported": True,
"uuid": SCENE_UUID,
"files": [".json"],
"subMetas": {},
"userData": {},
},
)
def main() -> int:
evidence = read_json(SOURCE_JSON)
land_evidence = read_json(LAND_SOURCE_JSON)
interaction_evidence = read_json(INTERACTION_SOURCE_JSON)
decor_evidence = read_json(DECOR_SOURCE_JSON)
top_hud_evidence = read_json(TOP_HUD_SOURCE_JSON)
for path in [ASSETS, ASSETS / "textures", ASSETS / "spine", ASSETS / "scenes", ASSETS / "scripts", ASSETS / "configs"]:
ensure_directory_meta(path)
textures = write_texture_assets(evidence, land_evidence, interaction_evidence, top_hud_evidence)
spine_asset = write_spine_assets(land_evidence)
decor_spines = write_decor_spine_assets(decor_evidence)
write_script_assets(land_evidence, interaction_evidence, top_hud_evidence)
write_config_assets(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence)
write_project_settings(evidence)
write_scene_assets(evidence, land_evidence, interaction_evidence, decor_evidence, top_hud_evidence, textures, decor_spines)
report = {
"status": "pass",
"project": str(PROJECT),
"sceneUuid": SCENE_UUID,
"scenePath": str(ASSETS / "scenes" / "stage1_world_camera.scene"),
"textures": textures,
"spineAsset": spine_asset,
"decorSpineAssets": decor_spines,
"decorNodeCount": len(decor_evidence["generatedNodes"]),
"visibleLandCount": len(land_evidence["visibleLands"]),
"interactionAssetCount": len(interaction_evidence["assets"]),
"topHudAssetCount": len(top_hud_evidence["assets"]),
"topHudNodeCount": len(top_hud_evidence["nodes"]),
"mockSeedCount": len(interaction_evidence["mockSeeds"]),
"mockUser": top_hud_evidence["mockUser"],
}
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())