たくのろじぃのメモ部屋

プログラミング関係や数学などの内容を備忘録として残すブログ。プログラミングはC#を中心に書いています。

【C#】Uno Platformを使ってみた

1. Uno Platform

Uno Platform は Windows, iOS, Android, Web 上で動くアプリケーションを開発できるクロスプラットフォームです。

platform.uno

Xamarinと似ていますが、いくつか違う点があります。一番はUWP/WPFと同じような形式でXAMLが書けることです。これによって、今までWindowsアプリを作っていた人が簡単にモバイル向けアプリのコーディングができるようになります。他にもいい点がありますが、ちょまどさんの記事を参考にするといいと思います。必要な環境構築も丁寧に書いてありますので...。

qiita.com

2. アプリをつくってみた

今回作成したアプリは「新型コロナウイルス感染症対策サイトへアクセスするためのツール」です。

github.com

このサイトは東京をメインに北海道や鹿児島県などの多岐にわたる地域に派生しています。派生先のURLはGithub上で共有されており、一般の人が探すにはちょっとハードルが高いと思います。そこで、見たいときに他県の情報もみれるようなアプリを作りたいと思います。

2.1 デザイン - XAMLコード

まずはデザインですね。Uno Platformには4つの各プラットフォーム向けプロジェクトと統合された1つの Shared というプロジェクトが生成されます。この Shared に書き込むことによって、どのプラットフォームにも反映されます。

Gridで要素を分割することで、WebViewによる全画面表示を防いでいます。

<Page
    x:Class="StopCOVID19ViewerApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:StopCOVID19ViewerApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
  
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
     <Grid.RowDefinitions>
            <RowDefinition Height="12*"/>
            <RowDefinition Height="1*"/>
     </Grid.RowDefinitions>
      <WebView x:Name="webView" Grid.Row="0"/>
      <TextBlock Text="地域の選択" Grid.Row="1" Margin="30 15" FontSize="16"/>
      <ComboBox x:Name="SelectBox" PlaceholderText="東京都" Grid.Row="1" Width="150" Height="30" Margin="130 10"/>       
      <Button Content="更新" x:Name="buttonRefresh" Grid.Row="1" Click="ButtonRefresh_Click" Margin="300 10 10 30"/>
     </Grid>
</Page>

2.2 ロジック - C#コード

今のところ例外処理などはあまり考えていません...。Dictionary に公開されている県とそのURLを追加して、それをXAMLから参照しています。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace StopCOVID19ViewerApp
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        WebSiteURLs webSite = new WebSiteURLs();

        public MainPage()
        {
            this.InitializeComponent();
            this.SetComboBoxItems();
        }

        protected void SetComboBoxItems()
        {
            foreach (var item in webSite.prefecture.Keys)
                SelectBox.Items.Add(item.ToString());
        }

        private void ButtonRefresh_Click(object sender, RoutedEventArgs e)
        {
            string valueFromCB = SelectBox.SelectedValue.ToString(); //コンボボックスから選択されたアイテムの取得
            string valueFromDic = webSite.prefecture[valueFromCB];
            var uri = new Uri(valueFromDic);
            webView.Navigate(uri);
        }
    }

    public class WebSiteURLs
    {
        public Dictionary<string, string> prefecture = new Dictionary<string, string>();

        public WebSiteURLs()
        {
            //都道府県を登録しておく
            prefecture.Add("北海道", "https://stopcovid19.hokkaido.dev/");
            prefecture.Add("埼玉県", "https://stopcovid19.e-toda.jp/");
            prefecture.Add("千葉県", "https://chiba-covid19.mypl.net/");
            prefecture.Add("東京都", "https://stopcovid19.metro.tokyo.lg.jp");
            prefecture.Add("神奈川県", "https://www.pref.kanagawa.jp/osirase/1369/");
            prefecture.Add("山梨県", "https://stopcovid19.yamanashi.dev/");
            prefecture.Add("岐阜県", "https://covid19-gifu.netlify.com/");
            prefecture.Add("愛知県", "https://stopcovid19.code4.nagoya/");
            prefecture.Add("三重県", "https://covid19-mie.netlify.com/");
            prefecture.Add("兵庫県", "https://stop-covid19-hyogo.org/");
            prefecture.Add("愛媛県", "https://ehime-covid19.com/");
            prefecture.Add("岡山県", "https://covid19-okayama.netlify.com/");
            prefecture.Add("鹿児島県", "https://covid19.codeforkagoshima.dev/");
        }
    }
}

3. 実行結果

プラットフォーム(OS)によってプロジェクトがわかれているので、スタートアッププロジェクトの設定を行ってやります。

3.1 Androidで実行してみた

ボタンが隠れてしまっていますね...。ロジックは順調に動いています。

