はい、Run()
や Update()
メソッドは、GUIアプリケーションやゲーム開発では「アプリのメインループ構造」として重要な役割を果たします。それぞれの典型的な役割を説明します。
✅ Run()
メソッドの役割
主な目的:
- アプリケーションのエントリーポイント
- 初期化 → メインループ実行 → 後処理(終了) という全体の制御
あなたの CardWindow
クラスでの例:
public void Run() { SDL.SDL_Init(...); // 初期化 window = SDL.SDL_CreateWindow(...); renderer = SDL.SDL_CreateRenderer(...); LoadCardImages("cards"); while (running) // メインループ { while (SDL.SDL_PollEvent(...)) { ... } // イベント処理 SDL.SDL_SetRenderDrawColor(...); SDL.SDL_RenderClear(renderer); DrawCards(); // 描画処理 SDL.SDL_RenderPresent(renderer); SDL.SDL_Delay(16); // FPS調整 } Cleanup(); // リソース解放 }
このように Run()
は「アプリケーションの全体フローを統括」しています。
✅ Update()
メソッドの役割(存在しない場合も多い)
Update()
は ゲームロジックや状態の更新処理 を担当するのが一般的です。たとえば:
- プレイヤーの移動処理
- 衝突判定
- スコア計算
- アニメーションの進行
あなたのコードでは Update()
は定義されていません が、もしゲームや動的な動きを入れるなら、以下のように入れられます。
例:追加するなら
private void Update() { // ゲーム状態の更新(例:タイマー・カード移動など) }
そして Run()
のループ内に追加します:
while (running) { HandleEvents(); Update(); // ← 状態更新 Render(); // ← 画面描画 SDL.SDL_Delay(16); // ← フレーム調整 }
✅ 結論
メソッド | 主な役割 |
---|---|
Run() | アプリ全体の制御、メインループ |
Update() | 状態やロジックの更新(任意実装) |
GUIやゲームアプリを作る場合、Run()
は必須の骨格で、Update()
は機能追加に応じて導入するというのが基本です。
ゲーム・アニメでよく使う主要メソッド
ゲームやアニメーションでよく使われるメソッドは、典型的な「ゲームループ構造」に基づいて設計されており、以下のようなメソッド群が定番です。SDL や Unity、Godot など多くのゲームエンジンで共通する構成です。
🎮 ゲーム・アニメでよく使う主要メソッド
メソッド名 | 役割 |
---|---|
Initialize() | 初期化処理(オブジェクト生成、リソース読み込みなど) |
LoadContent() | テクスチャや音声などのリソース読込(画像、BGM、SEなど) |
HandleInput() / ProcessInput() | キーボード・マウス・ジョイスティックなどの入力処理 |
Update() | ゲーム状態(座標、速度、スコア、HPなど)の更新 |
FixedUpdate() | 一定間隔で処理(物理演算やFPSに依存しない処理に使う) |
Render() / Draw() | 描画処理(背景・キャラ・UIなどを表示) |
UnloadContent() | 使用しなくなったリソースの解放 |
Cleanup() / Dispose() | 終了処理、全体のリソース解放 |
🔄 ゲームループでの典型的な流れ
Initialize(); LoadContent(); while (running) { HandleInput(); Update(); // 状態更新(位置、スコア、衝突など) Render(); // 描画(背景 → オブジェクト → UI) SDL.SDL_Delay(16); // 目安:60FPS } Cleanup();
🎯 よくある応用的なメソッド
CheckCollisions()
:当たり判定処理PlayAnimation()
:スプライトのアニメーション切替ChangeState()
:ゲームの状態遷移(例:タイトル → ゲーム → リザルト)PlaySound()
/PlayBGM()
:サウンド制御SaveGame()
/LoadGame()
:セーブ・ロード処理
✅ SDL2 + C# で設計するなら?
今の CardWindow
に分割するとすれば:
void Initialize() {...} // SDL初期化、Window生成 void LoadContent() {...} // 画像読み込み void HandleInput() {...} // SDL_PollEventの処理分離 void Update() {...} // 今後追加するゲームロジック void Render() {...} // DrawCardsを分離 void Cleanup() {...} // リソース解放
ご希望があれば、これらを分割した構成のサンプルコードにも書き換えできます。
ゲームエンジン風にリファクタリングしてみましょう
以下に、あなたの CardWindow
クラスを「ゲームエンジン風の構成」にリファクタリングした完全版を提示します。
メソッドを分割し、各責任(初期化・入力処理・更新・描画・解放)を明確にしました。
✅ CardWindow.cs
(リファクタリング後)
using System; using System.Collections.Generic; using SDL2; public class CardWindow { private IntPtr window; private IntPtr renderer; private bool running = true; private Dictionary<string, IntPtr> cardTextures = new(); public void Run() { Initialize(); LoadContent(); while (running) { HandleInput(); Update(); Render(); SDL.SDL_Delay(16); // 約60FPS } Cleanup(); } private void Initialize() { SDL.SDL_Init(SDL.SDL_INIT_VIDEO); SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG); window = SDL.SDL_CreateWindow("FreeCell Style", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, 1024, 768, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN); renderer = SDL.SDL_CreateRenderer(window, -1, 0); } private void LoadContent() { string[] suits = { "C", "D", "H", "S" }; for (int i = 1; i <= 13; i++) { foreach (string suit in suits) { string num = i.ToString("D2"); string key = $"{suit}-{num}"; string path = $"cards/{key}.png"; IntPtr surface = SDL_image.IMG_Load(path); if (surface == IntPtr.Zero) { Console.WriteLine($"Failed to load {path}: {SDL.SDL_GetError()}"); continue; } IntPtr texture = SDL.SDL_CreateTextureFromSurface(renderer, surface); SDL.SDL_FreeSurface(surface); cardTextures[key] = texture; } } } private void HandleInput() { while (SDL.SDL_PollEvent(out SDL.SDL_Event e) != 0) { if (e.type == SDL.SDL_EventType.SDL_QUIT) running = false; // 今後:キーボード、マウス入力追加可能 } } private void Update() { // 今後のゲームロジック、アニメーションなど } private void Render() { SDL.SDL_SetRenderDrawColor(renderer, 0, 64, 0, 255); SDL.SDL_RenderClear(renderer); DrawCards(); SDL.SDL_RenderPresent(renderer); } private void DrawCards() { int cardW = 71, cardH = 96; int startX = 50, startY = 100; int spacingX = 90, spacingY = 30; string[] testOrder = { "C", "D", "H", "S", "C", "D", "H", "S" }; for (int col = 0; col < testOrder.Length; col++) { string suit = testOrder[col]; for (int row = 0; row < 6; row++) { string key = $"{suit}-{(row + 1).ToString("D2")}"; if (!cardTextures.TryGetValue(key, out var tex)) continue; SDL.SDL_Rect dst = new SDL.SDL_Rect { x = startX + col * spacingX, y = startY + row * spacingY, w = cardW, h = cardH }; SDL.SDL_RenderCopy(renderer, tex, IntPtr.Zero, ref dst); } } } private void Cleanup() { foreach (var tex in cardTextures.Values) SDL.SDL_DestroyTexture(tex); SDL.SDL_DestroyRenderer(renderer); SDL.SDL_DestroyWindow(window); SDL_image.IMG_Quit(); SDL.SDL_Quit(); } }
🔍 改良ポイント
分割メソッド | 内容 |
---|---|
Initialize() | SDL の初期化、ウィンドウ作成 |
LoadContent() | カード画像の読み込み |
HandleInput() | SDLイベント処理(今後拡張可能) |
Update() | ゲーム状態更新(今後アニメ対応) |
Render() | 背景とカード描画 |
DrawCards() | 実際のカード描画ロジック |
Cleanup() | テクスチャと SDL の解放 |
この設計は将来的に:
- アニメーション追加
- カードドラッグ操作
- スコア処理や効果音
などの拡張にも対応しやすい構造です。
とても良い設計です!すでにかなり美しい構造になっていますが、以下の点でさらにリファクタリングできます:
✅ 改良ポイント(提案)
Card
クラスの分離
→Card.cs
に分けることで再利用性・テスト性向上- カードのロードを
Card.LoadAll()
のように責任分担
→CardWindow
は「表示・制御」、Card
は「画像保持・描画」に集中 - テクスチャ解放処理も
Card
に任せる
🔧 最終構成案(2ファイル)
📁 Card.cs
(クラス分離)
using SDL2; using System; public class Card { public string Suit { get; } public int Number { get; } public IntPtr Texture { get; private set; } public SDL.SDL_Rect DestRect { get; set; } public string Key => $"{Suit}-{Number.ToString("D2")}"; public Card(string suit, int number, IntPtr texture, int x, int y, int width, int height) { Suit = suit; Number = number; Texture = texture; DestRect = new SDL.SDL_Rect { x = x, y = y, w = width, h = height }; } public void Render(IntPtr renderer) { var rect = DestRect; SDL.SDL_RenderCopy(renderer, Texture, IntPtr.Zero, ref rect); } public void Dispose() { if (Texture != IntPtr.Zero) { SDL.SDL_DestroyTexture(Texture); Texture = IntPtr.Zero; } } }
📁 CardWindow.cs
(シンプル&明快に)
using System; using System.Collections.Generic; using SDL2; public class CardWindow { private IntPtr window; private IntPtr renderer; private bool running = true; private List<Card> cards = new(); public void Run() { Initialize(); LoadCards("cards"); while (running) { HandleInput(); Update(); Render(); SDL.SDL_Delay(16); // ≒60FPS } Cleanup(); } private void Initialize() { SDL.SDL_Init(SDL.SDL_INIT_VIDEO); SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG); window = SDL.SDL_CreateWindow("FreeCell Style", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, 1024, 768, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN); renderer = SDL.SDL_CreateRenderer(window, -1, 0); } private void LoadCards(string folder) { int cardW = 71, cardH = 96; string[] suits = { "C", "D", "H", "S" }; foreach (var suit in suits) { for (int num = 1; num <= 13; num++) { string key = $"{suit}-{num.ToString("D2")}"; string path = $"{folder}/{key}.png"; IntPtr surface = SDL_image.IMG_Load(path); if (surface == IntPtr.Zero) { Console.WriteLine($"Failed to load {path}: {SDL.SDL_GetError()}"); continue; } IntPtr texture = SDL.SDL_CreateTextureFromSurface(renderer, surface); SDL.SDL_FreeSurface(surface); if (texture == IntPtr.Zero) { Console.WriteLine($"Failed to create texture: {SDL.SDL_GetError()}"); continue; } cards.Add(new Card(suit, num, texture, 0, 0, cardW, cardH)); } } } private void HandleInput() { while (SDL.SDL_PollEvent(out SDL.SDL_Event e) != 0) { if (e.type == SDL.SDL_EventType.SDL_QUIT) running = false; } } private void Update() { // 今後:カードの移動やアニメーションなど } private void Render() { SDL.SDL_SetRenderDrawColor(renderer, 0, 64, 0, 255); SDL.SDL_RenderClear(renderer); DrawCards(); SDL.SDL_RenderPresent(renderer); } private void DrawCards() { int startX = 50, startY = 100; int spacingX = 90, spacingY = 30; int index = 0; foreach (string suit in new[] { "C", "D", "H", "S", "C", "D", "H", "S" }) { for (int row = 0; row < 6; row++) { int number = row + 1; var card = cards.Find(c => c.Suit == suit && c.Number == number); if (card == null) continue; card.DestRect = new SDL.SDL_Rect { x = startX + index * spacingX, y = startY + row * spacingY, w = card.DestRect.w, h = card.DestRect.h }; card.Render(renderer); } index++; } } private void Cleanup() { foreach (var card in cards) card.Dispose(); SDL.SDL_DestroyRenderer(renderer); SDL.SDL_DestroyWindow(window); SDL_image.IMG_Quit(); SDL.SDL_Quit(); } }
Program.cs(エントリーポイント)
class Program { static void Main(string[] args) { new CardWindow().Run(); } }
✅ 今後の拡張にも最適
- カードの移動・選択判定 →
Card
に HitTest() を追加 - アニメーション →
Update()
で Y座標や透明度を変更 - カード裏面や選択状態 →
CardState
enumを追加
はい、以下に今回の .NET + SDL2
フリーセル風アプリに含まれる各クラスの メンバー変数・メソッドとその簡単な役割の一覧表 をまとめました。
構成のまとめ
✅ Card
クラス:カード1枚の情報と描画
メンバー名 | 種類 | 概要 |
---|---|---|
Suit | プロパティ | カードのスート(C, D, H, S) |
Number | プロパティ | カードの番号(1〜13) |
Texture | プロパティ | SDLで作成されたカード画像テクスチャ |
DestRect | フィールド | 描画位置とサイズ情報(SDL_Rect構造体) |
Key | プロパティ | “C-01” のような一意キー(スート+番号) |
メソッド名 | 概要 |
---|---|
Card(...) | コンストラクタ:カードの初期化、位置とサイズを設定 |
Render(renderer) | SDL に描画要求を出す |
Dispose() | テクスチャをSDLから解放(メモリリーク防止) |
✅ CardWindow
クラス:ウィンドウ全体とゲーム管理
メンバー名 | 種類 | 概要 |
---|---|---|
window | フィールド | SDLウィンドウのポインタ |
renderer | フィールド | SDL描画コンテキスト |
running | フィールド | メインループ継続中フラグ |
cards | フィールド | 表示する Card オブジェクトのリスト |
メソッド名 | 概要 |
---|---|
Run() | 初期化 → メインループ → 解放まで全体制御 |
Initialize() | SDL全体、ウィンドウ、レンダラーの初期化 |
LoadCards(folder) | PNG画像を読み込んで Card オブジェクトを作成 |
HandleInput() | SDLイベント処理(現在は終了イベントのみ) |
Update() | ゲーム状態更新(今後拡張予定) |
Render() | 背景描画+カード描画の統合 |
DrawCards() | 配置ロジックに従ってカードを描画 |
Cleanup() | リソース(テクスチャ、SDL)をすべて解放 |
✅ Program
クラス:エントリーポイント
メソッド名 | 概要 |
---|---|
Main() | CardWindow を実行し、ゲームを開始 |
🧩 構成図イメージ
Program.cs └─ CardWindow.Run() ├─ Initialize() ├─ LoadCards() ├─ while (running) │ ├─ HandleInput() │ ├─ Update() │ └─ Render() → DrawCards() → Card.Render() └─ Cleanup()