用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ababup1192/flappy_bird --skill scene-editor命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Main.flix と Game.flix の書き方、XxxScene との連携方法のガイド。NodeTag 設計、trait instance のディスパッチ、カスタム effect(GamePhaseState・ライフサイクル副作用要求)、フェーズ遷移を含む(start / gameLoop の詳細は game-loop、個別の XxxScene 設計パターンは scene-editor を参照)
Game.flix の start と gameLoop の書き方ガイド。起動時の常駐ハンドラ設定(フェーズ Ref・エディタ・横断 effect)、フレーム毎の固定パイプライン、エディタ・ホットリロード統合、Scene への委譲ルールを含む。start / gameLoop / applyPhaseChange を編集するときに参照する
Flixの公式ドキュメントとプロジェクト固有のコーディングスタイルを確認する。パイプスタイル、エフェクト構文、テストの書き方、0.71.0固有の注意点を含む - Flixコードを新規作成・修正するとき、テストを書くとき、Flix構文を確認したいとき
基于 SOC 职业分类
正在显示 SKILL.md
| name | scene-editor |
| description | xxScene.flixを新規で作るとき・編集するとき、新しいゲームロジックを組む時に考えるべき設計パターン |
| user-invocable | false |
シーンの構築と更新に関する共通パターン。
mod XxxScene {
// (A) 型定義 — SpriteData, AreaData
// (B) 定数 — name / path / texture, speed, position
// (C) 構築 — add: Scene → Scene
// (D) ロジック — ready / process / start / input / move
// (E) ボイラプレート mapXxxSprite / mapXxxArea / mapXxx(合成)
}
NodeTagで使うデータは、Sceneごとに使用されるデータ型を定義する。
enum NodeTag {
case Player(PlayerScene.Data)
case PlayerArea(PlayerScene.AreaData)
case CoinLabel(HUDScene.Coin)
}
プリミティブラッパーパターンは、データの意味を明確にし、可読性を高めるために有効:
pub type alias Coin = Int32
ノードツリー構造に合わせて、親ノードと子ノードのデータを分ける:
ルートノードのデータ型の場合は、Dataとして、プレフィックスを省略する
// CharacterBody2D(Player)
// └── Area2D (PlayerArea)
pub type alias Data = { velocity = Vec2.Vec2, hp = Int32 }
pub type alias AreaData = { hit = Bool }
Nodeの名前やパス, テクスチャ名は、定数で一元管理する。ハードコードは禁止。
def name(): String = "xxx"
def xxxPath(): NodePath = name() :: Nil
def areaName(): String = "area"
def areaPath(): NodePath = name() :: areaName() :: Nil // 非公開
def xxxTexture = "xxx.png"
def xxxBgm = "xxx_bgm.ogg"
パラメータ、物理パラメータなども定数で管理する:
def speed(): Float64 = 100.0
def velocityY(): Float64 = -300.0
add関数は、ノードツリーを構築して、Sceneに追加する。
このときに、NodeTagと(A)で定義したデータ型(初期値)を紐づける。
さらに細かい単位で、Sceneに追加したい場合は、addXxx のような関数を追加する。
Scene情報は、JSONファイルで管理し、add関数では、JSONをロードして、ノードツリーを構築する。Nodeに付与するメタデータは、NodeTagを使って付与する。
def sceneJsonPath(): String = "src/scenes/XxxScene.scene.json"
pub def add(scene: Scene[NodeTag]): Scene[NodeTag] \ Fs.FileRead =
let data: data = {speed = speed(), state = Walking};
SceneLoader.loadScene(sceneJsonPath(), scene)
|> Scene.setState(playerPath(), NodeTag.Player(data))
{
"type": "RigidBody2D",
"name": "player",
"pos": [144, 256],
"shape": {"kind": "CircleShape2D", "radius": 13},
"gravityScale": 1.0,
"linearVelocity": [100, 0],
"children": [
{
"type": "AnimatedSprite2D",
"name": "sprite",
"pos": [0, 0],
"scale": [2, 2],
"animations": {
"walk": [],
"run": []
},
"initialAnimation": "walk",
"fps": 10
},
{
"type": "Area2D",
"name": "area",
"pos": [0, 0],
"shape": {"kind": "CircleShape2D", "radius": 13}
}
]
}
動的スポーン(名前やパラメータが実行時に決まる)の場合は、JSONを使わず、構築関数内でノードツリーを構築する。
pub def spawnXxx(name: String, position: Vec2.Vec2,
scene: Scene[NodeTag]): Scene[NodeTag] =
scene
|> Scene.addNode(name,
EngineNode.RigidBody2DWithState(body, NodeTag.XxxData(id)))
|> Scene.addToGroup(name :: Nil, xxxGroup()) // グループで一括削除用
|> Scene.addChild(name, childName(),
EngineNode.AnimSprite2DWithState(sprite, NodeTag.NoTag))
構築 API:
Scene.addNode(name, engineNode) — ルートに追加Scene.addChild(parent, child, engineNode) — 子を追加Scene.addToGroup(path, group) — グループに登録ゲームエンジンでは、以下のライフサイクルが存在する。 各Sceneでは、ライフサイクルに応じたロジックを定義し、Game.flixは、各Sceneのライフサイクルロジックを呼び出し、委譲する。
// XxxScene 側
pub def ready(node: EngineNode[NodeTag], _path: NodePath,
scene: Scene[NodeTag]): (EngineNode[NodeTag], Scene[NodeTag]) =
match node {
case EngineNode.RigidBody2DWithState(body, NodeTag.Player(data)) =>
if (playerData#state == PlayerState.Walking) {
(EngineNode.RigidBody2DWithState(run(body), NodeTag.Player(data)), scene)
} else {
(node, scene)
}
case _ => (node, scene)
}
pub def input(event: GameEngine.Key, scene: Scene[NodeTag]): Scene[NodeTag] \ GameEngine.Audio =
match getPlayerData(scene)#state {
case PlayerState.Walking =>
if (key == GameEngine.Key.Space) {
GameEngine.Audio.playAudio("sfx_wing");
scene |> mapPlayerBody((body, birdData) -> (jump(body), birdData))
} else scene
case _ => scene
}
pub def physicsProcess(_delta: Float64, node: EngineNode[NodeTag],
_path: NodePath, scene: Scene[NodeTag]
): (EngineNode[NodeTag], Scene[NodeTag]) =
match node {
case EngineNode.RigidBody2DWithState(body, NodeTag.Player(data)) =>
let newBody = match playerData#state {
case PlayerState.Walking => walkingUpdate(body)
case _ => body
};
(EngineNode.RigidBody2DWithState(newBody, NodeTag.Player(data)), scene)
case _ => (node, scene)
}
入力や衝突の判定などの処理は、Game.flixで、イベントのハンドラーのインスタンスを定義する。そこでは、イベント対象のパターンマッチを行い、該当するSceneのロジックを呼び出し、処理を委譲する。
instance AreaEvent.AreaHandler[NodeTag] {
type Aef = { GameEngine.Audio }
pub def onAreaEntered(selfPath: NodePath, selfState: NodeTag,
otherPath: NodePath, otherState: NodeTag,
scene: Scene[NodeTag]): Scene[NodeTag] \ {GameEngine.Audio} =
match (selfState, otherState) {
case (NodeTag.PlayerArea, NodeTag.EnemyArea) =>
PlayerScene.onPlayerHitEnemy(scene)
case (NodeTag.EnemyArea, NodeTag.PlayerArea) =>
PlayerScene.onPlayerHitEnemy(scene)
case _ => checked_ecast(scene)
}
}
各Sceneが、ゲームに影響を与えるには、Scene[NodeTag]を受け取って、中のノードの内容を変換したScene[NodeTag]を返すことで実現する。しかし、Scene[NodeTag]は、ツリー構造のため、特定のノードをパターンマッチして変換するのは、冗長である。そこで、Scene[NodeTag]の特定のノードをパターンマッチして変換するための、ボイラプレート関数を用意する。
pub def mapPlayerBody(f: (RigidBody2D, PlayerData) -> (RigidBody2D, PlayerData), scene: Scene[NodeTag]): Scene[NodeTag] =
Scene.mapEngineNode(playerPath(), engineNode -> match engineNode {
case EngineNode.RigidBody2DWithState(body, NodeTag.Player(data)) =>
let (newBody, newData) = f(body, data);
EngineNode.RigidBody2DWithState(newBody, NodeTag.Player(newData))
case other => other
}, scene)
同様に、NodeTagに紐づけられた、データも取り出すための関数も用意すると、便利だ。
pub def getPlayerData(scene: Scene[NodeTag]): PlayerData =
match Scene.getState(playerPath(), scene) {
case Some(NodeTag.Player(playerData)) => playerData
case _ => bug!("player not found")
}
| やりたいこと | 手段 |
|---|---|
| sprite + data を変換 | mapXxxSprite |
| 子ノード(Area2D 等)を変換 | mapXxxArea |
| sprite + data + 子ノードを一括変換 | mapXxx(合成) |
| 単一ノードの見た目変更 | Scene.mapEngineNode(path, f) |
| 状態のみ変更 | Scene.setState(path, newState) / Scene.mapState(path, f) |
| ノード追加 | Scene.addNode / Scene.addChild |
| ノード削除 | Scene.removeAt(path) / Scene.removeGroup(group) |
| グループ一括削除 | Scene.addToGroup → Scene.removeGroup |