feat(scripts): 添加 Extra2D 项目脚手架工具
实现创建 Extra2D 游戏项目的自动化脚本,包括: - 创建标准目录结构 - 生成 main.cpp 模板文件 - 配置 xmake.lua 构建脚本 - 添加 .gitignore 和 README.md - 支持 Windows 和 Nintendo Switch 平台
This commit is contained in:
parent
f02b368dc9
commit
06de1e79af
|
|
@ -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 <extra2d/extra2d.h>
|
||||||
|
|
||||||
|
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<FontAtlas> 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<MainScene>());
|
||||||
|
|
||||||
|
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()
|
||||||
Loading…
Reference in New Issue