いいですね!🎮
ここから「本格的なゲームループ」へ移行すれば、処理の管理がしやすくなり、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)
気になる拡張あれば言ってね!
「敵を追いかけてくる」とか「スコア表示」とか、何でも追加できます!🔥