mcpField
R
rive

rive

github.com/bowtiedswanクリエイティブ・メディア27346 回インストール39 週間前に更新
リポジトリ

rive について

rive は、Rive のインタラクティブアニメーション制作やアプリへの組み込みを支援するスキルです。Luau スクリプト、ステートマシン、データバインディング、各種ランタイムを扱います。

主な機能

  1. 01Luau スクリプトの作成
  2. 02ステートマシンアニメーションの構築
  3. 03View Models によるデータ連携
  4. 04ポインターイベントと独自描画
  5. 05Web・モバイル・ゲーム用ランタイム

活用シーン

  1. 01React または Next.js アプリへのアニメーション組み込み
  2. 02スクロール連動やパララックスアニメーションの制作
  3. 03ステートマシン入力によるアニメーション制御
  4. 04独自描画やパスエフェクトのスクリプト作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bowtiedswan/rive-skills --skill rive

インストール後、Claude Code で/riveを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
references/
rive-data-binding.md
rive-manipulating-shapes.md
rive-flutter-runtime.md
rive-editor-fundamentals.md
rive-game-runtimes.md
rive-animation-mode.md
rive-events.md
rive-constraints.md
rive-layouts.md
rive-mobile-runtimes.md
rive-text.md
rive-web-runtime.md
rive-state-machine.md
rive-react-runtime.md
rive-scripting-api.md

詳細

Rive アニメーションプラットフォームのスキル

このスキルでは、Web、モバイル、ゲームの各プラットフォームでインタラクティブなグラフィックを作成・実行できる、インタラクティブアニメーションプラットフォーム Rive の包括的な知識を提供します。

概要

Rive は、強力なランタイムを備えた軽量でインタラクティブなグラフィックを作成できるデザイン・アニメーションツールです。主な機能は次のとおりです。

  • スクリプト:Rive Editor で Luau スクリプトを直接記述し、機能を拡張できます
  • ステートマシン:状態とトランジションを使って、複雑なインタラクティブアニメーションを作成できます
  • データバインディング:View Model を介して、アニメーションを動的データに接続できます
  • クロスプラットフォームのランタイム:Web(React/Next.js)、iOS、Android、Flutter、Unity、Unreal にデプロイできます

このスキルを使用する場面

次のような場合に、このスキルを使用してください。

  • Rive スクリプトを作成する場合(Node、Layout、Converter、PathEffect の各プロトコル)
  • Rive アニメーションを React または Next.js アプリケーションに統合する場合
  • Rive でスクロールベースまたはパララックスアニメーションを実装する場合
  • Rive のステートマシンと実行時の入力を扱う場合
  • Rive の描画 API を使ってカスタムレンダリングを行う場合
  • ポインターイベントを使ったインタラクティブアニメーションを作成する場合
  • View Model を使ったデータバインディングを実装する場合

クイックスタート

React/Next.js の統合
import { useRive } from '@rive-app/react-canvas';

function MyAnimation() {
  const { rive, RiveComponent } = useRive({
    src: '/animation.riv',
    stateMachines: 'MainStateMachine',
    autoplay: true,
  });

  return <RiveComponent style={{ width: 400, height: 400 }} />;
}
ステートマシン入力の制御
const { rive, RiveComponent } = useRive({
  src: '/animation.riv',
  stateMachines: 'State Machine 1',
});

// Get and set inputs
const scrollInput = rive?.stateMachineInputs('State Machine 1')
  ?.find(i => i.name === 'scrollProgress');
  
// Update on scroll (0-100 range example)
scrollInput?.value = scrollProgress * 100;

Rive スクリプティング(Luau)

Rive スクリプトでは Luau(Lua の派生言語)を使用し、特定のプロトコルに従います。詳細な API リファレンスについては、@references/rive-scripting-api.md を参照してください。

