花鬼の庵

開発とかゲームとか実況とか

【Python】の勉強を始めよう_環境設定

前々からwebサイト等の自動化処理を学習したいと思っていたのを、ついに進めようかと。
そこで選ばれたのはPython、理由は軽く調べた感じネットでの情報量も大きく学習しやすそうだったから。
ということで環境設定の備忘録をまとめる。

Pythonのインストール

Python自体のインストールをしないと何も始まらない。

Welcome to Python.org

↑からPythonの公式HPに飛べる。

f:id:HanaoniEruni:20220328192932p:plain

Downloadsをクリックすると最新バージョンのWindowsPythonをDLできる。

DLした.exeファイルを実行したら「Add Python 3.10 to PATH」にcheckを入れてInstall Nowをクリック。

f:id:HanaoniEruni:20220328194055p:plain

最後に Disable path length limit (パスの長さを無効にするかどうか)と聞かれてますが、これは無視してCloseをクリックし、Python自体のインストールは終了。

コマンドプロンプトを起動し「python --version」 を入力し実行。
以下のようにバージョンが表示されば問題なし。

f:id:HanaoniEruni:20220328194738p:plain
問題あればもう一度DLした.exeファイルを実行してRepairか再インストール。

パッケージのインストール

次に開発に使うパッケージ(ライブラリ)のインストール。
コマンドプロンプトを起動し、
pip install selenium
pip install pandas
pip install webdriver-manager
を一行ずつ実行。
以下のようにパッケージのインストールが進んでく。

f:id:HanaoniEruni:20220328195252p:plain

3つともインストールが終わったら
pip freeze
を実行。
インストールされたパッケージが一覧でバージョンとともに表示されるので、その中にselenium、pandas、webdriver-managerが存在していれば問題なしです。

コードは安定のVisualStudioで

コードはVisualStudio2019で書いてく。
「新しいプロジェクトの作成」からPythonアプリケーションを選択。

f:id:HanaoniEruni:20220328200040p:plain

ここでPythonのテンプレートが見つからないようであれば「ツールと機能をインストールする」をクリック、もしくはインストーラーからPython開発を追加すればテンプレートが追加される。

Python開発を追加したときに注意なのが、たまにVisualStudio内部でPythonの環境が変わってインストールしたはずのパッケージが使えなくなっていることがある…らしい。
そのさいの対処法というか解決方法というか、元々使う予定の環境への切り替え方が載っているサイトがあったので参考までに。

Visual StudioのPython環境の切り替え変更 | イメージングソリューション

締め

以上で環境設定、事前準備は終了。
とりあえず目標は楽天競馬の投票を自動化させてアプリにしたい。
今のところそこまで難しくなさそう…少なくともUWPよりは断然簡単な感じがするけど…。
はてさてどうなることやら。

なにか質問等々ございましたらコメントいただければと思います。

【C#】で行うDB接続とクエリ文の実行、値の保存と更新、削除

hanaonieruni.hatenablog.com

↑の続き

前はC#でDBへ接続して値を取得(SELECT)した。
新規保存や更新、削除(INSERTやUPDATE、DELETE)もそのままで行けるけど、
ExecuteReader()よりも
ExecuteNonQuery()を使った方が良いらしい。
とりあえず以下の関数を前回の記事に載せた" class DBconnect "に追加する。

static public int ExecuteNonQuery(SqlConnection connection, String sql, Dictionary<String, String> param = null)
        {
            int rtn = -1;
            try
            {
                // データベースの接続開始
                connection.Open();
                Console.WriteLine(connection.State.ToString());
                using (SqlCommand command = new SqlCommand() { CommandText = sql, Connection = connection })
                {
                    try
                    {
                        if (param != null)
                        {
                            foreach (string Key in param.Keys)
                            {
                                command.Parameters.AddWithValue(Key, param[Key]);
                            }
                        }
                        // SQLの実行
                        rtn = command.ExecuteNonQuery();
                    }
                    catch
                    {
                        throw;
                    }
                }
            }
            catch
            {
            }
            return rtn;
        }

簡単に解説

