「C#」カテゴリーアーカイブ

【北海道大戦】リソースファイルをパッケージ化

使用しているライブラリ、Altseedには、リソースファイルを一つのファイルにパッケージ化するツールが用意されていて、

これを使えば、大量のファイルでかさばることもないし、勝手にデータを書き換えることもできなくなります。

使用する場合は、こんな感じでエンジンに登録するだけ。

            asd.Engine.File.AddRootPackage("hokkaido.pack");

で、フォントやテクスチャ画像は、ソースを変更する必要は無いのですが、

マップデータを記述しているjsonファイルは標準機能で読み出しているので、

これに関してはちょっと細工が必要になります。

        private const string _filename = "hokkaido.json";
        public static MapData Load()
        {
            string str = string.Empty;
            asd.StreamFile stream = asd.Engine.File.CreateStreamFile(_filename);
            List<byte> buffer = new List<byte>();
            stream.Read(buffer, stream.Size);
            string json = Encoding.UTF8.GetString(buffer.ToArray());
            return JsonConvert.DeserializeObject<MapData>(json);
        }

CreateStreamFileでjsonファイルを取り出し、

readメソッドでデータをバイトのリストで取得。

これをjsonに変換するには、さらにbyte配列に変換して、文字列に変換して

これでようやくjsonからオブジェクトに変換出来ます。

めんどくさい。

でも、これだけで動いたからまだマシな方だと思うけど。

【北海道大戦】札幌強すぎ問題

マップを修正して。札幌を9区に分けてみました。

これで単純計算で札幌の戦力が1/9になったはずですが、

都市間のリンクがぐちゃぐちゃになりまして、それをできる限り修正してみたらかなりいびつな形になっちゃいました。

まあいいや。

今後はリアル北海道地図でやりたいなぁ。

【北海道大戦】バトルのバランス調整

ちょっとコードを書き換えてバトルのバランス調整をしました。

        private void onClickMouseShowActionResult(asd.Vector2DF pos)
        {
            var result = janken(selectedAttack, selectedDeffece);
            if(result == BattleResult.win)
            {
                _deffencePower -= (int)Math.Floor(_attackPower * (Singleton.Random.NextDouble() + 0.1));
                if(_deffencePower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.win);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                    _deffenceParam.Text = "戦闘力:0";
                }
                else
                {
                    _deffenceParam.Text = "戦闘力:" + _deffencePower;
                }
            }
            else if(result == BattleResult.lose)
            {
                
                _attackPower -= (int)Math.Floor(_deffencePower * (Singleton.Random.NextDouble() + 0.1));
                if (_attackPower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.lose);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                    _attackParam.Text = "戦闘力:0";
                }
                else
                {
                    _attackParam.Text = "戦闘力:" + _attackPower;
                }
            }
            _attackResult.Hide();
            _deffenceResult.Hide();
            _status = GameStatus.SelectDeffenceAction;
        }

単純に戦力値で引き算する訳じゃ無くて、

乱数の係数を掛け合わせてみました。

単純計算ではわずかな戦力差でも一撃で決着が付いてしまいましたが、

これによって、僅差であれば、一撃で決着することは無くなります。

あとは札幌強すぎ問題ですね・・・

【北海道大戦】確認ダイアログ追加とバグ修正

https://github.com/takishita2nd/HokkaidoWar

最初のプレイヤーの都市選択で確認ダイアログを出すようにしました。

あとは、最後までプレイできることも確認しました。

ただ、戦力差がありすぎると、バトルがワンパンで決着が付いてしまうのが、ちょっとよろしくないですね。

【北海道大戦】戦闘結果をメインシーンへ反映

たぶん、これが最後の仕上げ。

