Git Product home page Git Product logo

wpf_successor_001_to_vahren's Introduction

Name ローガントゥーガ

ローグ要素(追加予定)のあるヴァーレントゥーガの後継(自称)です。

Image

image image image

Features

  • ゲームエンジン
  • ARCHITECTURE.mdに主幹クラスについて記載(UMLで書けという話ですがポンチ絵で失礼!
  • ARCHITECTUREUNIT.mdに都市駐屯ユニットについて記載

Requirement

  • WindowsAPICodePack-Shell 1.1.1

Installation

無し

Usage

VisualStudioで実行して下さい

Note

無し

Author

要望まとめ

ファイル「001_Warehouse」直下の「リクエスト.txt」にまとめました。

License

"ローガン" is under MIT license.

ご協力

  • Yutaka-Sawada氏(技術的問題)
  • 数学を愛する会
  • Dミス研究会
  • 陽輝満月氏
  • コントリビューター各位

感謝申し上げます。ありがとうございます。

wpf_successor_001_to_vahren's People

Contributors

carta-vt avatar fluffysheepinx avatar yutaka-sawada avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

wpf_successor_001_to_vahren's Issues

既存の Page を UserControl に変更する方法

ナビゲーション機能を使ってないので、
Page005_StrategyMenu を Page から UserControl に変えてみました。

まず Page005_StrategyMenu.xaml ファイルの 1行目と 80行目の
<Page ~ </Page> という部分を
<UserControl ~ </UserControl> に書き変えます。
あと、独立した子ウインドウではなくなるので

Title="Page005_StrategyMenu"
Background="Transparent"

という行を消します。

次に Page005_StrategyMenu.xaml.cs ファイルの 21行目の

public partial class Page005_StrategyMenu : Page

という行の最後を書き変えて

public partial class Page005_StrategyMenu : UserControl

にします。

これで、Visual Studio から UserControl として認識されるようになり、
xaml デザイナーでも正しく表示されます。

最後に MainWindow.xaml.cs ファイルの SetWindowStrategyMenu 関数において
Frame に Page を追加してた部分

frame.Navigate(this.ClassGameStatus.WindowStrategyMenu);
frame.Margin = new Thickness()
{
    Left = this.canvasUIRightBottom.Width - widthCanvas,
    Top = this.canvasUIRightBottom.Height - HeightCanvas
};
frame.Name = StringName.canvasWindowStrategy;
this.canvasUIRightBottom.Children.Add(frame);

ここを frame を使わずに、直接 UserControl を追加します。

this.ClassGameStatus.WindowStrategyMenu.Margin = new Thickness()
{
    Left = this.canvasUIRightBottom.Width - widthCanvas,
    Top = this.canvasUIRightBottom.Height - HeightCanvas
};
this.canvasUIRightBottom.Children.Add(this.ClassGameStatus.WindowStrategyMenu);

こちらの方がシンプルです。

不要になった

//TODO https://yudachi-shinko.blogspot.com/2019/09/wpfframepage.html
while (frame.CanGoBack)
{
    frame.RemoveBackEntry();
}

という箇所は削除します。

他のナビゲーション要素として使われてない Page も、同様に UserControl に変更できそうです。

勢力選択時に領地をクリックした際の不具合

勢力選択時に領地を右クリックすると出撃画面になる不具合

通常はマップを右クリックすると、前の画面に戻るんですが、領地を右クリックした時だけ、出撃画面になります。
どうやら、クリック・イベント内でゲーム状態を判断してないからのようです。

なので、修正方法は状況判定コードを追加するだけです。

private void ButtonSelectionCity_RightKeyDown(object sender, EventArgs e)

関数の最初の所

var cast = (Button)sender;
if (cast.Tag is not ClassPowerAndCity)
{
    return;
}

において、現在の状態を調べて、勢力選択中に領地を右クリックしても、出撃画面を表示しないようにします。

if (this.NowSituation == Situation.SelectGroup)
{
    return; //勢力選択中は出撃しない。
}
var cast = (Button)sender;
if (cast.Tag is not ClassPowerAndCity)
{
    return;
}

これで、領地以外を右クリックした時と同じく、前の画面に「戻る」動作になります。

勢力選択時に中立領地を左クリックするとエラー終了するバグ

勢力選択時に特定の領地(中立領地)をクリックすると強制終了されます。
ワールドマップ上でどの領地が中立なのか識別不能なので、初めての人は唖然とするかも。

どうやら、画像読み込み時にマスターの顔グラが存在しないからのようです。
で、中立領地はマスターが設定されてないから、エラーになる訳です。
そもそも、中立領地をクリックして勢力情報画面を表示するのが変なので、表示しなければいい。

解決方法の案を書いておきます。参考にしてください。

private void DisplayPowerSelection(object sender)

関数の最初の所

var cast = (Button)sender;
if (cast.Tag is not ClassPowerAndCity)
{
    return;
}

の直後に、

var classPowerAndCity = (ClassPowerAndCity)cast.Tag;

という後の方にあった行を移動させて、

var cast = (Button)sender;
if (cast.Tag is not ClassPowerAndCity)
{
    return;
}

var classPowerAndCity = (ClassPowerAndCity)cast.Tag;
if (classPowerAndCity.ClassPower.MasterTag == string.Empty)
{
    return; //選択領地のマスター名が空なら、勢力情報画面を表示しない。
}

とします。比較項目は他のでもよさそう。中立かどうかさえ分かればいいです。
これで、中立領地を左クリックしても勢力情報画面は表示されず、エラーも発生しなくなります。

イベント実行中にユーザーの応答を待つ方法

掲示板から転載

<躓いたところ>
コンパイラ……というか、インタープリタと言いますか、
記された命令文を実行する機構の製造中で、msg関数とdialog関数に出会いました。
ご存じのように、これらはユーザーの応答があり次第、処理を進めるものとなります。
(msg関数はクリック、dialog関数は選択肢から選択をクリックすることが応答となります)

私の開発した機構は最後まで処理を実行してしまうもので(というかそれが普通?)
ユーザーの応答を待つように出来ていませんでした。
そこでasync/awaitを用いてユーザーの応答を待つようにしたものの、
予測しない挙動が発生、その挙動が解決しない為、ギブアップさせて頂く次第となりました。

更新されたデータファイルで試したら、今度は読み込むことができました。
まだ製作途中とのことで、不具合・未作成部分が多いのは仕方ないです。
で、肝心の「予測しない挙動」とは最初のイベントにおけるmsg命令3個の所っぽい。
どうやらタスクの終了待ちでずっと止まってしまうようです。

私はC#のことはよく分からないけど、ネットで調べながらいろいろ試してたら、この問題を解決できたみたいなので、何が問題か、どうすればいいか、を書いておきます。

とりあえず、非同期処理は危険です。今回もデッドロックが発生してました。
まずは問題のある個所を説明します。
ファイルMainWindow.xaml.cs内のDoWork関数の中で、await Task.Runで別スレッドにタスクを実行させてます。
そのタスクの中で、メッセージ表示用のcanvas部品Frameを追加してから、condition.Waitでタスクが終わるのを待ってます。
問題は「シグナルを発生させるはずのFrameが実際に表示されるのは、タスクが終わってから」という事のようです。
タスクを終わらせるFrameが表示されないから、ユーザーがタスクを終了させることができなくなる。
それで、タスクが終わらなくなって、強制終了するしかなくなる。
作者的には「async/awaitを用いれば、Frameの表示・入力が非同期に実行されるだろう」と期待したそうですが、残念ながらうまくいかなかった。
非同期処理は実装が難しくて、私もどう使ったらいいかは分かりません。

なので、私はシグナル待ちのループを使って解決することにしました。
とにかく無駄だったasync/awaitは取り除きます。
次にDoWork関数の中でFrameを追加したら、強制的に表示します。
最後は、ユーザーの入力があるまで無限ループで待ち続けます。
実際にどういう処理の流れなのか、自分でもほとんど分からないんですが、とにかくこれで動きました。
ネットで調べてコードを真似ただけなので、改良の余地は大いにあります。
とりあえず、動作はするので、基本的な仕組みだけでも参考にしてください。

####コードの変更点
Evaluator.csの109行付近
awaitを取り除きました。asyncの方はよくわからず。

                        this.window.DoWork(systemFunctionLiteral);

MainWindow.xaml.csの3738行付近
UIメッセージを強制的に処理する関数を追加しました。参考リンク:WPF-Viewを明示的に更新する
DoWork関数は待機方法を変更しました。

        /// <summary>
        /// 現在メッセージ待ち行列の中にある全てのUIメッセージを処理します。
        /// </summary>
        private void DoEvents()
        {
            DispatcherFrame frame = new DispatcherFrame();
            var callback = new DispatcherOperationCallback(obj =>
            {
                ((DispatcherFrame)obj).Continue = false;
                return null;
            });
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, callback, frame);
            Dispatcher.PushFrame(frame);
        }

        public void DoWork(SystemFunctionLiteral systemFunctionLiteral)
        {
            // メッセージ枠に表示する文字列を設定する。
            // 文字列の最初の " が残ってるのは、解析処理部分のミスっぽい。
            if (systemFunctionLiteral.Token.Type == TokenType.MSG)
            {
                Application.Current.Properties["message"] = systemFunctionLiteral.Parameters[0].Value;
            }

            // キャンバスにメッセージ枠を追加する。
            Uri uri = new Uri("/Page015_Message.xaml", UriKind.Relative);
            Frame frame = new Frame();
            frame.Source = uri;
            frame.Margin = new Thickness(0, 0, 0, 0);
            frame.Name = StringName.windowSortieMenu;
            this.canvasMain.Children.Add(frame);
            Application.Current.Properties["window"] = this;

            // キャンバス表示を更新する。これが無いとメッセージ枠が表示されない。
            DoEvents();

            // メッセージ枠への入力を待つ。
            // 実際にはcanvasのどこかに入力ハンドラーを作ればいいっぽい。
            // メインウインドウ全体の入力イベントに連動させた方が、操作しやすそう。
            condition.Reset();
            while (condition.Wait(100) == false)
            {
                // 待っている間も一定時間ごとに表示を更新する。
                // これによって、ウインドウの操作や入力の処理が動くっぽい。
                DoEvents();
            }
            Thread.Sleep(1);
            condition.Reset();

            // メッセージ枠を取り除く。
            this.canvasMain.Children.Remove(frame);
        }

