頭ん中

しがないITエンジニアが、考えた事を書きます。

Uno PlatformのWebAssemblyでゲームを作る

Uno PlatformのWasmを試していて、タイピングみたいな簡単なゲームならできるかも?と思って完成したのがこれ。

https://typeratta.azurewebsites.net/

f:id:siamcats:20200601094526g:plain

ちなみにUWP版はこんな感じです。コードはSharedプロジェクト内にしか書いてないので、ほぼワンコードで同じ動作をするアプリのデスクトップ版・Web版ができました。 f:id:siamcats:20200601022550g:plain

コードはGitHubに。 github.com

以降、Wasmで動作させる時のポイントというか躓いたとこをいくつか。

KeyDownイベント

UWPではCoreWindowのKeyDownイベントを拾うのが一般的だと思うのですが(キーボードショートカットのビヘイビアとかでよく使う)、これがUno Platformでは未対応で、Wasmの時にキータイピングを拾えませんでした。

public MainPage()
{
    this.InitializeComponent();

    Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
}

private void CoreWindow_KeyDown(CoreWindow sender, KeyEventArgs e)
{
    //Wasmだと発生しない
}

issueには上がっていて、「UIElementなら対応してるから我慢してね!(意訳)」とあるので、仕方ないからWasmの場合だけそっちで拾うようにします。

<Page
    中略
    KeyDown="UIElement_OnKeyDown">
public MainPage()
{
    this.InitializeComponent();

    //Wasm非対応
    Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown; 
}

private void CoreWindow_KeyDown(CoreWindow sender, KeyEventArgs e)
{
    e.Handled = true;
    Keystroke(e.VirtualKey);
}

private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
    e.Handled = true;
#if __WASM__
    Keystroke(e.Key);
#endif
}

private void Keystroke(VirtualKey key)
{
     //ここにキータイピングした時の処理
}

本当はこれだとコントロールにフォーカスが当たってるときに拾えないため注意が必要。今回はFocusableなコントロールが一切無いページだからこれで良しとする。

Storyboard アニメーション

こんな感じで、タイポした時に画面を赤く点滅させるアニメーションを設定します。

<Storyboard x:Name="ErrorStoryboard">
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="0.0" To="1" Duration="0:0:0.02" AutoReverse="True"/>
</Storyboard>

UWPはこれでOKなんですが、WasmはAutoReverseが対応してないみたいで、Opacityが0→1になったっきり、真っ赤なまま戻らなくなってしまう。

それではとKeyFrame指定して設定してみますが…

<Storyboard x:Name="ErrorStoryboard">
    <DoubleAnimationUsingKeyFrames
            Storyboard.TargetName="MistakeFlashing"
            Storyboard.TargetProperty="Opacity">
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.02" Value="1"/>
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

これにも対応していないせいか、今度はそもそも全く反応しません。

最終的には、こんな感じでUWP用とWasm用のStoryboardを2つ定義して

<Storyboard x:Name="ErrorStoryboardWasm">
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="0.0" To="1" Duration="0:0:0.02"/>
    <DoubleAnimation
        Storyboard.TargetName="MistakeFlashing"
        Storyboard.TargetProperty="Opacity"
        From="1" To="0.0" Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Name="ErrorStoryboardUwp">
    <DoubleAnimationUsingKeyFrames
            Storyboard.TargetName="MistakeFlashing"
            Storyboard.TargetProperty="Opacity">
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.02" Value="1"/>
        <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>

コード側から呼び出すときに呼び分けました。

//ミス
if (!result)
{
    vm.Mistake++;
#if __WASM__
    ErrorStoryboardWasm.Begin();
#else
    ErrorStoryboardUwp.Begin();
#endif
    return;
}

1つのStoryboard内で同じ要素に同じAnimationを複数定義すると、本来は同時に動いてエラーになるはずなんですが、Wasmだと順に実行されるようで上手くきました。いいのか?

雑感

Webサイトだと思うからJSを使いたくなる訳で、あまりWebの世界やりとりのない、簡単なゲームアプリ、例えば教育教材みたいなアプリならUno PlatformのWasmもなかなか勝負できるのでは、と思った。ただ、それならQtとかUnityという選択肢がある。

SPAとかPWAの延長線でWasmが語られていることを考えると、GUIアーキテクチャ(をラッピングする言語)にXAMLを掲げるUno Platformの普及は、なかなか苦しい気がする。Webも含めたフロントエンド全体から見たら、WPFやUWPのシェアというのは極僅かなものだろうから。Silverlightが生きていればなぁ。

さて、次はBlazor触るか…