スクリプトプロトコルの概要
プロトコル用途主な関数
Nodeカスタム描画/レンダリングinit()、advance(seconds)、draw(renderer)
Layoutカスタムレイアウト動作measure()、resize(size) + Node 関数
Converterデータ変換convert(input)、reverseConvert(input)
PathEffectパスの変更init()、update(pathData)、advance(seconds)
Testテストハーネススクリプトの単体テスト
Node スクリプトのテンプレート
-- Define script data type with inputs
type MyNode = {
  color: Input<Color>,
  speed: Input<number>,
  path: Path,
  paint: Paint,
}

function init(self: MyNode): boolean
  self.path = Path.new()
  self.paint = Paint.new()
  self.paint.style = 'fill'
  return true
end

function advance(self: MyNode, seconds: number): boolean
  -- Animation logic here, called every frame
  return true -- Return true to keep receiving advance calls
end

function update(self: MyNode)
  -- Called when any input changes
end

function draw(self: MyNode, renderer: Renderer)
  renderer:drawPath(self.path, self.paint)
end

return function(): Node<MyNode>
  return {
    init = init,
    advance = advance,
    update = update,
    draw = draw,
    color = Color.rgba(255, 255, 255, 255),
    speed = 1.0,
    path = late(),
    paint = late(),
  }
end
Layout スクリプトのテンプレート
type MyLayout = {
  spacing: Input<number>,
}

function measure(self: MyLayout): Vec2D
  -- Return desired size (used when Fit type is Hug)
  return Vec2D.xy(200, 100)
end

function resize(self: MyLayout, size: Vec2D)
  -- Called when layout receives new size
  print("New size:", size.x, size.y)
end

-- Include Node functions (init, advance, draw) as needed

return function(): Layout<MyLayout>
  return {
    measure = measure,
    resize = resize,
    spacing = 10,
  }
end
Converter スクリプトのテンプレート
type NumberToString = {}

function convert(self: NumberToString, input: DataInputs): DataOutput
  local dv: DataValueString = DataValue.string()
  if input:isNumber() then
    dv.value = tostring((input :: DataValueNumber).value)
  else
    dv.value = ""
  end
  return dv
end

function reverseConvert(self: NumberToString, input: DataOutput): DataInputs
  local dv: DataValueNumber = DataValue.number()
  if input:isString() then
    dv.value = tonumber((input :: DataValueString).value) or 0
  end
  return dv
end

return function(): Converter<NumberToString, DataValueNumber, DataValueString>
  return {
    convert = convert,
    reverseConvert = reverseConvert,
  }
end
PathEffect スクリプトのテンプレート
type WaveEffect = {
  amplitude: Input<number>,
  frequency: Input<number>,
  context: Context,
}

function init(self: WaveEffect, context: Context): boolean
  self.context = context
  return true
end

function update(self: WaveEffect, inPath: PathData): PathData
  local path = Path.new()
  -- Transform path geometry here
  for i = 1, #inPath do
    local cmd = inPath[i]
    -- Process each PathCommand
  end
  return path
end

function advance(self: WaveEffect, seconds: number): boolean
  -- Called each frame for animated effects
  return true
end

return function(): PathEffect<WaveEffect>
  return {
    init = init,
    update = update,
    advance = advance,
    amplitude = 10,
    frequency = 1,
    context = late(),
  }
end

スクリプト入力

Rive Editor で設定可能なプロパティを公開する入力を定義します。

type MyNode = {
  -- Basic inputs
  myNumber: Input<number>,
  myColor: Input<Color>,
  myString: string,  -- Non-input, internal use only
  
  -- View Model inputs
  myViewModel: Input<Data.Character>,
  
  -- Artboard inputs (for dynamic instantiation)
  enemyTemplate: Input<Artboard<Data.Enemy>>,
}

-- Access input values
function init(self: MyNode): boolean
  print("Number:", self.myNumber)
  print("Color:", self.myColor)
  print("ViewModel property:", self.myViewModel.health.value)
  return true
end

-- Listen for changes
function init(self: MyNode): boolean
  self.myNumber:addListener(function()
    print("myNumber changed!")
  end)
  return true
end

