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に基づくアクセス制御が適用されます。
パフォーマンスの工夫
- image-rendering: pixelated — 16x16 SVGの拡大表示でピクセルの鮮明さを維持
- React.memo — 親の再レンダリングによるアニメーションリセットを防止
- CSS transition — requestAnimationFrameではなくGPUアクセラレーションを活用
まとめ
Identity Avatarのアニメーションエンジンは、データ駆動 × CSS/JS責務分離 × ステートマシンで設計しました。151体の個性は全てデータ定義で、コード変更なしにキャラ追加が可能です。
プロフィールページに住む小さなピクセルアートの住人たち、ぜひ myna.me で体験してみてください。
Discussion