投稿記事

ブラウンチップ☆ 2019/12/21 19:53

【DotFeather】便利なオブジェクトを作る

はいどーもこんばんは。
今日も昨日の続きでDoFeatherを使っていきたいと思いますよ。
本日のお題は便利なオブジェクトです。

便利なオブジェクトとは?

前回の記事で「クリックでイベントを発火するテキストオブジェクト」を作りました。
これはこれでいいのですが、以下のような機能も盛り込みたくはならないでしょうか?

  • マウスポインタが重なった瞬間にイベント発火
  • マウスポインタが離れた瞬間にイベント発火
  • 長押しされている間ずっと発火

ノベルゲーだと欲しそうですよね。
さらに言うなら、この辺の機能ってテキストに限らずスプライトでも欲しいですよね。
では設計を考えてみましょう。

愚直な実装方法

もっとも単純な実装方法は、昨日作ったClickableTextクラスに上述した機能を一つ一つ盛り込み、イベントを追加してあげることです。
これでもよいのですが、以下の問題点があります。

  • 不要なイベントまで付与されてしまう
    • 例えばクリック機能だけでいいのに、マウスポインタが重なった瞬間のイベントも実装されてします。ただし中身を空にすれば動作自体に支障はない
  • スプライトに使い回せない
    • あくまでもテキストに対してのみしか機能が有効でなく、スプライトでも似たような機能が欲しくなったとき、似たようなコードを書かなければならない

そこで今回は

  • コンポーネント方式

を採用しようと思います。
つまりUnityのAddComponent的なやつですね。あるいはデコレータパターンとして作ってあげてもいいかもしれません。
まあこの辺はお好みで。

コンポーネント方式の基盤

上記では「スプライトで使い回せない」と書きましたが、とりあえずのところスプライトは無視して考えます。
というわけで今考えるのはTextDrawableのみですね。
コンポーネントのコードはこんな感じでどうでしょうか。


           internal abstract class TextComponentBase {

        ////=============================================================================
        //// Properties
        ////  
        ////=============================================================================

        public GameTextObject Parent { get; private set; }

        /// <summary>
        /// 何らかのタイミングで発火させたいイベント
        /// </summary>
        public event EventHandler Subject;

        ////=============================================================================
        //// Public Methods
        ////  
        ////=============================================================================

        public abstract void Update();

        /// <summary>
        /// GameTextObject以外からセットして欲しくない
        /// </summary>
        public void SetParent(GameTextObject parent) {
            Parent = parent;
        }

        /// <summary>
        /// 親からのコンポーネントを取得
        /// </summary>
        /// <typeparam name="T">取得したい型</typeparam>
        /// <returns></returns>
        public T GetComponent<T>() where T : TextComponentBase {
            return Parent.GetComponent<T>();
        }
        ////=============================================================================
        //// Protected Methods
        ////  
        ////=============================================================================

        protected virtual void OnEvent(EventArgs e) {
            Subject?.Invoke(this, e);
        }
    }

特に見るべき箇所はないですね。
OnEventの名前が気に入らないだとか(本当はイベントごとに名前を変えたい)、Parentが実質どこからでもセットできちゃうとか、問題は色々ありますが……。
とりあえずはこれで行きましょう! 問題が出てきそうなら変える方針で。