Page015_Message.xamlの11行付近
動作実験用にメッセージを表示するためのLabelを追加しました。

    <Canvas x:Name="canvasMessage" KeyUp="canvasMessage_KeyUp"  MouseLeftButtonDown="canvasMessage_MouseLeftDown">
        <Border x:Name="borLeftWindow" Height="420" Width="1770" Margin="15,15,0,0" Background="#454545" BorderBrush="Black" BorderThickness="4" >
        </Border>
        <Label FontSize="16" Foreground="White" Content="メッセージウインドウをクリックすると閉じます。" Canvas.Left="150" Canvas.Top="20" />
        <Label Name="Label1" FontSize="20" Foreground="White" Content="未設定" Canvas.Left="150" Canvas.Top="60" Height="350" Width="1500" />
    </Canvas>

Page015_Message.xaml.csの25行付近
キー入力が駄目っぽいのでマウス入力を受け付けるようにしました。

        public Page015_Message()
        {
            InitializeComponent();

            // テキスト表示の例
            // 改行がうまくいかない。特殊文字に変換しないといけないもよう。
            Label1.Content = Application.Current.Properties["message"];
        }

        // コントロールにフォーカスが無い状態だと、キー入力を受け取れないみたい。
        // なので、マウス入力のハンドラーを別に作りました。
        private void canvasMessage_KeyUp(object sender, KeyEventArgs e)
        {
            var window = Application.Current.Properties["window"];
            if (window == null)
            {
                return;
            }
            var mainWindow = window as MainWindow;
            if (mainWindow == null)
            {
                return;
            }

            //MessageBox.Show("キー入力 ok ?\n");
            mainWindow.condition.Signal();
            //Thread.Sleep(1);
            //mainWindow.condition.Reset();
        }

        private void canvasMessage_MouseLeftDown(object sender, MouseButtonEventArgs e)
        {
            var window = Application.Current.Properties["window"];
            if (window == null)
            {
                return;
            }
            var mainWindow = window as MainWindow;
            if (mainWindow == null)
            {
                return;
            }

            //MessageBox.Show("マウス入力 ok ?\n");
            mainWindow.condition.Signal();
        }

