片隅アイロニー奮闘記その1

Created: 2025/01/07

本記事は、Unity初心者が「片隅アイロニー」というゲームを作り上げるまでの過程を記したドキュメンタリーです。
今回は企画が生まれてからタイトルシーンができるまでを記事にしました。

まず片隅アイロニーってなに?

筆者がUnity6で作っている、

パソコンの片隅で、
一緒に作業しながらキャラクターを育てる、
パソコンユーザーのための、
放置育成型ストーリーゲーム

です。

ようは、
ちっちゃい画面サイズで、作業中にアイロニーというキャラクターを育てて
ストーリーを進める2Dゲームですね。
作業に最適化されたゲームメカニズムと、PCゲームの特性を活かしたストーリーがウリです。
もっと詳しく知りたい方は企画書をご覧ください。
https://drive.google.com/file/d/1Hj4OS8XJ9xzO1r0hk3mcNwvKAsXpaQr-/view?usp=drive_link

ひらめき

「片隅アイロニー」が生まれたのは2024年7月中旬の木曜日、電子情報工学概論の講義を受けているときのことです。
インターネットのあれこれをぼけーっと学びながら、わたしは思いました。

ねむい。
せめて何か、パソコンのすみっこで面白映像とかながれていればなぁ…と。

!!!?!??!?!!?


ひらめきました。パソコンの片隅で遊べるゲームを。
作業中にプレイして、退屈を紛らわせることに特化したゲームを。

その後、「片隅」から連想するキーワードを考えていたら、おぼろげながらアイロニーという言葉が浮かんできたんです。
このとき私は言葉の意味を知りません。語感だけで思いつきました。
検索するとアイロニー=皮肉とのこと。
皮肉…片隅…作業中にプレイ…

?!??!??!?!?!!!!


最初はゲーム画面を小さくして、作業中にプレイをしやすくしよう!
ゲームが進むごとに画面を大きくして、最終的にフルスクリーンにしよう!
ああ、これは皮肉!やればやるほど作業の邪魔になっていく!
となると、キャラクター育成ゲームがふさわしい!キャラの成長につれて画面サイズもデカくしよう!
操作もほとんどないから作業中プレイに適している!

こうして、ながら育成ストーリーゲーム、「片隅アイロニー」が生まれました。

PythonからUnityに

最初はプログラマー志望の子にコードを任せ、PythonのPygameで作ろうとしていましたが一向に進まず。
2024年11月になり、ちょうどUnity6がリリースされたので思い切って切り替え、
Unity6000.0.26f1で本格的に開発を始めました。
このあたりで件のプログラマーと完全に決裂し、一人で進めるように。
今考えても…この縁切りは賢明な判断だと思います。

ちなみになぜUnity6なのかというと、ゲーム起動時のスプレッド画像を変えられるから。
それだけです。新機能はまったく活用してません。

タイトルシーン

さて、Unityでゲームを作ってみようとやる気になったものの、ツクールとティラノスクリプトしか使ったことがないので
なにをやればいいのかさっぱり分かりません。C言語も分かりません。
なのでChatGPTに聞いてみました。
以下、やり取りを全部テキストにしてるのでめっちゃ長いです。

Q:
unity6で2Dのゲームを制作します。ゲームを起動したらウィンドウモード、画面サイズ480*360ピクセルのtitle_sceneを開き、そのシーン内にはクリックできる162*48ピクセルのボタンを配置します。背景には3枚の差分がある480*360ピクセルの画像を切り替えてアニメーションさせます。ボタンを押したら画面全体を5フレームだけ480*360ピクセルの画像で覆うトランジションを挟んだのち、画面サイズを240*180ピクセルに変更してscene_1へ遷移します。 ここまでの流れを作るにはどのようにすればいいですか?1工程ずつ具体的に教えてください。

A:
Unity6(Unity 2021以降のバージョン)での具体的な手順を、順を追って説明します。

1: プロジェクトの作成
Unity Hubで新規プロジェクトを作成します。プロジェクトタイプは「2D」を選択し、プロジェクト名や保存場所を指定してプロジェクトを作成します。

2: ウィンドウモードと画面サイズの設定
Edit > Project Settingsを開き、Player設定に移動します。
「Resolution and Presentation」セクションで、デフォルトのスクリーンモードをウィンドウモードに変更します。
Default WidthとDefault Heightにそれぞれ「480」と「360」を設定し、ウィンドウサイズを固定します。

3: Title Sceneの作成
File > New Sceneから新しいシーンを作成し、保存時に「title_scene」と命名します。

