地上の洞窟

どこにも行かず、液晶と「にらめっこ」し続ける人の物語。

サイト案内

このブログでは、地上に住まう地底人のようなものが
RGSS3の素材を配布してたり、プログラムの話をしたりしています


このブログについて 「地上の洞窟」についてもう少し深く語っています。
RGSS3素材 「RPGツクールVXAce」で使えるRGSS3の素材を配布しています。
Compact Audio Player 小さな音楽プレーヤーを配布しています。
SplineEXP 経験値曲線を細かく調整して作れるツールを配布しています。


更新履歴


2026/02/21

2025/10/10

2025/08/08

2025/08/07

2025/05/27

2024/08/12

2024/08/07

2024/08/05

2024/05/19

2024/03/02

2024/01/23

【Ruby】二分探索 自作

class Array
  def bsearch(v)
    l = -1
    r = size
    while r - l > 1
      m = (l + r) / 2
      self[m] >= v ? r = m : l = m
    end
    self[r] == v ? self[r] : nil
  end

  def bsearch_index(v)
    l = -1
    r = size
    while r - l > 1
      m = (l + r) / 2
      self[m] < v ? l = m : r = m
    end
    r 
    # self[r] == v ? r : nil
  end

  def bsearch_insert(v)
    l = -1
    r = size
    while r - l > 1
      m = (l + r) / 2
      self[m] >= v ? r = m : l = m
    end
    insert(r, v) if self[r] != v
  end
  
  def bserach_delete(v)
    l = -1
    r = size
    self[m = (l + r) / 2] >= v ? r = m : l = m while r - l > 1
    self[r] == v ? delete_at(r) : nil
  end
end

古に作った物の備忘録です。
アルゴリズム確認用としてどうぞどうぞ。

SplineEXP

SplineEXP
凝った経験値曲線を簡単に作成

RPGなどで用いられる経験値テーブルを自在に作成するためのツールです。

  • レベル
  • そのレベルの辺りで、レベルアップに必要な経験値がどれぐらい増加しているか

これを設定していくだけです。

必要経験値を区間的に頭打ちになる部分を、齟齬なく滑らかに作ったりできます。たぶん。

動作環境

  • Windows 7以降(当方Windows 10で動作確認)
  • .NET Framework 4.7.2

本ソフトウェアの使用により発生した損害について、作者は一切の責任を負いかねます。
自己責任での使用をお願いします。

ダウンロード

github.com

あとがきのざつがき

ゲーム制作で使おうと思って、2-3日で突貫で作りもうした。
自分で使う前に「ええやんこれ!上げよ」と思って公開しているので、バグが無いかとか使いやすさについては謎です。
ただ、しっかりバージョニングしてアプデするようなものかも謎なので、不具合があっても更新するかは謎。
ていうか私よ、こんなことしてないでゲーム作れ、まじで。

まぁ経験値曲線って本当に作るのが大変。
シンプルな計算式だと、低レベル帯で上がりやすい~みたいのが、かなり作りにくくって。
上がりやすい区間と激キツな区間を、計算式を分けて調整したりすると、その間の補正はどうするのよ?って話になるしで。

3000必要かと思ったら次でいきなり5000とか、その段差を手で調整していたら、5000の次に必要な経験値が4500になったりして、「あれ、おかしいぞ」ってな。
100も段もあるものを、まるで想定も出来てない状態から調整する…なんて無鉄砲。
適当な計算式で妥協してゲーム制作を続ければいいのにね。
でもこんだけ考えだしたら止まらない。

複数の点を滑らかに移動するような軌道を計算するためのもの――
色々考えている内に、これ加減速の表現、イージング関数とかその辺に似ているな、と。
実際「合計経験値・次に必要な経験値・必要経験値の変動量」が「移動距離・速度・加速度」にそのまま変換できるっぽい感じで。
補間の技術とか、自分好みの加減速を作るとか、その辺は興味あったので、スプライン補間の技術を持ち込むことに。
よく分からないままPCHIPというアルゴリズムをコピペしたので、その内勉強したいですね。

今更ながら、Lvも変化量の操作も、スライダーっぽいのにすればよかったか…

追記: スライダー付けたり処理軽減したりバグ減らしたりしました。

探すということ

私たちはいつも、何かを探し求めている。
けれど、探すという行いを理によって主体的にするならば、それは大体「知っている」ことしか見つけられない、致命的な矛盾だ。
知りもしなければ、上手く行った試しもない中で、「見つける」「いいものを求める」なら。
探すことは時に間違いだ。

私の暮らしにいいものとして、例えばお金が欲しいとか、困った時に助けてくれる人がいるとか。
そういうものを打ち出して探してはみるものの。

そんなものは実際の自分が「見たこともない」し。
探し続けて、上手く行ったこともない。

結局「なんか上手く行かないかな~」ぐらいのノリで探し続けてる。
いつの間にか、探すことが目的になっている。
おいおい「見つける」ことが目的だったろうにって。
だから焦るし失敗し続けるしで、なんなら騙される。

別になんだかんだ探さなくたって、よくよく思い返せば。
なんかくれる人はいるし、なんか助けてくれる人はいる。
じゃあ探す方が、よっぽど遠ざかっているのである。

昔々、私が子供の頃の作文の一節に、こんなものがあった。
「素晴らしくて大切なものは、自分のそばにいつもいることを、大人になっても忘れないでいたいと思います。」
おうおうガキの頃の自分に往復ビンタされとるわい。
ばっちり忘れとるやないかい。
正直なんでこんなこと書いたのかさっぱり分からないけど、子供の頃の自分の方が余程人間が出来てるじゃねーか!!
と、今の体たらくを見て言いたくなったりならなかったり。

もちのろん、分かっていても、探すなと言われても、無理なものは無理だ。
人はいつもいつでも強いわけではないから。

それでも前に進むなら。
心が軽々と歩き続ける方を、一心不乱に見続けるしかない。
楽しいだけでも、すごいだけでも、案外ダメなんだ。

理屈で探せないなら、心と直感で探さなければならない。
心と直感が探したがらないのなら、今は探せない。

それは探しても無駄なのだという諦めのように感じるし。
一方で単に、探さなくてもいいという、前向きなことでもあることを、私は覚えておきたい。

【C#】ジャンプリストの作り方

ジャンプリスト
ジャンプリスト

タスクバーやスタートメニューに登録したアプリケーションを、右クリックした時に表示されるメニュー、それがジャンプリストである。
このジャンプリストの項目を、アプリケーション独自に変更する方法を紹介する。

概念

ジャンプリスト自体は、単にアプリケーションを起動する機能である。
各項目には、アプリケーションのパス、起動時引数が設定されている。
起動時引数は、そのアプリケーションに開かせたいファイルのパスや、アプリケーション固有の識別子(コマンド)が付けられたりする。

自分のアプリケーションの機能を呼び出したい場合は、以下のような仕組みを作る。

  • ジャンプリストの項目の起動時引数に、固有のコマンドを設定
  • 引数にコマンドが渡されているかを調べ、機能を実行する

既にアプリケーションが起動していて、そのプロセス上で機能を呼び出したいという場合。
例えば、ブラウザの「新しいタブを開く」「よく見るページを開く」といったもの。
この場合は、多重起動かを調べ、元プロセスにコマンドを送信する仕組みが必要になる。

実装例

ここでは、WindowsAPICodePackを使用したコード例を提示する。
必要があればNuGetで。

「タスク」に項目を追加

using Microsoft.WindowsAPICodePack.Taskbar;

// ジャンプリストを初期化
var jumpList = JumpList.CreateJumpList();

// 自分のアプリケーションのパスを調べる
var myAppPath = System.Reflection.Assembly.GetEntryAssembly().Location;

// [タスクA]
var taskA = new JumpListLink(myAppPath, "タスクA")
{
    Arguments = "/taskA"
};
            
// [タスクB]
var taskB = new JumpListLink(myAppPath, "タスクB")
{
    Arguments = "/taskB"
};

// 分割線
var separator = new JumpListSeparator();

// [タスクC]
var taskC = new JumpListLink(myAppPath, "タスクC")
{
    Arguments = "/taskC"
};

// ジャンプリストに項目を追加
jumpList.AddUserTasks(taskA);
jumpList.AddUserTasks(taskB);
jumpList.AddUserTasks(separator);
jumpList.AddUserTasks(taskC);

// ジャンプリストを更新
jumpList.Refresh();

「最近」に項目を追加

[System.Runtime.InteropServices.DllImport("shell32.dll")]
static extern void SHAddToRecentDocs(uint uFlags, string path);
private const uint SHARD_PATHW = 0x00000003;

public void AddRecentlyUsedFile(string path)
{
    SHAddToRecentDocs(SHARD_PATHW, path);
}

起動時引数の取得

ジャンプリストの項目に紐づけられた引数は、起動されるアプリの起動時引数に渡される。
そこでファイルパスだったり、固有のコマンドだったりの判別を行う。
起動時引数の取得は

  • Mainに引数を付けて受け取る
  • System.Environment.GetCommandLineArgsを使う

などがある。
以下の例を参考にしてほしい。

Mainに引数を付けて受け取る
static void Main(string[] args)
{
    // コマンドの判別と処理
    switch (args[0])
    {
        case "/taskA":
            break;
        case "/taskB":
            break;
        case "/taskC":
            break;
        default:
            break;
    }
}
System.Enviroment.GetCommandLineArgsを使う
// Environment.GetCommandLineArgs[0] は実行ファイルのパスが入っている
// 1番目以降が実際に渡された引数
var args = System.Environment.GetCommandLineArgs();

if (args.Length > 1)
{
    // コマンドの判別と処理
    switch (args[1])
    {
        case "/taskA":
            break;
        case "/taskB":
            break;
        case "/taskC":
            break;
        default:
            break;
    }
}

起動中のプロセスで機能を実行させる

既に起動しているアプリケーションで、ジャンプリストから機能を実行させる場合を考える。
この場合は、以下の手順を踏む。

  • 多重起動の検出
  • 元プロセスを探す
  • 元プロセスへのコマンド送信
  • 元プロセスでコマンドの受信
多重起動の検出
using System.Threading;

static void Main()
{
    using (var mutex = new Mutex(true, "ApplicationMutexName", out bool createdNew))
    {
        if (createdNew)
        {
            // 初回起動時
            // 通常通りアプリケーションを実行する処理
            Run();
        }
        else
        {
            // 多重起動時
            // ここで起動時引数にコマンドが渡されてるかを判別し、元プロセスに送信する
            return;
        }
    }
}

多重起動の検出には、Mutexを使う。
Mutexは一つのスレッドだけが利用できる個室のようなもので、元プロセスがこの部屋に入る。
同じ部屋を使おうとしても入れないので、自身が後のプロセスであると分かる、そんな感じ。

"ApplicationMutexName"は他のアプリと被らなさそうな固有の名前を付ける。

一連の操作が終わったら、通常起動時の処理は行わずにプログラムを終了させる。

元プロセスを探す
private Process FindPrevProcess()
{
    var currentProcess = Process.GetCurrentProcess();
    var currentPath = currentProcess.MainModule.FileName;
    var allProcesses = Process.GetProcessesByName(currentProcess.ProcessName);

    foreach (var process in allProcesses)
    {
        if (process.Id != currentProcess.Id)
        {
            try
            {
                if (string.Compare(process.MainModule.FileName, currentPath, true) == 0)
                {
                    return process;
                }
            }
            catch (Exception)
            {
                continue;
            }
        }
    }
    return null;
}

自プロセスと同じ、元プロセスを探す。

  1. 自プロセスのファイルパスを取得する
  2. 自プロセスと同じプロセス名のプロセス一覧を取得する
  3. プロセス一覧を調べ上げ、元プロセスを探す

元プロセスかの判定は以下の通り。

  • 自プロセスとは異なるプロセスIDである
  • 自プロセスと同じファイルパスのプロセスである

ファイル絡みでエラーが出たり出なかったりするので、例外処理を挟んでいる。

元プロセスへのコマンド送信
private const int WM_COPYDATA = 0x004A;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam);

[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT
{
    public IntPtr dwData;   // (IntPtr.Zero)
    public uint cbData;     // lpDataが指すデータのサイズ
    public string lpData;   // 渡されるデータ
}

public void SendTextToPrevProcess(string text)
{
    var prevProcess = FindPrevProcess();
    if (prevProcess != null)
    {
        var copyData = new COPYDATASTRUCT
        {
            dwData = IntPtr.Zero,
            cbData = (uint)Encoding.Default.GetByteCount(text) + 1,
            lpData = text
        };
        SendMessage(prevProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref copyData);
    }
}

元プロセスへのコマンド送信は、ウィンドウメッセージを使う。
この方法は単純に文字列を送る方法なので、ファイルパスとかも送れる。
前項の方法で元プロセスを探す。
見つけた元プロセスに、Win32APIのSendMessageで、WM_COPYDATAを送信する。
送信するデータは、COPYDATASTRUCT*1という構造体に納める。

元プロセスでコマンドの受信
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_COPYDATA)
    {
        // 構造体から文字列を取り出す
        var copyData = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam);
        
        // 受け取った文字列
        var text = copyData.lpData;
        
        // 以下、ここでコマンドを処理
    }
    base.WndProc(ref m);
}

受け取り側。
メインウィンドウでメッセージ処理を行う。
受け取ったメッセージにはCOPYDATASTRUCTのポインタが入っているので、それをもとに構造体を持ってきて、テキストを取り出す。

*1:64bit版はサイズが違うらしいが、ぶっちゃけよくわからない

【C#】多重起動の防止・元プロセスにファイルパスを渡す方法など

エクスプローラー上でファイルを開き、アプリケーションを起動する場合。
既に起動しているアプリケーションにファイルを渡したい時、どうしたらよいか。
主に、以下の手順を組むことになるだろう。

  1. 多重起動の検出
  2. 元プロセスを探す
  3. 元プロセスへファイルパスを送る
  4. 元プロセスでファイルパスを受け取る

これら各手順の方法をざっくり解説していこうと思う。

多重起動の検出

using System.Threading;

static void Main()
{
    using (var mutex = new Mutex(true, "ApplicationMutexName", out bool createdNew))
    {
        if (createdNew)
        {
            // 初回起動時
            // 通常通りアプリケーションを実行する処理
            Run();
        }
        else
        {
            // 多重起動時
            // ここで元プロセスにデータを送信する処理を入れたり、単に終了したりする
            return;
        }
    }
}

多重起動かどうかを判別するには、Mutexを使う。
Mutexは、排他制御、同期のための機構…らしい。
「マルチスレッドで、同じリソースを扱うけど、同時に処理されると困る…」
といった場合に用いられる、スレッドのロックの一種と言えば、分かりやすいだろうか?

性質は以下の通り。

  • 一スレッドだけが取得・所有できる
  • 固有の識別子を持たせて、他のアプリケーションからもアクセスできる
  • OSとかカーネルとかそういうレベルでの操作が入るので、速度は遅い

"ApplicationMutexName"はMutexを識別するための任意の文字列を自分で指定する。
他のアプリと重複すると困ったことになるので、被らなさそうなものをきちんと設定しよう。

元プロセスを探す

private Process FindPrevProcess()
{
    var currentProcess = Process.GetCurrentProcess();
    var currentPath = currentProcess.MainModule.FileName;
    var allProcesses = Process.GetProcessesByName(currentProcess.ProcessName);

    foreach (var process in allProcesses)
    {
        if (process.Id != currentProcess.Id)
        {
            try
            {
                if (string.Compare(process.MainModule.FileName, currentPath, true) == 0)
                {
                    return process;
                }
            }
            catch (Exception)
            {
                continue;
            }
        }
    }
    return null;
}

多重起動時、自プロセスと同じ、元プロセスを探す。

ロジックは以下の通り。

  1. 自プロセスのファイルパスを取得する
  2. 自プロセスと同じプロセス名のプロセス一覧を取得する
  3. プロセス一覧を調べ上げ、元プロセスを探す

元プロセスであるかの判定は以下の通り。

  • 自プロセスとは異なるプロセスIDである
  • 自プロセスと同じファイルパスのプロセスである

ファイル絡みでエラーが出たり出なかったりするので、一応例外処理を挟んでたり。

元プロセスへファイルパスを送る

private const int WM_COPYDATA = 0x004A;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam);

[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT
{
    public IntPtr dwData;   // (IntPtr.Zero)
    public uint cbData;     // lpDataが指すデータのサイズ
    public string lpData;   // 渡されるデータ
}

public static void SendTextToPrevProcess(string text)
{
    var prevProcess = FindPrevProcess();
    if (prevProcess != null)
    {
        var copyData = new COPYDATASTRUCT
        {
            dwData = IntPtr.Zero,
            cbData = (uint)Encoding.Default.GetByteCount(text) + 1,
            lpData = text
        };
        SendMessage(prevProcess.MainWindowHandle, WM_COPYDATA, IntPtr.Zero, ref copyData);
    }
}

ファイルパス、に限定されず、文字列データを送信する方法である。

元プロセスにファイルパスなどデータを送るのは、プロセス間通信…という考え方になってくる。
まぁ実際にはもっと簡単に考えて、ウィンドウメッセージのやり取りで送信ができる。

元プロセスに、Win32APIのSendMessage関数で、WM_COPYDATAメッセージを送信する。
送信するデータは、COPYDATASTRUCTという構造体に納めなければならない。
dwDataはIntPtr.Zeroでいいっぽい*1、cbDataは送信するデータのバイト数。
lpDataは本来、送信するデータのポインタらしいが、文字列はそのままでも良さそう。

元プロセスでファイルパスを受け取る

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_COPYDATA)
    {
        // 構造体から文字列を取り出す
        var copyData = Marshal.PtrToStructure<COPYDATASTRUCT>(m.LParam);
        
        // 受け取った文字列
        var text = copyData.lpData;
    }
    base.WndProc(ref m);
}

受け取り側、メインウィンドウのメッセージ処理。
送られてきたメッセージのデータはポインタである。
なのでポインタの先にある構造体を引っ張ってきて、実際の文字列を取り出す。

*1:32bit・64bitアプリケーションでサイズが変わるらしいので注意

【C#】エクスプローラーでファイルを開く際、アプリ側でのファイルパスの取得

エクスプローラー上で

  • アプリケーションに対してファイルをドラッグドロップ
  • ファイルを右クリック→[プログラムから開く]→アプリケーションを選択

などとして、アプリケーションを起動した際。
この時、渡されたファイルはどうやって取得するのか?

実践

渡されたファイルはアプリケーション起動時の引数になっている。
この引数から、渡されたファイルのパスを取り出すことが出来る。
起動時の引数の取得方法は、主に以下の二つ。

Mainに引数を付けて受け取る
// Mainで引数を受け取る方法
static void Main(string[] args)
{
    if (args.Length > 0)
    {
        var path = args[0];
    }
}
System.Environment.GetCommandLineArgsで取得
// 関数で起動時の引数を取得する方法
public string GetStartupFilePath()
{
    // アプリケーション起動時の引数を取得
    var args = System.Environment.GetCommandLineArgs();

    // 引数が2個以上あるなら、ファイルが渡されている
    if (args.Length > 1)
    {
        // 渡されたファイルパスを取得
        // Environment.GetCommandLineArgs[0] は実行ファイルのパスが入っている
        // 1番目以降が実際に渡されたパスになる
        var path = args[1];
        return path;
    }
    else
    {
        return null;
    }
}

今度、多重起動時に、既に起動しているプロセスにファイルパスを渡す方法を書こうと思います、たぶん。

書きました(`・ω・´)↓
under-overworld.hatenablog.jp