解説といっても
// SQLの実行
reader = command.ExecuteReader();
これを
command.ExecuteNonQuery();
に変えただけ。

ExecuteNonQuery()は影響を受けた行数を返してくれる。
INSERTで1行追加したなら”1”
UPDATEで5行のとある値を更新したなら”5”
条件(WHERE)を間違えてなにも変更が起きなかったらもちろん”0”が返される。

実装例

あとの使い方は大体同じ。
新規保存(INSERT)する場合はの場合は

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

namespace CheckSQL
{
    class Program
    {
        static void Main(string[] args)
        {
            // DB接続
            string connectionString = DBconnect.GetConnectionString();
            StringBuilder sql = new StringBuilder();

            // クエリ文実行
            sql.AppendLine("INSERT INTO C_スケジュール");
            sql.AppendLine(" ( 日付 , ID ) ");
            sql.AppendLine("VALUES");
            sql.AppendLine("('2022/03/28', 1 ) ");

            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                if(RdoConnect.ExecuteNonQuery(connection, sql.ToString()) >= 0)
                {
                    // 成功
                }else
                {
                    // 失敗
                }
            }
        }
    }
}

更新と削除(UPDATEとDELETE)もクエリ文だけ変えればいい。

締め

以上、前の記事と処理自体はほとんど同じなんだけど、そのせいでいぞ使うときパッと出てこないもので、記事に残しておこうと思った次第。

なにか質問等々ございましたらコメントいただければと思います。

【UWP】アプリのパッケージ作成の際に表示されたエラー

自作アプリも作った際ストアにせよサイトにせよ、公開するにはパッケージ作成を行う必要があります。
自分もこの間、初めて自作アプリを作成しストアに提出するため、パッケージ作成を行ったのですが…ここでとあるエラーが表示されました。

『エラー:ペイロードに含まれている複数のファイルで同じターゲット パス '〇〇〇' が指定されていますが、内容が異なります。』が表示される。

具体的には以下のようなエラーが表示されました。

ペイロードに含まれている複数のファイルで同じターゲット パス 'WindowsBase.dll' が指定されていますが、内容が異なります。ソース ファイル: 
C:\Users\user\.nuget\packages\runtime.win10-x64.microsoft.netcore.universalwindowsplatform\6.2.13\runtimes\win10-x64\lib\uap10.0.15138\WindowsBase.dll
C:\Users\user\.nuget\packages\runtime.win10-x64-aot.microsoft.netcore.universalwindowsplatform\6.2.13\runtimes\win10-x64-aot\lib\uap10.0.15138\WindowsBase.dll

上記では『WindowsBase.dll』のエラーではありますが『runtime.win10-x64.microsoft.netcore.universalwindowsplatform』関連の.dllで160程同じエラーが表示されています。

むろん、普通にdebug、releaseを行えばエラー、警告もなく正常に動作しています。
なにかdllを別に追加した覚えもありません。

なんとなく言っている意味は分かります。
「重複してdllが2つある上に内容が違うからどっちか消してね」ということでしょう。
問題はこの2つ、より詳しくいうなら『C:\Users\user.nuget\packages』自体がパッケージ作成の際に”自動生成”されているということ。
プロジェクトを確認してみますが、重複して参照しているようなファイルは見つかりません。

いや、もうさっぱり原因がわかりませんでした。
ちなみVisual Studio 2019を使用して開発しています。

とりあえずググって解決方法を探ってみる

そもそもUWPの情報自体が少ないなかで解決方法が見つかるのか…。
調べてみたところ、
ペイロードに含まれている複数のファイルで同じターゲット パス '〇〇〇' が指定されていますが、サイズが異なります。』というエラーが表示された
という人がちらほら…。
最後の文言は違いますが、おそらく根本的な原因は同じでしょう。

解決方法を載せている方もいたので試してみます。

--①--
まず一旦ソリューションを閉じて手動でbin、obj、AppPackageフォルダを削除。
その後、改めてソリューションを開いて、パッケージ作成を行う。

結果
ダメでした。
ソリューションを開いてビルドはさんでからパッケージ作成、ソリューションを開いたままにして不要フォルダを削除して…などいろいろ試してみましたが全部ダメ。
相変わらずエラーが表示されます。

