【Python】ダンガンロンパのガチャ(モノモノマシーン)を自動で回し続けるためのマクロを作る(Steam版)

f:id:Kanchi0914:20191214221134j:plain

ゲーム「ダンガンロンパ」にはモノモノマシーンと呼ばれるガチャガチャが存在しており、ゲーム内で得られるコインを投入することで仲間の好感度を上げるためのプレゼントアイテムを入手することができます。 Steam版では実績の一つに、このプレゼントを全種類集めるというものがあり、そのためには出現するプレゼントが全種類揃うまでひたすらガチャを回し続ける必要があります。 一度に投入するコインの枚数を増やすことで、プレゼントが重複する確率を下げることができるのですが、問題は一枚あたりの上昇確率が非常に悪く、結局一枚ずつ回すのが一番効率が良くなってしまっていることです。 ゲームをプレイしたことがある人には伝わると思うのですが、このガチャは非常にテンポが悪く、一度回すごとにおよそ7秒くらいの演出が入るため、上記の一枚ずつコインを投入するやり方では、たいへん手間がかかります。 適当に動画を見ながら作業していたのですが、あまりに面倒だったため、キーボードマクロを組んで自動で実行することにしました。

需要あるかわかりませんが、一応実行ファイル化してあります。 Danron_AutoGacha.exeをクリックするとウィンドウが表示され、qキーでマクロを実行します。 実行中は、再びqキーが押されるまでキーボードのsキー(矢印↓)とenterキーが約1秒ごとに押され続けます。 コインがなくなるまで回し続けるので、キリのいいところで終了してください。

github.com

以下は技術面の話です。

つまいづいた点

キーボードの入力イベントの取得

pythonでキーボードの入力イベントを取得するためにはmsvcrtが一番簡単っぽいのですが、これはwindowsでしか動かなかったり、別のウィンドウからの入力を受け付けないなどの難点があります。 今回は代わりににpynputを使用しました。

www.tohuandkonsome.site

engineeringnote.hateblo.jp

DirectInputへの対応

そもそも開発の経緯として、キーボードマクロを設定するフリーソフトは世の中に多く存在するので、最初はそれを使おうと思ったのですが、どれも簡単なマクロを実行するだけには高機能過ぎたり、うまく動かなかったりという点がありました。 最初はpyautoguiというパッケージを使って次のように簡単に書けると思ったのですが、

import pyautogui as pgui
import time

def do_loop():
    is_doing = True
    while True:
        if is_doing:
            time.sleep(1)
            print("running")
            pgui.press('down')
            time.sleep(1)
            pgui.press('enter')

if __name__ == '__main__':
    do_loop()

これだとキー自体は押されるのですが、実際のゲーム画面では動きませんでした。 要は入力方式の違いで、Direct Inputに対応させる必要があるみたいです。

stackoverflow.com

上のコードは別クラスからの呼び出しだと動かなかったので、下のように変更しました。

stackoverflow.com

tkinterのマルチスレッド化について

配布するためにtkinterGUIを作っているのですが、tkinterでメインウィンドウを表示させるroot.mainloop() を実行すると、それ以降のコードは実行されません。 並行して別の処理を行うためには別スレッドで処理を行う必要があるみたいです。

def process1():
    while True:
        if is_running:
            key_inputer.input()


def process2():
    with Listener(
        on_press = on_press,
        on_release= on_release
    ) as listener:
        listener.join()

ただしこうすると、メインウィンドウを閉じてもバックグラウンドにプロセスが残り、マクロが終了されません。 ウィンドウを閉じたときはメインスレッドだけが終了され、他のスレッドは終了されないためです。 これを回避するためには処理用のスレッドをデーモンスレッド化する必要があります。

teratail.com

並行処理についてあまり詳しくないのでもう少し勉強する必要があると感じました。

おわりに

Steam上のコミュニティではAutoHotKeyでのマクロが紹介されており、それが動くならそちらを使用されたほうが無難です。

steamcommunity.com

今回は練習のために自分で実装しましたが、Steamでの操作をマクロ化したいという方の参考になれば幸いです。

作業時間を自動で記録できるアプリを作りました(Windows向け)

だいぶ前の記事でこういうの作りたいと書きました。

kanchi0914.hatenablog.com

あれから少しずつですがC#+WPFで開発を進め、ようやく公開できるレベルのものが作れたので、GithubでReleaseするに至りました。

github.com

