Token导航 LogoToken导航TokenDH.com
研究检索权限需确认github未标认证来源可访问clear审计未展示

animation-system动画系统

Agent Skill

用于辅助视频生成、动画合成、脚本化剪辑或 Remotion 等视频项目开发。它适合让 Agent 组织镜头、生成素材说明、维护合成代码或排查渲染问题。使用时需要确认分辨率、时长、素材路径和导出格式;涉及外部素材、人物肖像或商业发布时,应先核对版权授权和内容审核要求。

总安装

306

周安装

13

GitHub Stars

公开资料未说明

下载量

107
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:animation-system(动画系统)
来源仓库:https://github.com/taozhuo/game-dev-skills
仓库路径:skills/animation-system
安装命令:
npx skills add taozhuo/game-dev-skills --skill "animation-system"
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

AgentSkills.tonpx skills
npx skills add taozhuo/game-dev-skills --skill "animation-system"

简介

animation-system 用于辅助视频生成、动画合成、脚本化剪辑或 Remotion 项目开发。

  • 它适合组织镜头、生成素材说明、维护合成代码或排查渲染问题。
  • 通过 npx skills add taozhuo/game-dev-skills --skill "animation-system" 命令安装。
  • 使用时需确认分辨率、时长、素材路径和导出格式;涉及外部素材或人物肖像时应核对版权授权。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

name
animation-system
description
Implements animation systems including custom animators, animation blending, procedural animation, and IK. Use when creating character animations, custom rigs, or procedural movement.
allowed-tools
Read, Write, Edit, Glob, Grep

Roblox Animation Systems

Quick Reference Links

Official Documentation:

Wiki References:


When implementing animations, follow these patterns for smooth, performant character and object animations.

Animation Basics

Loading and Playing Animations

local function setupAnimations(character)
    local humanoid = character:WaitForChild("Humanoid")
    local animator = humanoid:WaitForChild("Animator")

    -- Create animation instance
    local walkAnim = Instance.new("Animation")
    walkAnim.AnimationId = "rbxassetid://123456789"

    -- Load animation track
    local walkTrack = animator:LoadAnimation(walkAnim)

    -- Configure track
    walkTrack.Priority = Enum.AnimationPriority.Movement
    walkTrack.Looped = true

    -- Play with parameters
    walkTrack:Play(
        0.1,  -- Fade in time
        1,    -- Weight (0-1)
        1     -- Speed multiplier
    )

    return walkTrack
end

Animation Priorities

-- Priority order (lowest to highest):
-- Core < Idle < Movement < Action < Action2 < Action3 < Action4

local function setAnimationPriority(track, priority)
    track.Priority = priority
end

-- Example priority usage
idleTrack.Priority = Enum.AnimationPriority.Idle
walkTrack.Priority = Enum.AnimationPriority.Movement
attackTrack.Priority = Enum.AnimationPriority.Action
-- Action always overrides Movement, Movement overrides Idle

Animation Events (Keyframe Markers)

-- Add markers in Animation Editor, then listen:
local function setupAnimationEvents(track)
    -- Listen for specific marker
    track:GetMarkerReachedSignal("Footstep"):Connect(function(paramValue)
        playFootstepSound()
    end)

    track:GetMarkerReachedSignal("DamageFrame"):Connect(function()
        applyDamage()
    end)

    track:GetMarkerReachedSignal("SpawnVFX"):Connect(function(vfxName)
        spawnEffect(vfxName)
    end)
end

-- Animation completion
track.Stopped:Connect(function()
    print("Animation stopped or completed")
end)

-- Check if playing
if track.IsPlaying then
    -- Animation is active
end

Animation Controller

State-Based Animation Controller

local AnimationController = {}
AnimationController.__index = AnimationController

function AnimationController.new(character)
    local self = setmetatable({}, AnimationController)

    self.character = character
    self.humanoid = character:WaitForChild("Humanoid")
    self.animator = self.humanoid:WaitForChild("Animator")
    self.tracks = {}
    self.currentState = "Idle"
    self.stateAnimations = {}

    return self
end