f:id:takunology:20200318153942p:plain

3.2 iOS で実行してみた

実はXAMLiOSに合わせているので、当然ながら問題ないですね。

f:id:takunology:20200319023507p:plain

3.3 UWPで実行してみた

ボタンやコンボボックスは左下表示されています。問題なく動きます。

f:id:takunology:20200318153829p:plain

3.4 Webで実行してみた

一番下のウィンドウが Edge, 左が Chrome, 右が Firefoxです。 更新ボタンを押しても何も表示されません。Webに対してWebViewは対応していないのでしょうか?

f:id:takunology:20200319023710p:plain

4. おわりに

1つのファイルを書きかえるだけですべてのプラットフォームに対応するなんて素晴らしいです!感動しました。ただWebで表示できなかった理由が分かりませんでした。

マジですごいですね。これ。

参考ページ

Uno Platformを初めて使うならこちらのページがオススメです。後はWPFまたはUWPのXAMLとコードビハインドの書き方を調べれば作れます!

qiita.com

qiita.com

Javaは分からないけどマイクラMODを作りたい #7 AIを作る

前回はもともと定義されているAIの機能をいじりましたが、今回からは自分でAIを作っていきます。

1. EntityAIBase を継承する

LittleMaidMob のカスタムAIを見てみると、どうやら EntityAIBase を継承しているようです。これを参考にAIを作ってみたいと思います。

1.1 AIの行動におけるメソッド

まずは EntityAIBase を継承して機能を記述していくにあたって必要なメソッドやパラメータなどを書いていきます。

メソッド名 return 内容
= className constructor ここにはメインの処理は書かない。あらかじめ渡されたパラメータによる初期化などを行う。メインのメソッドを呼び出す必要もない。
shouldExecute boolean 行動を開始するための条件を記述するメソッド。行動させるための条件を記述し、行動させる場合は true を返す。無条件で動作させたい場合はそのまま return true; で書けば良さそう。abstruct 修飾子がついているのでオーバーライド必須。
shouldContinueExecuting boolean 行動を続行させるかの条件を記述するメソッド。デフォルトでは return this.shouldExecute(); となっているため初期条件が true であれば続行となる。shudExecute() は初期条件であったが、これは途中条件?になる。
isInterruptible boolean 行動するタスクの優先順位を変更可能かどうかを決める。デフォルトでは true になっており、現在行動しているタスクよりも優先順位が高いタスクが来た場合は中断される。
startExecuting void AIの初回行動時に呼び出されるメソッドで、1回だけ行動させたい条件や内容を書く。メインの動作をさせるための初期化など。
resetTask void 他のタスクによって中断された場合に呼び出されるメソッドで、内部状態(行動やパラメータなど)をリセットする。主にAIが行動を終了する際の内容を記述する。
updateTask void メインとなる行動内容を記述する。このメソッドは常に更新される(繰り返される)ため、1回で終わらないような処理(例えばプレイヤーに追従するために移動し続ける)などを書く。

AIの行動に関するメソッドはこんな感じです。

1.2 AIのタスクに関するメソッド

AIのタスクは場合によっては非同期処理されます。例えば、モンスターを例に挙げてみます。水中にモンスターを落とすと水面に上がる動作をします。これにプレイヤーが近づくと攻撃されます。加えてプレイヤーを見てきます。

この場合 AI のタスクは 3つにスレッドが分かれて同時進行しています。ここで3つのタスクそれぞれ「泳ぐ」「攻撃」「睨みつけ」が動いているわけですね。で、このタスクの同時進行が可能かどうかを mutexBits というパラメータで調整します。

mutexBits は他のAIのタスクから保護(矛盾がないように)するのに使用されるといってもいいと思います。また例え話になりますが、狼なんかはいい例だと思います。普段はプレイヤーを追従していますが、モンスターとの戦闘になった場合はモンスターへの攻撃を優先します。この場合、「追従」と「攻撃」の両タスクを実行することはできないようになっています。もし両者のタスクが同時で実行されると、AIは固まります。

ちなみにどのような判定で決めているかはこちらの記事に書いてあったので載せておきます。論理積をとることで判定しているようですね。

w.atwiki.jp

これを見る限り、処理はある程度グルーピングされているようです。「攻撃系」「首の動き」「泳ぎ」などですかね。同じ属性に入るような動きは矛盾が生じてしまうため、この表を参考にパラメータを合わせたほうがよさそうです。

これらを処理するメソッドは次の通りです。C#でいうところの getter / setter でしょうか....。

メソッド名 return 内容
setMutexBits void mutex フラグ用の値を設定するためのメソッド。1 の場合はモーションなどの動作関連、2 は視線などの首の動き関連、4 は水泳、その他などに割り振られているらしい。
getMutexBits void mutex による処理で他のタスクと同時実行可能か否かを判別するための処理。競合する(AND演算が true である)とき、このAIタスクは排他的に実行される。

