ブラウザだけでリアルタイム3Dバー空間を作った話【Three.js + Socket.io】

「バーで見知らぬ人と話す」という体験をブラウザ上で再現できないか、ふと思い立ってしまった。インストール不要で、URLを送るだけで誰でも入れる3D交流空間を作りたかった。そんな衝動から始まったのが Avatar SNS というプロジェクトだ。

実際に動くものを公開しているので、まずは触ってみてほしい。

👉 https://avatarsns.site

できること

  • ユーザー名を入れるだけでログイン(登録不要)
  • アバターを作ってバーに入店
  • WASD / 矢印キーで移動、スペースでジャンプ
  • リアルタイムチャット(他のプレイヤーと会話)
  • バーテンダーに注文してドリンクを受け取る
  • 椅子に着席
  • ジュークボックスでBGMを変更(全員に同期)
  • スマホ対応(バーチャルD-pad付き)

技術スタック

フロントエンド: Three.js r160(ES modules + importmap)
リアルタイム通信: Socket.io 4.7
サーバー: Express.js + Node.js
セッション管理: express-session + lowdb
インフラ: さくらVPS(Rocky Linux)+ Nginx + PM2 + Let's Encrypt

外部UIフレームワークは一切使っていない。バーの内装・アバター・エフェクトはすべて Three.js のプリミティブと Canvas API で生成している。

アーキテクチャの概要

Browser (Three.js)
  ↕ WebSocket (Socket.io)
Express Server
  ├── /api/auth    セッション発行
  ├── /api/me      ログイン状態確認
  └── Socket.io    位置同期・チャット・ジュークボックス
  lowdb (db.json)  アバターデータ永続化

プレイヤーの位置は50msごとにサーバーへ送信し、サーバーが全員にブロードキャストする。O(n²) の構造なので、快適な同時接続はPC15人・スマホ8人程度が現実的な上限だ。

実装のポイント

アバターはすべてCanvas生成

3Dモデルファイルを一切使っていない。球体ジオメトリに Canvas で描画した顔テクスチャを貼ることで、ダウンロード0バイトのアバターを実現している。色・目のスタイルはユーザーが選択でき、サーバー経由で他プレイヤーに同期される。

バーの内装もすべてプリミティブ

床・壁・カウンター・椅子・ランプ・植物・絵画フレームまで、BoxGeometry / CylinderGeometry / ConeGeometry の組み合わせだけで作っている。GLTFモデルを使わないことでロードが速く、スマホでも動く。

スマホ対応

タッチデバイスを検出してバーチャルD-padを表示し、touchstart / touchend で keys オブジェクトを直接操作する。PCのキーボード処理をそのまま使えるので、ゲームループのコードを変更せずに対応できた。

const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

function bindMoveBtn(id, code) {
  const el = document.getElementById(id);
  el.addEventListener('touchstart', e => { e.preventDefault(); keys[code] = true; }, { passive: false });
  el.addEventListener('touchend',   e => { e.preventDefault(); keys[code] = false; }, { passive: false });
}
bindMoveBtn('mb-up', 'KeyW');
bindMoveBtn('mb-down', 'KeyS');

モバイルでは PixelRatio を1倍固定・PointLight省略・シャドウ無効化してパフォーマンスを確保している。

セッション管理のハマりどころ

Nginx リバースプロキシ越しで動かす場合、app.set(‘trust proxy’, 1) を忘れると Android Chrome でセッションクッキーが正しく扱われない。また sameSite: ‘lax’ の明示と req.session.save() の明示的な呼び出しも必要だった。

app.set('trust proxy', 1);

const sessionMiddleware = session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 7 * 24 * 60 * 60 * 1000, httpOnly: true, sameSite: 'lax', secure: false }
});

req.session.save((err) => {
  if (err) return res.status(500).json({ ok: false });
  res.json({ ok: true });
});

今後の展望

3Dはブラウザで動かすには端末負荷が高く、コンテンツの拡張も手間がかかる。次は 2Dピクセルアート × Canvas 2D API で交流型ゲーム空間を作り直す予定だ。インタラクションの豊かさという点では、Habbo Hotel や Club Penguin のような2Dの方が作りやすく、スマホでも快適に動く。

ぜひ avatarsns.site で遊んでみてください。