function AnimationController:loadAnimation(name, animationId, config)
    config = config or {}

    local animation = Instance.new("Animation")
    animation.AnimationId = animationId

    local track = self.animator:LoadAnimation(animation)
    track.Priority = config.priority or Enum.AnimationPriority.Movement
    track.Looped = config.looped or false

    self.tracks[name] = track
    return track
end

function AnimationController:setState(stateName, fadeTime)
    fadeTime = fadeTime or 0.1

    if self.currentState == stateName then return end

    -- Stop current state animation
    local currentTrack = self.tracks[self.currentState]
    if currentTrack and currentTrack.IsPlaying then
        currentTrack:Stop(fadeTime)
    end

    -- Play new state animation
    local newTrack = self.tracks[stateName]
    if newTrack then
        newTrack:Play(fadeTime)
    end

    self.currentState = stateName
end

function AnimationController:playOneShot(name, fadeTime, weight, speed)
    local track = self.tracks[name]
    if track then
        track:Play(fadeTime or 0.1, weight or 1, speed or 1)
    end
    return track
end

-- Usage
local controller = AnimationController.new(character)
controller:loadAnimation("Idle", "rbxassetid://idle", {looped = true, priority = Enum.AnimationPriority.Idle})
controller:loadAnimation("Walk", "rbxassetid://walk", {looped = true, priority = Enum.AnimationPriority.Movement})
controller:loadAnimation("Attack", "rbxassetid://attack", {priority = Enum.AnimationPriority.Action})

controller:setState("Idle")
-- When moving:
controller:setState("Walk")
-- Attack (plays on top):
controller:playOneShot("Attack")

Movement-Based Animation Selection

local function setupMovementAnimations(character)
    local humanoid = character:WaitForChild("Humanoid")
    local animator = humanoid:WaitForChild("Animator")
    local hrp = character:WaitForChild("HumanoidRootPart")

    local animations = {
        idle = loadAnimation(animator, "rbxassetid://idle"),
        walk = loadAnimation(animator, "rbxassetid://walk"),
        run = loadAnimation(animator, "rbxassetid://run"),
        jump = loadAnimation(animator, "rbxassetid://jump"),
        fall = loadAnimation(animator, "rbxassetid://fall")
    }

    -- Set looping
    animations.idle.Looped = true
    animations.walk.Looped = true
    animations.run.Looped = true
    animations.fall.Looped = true

    local currentAnim = nil

    local function updateAnimation()
        local velocity = hrp.AssemblyLinearVelocity
        local horizontalSpeed = Vector3.new(velocity.X, 0, velocity.Z).Magnitude
        local isGrounded = humanoid.FloorMaterial ~= Enum.Material.Air

        local targetAnim

        if not isGrounded then
            if velocity.Y > 1 then
                targetAnim = animations.jump
            else
                targetAnim = animations.fall
            end
        elseif horizontalSpeed < 0.5 then
            targetAnim = animations.idle
        elseif horizontalSpeed < 12 then
            targetAnim = animations.walk
            -- Adjust speed based on movement
            animations.walk:AdjustSpeed(horizontalSpeed / 8)
        else
            targetAnim = animations.run
            animations.run:AdjustSpeed(horizontalSpeed / 16)
        end

        if targetAnim ~= currentAnim then
            if currentAnim then
                currentAnim:Stop(0.2)
            end
            targetAnim:Play(0.2)
            currentAnim = targetAnim
        end
    end

    RunService.Heartbeat:Connect(updateAnimation)
end

Animation Blending

Weight-Based Blending

local BlendedAnimator = {}

function BlendedAnimator.new(animator)
    return {
        animator = animator,
        layers = {}
    }
end

function BlendedAnimator:addLayer(name, animationId, priority)
    local animation = Instance.new("Animation")
    animation.AnimationId = animationId

    local track = self.animator:LoadAnimation(animation)
    track.Priority = priority or Enum.AnimationPriority.Movement
    track.Looped = true

    self.layers[name] = {
        track = track,
        weight = 0,
        targetWeight = 0
    }

    track:Play(0, 0)  -- Start at weight 0
    return track
end