TextDrawableを継承するクラスの方はこんな感じ。

    internal class GameTextObject : TextDrawable, IUpdatable {
        ////=============================================================================
        //// Local Members
        ////  
        ////=============================================================================

        /// <summary>
        /// コンポーネントのリスト
        /// </summary>
        private readonly List<TextComponentBase> _componentList = new List<TextComponentBase>();

        ////=============================================================================
        //// コンストラクタ
        //// 
        ////=============================================================================

        public GameTextObject(string text, DotFeather.Font font, Color? color = null) : base(text, font, color) {
        }

        public GameTextObject(string text, float fontSize = 16, DotFeather.FontStyle fontStyle = DotFeather.FontStyle.Normal, Color? color = null) : base(text, fontSize, fontStyle, color) {
        }


        ////=============================================================================
        //// Public Methods
        ////  
        ////=============================================================================
        public virtual void OnUpdate(GameBase game) {
            _componentList.ForEach(x => x.Update());
        }

        public GameTextObject AddComponent(TextComponentBase component) {
            if(CanAddComponent(component)) {
                _componentList.Add(component);
                component.SetParent(this);
            }
            return this;
        }

        public T GetComponent<T>() where T : TextComponentBase {
            var result = _componentList.FirstOrDefault(x => {
                var component = x as T;
                return component != null;
            });
            return result as T;
        }


        ////=============================================================================
        //// Private Methods
        ////  
        ////=============================================================================

        private bool CanAddComponent(TextComponentBase component) {
            var isComponentNull = component == null;
            var isAlreadyContainComponent = _componentList.Contains(component);
            var isAddedType = (_componentList.FirstOrDefault(x => x.GetType() == component.GetType()) != null);

            return !isComponentNull && !isAlreadyContainComponent && !isAddedType;
        }
    }

AddComponentはfluentな作りになっているため、AddComponentからの戻り値からさらにAddComponentすることができます。

hoge.AddComponent(fuga).AddComponent(piyo);

みたいなやつですね。
GetComponentについてはUnityのGetComponentみたいに使えたらなーという感じで作りました。

hoge.GetComponent<FugaComponent>()

みたいな感じで付与されているコンポーネントを取得できます。
細かいエラー処理とか何も入ってないので問題あるかもですが、まあとりあえずはまあこんなもんで。

各コンポーネントを作ってみる

ここまででコンポーネント方式の基盤ができました。
あとは各コンポーネントを作りましょう。
具体的には

  • オブジェクトがクリックされたら発火するコンポーネント
  • オブジェクトの中にマウスが入ったら発火するコンポーネント
  • オブジェクトの外にマウスが出ていったら発火するコンポーネント
  • MAXHPと現在HPを持つコンポーネント
  • 現在HPが減ると発火するコンポーネント
  • 死ぬ(現在HPが0になる)と発火するコンポーネント

上の3つはいいですね。
問題なのは下の2つです。
「現在HPが半分以下のとき発火するコンポーネント」は「MAXHPと現在HPを持つコンポーネント」のことを知っていなければなりません。
コンポーネント同士はお互いのことを知っていてほしくないので、これは不都合です。
また文字を描画するものに対して「現在HP」というのは、かなり特殊なコンポーネントな気がしますね。

そこで今回はTextEnemyCore(GameTextObjectから継承)TextEnemyComponentBaseを作成し、Coreは他のコンポーネントから知ってもらえることとします(Core自体をコンポーネントとして用意する方法も考えましたが、AddComponentする順番を間違えると死んじゃうのでこうしました)。
すなわち、
TextEnemyComponentBaseはTextComponentBaseを継承して作成**し、TextEnemyCoreコンポーネントへの参照を持つことにします。

各コンポーネントはCoreのイベントを参照し、そこから登録していく感じです。
これならコンポーネントごとに処理をわけることも可能です。
さらに言うなら、各イベントをTextEnemyCoreの方につけてあげると、例えばクリック時に「音を鳴らす処理」「画像を切り替える処理」「HPを減らす処理」を同時に(1フレーム以内に)おこないたいと、別クラスにわけちゃうこともできそうです。
もしそうしなければ「オブジェクトがクリックされたときに発火するコンポーネント」の中に上記3つの処理全てを入れなければなりません。
これはSOLID原則の1つである「単一責務の原則」に反しますね。
クラスが変更される理由はなるべく一つにしてあげましょう。

以上をまとめると、以下のようになります。

  • Coreオブジェクト
    • MAXHPと現在HPを持つ
  • Coreオブジェクトへの参照を持つベースコンポーネント
  • 現在HPが減ると発火するコンポーネント
    • HPが減るのはクリックされたらとする
      • HPが減ると音を鳴らす
  • 死ぬと発火するコンポーネント
    • 死ぬときに音を鳴らす
      • 死ぬと文字色が変わる
  • マウスポインタが重なったときに発火するコンポーネント
    • これをロック状態として、死んでいないならば色が変わる
  • マウスポインタが離れたときに発火するコンポーネント
    • これをアンロック状態として、死んでいないならば色が変わる