4: 背景アニメーションの作成
必要な3枚の背景画像(480x360ピクセル)をAssetsフォルダにインポートします。
シーン内にImageコンポーネントを配置し、サイズを「480x360」に設定して、背景画像をスプライトとして適用します。
スクリプトを作成して、背景画像を3枚の画像で切り替えるアニメーションを設定します。
アニメーションをスクリプト内で一定間隔(例:0.5秒毎)でループするように設定します。

背景アニメーション用のスクリプト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;
using UnityEngine.UI;

public class BackgroundAnimation : MonoBehaviour
{
    public Sprite[] backgrounds;
    public Image backgroundImage;
    private int currentImageIndex = 0;
    private float timer = 0;
    public float interval = 0.5f;

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= interval)
        {
            timer = 0;
            currentImageIndex = (currentImageIndex + 1) % backgrounds.Length;
            backgroundImage.sprite = backgrounds[currentImageIndex];
        }
    }
}

・BackgroundAnimationスクリプトをCanvas内のImageオブジェクトにアタッチし、backgrounds配列にインポートした3枚の背景画像を設定します。

5: クリック可能なボタンの作成
UI > Buttonでボタンを追加し、サイズを「162x48」に設定します。
ボタンのテキストやデザインを必要に応じて変更します。
ボタンがクリックされた際にトランジションが始まるよう、次の手順で設定します。

6: トランジション用の画面覆い画像の設定
480x360ピクセルのトランジション用画像をAssetsフォルダにインポートします。
新たにImageコンポーネントをシーン内に配置し、トランジション用画像をスプライトとして設定します。
トランジションのImageオブジェクトは、初期状態で非表示(enabled = false)にしておきます。

7: トランジションとシーン遷移を管理するスクリプトの作成
ボタンクリック時に5フレームだけ画面を覆った後、画面サイズを変更してscene_1へ遷移するスクリプトを作成します。

トランジションとシーン遷移スクリプト
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections;

public class TransitionManager : MonoBehaviour
{
    public Image transitionImage;
    public Button startButton;

    void Start()
    {
        startButton.onClick.AddListener(() => StartCoroutine(TransitionToScene1()));
    }

    IEnumerator TransitionToScene1()
    {
        // トランジションを5フレーム表示
        transitionImage.enabled = true;
        for (int i = 0; i < 5; i++)
        {
            yield return null;
        }
        transitionImage.enabled = false;

        // 画面サイズ変更
        Screen.SetResolution(240, 180, false);

        // 次のシーンへ移動
        SceneManager.LoadScene("scene_1");
    }
}

・TransitionManagerスクリプトをCanvasの任意のオブジェクトにアタッチし、transitionImageフィールドにトランジション用のImageオブジェクト、startButtonフィールドにボタンオブジェクトを指定します。

8: Scene_1の作成
File > New Sceneで新しいシーンを作成し、「scene_1」として保存します。
必要なゲームオブジェクトや背景、キャラクターなどを配置し、scene_1を構築します。

9: テストとデバッグ
最後に、title_sceneからscene_1への遷移がスムーズに行われるかテストします。

うわぁすごい。よくわかりませんがめちゃくちゃ具体的に教えてくれました。
この後、初心者にもわかるように教えてくださいとお願いしたら
もっと丁寧に教えてくれました。

言うとおりにエディタやコードをいじってみますが、当然エラーが出ます。
しかしAI君はエラーの解決方法も丁寧に教えてくれました。
○○ができないエラーがおきました or エラーコードコピペ するだけで。
デバッグに関しては確実にシンギュラリティ起きてます。

なんどもやり取りを繰り返し、背景画像をアニメーションさせるBackgroundAnimation、タイトルシーンを制御するTitleSceneController、シーン遷移の演出を管理するTransitionManagerができました。

↓背景画像を切り替えまくってアニメーションさせています。

BackgroundAnimation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;
using UnityEngine.UI;

public class BackgroundAnimation : MonoBehaviour
{
    public Sprite[] backgrounds;  // ここに3枚の背景画像を登録
    public Image backgroundImage;
    private int currentImageIndex = 0;
    private float timer = 0;
    public float interval = 0.5f;  // 背景が切り替わる間隔

    void Update()
    {
        timer += Time.deltaTime;
        if (timer >= interval)
        {
            timer = 0;
            currentImageIndex = (currentImageIndex + 1) % backgrounds.Length;
            backgroundImage.sprite = backgrounds[currentImageIndex];
        }
    }
}

↓ボタンを押したらセーブデータをロードして、TrantisionManagerを起動しています。