function BlendedAnimator:setLayerWeight(name, weight, blendTime)
    local layer = self.layers[name]
    if not layer then return end

    layer.targetWeight = math.clamp(weight, 0, 1)

    if blendTime and blendTime > 0 then
        -- Smooth blend
        local startWeight = layer.weight
        local startTime = os.clock()

        local conn
        conn = RunService.Heartbeat:Connect(function()
            local elapsed = os.clock() - startTime
            local t = math.min(elapsed / blendTime, 1)

            layer.weight = startWeight + (layer.targetWeight - startWeight) * t
            layer.track:AdjustWeight(layer.weight)

            if t >= 1 then
                conn:Disconnect()
            end
        end)
    else
        layer.weight = layer.targetWeight
        layer.track:AdjustWeight(layer.weight)
    end
end

-- Usage: Blend between walk and limp
local blender = BlendedAnimator.new(animator)
blender:addLayer("Walk", "rbxassetid://walk", Enum.AnimationPriority.Movement)
blender:addLayer("Limp", "rbxassetid://limp", Enum.AnimationPriority.Movement)

-- Normal walking
blender:setLayerWeight("Walk", 1, 0.3)
blender:setLayerWeight("Limp", 0, 0.3)

-- Injured (blend to limp)
blender:setLayerWeight("Walk", 0.3, 0.5)
blender:setLayerWeight("Limp", 0.7, 0.5)

Additive Animation Blending

-- Additive animations add on top of base animation
local function setupAdditiveBlending(animator)
    local baseWalk = loadAnimation(animator, "rbxassetid://walk")
    local leanLeft = loadAnimation(animator, "rbxassetid://lean_left")
    local leanRight = loadAnimation(animator, "rbxassetid://lean_right")

    baseWalk.Looped = true
    leanLeft.Looped = true
    leanRight.Looped = true

    baseWalk:Play()
    leanLeft:Play(0, 0)  -- Start at 0 weight
    leanRight:Play(0, 0)

    -- Update lean based on input
    local function updateLean(turnAmount)
        -- turnAmount: -1 (left) to 1 (right)
        if turnAmount < 0 then
            leanLeft:AdjustWeight(math.abs(turnAmount))
            leanRight:AdjustWeight(0)
        else
            leanLeft:AdjustWeight(0)
            leanRight:AdjustWeight(turnAmount)
        end
    end

    return updateLean
end

Procedural Animation

Procedural Head Look

local function setupHeadLook(character, target)
    local neck = character:FindFirstChild("Neck", true)
    if not neck then return end

    local originalC0 = neck.C0

    RunService.RenderStepped:Connect(function()
        if not target then
            neck.C0 = originalC0
            return
        end

        local headPos = neck.Part1.Position
        local targetPos = target.Position
        local direction = (targetPos - headPos).Unit

        -- Convert to local space
        local torsoLook = neck.Part0.CFrame.LookVector
        local torsoCFrame = neck.Part0.CFrame

        local localDirection = torsoCFrame:VectorToObjectSpace(direction)

        -- Calculate angles
        local yaw = math.atan2(localDirection.X, -localDirection.Z)
        local pitch = math.asin(localDirection.Y)

        -- Clamp to prevent unnatural rotation
        yaw = math.clamp(yaw, math.rad(-70), math.rad(70))
        pitch = math.clamp(pitch, math.rad(-40), math.rad(40))

        -- Apply rotation
        local lookCFrame = CFrame.Angles(pitch, yaw, 0)
        neck.C0 = originalC0 * lookCFrame
    end)
end

Procedural Breathing

local function setupBreathing(character)
    local torso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
    if not torso then return end

    local waist = character:FindFirstChild("Waist", true)
    if not waist then return end

    local originalC0 = waist.C0
    local breathSpeed = 2  -- Cycles per second
    local breathIntensity = 0.02

    local time = 0

    RunService.RenderStepped:Connect(function(dt)
        time = time + dt

        local breathOffset = math.sin(time * breathSpeed * math.pi * 2) * breathIntensity

        waist.C0 = originalC0 * CFrame.new(0, breathOffset, 0)
    end)
end

Procedural Tail/Cape Physics

