GoogleAdsence

お金より愛を選んだゲームプログラマーの末路

2020年1月29日

究極の弾幕を作るという目標

去年から弾幕シューティングゲームの制作を始めて、講座は終わりその後ずっと数学を勉強しひたすら、 東方永夜抄における八雲紫のラストワード 深弾幕結界-夢幻泡影-の再現するために全力を尽くしました。
そして、これができた時に思わずガッツポーズ!!!

ゲームを作るより優先してしまったこと

本来ならば、ゲームクリエイターは、プレイヤーを楽しませるためにゲームを作るのが最終目標です
しかし、私はこのゲームを作ってるうちに、弾幕を作るのが楽しくて、楽しくて、夢中になっていました。
この時点でゲームクリエイターとしては失格です。

マネタイズよりも自分の好きなことを徹底的にした

前にも書きましたが、ソーシャルゲームのマネタイズが大嫌いで、かといって自分の好きなコンシューマーゲームを作ることも許されない立場でした。
今はフリーランスの立場で仕事をしてるので、好きな物を作る事自体はできますが、マーケティングやマネタイズ、宣伝などをまったくせずにひたすら好きなプログラムを書いた結果、苦労して作った作品は世の中に出ずに埋もれてゆくしかないというのが、はっきりわかりました。

過程だけでも結果だけでもだめなのが人生

世の中お金がすべてじゃないという言葉は、よく聞きます。しかし、お金がなければ、得られないものもたくさんあります。

  • 自分の好きな事を貫き通す(過程)
  • お金を稼いで多くの人にプレイしてもらう事(結果)

この2つの要素がなければ、ゲームクリエイターは幸せになることができない生き物なのです。

この素晴らしい弾幕に祝福を

しかし、何度も何度も苦労してあきらめそうになりながらも作った思い出いっぱいのソースコード。
たぶんあっという間に忘れてしまうので、思い出としてソースコードをブログに残しておきたい。
自分には絵を描くこともできないし、音楽を作る事もできない、ソースコードでしか語ることができない。

public class CSindanmakukekkai : CBossBulletFactory
{
    public CSindanmakukekkai(MOVE_BOSS_POS move_boss_pos) : base(move_boss_pos)
    {
        SpelName = "深弾幕結界-夢幻泡影";
    }