主な仕様は以下のようになっています。

  • 秒単位で起動しているアプリの実行時間が記録可能で、最小化したものは記録しないなどの設定が可能
  • 開いているファイル名ごとに作業時間の記録が可能
  • 一定時間操作がないと計測を中断(作業していないのに記録され続けることがない)
  • 時間管理系WebサービスTogglのAPIを利用し、Web上で日々の記録を確認できる

そもそもこれどういうモチベーションで開発したかというと、名前の元ネタになっている1万時間の法則という言葉があるんですね。

lineblog.me

この言葉が正しいかどうかはさておいて、なにか一つのことを極めようとする人にとって、自分が今までそれにどの程度時間を費やしたかというのは、例えば創作活動における一つのモチベーションや自信につながるのではないかと思っていて。実際に自分がそうなので、そういう用途のためにこれを開発しました。 ほとんど自分が使いたいからそうなっているのですが、主なターゲットはイラストや漫画を制作する方で、特に自分が使っているClip Studio Paintにおいて、特定のイラストを仕上げるのにかかった時間を自動で記録したいと感じている人は少なくないだろうと思ったので、そこの部分に力を入れています。 実際、ググってみるとやっぱりそういう要望は多く、しかし未だに公式で実装されていないので、だったら自分でつくろうというのが開発のきっかけです。

www.clip-studio.com

ask.clip-studio.com

例えば自分はできるだけ毎日ペンを持つようにしているのですが、そういった日々の積み重ねがデータとして残るのはやっぱり楽しいというか、自己満足に過ぎませんが「やっている」感が出て気持ちいいです。 Togglというサービスは個人的にかなり好きなのですが、あまり知られていないっぽいのがもったいないと思っているので、これをきっかけに少しでもユーザーが増えて欲しいという希望もあります。当アプリはまだまだ開発段階で今後もアップデート予定ですが、よければぜひ使っていただいてバグ報告やフィードバック等いただければと思います。 また今後の記事では知識の整理も兼ねて、開発で得られた知見について技術的な面で書ければと思っています。

(ちなみにこの記事を書くのにかかった時間は以下の通りです) f:id:Kanchi0914:20191106223840p:plain

【C#】ナップサック問題の最適値を満たす商品の番号を求める(動的計画法)

最近、蟻本と呼ばれる本を使って競技プログラミングの勉強をしています。 動的計画法ナップサック問題を解く部分で、最適値ではなく最適解(選ぶ品物の番号)を求めるコードが書かれていなかったので、少し調べてみました。 各品物が使われるかどうかを、作成したDPテーブルを逆順に辿って調べる必要があるらしく、意外なことにその部分に関してあまりコードが転がってなかったので、少し自分で考えてみました。

    public static class KnapsackSolver
    {
        static int[] weights = new int[] { 2, 2, 4, 3, 2};
        static int[] values = new int[] { 1, 2, 4, 2, 3};

        static int numOfItems = weights.Length;
        static int weightLimit = 4;

        //DP_tabel[i,w]はi+1番目からn番目の荷物のみを使って、残り容量wのナップサックに詰め込める価値の最大値
        static int[,] DP_tabel = new int[weights.Length + 1, weightLimit + 1];

        public static void Main()
        {
            Solve();
            var answer = GetOptimalItems();
            PrintAnswer(answer);
        }

        public static void Solve()
        {
            for (int i = numOfItems - 1; i >= 0; i--)
            {
                for (int j = 0; j <= weightLimit; j++)
                {
                    if (j < weights[i])
                    {
                        DP_tabel[i, j] = DP_tabel[i + 1, j];
                    }
                    else
                    {
                        var value1 = DP_tabel[i + 1, j];
                        var value2 = DP_tabel[i + 1, j - weights[i]] + values[i];
                        DP_tabel[i, j] = Math.Max(value1, value2);
                    }
                }
            }
        }
        
        static int[] GetOptimalItems()
        {
            var answer = new int[numOfItems];
            var enableWeight = weightLimit;

            for (int i = 0; i < numOfItems; i++)
            {
                if (enableWeight >= weights[i])
                {
                    //入れる場合に可能な最大価値
                    var value1 = DP_tabel[i + 1, enableWeight - weights[i]] + values[i];
                    //入れない場合に可能な最大価値
                    var value2 = DP_tabel[i + 1, enableWeight];

                    if (Math.Max(value1, value2) == value1)
                    {
                        answer[i] = 1;
                        enableWeight -= weights[i];
                    }
                    else
                    {
                        answer[i] = 0;
                    }
                }
            }
            return answer;
        }

        static void PrintAnswer(int[] answer)
        {
            for (int i = 0; i < answer.Length; i++)
            {
                if (answer[i] == 1) Console.WriteLine($"品物{i + 1}、重さ{weights[i]}、価値{values[i]}");
            }
        }
    }

