いいですね!🎮
ここから「本格的なゲームループ」へ移行すれば、処理の管理がしやすくなり、FPS制御・時間経過管理・状態制御などが柔軟になります。
目次
✅ ゲームループ構成にする理由
現状は System.Timers.Timer
を使ってる簡易ループですが、本格ゲームでは以下が重要になります:
必要な要素 | 説明 |
---|---|
Update() | ロジック更新(位置計算など) |
Render() | 描画処理 |
FixedTimeStep | 安定した物理演算 |
DeltaTime | 時間経過に基づくスムーズな動き |
高FPS対応 | 画面更新とロジックを分離し、60FPS以上にも対応 |
🧱 ゲームループ構成:実装概要
GTKはUIスレッドが必要なので、GLib.Timeout.Add()
を使って描画と更新の統合ループを作成します。
💡 改良ポイント
🔁 GLib.Timeout.Add()
でゲームループ開始
GLib.Timeout.Add(16, () => { double now = GetCurrentTime(); double deltaTime = now - lastTime; lastTime = now; Update(deltaTime); drawingArea.QueueDraw(); return true; });
⏱ 時間取得(ミリ秒)
private double GetCurrentTime() { return DateTime.Now.Ticks / (double)TimeSpan.TicksPerSecond; }
🧠 Update(deltaTime)
関数でロジック制御
private void Update(double deltaTime) { foreach (var img in images) { img.X += img.DX * deltaTime * Speed; img.Y += img.DY * deltaTime * Speed; // 壁でバウンド if (img.X < 0 || img.X + image.Width > Width) img.DX = -img.DX; if (img.Y < 0 || img.Y + image.Height > Height) img.DY = -img.DY; } // 衝突判定(略) }
✨ Main()
でループ開始
lastTime = GetCurrentTime(); GLib.Timeout.Add(16, GameLoop);
🔁 フルリファクタ版をご希望なら…
「ゲームループ対応のフルコード」も提供できます!
次のようなスタイルで構築:
Game
クラスを作るGame.Update(deltaTime)
Game.Render(context)
- イベントも分離
🎁 提供可能なフルコード
- ✅ 本格ゲームループ
- ✅ DeltaTime による滑らかさ
- ✅ マルチ画像
- ✅ 壁バウンド&衝突判定
- ✅ クリックでスポーン
サンプルコード
GTK3 + C# + 本格ゲームループ構成のフルサンプルをお届けします!
✅ このサンプルで実装されていること
- ゲームループ:GLib の
Timeout.Add
による毎フレーム処理 - DeltaTime:経過時間に応じた移動(滑らかな動き)
- 画像描画:Pixbuf を使って画像を描画
- マウスクリックで画像追加
- 壁バウンド&衝突判定
- 全てがクラスで整理
🧾 GameApp.cs
(プロジェクト直下に保存)
using Gtk; using Cairo; using Gdk; using System; using System.Collections.Generic; public class GameApp : Window { private DrawingArea drawingArea; private Pixbuf image; private Game game; public GameApp() : base("GTK3 - Game Loop Sample") { SetDefaultSize(800, 600); SetPosition(WindowPosition.Center); DeleteEvent += (o, args) => Application.Quit(); drawingArea = new DrawingArea(); drawingArea.AddEvents((int)EventMask.ButtonPressMask); drawingArea.Drawn += OnDraw; drawingArea.ButtonPressEvent += OnClick; Add(drawingArea); ShowAll(); image = new Pixbuf("icon.png"); game = new Game(image, drawingArea.AllocatedWidth, drawingArea.AllocatedHeight); // ゲームループ開始 game.Start(() => drawingArea.QueueDraw()); } private void OnClick(object o, ButtonPressEventArgs args) { game.Spawn(args.Event.X, args.Event.Y); } private void OnDraw(object o, DrawnArgs args) { Context cr = args.Cr; game.Render(cr); } public static void Main() { Application.Init(); new GameApp(); Application.Run(); } }
🧱 Game.cs
(同じくプロジェクト内に)
using Cairo; using Gdk; using System; using System.Collections.Generic; public class Game { private List<MovingImage> images = new List<MovingImage>(); private Pixbuf image; private int width, height; private double lastTime; private const double Speed = 100.0; // pixels per second public Game(Pixbuf img, int width, int height) { this.image = img; this.width = width; this.height = height; } public void Start(Action requestDraw) { lastTime = GetTime(); GLib.Timeout.Add(16, () => { double now = GetTime(); double delta = now - lastTime; lastTime = now; Update(delta); requestDraw(); return true; }); } public void Spawn(double x, double y) { var rnd = new Random(); double dx = rnd.NextDouble() * 2 - 1; double dy = rnd.NextDouble() * 2 - 1; images.Add(new MovingImage(x, y, dx, dy)); } public void Update(double dt) { foreach (var img in images) { img.X += img.DX * Speed * dt; img.Y += img.DY * Speed * dt; // 壁バウンド if (img.X < 0 || img.X + image.Width > width) img.DX = -img.DX; if (img.Y < 0 || img.Y + image.Height > height) img.DY = -img.DY; } // 簡単な衝突判定 for (int i = 0; i < images.Count; i++) { for (int j = i + 1; j < images.Count; j++) { var a = images[i]; var b = images[j]; if (IsColliding(a, b)) { (a.DX, b.DX) = (b.DX, a.DX); (a.DY, b.DY) = (b.DY, a.DY); } } } } public void Render(Context cr) { // 背景 cr.SetSourceRGB(1, 1, 1); cr.Paint(); // 画像を描画 foreach (var img in images) { Gdk.CairoHelper.SetSourcePixbuf(cr, image, img.X, img.Y); cr.Paint(); } } private bool IsColliding(MovingImage a, MovingImage b) { return !(a.X + image.Width < b.X || a.X > b.X + image.Width || a.Y + image.Height < b.Y || a.Y > b.Y + image.Height); } private double GetTime() { return DateTime.Now.Ticks / (double)TimeSpan.TicksPerSecond; } private class MovingImage { public double X, Y, DX, DY; public MovingImage(double x, double y, double dx, double dy) { X = x; Y = y; DX = dx; DY = dy; } } }
🛠 ビルド方法(Linux)
mcs -pkg:gtk-sharp-3.0 -out:GameApp.exe GameApp.cs Game.cs mono GameApp.exe
✅ 次にやってみる?
- 🎮 キーボードで移動するプレイヤー追加
- 🧠 状態(ゲームスタート/ゲームオーバー)
- 🎵 音追加
- 🎨 画像アニメーション(SpriteSheet)
気になる拡張あれば言ってね!
「敵を追いかけてくる」とか「スコア表示」とか、何でも追加できます!🔥