あとはパラメータ mutexBits があり、整数型です。この値をもとに判定されます。

2. サンプルコードで練習

こちらのサイト に実装例があったので、これを参考にします。バージョンが少し古いですが、そこは読み換えて何とかするしかなさそうです。

サイトでは「レッドストーンを持っているプレイヤーに突進」とありますが、これを「ダイヤモンドを持っているプレイヤーを追従」に変更します。
まるで宝石に群がる人みたいですw

2.1 継承したクラスの作成

まずはAIの機能を書いていきます。 EntityAIBase を継承します。

package jp.takunology.takunologymod.entity.ai;

import jp.takunology.takunologymod.entity.HumanUnitBase;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.ItemStack;

public class HumanUnitAIDiamondFollow extends EntityAIBase
{
    HumanUnitBase owner; //このAIを搭載した Entity
    EntityPlayer target; //対象となるプレイヤー

    public HumanUnitAIDiamondFollow(HumanUnitBase humanUnitBase)
    {
        owner = humanUnitBase;
        setMutexBits(1); //一応動作に関する部分なので 1 にしました
    }

    @Override //AIの行動開始条件
    public boolean shouldExecute() {
        target = owner.world.getClosestPlayerToEntity(owner, 10.0D);
        if(target != null) //対象プレイヤーがいるかどうか nullチェック
        {
            if(owner.getDistance(target) < 2.0D) //すでに接近している場合
            {
                return false;
            }
            
            ItemStack itemStack = target.getHeldItemMainhand();
            if(itemStack != null) //アイテムがあるかどうか nullチェック
            {
                //ダイヤモンドを持っていた場合は実行する
                if(itemStack.getItem() == Items.DIAMOND) 
                {
                    return true;
                }        
            }
        }
        return false; //対象のプレイヤーがいない場合は何もしない
    }

    @Override //AIが行動を続けるかどうか
    public boolean shouldContinueExecuting()
    {
        return super.shouldContinueExecuting();
    }

    @Override //AI行動開始1回目に行う処理
    public void startExecuting()
    {

    }

    @Override
    public void resetTask()
    {
        target = null; //処理が終わったらターゲットを初期化して終了
    }

    @Override
    public void updateTask()
    {
        if(target != null)
        {
            //ターゲットがいる限りついていく
            owner.getNavigator().tryMoveToEntityLiving(target, 0.55F);
        }
    }
}

あとはこのAI行動を Entity のタスクに登録すれば完了です。コンストラクタの引数は this 修飾子を使ってそのオブジェクトを渡しています。

this.targetTasks.addTask(2, new HumanUnitAIDiamondFollow(this));

2.2 使用したメソッド

使用したメソッドは表の通りです。参考にしたサイトのバージョンが古く、対応していないメソッドもありましたが、ライブラリを参照しながら適当に試しました。

アイテムの比較を行う際に、参考サイトでは itemStack.itemID == Item.redstone.itemID としていましたが、これは 1.12.2 では使用できませんでした。代わりに itemStack.getItem() == Items.DIAMOND を用いています。アイテムIDや名前の管理は Json 形式になったため、変更されたのだと思います。

一度、itemStack にプレイヤーが持っているアイテムを保持しておき、これを比較しています。念のため null チェックを入れています。

メソッド名 return 内容
getClosestPlayerToEntity player Entity とプレイヤー間の距離を引数とし、引数に指定した範囲内で最も近いプレイヤーを取得する
getHeldItemMainhand item 右手に持っているアイテムを取得する。
getDistance float プレイヤーとの距離を取得する。
tryMoveToEntityLiving boolean 追従の対象となるEntityと移動速度を引数に入れ、その対象に可能な限り追従する。
getItem item ItemStack に保持されたアイテムを取得する。
Items.DIAMOND item デフォルトで登録されているアイテムへアクセスし、その名前のアイテムを取得する。

2.3 動かしている様子

ダイヤモンドを持っていて、ある条件になると追従します。

youtu.be

3. EntityAIBase のフロー

ソースコードだけでは見づらかったので、図にしてみました。(ソフトウェア工学で使うような図はよく分からないのでテキトーに。)あと、タスクが実行されるときはタスクを束ねている EntityAITasks というクラスが動くらしいです。

f:id:takunology:20200316175053p:plain

4. おわりに

とりあえず EntityAIBase クラスのメソッドと使い方は把握できたかもしれません。ここまで理解するのに2日くらいかかりましたが、今はなんとなく雰囲気で書けるようになってきました。あとは鬼のような量のライブラリを参照すれば自力で書けるかもしれません。

C#と似ている点があるので作業はしやすいです。