アイテム、インベントリ、物々交換

はじめに

これは 趣 Advent Calendar 2025 の10日目の記事です。昨日は enzirionさんの「12/9 × 趣」 に対する俺の答えはこれやでした。

見えないものを、 見えるものより信じ込んでしまう。 そんな、 バグみたいな機能のせいで。ただの石ころに、物語がこびりついて、データはどうしようもなく重たくなる。重いから、手渡すしかなかった。その摩擦で、私たちは引力を知った。 他人同士がぶつかり合って、熱を持った。そうやって、私たちは世界を作った。

この記事には、この記事だけの意味で使っている言葉があります。

  • pf = platform の略
    • 「アイテムからみた外側の世界」程度の意味で使っています
    • 大規模な課金経済圏を持つゲームというよりは、個人開発のゲームやワールド、小規模なチャットアプリ、TRPGツールなどがいくつもある状態を思い浮かべてください
  • ワールド = pf の管理下にある3D空間
  • アイテム = UI, アバター, ワールド以外のもの
    • 手に持てる・身に着けられる・持ち歩ける程度の大きさのものを想像してください

やろうとしていること

バーチャル空間上での「所有」を実現して、 私たちがバーチャル空間上に実在するための段階を踏もうとしています。そのために、アイテムが pf を越えて

  • ポータビリティをもち
  • 永続性をもち
  • 贈与・交換できる

という仕組みをつくろうとしています。

やらないこと

  • 個数がある・消費するようなアイテムはこの記事では設計しません
    • ゲームというよりはワールドやツールでアイテムを持ちまわりたいので、無くてもよさそうです
    • いざ必要となったら実装できるとは思います
  • お金は実装しません
    • お金をアイテムとして扱うこともしません
    • 売買というよりも、温かみのある手渡しの体験に寄せたいので、やらないことにしました
  • アイテムの複製は実装しません
    • ほぼコストゼロで無限に複製しうる物は、手渡しの温かみが減ると思うので、複製は実装しません
    • ただし、いわゆる「設計図」的なアイテムは所持できてOK、pf側で複製するのもOKということにします
  • メールボックスのような非同期なメッセージングや、全ユーザーに一斉配布するような実装はしません
    • これも、手渡しの体験を重視するため、意図的にサポートしないことにします

関連する規格や取り組み

VRM

人型のキャラクター・アバターを取り扱うための、クロスプラットフォームglTF ベース のフォーマットです。自分のアバターVRMで取り扱うことで、いろいろなアプリケーションやゲームで相互に行き来できるようになりました。

OMI (Open Metaverse Interoperability Group) 

OMI は国際的な標準化団体で、glTF拡張の議論を進めています。

PITM (Programmable Item Template Model; ぴーあいてぃーえむ)

PITM は cluster がクラフトアイテムやアクセサリーの3Dモデルや振る舞いの表現に用いるデータ形式です。PITMは、インタラクティブ3DオブジェクトをOSや同期システムの詳細に依存せずに表現することを目指して開発されています。glTFの3Dモデル表現に、cluster内でのアイテムとしてのメタデータや Cluster Script が付加されています。

Loot (for Adventurers) 

Loot は画像データを持たず、「Katana」という文字情報だけをブロックチェーンに刻んだNFTプロジェクトです。「見た目がないからこそ、みんなが自由に想像(実装)できる」という逆転の発想は、今回の「Payload(見た目)を棄却してもいい」という設計に似ています。

ただし、Lootが投機やマーケットを前提にしているのに対し、この記事では「友達への手渡し」を前提にしている点が大きな違いです。お金の匂いを消して、贈与の仕組みを目指しています。

VCI (Virtual Cast Interactive)

アイテムにスクリプト(振る舞い)を埋め込む規格として VCI があります。Lua が書けて非常に強力な、Virtual Castで使うためのアイテムの形式です。

概要

  • 2つが両立できそうなアイテムのフォーマットを考えます
    • pf 側が受け入れ・拒否しやすい
    • リッチな見た目を持たせられる
  • アイテムの永続化
    • pf とは別の、アイテムを永続化するためのサーバーを作ります
  • 贈与・交換の実装
    • 手渡しできる仕組みを作ります

アイテムのポータビリティ

アイテムを自由に持ち運ぶことを考える時に、まずはじめに思いつくのがアイテム自体の問題です。アイテムを別の pf に持っていけないのは、アイテム自体に2つの原因がありそうです。

  • ゲームエンジン間の相互運用性
    • Unityで作られた剣をUnreal Engineのゲームに持ち込めるでしょうか?
    • 物理演算の係数が違ったらどうしますか?
  • 世界観やゲームバランスの壁
    • 中世ヨーロッパ風の没入感ある世界に「 #ff00ff の看板」を持ち込むことは可能でしょうか?
    • pf が持ち込みを拒否するのは、正当な防衛権だと思います