まずはじゃんけんの結果を判定します。

    class BattleScene : asd.Scene
    {
        private void onClickMouseShowActionResult(asd.Vector2DF pos)
        {
            var result = janken(selectedAttack, selectedDeffece);
            if(result == BattleResult.win)
            {
                _deffencePower -= _attackPower;
                if(_deffencePower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.win);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                }
                _deffenceParam.Text = "戦闘力:" + _deffencePower;
            }
            else if(result == BattleResult.lose)
            {
                _attackPower -= _deffencePower;
                if (_attackPower <= 0)
                {
                    Singleton.GameData.BattleResultUpdate(BattleResult.lose);
                    var scene = new MainScene();
                    asd.Engine.ChangeScene(scene);
                }
                _attackParam.Text = "戦闘力:" + _attackPower;
            }
            _attackResult.Hide();
            _deffenceResult.Hide();
            _status = GameStatus.SelectDeffenceAction;
        }

        private BattleResult janken(BattleIcon.Icon attack, BattleIcon.Icon deffence)
        {
            switch (attack)
            {
                case BattleIcon.Icon.Gu:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.draw;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.win;
                        case BattleIcon.Icon.Par:
                            return BattleResult.lose;
                        default:
                            return BattleResult.draw;
                    }
                case BattleIcon.Icon.Choki:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.lose;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.draw;
                        case BattleIcon.Icon.Par:
                            return BattleResult.win;
                        default:
                            return BattleResult.draw;
                    }
                case BattleIcon.Icon.Par:
                    switch (deffence)
                    {
                        case BattleIcon.Icon.Gu:
                            return BattleResult.win;
                        case BattleIcon.Icon.Choki:
                            return BattleResult.lose;
                        case BattleIcon.Icon.Par:
                            return BattleResult.draw;
                        default:
                            return BattleResult.draw;
                    }
                default:
                    return BattleResult.draw;
            }
        }

今回のじゃんけん判定はベタな方法で実装しました。

上手い方法が思い浮かばなかった。

janken()でじゃんけんの結果を判定し、その結果を反映、勝負が決まったら、その情報をGameData.BattleResultUpdate()に反映し、MainSceneに遷移します。

    class GameData
    {
        public void BattleResultUpdate(BattleResult result)
        {
            if (gameStatus == GameStatus.ActionEnemy)
            {
                Battle.EnemyTurnEnd(result);
            }
            else if (gameStatus == GameStatus.ActionPlayer)
            {
                Battle.MyTurnEnd(result);
                gameStatus = GameStatus.ActionEnemy;
            }
        }

状態が敵のターンか、自分のターンかで分岐させます。

    class Battle
    {
        public void EnemyTurnEnd(BattleResult result)
        {
            if(result == BattleResult.win)
            {
                lastAttack.CombinationCity(lastDeffece);
                lastDeffece.Lose();
                aliveCities.Remove(lastDeffece);
                lastDeffece = null;
            }
        }

        public void MyTurnEnd(BattleResult result)
        {
            if (result == BattleResult.win)
            {
                lastAttack.CombinationCity(lastDeffece);
                lastDeffece.Lose();
                aliveCities.Remove(lastDeffece);
                lastDeffece.ClearPaint();
                lastDeffece = null;
            }

            lastAttack.ClearPaint();
            lastAttack = null;

            cityCnt++;
            if (cityCnt >= _cities.Count)
            {
                _cities = cityRandomReplace(aliveCities);
                aliveCities = copyCity(_cities);
                cityCnt = 0;
                turn++;
            }
        }

これでデータの反映もできたはず。

あとはもう少しブラッシュアップさせて行きます。

【北海道大戦】じゃんけんバトルを実装

実際にじゃんけんバトルを実装していきます。

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

            switch (_status)
            {
                case GameStatus.SelectDeffenceAction:
                    cycleProcessSelectDeffenceAction(pos);
                    break;
                case GameStatus.SelectAttackAction:
                    cycleProcessSelectAttackAction(pos);
                    break;
                case GameStatus.ShowActionResult:
                    cycleProcessShowActionResult(pos);
                    break;
            }
            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                switch (_status)
                {
                    case GameStatus.SelectDeffenceAction:
                        onClickMouseSelectDeffenceAction(pos);
                        break;
                    case GameStatus.SelectAttackAction:
                        onClickMouseSelectAttackAction(pos);
                        break;
                    case GameStatus.ShowActionResult:
                        onClickMouseShowActionResult(pos);
                        break;
                }
            }
        }

        private void cycleProcessSelectDeffenceAction(asd.Vector2DF pos)
        {
            if(_player == Player.Deffence)
            {
                _image_gu_deffence.Show();
                _image_choki_deffence.Show();
                _image_par_deffence.Show();
                _image_gu_deffence.OnMouse(pos);
                _image_choki_deffence.OnMouse(pos);
                _image_par_deffence.OnMouse(pos);
            }
            else
            {
                var r = Singleton.Random;
                switch(r.Next(0, 3))
                {
                    case 0:
                        selectedDeffece = BattleIcon.Icon.Gu;
                        break;
                    case 1:
                        selectedDeffece = BattleIcon.Icon.Choki;
                        break;
                    case 2:
                        selectedDeffece = BattleIcon.Icon.Par;
                        break;
                }
                _status = GameStatus.SelectAttackAction;
            }
        }

        private void cycleProcessSelectAttackAction(asd.Vector2DF pos)
        {
            if (_player == Player.Attack)
            {
                _image_gu_attack.Show();
                _image_choki_attack.Show();
                _image_par_attack.Show();
                _image_gu_attack.OnMouse(pos);
                _image_choki_attack.OnMouse(pos);
                _image_par_attack.OnMouse(pos);
            }
            else
            {
                var r = Singleton.Random;
                switch (r.Next(0, 3))
                {
                    case 0:
                        selectedAttack = BattleIcon.Icon.Gu;
                        break;
                    case 1:
                        selectedAttack = BattleIcon.Icon.Choki;
                        break;
                    case 2:
                        selectedAttack = BattleIcon.Icon.Par;
                        break;
                }
                _status = GameStatus.ShowActionResult;
            }
        }

        private void cycleProcessShowActionResult(asd.Vector2DF pos)
        {
            _attackResult.SetIcon(selectedAttack);
            _attackResult.Show();
            _deffenceResult.SetIcon(selectedDeffece);
            _deffenceResult.Show();
        }

        private void onClickMouseSelectDeffenceAction(asd.Vector2DF pos)
        {
            if (_player == Player.Deffence)
            {
                if (_image_gu_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Gu;
                }
                else if (_image_choki_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Choki;
                }
                else if (_image_par_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Par;
                }
                _image_gu_deffence.Hide();
                _image_choki_deffence.Hide();
                _image_par_deffence.Hide();
                _status = GameStatus.SelectAttackAction;
            }
        }

        private void onClickMouseSelectAttackAction(asd.Vector2DF pos)
        {
            if (_player == Player.Attack)
            {
                if (_image_gu_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Gu;
                }
                else if (_image_choki_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Choki;
                }
                else if (_image_par_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Par;
                }
                _image_gu_attack.Hide();
                _image_choki_attack.Hide();
                _image_par_attack.Hide();
                _status = GameStatus.ShowActionResult;
            }
        }

        private void onClickMouseShowActionResult(asd.Vector2DF pos)
        {
            _attackResult.Hide();
            _deffenceResult.Hide();
            _status = GameStatus.SelectDeffenceAction;
        }