    int sub = 0;
    int count = 0;
    int pattan = 0;
    public override bool Run(double x, double y)
    {
        const int ASPEED = 56;     // アシストが90度回転するスピード
        const int ASPEED2 = 80;     // アシスト2が90度回転するスピード
        const int ASPEED3 = 60;     // アシスト3が90度回転するスピード
        const int MAPX = 300;    // フィールドをこの大きさで考えてたので、FMXだとずれるかもしれません
        const int MAPY = 350;    // 上に同じ
        const double CRANGE_SHIN = 5.0;    // 自機のあたり判定の大きさ

        /* ▼ ボスの初期位置変更 */
        
        if (Cnt == 0 && sub == 0)
        {
            sub = 1;
            Cnt -= 15;
            //input_phy_pos(FMX / 2, FMY / 2, 10);
        }
        /* ▲ ボスの初期位置変更 */
        int i, j, t2 = Cnt;
        double bx, by;
        //static int memory[BOSS_BULLET_MAX];    // カウント記憶用


        // カウンタ
        if (t2 == 0 || t2 == ASPEED * 13 + (ASPEED / 3) || t2 == ASPEED * 13 + (ASPEED / 3) + ASPEED2 * 9 || t2 == ASPEED * 13 + (ASPEED / 3) + ASPEED2 * ASPEED3 * 11)
        {
            count = 0;
        }
        else
        {
            count++;
        }

        // 弾幕パターン分岐フラグ
        if (t2 == 0)
            pattan = 1;
        else if (t2 == ASPEED * 13 + (ASPEED / 3))
            pattan = 2;
        else if (t2 == ASPEED * 13 + (ASPEED / 3) + ASPEED2 * 9)
            pattan = 3;
        else if (t2 == ASPEED * 13 + (ASPEED / 3) + ASPEED2 * 9 + ASPEED3 * 11)
            pattan = 4;

        // 弾幕パターン1
        if (pattan == 1)
        {
            //ボスのアシスト作成
            if (count == 0)
            {
                for (i = 0; i < 2; i++)
                {
                    SGP.CreateBullet(x: x, y: y, 
                        speed: (MAPX + 36) * PI / (ASPEED * 4),// スピード 半径MAPX/2の円を200カウントで移動する速度
                        angle: PI / 20 + PI / 2 * (i % 2 == 0 ? 1 : -1),
                        graph_type: 0, color: 2, pi2or1: 1.0,state:-i-1,se_flag:false);
                }
                SGP.PlaySound(0);
            }
            // 内側弾幕
            if (count >= ASPEED && count < ASPEED * 9 + (ASPEED / 3) && (count - 12) % 33 < 24 && count % 4 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    // アシスト弾幕なら
                    if (state == -1 || state == -2)
                    {
                        bx = SGP.Bullet[i].X;
                        by = SGP.Bullet[i].Y;
                        SGP.CreateBullet(x: bx, y: by,
                            speed: 0, 
                            angle: SGP.GetBossAnglePI2(bx, by) + PI / 180 * 24 / (ASPEED * 9 + (ASPEED / 2)) * count, // 角度 (ASPEED*9+(ASPEED/2))カウンけて24度回転させる
                            graph_type: 6, color: (state == -1 ? 0 : 1),
                            pi2or1: 1.0, state: 0, se_flag: false);
                    }
                }
                SGP.PlaySound(0);
            }
            // 外側弾幕
            if (count >= ASPEED && count < ASPEED * 9 + (ASPEED / 2) && count % 4 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    // アシスト弾幕なら
                    if (state == -1 || state == -2)
                    {
                        for (j = 0; j < 2; j++)
                        {
                            bx = SGP.Bullet[i].X;
                            by = SGP.Bullet[i].Y;
                            SGP.CreateBullet(x: bx, y: by, speed: 0.5,
                                angle: SGP.GetBossAnglePI2(bx, by) + PI + PI * 20 / 2 / 180 * (j % 2 == 0 ? 1 : -1),
                                graph_type: 6, color: state == -1 ? 0 : 1, pi2or1: 1.0, state: 1, se_flag: false);
                        }
                    }
                }
            }
            // 計算部
            for (i = 0; i < SGP.Bullet.Count; i++)
            {
                int cnt = SGP.Bullet[i].Cnt;
                int state = SGP.Bullet[i].State;
                double spd = SGP.Bullet[i].Speed;

                switch (state)
                {
                    case 0:
                        if (count <= cnt * 6.6)
                        {
                            SGP.Bullet[i].Speed = 0;
                            SGP.Bullet[i].State = 2;
                            SGP.Bullet[i].Memory = cnt;
                            //memory[i] = cnt;
                        }
                        else
                        {
                            SGP.Bullet[i].Speed += 4.0 / (ASPEED / 2);
                            if (spd >= 4)
                                SGP.Bullet[i].Speed = 4;
                        }
                        break;
                    case 1: // 外側の弾幕が外に出るのが間にあわないので追加
                    case 2:
                        if (SGP.Bullet[i].Memory + ASPEED * 9 + ASPEED / 2 <= count)
                            SGP.Bullet[i].Speed += 3.0 / (ASPEED / 2);
                        if (spd >= 3)
                            SGP.Bullet[i].Speed = 3;
                        break;
                }
            
            }
            if (count == ASPEED * 9 + (ASPEED / 3))
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        SGP.Bullet[i].Life = 0;// 出現しているアシストを全消去
                    }
                }                      
            }
        }

        // 弾幕パターン2
        if (pattan == 2)
        {
            //ボスのアシスト作成
            if (count == 0)
            {
                for (i = 0; i < 2; i++)
                {
                    SGP.CreateBullet(x: x, y: y,
                        speed: (MAPX - 36) * PI / (ASPEED2 * 4), // スピード 半径MAPX/2の円を200カウントで移動する速度
                        angle: PI2 / 5 - PI / 2 * (i % 2 == 0 ? 1 : -1),
                        graph_type: 0, color: 2, pi2or1: 1.0, state: -i-1, se_flag: false);
                }
                SGP.PlaySound(0);
                //se_flag[0] = 1;
            }
            //内側弾幕
            if (count >= ASPEED2 && count < ASPEED2 * 7 && (count - ASPEED2) % 60 < 45 && count % 3 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        bx = SGP.Bullet[i].X;
                        by = SGP.Bullet[i].Y;
                        SGP.CreateBullet(x: bx, y: by,
                            speed: 0,
                            angle: SGP.GetBossAnglePI2(bx, by) + (PI / 180 * 30) - (PI / 180 * 80) / (ASPEED2 * 7 - ASPEED2 / 12) * count,// 角度 (ASPEEDASPEED/2))カウントかけて24度回転させる
                            graph_type: 6, color: state == -1 ? 0 : 1,
                            pi2or1: 1.0, state: 0, se_flag: false);
                    }
                }
                SGP.PlaySound(0);                    
            }
            // 外側弾幕
            if (count >= ASPEED2 && count < ASPEED2 * 7 && count % 3 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        bx = SGP.Bullet[i].X;
                        by = SGP.Bullet[i].Y;
                        for (j = 0; j < 2; j++)
                        {
                            SGP.CreateBullet(x: bx, y: by,
                                speed: 0.5,
                                angle: SGP.GetBossAnglePI2(bx, by) + PI + PI * 20 / 2 / 180 * (j % 2 == 0 ? 1 : -1),
                                graph_type: 6, color: (state == -1 ? 0 : 1),
                                pi2or1: 1.0, state: 1, se_flag: false);
                        }
                    }
                }
            }
            // 計算部
            for (i = 0; i < SGP.Bullet.Count; i++)
            {
                int cnt = SGP.Bullet[i].Cnt;
                int state = SGP.Bullet[i].State;
                double spd = SGP.Bullet[i].Speed;

                switch (state)
                {
                    case 0:
                        if (count <= cnt * 10)
                        {
                            SGP.Bullet[i].Speed = 0;
                            SGP.Bullet[i].State = 2;
                            SGP.Bullet[i].Memory = cnt;
                        }
                        else
                        {
                            SGP.Bullet[i].Speed += 4.0 / (ASPEED2 / 2);
                            if (spd >= 4)
                                SGP.Bullet[i].Speed = 4;
                        }
                        break;
                    case 1:// 外側の弾幕が外に出るのが間にあわないので追加
                    case 2:
                        if (SGP.Bullet[i].Memory + ASPEED2 * 7 <= count)
                            SGP.Bullet[i].Speed += 3.0 / (ASPEED2 / 2);
                        if (spd >= 3)
                            SGP.Bullet[i].Speed = 3;
                        break;
                }                  
            }
            if (count == ASPEED2 * 7)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        SGP.Bullet[i].Life = 0;// 出現しているアシストを全消去
                    }
                }
            }
        }

        // 弾幕パターン3
        if (pattan == 3)
        {
            // ボスのアシスト作成
            if (count == 0)
            {
                for (i = 0; i < 2; i++)
                {
                    SGP.CreateBullet(x: x, y: y,
                        speed: (MAPY + 8) * PI / (ASPEED3 * 4),// スピード 半径MAPX/2の円を200カウントで移動する速度
                        angle: -PI2 / 5 - PI / 2 * (i % 2 == 0 ? 1 : -1),
                        graph_type: 0, color: 2, pi2or1: 1.0, state: -i-1, se_flag: false);
                }
                SGP.PlaySound(0);
            }
            // 内側弾幕
            if (count >= ASPEED3 && count < ASPEED3 * 7 && count % 3 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        for (j = 0; j < 2; j++)
                        {
                            bx = SGP.Bullet[i].X;
                            by = SGP.Bullet[i].Y;
                            SGP.CreateBullet(x: bx, y: by,
                                speed: 0,
                                angle: SGP.GetBossAnglePI2(bx, by) - PI / 180 * (CRANGE_SHIN / 2),// 角度 (ASPEED*9+(ASPEED/2))カウントかけて24度回転させ
                                graph_type: 5, color: (j % 2 == 0 ? 0 : 1),
                                pi2or1: 1.0, state: (state == -1 ? 0 : 1), se_flag: false);
                        }
                    }
                }
                SGP.PlaySound(0);
                //se_flag[0] = 1;
            }
            // 外側弾幕
            if (count >= ASPEED3 && count < ASPEED3 * 7 && count % 3 == 0)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    int state = SGP.Bullet[i].State;
                    if (state == -1 || state == -2)
                    {
                        for (j = 0; j < 2; j++)
                        {
                            bx = SGP.Bullet[i].X;
                            by = SGP.Bullet[i].Y;
                            SGP.CreateBullet(x: bx, y: by,
                                speed: 0.5,
                                angle: SGP.GetBossAnglePI2(bx, by) + PI + PI * 20 / 2 / 180 * (j % 2 == 0 ? 1 : -1),
                                graph_type: 5, color: (state == -1 ? 0 : 1), pi2or1: 1.0, state: 3, se_flag: false);
                        }
                    }
                }
            }
            // 計算部
            for (i = 0; i < SGP.Bullet.Count; i++)
            {
                int cnt = SGP.Bullet[i].Cnt;
                int state = SGP.Bullet[i].State;
                double spd = SGP.Bullet[i].Speed;
                switch (state)
                {
                    case 0:
                        if (count <= cnt * 4.7)
                        {
                            SGP.Bullet[i].Speed = 0;
                            SGP.Bullet[i].State = 2;
                            SGP.Bullet[i].Memory = cnt;
                        }
                        else
                        {
                            SGP.Bullet[i].Speed += 4.0 / (ASPEED3 / 2);
                            if (spd >= 4)
                                SGP.Bullet[i].Speed = 4;
                        }
                        break;
                    case 1:
                        if (count <= cnt * 6)
                        {
                            SGP.Bullet[i].Speed = 0;
                            SGP.Bullet[i].State = 2;
                            SGP.Bullet[i].Memory = cnt;
                        }
                        else
                        {
                            SGP.Bullet[i].Speed += 4.0 / ASPEED3;
                            if (spd >= 4)
                                SGP.Bullet[i].Speed = 4;
                        }
                        break;
                    case 2:
                        if (SGP.Bullet[i].Memory + ASPEED3 * 7 <= count)
                            SGP.Bullet[i].Speed += 3.0 / (ASPEED3 / 2);
                        if (spd >= 3)
                            SGP.Bullet[i].Speed = 3;
                        break;
                }                   
            }
            if (count == ASPEED3 * 7)
            {
                for(i = 0; i < SGP.Bullet.Count; ++i)
                {
                    int state = SGP.Bullet[i].State;
                    if(state == -1 || state == -2)
                    {
                        SGP.Bullet[i].Life = 0;// 出現しているアシストを全消去
                    }
                }
            }
        }

        // 弾幕パターン4
        if (pattan == 4)
        {
            if (count < 300)
            {
                int judge = 0;    // 判断用

                for (i = 0; i < 500; i++)
                {    // 一度に表示したい数

                    double rand_x = DX.GetRand(FIELD_MAX_X);
                    double rand_y = DX.GetRand(FIELD_MAX_Y);
                    


                    for (j = 0; j < SGP.Bullet.Count; j++)
                    {
                        int state = SGP.Bullet[j].State;
                        //if (0 < state)
                        {
                            double x1 = SGP.Bullet[j].X - rand_x;
                            double y1 = SGP.Bullet[j].Y - rand_y;
                            double r = 15;
                            bx = x - rand_x;
                            by = y - rand_y;
                            double br = 50;
                            if (x1 * x1 + y1 * y1 < r * r || bx * bx + by * by < br * br)
                            {
                                judge = 1;
                            }
                        }
                    }

                    if (judge != 1)
                    {
                        SGP.CreateBullet(x: rand_x, y: rand_y, speed: 0, angle: SGP.GetBossAnglePI2(rand_x, rand_y),
                            graph_type:5,color:1,state: 0, pi2or1: 1.0,se_flag:false);
                    }
                   
                }
                SGP.PlaySound(0);
            }
            if (count == 300)
            {
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    SGP.Bullet[i].Speed = (DX.GetRand(500) + 1) / 2000.0;
                }
            }
            if (count >= 300)
            {
                //boss.hp -= boss.hp_max / 500;
            }
            if (count == 800)
            {                   
                SGP.PlaySound(7);
                for (i = 0; i < SGP.Bullet.Count; i++)
                {
                    SGP.Bullet[i].Life = 0;
                }
                return false;
            }
        }

        // ボスのアシスト処理
        for (i = 0; i < SGP.Bullet.Count; i++)
        {
            int state = SGP.Bullet[i].State;
            if (state == -1 || state == -2)
            {
                switch (pattan)
                {
                    case 1:
                        if (count < ASPEED)
                            SGP.Bullet[i].Angle += PI / ASPEED;
                        else
                            SGP.Bullet[i].Angle += PI / 2.0 / ASPEED;    // 4分の1円をASPEEDカウントて90度回転させる
                        break;
                    case 2:
                        if (count < ASPEED2)
                            SGP.Bullet[i].Angle -= PI / ASPEED2;
                        else
                            SGP.Bullet[i].Angle -= PI / 2.0 / ASPEED2;    // 4分の1円をASPEED2カウントて90度逆回転させる
                        break;
                    case 3:
                        if (count < ASPEED3)
                            SGP.Bullet[i].Angle += PI / ASPEED3;
                        else
                            SGP.Bullet[i].Angle += PI / 2.0 / ASPEED3;    // 4分の1円をASPEED2カウントて90度逆回転させる
                        break;
                }
            }
        }
        ++Cnt;
        return true;
    }
}

結論 愛もお金も大事

やはり趣味としてではなく、バーチャルゲームプログラマーとして個人でゲーム制作でお金を稼ぐには、徹底的にマーケティング、リサーチ、マネタイズ、PDCAのサイクルを意識して回すことが絶対必要です。
これについては、Unityで実践できるイメージを持ってます。
しかし、あまりにもDXライブラリを使いこんでしまったため、道具に愛情が湧いてしまいました。もうずっと一緒にいる恋人と変わらない思いです。
すべてをわかっているのに、本当に自分はバカな人間ですよね…

愛しのDXライブラリと最後に共にすること

やはり今の時代DXライブラリでマネタイズをするのはきついです。そこでUnityを使うことになるのですが、最後にやりたいことがあります。

  • 音楽ゲームの講座を作る
  • アクションゲームの講座を作る

以上の2つをやっておこうと思います。絵などの素材がないし音楽も作れないので、完成しても配布できませんが、どうしても自分が今まで培ってきた技術や思い出を誰かに伝えたいからです。