目次

ハイハイスクールアドベンチャー AvaloniaUI版

あらすじ

2019年神奈山県立ハイ高等学校は 地盤が弱く校舎の老朽化も進んだため、 とうとう廃校にする以外方法がなく なってしまった。

ところで大変な情報を手に入れた。 それは、

「ハイ高校にATOMIC BOMBが仕掛けられている。」

と、いうものだ。 どうやらハイ高が廃校になった時、 気が狂った理科の先生がATOMIC BOMBを 学校のどこかに仕掛けてしまったらしい。

お願いだ。我が母校のコナゴナになった 姿を見たくはない。 早くATOMIC BOMBを取り除いてくれ……!!

行動は英語で、“<動詞>” 或いは、“<動詞>”+“<目的語>“のように入れていただきたい。 例えば、”look room”と入れれば部屋の様子を見ることが出来るという訳だ。

それでは Good Luck!!!…………

概要

.NET はいつの間にかかなり本格的なマルチプラットフォームのフレームワークに進化していました。 実際、多少の手間こそあるものの、SDL2版は Windowsでも Linuxでも動きました。 ただ、SDL2なので、UIがかなり手抜きになっています。

せっかくのマルチプラットフォームなのだから、マルチプラットフォームなツールキットがあるんじゃないかと、AIに聞いたら、WPFをそのまま移植するのは無理だが、いくつかマルチプラットフォームのツールキットは存在しているよ、というので、じゃあせっかくだし、それで動くのを作ったら、それはそれで楽しいんじゃなかろうか、と思って、AvaloniaUIを使ったバージョンを実装しようと思い立ったのでした。

しかし、これがそんなに甘くはないんだということに気づくのに、それほど時間は必要ではありませんでした。

なお、2025年10月14日現在、まだ完成に至っていません。 ゲームはできるんですが、設定画面が全く動かなくて……。

AvaloniaUI版への困難な道

ビジュアルなUIデザイナがない?

ないのです。 Visual Studio Code 用に AvaloniaUI の拡張があって、.NET9ならなにやらビジュアルなツールが使えるようなことが書いてあったのですが、一応開発はLTS版である .NET8 をターゲットとしているので、それは使えない。

と、なると、XMLをじかに書き換えるしかないのです。

まあそんなにUIの点数の多いゲームではないので、大きな難点ではないのですが、いろいろ問題が起きたときに、修正がそれなりにめんどくさかったりするので。

また、ほぼXAMLなのに、UI要素があったりなかったり、また同じ名前でも持ってる属性が違ったりと、書き換えているうちに自分がなにをやっているのか見失いがちなのも難点でした。

UIスレッド

WPFでもそうだし、まあAndroidなんかも、UIの操作はUIスレッドが受け持つのです。

とはいえ、WPFにしてもSDL2にしても、ハイハイスクールアドベンチャーはUIからのイベントで駆動されるので、大体の処理はUIスレッドがそのまま受け持っていたので、あまり気にする必要はなかったのですが、AvaloniaUIの実装はかなり積極的にスレッドを起こして、処理ごとにスレッドが張り付くような格好になっているので、イベントの処理もUIスレッドじゃないスレッドが持ってくることが多いのです。

すると何が起こるか……画面の書き換えなどを行っても、それが反映されるのが、次にUIスレッドに処理が回ったときになるので、ゲームの進行と無関係に画面が書き換わるという困った状態になってしまうのです。

なので、画面を書き換えたらUIスレッドを起こして、処理が終わるのを待ってから進むようにしないといけないのです。 似たような話は Androidでもあったので、まあ、それはそれなんですが、処理の受け渡しなどの作法が、AIに聞きながらやっていると、それじゃないというコードが出てきたりして、ちょっと手間がかかりました。

モーダルダイアログ

WPFでもAvaloniaUIでもダイアログは ShowDialog()で表示をかけるのですが、その挙動が違っています。 WPFは同期してダイアログが閉じるまで ShowDialog()から戻らないのですが、AvaloniaUIのそれは非同期で即座に戻ってきます。 これを、ダイアログが閉じられるまで待つなら await をつけて呼び出す必要があります。

await をつけると、その処理を含む関数は async 属性にしないといけなくて、さらにその処理を呼び出しているところで、その関数を awaitしないといけないのです。 当然、その関数自体も async にして……となります。

これが、後で別の問題を起こすのですが、それはまた別途。

イベントハンドラと非同期

イベントハンドラは非同期にすること自体は特に問題ないとAIはいうのですが、実は問題があるケースがあります。 メニューから呼び出されるイベントハンドラは非同期だとダメなのです。 でも、メニューから呼び出す機能って、設定だったりヘルプだったり、ダイアログを呼び出したりするものが少なからずあって、非同期にせざるを得なかったりするのです。

じゃあどうするのか?

非同期のイベントハンドラを呼び出すためだけの非同期でないイベントハンドラを作って、そこから非同期のイベントハンドラを呼ぶようにして、非同期じゃないハンドラの方をメニューに登録します。

モデルビューとリアクティブUI

設定値を変更したときに、それをメインの画面に反映させるために、様々な方法があるのですが、AIに聞くと、AvaloniaUIではモデルビューとリアクティブUIを使うことで美しく実装できるし簡単だよ、というので、じゃあそれで、っていうことでコードをがさがさと書き換えたのですが、これが恐ろしいどっぱまりの始まりでした。

Qtでも WPFでも、設定ダイアログのコードビハインドでこのあたりの処理をしていたんですが、設定値は ModelView をつかって、設定画面の DataContext としてマッピングして、サービスを介在させて、親画面に変更が伝わるようにすればいい、ということらしいのですが、そのようにやったとたんにビルドすら通らなくなりました。

結論から言うと、Avalonia.ReactiveUI のパッケージと、その依存しているパッケージの間に不整合があって、結果、ビルドできなかったり例外はいたりと様々な問題を引き起こしていました。

AIは、あれなおせ、これなおせ、といっていろいろ示唆をくれるのですが、こういう状況ではしばしばループに陥って、正解にたどり着けなくなるので、こっちからも「こうじゃないの?ああじゃないの?」というのを入れていかないといけません。

結局のところ、Avalonia.ReactiveUIは捨てて、ReactiveUIの機能だけを使って、残りは手で実装しよう、とかいうことになりまして、実はここの部分はまだ進行中なのです。

スクロールバーが出ない?

ハイハイスクールアドベンチャーでは、画面の下部にメッセージビューを置いて、長くなったらスクロールバーが出るようにしているのですが、これがメッセージがいくら長くなっても出てこないのです。

おかしいでしょう?

結論から言うと StackPanelを使っていると、ログビュワーの縦方向が無限に長い扱いになるらしくて、スクロール必要にならない。 なので、StackPanelじゃなくて Gridを使いましょうという結論なわけです。 わからんよ、そんなの!

色味違わない?

色コードの扱いの違いなのか、同じロジックで実装しているのに、他の環境と微妙に色味が違っている部分があるような気がします。

AvaloniaUIの良いところ

テーマの統一的検出機能

WPFやQtの6.8以前はレジストリをじか読みしたり、GNOMEの設定ファイルを読んだりしないとシステムのテーマを取り出せないというめんどくささがあったのですが、AvaloniaUIは、機種に依存しない取得方法と設定方法の両方を提供しています。

また、独自テーマを作る際にも、XAMLのような辞書方式や、Qtのスタイルシート方式のどちらでもできるらしく、柔軟性に優れています。