← 仕様書・設計書一覧

タイムライン演出システム仕様書(作成途中)

シーカブル(任意時刻で同一結果を再現可能)なキーフレームベースのアニメーション評価エンジンと、テキスト・音声・フラグ等の時刻トリガーイベントシステムの仕様書です。

システム構成

レイヤー役割
Tween 評価数値プロパティのキーフレーム補間(カメラ移動、エンティティ座標、透明度等)
イベント発火非数値の時刻トリガーイベント(テキスト表示、効果音、BGM、フラグ設定)

Tween タイムライン

データモデル(v1.1)

階層構造: TimelineRoot > Track[] > Clip[] > Channel[] > Keyframe[]

TimelineRoot

interface TimelineRoot {
  schemaVersion: 1;
  durationMs: number;    // タイムライン全体の長さ(ms)
  tracks: Track[];
}

Track

4種類のトラックがあり、kind で判別します。

kindtargetId用途
camera"camera"(固定)カメラのパン・ズーム・回転
entityエンティティIDキャラクター・背景の座標・透明度
audioオーディオソースID音量・パン
eventなしイベントトリガー
interface BaseTrack {
  id: string;
  kind: TrackKind;
  order: number;      // 描画・評価順序
  enabled: boolean;    // false で評価スキップ
  clips: Clip[];
}

Clip

時間範囲を持つアニメーション単位です。同一トラック内でクリップが重複してはいけません。半開区間 [startMs, endMs) で判定されます。

interface Clip {
  id: string;
  startMs: number;     // 開始時刻(含む)
  endMs: number;       // 終了時刻(含まない)
  fillMode?: FillMode; // デフォルト: "hold"
  channels: Channel[];
}

FillMode(クリップ範囲外の挙動):

FillMode説明
holdクリップ終了後も最終値を保持(デフォルト)
noneクリップ外では値を変更しない
resetクリップ終了後は defaultValue に戻す

Channel

interface Channel {
  property: string;      // "x", "y", "scaleX", "alpha" 等
  defaultValue: number;  // 初期値(fillMode: reset で使用)
  keyframes: Keyframe[];
}

Keyframe

interface Keyframe {
  tMs?: number;          // v1 互換(非推奨)
  clipOffsetMs?: number; // v1.1 推奨(clip 開始からの相対時間)
  value: number;
  easing?: Easing;       // デフォルト: "linear"
}

両方指定された場合は clipOffsetMs を優先。

イージング関数

10種類のイージングをサポート。すべて [0,1] → [0,1] の変換関数です。

Easing説明
linear等速(デフォルト)
easeIn / easeInCubic三次加速
easeOut / easeOutCubic三次減速
easeInOut / easeInOutCubic三次加速→減速
easeInQuad二次加速
easeOutQuad二次減速
easeInOutQuad二次加速→減速

評価アルゴリズム

evaluateTimeline(timeline, currentTimeMs) EvaluationResult[]

  1. 有効なトラックのみ処理(enabled: true
  2. 各トラックで currentTimeMs にアクティブなクリップを検索
  3. アクティブなクリップがない場合 → fillMode に従って処理
  4. アクティブなクリップ内の各チャンネルでキーフレーム間を lerp(a, b, applyEasing(progress, easing)) で補間
  5. トラックごとに EvaluationResult を返す
interface EvaluationResult {
  trackId: string;
  targetId: string | null;
  properties: Record<string, number>;  // { x: 100, y: 200, alpha: 0.5 }
}

設計原則: 同じ currentTimeMs を渡せば常に同じ結果が返る(シーカブル)。

バリデーション

validateTimeline(timeline) で以下を検証:

  • schemaVersion === 1durationMs > 0
  • トラック種別の妥当性、targetId の整合性
  • 同一トラック内のクリップ重複検出
  • クリップの時間範囲(startMs < endMs
  • キーフレームの時刻がクリップ範囲内
  • イージング値・FillMode の妥当性

イベントタイムライン

数値補間ではなく、特定時刻に発火するイベントを管理するシステムです。

EventProject

interface EventProject {
  schemaVersion: 1;
  durationMs: number;
  eventTracks: EventTrack[];
}

EventTrack

kind用途
textテキスト表示
audio効果音・BGM
logicフラグ設定
markerマーカー(ラベル)

イベント型

typekind主要フィールド
SHOW_TEXTtextspeaker?, text, appendToBacklog?
SFXaudioassetId, volume?
PLAY_BGMaudioassetId, loop?, fadeInMs?, volume?
STOP_BGMaudiofadeOutMs?
SET_FLAGlogickey, value
MARKmarkerlabel

すべてのイベントは tMs(タイムライン絶対時刻)を持ちます。

主要関数

関数説明
emitEventsBetween(project, tPrev, tNow)(tPrev, tNow] の範囲内のイベントを発火
seekStateAt(project, tNow, baseState)指定時刻のランタイム状態を復元(BGM・フラグ)
pickSeekOneShot(project, tNow)シーク時の最新テキストを取得
validateEventProject(project)イベントプロジェクトのバリデーション

RuntimeEventState

interface RuntimeEventState {
  bgm: {
    playing: boolean;
    assetId?: string;
    loop?: boolean;
    volume?: number;
  };
  flags: Record<string, unknown>;
  text?: { speaker?: string; text: string };
}

seekStateAt は全ステートフルイベントを時刻0から畳み込んで状態を復元します(シーカブル)。

JSON サンプル(v1.1)

{
  "schemaVersion": 1,
  "durationMs": 5000,
  "tracks": [
    {
      "id": "cam",
      "kind": "camera",
      "targetId": "camera",
      "order": 0,
      "enabled": true,
      "clips": [{
        "id": "cam-pan",
        "startMs": 0,
        "endMs": 3000,
        "fillMode": "hold",
        "channels": [{
          "property": "x",
          "defaultValue": 0,
          "keyframes": [
            { "clipOffsetMs": 0, "value": 0, "easing": "easeInOut" },
            { "clipOffsetMs": 3000, "value": 200 }
          ]
        }]
      }]
    },
    {
      "id": "hero",
      "kind": "entity",
      "targetId": "hero",
      "order": 1,
      "enabled": true,
      "clips": [{
        "id": "hero-enter",
        "startMs": 500,
        "endMs": 2500,
        "channels": [{
          "property": "alpha",
          "defaultValue": 0,
          "keyframes": [
            { "clipOffsetMs": 0, "value": 0, "easing": "easeOut" },
            { "clipOffsetMs": 1000, "value": 1 }
          ]
        }]
      }]
    }
  ]
}

シネマティックプリセット

デモで実装済みの演出プリセット:

プリセット演出内容
dollyZoomカメラのドリーズーム(前進しながらズームアウト)
dramaticCutInキャラクターのドラマティックなカットイン
cinematicPanシネマティックなカメラパン
crossFadeクロスフェード切り替え
dutchAngleオランダアングル(傾斜カメラ)
parallaxEffect視差効果(パララックス)
impactFreezeインパクト時のフリーズ演出
slowMotionEntranceスローモーション登場

実装状況

コンポーネント状態
コア型定義(v1.1)完了
評価エンジン完了
イージング関数(10種)完了
バリデータ完了
イベントタイムライン完了
コアテスト完了
Web タイムラインプレイヤーv1.1 対応中(一時無効化)
WebEngine.playTimeline()未実装(スタブ)
タイムラインエディタ UI未実装