たくのろじぃのメモ部屋

プログラミング(C#)の基礎やそれを応用した技術情報をメモしておくブログです。

【C#】Minecraft自動化 #2 プレイヤー座標の取得とブロックの配置

はじめに

前回は環境構築とマイクラに対してコマンドを送れるようにしました。今回はプレイヤーの座標を取得し、ブロックをその座標へ配置していきます。

お品書き

  1. ブロックを置くコマンド
  2. C# を用いてブロックを配置したいが...
  3. プレイヤーの座標を取得する
  4. 座標指定によるブロック配置

1. ブロックを置くコマンド

イクラ1.7.10 でブロックを置くためのコマンドは次の通りです。

/setblock x座標 y座標 z座標 ブロック名 

ただし、これ以降のバージョンでは /fill コマンドを使うらしいです。 また、自分のいる座標を指定する場合は "~" チルダ記号を用います。 ブロック操作に関する詳しい内容はこちらを参考にしてみてください。

minecraft-ja.gamepedia.com

こんな感じにコマンドを入力してみます。

/setblock ~ ~ ~ stone

f:id:takunology:20190825105540p:plain

石ブロックが設置できました。これをプログラムで動かしたいわけです。

2. C# を用いてブロックを配置したいが...

新しくコンソールアプリケーションのプロジェクトを作成して先ほどのコマンドを送信してみたいと思います。RCONのパッケージ導入を忘れずに。

using CoreRCON;
using System;
using System.Net;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Now Starting...");
            await minecraft();
        }

        static async Task minecraft()
        {
            var serveraddress = IPAddress.Parse("127.0.0.1"); //IPアドレスとして扱うための変換
            var serverpass = "minecraft"; //RCONでログインするためのパスワード
            ushort port = 25575; //サーバのポート番号

            var SetBlock = "/setblock ~ ~ ~ stone";

            //CoreRCONを使ってMinecraftへ接続
            try
            {
                var connection = new RCON(serveraddress, port, serverpass);
                var result = await connection.SendCommandAsync(SetBlock);

                Console.WriteLine(result);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            Console.ReadKey();
        }
    }
}

実行してみると

f:id:takunology:20190825110338p:plain

ワールド範囲外には設置できないというエラーが表示されてしまいました。原因はチルダ記号を認識していないためです。(原因はわかりませんが、仕様でしょうか?今までにチルダ記号を使ったことがないです。)なので、プレイヤーの座標を数値で入力する必要があります。

3. プレイヤーの座標を取得する

残念ながら、マイクラのコマンドには座標を直接取得するようなコマンドはありません。代わりにテレポートするコマンドを用います。マイクラ上でテレポートコマンドを実行してみると、座標がログに表示されます。

/tp ~ ~ ~ 

f:id:takunology:20190825110951p:plain

さて、これをC#から実行してコンソール上にログを表示してみます。コマンドを新たに追加して実行します。

// 適当な場所に追記
string Player_Name = "プレイヤー名";
string TPCommand = "/tp " + Player_Name + " ~ ~ ~";
//変数を書きかえただけ
var result = await connection.SendCommandAsync(TPCommand);

f:id:takunology:20190825111646p:plain

これでプレイヤーの座標を得ることができました。が、このままでは値として扱えません。 まずは数値だけのデータが欲しいですね。これを実現するために、正規表現を用いて文字列にフィルタをかけます。C#では便利なクラスとメソッドがあるのでディレクティブを追加して、以下を追記します。

using System.Text.RegularExpressions;

string GetNum = Regex.Replace(result, @"[^0-9,.]", "");
Console.WriteLine(GetNum);

@ は正規表現を示しており、^ は含むという意味があります。なので、[ ]の中に指定した0~9までの数字とカンマ、ピリオド(小数点)だけを残して、あとは破棄します。結果として、数値・カンマ・ピリオドを得ることができます。ただし、この数値はまだ文字列型です。実行してみると、3行目にフィルタ済みデータが表示されます。

f:id:takunology:20190825112544p:plain

これで数値データだけを得ることができました。が、まだです。これではカンマが残ってしまっているので、それぞれの値を別で取り出したいです。 そこで活躍するのが Split メソッドです。Split は ( ) で指定した文字で部分的に分割し、それぞれを配列に保持してくれるメソッドです。素晴らしく便利ですね。これを追記して挙動を確認してみます。 分割したデータを入れるために配列を定義し、そこに入れます。判定文字はカンマで区切られているので、カンマを Splitメソッドの引数とします。また、配列の数だけ表示する必要があるので、みんな大好きイテレータ ( foreach文 )で繰り返します。

string[] StrArray = GetNum.Split(',');