local function setupProceduralChain(parts, config)
    config = config or {}
    local stiffness = config.stiffness or 0.5
    local damping = config.damping or 0.3
    local gravity = config.gravity or Vector3.new(0, -10, 0)

    local velocities = {}
    local restOffsets = {}

    -- Store rest positions
    for i, part in ipairs(parts) do
        velocities[i] = Vector3.new()
        if i > 1 then
            restOffsets[i] = parts[i-1].CFrame:ToObjectSpace(part.CFrame)
        end
    end

    RunService.Heartbeat:Connect(function(dt)
        for i = 2, #parts do
            local part = parts[i]
            local parent = parts[i-1]

            -- Target position (relative to parent)
            local targetCFrame = parent.CFrame * restOffsets[i]
            local targetPos = targetCFrame.Position

            -- Current position
            local currentPos = part.Position

            -- Spring force toward target
            local displacement = targetPos - currentPos
            local springForce = displacement * stiffness

            -- Apply gravity
            local totalForce = springForce + gravity

            -- Update velocity with damping
            velocities[i] = velocities[i] * (1 - damping) + totalForce * dt

            -- Update position
            local newPos = currentPos + velocities[i]

            -- Maintain distance constraint
            local toParent = parent.Position - newPos
            local distance = toParent.Magnitude
            local restDistance = restOffsets[i].Position.Magnitude

            if distance > restDistance then
                newPos = parent.Position - toParent.Unit * restDistance
            end

            -- Apply
            part.CFrame = CFrame.new(newPos) * (targetCFrame - targetCFrame.Position)
        end
    end)
end

Inverse Kinematics (IK)

Two-Bone IK (Arms/Legs)

local function solveTwoBoneIK(upperBone, lowerBone, target, pole)
    local upperLength = (lowerBone.Position - upperBone.Position).Magnitude
    local lowerLength = (target - lowerBone.Position).Magnitude

    local origin = upperBone.Position
    local targetPos = target
    local polePos = pole or (origin + Vector3.new(0, 0, 1))

    -- Calculate distance to target
    local targetDistance = (targetPos - origin).Magnitude
    local totalLength = upperLength + lowerLength

    -- Clamp target to reachable distance
    if targetDistance > totalLength * 0.999 then
        targetDistance = totalLength * 0.999
    end

    -- Law of cosines to find angles
    local a = upperLength
    local b = lowerLength
    local c = targetDistance

    -- Angle at upper joint
    local upperAngle = math.acos(
        math.clamp((a*a + c*c - b*b) / (2*a*c), -1, 1)
    )

    -- Angle at lower joint (elbow/knee)
    local lowerAngle = math.acos(
        math.clamp((a*a + b*b - c*c) / (2*a*b), -1, 1)
    )

    -- Direction to target
    local directionToTarget = (targetPos - origin).Unit

    -- Calculate pole plane
    local poleDirection = (polePos - origin).Unit
    local cross = directionToTarget:Cross(poleDirection)
    local normal = cross:Cross(directionToTarget).Unit

    -- Apply rotations
    local upperRotation = CFrame.fromAxisAngle(cross, -upperAngle)
    local elbowPosition = origin + upperRotation:VectorToWorldSpace(directionToTarget) * upperLength

    return elbowPosition, lowerAngle
end

-- Foot IK for terrain
local function setupFootIK(character)
    local humanoid = character:WaitForChild("Humanoid")
    local hrp = character:WaitForChild("HumanoidRootPart")

    local leftFoot = character:FindFirstChild("LeftFoot")
    local rightFoot = character:FindFirstChild("RightFoot")
    local leftLeg = character:FindFirstChild("LeftLowerLeg")
    local rightLeg = character:FindFirstChild("RightLowerLeg")

    local rayParams = RaycastParams.new()
    rayParams.FilterDescendantsInstances = {character}

    RunService.RenderStepped:Connect(function()
        if humanoid.FloorMaterial == Enum.Material.Air then return end

        -- Raycast for each foot
        for _, footData in ipairs({{leftFoot, leftLeg}, {rightFoot, rightLeg}}) do
            local foot, lowerLeg = footData[1], footData[2]

            local result = workspace:Raycast(
                foot.Position + Vector3.new(0, 1, 0),
                Vector3.new(0, -2, 0),
                rayParams
            )

            if result then
                local targetY = result.Position.Y
                local offset = targetY - foot.Position.Y + 0.1

                -- Apply IK offset (simplified)
                -- In practice, you'd solve the full IK chain
            end
        end
    end)
