diff --git a/scripts/create_project.py b/scripts/create_project.py new file mode 100644 index 0000000..8fd8ac9 --- /dev/null +++ b/scripts/create_project.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Extra2D 项目脚手架工具 +用于快速创建新的 Extra2D 游戏项目 + +用法: + python create_project.py <项目名称> [选项] + +示例: + python create_project.py my_game + python create_project.py my_game --path ./games + python create_project.py my_game --author "Your Name" +""" + +import os +import sys +import argparse +from pathlib import Path + + +class ProjectCreator: + """Extra2D 项目创建器""" + + 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.project_path = self.output_path / project_name + + def create(self) -> bool: + """创建项目""" + print(f"正在创建 Extra2D 项目: {self.project_name}") + print(f"项目路径: {self.project_path}") + + if self.project_path.exists(): + print(f"错误: 项目目录已存在: {self.project_path}") + return False + + try: + self._create_directories() + 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}") + + return True + except Exception as e: + print(f"创建项目时出错: {e}") + return False + + def _create_directories(self): + """创建项目目录结构""" + print("创建目录结构...") + self.project_path.mkdir(parents=True, exist_ok=True) + (self.project_path / "romfs" / "assets" / "images").mkdir(parents=True, exist_ok=True) + (self.project_path / "romfs" / "assets" / "audio").mkdir(parents=True, exist_ok=True) + (self.project_path / "src").mkdir(parents=True, exist_ok=True) + + def _create_main_cpp(self): + """创建 main.cpp 文件(放在 src 目录下)""" + print("创建 src/main.cpp...") + content = f'''#include + +using namespace extra2d; + +// ============================================================================ +// 主场景 +// ============================================================================ + +/** + * @brief 主游戏场景 + */ +class MainScene : public Scene {{ +public: + /** + * @brief 场景进入时调用 + */ + void onEnter() override {{ + E2D_LOG_INFO("MainScene::onEnter - 进入场景"); + + // 设置背景颜色 + setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f)); + + // 加载字体(请确保 assets 目录下有 font.ttf 文件) + auto &resources = Application::instance().resources(); + font_ = resources.loadFont("assets/font.ttf", 32, true); + + if (!font_) {{ + E2D_LOG_ERROR("字体加载失败!请确保 assets 目录下有 font.ttf 文件"); + return; + }} + + // 创建标题文本 + auto title = Text::create("{self.project_name}", font_); + title->setCoordinateSpace(CoordinateSpace::Screen); + title->setScreenPosition(640.0f, 200.0f); + title->setAnchor(0.5f, 0.5f); + title->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); + addChild(title); + + // 创建提示文本 + auto hint = Text::create("按 START 退出", font_); + hint->setCoordinateSpace(CoordinateSpace::Screen); + hint->setScreenPosition(640.0f, 650.0f); + hint->setAnchor(0.5f, 0.5f); + hint->setTextColor(Color(0.7f, 0.7f, 0.7f, 1.0f)); + addChild(hint); + }} + + /** + * @brief 每帧更新时调用 + * @param dt 时间间隔(秒) + */ + void onUpdate(float dt) override {{ + Scene::onUpdate(dt); + + // 检查退出按键 + auto &input = Application::instance().input(); + if (input.isButtonPressed(GamepadButton::Start)) {{ + E2D_LOG_INFO("退出应用"); + Application::instance().quit(); + }} + }} + +private: + Ptr font_; +}}; + +// ============================================================================ +// 程序入口 +// ============================================================================ + +int main(int argc, char **argv) {{ + // 初始化日志系统 + Logger::init(); + Logger::setLevel(LogLevel::Debug); + + E2D_LOG_INFO("========================"); + E2D_LOG_INFO("{self.project_name}"); + E2D_LOG_INFO("========================"); + + // 获取应用实例 + auto &app = Application::instance(); + + // 配置应用 + AppConfig config; + config.title = "{self.project_name}"; + config.width = 1280; + config.height = 720; + config.vsync = true; + config.fpsLimit = 60; + + // 初始化应用 + if (!app.init(config)) {{ + E2D_LOG_ERROR("应用初始化失败!"); + return -1; + }} + + // 进入主场景 + app.enterScene(makePtr()); + + E2D_LOG_INFO("开始主循环..."); + + // 运行应用 + app.run(); + + E2D_LOG_INFO("应用结束"); + + return 0; +}} +''' + with open(self.project_path / "src" / "main.cpp", "w", encoding="utf-8") as f: + f.write(content) + + def _create_xmake_lua(self): + """创建 xmake.lua 文件(使用远程包引入方式)""" + print("创建 xmake.lua...") + content = f'''-- ============================================== +-- {self.project_name} - Extra2D 游戏项目 +-- 构建系统: Xmake +-- 支持平台: MinGW (Windows), Nintendo Switch +-- ============================================== + +-- 项目元信息 +set_project("{self.project_name}") +set_version("1.0.0") +set_license("MIT") + +-- 语言设置 +set_languages("c++17") +set_encodings("utf-8") + +-- 构建模式 +add_rules("mode.debug", "mode.release") + +-- ============================================== +-- 依赖包(通过 xmake 远程包管理) +-- ============================================== + +add_requires("extra2d") + +-- ============================================== +-- 目标定义 +-- ============================================== + +target("{self.project_name}") + set_kind("binary") + add_files("src/**.cpp") + add_packages("extra2d") + + -- Nintendo Switch 平台配置 + if is_plat("switch") then + set_targetdir("build/switch") + + -- 构建后生成 NRO 文件 + after_build(function (target) + local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" + local elf_file = target:targetfile() + local output_dir = path.directory(elf_file) + local nacp_file = path.join(output_dir, "{self.project_name}.nacp") + local nro_file = path.join(output_dir, "{self.project_name}.nro") + local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") + local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") + + if os.isfile(nacptool) and os.isfile(elf2nro) then + os.vrunv(nacptool, {{"--create", "{self.project_name}", "{self.author}", "1.0.0", nacp_file}}) + local project_dir = os.scriptdir() + local romfs = path.join(project_dir, "romfs") + if os.isdir(romfs) then + os.vrunv(elf2nro, {{elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs}}) + else + os.vrunv(elf2nro, {{elf_file, nro_file, "--nacp=" .. nacp_file}}) + end + print("Generated NRO: " .. nro_file) + end + end) + + -- 打包时将 NRO 文件复制到 package 目录 + after_package(function (target) + local nro_file = path.join(target:targetdir(), "{self.project_name}.nro") + local package_dir = target:packagedir() + if os.isfile(nro_file) and package_dir then + os.cp(nro_file, package_dir) + print("Copied NRO to package: " .. package_dir) + end + end) + + -- Windows 平台配置 + elseif is_plat("mingw", "windows") then + set_targetdir("build/windows") + add_ldflags("-mwindows", {{force = true}}) + + -- 复制资源到输出目录 + after_build(function (target) + local project_dir = os.scriptdir() + local romfs = path.join(project_dir, "romfs") + if os.isdir(romfs) then + local target_dir = path.directory(target:targetfile()) + local assets_dir = path.join(target_dir, "assets") + + if not os.isdir(assets_dir) then + os.mkdir(assets_dir) + end + + os.cp(path.join(romfs, "assets/**"), assets_dir) + print("Copied assets to: " .. assets_dir) + end + end) + end +target_end() +''' + with open(self.project_path / "xmake.lua", "w", encoding="utf-8") as f: + f.write(content) + + def _create_gitignore(self): + """创建 .gitignore 文件""" + print("创建 .gitignore...") + content = '''# 构建产物 +.build/ +build/ +.xmake/ + +# IDE 配置 +.vscode/ +.idea/ +*.swp +*.swo + +# 操作系统文件 +.DS_Store +Thumbs.db + +# 临时文件 +*.tmp +*.temp +*.log +''' + with open(self.project_path / ".gitignore", "w", encoding="utf-8") as f: + f.write(content) + + def _create_readme(self): + """创建 README.md 文件""" + print("创建 README.md...") + content = f'''# {self.project_name} + +使用 Extra2D 游戏引擎开发的游戏项目。 + +## 支持平台 + +- MinGW (Windows) +- Nintendo Switch + +## 构建说明 + +### 前置要求 + +- [xmake](https://xmake.io/) 构建工具 +- MinGW-w64 工具链 (Windows) 或 devkitPro (Switch) + +### 构建步骤 + +#### Windows 平台 + +```bash +# 配置项目 +xmake config -p mingw + +# 构建项目 +xmake build + +# 运行项目 +xmake run {self.project_name} +``` + +#### Nintendo Switch 平台 + +```bash +# 配置项目 +xmake config -p switch + +# 构建项目 +xmake build + +# 生成的 NRO 文件位于 build/switch/ 目录 +``` + +### 项目结构 + +``` +{self.project_name}/ +├── src/ # 源代码目录 +│ └── main.cpp # 主程序入口 +├── romfs/ # 资源文件目录 +│ └── assets/ +│ ├── images/ # 图片资源 +│ └── audio/ # 音频资源 +├── xmake.lua # 构建配置 +└── README.md # 项目说明 +``` + +## 资源文件 + +请将游戏所需的资源文件放入 `romfs/assets/` 目录: + +- `images/` - 图片资源(PNG, JPG 等) +- `audio/` - 音频资源(WAV, OGG 等) +- `font.ttf` - 字体文件(需要手动添加) + +## 作者 + +{self.author} + +## 许可证 + +MIT License +''' + with open(self.project_path / "README.md", "w", encoding="utf-8") as f: + f.write(content) + + +def main(): + parser = argparse.ArgumentParser( + description="Extra2D 项目脚手架工具", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + python create_project.py my_game + python create_project.py my_game --path ./games + python create_project.py my_game --author "Your Name" + """ + ) + + parser.add_argument( + "name", + help="项目名称(只能包含字母、数字和下划线)" + ) + + parser.add_argument( + "--path", "-p", + help="项目创建路径(默认为引擎根目录下的 projects 文件夹)" + ) + + parser.add_argument( + "--author", "-a", + default="Extra2D Team", + help="项目作者(默认: Extra2D Team)" + ) + + args = parser.parse_args() + + # 验证项目名称 + if not args.name.replace("_", "").isalnum(): + print("错误: 项目名称只能包含字母、数字和下划线") + sys.exit(1) + + # 创建项目 + creator = ProjectCreator( + project_name=args.name, + output_path=args.path, + author=args.author + ) + + if not creator.create(): + sys.exit(1) + + +if __name__ == "__main__": + main()