シーンの状態は、

  1. 防御側の選択
  2. 攻撃側の選択
  3. 結果の表示

の順で遷移していきます。

防御側選択状態では、プレイヤーが防御側ならば、じゃんけんアイコンを選択します。プレイヤーが攻撃側ならば、ランダムで相手が何を選択したかを決定します。

攻撃側選択状態は、防御側選択の逆で、プレイヤーが攻撃側ならば、じゃんけんアイコンを選択、プレイヤーが防御側ならば、ランダムで相手が何を選択したかを決定します。

そして最後に攻撃側と防御側が選択したアイコンを表示し、じゃんけんの結果を表示します。

まぁ、ここまでは表示をどうするかの処理が中心になります。

次回はじゃんけんの結果を処理する実装を行います。

【北海道大戦】じゃんけんアイコンをクラス化

じゃんけんアイコンを使いやすい様にクラス化させます。

    class BattleIcon
    {
        public enum Icon
        {
            Gu,
            Choki,
            Par
        }

        public enum Position
        {
            Attack,
            Deffence
        }

        private asd.TextureObject2D _image = null;
        private Icon _icon;
        private Position _position;
        private int _x;
        private int _y;
        private int width = 80;
        private int height = 80;

        public BattleIcon(Icon icon, Position pos)
        {
            _icon = icon;
            _position = pos;
            _image = new asd.TextureObject2D();
            if (pos == Position.Attack)
            {
                _y = 500;
                switch (icon)
                {
                    case Icon.Gu:
                        _x = 300;
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _x = 450;
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _x = 600;
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
                _image.Position = new asd.Vector2DF(_x, _y);
            }
            else
            {
                _y = 250;
                switch (icon)
                {
                    case Icon.Gu:
                        _x = 300;
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _x = 450;
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _x = 600;
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
                _image.Position = new asd.Vector2DF(_x, _y);
            }
        }

        public void AddLayer(asd.Layer2D layer)
        {
            layer.AddObject(_image);
        }

        public void Show()
        {
            switch (_icon)
            {
                case Icon.Gu:
                    _image.Texture = Singleton.ImageGu;
                    break;
                case Icon.Choki:
                    _image.Texture = Singleton.ImageChoki;
                    break;
                case Icon.Par:
                    _image.Texture = Singleton.ImagePar;
                    break;
            }
        }

        public void Hide()
        {
            _image.Texture = null;
        }

        public void OnMouse(asd.Vector2DF pos)
        {
            if (IsOnMouse(pos))
            {
                switch (_icon)
                {
                    case Icon.Gu:
                        _image.Texture = Singleton.ImageGu2;
                        break;
                    case Icon.Choki:
                        _image.Texture = Singleton.ImageChoki2;
                        break;
                    case Icon.Par:
                        _image.Texture = Singleton.ImagePar2;
                        break;
                }
            }
            else
            {
                switch (_icon)
                {
                    case Icon.Gu:
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
            }
        }

        public bool IsOnMouse(asd.Vector2DF pos)
        {
            if (pos.X > _x && pos.X < width + _x
                && pos.Y > _y && pos.Y < height + _y)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

とりあえず、使いそうなメソッドをいくつか実装しました。

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

            switch (_status)
            {
                case GameStatus.SelectDeffenceAction:
                    cycleProcessSelectDeffenceAction(pos);
                    break;
                case GameStatus.SelectAttackAction:
                    break;
                case GameStatus.ShowActionResult:
                    break;
            }
            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                switch (_status)
                {
                    case GameStatus.SelectDeffenceAction:
                        break;
                    case GameStatus.SelectAttackAction:
                        break;
                    case GameStatus.ShowActionResult:
                        break;
                }
            }
        }

        private void cycleProcessSelectDeffenceAction(asd.Vector2DF pos)
        {
            _image_gu_deffence.OnMouse(pos);
            _image_choki_deffence.OnMouse(pos);
            _image_par_deffence.OnMouse(pos);
        }

とりあえず、こんなところか。

マウスカーソルを合わせることで色を変えるようにしました。

うん、今日もがんばった。

【北海道大戦】バトルシーンへパラメータを渡す。

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

バトルシーンを作成するために、バトルシーンで使用するパラメータを設定します。

シーンはその都度作成しなくちゃいけないので、コンストラクタで渡すのが一番良いでしょう。

        public enum Player
        {
            Attack,
            Deffence
        }

        public BattleScene(City attack, City deffence, Player player)
        {
            _attack = attack;
            _attackPower = attack.Population;
            _deffence = deffence;
            _deffencePower = deffence.Population;
            _player = player;
            _status = GameStatus.SelectDeffenceAction;
        }

playerというパラメータは、プレイヤーが攻撃側か防御側かを示すパラメータです。

あれこれ悩んだ結果、これが一番簡単だろうと。

これを画面に表示させます。

            var attackCityLabel = new asd.TextObject2D();
            attackCityLabel.Font = Singleton.LargeFont;
            attackCityLabel.Text = _attack.Name;
            attackCityLabel.Position = new asd.Vector2DF(450, 150);
            layer.AddObject(attackCityLabel);

            var deffenceCityLabel = new asd.TextObject2D();
            deffenceCityLabel.Font = Singleton.LargeFont;
            deffenceCityLabel.Text = _deffence.Name;
            deffenceCityLabel.Position = new asd.Vector2DF(450, 650);
            layer.AddObject(deffenceCityLabel);

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

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

さらに、

このフローからバトルシーンの状態を抽出。

        enum GameStatus {
            SelectDeffenceAction,
            SelectAttackAction,
            ShowActionResult
        }

これを実装。

        protected override void OnUpdated()
        {
            switch (_status)
            {
                case GameStatus.SelectDeffenceAction:
                    break;
                case GameStatus.SelectAttackAction:
                    break;
                case GameStatus.ShowActionResult:
                    break;
            }
            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                switch (_status)
                {
                    case GameStatus.SelectDeffenceAction:
                        break;
                    case GameStatus.SelectAttackAction:
                        break;
                    case GameStatus.ShowActionResult:
                        break;
                }
            }
        }

あとは、各状態について実装していけば良いのですな。

とりあえず今回はここまでにしておこう。

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

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

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

詳細は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のプロパティなので、やっぱりシーンを作り直さなくちゃいけないんだろうなぁ、と思います。

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

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

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

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