TitleSceneController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class TitleSceneController : MonoBehaviour
{
    public TransitionManager transitionManager;  // TransitionManager を参照

    private void Start()
    {
        Screen.SetResolution(480, 360, false);
    }
    public void LoadGame()
    {
        // トランジションを開始
        if (transitionManager != null)
        {
            transitionManager.StartTransition();  // トランジションをスタート
        }

        // トランジションの終了後にシーンをロード
        StartCoroutine(WaitForTransition());
    }

    private IEnumerator WaitForTransition()
    {
        // トランジションが終了するまで待機
        yield return new WaitUntil(() => !transitionManager.IsTransitioning);

        // ゲームデータをロード
        LoadGameData();

        // トランジションが終わったらシーンをロード
        if (GameManager.Instance.SceneLevel > 0)
        {
            SceneManager.LoadScene("Scene" + GameManager.Instance.SceneLevel);
        }
        else
        {
            Debug.LogWarning("No valid save data found. Starting new game.");
        }

    }
    private void LoadGameData()
    {
        // セーブデータをロード
        SaveManager.Instance.LoadGame();

        // ステータスがロードされたかを確認(Debug用)
        Debug.Log($"Loaded Game Data: SceneLevel={GameManager.Instance.SceneLevel}, ScenarioLevel={GameManager.Instance.ScenarioLevel}, Exp={GameManager.Instance.Exp}, etc.");
    }
}

↓暗転からの一瞬画像をチラ見させる演出を入れ、終わったらセーブデータの変数SceneLevelに応じてシーンを遷移させています。
(画面サイズ変更うんぬんは修正するかもしれない)

TrantisionManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class TransitionManager : MonoBehaviour
{
    public Image fadeImage;         // 暗転用のImage(黒の背景画像)
    public Image transitionImage;   // トランジション用のImage
    private bool isTransitioning = false;

    // トランジション中かどうかを外部から確認できるプロパティ
    public bool IsTransitioning => isTransitioning;


    void Start()
    {
        // 初期設定
        fadeImage.color = new Color(0, 0, 0, 0);   // 完全に透明な黒
        fadeImage.enabled = true;                  // fadeImageを有効化
        transitionImage.enabled = false;           // トランジション用の画像は最初は非表示
    }
    public GameObject buttonObject; // ボタンのGameObject
    public void StartTransition()
    {
        if (!isTransitioning)
        {
            isTransitioning = true;

            // ボタンを非表示にする
            if (buttonObject != null)
            {
                buttonObject.SetActive(false); // ボタンを無効化
            }

            StartCoroutine(TransitionSequence());
        }
    }

    private IEnumerator TransitionSequence()
    {
        // 1. 暗転処理(120フレーム)
        for (int i = 0; i <= 120; i++)
        {
            float alpha = (float)i / 120f;   // 0から1へ増加
            fadeImage.color = new Color(0, 0, 0, alpha); // 徐々に黒に
            yield return null; // 1フレーム待機
        }

        // 2. トランジション画像の表示(2フレーム)
        transitionImage.enabled = true;
        fadeImage.enabled = false; // 暗転用の画像を非表示に
        yield return new WaitForSeconds(0.5f / 60f); // 2フレーム待機(約0.33秒)

        // 3. シーン遷移の準備(画面サイズ変更を遅延)
        string sceneName = "scene" + GameManager.Instance.SceneLevel;
        SceneManager.LoadScene(sceneName);

        // 4. シーンロード後に画面サイズを設定
        StartCoroutine(WaitForSceneLoad(sceneName));
    }

    private IEnumerator WaitForSceneLoad(string sceneName)
    {
        // シーンが完全にロードされるまで待機
        while (!SceneManager.GetSceneByName(sceneName).isLoaded)
        {
            yield return null;
        }

        // シーンがロードされた後に画面サイズ変更
        switch (sceneName)
        {
            case "scene1":
                Screen.SetResolution(240, 180, false);
                break;
            case "scene2":
                Screen.SetResolution(240, 180, false);
                break;
            case "scene3":
                Screen.SetResolution(320, 240, false);
                break;
            case "scene4":
                Screen.SetResolution(400, 300, false);
                break;
            case "scene5":
                Screen.SetResolution(480, 360, false);
                break;
            case "scene6":
                Screen.SetResolution(480, 360, false);
                break;
        }

        Debug.Log("Screen resolution set for " + sceneName);
    }
}

そして出来たタイトルシーンが記事のトップ画像です。ちゃんと背景画像はアニメーションして、はじめるボタンを押すとScene1に遷移してくれます。
この3つのスクリプトを含むタイトルシーンの処理、なんと2日で出来ました。
ChatGPTのエンジニア力はすごいです。シンギュラリティを実感しましたね。
ときどき間違いを起こすので注意は必要ですが、使いこなせればとても便利な代物だと感じました。

おわり

今回の記事はここまでとさせていただきます。
次回があればScene1のはしりと、中ボスクラスのスクリプト、会話システムを司るConversationControllerのことを書きます。