end

Custom Rigs

Motor6D Setup for Custom Rigs

local function createCustomRig(model)
    local root = model.PrimaryPart
    local parts = {}

    for _, part in ipairs(model:GetDescendants()) do
        if part:IsA("BasePart") and part ~= root then
            table.insert(parts, part)
        end
    end

    -- Create Motor6Ds
    local motors = {}

    for _, part in ipairs(parts) do
        local motor = Instance.new("Motor6D")
        motor.Name = part.Name

        -- Find parent part (closest connected part toward root)
        local parentPart = findParentPart(part, root, parts)

        motor.Part0 = parentPart
        motor.Part1 = part

        -- Calculate C0 and C1 (joint positions)
        local jointPos = (parentPart.Position + part.Position) / 2
        motor.C0 = parentPart.CFrame:ToObjectSpace(CFrame.new(jointPos))
        motor.C1 = part.CFrame:ToObjectSpace(CFrame.new(jointPos))

        motor.Parent = parentPart
        motors[part.Name] = motor
    end

    return motors
end

Motor6D C0/C1 Joint Positioning (CRITICAL)

Key insight: C0 and C1 define where the joint is relative to each part. For parts to TOUCH (no gap), place the joint at the EDGE of each part, not the center.

-- WRONG: Parts will have gap (joint at centers)
motor.C0 = CFrame.new()  -- Center of Part0
motor.C1 = CFrame.new()  -- Center of Part1

-- CORRECT: Parts touch (joint at edges)
-- If Part0 is in front, Part1 behind (along Z axis):
motor.C0 = CFrame.new(0, 0, part0Size.Z/2)   -- Back edge of Part0
motor.C1 = CFrame.new(0, 0, -part1Size.Z/2)  -- Front edge of Part1

-- Example: Dragon spine chain
local chestSize = Vector3.new(5, 4, 6)
local midSize = Vector3.new(4.5, 3.5, 5)

-- Chest to MidBody (MidBody is behind Chest)
motors.Spine1 = createMotor(chest, midBody,
    CFrame.new(0, 0, chestSize.Z/2),   -- Back of chest
    CFrame.new(0, 0, -midSize.Z/2),    -- Front of midBody
    "Spine1")

Creature Rigging Pattern

Build creatures with a clear hierarchy from root outward:

--[[
Creature Hierarchy:
    HumanoidRootPart (invisible, anchored for physics)
        └── Chest (body center)
            ├── Neck1 → Neck2 → Neck3 → Skull → Snout → Jaw
            ├── LShoulder → LUpperArm → LForearm → LWrist → Fingers
            ├── RShoulder → RUpperArm → RForearm → RWrist → Fingers
            ├── MidBody → Hips
            │       ├── LUpperLeg → LLowerLeg → LFoot
            │       ├── RUpperLeg → RLowerLeg → RFoot
            │       └── Tail1 → Tail2 → Tail3 → ...
]]

local function createCreatureMotor(part0, part1, c0, c1, name)
    local motor = Instance.new("Motor6D")
    motor.Part0 = part0
    motor.Part1 = part1
    motor.C0 = c0
    motor.C1 = c1 or CFrame.new()
    motor.Name = name
    motor.Parent = part0
    return motor
end

-- Store motors in a table for animation access
local motors = {}
motors.Spine1 = createCreatureMotor(chest, midBody, ...)
motors.Neck1 = createCreatureMotor(chest, neck1, ...)
motors.LWingFlap = createCreatureMotor(lShoulder, lUpperArm, ...)

Creature Animation Patterns