GitHubのpull requestの使い方がよくわからないので、ここに書きました。
変更したファイル (Vahren後継_変更点_2022-08-28.zip) をOneDriveから直接ダウンロードできるようにしておきました。
ヴァーレン関連のファイル置き場のリンク

識別名を Contains で比較してる問題

Page001_Conscription.xaml.cs において
ユニットの識別名を比較する箇所で Contains (部分一致)を使うと
似たような名前のクラスを混同してしまう危険性があります。
63行目と 399行目と、529行目です。

Win010_TestBattle.xaml.cs において、
戦場に配置するユニットの識別名に Contains を使ってます。
59行目と 80行目です。

MainWindow.xaml.cs
1048行目

var result = this.ClassGameStatus.AllListSpot.Where(x => x.ListMember.Contains(new(classPowerAndCity.ClassPower.MasterTag, 0))).FirstOrDefault();

がうまく動いてないようです。
Linqの事はよく分からないんですが、
マスターの識別名を初期メンバーに含む領地を探してるはずですが、
なぜかマスター所在地を取得できてません。

var result = this.ClassGameStatus.AllListSpot.Where(x => x.ListMember.Contains(new(classPowerAndCity.ClassPower.MasterTag, 1))).FirstOrDefault();

に書き変えたら、ちゃんと取得できるようになりました。
データの構造的に、人数も指定しないと駄目だったのかも。

ゲーム開始時に領地にユニットを配置する際に
識別名を比較しようと Contains を使ってます。
1392行目と 1412行目です。

識別名を比較する時の Equals と == の違いはよく分かりません。
とりあえず、Contains は使わない方が良さそうです。
修正お願いします。

ランダム配置されるユニット

いちいち中立時モンスターを決めるのは大変なので、
ランダムで出すことも出来るようにする。

情報ここから

ヴァーレンだと、
ランダム配置されるユニットの数は
context構造体の
neutral_max = 12
neutral_min = 6
neutral_member_min = 3
neutral_member_max = 7
で指定します。

ここまで

ウインドウのサイズが小さいと操作できない問題

現在のキャンバス・サイズが 1800 x 1000 なのは、
1920 x 1080 のディスプレイを使ってフルスクリーンで
プレイする前提になってるからのようです。

ただ、もっと小さいディスプレイで動かす場合や、
ウインドウ・モードで小さくした時に、UIが表示されなくて、
操作できなくなる問題があります。
戦略マップや戦闘画面のマップはスクロールできるけど、
現状では、操作パネルの位置が固定されてるからです。

それで、キャンバスに配置したコントロールの位置を後から
調節することができるか試してみました。

canvasTop_SizeChanged 関数の末尾に以下のコードを追加します。

// 幅が変更されたら、UIの位置を調節する。
if (e.WidthChanged == true)
{
    // 難易度選択ボタンが存在するなら、横位置だけ変更する。
    foreach (var item in this.canvasMain.Children.OfType<Button>())
    {
        switch (item.Tag.ToString())
        {
        case "0": // Easy
        case "1": // Normal
        case "2": // Hard
        case "3": // Luna
            int startSpaceLeft = 30;
            double currentTop = item.Margin.Top;
            double newLeft;
            // ウインドウがキャンバスよりも大きい場合
            if (si.Width >= this.CanvasMainWidth)
            {
                newLeft = this.CanvasMainWidth - item.Width - startSpaceLeft;
            }
            // ウインドウがキャンバスよりも小さい場合
            else
            {
                newLeft = si.Width - item.Width - startSpaceLeft;
            }

            item.Margin = new Thickness()
            {
                Top = currentTop,
                Left = newLeft,
            };
            break;
        }
    }
}

あと、小さくする実験用に MainWindow.xaml 内のサイズ指定を変更します。

Height="900"
Width="1600"
MinHeight="600"
MinWidth="800"

これで、タイトル画面の難易度選択ボタンの位置が、
ウインドウの大きさに応じて調節されます。

他のパネルの位置や大きさも、同様に変更できそうです。
よければ参考にしてください。

戦略画面の勢力メニューウインドウ

戦略画面において右下に表示される勢力メニューのウインドウに
[領地数] と [ユニット数] を表示する方法が分かったので
参考までにコードを書いておきます。

ファイル UserControl005_StrategyMenu.xaml.cs の
DisplayPowerStatus 関数において値を設定してる部分

//領土数
{
    this.lblNameNumberSpot.Content = "";
}
//ユニット数
{
    this.lblNameNumberUnit.Content = "";
}

に Class データを参照するコードを追加します。

//領地数
{
    this.lblNameNumberSpot.Content = mainWindow.ClassGameStatus.SelectionPowerAndCity.ClassPower.ListMember.Count;
}
//ユニット数
{
    string select_NameTag = mainWindow.ClassGameStatus.SelectionPowerAndCity.ClassPower.NameTag;
    int unit_count = 0;
    var list_spot = mainWindow.ClassGameStatus.AllListSpot.Where(x => x.PowerNameTag == select_NameTag);
    foreach (var item_spot in list_spot)
    {
        foreach (var item_group in item_spot.UnitGroup)
        {
            unit_count += item_group.ListClassUnit.Count;
        }
    }
    this.lblNameNumberUnit.Content = unit_count;
}