pf 側が持ち込みを許可・拒否しやすいためのアイテムのフォーマットがあると良さそうです。

{
  "name": "...",

  // Semantics ( pf への「お願い」)
  "semantics": {
    "properties": ["sword", "long"],
    "materials": ["blue", "glowing"],
    "behaviours": ["draw", "swing", "sheathe"]
  },

  // Payload (アイテムの「実体」)
  "payload": [
  {
        "type": "unity_asset_bundle",

        "fallback_required": true, // これがあるなら、下の safe も必須

        "data": ...
    },
    {
        "type": "glb",

        "risk_level": "safe",
        "data": ...
    }]
}

Payload(実体)

 ここにはクリエイターが作ったオリジナルのアセットを、バイト列としてそのまま詰め込むことにします。 これを使えば、対応している pf では「作者が意図した通りの見た目」を再現できそうです。

Semantics(意味)

もし pf 側が「重いデータは禁止」あるいは「世界観に合わない」と判断したら、 Payload は無視してよいことにします。 代わりに semantics(剣、長い、光る)が読み取られ、pf 固有の代替モデルに置換されます。

アイテムの永続性

pf の寿命 >= アイテムの寿命

ということは、 所有権が pf 側にあるということで、使用権をユーザーがもらった・買った状態であり、長い長いレンタルをしているに過ぎません。

だから pf とは独立した個人のインベントリにアイテムが残り続けることは、所有の実現に必要で、まずはサーバーを実装するのがよさそうです。

管理者が「私」に変わっただけで、「私」がサーバー代を払えなくなったら消えてしまう、というのはその通りです。本当はP2Pにできればよいのですが、サーバーに比べてセキュリティや交換の実装の難易度が高いので、今はまだ諦めることにします。

ユーザーインベントリ (User Inventory)

ユーザーが自分の持ち物を確認するAPIです。

  • インベントリ取得
    • GET /api/v1/users/me/inventory?semantics=sword&limit=20
  • アイテム詳細取得
    • GET /api/v1/items/{item_id}

ワールドインベントリ (World Drop List)

pf もこの仕組み上にインベントリを持てる、ということにして、「誰のものでもないアイテム」を表現します。ワールドインベントリは2つでできていて

  • REST API
    • アイテムを落とす・拾う
    • この後の「贈与・交換」のところで書きます
  • gRPC
    • 誰かがアイテムを置いた・拾った
    • ユーザー自身が移動して、アイテムの表示範囲に入った・出た

を提供します。

アイテムの贈与・交換

pf における交換は、主にマーケットプレイスやショップを通じた売買で、ほぼすべてのアイテムに客観的な定価がついています。 定価の世界では、腐りかけたリンゴは「価値ゼロのゴミ」として処理されるでしょう。

でも、アイテム同士の直接交換ならどうでしょうか。 手元の鞄から相手の鞄へ、直接データを手渡す。そこには「定価」は存在せず、当事者同士の合意だけがあります。今にも餓死しそうなプレイヤーにとって、あなたの持っている腐りかけのリンゴは、新品のダイヤよりも価値があるかもしれません。

Handover: 「手渡し」という儀式の実装

以前の私は、交換のために「取引IDを発行して、相手が承認して…」という複雑な手続きを考えていました。しかし、それでは「手渡し」の温かみは生まれません。

そこで「カプセル」という仕組みを導入します。

どのようなアイテムであれ、ワールドに出すときは一度、システム共通のカプセルに梱包されます。

  1. Pack (包む)
    • 渡したいアイテムを選び、パスワード付きの「トークン(QRコードや3Dの箱)」を生成します。この時点でアイテムはインベントリから消え、宙に浮いた状態になります。
  2. Drop (置く/渡す)
    • そのトークンを、VR空間上で直接相手に手渡すか、地面に置きます。
  3. Unpack (開ける)
    • 受け取りてがカプセルを拾い、「開ける(Unpack)」操作をした瞬間、初めて中身のデータ(Payload)がダウンロードされます。 ここで「中身を受け取るか?」の最終確認が行われ、Yesなら所有権が移動します。

これは重いデータ対策にもなっていて、いきなりリッチな剣を表示するのではなく、「箱」を表示することで、箱が開くアニメーションの間に DL して、ロードの遅延を誤魔化すこともできそうです。これで技術的な無理がなくなり、かつ「バーチャルならではの身体性(カプセルを投げる)」という新しい面白さが加わります。

Pack についてもう少し

Packする段階で、recipient_id(受取人のID)を指定できるようにします。

  • ID指定なし
    •  誰でも開けられるカプセルです
  • ID指定あり
    • 指定されたユーザーしか開けられないカプセルです
    • 他のユーザーが拾って開けようとしても、「鍵がかかっています」と表示されて弾かれます