実際にはCoreは

  • クリックされたら発火するコンポーネント
  • マウスが重なったら発火するコンポーネント
  • マウスが離れたら発火するコンポーネント

を内部で生成し、それらのイベントになんやらわかりやすい名前をつけていきます(クリックされると発火~というより死ぬと発火~のほうが動作がわかりやすい)。

さあ設計方針が決まりました。
あとはガンガン作っていくだけです!

フォロワー以上限定無料

特典はほとんどありませんが、入ってくださる方が増えれば増えるほど僕が喜ぶプランです(笑)

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

ブラウンチップ☆ 2019/12/17 23:33

DotFeatherを使ってみる

はいどーもこんばんは、無職まっしぐらの無職です。
本日はDotFeatherを使ってみようと思います。

DotFeatherってなに

DotFeatherとは、Xelticaさんが制作しているC#用のゲームエンジンです。
C#のAdventCalendarをボーッと眺めていたらゲームエンジンの話があり、ちょろっと覗いて興味を持った感じですね。
詳しい情報はQiitaの記事で概要を見てもらえればなと思います。

ゲームウィンドウを表示してみる

Qiitaの記事に従って準備を完了させたら、とりあえずコードを書いてみます。
まずはゲームウィンドウの表示ですね。

    public class Game : GameBase {
        public Game(int width, int height) : base(width, height) {

        }

        static void Main() {
            using(var g = new Game(816, 624)) {
                g.Run();
            }
        }
    }

はいこれだけです。
これでゲームウィンドウが表示できます。

とりあえず動いたので、あとは何を作るかなんですよね。
今回は「ノベルゲーっぽい挙動するなにか」を作ってみようと思います。

タイトル画面を作ってみる

ゲームは「シーン」という単位で画面を管理させたいことが多いです(ツクールもUnityも基本はシーン単位で動いてます。ゲームの作り手によっては1シーンのみでやりくりする場合もありますが……)。
というわけで、DotFeatherでもシーンを作ってみましょう。
公式サイトの説明を参考に組み立てて行きますよ。

シーンを作成する

まずはSceneTitleというクラスを作りましょう。
こんな感じ。

    /// <summary>
    /// タイトルシーン
    /// </summary>
    internal class SceneTitle : Scene {

    }

わお簡単ですね。ツクール並の簡単さです。
公式ドキュメントによれば、Router.ChangeScene<T>()でシーンの遷移が可能なようです。
また、第2引数に辞書型変数を渡すことで次のシーンのOnStart()に値を渡せるようです(例ではジェネリック版ですが、第2引数云々と書いているのは非ジェネリックメソッド版の話かと思われます)。
これの何が嬉しいかと言えば、シーンをまたいでデータのやり取りができることですね。
Unityはシーンをまたいだデータのやり取りがちょっと面倒なので(シングルトンとか使うなら簡単ですけど……)、これは嬉しい点ですね。

タイトルシーンを呼んでみる

というわけで呼んでみましょう。
こんな感じでどうでしょう。

    public class Game : GameBase {

        private Router _router;

        public Game(int width, int height) : base(width, height) {
            _router = new Router(this);
        }

        protected override void OnLoad(object sender, EventArgs e) {
            var initData = new Dictionary<string, object> {
                { "キーA", "山田太郎" },
                { "キーB", "次郎さん" },
                { "キーC", "ちょんまげ君" }
            };
            _router.ChangeScene<SceneTitle>(initData);
        }

        protected override void OnUpdate(object sender, DFEventArgs e) {
            _router.Update(e);
        }

        static void Main() {
            using(var g = new Game(816, 624)) {
                g.Run();
            }
        }
    }

changeSceneでデータを渡していますが、特に深い意味はないです。
単に本当に使えるのかなーというレベルの興味で渡してみただけですね。
一応これでタイトルシーンが呼ばれているはずなのですが、DFEventArgsとかなんやねんとかいうのがまだわからないので、ルーターのupdateとかさっぱり意味がわからないので、ちゃっちゃかタイトルシーン本体に行きましょう。