foreach(string str in StrArray)
{
    Console.WriteLine(str);
}

f:id:takunology:20190825113925p:plain

良い感じです。あとはこれらの値を、分かりやすい変数名に代入し、小数点型として扱えば値の計算もできるようになります。マイクラの世界ではxが南北、yが高さ、zが東西を表しているようです。後からいじれるように、それぞれ得られた値を各変数へ保持します。ただし、文字列型なので小数点型に変換するメソッド Parse を用います。

double Position_x = double.Parse(StrArray[0]); //南北方向 北へ進むほど数値が小さくなる
double Position_y = double.Parse(StrArray[1]); //高さ
double Position_z = double.Parse(StrArray[2]); //東西方向 東へ進むほど数値が大きくなる

これでプレイヤーの位置情報を得ることができました。

4. 座標指定によるブロック配置

これで位置情報とブロックを置くコマンドを使用する準備ができたので、これらを用いて実装していきます。

4.1 プレイヤーの位置にブロックを配置する

まずはプレイヤーの位置にブロックを置いてみます。プレイヤーの位置はそのまま受け取った値を用いればいいので、コマンドに変数を連結して使用します。

//ブロック名
string Block_Name = "stone";
//ブロックを置くコマンド
string SetBlock = "/setblock " + Position_x.ToString() + " " + Position_y.ToString() + " " + Position_z.ToString() + " " + Block_Name;
//コマンド送信
var result = await connection.SendCommandAsync(SetBlock);
Console.WriteLine(result);

一応、コード全体を載せておきます。

f:id:takunology:20190825150037p:plain

これで実行してみましょう。

f:id:takunology:20190825150204p:plain

コンソールにも Block placed と表示されています。 ちなみに、ブロックを削除するコマンドはないので、ブロック名を air とします。すると、空気ブロックに置き換えられてブロックが削除できます。

4.2 線形にブロックを配置する

線形は1次元の方向です。なので、繰り返し文を使用してブロックを置くようにすればいいです。例えば x方向に石ブロックを5個並べてみます。

注意点としては、プレイヤーの位置情報に直接手を加えると、プログラムが終了したときに変更後の座標のままになってしまいます。なので、データをいじるための変数を新しく用意します。オリジナルは残しておきます。また、コマンドは文字列なので、値を文字列型に変更します。

double x = Position_x, y = Position_y, z = Position_z; //配置するために使用する座標
for(int i = 0; i < 5; i++)
{
    x++;
    string SetBlock = "/setblock " + x.ToString() + " " + y.ToString() + " " + z.ToString() + " " + Block_Name;

    result = await connection.SendCommandAsync(SetBlock);
    Console.WriteLine(result);
}

これで実行してみます。

f:id:takunology:20190825151456p:plain

これで石が5個x方向に配置されました。

4.3 2次元方向にブロックを配置する

2次元方向(x, z)方向にブロックを配置して面を作ります。2次元方向なので、2重 for文を使って実装すればできます。同じく5個ずつ配置して正方形を作ってみます。

注意点としては、z方向を5回繰り返してそのままにすると、x方向が2回目のループに入った時にz座標が+5された状態で再び5回加算されます。なので、繰り返しが進むにつれてブロックが階段状に配置されてしまいます。なので、一度z方向をオリジナルの位置情報に修正する必要があります。オリジナルと分けたのはこのためでもあります。

for(int i = 0; i < 5; i++)
{
    x++;
    for (int j = 0; j < 5; j++)
    {
        z++;
        string SetBlock = "/setblock " + x.ToString() + " " + y.ToString() + " " + z.ToString() + " " + Block_Name;
        result = await connection.SendCommandAsync(SetBlock);
        Console.WriteLine(result);
    }
    z = Position_z;
}
Console.ForegroundColor = ConsoleColor.Green; //コンソール文字列に色付け
Console.WriteLine("\nDone."); //ちょっとカッコつけて完了の表示
Console.ForegroundColor = ConsoleColor.White; //色を戻す

これで実行してみます。

f:id:takunology:20190825152651p:plain

良い感じに仕上がってますね。ちょっとカッコつけてプログラム終了時に表示される文字に色を付けてみました。 3次元は省略しますが、3重for文にすれば実現できます。

動作確認がてら動画載せておきます。

www.youtube.com

さいごに

今回はプレイヤーの位置情報とブロックの配置方法(線形と2次元)についてやりました。ここまでくれば湧きつぶしもすぐにできそうです。湧きつぶしは一定間隔ごとにたいまつを置いていけばいいので、簡単なアルゴリズムで実装できると思います。

今回作成したコード

参考にどうぞ。

github.com