From cd6c65a555c955bc2e8581998dc276b12431b31c Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Fri, 13 Feb 2026 21:50:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(create=5Fproject):=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E5=88=9B=E5=BB=BA=E5=B7=A5=E5=85=B7=E5=B9=B6?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BC=80=E5=8F=91=E7=8E=AF=E5=A2=83=E6=A3=80?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 DevToolsChecker 类用于检测开发环境工具链 - 自动克隆引擎源码到项目目录 - 添加系统字体自动复制功能 - 重构 xmake.lua 模板以支持本地引擎源码构建 - 改进 README.md 模板结构和内容 - 添加开发工具链下载提示功能 --- scripts/create_project.py | 406 +++++++++++++++++++++++++++++++++++--- 1 file changed, 375 insertions(+), 31 deletions(-) diff --git a/scripts/create_project.py b/scripts/create_project.py index 8fd8ac9..afdcb83 100644 --- a/scripts/create_project.py +++ b/scripts/create_project.py @@ -11,12 +11,109 @@ Extra2D 项目脚手架工具 python create_project.py my_game python create_project.py my_game --path ./games python create_project.py my_game --author "Your Name" + +项目结构: + my_game/ + ├── src/ + │ └── main.cpp + ├── romfs/ + │ └── assets/ + ├── xmake.lua + ├── README.md + └── Extra2D/ # 引擎源码(自动克隆) + ├── Extra2D/ + ├── xmake/ + └── ... """ import os import sys import argparse +import subprocess +import platform +import shutil from pathlib import Path +from typing import Optional + +ENGINE_REPO = "https://github.com/ChestnutYueyue/Extra2D.git" +DEVKITPRO_URL = "https://github.com/devkitPro/installer/releases/download/v3.0.3/devkitProUpdater-3.0.3.exe" +MINGW_URL = "https://github.com/brechtsanders/winlibs_mingw/releases/download/16.0.0-snapshot20251026posix-14.0.0-ucrt-r1/winlibs-i686-posix-dwarf-gcc-16.0.0-snapshot20251026-mingw-w64ucrt-14.0.0-r1.zip" + + +class DevToolsChecker: + """开发工具检测器""" + + def __init__(self): + self.is_windows = platform.system() == "Windows" + + def check_git(self) -> bool: + """检查 Git 是否安装""" + return shutil.which("git") is not None + + def check_xmake(self) -> bool: + """检查 xmake 是否安装""" + return shutil.which("xmake") is not None + + def check_mingw(self) -> bool: + """检查 MinGW 是否安装""" + if shutil.which("gcc"): + result = subprocess.run( + ["gcc", "-dumpmachine"], + capture_output=True, + text=True, + creationflags=subprocess.CREATE_NO_WINDOW if self.is_windows else 0 + ) + return "mingw" in result.stdout.lower() + return False + + def check_devkitpro(self) -> bool: + """检查 devkitPro 是否安装""" + devkitpro = os.environ.get("DEVKITPRO", "C:/devkitPro") + devkita64 = os.path.join(devkitpro, "devkitA64") + return os.path.isdir(devkita64) + + def get_missing_tools(self) -> dict: + """获取缺失的工具列表""" + missing = {} + + if not self.check_git(): + missing["git"] = "Git 版本控制工具" + + if not self.check_xmake(): + missing["xmake"] = "xmake 构建工具" + + return missing + + def get_missing_dev_tools(self) -> dict: + """获取缺失的开发工具链""" + missing = {} + + if not self.check_mingw(): + missing["mingw"] = MINGW_URL + + if not self.check_devkitpro(): + missing["devkitpro"] = DEVKITPRO_URL + + return missing + + def print_tool_status(self): + """打印工具状态""" + print("\n========================================") + print("开发环境检测") + print("========================================") + + tools = [ + ("Git", self.check_git()), + ("xmake", self.check_xmake()), + ("MinGW (Windows开发)", self.check_mingw()), + ("devkitPro (Switch开发)", self.check_devkitpro()), + ] + + for name, installed in tools: + status = "✓ 已安装" if installed else "✗ 未安装" + print(f" {name}: {status}") + + print("========================================\n") class ProjectCreator: @@ -25,14 +122,13 @@ class ProjectCreator: def __init__(self, project_name: str, output_path: str = None, author: str = "Extra2D Team"): self.project_name = project_name self.author = author - self.script_dir = Path(__file__).parent.resolve() - self.engine_root = self.script_dir.parent - self.output_path = Path(output_path) if output_path else self.engine_root / "projects" + self.output_path = Path(output_path) if output_path else Path.cwd() self.project_path = self.output_path / project_name + self.engine_path = self.project_path / "Extra2D" def create(self) -> bool: """创建项目""" - print(f"正在创建 Extra2D 项目: {self.project_name}") + print(f"\n正在创建 Extra2D 项目: {self.project_name}") print(f"项目路径: {self.project_path}") if self.project_path.exists(): @@ -41,17 +137,20 @@ class ProjectCreator: try: self._create_directories() + self._copy_system_font() self._create_main_cpp() self._create_xmake_lua() self._create_gitignore() self._create_readme() - print(f"\n项目创建成功!") - print(f"\n后续步骤:") - print(f" 1. cd {self.project_path}") - print(f" 2. xmake config") - print(f" 3. xmake build") - print(f" 4. xmake run {self.project_name}") + print(f"\n✓ 项目创建成功!") + + if self._clone_engine(): + self._print_next_steps() + else: + print("\n请手动克隆引擎源码:") + print(f" cd {self.project_path}") + print(f" git clone {ENGINE_REPO} Extra2D") return True except Exception as e: @@ -66,6 +165,61 @@ class ProjectCreator: (self.project_path / "romfs" / "assets" / "audio").mkdir(parents=True, exist_ok=True) (self.project_path / "src").mkdir(parents=True, exist_ok=True) + def _copy_system_font(self): + """复制系统字体到项目 assets 目录""" + print("复制系统字体...") + font_dest = self.project_path / "romfs" / "assets" / "font.ttf" + + if platform.system() == "Windows": + font_sources = [ + Path("C:/Windows/Fonts/msyh.ttc"), # 微软雅黑 + Path("C:/Windows/Fonts/msyh.ttf"), # 微软雅黑 (部分系统) + Path("C:/Windows/Fonts/simhei.ttf"), # 黑体 + Path("C:/Windows/Fonts/simsun.ttc"), # 宋体 + ] + else: + font_sources = [ + Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"), + Path("/usr/share/fonts/TTF/DejaVuSans.ttf"), + Path("/System/Library/Fonts/PingFang.ttc"), # macOS + ] + + for font_src in font_sources: + if font_src.exists(): + try: + shutil.copy2(str(font_src), str(font_dest)) + print(f"✓ 已复制字体: {font_src.name}") + return True + except Exception as e: + print(f"✗ 复制字体失败: {e}") + continue + + print("⚠ 未找到系统字体,请手动添加字体文件到 romfs/assets/font.ttf") + return False + + def _clone_engine(self) -> bool: + """克隆引擎源码到项目目录内的 Extra2D 子目录""" + print(f"\n正在克隆 Extra2D 引擎源码...") + print(f"仓库地址: {ENGINE_REPO}") + print(f"目标路径: {self.engine_path}") + + try: + result = subprocess.run( + ["git", "clone", ENGINE_REPO, str(self.engine_path)], + cwd=str(self.project_path), + creationflags=subprocess.CREATE_NO_WINDOW if platform.system() == "Windows" else 0 + ) + + if result.returncode == 0: + print("✓ 引擎源码克隆成功!") + return True + else: + print("✗ 引擎源码克隆失败!") + return False + except Exception as e: + print(f"✗ 克隆过程中出错: {e}") + return False + def _create_main_cpp(self): """创建 main.cpp 文件(放在 src 目录下)""" print("创建 src/main.cpp...") @@ -183,7 +337,7 @@ int main(int argc, char **argv) {{ f.write(content) def _create_xmake_lua(self): - """创建 xmake.lua 文件(使用远程包引入方式)""" + """创建 xmake.lua 文件(使用项目内的引擎源码)""" print("创建 xmake.lua...") content = f'''-- ============================================== -- {self.project_name} - Extra2D 游戏项目 @@ -204,10 +358,96 @@ set_encodings("utf-8") add_rules("mode.debug", "mode.release") -- ============================================== --- 依赖包(通过 xmake 远程包管理) +-- 平台检测与配置 -- ============================================== -add_requires("extra2d") +local host_plat = os.host() +local target_plat = get_config("plat") or host_plat +local supported_plats = {{mingw = true, switch = true}} + +if not supported_plats[target_plat] then + if host_plat == "windows" then + target_plat = "mingw" + else + error("Unsupported platform: " .. target_plat .. ". Supported platforms: mingw, switch") + end +end + +set_plat(target_plat) + +if target_plat == "switch" then + set_arch("arm64") +elseif target_plat == "mingw" then + set_arch("x86_64") +end + +-- ============================================== +-- 加载工具链配置 +-- ============================================== + +-- 引擎目录(克隆的仓库目录) +local engine_repo = "Extra2D" +-- 引擎源码目录(仓库内的 Extra2D 子目录) +local engine_src = path.join(engine_repo, "Extra2D") + +if target_plat == "switch" then + includes(path.join(engine_repo, "xmake/toolchains/switch.lua")) + set_toolchains("switch") +elseif target_plat == "mingw" then + set_toolchains("mingw") +end + +-- ============================================== +-- 添加依赖包 (MinGW) +-- ============================================== + +if target_plat == "mingw" then + add_requires("glm", "libsdl2", "libsdl2_mixer") +end + +-- ============================================== +-- 引擎库定义 +-- ============================================== + +target("extra2d") + set_kind("static") + + -- 引擎源文件 + add_files(path.join(engine_src, "src/**.cpp")) + add_files(path.join(engine_src, "src/glad/glad.c")) + + -- 头文件路径 + add_includedirs(path.join(engine_src, "include"), {{public = true}}) + add_includedirs(path.join(engine_src, "include/extra2d/platform"), {{public = true}}) + + -- 平台配置 + if target_plat == "switch" then + local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" + add_includedirs(devkitPro .. "/portlibs/switch/include", {{public = true}}) + add_linkdirs(devkitPro .. "/portlibs/switch/lib") + add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg", + "modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau", + {{public = true}}) + elseif target_plat == "mingw" then + add_packages("glm", "libsdl2", "libsdl2_mixer", {{public = true}}) + add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {{public = true}}) + end + + -- 编译器标志 + add_cxflags("-Wall", "-Wextra", {{force = true}}) + add_cxflags("-Wno-unused-variable", "-Wno-unused-function", "-Wno-unused-parameter", {{force = true}}) + add_cxflags("-Wno-strict-aliasing", "-Wno-implicit-fallthrough", {{force = true}}) + add_cxflags("-Wno-missing-field-initializers", {{force = true}}) + add_cxxflags("-Wno-deprecated-copy", "-Wno-class-memaccess", {{force = true}}) + + if is_mode("debug") then + add_defines("E2D_DEBUG", "_DEBUG", {{public = true}}) + add_cxxflags("-O0", "-g", {{force = true}}) + else + add_defines("NDEBUG", {{public = true}}) + add_cxxflags("-O2", {{force = true}}) + end +target_end() -- ============================================== -- 目标定义 @@ -216,7 +456,7 @@ add_requires("extra2d") target("{self.project_name}") set_kind("binary") add_files("src/**.cpp") - add_packages("extra2d") + add_deps("extra2d") -- Nintendo Switch 平台配置 if is_plat("switch") then @@ -278,6 +518,18 @@ target("{self.project_name}") end) end target_end() + +-- ============================================== +-- 项目信息输出 +-- ============================================== + +print("========================================") +print("{self.project_name} Build Configuration") +print("========================================") +print("Platform: " .. target_plat) +print("Architecture: " .. (get_config("arch") or "auto")) +print("Mode: " .. (is_mode("debug") and "debug" or "release")) +print("========================================") ''' with open(self.project_path / "xmake.lua", "w", encoding="utf-8") as f: f.write(content) @@ -320,6 +572,21 @@ Thumbs.db - MinGW (Windows) - Nintendo Switch +## 项目结构 + +``` +{self.project_name}/ +├── src/ # 源代码目录 +│ └── main.cpp # 主程序入口 +├── romfs/ # 资源文件目录 +│ └── assets/ +│ ├── images/ # 图片资源 +│ └── audio/ # 音频资源 +├── Extra2D/ # 引擎源码 +├── xmake.lua # 构建配置 +└── README.md # 项目说明 +``` + ## 构建说明 ### 前置要求 @@ -354,20 +621,6 @@ xmake build # 生成的 NRO 文件位于 build/switch/ 目录 ``` -### 项目结构 - -``` -{self.project_name}/ -├── src/ # 源代码目录 -│ └── main.cpp # 主程序入口 -├── romfs/ # 资源文件目录 -│ └── assets/ -│ ├── images/ # 图片资源 -│ └── audio/ # 音频资源 -├── xmake.lua # 构建配置 -└── README.md # 项目说明 -``` - ## 资源文件 请将游戏所需的资源文件放入 `romfs/assets/` 目录: @@ -387,6 +640,74 @@ MIT License with open(self.project_path / "README.md", "w", encoding="utf-8") as f: f.write(content) + def _print_next_steps(self): + """打印后续步骤""" + print(f"\n后续步骤:") + print(f" 1. cd {self.project_path}") + print(f" 2. xmake config -p mingw") + print(f" 3. xmake build") + print(f" 4. xmake run {self.project_name}") + + +def prompt_download_tools(dev_tools: dict): + """ + 提示用户下载缺失的开发工具链 + + @param dev_tools: 缺失的开发工具链 + """ + if not dev_tools: + return + + print("\n========================================") + print("检测到以下开发工具链未安装:") + print("========================================") + + if "mingw" in dev_tools: + print(" - MinGW-w64 (Windows 开发)") + if "devkitpro" in dev_tools: + print(" - devkitPro (Switch 开发)") + + print("\n是否需要下载这些工具?") + print(" 1. 下载 MinGW-w64 (Windows 开发)") + print(" 2. 下载 devkitPro (Switch 开发)") + print(" 3. 下载全部") + print(" 4. 跳过下载") + + try: + choice = input("\n请选择 (1-4): ").strip() + + import webbrowser + + if choice == "1" and "mingw" in dev_tools: + print(f"\n正在打开 MinGW 下载页面...") + print(f"下载地址: {MINGW_URL}") + webbrowser.open(MINGW_URL) + elif choice == "2" and "devkitpro" in dev_tools: + print(f"\n正在打开 devkitPro 下载页面...") + print(f"下载地址: {DEVKITPRO_URL}") + webbrowser.open(DEVKITPRO_URL) + elif choice == "3": + if "mingw" in dev_tools: + print(f"\n正在打开 MinGW 下载页面...") + print(f"下载地址: {MINGW_URL}") + webbrowser.open(MINGW_URL) + if "devkitpro" in dev_tools: + print(f"\n正在打开 devkitPro 下载页面...") + print(f"下载地址: {DEVKITPRO_URL}") + webbrowser.open(DEVKITPRO_URL) + else: + print("\n已跳过下载。") + + if dev_tools: + print("\n安装提示:") + if "mingw" in dev_tools: + print(" MinGW: 解压后将 bin 目录添加到系统 PATH 环境变量") + if "devkitpro" in dev_tools: + print(" devkitPro: 运行安装程序,按提示完成安装") + + except KeyboardInterrupt: + print("\n已取消。") + def main(): parser = argparse.ArgumentParser( @@ -397,6 +718,16 @@ def main(): python create_project.py my_game python create_project.py my_game --path ./games python create_project.py my_game --author "Your Name" + +项目结构: + my_game/ + ├── src/ + │ └── main.cpp + ├── romfs/ + │ └── assets/ + ├── xmake.lua + ├── README.md + └── Extra2D/ # 引擎源码(自动克隆) """ ) @@ -407,7 +738,7 @@ def main(): parser.add_argument( "--path", "-p", - help="项目创建路径(默认为引擎根目录下的 projects 文件夹)" + help="项目创建路径(默认为当前目录)" ) parser.add_argument( @@ -418,12 +749,25 @@ def main(): args = parser.parse_args() - # 验证项目名称 + checker = DevToolsChecker() + checker.print_tool_status() + + missing_tools = checker.get_missing_tools() + if missing_tools: + print("错误: 缺少必要的工具:") + for tool, desc in missing_tools.items(): + print(f" - {desc}") + print("\n请先安装这些工具后再创建项目。") + sys.exit(1) + + dev_tools = checker.get_missing_dev_tools() + if dev_tools: + prompt_download_tools(dev_tools) + if not args.name.replace("_", "").isalnum(): print("错误: 项目名称只能包含字母、数字和下划线") sys.exit(1) - # 创建项目 creator = ProjectCreator( project_name=args.name, output_path=args.path,