タイトルシーンでデータを受け取る

とりあえずチュートリアルの通りにやるならばこんな感じでしょうか。

    /// <summary>
    /// タイトルシーン
    /// </summary>
    internal class SceneTitle : Scene {

        public override void OnStart(Router router, GameBase game, Dictionary<string, object> args) {
            System.Diagnostics.Debug.Write(args["キーB"]);
            //Print("C");
            //Print("Press ESC to return");
        }

    }

argsの中にはrouterから渡されたディクショナリが入っています。
キーBだと「次郎さん」が返ってくるはずで、実際に入っていたのでOKです。
ただPrintメソッドはSceneの中にどうもない臭く、以下のように書かなければなりません。

            game.Print("C");
            game.Print("Press ESC to return");

未確認ですが、おそらくイベントフック系の処理にはgameとrouterがそれぞれ渡されて来るので、シングルトンでGameBaseやらRouterやらを保持するなよ! そういう書き方するなよ! ということなのだと思います。

とにもかくにも、これでシーンごとにデータを受け渡すこともできますね。
夢が広がります。

フォロワー以上限定無料

特典はほとんどありませんが、入ってくださる方が増えれば増えるほど僕が喜ぶプランです(笑)

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

ブラウンチップ☆ 2019/12/12 08:09

バージョン管理のお話~Forkの使い方を紹介してみる~

はいどーもこんにちは、もう心も体もボロボロのツミオです。
最近更新をさぼっていたので、本日は2日連続更新です。
ちゅーわけで本日はGitの使い方の続きもとい、Forkの紹介をやってみようと思いますよ。

Forkのダウンロードとインストール

まずはFork本体をダウンロードしましょう。
公式サイトに行って、Download Fork for Windows(MacならMacでいいですが、僕はMacについては門外漢です)を押して、あとは言われるがままにインストールしてください。
適当にNext押しまくってるだけで基本OKです。

フォロワー以上限定無料

特典はほとんどありませんが、入ってくださる方が増えれば増えるほど僕が喜ぶプランです(笑)

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

ブラウンチップ☆ 2019/12/11 08:19

バージョン管理について書いてみる 紹介編

バージョン管理について書いてみる 紹介編

はいどーもこんばんは、限りなく無色に近い無職です。
本日はバージョン管理について書いていきたいと思いますよ。
Gitとかのアレです。

GitとGitHub

結構いろんな方がすでにGitという言葉を耳にしたことがあるのかなーと思います。
そんな人で「Gitって要はなんなの?」だとか「GitHubもよく聞くけど違いは何?」とかいう疑問を持ち、よくわからないしなんだか難しそうなので敬遠している方もいるのではないでしょうか。
かく言う僕もプログラミングを始めた頃は名前ばかりでよくわかりませんでした。
そこで小難しい話はなしにして、ズバリ簡潔にGitとGitHubの違いをまとめたいと思います(かなり雑にまとめているのでツッコミどころあるかと思いますがスルーしてください)。

  • Git
    • バージョン管理ツール
    • ウェブに繋げる必要はない
  • GitHub
    • Gitを使ったウェブサービス
    • 複数人で使うことが多い

イメージが湧いて来ましたでしょうか。

フォロワー以上限定無料

特典はほとんどありませんが、入ってくださる方が増えれば増えるほど僕が喜ぶプランです(笑)

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

ブラウンチップ☆ 2019/11/23 10:31

いじいじしたプラグイン

はいどーもこんにちは、本日も制作中のプラグインについて書いていきますよ。
よろしくお願いします。

収集システム

前回の記事でも「収集システム」を作っている的な話をしていたと思うのですが、こちら細かい調整等が続いていまして、先日ようやくいったん完成ということになりました。
たぶんそのうち冬空さんがゲームを公開してくれるんじゃないかな!?(R18注意)。
実績一覧もそろそろ更新しないとですね。

あとは仕事の話となるので、続きは……。

フォロワー以上限定無料

特典はほとんどありませんが、入ってくださる方が増えれば増えるほど僕が喜ぶプランです(笑)

無料

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

1 2 3 4 5

記事のタグから探す

月別アーカイブ

記事を検索