注意しなければいけない点として、結果を逆算するときのDPテーブルの走査順は作成時と逆になります。 今回の場合は蟻本に乗ってるのと同様に i = numOfItems - 1から降順にDPテーブルを作成していますが、i = 0から昇順にDPテーブルを作成した場合は、最適解を求める際にi = numOfItems - 1からforループを始める必要があるということです。

参考:https://mathwords.net/napusaku#i-4

【C#】Unityで一からギャルゲー(ノベルゲーム)作るためのサンプルコード

一応は前回の記事の続きにあたりますが、コードの内容はほとんど別物です。 前回の記事ではゲームの方針として脱出ゲームのようなものを考えていましたが、今回はやや方向性を変えギャルゲーっぽいものを作ることを想定します。

f:id:Kanchi0914:20191002174115g:plain

今回の記事でやりたいことは以下になります。

1.シナリオをスクリプト外のデータから読み込み、それに合わせて処理を行う
2.キャラクターの立ち絵に簡単なアニメーションをつける
3.テキストの文字送り機能の実装

コードは以下に上げてあります。 github.com

またUnityroomで動作確認できます。 unityroom.com

テキストベースのシナリオ処理

まず上で述べた1に関してです。データの保存形式にはcsvjsonなどいろいろありますが、調べてみるとメタ文字を設定したテキストデータで記述するのが一般的らしいので、その例に倣います。今回は#charaタグで立ち絵を登場させ、#imageタグでそのグラフィックを変更するなどを行いました。今回は実装していませんが他には背景を変えたり、BGMや効果音などのタグを用意しそれに合わせて音を鳴らすなどもできそうです。

#scene=001
#speaker=主人公
{あ~ 今日もいい天気だなぁ}
#speaker=???
{お~い 待ってよ~~~}
#speaker=主人公
{ん?この声は…}
#chara=hiroko
#image_hiroko=smile
#speaker=ヒロ子
{じゃ~ん あたしだよ~~~
一緒に学校行こうよ~~~}

1シナリオの流れは、シーンという小単位に区切って管理するものとします。 1シーンに相当する文字列を保持しておくSceneクラスを用意し、 別途パーサークラスを用意して一行ずつ読み取らせることで処理を行います。

public class Scene
{
    public string ID { get; private set; }
    public List<string> Lines { get; private set; } = new List<string>();
    public int Index { get; private set; } = 0;

    public Scene(string ID = "")
    {
        this.ID = ID;
    }
    ...

読み取った位置行ごとの文字列に応じて、実際に処理させるクラスのメソッドを呼び出します。 例えば現在の行が#speakerタグのついた位置に達したときにはSetSpeakerメソッドが、#imageタグの位置に来たときはSetImageメソッドが呼び出される等です。

    public void SetSpeaker(string name = "")
    {
        gui.Speaker.text = name;
    }

    public void SetImage(string name, string ID)
    {
        var character = Characters.Find(c => c.Name == name);
        character.SetImage(ID);
    }

前回はデリゲートメソッドを用いてボタンクリック時に実行されるメソッドを指定することで 選択肢を実装していましたが、今回は#optionというタグを付けることで 選択した先のシーンに遷移し、その先で処理を行うものとします。

#speaker=ヒロ子
{そういえば、今日はテストだったよね~勉強した?}
#options={
{した,002}
{してない,003}
}

#scene=002
#speaker=主人公
{したに決まってるだろ}
#image_hiroko=aseri
#speaker=ヒロ子
{ほんと~?あたし全くしてないよ~~~}
#speaker=主人公
{おまえはいつもそうだな~早く学校行くぞ}
#next=004

#scene=003
#speaker=主人公
{してない。面倒だから}
#image_hiroko=smile
#speaker=ヒロ子
{ほんと?やった~あたしと同じだ~}
#speaker=主人公
{そんなんで喜ぶなよ…早く学校行くぞ}
#next=004

