📘

151体のピクセルアートキャラクターを自律的に動かす — Identity Avatarアニメーションエンジンの設計

に公開

はじめに

プロフィールリンクサービス myna.me に「Identity Avatar」機能を実装しました。151体のピクセルアートキャラクターが、ユーザーのプロフィールページ上で自律的に歩き回り、投稿を吹き出しでつぶやき、クリックすると喜ぶ——そんなインタラクティブな仮想ペットシステムです。

この記事では、**10種の移動パターン(MoveType)× 8種の待機アニメーション(IdleAnim)**の組み合わせで151体全てに個性的な動きを与えるアニメーションエンジンの設計について解説します。

アーキテクチャ概観

アニメーションシステムは3層で構成されています。

PixelCharacter (React)         ← UIレイヤー
  状態管理 / ユーザーインタラクション

character-animation.ts          ← 設定レイヤー
  MoveConfig / IdleAnim CSS

character-sprites.ts            ← データレイヤー
  151体の定義 / MoveType・IdleAnim割り当て

MoveType — 10種の移動パターン

各キャラクターには1つのMoveTypeが割り当てられ、JavaScriptの行動ループで移動先の座標を決定します。

MoveType 動き 速度 代表キャラ
wander ランダム歩行 60px/s cat, corgi
patrol 左右往復 50px/s knight, samurai
orbit 楕円軌道 45px/s fairy, angel
bounce 大きいY振幅 70px/s frog, rabbit
dash 高速+長休憩 140px/s cheetah, falcon
float ゆっくり浮遊 30px/s ghost, jellyfish
zigzag 短距離ジグザグ 80px/s bee, butterfly
creep 超低速地面寄り 20px/s snail, tortoise
teleport フェード瞬間移動 200px/s alien, phoenix
hover 微小振動 15px/s owl, bat

MoveType設定の型定義

type MoveConfig = {
  speed: number;      // 移動速度 px/s
  idleMin: number;    // 最小待機時間 ms
  idleMax: number;    // 最大待機時間 ms
  pauseMin: number;   // 到着後の最小休憩 ms
  pauseMax: number;   // 到着後の最大休憩 ms
  walkCSS: string;    // 歩行時CSSアニメーション
  easing: string;     // position transitionのeasing
};

速度だけでなく待機時間の範囲を設定することで、同じMoveTypeでも毎回異なるタイミングで動く自然な挙動を実現しています。

移動先座標の決定ロジック

MoveTypeごとに次の移動先を決めます。

// wander: ハビタット内のランダムな位置
case "wander":
  return {
    x: Math.random() * (bounds.width - spriteSize),
    y: Math.random() * (bounds.height - spriteSize)
  };

// patrol: 現在位置の反対側へ水平移動
case "patrol":
  return {
    x: pos.x < bounds.width / 2
      ? bounds.width * 0.7 + Math.random() * bounds.width * 0.2
      : bounds.width * 0.1 + Math.random() * bounds.width * 0.2,
    y: pos.y + (Math.random() - 0.5) * 20
  };

IdleAnim — 8種の待機アニメーション

移動していない間のアニメーションはCSSキーフレームで実装しています。

const IDLE_ANIM_CSS: Record<IdleAnim, string> = {
  breathe: "idleBreathe 3s ease-in-out infinite",
  bob:     "idleBob 2.5s ease-in-out infinite",
  pulse:   "idlePulse 2.4s ease-in-out infinite",
  wobble:  "idleWobble 3.2s ease-in-out infinite",
  shimmer: "idleShimmer 4s ease-in-out infinite",
  sway:    "idleSway 3.5s ease-in-out infinite",
  glow:    "idleGlow 3s ease-in-out infinite",
  twitch:  "idleTwitch 2s ease-in-out infinite",
};

キーフレームの例

@keyframes idleBreathe {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-3px); }
}

@keyframes idleTwitch {
  0%, 85%, 100% { transform: translate(0, 0); }
  87% { transform: translate(1px, -1px); }
  89% { transform: translate(-1px, 0); }
}

行動ステートマシン

PixelCharacterコンポーネントは4つの状態を持ちます。

idle → walking → pause → idle
  ↑                        |
  └── feeding ←── (click) ─┘
type CharacterState = "idle" | "walking" | "pause" | "feeding";

インタラクション — クリックで餌やり

ハビタットをクリックすると、ランダムな食べ物絵文字が配置され、キャラクターが歩いて食べに行きます。食べ終わると >ω< や ^_^ のリアクションを表示します。

タイマー競合を避けるため、通常行動用の behaviorTimerRef と餌やり用の feedWalkTimerRef を分離しています。

訪問キャラクター

他ユーザーのキャラクターが遊びに来る「訪問システム」も実装しています。訪問キャラクターは本来の速度の0.6倍で動き、Six Degrees of Separationに基づくアクセス制御が適用されます。

パフォーマンスの工夫

  1. image-rendering: pixelated — 16x16 SVGの拡大表示でピクセルの鮮明さを維持
  2. React.memo — 親の再レンダリングによるアニメーションリセットを防止
  3. CSS transition — requestAnimationFrameではなくGPUアクセラレーションを活用

まとめ

Identity Avatarのアニメーションエンジンは、データ駆動 × CSS/JS責務分離 × ステートマシンで設計しました。151体の個性は全てデータ定義で、コード変更なしにキャラ追加が可能です。

プロフィールページに住む小さなピクセルアートの住人たち、ぜひ myna.me で体験してみてください。

Discussion