--②--
エラーが発生しているプロジェクトを選択し、プロパティを表示し、ビルドペインに移動。
『.NET ネイティブツールチェーンでコンパイルする』からチェックを外す。
というもの

結果
これもダメでした。
そもそもストア用のパッケージを作成する際にはこの項目は自動的にチェックされるっぽい。
自動的に…というとこから何かこれが原因っぽく感じてしまいますが、やっぱりこれでもないっぽい。

--③--
複数プロジェクトの中で重複したファイルが存在する場合、そのファイルを削除する。

結果
そもそも1つのプロジェクトしかないのでこれもダメ

お手上げかとおもいきや…

もうお手上げ状態です。
もしやVisual Studio 2019だからダメなのか?
と、思い一度Visual Studio 2019で新規UWPプロジェクトを作成、まっさらな状態でパッケージ作成を試してみることに。
…そうするとエラーが出ませんでした。
パッケージ作成が成功しています。

ということは作成している現在のプロジェクトの何かがおかしいということ…。

………。

xaml.cs、.csのファイルだけ新規UWPプロジェクトにお引越しさせてパッケージの作成をしてみました。
するとエラーも出ずにいけた!

これで作成したパッケージをMicrosoftストアに提出、無事に審査も通りストアに公開されました。
自分の別PCにDL、インストールしてみても正常に動作しています。
解決…したけど何かもやもやが残る結果となってしまいました。

締め

根本原因も何もわかりませんでしたが、解決方法としては

新規UWPプロジェクト作成

旧プロジェクトからソースのみを『追加→既存の項目』でお引越し

パッケージ作成

です。
でもこのエラーが出るたびプロジェクトを作りなおすのは手もだし…何より危険な香りが…。
なんとか、原因を探りたいところ…。

原因について何か御存知の方がいらっしゃいましたら是非コメントをいただきたいです。

またMicrosoftストアに公開している『TRPGシナリオ管理』というアプリがあります。
タイトル通りのアプリですが、TRPGを知らない人でも一度ご覧になってご感想等いただければと思います(_ _)

hanaonieruni.hatenablog.com

【C#】で行うDB接続とクエリ文の実行、値の受け取り

C#でDBへ接続して値を保存したり受け取ったり検証したり…。
プロジェクトの始めに用意するものの一つなので備忘録的に残して置こうかと思います。

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

namespace DBconnectTest
{
    class DBconnect
    {
        /// <summary>
        /// 接続情報
        /// </summary>
        static public string GetConnectionData()
        {
            return @"Data Source=localuser\test;"
                    + @"Integrated Security=false;"
                    + @"User ID=sa;"
                    + @"Password=123456"
                    + @"Initial Catalog=Test";
        }
        /// <summary>
        /// クエリ文を渡してDB処理開始
        /// </summary>
        static public SqlDataReader executeQueryToDataReader(SqlConnection connection, String sql, Dictionary<String, String> param = null)
        {
            SqlDataReader reader = null;

            try
            {
                // データベースの接続開始
                connection.Open();
                using (SqlCommand command = new SqlCommand() { CommandText = sql, Connection = connection })
                {
                    try
                    {
                        if (param != null)
                        {
                            foreach (string Key in param.Keys)
                            {
                                command.Parameters.AddWithValue(Key, param[Key]);
                            }
                        }

                        // SQLの実行
                        reader = command.ExecuteReader();
                    }
                    catch
                    {
                        throw;
                    }
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.Message);
                throw;
            }

            return reader;
        }
    }
}

簡単に解説

public string GetConnectionData 関数内の接続情報に関しては各々使用しているDBの情報へ下記直す必要がある。

Data Source=サーバー名
Integrated Security=Windowsアカウントの資格情報を使用するかどうか
User ID=ユーザーID
Password=パスワード
Initial Catalog=DB名

なお、今回は接続情報をべた書きしている。
個人開発でなら何も問題ないが、app.configから取得するようにした方がスマートなソースになる。
もし要望があればそちらの書き方も別途追記する…かもしれない。