local function setupCreatureAnimation(creature, motors)
    local RunService = game:GetService("RunService")

    -- Store original C0 values (CRITICAL for animation)
    local baseC0 = {}
    for name, motor in pairs(motors) do
        baseC0[name] = motor.C0
    end

    -- Animation state
    local wingAngle = 0
    local tailAngle = 0
    local breathAngle = 0

    RunService.Heartbeat:Connect(function(dt)
        -- Wing flapping (rotation around Z axis)
        wingAngle = wingAngle + dt * 6
        local flap = math.sin(wingAngle) * 0.5  -- 0.5 radians amplitude

        if motors.LWingFlap then
            -- Animate ON TOP of base C0, don't replace it!
            motors.LWingFlap.C0 = baseC0.LWingFlap * CFrame.Angles(0, 0, -flap)
        end
        if motors.RWingFlap then
            motors.RWingFlap.C0 = baseC0.RWingFlap * CFrame.Angles(0, 0, flap)
        end

        -- Tail sway (Y rotation for side-to-side)
        tailAngle = tailAngle + dt * 2
        local sway = math.sin(tailAngle) * 0.15

        for i = 1, 8 do
            local motor = motors["Tail" .. i]
            local base = baseC0["Tail" .. i]
            if motor and base then
                -- Each segment sways more than the previous
                motor.C0 = base * CFrame.Angles(0, sway * i * 0.3, 0)
            end
        end

        -- Breathing (subtle Y movement on chest)
        breathAngle = breathAngle + dt * 1.5
        local breathOffset = math.sin(breathAngle) * 0.1
        -- Apply to body motors...
    end)
end

Multi-Segment Neck Animation (Fire Breathing)

local function animateFireBreath(motors, baseC0, isBreathing, progress)
    if not isBreathing then return end

    -- progress: 0 to 1 over the breath duration
    local neckAngle

    if progress < 0.2 then
        -- Phase 1: Rear head back (windup)
        neckAngle = progress * 5 * math.rad(-20)
    else
        -- Phase 2: Thrust forward with shake
        neckAngle = math.rad(15) + math.sin(progress * 10) * math.rad(5)
    end

    -- Each neck segment gets progressively more rotation
    if motors.Neck1 then
        motors.Neck1.C0 = baseC0.Neck1 * CFrame.Angles(neckAngle * 0.5, 0, 0)
    end
    if motors.Neck2 then
        motors.Neck2.C0 = baseC0.Neck2 * CFrame.Angles(neckAngle * 0.7, 0, 0)
    end
    if motors.Neck3 then
        motors.Neck3.C0 = baseC0.Neck3 * CFrame.Angles(neckAngle, 0, 0)
    end
    if motors.Head then
        motors.Head.C0 = baseC0.Head * CFrame.Angles(neckAngle * 1.2, 0, 0)
    end

    -- Open jaw wide
    if motors.Jaw then
        motors.Jaw.C0 = baseC0.Jaw * CFrame.Angles(math.rad(35), 0, 0)
    end
end

Wing Structure (Bat-Style)

-- Detailed wing with multiple finger bones
local function createWing(side, shoulder, colors, scale)
    local sideName = side == 1 and "R" or "L"
    local motors = {}

    -- Upper arm
    local upperArmSize = Vector3.new(5, 1.2, 1.5) * scale
    local upperArm = createPart(upperArmSize, colors.secondary)
    motors.WingFlap = createMotor(shoulder, upperArm,
        CFrame.new(side * shoulder.Size.X/2, 0, 0) * CFrame.Angles(0, 0, math.rad(side * 10)),
        CFrame.new(-side * upperArmSize.X/2, 0, 0))

    -- Forearm
    local forearmSize = Vector3.new(4, 1, 1.2) * scale
    local forearm = createPart(forearmSize, colors.secondary)
    motors.Elbow = createMotor(upperArm, forearm,
        CFrame.new(side * upperArmSize.X/2, 0, 0),
        CFrame.new(-side * forearmSize.X/2, 0, 0))

    -- Wrist with 4 finger bones radiating out
    local wristSize = Vector3.new(1.5, 1.5, 1.5) * scale
    local wrist = createPart(wristSize, colors.primary)
    createMotor(forearm, wrist, ...)

    local fingerAngles = {-25, -8, 8, 25}  -- Spread angles in degrees
    local fingerLengths = {6, 7, 6, 4}

    for f = 1, 4 do
        local angle = math.rad(fingerAngles[f])

        -- First finger segment
        local finger1Size = Vector3.new(fingerLengths[f] * 0.6, 0.6, 0.5) * scale
        local finger1 = createPart(finger1Size, colors.secondary)
        createMotor(wrist, finger1,
            CFrame.new(side * wristSize.X/2, 0, 0) * CFrame.Angles(0, angle, 0),
            CFrame.new(-side * finger1Size.X/2, 0, 0))

        -- Second finger segment (tip)
        local finger2Size = Vector3.new(fingerLengths[f] * 0.5, 0.4, 0.4) * scale
        local finger2 = createPart(finger2Size, colors.secondary)
        createMotor(finger1, finger2,
            CFrame.new(side * finger1Size.X/2, 0, 0),
            CFrame.new(-side * finger2Size.X/2, 0, 0))
    end

    -- Membrane between fingers (semi-transparent)
    for m = 1, 3 do
        local membrane = createPart(Vector3.new(5, 0.15, 4) * scale, colors.secondary)
        membrane.Transparency = 0.2
        -- Position between finger angles...
    end

    return motors
