1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

実装のポイント

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

完成イメージ

image.png

image.png

image.png

image.png

image.png

1. 基本構造の設計

1.1 SVGの基本構造

<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 状態管理

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

let currentState = states.NORMAL;

2.2 感情切り替えの実装

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 マウス追従による目の動き

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 まばたきアニメーション

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 マウスイベントの設定

// ホバー時
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 タッチデバイス対応

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

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

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

発展的な実装案

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

参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?