-- Mark inputs assigned at runtime
return function(): Node<MyNode>
  return {
    init = init,
    myNumber = 0,
    myColor = Color.rgba(255, 255, 255, 255),
    myString = "default",
    myViewModel = late(),  -- Assigned via Editor
    enemyTemplate = late(),
  }
end

ポインターイベント

タッチ/マウス操作を処理します。

function pointerDown(self: MyNode, event: PointerEvent)
  print("Position:", event.position.x, event.position.y)
  print("Pointer ID:", event.id)  -- For multi-touch
  event:hit()  -- Mark as handled
end

function pointerMove(self: MyNode, event: PointerEvent)
  -- Handle drag
  event:hit()
end

function pointerUp(self: MyNode, event: PointerEvent)
  event:hit()
end

function pointerExit(self: MyNode, event: PointerEvent)
  event:hit()
end

return function(): Node<MyNode>
  return {
    pointerDown = pointerDown,
    pointerMove = pointerMove,
    pointerUp = pointerUp,
    pointerExit = pointerExit,
  }
end

動的なコンポーネントのインスタンス化

実行時にアートボードのインスタンスを作成します。

type Enemy = {
  artboard: Artboard<Data.Enemy>,
  position: Vec2D,
}

type GameScene = {
  enemyTemplate: Input<Artboard<Data.Enemy>>,
  enemies: { Enemy },
}

function createEnemy(self: GameScene, x: number, y: number)
  local enemy = self.enemyTemplate:instance()
  local entry: Enemy = {
    artboard = enemy,
    position = Vec2D.xy(x, y),
  }
  table.insert(self.enemies, entry)
end

function advance(self: GameScene, seconds: number): boolean
  for _, enemy in self.enemies do
    enemy.artboard:advance(seconds)
  end
  return true
end

function draw(self: GameScene, renderer: Renderer)
  for _, enemy in self.enemies do
    renderer:save()
    renderer:transform(Mat2D.fromTranslate(enemy.position.x, enemy.position.y))
    enemy.artboard:draw(renderer)
    renderer:restore()
  end
end

データバインディング

スクリプトから View Model にアクセスします。

function init(self: MyNode, context: Context): boolean
  local vm = context:viewModel()
  
  -- Get properties
  local score = vm:getNumber('score')
  local name = vm:getString('playerName')
  
  -- Set values
  if score then
    score.value = 100
  end
  
  -- Listen for changes
  if score then
    score:addListener(function()
      print("Score changed to:", score.value)
    end)
  end
  
  -- Access nested view models
  local settings = vm:getViewModel('settings')
  local volume = settings:getNumber('volume')
  
  return true
end

描画 API

完全な API リファレンスについては、@references/rive-scripting-api.md を参照してください。

パス操作
local path = Path.new()

-- Drawing commands
path:moveTo(Vec2D.xy(0, 0))
path:lineTo(Vec2D.xy(100, 0))
path:quadTo(Vec2D.xy(150, 50), Vec2D.xy(100, 100))
path:cubicTo(Vec2D.xy(75, 150), Vec2D.xy(25, 150), Vec2D.xy(0, 100))
path:close()

-- Reset path for reuse
path:reset()

-- Measure path
local length = path:measure()
local contours = path:contours()
ペイントの設定
local paint = Paint.new()
paint.style = 'fill'  -- or 'stroke'
paint.color = Color.rgba(255, 128, 0, 255)
paint.thickness = 3  -- For strokes
paint.cap = 'round'  -- 'butt', 'round', 'square'
paint.join = 'round' -- 'miter', 'round', 'bevel'
paint.blendMode = 'srcOver'