end

-- Animate custom rig
local function animateCustomRig(motors, animationData)
    -- animationData: {motorName = {CFrame sequence}}

    local time = 0
    local duration = animationData.duration or 1

    RunService.RenderStepped:Connect(function(dt)
        time = (time + dt) % duration
        local t = time / duration

        for motorName, keyframes in pairs(animationData.motors or {}) do
            local motor = motors[motorName]
            if motor then
                -- Interpolate between keyframes
                local transform = interpolateKeyframes(keyframes, t)
                motor.Transform = transform
            end
        end
    end)
end

Humanoid Description for NPCs

local function applyHumanoidDescription(character, description)
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    if not humanoid then return end

    -- Create or modify description
    local desc = description or Instance.new("HumanoidDescription")

    -- Body parts
    desc.Head = 123456789  -- Asset ID
    desc.Torso = 123456789
    desc.LeftArm = 123456789
    desc.RightArm = 123456789
    desc.LeftLeg = 123456789
    desc.RightLeg = 123456789

    -- Animations
    desc.IdleAnimation = 123456789
    desc.WalkAnimation = 123456789
    desc.RunAnimation = 123456789
    desc.JumpAnimation = 123456789
    desc.FallAnimation = 123456789

    -- Body scales
    desc.HeadScale = 1
    desc.BodyTypeScale = 0.5
    desc.ProportionScale = 1
    desc.WidthScale = 1
    desc.HeightScale = 1
    desc.DepthScale = 1

    humanoid:ApplyDescription(desc)
end

Animation Performance

Animation Caching

local AnimationCache = {}
AnimationCache.cache = {}

function AnimationCache.load(animator, animationId)
    local cacheKey = tostring(animator) .. "_" .. animationId

    if AnimationCache.cache[cacheKey] then
        return AnimationCache.cache[cacheKey]
    end

    local animation = Instance.new("Animation")
    animation.AnimationId = animationId

    local track = animator:LoadAnimation(animation)
    AnimationCache.cache[cacheKey] = track

    return track
end