public SqlDataReader executeQueryToDataReader 関数に関してはコピペで問題ない。
ただ、新規プロジェクトではSqlDataReaderを使う際、
パッケージSystem.Data.SqlClientのインストールが必要になる。
(インストールしていない場合下記画像のようにエラーがでる)
visual studio 2019を使用しているならSqlDataReaderを記載したあと、SqlDataReaderにカーソルを合わせて

"System.Data.SqlClient"のインストール→最新バージョンの検索とインストール→変更のプレビューをクリック→適用をクリック

することでSystem.Data.SqlClientがインストールされ、エラーが消える。

実装例

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

namespace CheckSQL
{
    class Program
    {
        static void Main(string[] args)
        {
            // DB接続
            string connectionString = DBconnect.GetConnectionString();
            StringBuilder sql = new StringBuilder();

            // クエリ文実行
            sql.AppendLine("SELECT * FROM M_手順");
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                SqlDataReader data = DBconnect.executeQueryToDataReader(connection, sql.ToString());

                if (data.HasRows)
                {
                    // 行数分ループ
                    while (data.Read())
                    {
                        // 列数分繰り返し
                        for (int i = 0; i < data.FieldCount; i++)
                        {
                            Console.Write(data[i] + ":");
                        }
                        Console.Write("\n");
                    }
                }
            }
        }
    }
}

実際に動作させます。

しっかりと指定したテーブルの内容が出力出来ました。

締め

DBの接続自体は簡単にできますが、問題はクエリ文ですよね…。 思うように出力した値が探しだせず悶々とする日々です。 joinとかは今でも正直ニュアンスで使っています。

なにか質問等々ございましたらコメントいただければと思います。

続き↓保存とか更新とか削除とか hanaonieruni.hatenablog.com

【C#】コピペで出来る!ログ出力実装

あらゆるアプリ必ず実装すべきログ出力処理。
とりあえず下記をコピペすれば、すぐ実装できます。

using System;
using System.Text;
using System.IO;

namespace LogProcess
{
    class LogWriter
    {
        static private string mBaseDebugLogFileName;
        static private bool mIsInitialize = false;
        static private string mLogPath;
        static private StringBuilder mInitErrLog;
        static private string mLogRotateFileName = "Log.rotate";

