おしるこの作り方

だらだら書く

メッセージ駆動を用いたマルチプレイRPGイベント処理

はじめに

この記事は私が所属しているサークルのアドベントカレンダー
デジクリ Advent Calendar 2018
の21日の記事になります。
最近はアウトプットが研究(しかもあんまりない)ところにきっかけを作ってくれた後輩には感謝です。
では本題に。

概要

メッセージ駆動を使ってRPGのイベント処理を行い、さらにマルチプレイに対応させます。
と大口をたたいてますがちょこっと実装してみただけくらいな感じです。

注意事項

実装は時間取れなかったので滅茶苦茶雑です。
一応人に見せるコードと思いながら書きましたがそれでもたぶんクソです。
それ以上に同期取らなきゃいけないイベント処理にめんどくさいからという理由でUDPを使っていたりする辺りがひどいです。
まあでもきっとこれ読んでやってみようかなと思うような人なら自分で直せるはず…(丸投げ
最初この記事書こうと思ったキッカケの話がつらつらと続くので早く本題!って人はメッセージって単語出るまで適当に飛ばしてください。

イベント駆動(イベントドリブン)

この内容でやろうと思ったきっかけのお話。

イベント駆動って?

プログラミングパラダイムの1つです。
イベント駆動では処理が上から下に流れていくのではなく、イベントを待機してイベントが発生するとそれに応じて処理が行われるという流れになります。
大学の講義やゲームプログラミングだけだと中々触る機会が無いけど一般的なソフトウェアを作る際には適しています。
GUIのソフトウェアにはボタンが多くありますが、どのボタンが押されるかというのはユーザ依存なのでどのボタンが押されたらどういう動作をするのかが分かりやすい(作りやすい?)イベント駆動が向いているのだと思います。
(実務経験皆無マンなのでこの辺は憶測入ってます)
イベント駆動については分かりやすく書いてるサイトが沢山あるので詳しくは話しませんので軽くググってみてください。

ゲームプログラミングとイベント駆動

ゲームプログラミングにイベント駆動が用いられることはあまりないと思います。
理由は色々あるとは思いますがコードが読みづらくなるとかゲームは常に状況が変化し続けるからとかがネットに転がっていました。
コードが読みづらくなるという点について言えばフロー駆動型に比べて処理順などが考慮されないので流れが分かりづらくなるというのはイベント駆動のデメリットであるのは間違いないです。
ゲームは常に状況が変化し続けるというのも実際その通りでアクションゲームなどは敵が常に動いていたりするのでループで管理した方が良いと思います。
では、動きの激しくないゲームでは使えるのでは?と思ったのが今回の記事のきっかけです。

イベント駆動に向いているゲーム?

動きが激しくなく、ボタン操作が多いゲームなどは向いているのではないかと思います。
具体的にはRPG(動きの激しくない昔のFFとかドラクエ系)、アドベンチャーゲーム等々が向いていると思います。
実際大学3年のときにはクイズゲームWindows Formsアプリケーションで作ったりしてました。(そのときは時間が無かったからですが

ググってみた

イベント駆動、ゲームとかで調べると上の方にCygamesのエンジニアブログやSlideShareなどがヒットします。
メッセージベースによるゲーム駆動(Cygames Engineers' Blog)
RPGにおけるイベント駆動型の設計と実装(SlideShare)

この2つに共通しているのがメッセージを利用しているということです。
ということで長々と話してきましたがメッセージを用いたゲームを作成していきたいと思います。
が、時間が無かったのでメッセージの便利な点を1点説明していこうと思います。

メッセージ駆動

今回扱うメッセージ駆動

(メッセージ駆動とイベント駆動を区別することもあるっぽい) 今回はゲームにおけるメッセージ駆動ということで。
メッセージ駆動にはいくつか利点がありますが今回はネットワークに関する部分の利点に着目しました。

ネットワークとメッセージ駆動

Cygamesのエンジニアブログの方にこうあります。
「受け取ったモジュールは(ここではプレイヤー処理は)、マルチプレイ中であるかどうかを考慮する必要はありません。ただ、そのメッセージを処理すれば良いのです。」
この利点を生かしてマルチプレイもどきを作っていきたいと思います。

今回作るもの

ここからやっと作ったものの話

概要

キャラクターに話しかけるとイベント(?)が始まるプログラム。(流石にゲームって呼べない)

要件

  • 1人のキャラが別のキャラの隣で話しかけるボタンを押したらイベントが始まる
  • イベントの内容はお互いの名前(player,non_player,IPアドレス)が表示される
  • イベント中のキャラには話しかけられない
  • NPCが1人いる
  • 3人以上がマルチプレイできることを想定して作る(時間なくて2人用になりましたが3人にも対応できるようには作ったので微調整すればいけるはず

開発環境

  • Unity(2018.3.0f2)
  • C#
  • VisualStudio2017 ※キャラクターやマップはAssetStoreのTinyRPGTownを使わせていただきました。

メッセージの仕様

本当はこれが一番大切なはずなのに 初めてだったのでこれが正しいってことは100%無いと思う。
何が正解だったのか…

public class Message
{
    /// <summary>
    /// メッセージクラス
    /// コマンドと引数的な感じで定義
    /// </summary>
    
    public string Order { get; set; }
    public string Option { get; set; }
    public Message(string order)
    {
        this.Order = order;
    }
    public Message(string order,string option)
    {
        this.Order = order;
        this.Option = option;
    }
}

Order:key

Optionで押されたキーを指定(KeyInputクラスから
ゲーム終了やオンライン接続などシステム的な方のキー入力を処理

Order:send

指定されたIPアドレスに送りたいデータを送信
Option:"IPアドレス>送りたいデータ"

Order:online

ネットワークを介して送られてきたデータを処理
Option:"IPアドレス+送られてきたデータ"の形

Order.player

キー入力でplayerが移動した際に発生するメッセージ
オンラインユーザに移動を通知(プレイヤー位置の同期)

Order.event

MessageManagerに送られてくる場合

キー入力でイベントが発生した際にオンラインユーザに自分のイベント開始を通知  
Option:"start"or"end"

Characterに送られてくる場合

  • キー入力でイベントが発生した際にプレイヤーから送られて来る
  • ネットワーク側でイベントが発生した際に送られて来る(イベント状態の同期

イベント発生の流れ

f:id:bluetiger_ts:20181221131345j:plain
イベントはっせいの流れ1
f:id:bluetiger_ts:20181221131349j:plain
イベントはっせいの流れ2
f:id:bluetiger_ts:20181221131352j:plain
イベントはっせいの流れ3
f:id:bluetiger_ts:20181221131355j:plain
イベントはっせいの流れ4
f:id:bluetiger_ts:20181221131358j:plain
イベントはっせいの流れ5
f:id:bluetiger_ts:20181221131342j:plain
イベントはっせいの流れ6

軽くソースコードの説明

ソースコードはこちら github.com scriptしか上げてないので動かしてみたい人はご一報ください。

MessageObject

メッセージを扱うオブジェクトの基底クラス

public class MessageObject : MonoBehaviour
{
    /// <summary>
    /// メッセージを扱うオブジェクトの基底クラス
    /// メッセージはキューで管理
    /// </summary>
    protected Queue<Message> message_queue;
    public void Start()
    {
        message_queue = new Queue<Message>();
    }
    public void Update()
    {
        if (message_queue.Count != 0)
        {
            ProcessingMessage();
        }
    }
    public virtual void ProcessingMessage()  //メッセージ処理
    {
        Debug.Log("Unimplemented");
    }
    public void MessageListner(Message mes)  //メッセージ受信
    {
        message_queue.Enqueue(mes);
    }
}

Updateメソッド内を見ればわかりますがqueueがある場合にはProcessingMessageを呼び出しています。
基本的には継承先でProcessingMessageをオーバーライドしてDequeueからの処理を想定してますが、
ここでProcessingMessageを実装しとかないとUpdateをいちいち書くことになるのであえてこういう実装にしてます。
(なんかいい方法あるのかな)

MessageManager(一部)

ProcessingMessageの実装です。

public override void ProcessingMessage()
    {
        while(message_queue.Count != 0)
        {
            var dequeue_mes = message_queue.Dequeue();
            Debug.Log("mes:"+dequeue_mes.Order+":"+dequeue_mes.Option);

            if(dequeue_mes.Order == "key")
            {
                KeyAction(dequeue_mes.Option);
            }
            else if(dequeue_mes.Order == "send")
            {
                SendMessage(dequeue_mes);
            }
            else if(dequeue_mes.Order == "online")
            {
                OnlineAction(dequeue_mes.Option);
            }
            else if(dequeue_mes.Order == "player")
            {
                foreach (var op in FindObjectsOfType<OnlinePlayer>())
                {
                    udp.SendMessage(op.IPAdress, dequeue_mes.Option);
                }
            }
            else if(dequeue_mes.Order == "event")
            {
                foreach (var op in FindObjectsOfType<OnlinePlayer>())
                {
                    udp.SendMessage(op.IPAdress, "event:" + dequeue_mes.Option);
                }
            }
        }
    }

MessageはOrderとOptionで構成されてます。(コマンドと引数的な Orderの内容によって処理を変えてます。
UDPで飛んできたものは全てOrderをonlineにして特別処理をしています。

実際の画面

f:id:bluetiger_ts:20181221042248p:plain
初期画面
f:id:bluetiger_ts:20181221042310p:plain
オンラインのプレイヤーが話しかけてきた
f:id:bluetiger_ts:20181221042706j:plain
同期されてる様子

終わりに

メッセージ管理でやったことによってマルチプレイが楽にできたかなと思います。(ほんまか?
UDP使ったり、イベント発生時のメッセージに0,1,2とか分かりづらい魔法の数字使ってたりしますがそれなりにしっかり動いたので良かったなと思います。
メッセージの仕様はしっかり定めましょう…

この記事を読んでちょっとでもメッセージ駆動とかイベント駆動に興味を持ってくれた人がいれば幸いです。