-- Gradient fills
paint.gradient = Gradient.linear(
  Vec2D.xy(0, 0),
  Vec2D.xy(100, 100),
  { GradientStop.new(0, Color.hex('#FF0000')),
    GradientStop.new(1, Color.hex('#0000FF')) }
)
レンダラーの操作
function draw(self: MyNode, renderer: Renderer)
  renderer:save()
  
  -- Transform
  renderer:transform(Mat2D.fromScale(2, 2))
  renderer:transform(Mat2D.fromRotation(math.pi / 4))
  renderer:transform(Mat2D.fromTranslate(50, 50))
  
  -- Draw path
  renderer:drawPath(self.path, self.paint)
  
  -- Draw image
  renderer:drawImage(self.image, ImageSampler.linear, 'srcOver', 1.0)
  
  -- Clipping
  renderer:clipPath(self.clipPath)
  
  renderer:restore()
end

React/Next.js ランタイムリファレンス

詳細なランタイム API については、@references/rive-react-runtime.md を参照してください。

インストール
# Recommended
npm install @rive-app/react-canvas

# Alternative options
npm install @rive-app/react-canvas-lite  # Smaller, no Rive Text
npm install @rive-app/react-webgl         # WebGL renderer
npm install @rive-app/react-webgl2        # Rive Renderer (WebGL2)
useRive フック
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';

function Animation() {
  const { rive, RiveComponent } = useRive({
    src: '/animation.riv',
    artboard: 'MainArtboard',
    stateMachines: 'StateMachine1',
    autoplay: true,
    layout: new Layout({
      fit: Fit.Contain,
      alignment: Alignment.Center,
    }),
  });

  // Playback control
  const play = () => rive?.play();
  const pause = () => rive?.pause();
  const stop = () => rive?.stop();

  return (
    <RiveComponent 
      style={{ width: '100%', height: '100vh' }}
      onMouseEnter={() => rive?.play()}
    />
  );
}
ステートマシン入力
function InteractiveAnimation() {
  const { rive, RiveComponent } = useRive({
    src: '/interactive.riv',
    stateMachines: 'Controls',
    autoplay: true,
  });

  useEffect(() => {
    if (!rive) return;
    
    const inputs = rive.stateMachineInputs('Controls');
    
    // Number input
    const progress = inputs?.find(i => i.name === 'progress');
    if (progress) progress.value = 50;
    
    // Boolean input
    const isActive = inputs?.find(i => i.name === 'isActive');
    if (isActive) isActive.value = true;
    
    // Trigger input
    const onClick = inputs?.find(i => i.name === 'onClick');
    onClick?.fire();
  }, [rive]);

  return <RiveComponent />;
}
スクロールベースのアニメーション
function ScrollAnimation() {
  const { rive, RiveComponent } = useRive({
    src: '/scroll-animation.riv',
    stateMachines: 'ScrollMachine',
    autoplay: true,
  });
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!rive) return;
    
    const progressInput = rive.stateMachineInputs('ScrollMachine')
      ?.find(i => i.name === 'scrollProgress');
    
    const handleScroll = () => {
      if (!containerRef.current || !progressInput) return;
      
      const rect = containerRef.current.getBoundingClientRect();
      const windowHeight = window.innerHeight;
      
      // Calculate scroll progress (0-100)
      const progress = Math.max(0, Math.min(100,
        ((windowHeight - rect.top) / (windowHeight + rect.height)) * 100
      ));
      
      progressInput.value = progress;
    };
    
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [rive]);

  return (
    <div ref={containerRef} style={{ height: '200vh' }}>
      <div style={{ position: 'sticky', top: 0, height: '100vh' }}>
        <RiveComponent style={{ width: '100%', height: '100%' }} />
      </div>
    </div>
  );
}
イベント処理
function AnimationWithEvents() {
  const { rive, RiveComponent } = useRive({
    src: '/events.riv',
    stateMachines: 'Main',
    autoplay: true,
  });

  useEffect(() => {
    if (!rive) return;
    
    // Listen for Rive events
    rive.on('statechange', (event) => {
      console.log('State changed:', event.data);
    });
    
    rive.on('riveevent', (event) => {
      console.log('Rive event:', event.data.name);
    });
  }, [rive]);

  return <RiveComponent />;
}

アニメーション API リファレンス

Animation オブジェクト
local anim = artboard:animation('AnimationName')