        static private readonly object mSyncObject = new object();
        /// <summary>
        /// 初期化
        /// </summary>
        /// <param name="path">ログファイルを保存するディレクトリまでの絶対path</param>
        /// <param name="baseDebugLogName">ログファイル名</param>
        /// <returns></returns>
        static public bool initialize(string path, string baseDebugLogName)
        {
            clear();

            if (setLogPath(path, baseDebugLogName) == false) return false;

            mIsInitialize = true;
            logRotate();

            return true;
        }
        /// <summary>
        /// classで保有しているログ情報初期化
        /// </summary>
        static public void clear()
        {
            mLogPath = null;
            mBaseDebugLogFileName = null;
            mInitErrLog = new StringBuilder();
        }
        /// <summary>
        /// ログファイルpath設定
        /// </summary>
        /// <param name="path"></param>
        /// <param name="baseDebugLogName"></param>
        /// <returns></returns>
        static public bool setLogPath(string path, string baseDebugLogName)
        {
            isLogPath(path);

            if (isLogBaseName(baseDebugLogName) == false) return false;

            mLogPath = path;
            mBaseDebugLogFileName = baseDebugLogName;
            return true;
        }
        /// <summary>
        /// ログの書き込み
        /// </summary>
        /// <param name="inFormat"></param>
        /// <param name="inArgs">可変長引数</param>
        static public void writeDebugLog(string inFormat, params object[] inArgs)
        {
            if (mIsInitialize == false) return;

            lock (mSyncObject)
            {
                try
                {
                    DateTime now = DateTime.Now;
                    string theNow = now.Year.ToString("0000") + "/" + now.Month.ToString("00") + "/" + now.Day.ToString("00");

                    string theLogFileName = getDebugLogFileName(theNow);
                    string outLogString = createNowDateString(theNow);
                    outLogString += " " + inFormat;

                    using (StreamWriter writer = new StreamWriter(theLogFileName, true))
                    {
                        if (inArgs == null || inArgs.Length == 0)
                        {
                            writer.WriteLine(outLogString);
                        }
                        else
                        {
                            writer.WriteLine(outLogString, inArgs);
                        }
                    }
                }
                catch (Exception)
                {
                }
            }
        }
        /// <summary>
        /// 指定変数にログファイル名が入っているか確認
        /// </summary>
        /// <param name="inLogBaseName"></param>
        /// <returns></returns>
        static private bool isLogBaseName(string inLogBaseName)
        {
            if (inLogBaseName == null ||inLogBaseName.Length == 0)
            {   
                mIsInitialize = false;
                mInitErrLog.Append("Not specify the log file base name");
                return false;
            }
            return true;
        }
        /// <summary>
        /// 指定pathにディレクトリが存在しているか確認
        /// </summary>
        /// <param name="inLogPath"></param>
        /// <returns></returns>
        static private void isLogPath(string inLogPath)
        {
            bool isExist = Directory.Exists(inLogPath);

            if (isExist == false)
            {
                mInitErrLog.Append("Log folder does not exist. Create folder:" + inLogPath);
                Directory.CreateDirectory(inLogPath);
            }
        }
        /// <summary>
        /// 年月日に時分秒をくっつけた文字列を取得
        /// </summary>
        /// <param name="inDateTime"></param>
        /// <returns></returns>
        static string createNowDateString(string inDateTime)
        {
            string dateString = "[" + inDateTime + " " + DateTime.Now.ToString("HH:mm:ss:fff") + "] ";

            return dateString;
        }
        /// <summary>
        /// 年月日が該当するファイル名を取得
        /// </summary>
        /// <param name="inDateTime"></param>
        /// <returns></returns>
        static string getDebugLogFileName(string inDateTime)
        {
            string theFileNameDate = inDateTime.Replace("/", "");
            string theLogFileName = mLogPath + mBaseDebugLogFileName + "_" + theFileNameDate + ".log";

            return theLogFileName;
        }
        /// <summary>
        /// 過去ログデータの削除
        /// </summary>
        static private void logRotate()
        {
            string theNow = DateTime.Now.ToString("yyyyMMdd");
            // 作成指定日数を過ぎたら削除
            // 決めうちで30日前に設定
            string rotateDate = DateTime.Now.AddDays(-30).ToString("yyyyMMdd");

            // ログに関するローテーションファイルがあるならその処理を優先
            string logRotateFile = mLogPath + mLogRotateFileName;
            string nextRotateDate = DateTime.Now.AddDays(1).ToString("yyyyMMdd");

            if (true == File.Exists(logRotateFile))
            {
                if (string.Compare(theNow, File.ReadAllText(logRotateFile)) > 0)
                {
                    // delete rotate file
                    File.Delete(mLogRotateFileName);               
                    File.WriteAllText(mLogRotateFileName, nextRotateDate);

                    // ログ削除
                    runRotate(rotateDate);
                }
            }
            else
            {
                File.WriteAllText(mLogRotateFileName, nextRotateDate);

                // ログ削除
                runRotate(rotateDate);
            }
        }
        /// <summary>
        /// 過去ログデータの削除判定と実施
        /// </summary>
        /// <param name="inRotateDate"></param>
        static private void runRotate(string inRotateDate)
        {
            DirectoryInfo di = new DirectoryInfo(mLogPath);
            FileInfo[] fis = di.GetFiles(mBaseDebugLogFileName + "*");
            foreach (FileInfo fi in fis)
            {
                if (string.Compare(inRotateDate, fi.LastWriteTime.ToString("yyyyMMdd")) > 0)
                {
                    fi.Delete();
                }
            }
        }
    }
}

書き込みだけでなく初期化処理の際に、
指定日数を過ぎたら削除してくれる処理にもなっています。
ソースの中でも決め打ちで整数を入れてますが定数にして管理しやすくした方が良いでしょう。

実装例

実際に確認してみます。 Hello World!の文字の描画とログ処理が走るだけの簡単なプロジェクトを作成。

using System;