これにより、「人混みの中で友人にアイテムを投げ渡しても、横から知らない人に盗まれる」という事故(横取り)を防げます。

PFへの「贈与」API

World Drop/Pickup API(ワールドへの配置・回収)

手渡しと同じ手順で「カプセル」を生成し、それを拾わずにその場に放置すれば、それがそのまま「ドロップアイテム」になります。

  • 捨てる
    • Packして、生成されたカプセルを地面に転がしておく
    • POST /api/v1/capsules
  • 拾う
    • 落ちているカプセルを見つけたら、 Unpack する
    • POST /api/v1/capsules/{capsure_id}/unpack

ワールド側は、放置されたカプセルを「誰でも拾える設定」にするか、あるいは一定時間後に「ガベージコレクション(掃除)」して消滅させるかを管理するだけです。

System Contribution API(システムへの譲渡)

特定のNPCへのプレゼント、イベントアイテムの納品、あるいはゴミ箱への廃棄。 これらは「アイテムを特定の機能に渡す」行為です。

ここでも、ユーザー間の交換と同じく「宛名書き」が重要になります。 単にその座標に投げ込むだけでは、横から他のプレイヤーに奪われたり、誤って隣の「寄付箱」に入ってしまったりする事故が起きるからです。

システムへの譲渡は、以下の手順で行います。

  1. ターゲットの特定 
    • ユーザーが視線を向けたり、インタラクトしたりすることで、対象のシステムIDを取得します
    • e.g. sys_trash_bin_01(ゴミ箱), sys_npc_merchant_A (ショップ店員)
  2. 宛名付きカプセルの生成
    • アイテムをカプセル化する際、取得したシステムIDを recipient_id に指定します
    • これにより、このカプセルは「そのゴミ箱(あるいはNPC)専用」になります。他のプレイヤーが空中でキャッチしても、開けることはできません

落とし穴

  • 意味論の解釈依存性
    • type: sword, damage: 100 と定義しても、受け入れ先のワールドにおける「100ダメージ」が即死級なのか、蚊に刺された程度なのかは定義できません
    • 「数値の意味は受け入れ側の解釈に委ねる」を仕様としていて、厳密なバランス調整は放棄し、あくまで「名目上のパラメータ」を受け渡すことに留めます
  • スケーラビリティの物理的制約
    • すべてのアイテム実体(Payload)は管理者のサーバーに保存されます
    • 3Dデータは容量が大きく、ユーザー数に比例してストレージ費用が増大するため、個人運営のまま大規模に公開することは経済的に難しいです
    • これは将来的にIPFSなどの分散ストレージへ移行し、コストをネットワーク全体で負担できるようになるはず、と思っています

セキュリティ

他ユーザーの client 上で描画している以上は完全にリッピングを防ぐことはできませんが、次のようなリッピング対策はできると思っています。

  • サーバーサイドで走査して検知する
    • geometry hash
    • perceptual hash
  • 電子透かしを入れておく

セキュリティと「表現の自由」のジレンマ

HLSL(カスタムシェーダー)を含むアセットの同梱を「 Safety Fallback を含める」条件をつけることで、可能にしたいと思っています。 Payload にリッチなアセット(AssetBundle等)を含める場合、同時に「安全で軽量な代替表現」(glTFの標準マテリアルや、単なるアイコン画像など)も定義しなければなりません。

コンテンツの中立性と検閲責任

サーバー管理者は、ホストされている Payload の内容に法的責任を負う可能性があります。例えば、著作権侵害データや違法コンテンツがアップロードされた場合、管理者が削除・検閲する必要が出てきます。これは「自由なインベントリ」を謳いつつ、管理者が検閲権限を持つ矛盾があります。

利用規約ToS)により、違法データのホスティング拒否権を明記し、完全な検閲耐性は諦めようと思っています。

負荷・コスト

  • 描画するユーザー側で描画する・しないを選べるようにするとよさそうです
  • アイテムのアップロード時にファイルサイズ制限をかけようと思っています

あとがき

いわゆるメタバースにいても、バーチャルの世界なんてものは、まだ本当には存在していないような感覚があって、足りないものは何だろうとずっと考えていました。そして、現実世界が今の形になるまでの過程を、バーチャルの世界で真似てみるのは面白いかもしれない、と思うようになってこの記事を書きました。

ここまで読んでくれてありがとうございます。いつか、街と街を隔てる透明な壁が溶けて、私たちが本当の意味で自由な旅人になれたら。 そのとき、あなたの鞄の奥底に「愛すべきガラクタ」が、ひとつでも残っていたら素敵だと思います。