-- Control
anim:advance(0.016)  -- Advance by time in seconds
anim:setTime(1.5)    -- Set time in seconds
anim:setTimeFrames(30)  -- Set time in frames
anim:setTimePercentage(0.5)  -- Set time as 0-1

-- Properties
local duration = anim.duration
Artboard オブジェクト
local artboard = self.myArtboard:instance()

-- Properties
artboard.width = 400
artboard.height = 300
artboard.frameOrigin = true

-- Control
artboard:advance(seconds)
artboard:draw(renderer)

-- Access nodes and bounds
local node = artboard:node('NodeName')
local minPt, maxPt = artboard:bounds()

-- Pointer events
artboard:pointerDown(event)
artboard:pointerUp(event)
artboard:pointerMove(event)

ベストプラクティス

パフォーマンス
  1. パスとペイントを再利用する:init() で作成し、draw() で再利用します
  2. 固定タイムステップを使用する:デバイス間で物理演算の一貫性を保ちます
  3. ステートマシン入力を最小限にする:可能な場合は更新をまとめます
  4. .riv ファイルを遅延読み込みする:特に複数のアニメーションを使用する場合に有効です
コードの構成
  1. 関心事を分離する:動作ごとに異なるスクリプトを使用します
  2. View Model を使用する:複雑な状態管理に使用します
  3. スクリプトに型を付ける:Luau の型システムを活用します
スクロールアニメーション
  1. scrollProgress 入力を使用する:スクロール位置を 0~100 の範囲に対応付けます
  2. スクロールハンドラーをデバウンスする:パフォーマンスの問題を防ぎます
  3. sticky 配置を使用する:スクロールをトリガーとするシーンに使用します
  4. Intersection Observer を検討する:表示されたときにアニメーションをトリガーするために使用します

トラブルシューティング

よくある問題
  1. スクリプトが一覧に表示されない:アセットパネルと問題パネルを確認してください
  2. アニメーションが再生されない:autoplay とステートマシン名を確認してください
  3. 入力が更新されない:入力名が完全に一致していることを確認してください
  4. パフォーマンスの問題:パスのリセットや再描画が過剰に行われていないか確認してください
デバッグツール
-- In scripts
print("Debug:", value)

-- Check Problems Panel in Rive Editor
-- Use Debug Panel for runtime inspection

追加リソース

リファレンスドキュメント

API リファレンスとガイドの詳細については、以下を参照してください。

スクリプトとコア

  • @references/rive-scripting-api.md - Luau スクリプト API の完全なリファレンス

エディター機能

  • @references/rive-editor-fundamentals.md - インターフェース、アートボード、シェイプ、コンポーネント
  • @references/rive-animation-mode.md - タイムライン、キーフレーム、イージング、アニメーションのミキシング
  • @references/rive-state-machine.md - ステート、入力、トランジション、リスナー、レイヤー
  • @references/rive-constraints.md - IK、距離、トランスフォーム、パス追従の制約
  • @references/rive-layouts.md - Flexbox のようなレイアウト、N スライシング、スクロール
  • @references/rive-manipulating-shapes.md - ボーン、メッシュ、クリッピング、ジョイスティック
  • @references/rive-text.md - フォント、テキストラン、モディファイア、スタイル
  • @references/rive-events.md - Rive イベント、オーディオイベント、ランタイムでのリスニング
  • @references/rive-data-binding.md - ビューモデル、リスト、ランタイムのデータバインディング

Web ランタイム

  • @references/rive-react-runtime.md - React/Next.js との統合
  • @references/rive-web-runtime.md - Vanilla JS、Canvas、WebGL、WASM

モバイルランタイム

  • @references/rive-flutter-runtime.md - Flutter ウィジェットとコントローラー
  • @references/rive-mobile-runtimes.md - iOS(Swift)、Android(Kotlin)、React Native

ゲームエンジンランタイム

  • @references/rive-game-runtimes.md - Unity、Unreal Engine、Defold

コメント