import pandas as pd from pathlib import Path # ============= 配置 ============= # Excel 路径 EXCEL_PATH = "found_方块.xlsx" # 根据你的实际文件名改 # 作为模板的 prefab(就是你贴出来那份,包含 Ground / Border / Grid) TEMPLATE_PREFAB = "Level90116.prefab" # 根据实际文件名改 OLD_LEVEL_NAME = "Level90116" # 模板 prefab 中的旧关卡名 # 输出目录 OUTPUT_DIR = Path("GeneratedPrefabs") OUTPUT_DIR.mkdir(exist_ok=True) # ============= Tile 行模板 ============= # 使用 Ground 里那种 tile(m_TileIndex = 0, m_TileSpriteIndex = 0) # 注意:花括号 {{ }} 是为了在 format 之后输出字面上的 { } TILE_LINE = """ - first: {{x: {x}, y: {y}, z: 0}} second: serializedVersion: 2 m_TileIndex: {tile_index} m_TileSpriteIndex: {sprite_index} m_TileMatrixIndex: 0 m_TileColorIndex: 0 m_TileObjectToInstantiateIndex: 65535 dummyAlignment: 0 m_AllTileFlags: 1073741825 """ # ============= 工具函数 ============= def load_template_text() -> str: """读取整份 prefab 模板文本""" return Path(TEMPLATE_PREFAB).read_text(encoding="utf-8") def replace_level_name(text: str, map_no) -> str: """ 把根节点名字从 Level80317 改为 Level{map_no} (只替换这一行) """ # 注意:只替换一次,避免误伤别的地方 old = f"m_Name: {OLD_LEVEL_NAME}" new = f"m_Name: Level{int(map_no)}" return text.replace(old, new, 1) def generate_tiles_block(coords): """ 根据坐标列表生成 m_Tiles: 区块(只包含 m_Tiles 本身,不包括 m_AnimatedTiles 之后的内容) coords: [(x, y, tile_index, sprite_index), ...] """ block = " m_Tiles:\n" for item in coords: if len(item) == 2: x, y = item tile_index = 1 sprite_index = 0 else: x, y, tile_index, sprite_index = item block += TILE_LINE.format(x=int(x), y=int(y), tile_index=int(tile_index), sprite_index=int(sprite_index)) return block def replace_ground_tiles(text: str, coords) -> str: """ 只修改 Ground Tilemap 的 m_Tiles: 列表 Border 的 m_Tiles: {} 不动 """ # 找到第一个 m_Tiles:(就是 Ground 的) # 本函数保持向后兼容性:如果模板中有多个 Tilemap,默认替换第一个 m_Tiles 区块(通常是 Ground) pos = text.find(" m_Tiles:") if pos == -1: raise RuntimeError("在模板 prefab 中找不到 'm_Tiles:'。") pos2 = text.find(" m_AnimatedTiles:", pos) if pos2 == -1: raise RuntimeError("在模板 prefab 中找不到 'm_AnimatedTiles:' 用来截断。") before = text[:pos] after = text[pos2:] tiles_block = generate_tiles_block(coords) return before + tiles_block + after def replace_tilemap_by_gameobject_name(text: str, gameobject_name: str, tiles_coords) -> str: """ 根据 GameObject 名称(m_Name)找到紧随其后的第一个 m_Tiles: 块并替换内容。 tiles_coords: list of (x,y,tile_index,sprite_index) 或 (x,y) """ lower_text = text.lower() target_name = f"m_name: {gameobject_name.lower()}" name_pos = lower_text.find(target_name) if name_pos == -1: raise RuntimeError(f"在 prefab 中找不到 GameObject 名称: {gameobject_name}") tiles_pos = lower_text.find(" m_tiles:", name_pos) if tiles_pos == -1: raise RuntimeError(f"在 prefab 中找不到 {gameobject_name} 对应的 m_Tiles: 区块") animated_pos = lower_text.find(" m_animatedtiles:", tiles_pos) if animated_pos == -1: raise RuntimeError("找不到 m_AnimatedTiles 来截断。") before = text[:tiles_pos] after = text[animated_pos:] if tiles_coords: new_tiles_block = generate_tiles_block(tiles_coords) else: new_tiles_block = " m_Tiles: {}\n" return before + new_tiles_block + after # ============= 主逻辑 ============= def main(): # 读取 Excel df = pd.read_excel(EXCEL_PATH) # 根据你的表头改名(如果不完全一样,请把左边字符串改成实际表头) df = df.rename(columns={ "地图号 (Map No)": "map", "X坐标 (X No)": "x", "Y坐标 (Y No)": "y", }) template_text = load_template_text() # 尝试读取 border 相关的 found 文件(可选) water_df = None wall_df = None jump_df = None if Path("found_water.xlsx").exists(): water_df = pd.read_excel("found_water.xlsx") water_df = water_df.rename(columns={"地图号 (Map No)": "map", "X坐标 (X No)": "x", "Y坐标 (Y No)": "y"}) if Path("found_wall.xlsx").exists(): wall_df = pd.read_excel("found_wall.xlsx") wall_df = wall_df.rename(columns={"地图号 (Map No)": "map", "X坐标 (X No)": "x", "Y坐标 (Y No)": "y"}) if Path("found_jump.xlsx").exists(): jump_df = pd.read_excel("found_jump.xlsx") jump_df = jump_df.rename(columns={"地图号 (Map No)": "map", "X坐标 (X No)": "x", "Y坐标 (Y No)": "y"}) # 按地图号分组,一个 Map 生成一个 prefab for map_no, group in df.groupby("map"): ground_coords = list(zip(group["x"], group["y"])) # 1) 换根节点名字 LevelXXXX text_with_name = replace_level_name(template_text, map_no) new_text = text_with_name # 2) 用当前地图的坐标替换 Ground 的 m_Tiles 区块 # 构建 (x,y,tile_index,sprite_index) 列表,Ground 使用索引 0/0(和原模板一致) ground_tiles = [(int(x), int(y), 1, 0) for x, y in ground_coords] if ground_tiles: try: new_text = replace_tilemap_by_gameobject_name(new_text, "Ground", ground_tiles) except Exception as e: # 回退到旧的替换方式 new_text = replace_ground_tiles(new_text, ground_coords) # 3) 对 Border 图层,合并 water/wall/jump(如果存在)并替换 border_tiles = [] # water -> m_TileIndex:2, m_TileSpriteIndex:2 if water_df is not None: wg = water_df[water_df['map'] == map_no] border_tiles += [(int(x), int(y), 5, 3) for x, y in zip(wg['x'], wg['y'])] # wall -> m_TileIndex:0, m_TileSpriteIndex:0 if wall_df is not None: wg = wall_df[wall_df['map'] == map_no] border_tiles += [(int(x), int(y), 4, 2) for x, y in zip(wg['x'], wg['y'])] # jump -> m_TileIndex:1, m_TileSpriteIndex:1 if jump_df is not None: wg = jump_df[jump_df['map'] == map_no] border_tiles += [(int(x), int(y), 3, 1) for x, y in zip(wg['x'], wg['y'])] if border_tiles: try: new_text = replace_tilemap_by_gameobject_name(new_text, "Border", border_tiles) except Exception as e: print(f"警告:替换 Border 时出错: {e}") else: # 没有 border 坐标,清空 Border 图层 try: new_text = replace_tilemap_by_gameobject_name(new_text, "Border", []) except Exception as e: print(f"警告:清空 Border 时出错: {e}") # 4) 写出新 prefab out_path = OUTPUT_DIR / f"Level{int(map_no)}.prefab" out_path.write_text(new_text, encoding="utf-8") print(f"生成 {out_path} ,共 Ground {len(ground_tiles)} 个 tile,Border {len(border_tiles)} 个 tile") if __name__ == "__main__": main()