という感じです。

あと、ヴァーレントゥーガでは「領土数」ではなく「領地数」と表記されてるので、
ファイル UserControl005_StrategyMenu.xaml の該当箇所を「領地数」に変えれば、
ヴァーレンと同じように表示されます。
まあ、意味は同じなんですが、表記揺れで、検索しにくくなるかもしれないので。

ClassUnit の陣形データ Formation の仕様

現在は、陣形データ ClassUnit.Formation の定義が

private ClassFormation _formation = new ClassFormation();

public ClassFormation Formation
{
    get { return _formation; }
    set { _formation = value; }
}

というふうに、ClassFormation という専用 class になってます。

そして、その ClassFormation の定義は

public class ClassFormation
{
    public int Id { get; set; }
    public Formation Formation { get; set; }
}

というふうに、整数のIdと列挙型のFormationで構成されてます。

列挙型Formationの定義は

public enum Formation
{
    F
    , M
    , B
}

ということですので、
Formation.F = 0
Formation.M = 1
Formation.B = 2
あるいは、
_010_Enum.Formation.F = 0
_010_Enum.Formation.M = 1
_010_Enum.Formation.B = 2
として扱えます。

ClassFormationのメンバである、この列挙型の変数を使って、
前衛・中衛・後衛、の3通りを表現してます。

で、私が不思議に思うのは、もう一つのメンバ変数 Id の役割です。
使い道は、ComboBox で選択する際の番号になってるようです。
しかし、番号として 0, 1, 2 の3種類で使うなら、
列挙型のFormation の数値表現と同じ値です。

つまり、ClassUnit の陣形データとしては、
列挙型のFormation だけで十分な気がします。

private _010_Enum.Formation _formation = _010_Enum.Formation.B;

public _010_Enum.Formation Formation
{
    get { return _formation; }
    set { _formation = value; }
}

みたいな感じで、定義を class から enum に変更すれば、
陣形データを参照する際は

currentFormation = selectedClassUnit.Formation;

陣形データを設定する際は

selectedClassUnit.Formation = _010_Enum.Formation.F;

みたいな感じで、より簡単にアクセスできそうです。

class や enum について学び始めたばかりですので、
もしかすると勘違いしてる所があるかもしれません。
陣形データとして数値を1個記録するためだけに class を使うよりは、
enum の方が単純明快でいいように思います。

部隊長問題

指摘された件を以下に

~~~
spot0001 テヤルガ の
member = "AbelIrijhorn,VictodyRoyle,JothianRoarcy,MabateRoarcy,LineInfantryM14*5";
が現状では
人材4人と一般兵5ユニット
(合計5部隊)になってます。

部隊長を指定してるのだから、
人材4人と一般兵5部隊
(合計9部隊)になるべき

※ヴァーレンで「一般兵*数値」を領地に
指定すると、数値分の部隊が配置されます。
~~~

右クリックした際の挙動について

シナリオ選択画面からタイトル画面に戻る方法

現在は、シナリオ選択画面で右クリックしても、タイトル画面に戻れません。
右クリックした際のイベントに対応するコードが無いからです。

それで、他のシーン遷移部分を参考にしながら、試しに書いてみました。

/// シナリオ選択画面でマウスが右クリックされた時の処理
/// タイトル画面に戻る
private void ScenarioSelection_MouseRightButtonUp(object sender, MouseEventArgs e)
{
    this.FadeOut = true;

    this.delegateMainWindowContentRendered = MainWindowContentRendered;

    this.FadeIn = true;
}

この関数を

/// シナリオ選択画面でいずれかのシナリオボタンが押された時の処理
private void ScenarioSelectionButton_click(Object sender, EventArgs e)

関数の隣に追加します。

次に呼び出し元として、

/// シナリオ選択ボタンを押す画面
private void SetWindowMainMenu()

関数内で追加してるキャンバス(左上と左下)を指定します。

canvas.Name = StringName.windowMainMenuLeftTop;
canvas.MouseRightButtonUp += ScenarioSelection_MouseRightButtonUp;

canvas.Name = StringName.windowMainMenuLeftUnder;
canvas.MouseRightButtonUp += ScenarioSelection_MouseRightButtonUp;

さらに、WindowMainMenuLeftTop_MouseEnter 関数内で作成してるキャンバス(右上と右下)も

canvas.Name = StringName.windowMainMenuRightTop;
canvas.MouseRightButtonUp += ScenarioSelection_MouseRightButtonUp;

canvas.Name = StringName.windowMainMenuRightUnder;
canvas.MouseRightButtonUp += ScenarioSelection_MouseRightButtonUp;

という感じで、Name 指定の下に MouseRightButtonUp を追加してみました。

これで、タイトル画面に戻れるんですが、