用意されたタグ以外の処理を行いたいときはどうすればいいのか考えたのですが、MethodInfoクラスというものを用いることで実現できるようです。これによりテキストデータ内にメソッド名を直接書いてしまうことができるので実装の幅が広がります。自分も調べて初めて知りましたがかなり便利そうです。

    if (line.Contains("method"))
    {
        line = line.Replace("method=", "");
        var type = actions.GetType();
        MethodInfo mi = type.GetMethod(line);
        mi.Invoke(actions, new object[] { });
    }

これを用いることで、「○○というフラグが立っていればシーン00Xに遷移し、そうでなければシーン00Yに遷移する」という、いわゆるif-gotoの処理を行うことができます。フラグを立てる事自体もこれでできるでしょう。

    public void HasKey()
    {
        if (ExistsKey()) sceneController.SetScene("00X");
        else sceneController.SetScene("00Y");
    }

DOTweenを使ったアニメーション

上記の2.3についてですが、これはアセットストアで配信されているDoTweenというアセットを導入することでごく簡単に実装することができます。簡単に説明するとUnityのtransformに対して時間経過で色々できる便利なやつで、Unityのアニメーションクリップを使わずともお手軽にアニメーションを導入できるので、複雑な動きを必要としない場合はこれだけで十分間に合うと思います。

f:id:Kanchi0914:20191002174028g:plain

詳しい説明は他サイト様にお任せするとして、例えばメッセージテキストのクリックを促す下矢印のアニメーションは、スクリプトから以下のように記述するだけでいいし、キャラクター登場時のエフェクトなんかもDoTweenでやっています。

    public GameObject Delta;

    private void Start()
    {
        Delta.transform.DOMoveY(-0.2f, 1.0f).SetRelative().SetEase(Ease.InCubic)
            .SetLoops(-1, LoopType.Restart);
    }

なおDoTweenはFree版と有料のPro版が存在しますが、多分基本的な機能に違いはないと思います。自分はPro版導入してるのでもしFree版で動かなかったらすみません。

