SVGで作る感情豊かなインタラクティブキャラクター実装ガイド

## はじめに
SVGを使用してインタラクティブなキャラクターを作成する実装方法を解説します。マウスインタラクションによる感情表現や、自然な目の動きの実装など、キャラクターをより生き生きとさせるテクニックを紹介します。

## 実装のポイント

1. SVGの基本構造設計
2. 感情表現の実装（喜怒哀楽）
3. インタラクティブな目の動きの制御
4. アニメーションとトランジション
5. イベントハンドリング

## 完成イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f72b76e1-ddbd-a345-98fa-2a299a9dcda0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/bb1122e2-4e21-2b41-610f-417909273c25.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/aba5d68e-26a4-b549-c467-6baddc0f56f0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9ef3b2c1-9392-d22a-e1bb-fa76a2bd94e9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/38c1708c-b455-1034-6f05-5ef9f7845cb8.png)

## 1. 基本構造の設計

### 1.1 SVGの基本構造

```html
<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg">
    <!-- インタラクションエリア -->
    <rect id="interaction-area" x="-100" y="-100" width="200" height="200" fill="transparent" />
    
    <g id="character">
        <!-- 本体(楕円) -->
        <ellipse cx="0" cy="0" rx="50" ry="45" fill="#4CAF50" />
        <!-- 顔部分 -->
        <ellipse cx="0" cy="0" rx="30" ry="25" fill="#FDF1D2" />
        
        <!-- 目 -->
        <g id="eyes">
            <ellipse class="eye" id="leftEye" cx="-10" cy="-5" rx="3" ry="3" fill="#000" />
            <ellipse class="eye" id="rightEye" cx="10" cy="-5" rx="3" ry="3" fill="#000" />
        </g>

        <!-- その他のパーツ -->
    </g>
</svg>
```

### 1.2 パーツの配置とグループ化

- 中心を原点(0,0)として配置
- 関連するパーツをグループ化
- クラスとIDを適切に付与

## 2. 感情表現の実装

### 2.1 状態管理

```javascript
const states = {
    NORMAL: 'normal',
    HAPPY: 'happy',
    SAD: 'sad',
    ANGRY: 'angry'
};

let currentState = states.NORMAL;
```

### 2.2 感情切り替えの実装

```javascript
function setEmotion(emotion) {
    switch(emotion) {
        case states.HAPPY:
            mouth.setAttribute('d', 'M -8 5 Q 0 15 8 5');
            // 目を細める（笑顔）
            eyes.forEach(eye => {
                eye.setAttribute('rx', '3');
                eye.setAttribute('ry', '1.5');  // 横長の目
            });
            cheeks.forEach(cheek => cheek.setAttribute('opacity', '0.4'));
            break;

        case states.SAD:
            mouth.setAttribute('d', 'M -8 8 Q 0 0 8 8');
            // 目を丸く大きく
            eyes.forEach(eye => {
                eye.setAttribute('rx', '3');
                eye.setAttribute('ry', '3.5');  // やや縦長の目
                eye.setAttribute('cy', '-4');  // 目を少し上げる
            });
            // 耳を下げる
            ears.forEach(ear => {
                const isLeft = ear.id === 'leftEar';
                ear.style.transform = 'rotate(-20deg)';
                ear.style.transformOrigin = isLeft ? '-20px -32px' : '20px -32px';
            });
            break;

        case states.ANGRY:
            // 怒り表現の実装
            // ...省略
    }
}
```

## 3. インタラクティブな目の動きの実装

### 3.1 マウス追従による目の動き

```javascript
interactionArea.addEventListener('mousemove', (e) => {
    if (!e.target || !eyes.length) return;

    const rect = e.target.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    // SVG座標系に変換
    const svgX = (x / rect.width) * 200 - 100;
    const svgY = (y / rect.height) * 200 - 100;
    
    // 目の移動範囲を制限
    const maxEyeMove = 2;
    
    // 各目の動きを計算
    eyes.forEach((eye) => {
        const baseX = eye.id === 'leftEye' ? -10 : 10;
        const centerX = baseX;
        const centerY = -5;

        // カーソルと目の中心との角度を計算
        const dx = svgX - centerX;
        const dy = svgY - centerY;
        const angle = Math.atan2(dy, dx);

        // 移動量を計算（距離が遠いほど目が大きく動く）
        const distance = Math.min(Math.hypot(dx, dy) / 30, 1);
        const moveX = Math.cos(angle) * maxEyeMove * distance;
        const moveY = Math.sin(angle) * maxEyeMove * distance;

        // 目の位置を更新
        eye.setAttribute('cx', (centerX + moveX).toString());
        eye.setAttribute('cy', (centerY + moveY).toString());
    });
});
```

### 3.2 まばたきアニメーション

```javascript
function blink() {
    eyes.forEach(eye => {
        const currentRy = parseFloat(eye.getAttribute('ry'));
        eye.setAttribute('ry', '0.1');
        setTimeout(() => eye.setAttribute('ry', currentRy.toString()), 100);
    });
}

// 定期的なまばたき
setInterval(blink, 3000);
```

## 4. イベントハンドラの実装

### 4.1 マウスイベントの設定

```javascript
// ホバー時
interactionArea.addEventListener('mouseenter', () => {
    if (!isPressed) setEmotion(states.HAPPY);
});

// 右クリック時
interactionArea.addEventListener('mousedown', (e) => {
    if (e.button === 2) {
        setEmotion(states.SAD);
    }
});

// 左クリック時
interactionArea.addEventListener('mousedown', (e) => {
    if (e.button === 0) {
        isPressed = true;
        setEmotion(states.ANGRY);
    }
});
```

### 4.2 タッチデバイス対応

```javascript
interactionArea.addEventListener('touchstart', (e) => {
    e.preventDefault();
    isPressed = true;
    setEmotion(states.ANGRY);
});

interactionArea.addEventListener('touchend', (e) => {
    e.preventDefault();
    isPressed = false;
    setEmotion(states.HAPPY);
    setTimeout(() => {
        if (!isPressed) setEmotion(states.NORMAL);
    }, 1000);
});
```

## インタラクションと感情表現の対応

| インタラクション | 感情表現 | 目の変化 | その他の変化 |
|----------------|----------|---------|------------|
| マウスホバー | 喜び | 横長（ry: 1.5） | 頬が赤くなる |
| 左クリック | 怒り | 細め（ry: 1.2）、下向き | 耳が後ろに倒れる |
| 右クリック | 悲しみ | 大きく（ry: 3.5）、上向き | 耳が下がる |
| 通常時 | 通常 | 丸い（rx=ry: 3） | - |

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/546cfcd7-dfe4-6778-646b-c1ed13b1640a.png)

SVGとJavaScriptを組み合わせることで、豊かな感情表現とインタラクションを持つキャラクターを実装できます。特に以下の点がポイントとなります：

1. SVG要素の適切な構造化
2. 感情に応じた目の形状制御
3. 自然な目の追従動作
4. スムーズなアニメーション処理

## 発展的な実装案

1. 音声効果の追加
2. より複雑なアニメーションパターン
3. 状態遷移のスムーズな補間
4. 物理演算を用いた動き

## 参考資料
- SVG 1.1仕様: https://www.w3.org/TR/SVG11/
- MDN SVGドキュメント: https://developer.mozilla.org/ja/docs/Web/SVG