private void SetWindowTitle(int targetNumber)
{
    // 既に何か表示されてたら消す (Now Loading... の画像とか)
    this.canvasMain.Children.Clear();

こんな感じで、最初に canvasMain の子コントロールを消す必要があります。
ヴァーレンのように Now Loading 画像を表示する際も、これで消せそうです。

特定のボタンで右クリックを無効にする方法

上記の方法だと、シナリオ選択ボタンを右クリックした場合も、
タイトル画面に戻ってしまいます。
特に操作上は問題ではないけど、ヴァーレンとは挙動が異なります。
ヴァーレンだと、シナリオ選択ボタンを右クリックしても、何も起きません。

それで、ボタンを右クリックした時用に、何もしないダミーの関数を作ってみました。

/// ボタン等を右クリックした際に、親コントロールが反応しないようにする
private void Disable_MouseEvent(object sender, MouseEventArgs e)
{
    e.Handled = true;
}

この関数を ScenarioSelection_MouseRightButtonUp 関数の隣にでも置いてください。

次に呼び出し元として、シナリオ選択ボタンに MouseRightButtonUp を追加します。

button.Click += ScenarioSelectionButton_click;
button.MouseRightButtonUp += Disable_MouseEvent;

左上と左下のキャンバスごとに加えました。

これで、キャンバスを右クリックしたら戻るけど、ボタンを右クリックしたら戻らない、
というヴァーレンと同じ挙動になります。

勢力情報表示ウインドウ(キャンバス)を右クリックで閉じる方法

ヴァーレンはウインドウを右クリックすると閉じるようになってます。
勢力選択画面において、勢力情報表示ウインドウを表示した際に、
同じ挙動にできないか試してみました。

「取り消し」ボタンを押したときに呼ばれる
ButtonSelectionPowerRemove_click 関数を呼び出すマウスイベント関数を作ります。

/// 勢力情報キャンバスを右クリックしたら閉じるようにする
private void SelectionPower_MouseRightButtonUp(object sender, MouseEventArgs e)
{
    ButtonSelectionPowerRemove_click(sender, e);

    e.Handled = true;
}

これを

/// 勢力選択画面で取り消しした時の処理
private void ButtonSelectionPowerRemove_click(object sender, EventArgs e)

の後にでも追加してください。左クリックと右クリックで違うけど、流用できるみたい。

最後に、勢力情報キャンバスに MouseRightButtonUp を追加します。

canvas.Name = StringName.windowSelectionPower;
canvas.MouseRightButtonUp += SelectionPower_MouseRightButtonUp;

こんな感じです。
勢力情報キャンバスを右クリックで、「取り消し」ボタンを押すのと同じになります。

フルスクリーン(全画面表示)でゲームを開始する方法

C# WPF の仕様が謎だったんですが、いろいろ試してなんとなく分かりました。
ウインドウを最大化して枠を消すと、フルスクリーンになるようです。
ディスプレイ画面全体に広がります。タスクバーは隠れます。

一方で、ウインドウ枠を表示したまま最大化すると、作業領域に広がるので、
タスクバーが表示されたままになります。
以前報告した「タスクバー表示の問題」はフルスクリーンなら発生しません。

ゲームするだけならフルスクリーンの方がいいんですが、
デバッグ中はウインドウ表示の方が便利なことが多いです。
それで、「WPFで画面を全画面表示する」というページを参考にして、
F11キーで状態を切り替るコードを書いてみました。
ゲーム中に、ESCキーで終了、F11キーでフルスクリーン切り替えします。

あと、UIのずれを解消するため、ウインドウを最大化してるかどうかで、
キャンバスの位置も調節するようにしました。
最大化・フルスクリーン中なら、画面の**にキャンバスを置き、
ウインドウ表示なら左上に合わせます。

変更するファイルは MainWindow.xaml と MainWindow.xaml.cs です。
以下に詳細を書いておきます。参考にしてください。

MainWindow.xaml のウインドウサイズ指定ですが、現在の

Height="300"
Width="300"

だと値が小さすぎるので、大きくして、最小サイズと位置も設定しました。

Height="1000"
Width="1800"
MinHeight="1000"
MinWidth="1800"
Top="0"
Left="0"

ウインドウの初期設定時にコードで設定してもいいんですが、
現在はキャンバスサイズが 1800x1000 に固定されてるっぽいので、
xaml に直接書きました。将来的には改善した方がよさそう。

MainWindow.xaml.cs の MainWindow_Initialized 関数の最初の所、現在は

//this.WindowStyle = WindowStyle.None;
this.WindowState = WindowState.Maximized;

というふうに最大化してるけどウインドウ枠を残してるので、コメントを外します。

// 初期状態でフルスクリーンにする。F11キーを押すとウインドウ表示になる。
this.WindowStyle = WindowStyle.None;
this.WindowState = WindowState.Maximized;
// ウインドウ表示で始めたい場合は、上をコメントアウトして、下のコメントを解除する。
//this.WindowStyle = WindowStyle.SingleBorderWindow;
//this.WindowState = WindowState.Normal;

これで初期状態がフルスクリーン表示になります。
いちおう、ウインドウ表示で始めたい場合のコードも書いてます。

次に、キャンバスのマージンはウインドウ状態に応じて変更するので、
MainWindow_ContentRendered 関数においてマージン設定してる部分を消します。

this.canvasMain.Margin = new Thickness()
{
    Top = (this._sizeClientWinHeight / 2) - (this.CanvasMainHeight / 2),
    Left = (this._sizeClientWinWidth / 2) - (this.CanvasMainWidth / 2),
};
this.WindowStyle = WindowStyle.SingleBorderWindow;

WindowStyle を指定してる部分も消してください。
MainWindow_Initialized 関数で指定済みですから。

そして、F11キーに反応するコードを書きます。
MainWindow_KeyDown 関数内で ESCキーに反応してる部分があります。

if (e.Key == Key.Escape)
{
    this.Close();
}

この下に追加してください。

// ESCキーを押すと終了する。
if (e.Key == Key.Escape)
{
    this.Close();
}
// F11キーを押すとフルスクリーン状態を切り替える。
else if (e.Key == Key.F11)
{
    if (WindowState == WindowState.Maximized)
    {
        // 最大化中なら、通常サイズにする。
        this.WindowStyle = WindowStyle.SingleBorderWindow; // タイトルバーと境界線を表示します。
        this.WindowState = WindowState.Normal;
    }
    else
    {
        // 通常サイズか最小化中なら、最大化する。
        this.WindowStyle = WindowStyle.None; // タイトルバーと境界線を非表示にします。
        this.WindowState = WindowState.Maximized;
    }
}

最後に、
フルスクリーン、最大化ウインドウ、通常ウインドウの三種類に対応するため
canvasTop_SizeChanged 関数にマージンを調節するコードを追加します。
現在の

// クライアント領域を知る方法
var si = e.NewSize;
this._sizeClientWinWidth = (int)si.Width;
this._sizeClientWinHeight = (int)si.Height;

という部分の下に追加してください。(上だと変数が更新されてない)

// クライアント領域を知る方法
var si = e.NewSize;
this._sizeClientWinWidth = (int)si.Width;
this._sizeClientWinHeight = (int)si.Height;

if (this.WindowState == WindowState.Maximized)
{
    // 最大化中なら、キャンバスをフルスクリーンの**に置く。
    this.canvasMain.Margin = new Thickness()
    {
        Top = (this._sizeClientWinHeight / 2) - (this.CanvasMainHeight / 2),
        Left = (this._sizeClientWinWidth / 2) - (this.CanvasMainWidth / 2)
    };
}
else
{
    // 通常サイズか最小化中なら、キャンバスをウインドウの左上位置に置く。
    this.canvasMain.Margin = new Thickness()
    {
        Top = 0, Left = 0
    };
}

クライアント領域のサイズを取得した後に、キャンバス位置を更新します。

メッセージ表示時間の設定

こういう機能があればいいなというリクエストです。

ヴァーレントゥーガは通知や警告などで、
短時間で消えるダイアログを出すことが多いです。
正確な時間は不明ですが、消えるまで 1秒ちょっとな感じです。

最初はすぐに消えて文章を読めないけど、慣れてくると読む必要ないし、
逆に消えるまで遅いとイライラします。
そのくせ、クリックして手動で消すほど待たせる訳でもないのが微妙な所です。

ゲームの設定画面でメッセージを表示する時間を設定できれば良いと思います。

内政の仕様について

内政の仕様について、以下は如何でしょうか。

都市の新しい仕様の構造体
NewFormatSpot spot0009
{

	name = "ハラーハッフ";
	image = "099_SNOW2.png";
	x = "2800";
	y = "680";
	map = "wefwefwefwefw";
	capacity = "6";
	member = "LineInfantryM14*2,DragoonFrancesca,HowitzerC11";
	text = "短い文章です。";
	
	# 実行出来る内政をinternalAffairsタグで指定
	internalAffairs = "abc,def,ghi";
}
internalAffairsDetail abc
{

	# 画面上に表示される名前
	title = "物流改善";
	
	# 100px*512pxで画面上に表示
	image = "abc.png";

	# 前提とする内政(これが実行されてなければ、この内政は実行不可能
	premise = "def";
	# 前提とする内政の必要回数(5なら5回実行が必要
	premiseNumber = "3";
	
	# 収入補正
	# %分、上昇する
	# -1だと実行されない
	incomeCorrection = "0.5";
	
	# 収入
	# 数値分、上昇する
	# -1だと実行されない
	income = "1000";
	
	# スキル付与(出撃時と防衛時、全部隊に
	# スキルのタグを指定する
	# -1だと実行されない
	skill = "gun01";
	
	# ユニット追加(出撃時と防衛時、操作不可、自動で移動と攻撃
	unit = "LineInfantryM14*2";
	
	# 上昇させる能力値
	# -1だと実行されない
	ability = "power";

	# 能力値上昇をさせる時(出撃時と防衛時どちらか
	# -1だと実行されない
	abilityTiming = "Sortie";
	
	# 能力値上昇値
	# -1だと実行されない
	abilityUp = "5";
}

画面UI
内政

勢力一覧ウィンドウの機能実装

勢力選択画面において、右上に表示される勢力一覧ウィンドウ
が機能してなかったので、動くようにしてみました。

MainWindow.xaml.cs に以下の二つの関数を追加してください。

// 勢力一覧ウィンドウのボタンを押した時の処理
private async void ButtonSelectionPowerMini_click(Object sender, EventArgs e)
{
    var cast = (Button)sender;
    if (cast.Tag is not ClassPower)
    {
        return;
    }

    var gridMapStrategy = (Grid)LogicalTreeHelper.FindLogicalNode(this.canvasMain, StringName.gridMapStrategy);
    if (gridMapStrategy == null)
    {
        return;
    }

    double target_X = 0, target_Y = 0;
    int spot_count = 0;
    var classPower = (ClassPower)cast.Tag;
    foreach (var item in classPower.ListInitMember)
    {
        var spot = this.ClassGameStatus.AllListSpot.Where(x => x.NameTag == item).FirstOrDefault();
        if (spot != null)
        {
            target_X = spot.X;
            target_Y = spot.Y;
            spot_count += 1;
        }
        // とりあえず、最初の領地だけ参照する。
        // 将来的には、全ての領地の座標の平均値にする予定
        break;
    }

    if (spot_count > 0)
    {
        // 目標にする座標をウインドウ**にする
        /*
        gridMapStrategy.Margin = new Thickness()
        {
            Top = this.CanvasMainHeight / 2 - target_Y,
            Left = this.CanvasMainWidth / 2 - target_X
        };
        */

        ClassVec classVec = new ClassVec();
        // 現在の Margin
        classVec.X = gridMapStrategy.Margin.Left;
        classVec.Y = gridMapStrategy.Margin.Top;

        // 目標にする領地の座標をウインドウ**にするための Margin
        classVec.Target = new Point(
            this.CanvasMainWidth / 2 - target_X,
            this.CanvasMainHeight / 2 - target_Y
        );
        classVec.Speed = 20;
        classVec.Set();

        while (true)
        {
            Thread.Sleep(5);

            if (classVec.Hit(new Point(gridMapStrategy.Margin.Left, gridMapStrategy.Margin.Top)))
            {
                break;
            }

            await Task.Run(() =>
            {
                Application.Current.Dispatcher.Invoke((Action)(() =>
                {
                    var ge = classVec.Get(new Point(gridMapStrategy.Margin.Left, gridMapStrategy.Margin.Top));
                    gridMapStrategy.Margin = new Thickness()
                    {
                        Left = ge.X,
                        Top = ge.Y
                    };
                }));
            });
        }
    }
}

// 勢力一覧ウィンドウの表示
private void ListSelectionPowerMini()
{
    int fontSizePlus = 5;
    int hei = 50;
    int widthIcon = 350;

    Grid gridPower = new Grid();
    gridPower.HorizontalAlignment = HorizontalAlignment.Left;
    gridPower.VerticalAlignment = VerticalAlignment.Top;
    gridPower.Height = (ClassGameStatus.ListPower.Count) * hei;
    gridPower.Width = widthIcon;
    gridPower.Name = StringName.windowSelectionPowerMini;
    // ウインドウの右上に配置する
    gridPower.Margin = new Thickness()
    {
        Left = this.CanvasMainWidth - widthIcon,
        Top = 0
    };
    //grid.AllowDrop = false;
    foreach (var item in ClassGameStatus.ListPower.Select((value, index) => (value, index)))
    {
        Grid gridPowerButton = new Grid();
        BitmapImage bitimg1 = new BitmapImage(new Uri(item.value.FlagPath));
        //旗を加工する処理を入れたい
        Int32Rect rect = new Int32Rect(0, 0, 32, 32);
        var destimg = new CroppedBitmap(bitimg1, rect);

        Image img = new Image();
        // 拡大せずに 32 x 32 のままで表示する
        //img.Stretch = Stretch.Fill;
        //img.Source = bitimg1;
        img.Source = destimg;
        img.Height = 32;
        img.Width = 32;
        img.HorizontalAlignment = HorizontalAlignment.Left;
        img.Margin = new Thickness { Left = 4, Top = 0 };
        gridPowerButton.Children.Add(img);

        TextBlock tbDate1 = new TextBlock();
        tbDate1.HorizontalAlignment = HorizontalAlignment.Left;
        tbDate1.VerticalAlignment = VerticalAlignment.Center;
        tbDate1.FontSize = tbDate1.FontSize + fontSizePlus;
        tbDate1.Text = item.value.Name;
        tbDate1.Foreground = Brushes.White;
        tbDate1.Margin = new Thickness { Left = 4 + 32 + 14, Top = 0 };

        gridPowerButton.HorizontalAlignment = HorizontalAlignment.Left;
        gridPowerButton.VerticalAlignment = VerticalAlignment.Top;
        gridPowerButton.Height = hei;
        gridPowerButton.Width = widthIcon;
        gridPowerButton.Margin = new Thickness()
        {
            Left = 0,
            Top = 0
        };
        gridPowerButton.Children.Add(tbDate1);

        // 半透明のブラシを作成する (黒の50%)
        SolidColorBrush mySolidColorBrush = new SolidColorBrush(Colors.Black);
        mySolidColorBrush.Opacity = 0.5;

        Button button = new Button();
        button.HorizontalAlignment = HorizontalAlignment.Left;
        button.VerticalAlignment = VerticalAlignment.Top;
        button.Content = gridPowerButton;
        button.Height = hei;
        button.Width = widthIcon;
        button.Margin = new Thickness
        {
            Left = 0,
            Top = item.index * hei
        };
        button.Tag = item.value;
        //button.Background = Brushes.Black;
        button.Background = mySolidColorBrush;
        button.Click += ButtonSelectionPowerMini_click;
        gridPower.Children.Add(button);
    }

    this.canvasUIRightTop.Children.Add(gridPower);
}

canvasMain ではなく canvasUIRightTop に追加するので、
ウインドウ・サイズ変更に追随して右上に固定されます。

ただ、canvasUIRightTop に変更したので、消去する部分も変更します。
DisplayPowerSelection 関数において canvasMain から探してる部分

{
    var ri2 = (Canvas)LogicalTreeHelper.FindLogicalNode(this.canvasMain, StringName.windowMapStrategy);
    if (ri2 != null)
    {
        var ri3 = (Grid)LogicalTreeHelper.FindLogicalNode(ri2, StringName.windowSelectionPowerMini);
        if (ri3 != null)
        {
            ri2.Children.Remove(ri3);
        }
    }
}

ここを次のように変えます。

{
    var ri2 = (Grid)LogicalTreeHelper.FindLogicalNode(this.canvasUIRightTop, StringName.windowSelectionPowerMini);
    if (ri2 != null)
    {
        this.canvasUIRightTop.Children.Remove(ri2);
    }
}

更に、勢力選択画面で取り消した際には、勢力一覧を表示し直します。
ButtonSelectionPowerRemove_click 関数の末尾に

var ri2 = (Grid)LogicalTreeHelper.FindLogicalNode(this.canvasUIRightTop, StringName.windowSelectionPowerMini);
if (ri2 == null)
{
    // 勢力一覧ウィンドウを出す
    ListSelectionPowerMini();
}

を追加してください。

SetMapStrategy関数において、「勢力ウィンドウを出す」部分はさくっと削除して、
関数の最後あたりにでも

// 勢力一覧ウィンドウを出す
ListSelectionPowerMini();
}

