「#北海道大戦」タグアーカイブ

【北海道大戦】メインシーンの作り直し

前回までの状況はこちら。

はい、大幅に書き換えました。

詳細はgitHubのソースを見て欲しいのですが、

https://github.com/takishita2nd/HokkaidoWar

まず、メインシーンで使用していたデータは全てGameDataクラスに移動しました。

    class GameData
    {
        public enum GameStatus
        {
            None,
            SelectCity,
            ActionEnemy,
            ActionPlayer,
            ShowResult,
            GameEnd,
            GameOver
        }

        public GameStatus gameStatus = GameStatus.None;
        public List<City> Cities = null;
        public List<City> AliveCities = null;
        public Battle Battle = null;
        public Player Player = null;

このクラスはシングルトンで管理します。

    class Singleton
    {
        private static GameData _gameData = null;

        public static GameData GetGameData()
        {
            if (_gameData == null)
            {
                _gameData = new GameData();
            }
            return _gameData;
        }

このシングルトンクラス、ゲッターのでいいんじゃないかと思い始めた。

やっていることは同じですが。

メソッドでやるか、プロパティでやるかの違いです。

気が向いたら直します。

シーン遷移は全てシーンクラスのインスタンスを作り直します。

            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                var scene = new MainScene();
                asd.Engine.ChangeScene(scene);
            }

そのときに、ゲームデータを元に画面を作り直す、という感じです。

これで、バトルシーンからメインシーンに切り替わっても、ゲームが止まることは無くなりました。

これでやっと次に進める・・・。

【北海道大戦】バトルシーンを状態遷移に組み込んでみる。

前回作ったバトルシーンを、既存の状態遷移に組み込んでみました。

変更前はこんな感じでした。

結構複雑になってきましたね。

でも修正はそんなに難しくない・・・と思いきや。

どうやらシーンは切り替えるごとに作り直さなくちゃいけないらしくて

バトルシーンからメインシーンに切り替わるときに、OnUpdated()処理が走らないという。

おそらくシーンのプロパティにIsAliveというのがあって、

これはReadOnlyのプロパティなので、やっぱりシーンを作り直さなくちゃいけないんだろうなぁ、と思います。

ということは、ゲームデータはシーンとは別に保持しなくちゃいけなくて、

大幅に設計変更が発生しそう、

と分かった時に心が折れました。

まぁ、完成はさせたいので、時間があるときに修正しておきます。

【北海道大戦】バトルシーンの画面を作成する

前回までの状況はこちら。

最新ソースはこちら(gitHub)。

https://github.com/takishita2nd/HokkaidoWar

バトルシーンの画面を作成していきます。

とは言っても部品を配置しているだけですが。

    class BattleScene : asd.Scene
    {
        private asd.TextObject2D _label = null;
        private asd.TextObject2D _attackCity = null;
        private asd.TextObject2D _deffenceCity = null;
        private asd.TextureObject2D _image_gu_attack = null;
        private asd.TextureObject2D _image_choki_attack = null;
        private asd.TextureObject2D _image_par_attack = null;
        private asd.TextureObject2D _image_gu_deffence = null;
        private asd.TextureObject2D _image_choki_deffence = null;
        private asd.TextureObject2D _image_par_deffence = null;
        private asd.TextObject2D _attackParam = null;
        private asd.TextObject2D _deffenceParam = null;

        public BattleScene()
        {

        }

        protected override void OnRegistered()
        {
            var layer = new asd.Layer2D();
            AddLayer(layer);

            // 下地
            var background = new asd.GeometryObject2D();
            layer.AddObject(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1800, 1000);
            background.Shape = bgRect;

            _label = new asd.TextObject2D();
            _label.Font = Singleton.GetLargeFont();
            _label.Text = "VS";
            _label.Position = new asd.Vector2DF(470, 400);
            layer.AddObject(_label);

            _attackCity = new asd.TextObject2D();
            _attackCity.Font = Singleton.GetLargeFont();
            _attackCity.Text = "札幌";
            _attackCity.Position = new asd.Vector2DF(450, 150);
            layer.AddObject(_attackCity);

            _deffenceCity = new asd.TextObject2D();
            _deffenceCity.Font = Singleton.GetLargeFont();
            _deffenceCity.Text = "小樽";
            _deffenceCity.Position = new asd.Vector2DF(450, 650);
            layer.AddObject(_deffenceCity);

            _attackParam = new asd.TextObject2D();
            _attackParam.Font = Singleton.GetLargeFont();
            _attackParam.Text = "戦闘力:10000";
            _attackParam.Position = new asd.Vector2DF(700, 650);
            layer.AddObject(_attackParam);

            _deffenceParam = new asd.TextObject2D();
            _deffenceParam.Font = Singleton.GetLargeFont();
            _deffenceParam.Text = "戦闘力:8000";
            _deffenceParam.Position = new asd.Vector2DF(700, 150);
            layer.AddObject(_deffenceParam);

            _image_gu_attack = new asd.TextureObject2D();
            _image_gu_attack.Texture = Singleton.GetImageGu();
            _image_gu_attack.Position = new asd.Vector2DF(300, 500);
            layer.AddObject(_image_gu_attack);

            _image_choki_attack = new asd.TextureObject2D();
            _image_choki_attack.Texture = Singleton.GetImageChoki();
            _image_choki_attack.Position = new asd.Vector2DF(450, 500);
            layer.AddObject(_image_choki_attack);

            _image_par_attack = new asd.TextureObject2D();
            _image_par_attack.Texture = Singleton.GetImagePar();
            _image_par_attack.Position = new asd.Vector2DF(600, 500);
            layer.AddObject(_image_par_attack);

            _image_gu_deffence = new asd.TextureObject2D();
            _image_gu_deffence.Texture = Singleton.GetImageGu();
            _image_gu_deffence.Position = new asd.Vector2DF(300, 250);
            layer.AddObject(_image_gu_deffence);

            _image_choki_deffence = new asd.TextureObject2D();
            _image_choki_deffence.Texture = Singleton.GetImageChoki();
            _image_choki_deffence.Position = new asd.Vector2DF(450, 250);
            layer.AddObject(_image_choki_deffence);

            _image_par_deffence = new asd.TextureObject2D();
            _image_par_deffence.Texture = Singleton.GetImagePar();
            _image_par_deffence.Position = new asd.Vector2DF(600, 250);
            layer.AddObject(_image_par_deffence);
        }

        protected override void OnUpdated()
        {

        }
    }

シンプルだけどまぁいいでしょう。

グー・チョキ・パーは絵文字が使えないっぽいので、絵文字を拡大してスクリーンショットで画像を作成しています。

次回は実際にシーンの切り替えをやってみます。

【北海道大戦】シーンを追加する

最新ソースはこちら(gitHub)。

https://github.com/takishita2nd/HokkaidoWar

バトルシーンを追加するに当たりまして、

今まではエンジンに直接オブジェクトを配置していましたが、

シーンを使用するとなると、このやり方では上手くいけないっぽい。

なので、メインシーンを追加して、このシーンのレイヤーにオブジェクトを配置する必要があります。

なので、今回はその修正を実行。

    class HokkaidoWar
    {

        public HokkaidoWar()
        {
        }

        public void Run()
        {
            asd.Engine.Initialize("北海道大戦", 1200, 1000, new asd.EngineOption());

            // シーンの登録
            var scene = new MainScene();
            asd.Engine.ChangeScene(scene);

            while (asd.Engine.DoEvents())
            {
                asd.Engine.Update();
            }
            asd.Engine.Terminate();
        }

    }

いままでエンジンに追加していたオブジェクトは全て無くなり、シーンを作成して、そのシーンに移行、という処理に変わります。

    class MainScene : asd.Scene
    {
中略
        protected override void OnRegistered()
        {
            var layer = Singleton.GetMainSceneLayer();
            AddLayer(layer);

            // 下地
            var background = new asd.GeometryObject2D();
            layer.AddObject(background);
            var bgRect = new asd.RectangleShape();
            bgRect.DrawingArea = new asd.RectF(0, 0, 1800, 1000);
            background.Shape = bgRect;

            cities = new List<City>();
            var r = new Random();
            foreach (var map in mapData.list)
            {
                City city = new City(map.name, map.point, map.population);
                cities.Add(city);
            }

            _battle = new Battle(cities);
            aliveCities = _battle.GetAliveCityList();
        }

        protected override void OnUpdated()
        {
            asd.Vector2DF pos = asd.Engine.Mouse.Position;

            switch (gameStatus)
            {
                case GameStatus.SelectCity:
                    cycleProcessSelectCity(pos);
                    break;
                case GameStatus.ActionEnemy:
                    cycleProcessActionEnemy(pos);
                    break;
                case GameStatus.ActionPlayer:
                    cycleProcessActionPlayer(pos);
                    break;
                case GameStatus.ShowResult:
                    break;
                case GameStatus.GameEnd:
                    cycleProcessGameEnd();
                    break;
                case GameStatus.GameOver:
                    cycleProcessGameOver(pos);
                    break;
            }

            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                switch (gameStatus)
                {
                    case GameStatus.SelectCity:
                        onClickMouseSelectCity(pos);
                        break;
                    case GameStatus.ActionEnemy:
                        break;
                    case GameStatus.ActionPlayer:
                        onClickMouseActionPlayer(pos);
                        break;
                    case GameStatus.ShowResult:
                        onClickMouseShowResult();
                        break;
                    case GameStatus.GameEnd:
                        break;
                    case GameStatus.GameOver:
                        break;
                }
            }
        }

後略

OnRegistered()はシーン登録時に実行されます。

オブジェクトの配置はここで行います。

レイヤーはシングルトンで取り出すようにしました。(いろんなところでオブジェクトの配置やっているので)

OnUpdated()には、while (asd.Engine.DoEvents()){ }内で実行していた処理を行います。

これは、asd.Engine.Update();実行時に実行されるイメージです。

細かい所は結構修正しているのですが、概要はこんな感じです。

とりあえず、シーンを使用して、今までと同じ動きが出来ることを確認しました。

次はバトルシーンの追加をしていきましょうか。

【北海道大戦】今後の実装について

まぁ、いまのままでもそこそこ楽しめるのですが、

ゲーム性を高めるために、いろいろ実装していこうかと思います。

まずは、今のバトルは完全乱数で発生させた値でのバトルなので、

ゲーム性を高めるために、じゃんけんバトルのようなものにしようかと思います。

そうなると必要なのがAltseedのシーン切り替え機能ですかね。

このシーン切り替え機能を使用するのに、どれだけ回収が必要なのかも考えないと行けません。

その検証を次回やりましょう。

あと、今は防御側勝利時、特に何もメリットがないので、防御側勝利時に1ターン戦力ボーナス・ペナルティを付与しようかなと思っています。

たぶん、そうすることでゲーム性が向上すると思うんですよね。

よし、がんばります。

あ、あと、Altseed2というのがリリースされたみたいですね。

https://altseed.github.io/index.html

【北海道大戦】ターンを飛ばされる都市がある問題を修正する。

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/HokkaidoWar

さて、現在のソースでは、ターンが飛ばされる都市がある事が分かりました。

https://github.com/takishita2nd/HokkaidoWar/blob/03569e0b8728860e73d11e38eeade24a0aafef9c/HokkaidoWar/Battle.cs#L15

原因はこの_citiesを一つで存在する都市と行動する都市を管理していたためです。

https://github.com/takishita2nd/HokkaidoWar/blob/03569e0b8728860e73d11e38eeade24a0aafef9c/HokkaidoWar/Battle.cs#L78

ここで行動済みの都市を削除すると(図の真ん中)、

図の右のように行動順が二つズレてしまうんですよね。

なので、次に行動するはずだった都市が飛ばされてしまいます。

なので、存在する都市と行動する都市を分けて管理する必要があります。

        List<City> cities = null;
        List<City> aliveCities = null;

必要になるのは、List<City>をコピーする処理。

このとき、Listの部分だけを複製して、中の都市オブジェクトは共有で管理します。

        private List<City> copyCity(List<City> cities)
        {
            List<City> ret = new List<City>();
            foreach(var c in cities)
            {
                ret.Add(c);
            }
            return ret;
        }

こんな感じで修正してみました。

これで、順番が飛ばされることはなくなるはず。

【北海道大戦】戦闘結果表示、ゲーム終了動処理を実装する。

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/HokkaidoWar

以前作成したこれ。

status=4、5の処理を作成していきます。

とは言っても、特に難しいことはしていません。

なので、一気に作成します。

ついでにゲームオーバーの処理も作成します。

    class HokkaidoWar
    {
        enum GameStatus
        {
            SelectCity,
            ActionEnemy,
            ActionPlayer,
            ShowResult,
            GameEnd,
            GameOver
        }

        public void Run()
        {
中略
            while (asd.Engine.DoEvents())
            {
                asd.Vector2DF pos = asd.Engine.Mouse.Position;

                switch (gameStatus)
                {
                    case GameStatus.SelectCity:
                        cycleProcessSelectCity(pos);
                        break;
                    case GameStatus.ActionEnemy:
                        cycleProcessActionEnemy(pos);
                        break;
                    case GameStatus.ActionPlayer:
                        cycleProcessActionPlayer(pos);
                        break;
                    case GameStatus.ShowResult:
                        break;
                    case GameStatus.GameEnd:
                        cycleProcessGameEnd();
                        break;
                    case GameStatus.GameOver:
                        cycleProcessGameOver(pos);
                        break;
                }

                if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
                {
                    switch (gameStatus)
                    {
                        case GameStatus.SelectCity:
                            onClickMouseSelectCity(pos);
                            break;
                        case GameStatus.ActionEnemy:
                            break;
                        case GameStatus.ActionPlayer:
                            onClickMouseActionPlayer(pos);
                            break;
                        case GameStatus.ShowResult:
                            onClickMouseShowResult();
                            break;
                        case GameStatus.GameEnd:
                            break;
                        case GameStatus.GameOver:
                            break;
                    }
                }
                asd.Engine.Update();
            }
        private void cycleProcessGameEnd()
        {
            var gameinfo = Singleton.GetGameProcessInfomation();
            gameinfo.ShowText(_player.City.GetPosition(), string.Empty);
            var info = Singleton.GetInfomationWindow();
            info.ShowText(cities[0].GetPosition(), "ゲームが終了しました\r\n");
            info.ShowText(cities[0].GetPosition(), cities[0].Name + "の勝利です\r\n");
        }

        private void cycleProcessGameOver(asd.Vector2DF pos)
        {
            var gameinfo = Singleton.GetGameProcessInfomation();
            gameinfo.ShowText(_player.City.GetPosition(), string.Empty);
            var info = Singleton.GetInfomationWindow();
            info.ShowText(pos, "敗北しました\r\n");
        }

        private void onClickMouseShowResult()
        {
            _battle.MyTurnEnd();
            cities = _battle.GetCityList();
            if(cities.Count <= 1)
            {
                gameStatus = GameStatus.GameEnd;
            }
            else
            {
                gameStatus = GameStatus.ActionEnemy;
            }
        }
    class Battle
    {
        public void MyTurnEnd()
        {
            if (lastDeffece != null)
            {
                lastDeffece.ClearPaint();
                lastDeffece = null;
            }
            if (lastAttack != null)
            {
                lastAttack.ClearPaint();
                lastAttack = null;
            }
            cityCnt++;
            if (cityCnt >= _cities.Count)
            {
                _cities = cityRandomReplace(_cities);
                cityCnt = 0;
                turn++;
            }
        }

その他、微調整も加えています。

これで一応一通り完成しました。

次は、ちょっとした不具合とか、微調整を行って行きます。

【北海道大戦】プレイヤーの行動処理を実装する。

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/HokkaidoWar

以前作成したこれ。

これの「自分の行動」の部分を作成していきます。

            while (asd.Engine.DoEvents())
            {
                asd.Vector2DF pos = asd.Engine.Mouse.Position;

                switch (gameStatus)
                {
                    case GameStatus.SelectCity:
                        cycleProcessSelectCity(pos);
                        break;
                    case GameStatus.ActionEnemy:
                        cycleProcessActionEnemy(pos);
                        break;
                    case GameStatus.ActionPlayer:
                        cycleProcessActionPlayer(pos);
                        break;
                }

                if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
                {
                    switch (gameStatus)
                    {
                        case GameStatus.SelectCity:
                            onClickMouseSelectCity(pos);
                            break;
                        case GameStatus.ActionEnemy:
                            break;
                        case GameStatus.ActionPlayer:
                            onClickMouseActionPlayer(pos);
                            break;
                    }
                }
                asd.Engine.Update();
            }

分かりやすいようにサブルーチン化した。

        private void cycleProcessActionPlayer(asd.Vector2DF pos)
        {
            _battle.MyTurn(_player.City);
            _player.City.PaintAttackColor();
            var info = Singleton.GetInfomationWindow();
            info.ShowText(pos, "都市を選択してください\r\n");
            var cities = _player.City.GetLinkedCities();
            foreach(var city in cities)
            {
                if (city.IsOnMouse(pos))
                {
                    city.OnMouse(pos);
                    city.PaintDeffenceColor();
                }
                else
                {
                    city.ClearPaint();
                }
            }
        }
        public void MyTurn(City player)
        {
            if (lastDeffece != null)
            {
                lastDeffece.ClearPaint();
            }
            if (lastAttack != null)
            {
                lastAttack.ClearPaint();
            }
            var info = Singleton.GetGameProcessInfomation();
            info.ShowText(player.GetPosition(), string.Format("{0} turn {1} / {2} {3}", turn, cityCnt + 1, _cities.Count, player.Name));
        }

周期処理です。

まず、自分の都市を赤くします。

攻撃対象を選択するのですが、自分の都市の上にマウスカーソルがある場合のみ、その都市を青く表示します。

        private City _target = null;
        private void onClickMouseActionPlayer(asd.Vector2DF pos)
        {
            var info = Singleton.GetInfomationWindow();
            info.ShowText(pos, String.Empty);
            var cities = _player.City.GetLinkedCities();
            foreach (var city in cities)
            {
                if (city.IsOnMouse(pos))
                {
                    _target = city;
                    _battle.MyTrunAttack(_player.City, _target);
                    gameStatus = GameStatus.ShowResult;
                }
            }
        }
        public void MyTrunAttack(City player, City target)
        {
            var info = Singleton.GetGameProcessInfomation();
            var r = Singleton.GetRandom();
            lastAttack = player;
            lastDeffece = target;
            double attack = lastAttack.Population * (double)(r.Next(minRate, maxRate) / 10.0);
            double deffence = lastDeffece.Population * (double)(r.Next(minRate, maxRate) / 10.0);
            if (attack > deffence)
            {
                info.ShowText(lastAttack.GetPosition(), string.Format("{0} turn {1} / {2} {3}\r\ntarget {4} \r\n{5} vs {6}\r\nwin",
                    turn, cityCnt + 1, _cities.Count, lastAttack.Name, lastDeffece.Name, (int)attack, (int)deffence));
                lastAttack.CombinationCity(lastDeffece);
                _cities.Remove(lastDeffece);
                lastDeffece = null;
            }
            else
            {
                info.ShowText(lastAttack.GetPosition(), string.Format("{0} turn {1} / {2} {3}\r\ntarget {4} \r\n{5} vs {6}\r\nlose",
                    turn, cityCnt + 1, _cities.Count, lastAttack.Name, lastDeffece.Name, (int)attack, (int)deffence));
            }
        }

マウスクリック時の処理です。

マウスカーソルにある都市に対して攻撃処理を行います。

攻撃対象は記憶しておきます。

【北海道大戦】敵の行動処理を実装する。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/HokkaidoWar

以前作成したこれ。

これの「敵の行動」の部分を作成していきます。

敵を行動させるのであれば、すでに作成済みである、

    class Battle
    {
        public void NextTurn()

を実行すればいいでしょう。

では、次の状態遷移の条件である、「自分の都市の順番が来た場合」を考えます。

それを行うには、自分の都市と、次に実行する都市をBattleクラスから取得する処理があればいいでしょう。

    class Battle
    {
        public City GetActionCity()
        {
            return _cities[cityCnt];
        }
    class Player
    {
        private City _city;

        public City City { get { return _city; } }

        public Player(City city)
        {
            _city = city;
        }
    }
    class HokkaidoWar
    {
        public void Run()
        {
// 中略
            while (asd.Engine.DoEvents())
            {
                asd.Vector2DF pos = asd.Engine.Mouse.Position;

                switch (gameStatus)
                {
// 中略
                    case GameStatus.ActionEnemy:
                        if (_player.City.Equals(_battle.GetActionCity()))
                        {
                            gameStatus = GameStatus.ActionPlayer;
                        }
                        else
                        {
                            Thread.Sleep(100);
                            _battle.NextTurn();
                        }
                        break;
                }

                if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
                {
// 中略
                    switch (gameStatus)
                    {
                        case GameStatus.ActionEnemy:
                            break;
                    }
                }
                asd.Engine.Update();

マウスクリック処理は特にやることがないので、何もやる必要はありません。

【北海道大戦】今後の実装にあたって。【設計のコツ】

たぶん、どんな感じで動かすか、と言うのをきちんと整理しておかないとごちゃごちゃしてくると思うので、

こんな感じのものをサクッと作成しました。

まぁ、フローチャートですわな。

ゲームの状態をステータス(status)で定義し、その状態によってループ処理とか、マウスクリック処理を変えていくように実装させて行く必要があると思います。

ゲーム開始時はstatus=1の状態で始まり、プレイヤーが担当する自治体を選択します。

選択したらゲームスタート。

自分の番が来るまで敵が行動するのですが、その状態がstatus=2です。

で、自分の番が来たらstatus=3に移行。

攻撃対象となる自治体を選択(クリック)します。

その後status=4に移行し、戦闘結果が表示されます。

その後クリックで再びstatus=2に移行し、以後、自治体が残り一つになるまでループします。

最終的に、自治体が残り一つになればstatus=5に遷移してゲーム終了。

とまぁ、サクッと説明しましたが、こういった整理が出来るかどうかで設計能力が問われるわけで。

「このステータスの時」「このイベントが起きると」「このアクションをする」

こんな感じで、図や表なんかに書き出してみると、きれいに整理できたりします。

案外こういうのが解決の近道だったりするので、困ったときは一度立ち止まって整理することをオススメします。