namespace MainProcess
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            // ログ初期化
            LogProcess.LogWriter.initialize("./log/", "LogTest");

            //
            LogProcess.LogWriter.writeDebugLog(System.Reflection.MethodBase.GetCurrentMethod().Name + " " + "ログテストstart");
            LogProcess.LogWriter.writeDebugLog("");
            LogProcess.LogWriter.writeDebugLog("Version : 1.0.0");
            
        }
    }
}

動作確認

んで、LogProcess.LogWriter.initialize にて指定したpathに
同じく指定したタイトル + ”_年月日”で出力されているか確認。

出力フォルダ

問題なさそうです。
書き込まれているかも見てみます。

書き込み確認

しっかり出力されていますね。
出力されたログには指定テキストの頭に年月日と時分秒コンマが書き込まれるようにしています。

締め

ログ処理は開発時には、
特に個人で作っているとありがたみは感じずらいと思います(Debug.WriteLine入れれば済むから)
ただ公開、配布するのであれば必ず実装しましょう。
ユーザーが操作中に発生したバグが、
何故か自分のところでは再現出来ない…という事態が稀によくあるからです。
そんな時、ユーザーのログファイルをもしかしたらそのバグの原因が分かるかもしれません。
まぁ、適切な場所にLogProcess.LogWriter.writeDebugLogを配置出来ていればの話なんですが…orz

なにか質問等々ございましたらコメントいただければと思います。

【自作アプリ】TRPGシナリオ管理 操作説明

積み重なっていくシナリオを
ジャンルや人数、プレイ時間や技能、その他メモと一緒に一括管理!

Microsoftストアで公開中のアプリ「TRPGシナリオ管理」の操作説明ページ
初めてUWPで作成したもので、そこで得た開発知識もおいおい紹介できればと思います。
無料DLはこちらから TRPGシナリオ管理 を入手 - Microsoft Store ja-JP

メイン画面

管理しているシナリオを確認・検索できる画面です。

f:id:HanaoniEruni:20220304121535p:plain

①シナリオ一覧の並び替え・絞り込みを行うことができます。
 ー並び替え(以下項目の昇順降順)
  登録順
  シナリオ名
  ジャンル
  評価
 ージャンル
  シナリオ詳細に記載したジャンルが自動的に追加されていき、その中から選択。
 ープレイ人数
  0~9人以上で選択。
 ーフリー検索
  検索したい単語を入力後、Enterを押下することでタグを追加することができます。(最大4つ)
 ー検索実行
  クリックすることで、並び替え~フリー検索で指定した項目をもとに並び替え・絞り込みを実行します。
 ー検索クリア
  クリックすることで、並び替え~フリー検索で指定した項目を初期状態に戻します。並び替え・絞り込みを実行はされません。

②管理するシナリオの追加、オプション
 ーシナリオ追加
  管理するシナリオを新規追加するには が表示されます。
 ーオプション
  オプション が表示されます。

③シナリオ一覧が表示されます。
 一覧の中から確認したいシナリオをクリックすることで シナリオ詳細画面 が表示されます。

シナリオ詳細画面

シナリオの情報を確認・編集することができます。

f:id:HanaoniEruni:20220304144756p:plain

①サムネイルの設定ができます。
 クリックすることでPCに保存されている画像をサムネイルとして設定できます。