と追加してください。canvasMain とは独立して canvasUIRightTop に配置してます。

ClassVec.cs に以下の関数を追加してください。命中判定に使います。

// 移動後の距離を試算して、標的に接近中なら false
// 距離が同じままか、あるいは遠ざかってるなら true を返す
public bool Hit(Point current)
{
    double current_distanceX = this.Target.X - current.X;
    double current_distanceY = this.Target.Y - current.Y;
    double next_distanceX = current_distanceX - (this.Vec.X * this.Speed);
    double next_distanceY = current_distanceY - (this.Vec.Y * this.Speed);
    if (next_distanceX * next_distanceX + next_distanceY * next_distanceY >= current_distanceX * current_distanceX + current_distanceY * current_distanceY)
    {
        return true;
    }
    return false;
}

不適切な出撃目標に対しても出撃ウインドウが表示される問題

現在はまだ自国領のエフェクトとか無いので、どこが自国かが分かりにくいです。
どこか領地をクリックすると、隣接してない領地でも出撃ウインドウが表示されます。
実際には、出撃元が存在しないから部隊選択できず、
出撃できないんですが、いちいちキャンセルしないといけないです。
自国領に対しても出撃ウインドウが表示されます。
害はないけど面倒なので、最初から出撃ウインドウを表示しない方が良さそうです。

