こちらAmusement Creators Advent Calenderの14日目の記事です。
はじめに
みなさん、初めまして。Amusement CreatorsのGON(ごん)です。
普段は、2D横スクロールゲームを開発しております。(冬コミ間に合うか??
今回は、ACACということで無い文章力を振り絞って書こうと思います。
みなさんはツール開発しますか?
個人的にゲーム開発には、 システム/リソース/調整 の大きな要素があると思います。
で、ちょっとクオリティの高いゲームを目指すと必ず詰まるのが、リソースの管理、配置の調整では無いでしょうか。(そもそも素材なんてねぇよというのは今回は無しで)
そこで、「ツール開発することで一気にリソース管理・調整の効率をあげましょう!!」というのが今回のお話です。
ツールを作る利点/欠点
パッと思いつくだけ書いてみました。
利点
- パラメータ入力が容易に
- プレビューしながら調整ができる
- プログラマ以外の人間が調整・配置が可能に
- 多人数で開発するのに向いてる
- 既存のツールを使うよりカスタマイズ性が高い
- 既存のツールは、汎用性をある程度必要としているため、痒いところに手が届かないor機能が多すぎて習得に時間がかかる
欠点
- 割に合わない(データが少ないならコードで配置した方がかえって楽かも)
- 急な仕様変更に弱い(仕様変更する奴が悪い説もある。ある程度はシステムの最適化で吸収できる→後述)
Altseedについて
とてもすごいゲームエンジンです。
ACAC1日目で良い感じに説明してくれてますね。そちらを見ましょう。アドベントカレンダーの有効利用です()
そして、Altseedは通常、初期化すると勝手にウィンドウを作ってくれるのですが、外部ウィンドウでAltseedの機能を使うこともできます。
そうすれば、既存のGUIフレームワークと組み合わせて、いろんなことができちゃうってわけですwktk
WPFに組み込む利点
- Bindingめっちゃ便利
- HTMLの感覚でフレキシブルな配置・デザインが可能
- 高DPI対応
ざっとこんな感じです。Bindingマジ便利。
ただAltseedの画面を外部のウィンドウに表示するだけなら、Windows Formsを使った方が楽です。(HWNDに紐つけるため)
しかし、Bindingを使うことで、Altseed側とWPF側でのデータのやり取りが容易になるので、ツールを開発するならWPFが良いと思います。
Altseed+WPF初級編
色々説明するより、実際に作ってみた方が早いので、以下そのレシピです。
まず、ファイル配置
WPFのプロジェクトを作りましょう。
こんな感じにApp.xaml
と最初に表示するウィンドウとなるMainWindow.xaml
があると思います。
そして、NuGetなりでAltseedを参照しましょう。
通常、メインループはWPF側でよしなにしてくれるので、気にする必要はないのですが、毎フレーム更新処理を行うゲーム特有の性質もあり、メインループは、こちらで実装した方が良さそうです。
ということで、エントリポイントとなっているApp.xaml
を消去し、Program.cs
を追加しましょう。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfAltseed
{
static class Program
{
[STAThread]
static void Main(string[] args)
{
// 外部のウィンドウを作成する。
bool closed = false;
MainWindow window = new MainWindow();
window.Closed += (sender, e) =>
{
closed = true;
};
window.Show();
// 外部のウィンドウを利用してAltseedを初期化する。
asd.Engine.InitializeByExternalWindow(window.altseed.Child.Handle, System.IntPtr.Zero, (int)window.altseed.RenderSize.Width, (int)window.altseed.RenderSize.Height, new asd.EngineOption());
while (asd.Engine.DoEvents())
{
// 外部のウィンドウの処理を進める。
MainWindow.DoEvents();
// 外部のウィンドウが閉じられたらAltseed用のゲームループも抜ける。
if (closed)
{
break;
}
asd.Engine.Update();
}
asd.Engine.Terminate();
}
}
}
ほぼこことやっているとは、変わりません。
要は、ウィンドウが閉じられた時にメインループから抜け出すことと、WPF側のイベントを処理できることを満たしていれば良いです。
WPF側の構築
WPFは、デザインを記述するXAML部分と挙動を記述するC#のソースコード部分に分かれます。
XAML
<Window x:Class="WpfAltseed.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAltseed"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="51*"/>
<ColumnDefinition Width="37*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBox" TextWrapping="Wrap" AcceptsReturn="True" Grid.Column="1" Margin="0,0,0,-1" />
<WindowsFormsHost x:Name="altseed"/>
</Grid>
</Window>
ここで重要なのがWindowsFormsHost
です。
Altseedの機能を外部ウィンドウで使いたい時、唯一必要なのがHWND
というWindowsAPIのウィンドウを管理するオブジェクトです。
Windows Formsの場合、このHWND
は各コントロールで個別に持っているのですが、WPFはウィンドウにつき一つです。
これが原因となって、WPFのみウィンドウではAltseedをウィンドウのある特定の領域のみに描画させることができません。
しかし、WPFとWindows Formsは相互連携機能を持っています。
つまり、Windows FromsからWPFを呼び出すこともその逆もできるってわけです。
今回は、WindowsFormsHost
を使うことで、WPFのウィンドウの中にWindows Formsを召喚させます。そうすることで、ウィンドウの特定領域のみAltseedを適応できます。
あと適当にTextBox
を配置しておきましょう。
C#
using System.Windows;
using System.Windows.Threading;
namespace WpfAltseed
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
altseed.Child = new System.Windows.Forms.Control();
}
public static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrames), frame);
Dispatcher.PushFrame(frame);
}
public static object ExitFrames(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
}
}
altseed.Child = new System.Windows.Forms.Control();
でWindows FormsのコントロールのインスタンスをWindowsFormsHost
に登録しておきます。
そして、ウィンドウのイベントを処理するためにDoEvents()
ExitFrames(object f)
関数を作りましょう。
あまり中身は気にしなくて良いと思います。
Altseed側の構築
今回は、適当に文字を表示することにしました。
ということで、Program.cs
のメインループ直前に以下を追加
// フォントを生成する。
var font = asd.Engine.Graphics.CreateDynamicFont("", 30, new asd.Color(255, 255, 255), 0, new asd.Color());
// 文字描画オブジェクトを生成する。
asd.TextObject2D obj = new asd.TextObject2D();
// 描画に使うフォントを設定する。
obj.Font = font;
// 描画位置を指定する。
obj.Position = new asd.Vector2DF(100, 100);
// 描画する文字列を指定する。
obj.Text = "C++完全に理解した";
// 文字描画オブジェクトのインスタンスをエンジンへ追加する。
asd.Engine.AddObject2D(obj);
ほぼサンプルそのままです。
正直なんでも良い。
バインディングしよう
ここ重要!! です。って言っても2行加えるだけですが。
めっちゃ簡単。
まず、Program.cs
でさっき付け足した直後に以下を追加
window.DataContext = obj;
で、MainWindow.xaml
のTextBox
部分を書き換える。
<TextBox x:Name="textBox" TextWrapping="Wrap" AcceptsReturn="True" Grid.Column="1" Margin="0,0,0,-1" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
重要なのは、Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
です。
{Binding Path}
でDataContext
に登録したオブジェクトのPath
プロパティをバインディングできます。
今回は、DataContext
にasd.TextObject2D
のobj
を代入し、TextBox
のText
プロパティを{Binding Text}
とすることで、TextBox
のText
プロパティとobj.text
を常に同期することができます。
できたね!!
上の工程を一通りやるとこんな感じなると思います。
しっかりと、テキストボックスの内容がAltseed側に反映されてますね。
これを応用すれば、WPF側でデータを入力させてAltseed側で即時反映できます。
また、WPFのBindingには型チェックの機能があります。
例えば、バインディング先がint型だった場合、数値以外が入力されるとテキストボックスが赤くなります。
これで、データの誤入力が減ると思います。
Altseed + WPFををする上での注意点
なくはないです。まあしょうがないです。
- Altseedの各種入力機能は使えない(WPFとAltseedを別ウィンドウにすれば問題ないらしい)
- Windowsのみ
-
空域問題(要は、
WindowsFormsHost
領域ではWPFのイベントは取れません。残念...)
まあ、大きな問題にはならないと思います。
終わりに
Altseed + WPFでツール開発(入門編) は、いかがだったでしょうか。
これで、簡単にAltseedが既存のGUIフレームワークと融合できることをわかってもらえたかと思います。
また今回は、ツール開発の具体的な導入法をメインで紹介しました。
個人的には、次回以降?でもっと踏み込んだ内容が書けたら良いと思っています。
割とこの周辺技術、ネタには困らないので。
次回予告??
ということで次回予告です。書けたら良いですね
- Altseed + WPFでツール開発(応用編) ~属性を使ってUI自動生成~
- Altseed + RoslynPadで簡単スクリプト
- Avaloniaで夢のクロスプラットホームツール開発