mcpField
R
rive

rive

github.com/bowtiedswan创意与媒体27346 次安装39 周前更新
代码仓库

关于 rive

rive 是介绍 Rive 交互动画平台的 skill,面向制作动画及将动画集成到应用的开发者。内容涵盖 Luau 脚本、状态机、数据绑定和多平台运行时。

主要功能

  1. 01编写 Luau 脚本
  2. 02构建状态机动画
  3. 03使用 View Models 绑定数据
  4. 04处理指针事件与自定义绘制
  5. 05集成 Web、移动端和游戏运行时

适用场景

  1. 01在 React 或 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 动画平台技能

本技能提供使用 Rive 的全面知识。Rive 是一个交互式动画平台,支持在 Web、移动端和游戏平台上创建并运行交互式图形。

概述

Rive 是一款设计与动画工具,可生成轻量级的交互式图形,并配备强大的运行时。核心能力:

  • 脚本编写:直接在 Rive 编辑器中编写 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 编辑器中公开可配置属性:

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

动态组件实例化

在运行时创建 artboard 实例:

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 Hook
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-Slicing、滚动
  • @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 - 原生 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

评论

支持 Markdown