ButtonSelectionCity_RightKeyDown 関数内にはチェックする部分があるんですが、
まだ項目を追加してないようなので、試しに私がコードを書いてみました。

隣接チェックをしてる箇所

////隣接チェック
//国に関係なく隣接都市名を抽出
List<string> NameRinsetuSpot = new List<string>();
foreach (var item in this.ListClassScenarioInfo[this.NumberScenarioSelection].ListLinkSpot)
{
    if (classPowerAndCity.ClassSpot.NameTag == item.Item1)
    {
        NameRinsetuSpot.Add(item.Item2);
        continue;
    }
    if (classPowerAndCity.ClassSpot.NameTag == item.Item2)
    {
        NameRinsetuSpot.Add(item.Item1);
    }
}
//国で隣接都市名を抽出
List<ClassSpot> classSpots = new List<ClassSpot>();
foreach (var item in NameRinsetuSpot)
{
    var ge = this.ClassGameStatus.AllListSpot.Where(x => x.NameTag == item).FirstOrDefault();
    if (ge == null)
    {
        continue;
    }
    if (ge.PowerNameTag != this.ClassGameStatus.SelectionPowerAndCity.ClassPower.NameTag)
    {
        continue;
    }
    classSpots.Add(ge);
}

の前後に、所属と隣接数のチェックを追加します。