②シナリオの詳細を記載できます。
 ーシナリオデータpath/URL
  「参照」ボタンからPC内にある.pdfや.txt、.docなどの文章データ、もしくはシナリオが置いてあるpixivやbooth(例:https://booth.pm/zh-tw/items/3438904 )等のURLを設定できます。
  設定したデータ、URLは③の「シナリオデータを開く」をクリックすることで開くことができます。
  設定されたデータはアプリのローカルフォルダーに保存されます。
 ーシナリオ名
  管理するシナリオ名を記載できます。
 ージャンル名
  シナリオのジャンルを記載できます。
 ー推奨人数
  シナリオで推奨されているプレイ人数を0~9人以上の間で設定できます。
 ー評価
  ☆をクリックすることでシナリオを5段階で評価できます。
 ータグ設定
  単語を入力後、Enterを押下することでプレイ時間や推奨技能など、自由にタグ付けすることができます。(最大6つ)
 ー自由欄
  プレイした人の名前やKPのメモ帳として記載できます。(最大1000文字)
③保存・削除・シナリオを開く
 ー保存
  変更した項目の保存ができます。シナリオ一覧から表示した際は上書き保存に、シナリオ追加から表示した際は新規保存となります。
 ーシナリオを開く
  シナリオデータpath/URL で設定したデータを開くことができます。
 ー削除
  保存されたデータを一覧から削除することができます。

オプション

アプリのサイズやキャッシュの削除ができます。

f:id:HanaoniEruni:20220304162756p:plain

①起動時アプリサイズの変更
 選択サイズでアプリが起動するようになります。
 変更はアプリを再起動することで適用されます。
②キャッシュのクリア  アプリのローカルフォルダーに残っているキャッシュを削除します。

管理するシナリオの新規追加

メイン画面で「シナリオ追加」をクリックすることで初期状態の詳細画面が表示されます。

f:id:HanaoniEruni:20220304165650p:plain

シナリオ名さえ記載すれば保存が可能です。
「シナリオ追加」から表示した詳細画面での保存はすべて新規保存となります。
追加できるシナリオは全部で60個です。

サポート情報とプライバリーポリシー

何か不明点等々あればコメント、もしくはTwitterの方にお願いします。
UWPやC#の処理に関することでもお気軽にご連絡いただければと思います。

またこちらのアプリケーションは、個人情報を収集したり公開しません。

締め

以上、「TRPGシナリオ管理」の操作説明でした。

また今回作成とそのテストに『ねずみと書庫』の二岡様にシナリオやそのサムネイルをお借りいたしました
下記にBoothのURLを張っておきます。
ねずみと書庫 - BOOTH
素敵なシナリオばかりなので是非ごらんください。

【当たり判定】弾丸やボール等が速すぎて当たり判定をすり抜けてしまう場合の解決方法

弾丸やボールが目標に当たった瞬間に何かしらの処理を実行したい。(体力低下やスコア加算等)
だけど弾丸やボール等のスピードが速すぎて的をすり抜けてしまう…。
何故なんだ…orz
理由は簡単、スピードが速すぎると1F(フレーム)に動く距離が長くなるからです。

f:id:HanaoniEruni:20181211205326g:plain
図1:物体の速度によっては当たらない

このように1Fに動く距離が長くなると的を通り過ぎてしまう、つまりすり抜ける。

なら1F内で少しずつ当たり判定を繰り返してあげれば良い

void main()
{
    vector3 ballPos{0,0,0}; // ボールの初期位置は{0,0,0}と仮定
    float  Len = 10;     // まず1フレームで進む距離を取得(10と仮定)
    int    num = 10;       // 何回に分けて判定するか
    bool isHit = false;

    for( int i = 0; i < num; i++ )
    {
        // ここでは10回に分けているので1/10ずつボールを動かす
        ballPos.x += Len / num;
        
        // 動かすたびに当たり判定を行う
        // (ここでは当たり判定の関数に通している)
        if( CheckHit(ballPos) )
        {
           isHIt = true;   
         break;
        }
    }
}
bool ChackHit(vector3 pos)
{
  vector3 EnemyPos {8,0,0};  // 敵(的)の位置は{8,0,0}と仮定
    // ここでは敵と重なったら当たったとする
    if( EnemyPos == pos )
    {
      return true;
    }
}

ballPosは1フレームでLen(上記では10)ずつ動く。
Lenをnumで割った分をnumだけ動かしていく(上記では1/10ずつ動かすという処理を10回行っている)。
そのたびに当たり判定を通して当たったら処理から抜ける。
もし1/10でもすり抜けるなら、numの値を大きくしてさらに細かく動かして判定を取れば、速度が速すぎてすり抜けるということはなくなると思います。(ただしその分重くなる可能性はもちろんある)

締め

考え方はどの言語でも共通です。

上のソースは説明用のものなのでコピペしてもエラーを吐きます。ご注意を。
なにか質問等々ございましたらコメントいただければと思います。