また3のテキストの文字送りに関して、本来であればUpdate関数内で一文字ずつ処理を行ったりしなければならないのですが、これもDoTweenのDOTextというメソッドを使うことでこれだけ簡単にできます。テキストのアニメーションを完了させることで文字送りのスキップも行なえます。 (参考:https://gist.github.com/anzfactory/da73149ba91626ba796d598578b163cc)

    public void SetText(string text)
    {
        tempText = text;
        if (textSeq.IsPlaying())
        {
            textSeq.Complete();
        }
        else
        {
            gui.Text.text = "";
            textSeq = DOTween.Sequence();
            textSeq.Append
                (gui.Text.DOText
                (
                    text,
                    text.Length * messageSpeed
                ).SetEase(Ease.Linear));
        }
    }

結論

だいぶかいつまんで説明しましたが、今回の記事で自分が言いたかったのは

  • MethodInfoを使えばだいたい何でもできそう
  • DoTweenは便利

の2点に尽きます。実際今回のコードに少し手を加えるだけでそれなりに見れるゲームは作れると思います。 実際には他に優れたUnity向けのツクール系エディタやアセットが存在するのでそれを使えばいいだけの話なのですが

その他

お借りした背景素材: きまぐれアフター 様

【C#/Unity】出現率から項目を一つルーレット選択で選ぶ拡張メソッド

RPGなどで、出現率(選択確率)などからある項目を一つ選びたい場面がしばしばあると考えられます。 例えば、敵の各行動が行われる確率を以下のようにDictionary形式で表すとするとき

    var enemyActionProbs = new Dictionary<string, int>()
    {
        { "attack", 60 },
        { "guard", 30 },
        { "escape", 10 },
    };

attack,guard,escapeの各項目が、それぞれ60%、30%、10%の確率で得られるメソッドかなにかが欲しいわけです。 こういった場合、おそらく遺伝的アルゴリズムなどで用いられるルーレット選択を用いて得ることが一般的であると思われます(実際にはどうかわかりません。調べてもあまり出てこなかったので)。

簡単に説明すると、それぞれが選択される確率の総和をとり、0からその和までの乱数の値を決定し、 その値に応じた値を選択するのがルーレット選択です。 以下は適当に作った図ですが、例えば乱数の数が65だった場合、上の例だとguardが選ばれます。

f:id:Kanchi0914:20190930053935p:plain:w600

なおこの例では各確率の総和が100になるようにしていますが、別にそうしなくても構いません。 例えばアイテムの出現割合を記したDictionaryから一つを選びたいときなどにも使えます。

自分は今まではクラス毎に実装していたのですが、流石に効率が悪いのでジェネリックを用いた拡張メソッドを使って実装することにしました。

using System.Collections.Generic;

public static class DictExtensions
{
    public static T GetByRouletteSelection<T>(this Dictionary<T, int> dict)
    {
        var sum = 0;
        T selected = default;

        foreach (var key in dict.Keys)
        {
            sum += dict[key];
        }

        int tempSum = UnityEngine.Random.Range(0, sum);

        foreach (var key in dict.Keys)
        {
            tempSum -= dict[key];
            selected = key;
            if (tempSum < 0)
            {
                break;
            }
        }

        return selected;
    }
}

以下のように使います。キーの型は何でも構わないので自作クラス等をキーにしたDictionaryにも使うことができます。

    public void DictTest()
    {
        var enemyActionProbs = new Dictionary<string, int>()
        {
            { "attack", 60 },
            { "guard", 30 },
            { "escape", 10 },
        };
        
        for (int i = 0; i < 50; i++)
        {
            Debug.Log(enemyActionProbs.GetByRouletteSelection());
        }
    }

(実行結果)

attack
guard
attack
attack
attack
escape
attack
guard
guard
attack
attack
...

【C#】Unity 2Dでできるだけ簡単にアドベンチャーゲーム(ノベルゲーム)を作る その2

kanchi0914.hatenablog.com

前回の記事の続きです。前回の記事では、クリックでテキスト送りの部分まで実装しましたが、当然それだけではゲームとして成立しません。今回は選択肢を表示させ、コマンドを実行することで最低限ゲームとして遊べるようにします。完成すると下のようになります。

f:id:Kanchi0914:20190723065603g:plain

まずはコマンドを選択するため、動的にコンポーネントの数が変化するPanelを作ります。HierarchyビューのCanvasに適当にPanelを配置し、PanelのInspectorビューからVertical Layout Groupを追加、以下のように設定します。なお今回脱出ゲームっぽい感じにしたかったので、それにあわせて背景も変えてあります。 f:id:Kanchi0914:20190723064530p:plain f:id:Kanchi0914:20190723064326p:plain

次に、今用意したボタン用のPanelに子要素としてButtonを追加し、ButtonのInspectorビューからLayout Elementを追加、Min Heightの値を適当に設定します。 f:id:Kanchi0914:20190723064420p:plain

設定した後、ButtonをAssetsフォルダにドラッグ&ドロップし、Buttonをプレハブ化します。 f:id:Kanchi0914:20190723064546p:plain

 これでUnityエディタ側の準備は完了です。では次にスクリプトを用意していきましょう。
 まずは以下のようにコマンド選択のためのOptionクラスを用意します。

    class Option
    {
        public string Text;
        public Action Action;
        public Func<bool> IsFlagOK = () => { return true; };
    }

 ここでActionFuncというのは、デリゲートと呼ばれる型の一種です。詳しい説明は省きますが、メソッドの参照をそのまま渡せる型だと思ってください。つまり、これを使ってさっき作ったボタンにメソッドの参照を渡すことで、ボタンでコマンドを選択→特定の処理を実行、という流れができるわけですね。  またScenarioクラスに、このOptionクラスのListを追加したりするなど修正を加えます。

    class Scenario
    {
        public string ScenarioID;
        public List<string> Texts = new List<string>();
        public List<Option> Options = new List<Option>();
        public string NextScenarioID;
    }

実際にはどう使うかというと、ScenarioクラスのOptionsに選ばせたいコマンドの数だけOptionクラスのインスタンスを追加し、Textにコマンドの内容、Actionにそのコマンドを実行した際の処理となるメソッドへの参照を代入します。

        scenario02 = new Scenario()
        {
            ScenarioID = "scenario02",
            Texts = new List<string>()
            {
                "どうする?",
            },
            Options = new List<Option>
            {
                new Option()
                {
                    Text = "辺りを見渡す",
                    Action = LookAround
                },
                new Option()
                {
                    Text = "鍵を拾う",
                    Action = TakeKey,
                    IsFlagOK = () =>
                    {
                        return isCheckedKey && !items.Contains("Key");
                    }
                },
                new Option()
                {
                    Text = "扉を開ける",
                    Action = OpenDoor,
                }
            }

        };
    public void LookAround()
    {
        var scenario = new Scenario();
        scenario.NextScenarioID = "scenario02";
        if (!items.Contains("Key"))
        {
            scenario.Texts.Add("足元に鍵が落ちている");
            isCheckedKey = true;
        }
        else
        {
            scenario.Texts.Add("足元には何もない");
        }
        SetScenario(scenario);
    }

    public void OpenDoor()
    {
        var scenario = new Scenario();
        if (items.Contains("Key"))
        {
            scenario.Texts.Add("鍵を使って扉を開いた");
            scenario.Texts.Add("クリアー!");
        }
        else
        {
            scenario.Texts.Add("鍵がかかっていて開かない");
            scenario.NextScenarioID = "scenario02";
        }
        SetScenario(scenario);
    }

    public void TakeKey()
    {
        var scenario = new Scenario();
        scenario.Texts.Add("鍵を拾った");
        scenario.NextScenarioID = "scenario02";
        SetScenario(scenario);
        items.Add("Key");
    }

またIsFlagOKは選択肢を出現させるかどうかのbool値で、基本的にはTrueをとりますが、これを特定の条件を満たしているかどうかで分けることができます。 ここでは、「”辺りを見渡す”コマンドを選択した」&「まだアイテム”Key”を拾っていない」状態でのみ、「鍵を拾う」コマンドが出現するようになっています。

                new Option()
                {
                    Text = "鍵を拾う",
                    Action = TakeKey,
                    IsFlagOK = () =>
                    {
                        return isCheckedKey && !items.Contains("Key");
                    }
                },

IsFlagOKの右辺のような書き方をラムダ式と呼び、簡単に言えばメソッドの宣言を省略して書くことが出来ます。 これと先程のデリゲートを組み合わせることで、かなり自由にコードを書くことができるようになります。

あとはコマンドに応じて先程プレハブ化したボタンを設定する処理を書いたりします。

    void SetOptions()
    {
        foreach (Option o in currentScenario.Options)
        {
            if (o.IsFlagOK())
            {
                Button b = Instantiate(optionButton);
                Text text = b.GetComponentInChildren<Text>();
                text.text = o.Text;
                b.onClick.AddListener(() => o.Action());
                b.onClick.AddListener(() => ClearButtons());
                b.transform.SetParent(buttonPanel, false);
            }
        }
    }

buttonPaneloptionButonは先程用意したパネルとボタンへの参照です。前回のText同様GameControllerのInspector上でアタッチしておきます。このように、Unityチュートリアルなどで見るボタンの処理には、先程のデリゲートとラムダ式が用いられていたわけですね。 その他色々修正したコード全体は以下に上げておきます。

https://github.com/kanchi0914/AdventureGameSample/

これで、コマンド選択の結果によってフラグの設定を切り替えたり、あるいはその内容に応じてコマンドを変更したりすることができるようになりました。 とりあえずここまでできれば、簡単なノベルゲーム程度なら作れるようになったのではないでしょうか。本スクリプトではcurrentScenarioにScenarioクラスのオブジェクトをセットすることでメッセージが表示されるため、例えば画面上の特定の点をクリックしたときにメッセージや選択肢を表示させたりすることで、脱出ゲーム的なのも作れると思います。

今回は処理を行うタイミングがコマンドを実行した場合に限られましたが、例えばC# ver7.0から導入されたタプル機能を使い、ScenarioクラスのTextsを以下のように変更することで、テキストを一行送る度に処理を行うことができるようになります。なおUnityでC# 7.0を使えるのは特定のバージョン以降に限られます(多分)。

public List<(string, Action)> Texts = new List<(string, Action)>();

こうすることで、例えばメッセージに併せてキャラクターの画像を変更できたりという、いわばギャルゲーっぽい処理を簡単に実装することができます。これについては今後時間があれば書きたいと思います。

【C#】Unity 2Dでできるだけ簡単にアドベンチャーゲーム(ノベルゲーム)を作る その1

だいぶ前に書いた書きかけの記事があったので、追記して更新します。 この記事は、「Unityでアドベンチャーゲーム作りたいけど、どうやったらいいのか分からない」という方のために書かれた、 Unity 2Dで可能な限り最小限のコードのみでアドベンチャーゲームを作る、超入門チュートリアルです。

  • はじめに
  • ゲームの設計
  • プロジェクトの基本設定
    • プロジェクトの作成
    • メッセージウィンドウの追加
  • C#スクリプト(文字送り編)
  • コード解説

続きを読む