//所属チェック
if (classPowerAndCity.ClassPower.NameTag == this.ClassGameStatus.SelectionPowerAndCity.ClassPower.NameTag)
{
    return; //自国には攻め込まない。
}

////隣接チェック
//国に関係なく隣接都市名を抽出
List<string> NameRinsetuSpot = new List<string>();
foreach (var item in this.ListClassScenarioInfo[this.NumberScenarioSelection].ListLinkSpot)
{
    if (classPowerAndCity.ClassSpot.NameTag == item.Item1)
    {
        NameRinsetuSpot.Add(item.Item2);
        continue;
    }
    if (classPowerAndCity.ClassSpot.NameTag == item.Item2)
    {
        NameRinsetuSpot.Add(item.Item1);
    }
}
//国で隣接都市名を抽出
List<ClassSpot> classSpots = new List<ClassSpot>();
foreach (var item in NameRinsetuSpot)
{
    var ge = this.ClassGameStatus.AllListSpot.Where(x => x.NameTag == item).FirstOrDefault();
    if (ge == null)
    {
        continue;
    }
    if (ge.PowerNameTag != this.ClassGameStatus.SelectionPowerAndCity.ClassPower.NameTag)
    {
        continue;
    }
    classSpots.Add(ge);
}
if (classSpots.Count == 0)
{
	return; //自国と隣接してないので出撃できない。
}

これで、出撃先が限定されます。
出撃可能な部隊が存在するかまでチェックするのは大変そうなのでやってません。
とりあえず、暫定的な物として、参考にしてください。

改行コードの違いについて

改行コードの消し方

データファイル内で改行コードを探す際に
System.Environment.NewLine を使うのは問題かもしれません。

なぜなら、テキストファイルがどんな環境で作られるか分からないからです。
ゲームの動作環境が Windows OS だからといって、
シナリオライターが Mac を使わないとは限りません。

改行コードとして使われるのは3種類あります。

\r
\n
\r\n

したがって、改行コードを消したい場合は、
.Replace(System.Environment.NewLine, "")
だけでは不十分です。

.Replace("\n", "").Replace("\r", "")
のように2文字をそれぞれ消さないといけません。

ヴァーレントゥーガのテキスト成形方法

いろいろ試した所、改行は常に無視されるけど、
スペースやタブは行の前後だけ無視されるようです。

なので、一旦行ごとに分割してから、各行の前後を掃除する必要があります。

見よう見まねでコードを書いてみました。

// 改行ごとに分割 (Split) するため、改行コードを「\n」に統一する。
string strTemp = first.Value.Replace("\r\n", "\n").Replace("\r", "\n");
// Split で各行に分割した後、Trim で前後のスペースとタブ、改行を取り除く。
char[] charsToTrim = {' ', '\t', '\n'};
string[] strLines = strTemp.Split("\n").Select(x => x.Trim(charsToTrim)).ToArray();
// 各行を連結して、一つに戻す。
strTemp = String.Join("", strLines);
// ヴァーレントゥーガのテキスト用特殊文字「$」を改行に置換する。
classScenario.ScenarioIntroduce = strTemp.Replace("$", System.Environment.NewLine);

もっと簡潔にできそうですが、ステップごとの処理を分かりやすくしました。

マップファイルの仕様について

頂いた指摘は以下の通り

~~~~~~~~~
タイルごとの
要素の個数をチェックできるのなら、
不要な項目は省略できた方が、
ファイル・サイズが小さくなります。

1個= field
2個= fieldobject
3個= field
objectunit
5個= field
objectunithoukou*zinkei

みたいな感じに順番だけ決めておけば、
要素の個数で存在する値を読み込んで
存在しない値は自動的にnullにすればいい。

なお、防衛施設や退却位置などのように
unitの方向、unitの陣形
が不要な場合もあるから、
unitの名前
を先に記録した方が、省略しやすい。
~~~~~~~~~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.