function AnimationCache.clear(animator)
    local prefix = tostring(animator) .. "_"

    for key, track in pairs(AnimationCache.cache) do
        if string.sub(key, 1, #prefix) == prefix then
            track:Stop()
            track:Destroy()
            AnimationCache.cache[key] = nil
        end
    end
end

LOD for Animations

local AnimationLOD = {}

function AnimationLOD.setup(character, camera)
    local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
    local hrp = character:WaitForChild("HumanoidRootPart")

    local LOD_DISTANCES = {50, 100, 200}
    local UPDATE_RATES = {1, 0.5, 0.25, 0.1}  -- Animation update rate

    local lastUpdate = 0
    local currentLOD = 1

    RunService.Heartbeat:Connect(function()
        local distance = (hrp.Position - camera.CFrame.Position).Magnitude

        -- Determine LOD level
        local lodLevel = 1
        for i, threshold in ipairs(LOD_DISTANCES) do
            if distance > threshold then
                lodLevel = i + 1
            end
        end

        -- Update animation rate based on LOD
        if lodLevel ~= currentLOD then
            currentLOD = lodLevel

            -- Adjust all playing animations
            for _, track in ipairs(animator:GetPlayingAnimationTracks()) do
                -- Distant characters: slower animation updates
                -- This is a simplified approach; Roblox handles this internally
            end
        end
    end)
end

Pooled Animation Tracks

local TrackPool = {}
TrackPool.pools = {}

function TrackPool.getTrack(animator, animationId)
    local poolKey = animationId

    if not TrackPool.pools[poolKey] then
        TrackPool.pools[poolKey] = {
            available = {},
            inUse = {}
        }
    end

    local pool = TrackPool.pools[poolKey]

    -- Check for available track
    local track = table.remove(pool.available)

    if not track then
        -- Create new track
        local animation = Instance.new("Animation")
        animation.AnimationId = animationId
        track = animator:LoadAnimation(animation)
    end

    table.insert(pool.inUse, track)
    return track
end

function TrackPool.releaseTrack(animationId, track)
    local pool = TrackPool.pools[animationId]
    if not pool then return end

    track:Stop(0)

    local index = table.find(pool.inUse, track)
    if index then
        table.remove(pool.inUse, index)
    end

    table.insert(pool.available, track)
end

Animation Tools

Animation Recording

local AnimationRecorder = {}

function AnimationRecorder.record(character, duration)
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    local motors = {}

    -- Find all Motor6Ds
    for _, motor in ipairs(character:GetDescendants()) do
        if motor:IsA("Motor6D") then
            table.insert(motors, motor)
        end
    end

    local keyframes = {}
    local startTime = os.clock()
    local recording = true

    -- Record at 30 fps
    local frameTime = 1/30
    local lastFrame = 0

    local conn
    conn = RunService.Heartbeat:Connect(function()
        local elapsed = os.clock() - startTime

        if elapsed >= duration then
            recording = false
            conn:Disconnect()
            return
        end

        if elapsed - lastFrame >= frameTime then
            lastFrame = elapsed

            local frame = {
                time = elapsed,
                poses = {}
            }

            for _, motor in ipairs(motors) do
                frame.poses[motor.Name] = {
                    C0 = motor.C0,
                    C1 = motor.C1,
                    Transform = motor.Transform
                }
            end

            table.insert(keyframes, frame)
        end
    end)

    -- Return promise-like
    return {
        getKeyframes = function()
            while recording do
                task.wait()
            end
            return keyframes
        end
    }
end

Animation Playback from Data

local function playRecordedAnimation(character, keyframes)
    local motors = {}

    for _, motor in ipairs(character:GetDescendants()) do
        if motor:IsA("Motor6D") then
            motors[motor.Name] = motor
        end
    end

    local duration = keyframes[#keyframes].time
    local startTime = os.clock()

    local conn
    conn = RunService.Heartbeat:Connect(function()
        local elapsed = os.clock() - startTime

        if elapsed >= duration then
            conn:Disconnect()
            return
        end

        -- Find surrounding keyframes
        local prevFrame, nextFrame
        for i, frame in ipairs(keyframes) do
            if frame.time <= elapsed then
                prevFrame = frame
                nextFrame = keyframes[i + 1]
            end
        end

        if not prevFrame or not nextFrame then return end

        -- Interpolate
        local t = (elapsed - prevFrame.time) / (nextFrame.time - prevFrame.time)

        for motorName, motor in pairs(motors) do
            local prevPose = prevFrame.poses[motorName]
            local nextPose = nextFrame.poses[motorName]

            if prevPose and nextPose then
                motor.Transform = prevPose.Transform:Lerp(nextPose.Transform, t)
            end
        end
    end)

    return conn
end

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Cursor

30.26%
按下载量换算32

Codex

22.96%
按下载量换算25

Claude Code

17.06%
按下载量换算18

windsurf

13.12%
按下载量换算14

OpenCode

8.08%
按下载量换算9

Antigravity

3.85%
按下载量换算4

安全审计

暂无安全审计结果可展示。

权限和风险

权限需确认

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills