地上の洞窟

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

サイト案内

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


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


更新履歴


2026/08/16

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

前提

ネットを見ていると、見知らぬ言葉がノイズのように流れてくることがある。
例えばそれは、ドパガキ、フレネミー、ショートスリーパー。
皆々悩みが多いのだろう。
私に言わせれば関係の無いノイズで、くだらないトレンドでもある。

おっと付け加えて言うなら、私に悩みが少ないわけではなく、皆々と違うことでマウントを取りたいという話ではない。
むしろ悩み疲れ切っているからロクに集中できないし、よくも眠れない、コミュ障だ元より。
揶揄される側には違いない。
しかしそれらの言葉は私の悩みを解決する言葉にはなり得ない。

しばしば「問題」と「悩み」は異なる。
これらの言葉は物事にレッテルを張り、もぐらたたきのように問題を潰し、本質から目を背ける効果がある。
多くの人にとっての問題解決はそれでいい、何もしなくても問題というのは流れ消え去るものだから。
しかし、それでいいのかと思うことの解答には、これらの言葉では不十分だ。

解決のためにうんうん頭をひねっている内は問題としての処理だ。
だが何のためにやるのか、本当にこれでいいのか、どちらがいいのか。
これらも同じ問題のように見えるが、状態としては問題に真っすぐ向かえていない。
葛藤や逡巡に足踏みで行ったり来たりしている。

人はなぜ思い悩むのだろう。
一つ考えられるのはどちらでもいいからというのもある。
自動車であるならば、例えば似たような速さの車でも、姿形はまるで異なることもある。
十分に洗練されていれば、目的は果たせる、もう是非では切れない。
そこまできたら、やるかやらないか、なのかもしれない。
良し悪しなんてのはそもそも、何のためにそうするのかというものでまるっきり変わってしまう。
スーパーカーでは砂利道を走りきれないかもしれないし、何より跳ねた石で傷が付けば、怒涛の勢いで価値が減っていくだろう。
そして何より、自身はスーパーカーにはなれない以上、自分にできることをやるしかない。

話が逸れてしまった。
今の問題と悩みの話は、それらに向き合うものの話であると言える。
では冒頭で挙げた言葉は何だろうか、問題を潰して向き合わないための言葉のように私は思う。
そしてこれらの言葉通りに何かをやることは蛇足になると私は思う。

なぜなら、前提が間違っているからだ。
例えばそもそも、ドパガキにならない人はいるのか?
老害にならない人はいるのか?
ちょっと寝不足になれば人は愚かになるのに、そうやって揶揄することは適切だろうか。

承認欲求といった、おおよそ否定的に用いられる言葉一つであっても
それを持たない人間はいないし、持たない人間は利他的であるかという疑問もある。
つまり他人の承認欲求が目に付くということはその度合いの問題であるとか、
単にその行動が自分のためにならないと考えているということであり、
なぜそう思うか、その理由や目的との違いを話せばいいだけの事を、人を責める言葉にしてしまうのは、勿体ない。

こういう人と関わらない方が良いなんて議題は常に流れている。
しかし、他人に向けるならば自分はどうであるか。
単にその人が嫌であるとか、あるいはその人を面白おかしく言うために
嫌な所探し名人みたいになっているのではないのか。
自分に向けたってどうしようもない、傷つくだけだ。
それぞれの人はそれぞれの人なりに洗練されている。
そうでないのなら、そのままに何のためにあるかを探すのが本筋だろう。
いちいち指摘したって生きづらいだけじゃないのか。

短く寝る事は、単に寝不足である。
…そもそもこの題の発祥を知らないので本来の言いたい事と違うかもしれないが。

まぁ私の言っていることも単なる連想ゲームの末端である。
単にこういうよく分からない人斬りの言葉が流れてくるのがイヤという話なのだから。
大概の問題は自身がどうこうできる範疇を超えているし
なんもかんも思うとおりにすることはどれほどのものかと思う今日この頃である。

【AviUtl ExEdit2 プラグイン】TextInput

TextInput
打ち込んだテキストを、そのまま追加!

AviUtl ExEdit2 で使用できる、テキスト入力プラグインです。
テキストボックスに入力した文字を直接テキストオブジェクトとして追加できます。
連続したテロップ追加の作業がちょっとだけ楽になるんじゃないかな。

ダウンロード

右のReleasesからダウンロードができます。

github.com

【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

昔のRubyには無い二分探索の自前実装です。
アルゴリズム確認用として。

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版はサイズが違うらしいが、ぶっちゃけよくわからない