◈ FIGURA
Complete Reference
The definitive guide to everything you can (and can't) do with Figura — avatars, Lua scripting, every API, code examples, and more.
Figura is a Minecraft Java client mod that lets you fully replace your player model with a custom Avatar made in Blockbench, scripted in Lua. Other players running Figura see your avatar synced in real time — no server mod required.
This wiki covers the full Figura 0.1.5 API for Minecraft 1.21.1, extracted directly from the mod source code and JAR files.
What You Can & Cannot Do
Figura runs a sandboxed Lua 5.2 environment. Understanding the limits is crucial before building complex avatars.
✓ You Can Do
avatar:store()✗ You Cannot Do
debug, dofile, loadfile, or collectgarbage — these are removed from the sandboxInstallation
Install a Mod Loader
Figura supports Fabric, Forge, Quilt, and NeoForge for Minecraft 1.21.1. Get the latest loader for your choice.
Download Figura
Get figura-0.1.5-1.21.1-[loader].jar from Modrinth, CurseForge, or GitHub. Drop it into .minecraft/mods/.
Launch & Find the Button
Boot Minecraft. Figura adds a button to your inventory screen. Click it to open the Wardrobe.
Create Your Avatar Folder
Navigate to .minecraft/figura/avatars/ and create a new folder. Inside it, add at minimum an empty file named avatar.json.
File Structure
.minecraft/figura/avatars/ └── MyAvatar/ ├── avatar.json ← Required: metadata (can be empty {}) ├── model.bbmodel ← Blockbench model file ├── skin.png ← Texture (referenced by model) ├── skin_e.png ← Emissive layer (suffix _e) ├── script.lua ← Main Lua script ├── utils.lua ← Additional scripts (all auto-loaded) ├── sounds/ │ └── blast.ogg ← Custom sounds (.ogg only) └── libs/ └── math_utils.lua ← Use require("libs/math_utils")
require("filename") (without .lua) only when you need to control load order or get return values from another script.avatar.json
The avatar.json file is the only required file. It tells Figura the folder is an avatar. All fields are optional.
{
"name": "My Cool Avatar",
"description": "A custom Figura avatar",
"authors": ["YourName"],
"version": "1.0.0",
"color": "#5af0c8"
}Wardrobe Screen
| Button | Action |
|---|---|
↑ Upload | Upload avatar to the Figura cloud (Main, Server, or both) |
↺ Reload | Reload avatar from disk without restarting Minecraft |
✕ Delete | Remove avatar from the cloud (Main, Server, or both) |
⊞ Folder | Open figura/avatars/ in your file manager |
✦ New Avatar | Launch Avatar Wizard to generate a starter avatar |
♪ Sounds | List all sounds the selected avatar has registered |
⌨ Keybinds | Show keybinds registered by the selected avatar |
Lua Scripting Basics
Figura runs Lua 5.2 in a sandbox. The following standard Lua globals are removed for safety: debug, dofile, loadfile, collectgarbage.
Figura-Added Globals
Figura adds these functions on top of standard Lua:
| Function | Description |
|---|---|
print(...) | Print to the in-game chat (multiple args allowed) |
printTable(t) | Print a table or Figura type to chat |
printJson(str) | Print a Minecraft JSON string formatted to chat |
parseJson(str) | Convert a JSON string to a Lua table |
toJson(val) | Convert a Lua value to a JSON string |
require("name") | Load another script by name (without .lua). Only runs once; returns its return value. |
listFiles(path) | Return a table of all script filenames at a path (nil = root) |
type(val) | Overridden: returns Figura type name for Figura objects |
vec(x,y,z,w) | Alias for vectors.vec() |
log(...) | Same as print — writes to chat |
Added Math Functions
| Function | Description |
|---|---|
math.lerp(a, b, t) | Linear interpolation. Works on numbers and vectors |
math.clamp(n, min, max) | Clamp a number between min and max |
math.round(n) | Round to nearest integer |
math.map(n, a1, b1, a2, b2) | Map value from one range to another |
math.shortAngle(a, b) | Shortest angle between two angles |
math.lerpAngle(a, b, t) | Lerp between two angles (wraps around 360°) |
math.sign(n) | Returns -1, 0, or 1 based on sign of n |
math.playerScale | Constant: player scaling relative to world |
math.worldScale | Constant: world scaling relative to player |
-- All scripts run top to bottom when loaded vanilla_model.ALL:setVisible(false) -- Use require to share code between files local utils = require("libs/utils") -- math.lerp works on vectors too local v1 = vec(0, 0, 0) local v2 = vec(1, 2, 3) local v3 = math.lerp(v1, v2, 0.5) -- {0.5, 1, 1.5} -- type() returns Figura type names print(type(v3)) -- "Vector3"
All Global APIs
| Global | Description | Notes |
|---|---|---|
player | Proxy for the avatar owner's player entity | all |
user | Alias for player | |
models | Root of all loaded .bbmodel parts | all |
events | Register callbacks for game events | all |
animations | Access all Blockbench animations | all |
vanilla_model | Control vanilla player model visibility | perm |
pings | Functions synced to all viewers | all |
action_wheel | Build the radial action wheel menu | all |
renderer | Camera, FOV, fire, outline, arms | all |
nameplate | Customize the player nameplate | perm |
keybinds | Register custom key bindings | host |
sounds | Play vanilla/custom sounds | all |
particles | Spawn vanilla particles | all |
world | Query blocks, biomes, entities, time | all |
client | FPS, window, camera, system time | all |
host | Host-only: chat, title, slots, cursor | host |
avatar | Avatar metadata, complexity, UUID | all |
textures | Create and manipulate textures at runtime | all |
config | Persist data between sessions | host |
raycast | Raycast blocks, entities, AABBs | all |
net | HTTP requests and WebSockets | perm High+ |
file | Read/write files in the avatar folder | perm |
data | Create byte buffers | all |
json | Build JSON objects programmatically | all |
resources | Access game resource pack data | all |
vectors | Construct Vec2/3/4 | all |
matrices | Construct Mat2/3/4 | all |
host:isHost() before host-only logic if needed.Events
Register functions with events.EVENT_NAME:register(fn, "optional_name") or the shorthand function events.EVENT_NAME() end.
-- Shorthand syntax function events.TICK() -- runs every game tick end -- Register syntax (allows multiple handlers, named removal) events.TICK:register(function() -- also runs every tick end, "myHandler") -- Remove by name events.TICK:remove("myHandler") -- Clear all handlers from an event events.TICK:clear()
All Available Events
-- RENDER: smooth rotation following body yaw function events.RENDER(delta, context) local yaw = player:getBodyYaw(delta) models.MyModel.Body:setOffsetRot(0, -yaw, 0) end -- TICK: run only on host function events.TICK() if not host:isHost() then return end -- host-only logic here end -- CHAT_SEND_MESSAGE: cancel any message containing "oops" function events.CHAT_SEND_MESSAGE(msg) if msg:find("oops") then return false end end -- ON_PLAY_SOUND: mute explosion sounds function events.ON_PLAY_SOUND(id, pos, vol, pitch, loop) if id == "minecraft:entity.generic.explode" then return true -- cancel the sound end end
ModelPart API
Every cube, group, and mesh in your Blockbench model is a ModelPart. Access them through models.ModelName.PartName.
Transform Functions
local part = models.MyModel.Head -- Position (offset from Blockbench position) part:setPos(0, 5, 0) -- move up 5 units part:getPos() -- returns Vec3 part:getTruePos() -- pos + anim pos -- Rotation (absolute, in degrees) part:setRot(45, 0, 0) -- tilt 45° on X part:setOffsetRot(0, 10, 0) -- relative rot offset -- Scale (multiplier, 1 = normal) part:setScale(1.5, 1, 1) -- 150% width part:setOffsetScale(2, 2, 2)-- relative scale -- Pivot (absolute) part:setPivot(0, 24, 0) part:setOffsetPivot(0, 4, 0)
Visibility & Rendering
-- Visibility part:setVisible(false) -- hide (children too) part:getVisible() -- true if this and all parents visible -- Color (RGB 0-1) part:setColor(1, 0.5, 0) -- orange tint part:setPrimaryColor(1, 0, 0) part:setSecondaryColor(0, 0, 1) -- Opacity (0-1). Needs TRANSLUCENT render type! part:setOpacity(0.5) -- Custom light level (0-15 block light, 0-15 sky light) part:setLight(15, 15) -- full bright -- Overlay (white flash = 0-15, damage red = 0-15) part:setOverlay(15, 0) -- white flash -- Render type part:setPrimaryRenderType("TRANSLUCENT") part:setSecondaryRenderType("EYES") -- UV scrolling (0.0–1.0 normalized) part:setUV(0.5, 0) -- scroll half width part:setUVPixels(8, 0) -- in pixels -- Parent type (controls where in skeleton this attaches) part:setParentType("Head") -- Texture override part:setPrimaryTexture("RESOURCE", "minecraft:textures/entity/creeper/creeper.png") part:setPrimaryTexture("CUSTOM", textures["myTexture"])
Render Tasks (Attach to Parts)
-- Add a text label floating above a part local task = part:newText("myLabel") task:setText('{"text":"Hello World","color":"gold"}') task:setPos(0, 12, 0) task:setScale(0.5, 0.5, 0.5) -- Add a floating item local item = part:newItem("myItem") item:setItem("minecraft:diamond_sword") item:setDisplayMode("GROUND") -- Add a floating block local block = part:newBlock("myBlock") block:setBlock("minecraft:tnt") block:setScale(0.5, 0.5, 0.5) -- Add a sprite (texture quad) local spr = part:newSprite("mySprite") spr:setTexture(textures["myTex"], 16, 16) -- Remove a task part:removeTask("myLabel")
World Matrix
-- Get world position of a part (use in POST_RENDER) function events.POST_RENDER() local mat = models.MyModel.Hand:partToWorldMatrix() local worldPos = mat:apply(vec(0, 0, 0)) -- worldPos is now the hand's world coordinate end
Vanilla Model Parts
All available groups and parts under vanilla_model:
| Part | Description |
|---|---|
vanilla_model.ALL | Multi-part: entire vanilla model |
vanilla_model.PLAYER | Multi-part: outer + inner layers + cape |
vanilla_model.INNER_LAYER | Multi-part: main body (no outer layer) |
vanilla_model.OUTER_LAYER | Multi-part: outer layer (hat, jacket, sleeves, pants) |
vanilla_model.HEAD | Head (no hat) |
vanilla_model.BODY | Body (no jacket) |
vanilla_model.LEFT_ARM | Left arm (no sleeve) |
vanilla_model.RIGHT_ARM | Right arm (no sleeve) |
vanilla_model.LEFT_LEG | Left leg (no pants) |
vanilla_model.RIGHT_LEG | Right leg (no pants) |
vanilla_model.HAT | Hat layer |
vanilla_model.JACKET | Jacket layer |
vanilla_model.LEFT_SLEEVE | Left sleeve layer |
vanilla_model.RIGHT_SLEEVE | Right sleeve layer |
vanilla_model.LEFT_PANTS | Left pants layer |
vanilla_model.RIGHT_PANTS | Right pants layer |
vanilla_model.CAPE | Multi-part: cape |
vanilla_model.ARMOR | Multi-part: all armor |
vanilla_model.HELMET | Multi-part: helmet |
vanilla_model.CHESTPLATE | Multi-part: chestplate |
vanilla_model.LEGGINGS | Multi-part: leggings |
vanilla_model.BOOTS | Multi-part: boots |
vanilla_model.ELYTRA | Multi-part: elytra wings |
vanilla_model.HELD_ITEMS | Multi-part: items in hands |
vanilla_model.LEFT_ITEM | Item in left hand |
vanilla_model.RIGHT_ITEM | Item in right hand |
vanilla_model.PARROTS | Multi-part: shoulder parrots |
-- Hide everything except armor vanilla_model.ALL:setVisible(false) vanilla_model.ARMOR:setVisible(true) -- Read vanilla head rotation (for coping to custom model) local headRot = vanilla_model.HEAD:getOriginRot() models.MyModel.Head:setOffsetRot(headRot)
Animations
-- Access: animations.modelName.animationName local walk = animations.MyModel.walk -- Playback controls walk:play() walk:pause() walk:resume() walk:stop() walk:restart() -- State checks walk:isPlaying() -- true/false walk:isPaused() walk:isStopped() -- Time control walk:setTime(1.5) -- jump to 1.5 seconds walk:getTime() walk:setSpeed(2.0) -- 2x speed walk:setWeight(0.5) -- blend weight 0-1 -- Override vanilla transforms walk:setOverride(true) -- Loop mode walk:setLoopMode("LOOP") -- "ONCE", "LOOP", "HOLD" -- Blend in/out over N ticks walk:setBlend(5) -- Play animation based on player movement function events.TICK() if player:isMoving() then animations.MyModel.walk:play() else animations.MyModel.walk:stop() end end
Player API
Available via the global player (or user). Extends Entity and LivingEntity.
| Method | Returns | Description |
|---|---|---|
:getFood() | int | Hunger level 0–20 |
:getSaturation() | number | Saturation level |
:getExhaustion() | number | Exhaustion level |
:getExperienceProgress() | number | XP progress 0–1 |
:getExperienceLevel() | int | Current level |
:getModelType() | string | "SLIM" or "DEFAULT" |
:getGamemode() | string | "SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR" |
:hasCape() | bool | Has a cape loaded |
:hasSkin() | bool | Has a custom skin |
:isSkinLayerVisible(layer) | bool | Skin customization layer visible |
:isFishing() | bool | Currently fishing |
:getShoulderEntity(left) | table | NBT of shoulder parrot (true=left) |
:getTeamInfo() | table | Team data or nil |
:getCooldownPercent(item) | number | Item cooldown 0–1 |
LivingEntity Methods (inherited by player)
| Method | Returns | Description |
|---|---|---|
:getHealth() | number | Current health |
:getMaxHealth() | number | Max health |
:getArmor() | int | Armor points |
:getBodyYaw(delta) | number | Body yaw in degrees |
:getHeldItem(offhand) | ItemStack | Item in hand |
:getActiveItem() | ItemStack | Item currently being used |
:isUsingItem() | bool | Eating/blocking/etc |
:isClimbing() | bool | On ladder/vine |
:isGliding() | bool | Elytra gliding |
:isBlocking() | bool | Shield blocking |
:getDeathTime() | int | Ticks dead (0 if alive) |
:getArrowCount() | int | Arrows stuck in entity |
:getAbsorptionAmount() | number | Absorption hearts |
:getSwingArm() | string | "MAIN_HAND" or "OFF_HAND" |
:isSwingingArm() | bool | Arm is swinging |
Entity Methods (inherited by player)
| Method | Returns | Description |
|---|---|---|
:getPos(delta) | Vec3 | World position (interpolated with delta) |
:getRot(delta) | Vec2 | Pitch and yaw in degrees |
:getVelocity() | Vec3 | Current velocity |
:getLookDir() | Vec3 | Unit vector in look direction |
:getUUID() | string | UUID as string |
:getType() | string | Entity id e.g. "minecraft:player" |
:getName() | string | Display name |
:getPose() | string | "STANDING", "CROUCHING", "SWIMMING", etc. |
:isOnGround() | bool | Currently on ground |
:isSprinting() | bool | Currently sprinting |
:isSneaking() | bool | Logic sneak (no fall off edges) |
:isCrouching() | bool | Visual sneak |
:isMoving(noY) | bool | Has velocity (noY=true ignores vertical) |
:isFalling() | bool | Negative Y velocity and not on ground |
:isInWater() | bool | In water block |
:isUnderwater() | bool | Eyes submerged |
:isInLava() | bool | In lava |
:isOnFire() | bool | Currently burning |
:isWet() | bool | In water, rain, or bubble column |
:isGlowing() | bool | Has glow effect |
:isInvisible() | bool | Has invisibility |
:isAlive() | bool | Alive (not dead) |
:getNbt() | table | Entity NBT as Lua table |
:getItem(slot) | ItemStack | Item at slot (1=mainhand, 2=offhand, 3-6=armor) |
:getBoundingBox() | Vec3 | Hitbox size (width, height, width) |
:getVehicle() | Entity | Entity being ridden (or nil) |
:getTargetedBlock(dist) | BlockState | Targeted block (max 20 blocks) |
:getTargetedEntity(dist) | Entity | Targeted entity (max 20 blocks) |
:getNearestEntity(type,radius) | Entity | Nearest entity (optional type filter) |
:hasAvatar() | bool | Entity has a Figura avatar loaded |
:getPermissionLevel() | int | Permission level (4 = op) |
:getVariable(key) | any | Read another avatar's stored variable |
:getDimension() | string | Dimension id |
:getFrozenTicks() | int | Ticks in powder snow |
World API
-- Block queries local bs = world:getBlockState(vec(0, 64, 0)) print(bs:getId()) -- "minecraft:grass_block" print(bs:getProperties()) -- {snowy=false, ...} bs:hasTag("minecraft:logs") -- tag check bs:isOpaque() -- Get area of blocks (max 8x8x8) local blocks = world:getBlocks(vec(-2,60,-2), vec(2,64,2)) -- Biome local biome = world:getBiome(player:getPos()) print(biome:getId()) -- "minecraft:plains" biome:getTemperature() biome:isHot() biome:isCold() biome:hasRain() biome:hasSnow() -- Light world:getLightLevel(player:getPos()) world:getSkyLightLevel(pos) world:getBlockLightLevel(pos) world:isOpenSky(pos) -- Time & weather world:getTime() -- total world time world:getTimeOfDay() -- 0-24000 world:getDayTime() -- 0-24000 world:getDay() world:getMoonPhase() -- 0-7 world:getRainGradient(delta) -- 0-1 world:isThundering() -- Entities local players = world:getPlayers() -- {name=EntityAPI, ...} local ents = world:getEntities(pos1, pos2) local ent = world:getEntity("uuid-string") -- Misc world:getDimension() -- "minecraft:overworld" world:getSpawnPoint() world:getHeight(x, z) -- highest block at XZ world:exists() -- world loaded? -- Parse new block/item from string world:newBlock("minecraft:stone[lit=true]") world:newItem("minecraft:diamond_sword", 1)
Renderer API
Controls how your avatar is rendered. Changes are local — only affect your own view for host-side settings.
-- Toggle rendering features renderer:setRenderFire(false) -- hide fire overlay renderer:setRenderVehicle(false) -- hide your vehicle renderer:setRenderCrosshair(false) -- hide crosshair renderer:setRenderHud(false) -- hide vanilla HUD renderer:setForcePaperdoll(true) -- always show paperdoll renderer:setUpsideDown(true) -- render upside down -- Shadow renderer:setShadowRadius(3) -- 0–12 (default 0.5) -- Camera position offset renderer:setCameraPos(0, 0.5, 0) renderer:setOffsetCameraPivot(0, 1, 0) -- Camera rotation offset renderer:setCameraRot(0, 45, 0) -- absolute renderer:setOffsetCameraRot(0, 10, 0)-- offset -- FOV multiplier renderer:setFov(1.5) -- 150% FOV (nil = default) -- Custom outline color (for glowing effect) renderer:setOutlineColor(1, 0, 0, 1) -- red glow -- Custom fire texture renderer:setPrimaryFireTexture("figura:textures/fire.png") -- First-person arm visibility renderer:setRenderLeftArm(true) renderer:setRenderRightArm(true) -- Eye offset (affects targeted block/entity) renderer:setEyeOffset(0, 0.5, 0) -- Block outline color renderer:setBlockOutlineColor(0, 1, 1, 0.4) -- Post effect (Super Secret Settings style) renderer:setPostEffect("desaturate") -- Query states renderer:isFirstPerson() renderer:isCameraBackwards()
Host API host only
All host functions only work when you are the avatar's owner. On other clients they silently do nothing.
-- Check if running as host if not host:isHost() then return end -- Logging host:writeToLog("debug message") -- to minecraft log, not chat host:warnToLog("warning!") -- Title / subtitle / actionbar host:setTitle('{"text":"Hello","color":"gold"}') host:setSubtitle('{"text":"subtitle"}') host:setActionbar('{"text":"action bar"}') host:setTitleTimes(10, 40, 10) -- fade-in, stay, fade-out ticks host:clearTitle() -- Chat host:sendChatMessage("Hello world!") host:sendChatCommand("gamemode creative") host:appendChatHistory("my message") host:getChatMessage(1) -- last chat message host:setChatMessage(1, nil) -- nil removes it host:getChatText() -- text being typed host:setChatText("draft") host:isChatOpen() -- Inventory slots host:getSlot(0) -- get first hotbar slot host:getSlot("armor.head") host:setSlot(-1, world:newItem("minecraft:stick")) -- Cursor host:setUnlockCursor(true) -- free the cursor host:isCursorUnlocked() -- Arm swing host:swingArm(false) -- false=main, true=offhand -- Clipboard host:getClipboard() host:setClipboard("text") -- Screen info host:getScreen() -- current screen class name host:isContainerOpen() host:getScreenSlot(0) -- Status effects local effects = host:getStatusEffects() -- [{name=..., amplifier=..., duration=..., ambient=..., particles=...}] -- Screenshot (returns Texture) local tex = host:screenshot() -- Player state host:isJumping() host:isFlying() host:getAttackCharge() -- 0-1 host:getAir() host:getReachDistance() host:getPickBlock() -- targeted block host:getPickEntity() -- targeted entity host:isAvatarUploaded() -- Camera host:isFirstPerson() host:isCameraBackwards()
Client API
-- Performance / version client:getFps() client:getVersion() -- "1.21.1" client:getFiguraVersion() -- "0.1.5" client:getClientBrand() -- "fabric", "forge", etc. client:getServerBrand() -- "vanilla", "paper", etc. client:isSnapshot() -- Window & GUI client:getWindowSize() -- Vec2 in pixels client:getScaledWindowSize() -- Vec2 in GUI units client:getGuiScale() client:getMousePos() -- Vec2 pixel coords client:isWindowFocused() client:isHudEnabled() client:isDebugOverlayEnabled() -- F3 screen open client:isPaused() client:getFov() -- Camera client:getCameraPos() client:getCameraRot() client:getCameraDir() client:getCameraEntity() -- spectated entity or self client:getViewer() -- the local player entity -- Time client:getSystemTime() -- Unix time in milliseconds client:getDate() -- {year, month, day, hour, ...} client:getFrameTime() -- delta (same as RENDER event) -- Text measuring client:getTextWidth("Hello") client:getTextHeight("Hello") -- Memory client:getUsedMemory() client:getMaxMemory() client:getJavaVersion() -- Mod detection client:isModLoaded("sodium") client:hasShaderPackMod() client:hasShaderPack() client:getShaderPackName() -- Server info client:getServerData() -- {name, ip, motd, ...} client:getActiveLang() -- "en_us" -- HUD overlays client:getActionbar() client:getTitle() client:getSubtitle() client:getScoreboard() client:getTabList() -- Registries & resources client:getRegistry("block") -- all block IDs client:hasResource("minecraft:textures/block/stone.png") client:getActiveResourcePacks() -- UUID utils client:generateUUID() client:compareVersions("0.1.4", "0.1.5") -- -1, 0, 1 -- Translation client:getTranslatedString("item.minecraft.diamond")
Sounds API
-- Play a vanilla sound at a world position sounds:playSound("minecraft:entity.player.levelup", player:getPos(), -- position 1.0, -- volume 1.0 -- pitch ) -- Play a custom sound from your avatar (.ogg file) sounds:playSound("sounds/blast", pos, 1, 1) -- Stop all sounds from your avatar sounds:stopSound() sounds:stopSound("minecraft:music.game") -- specific ID -- Register a custom sound at runtime from bytes sounds:newSound("mySound", byteArray) -- Check if a sound ID is valid sounds:isPresent("minecraft:block.stone.break") -- Get all custom sounds registered by this avatar local customSounds = sounds:getCustomSounds()
Particles API
-- Spawn a particle particles:newParticle( "minecraft:flame", -- particle ID player:getPos(), -- world position vec(0, 0.1, 0) -- velocity ) -- Special dust particle (requires color + size) particles:newParticle( "minecraft:dust 1 0 0 1.5", -- red dust, size 1.5 pos ) -- Remove all particles from this avatar particles:removeParticles() -- Check if a particle ID exists particles:isPresent("minecraft:heart") -- Spawn hearts in a circle every tick function events.TICK() for i = 0, 7 do local angle = i / 8 * math.pi * 2 local pos = player:getPos() + vec(math.cos(angle), 2, math.sin(angle)) particles:newParticle("minecraft:heart", pos) end end
Keybinds API host only
-- Create a keybind local kb = keybinds:newKeybind("Toggle Wings", "key.keyboard.g") -- Handle press and release kb:setOnPress(function(modifiers) -- modifiers: bitmask (1=shift, 2=ctrl, 4=alt) pings.toggleWings(true) end) kb:setOnRelease(function() pings.toggleWings(false) end) -- Check state kb:isPressed() kb:getKey() -- current key string kb:getKeyName() -- localized name kb:isDefault() -- not remapped by user? kb:setEnabled(false) -- disable this keybind kb:setGui(true) -- also trigger in GUIs -- Change the key kb:setKey("key.keyboard.h")
Action Wheel
Hold the Action Wheel key (default B) to show the wheel. Up to 8 slots per page.
-- Create a page and set it active local page = action_wheel:newPage("main") action_wheel:setPage(page) -- Add a simple action local act = page:newAction() :title("Fly Mode") :item("minecraft:elytra") :color(0.4, 0.8, 1) :hoverColor(0.7, 1, 1) :onLeftClick(function() pings.toggleFly() end) -- Toggle action (remembers state) local toggle = page:newAction() :title("Wings OFF") :toggleTitle("Wings ON") :item("minecraft:feather") :onToggle(function(state) pings.setWings(state) end) -- Scroll action local scroll = page:newAction() :title("Volume") :onScroll(function(dir) volume = math.clamp(volume + dir * 0.1, 0, 1) end) -- Custom texture on action act:setTexture(textures["myTex"], 0, 0, 16, 16) -- Multiple pages local page2 = action_wheel:newPage("weapons") action_wheel:setPage(page2) -- switch to it -- Check wheel state action_wheel:isEnabled() -- wheel open? action_wheel:getSelected() -- selected slot index
Pings
Pings let you sync state from the host to all viewers. Define a function under pings, and when the host calls it, everyone runs it.
-- Define ping functions function pings.setWingState(visible) models.MyModel.Wings:setVisible(visible) end function pings.playEffect(effectName) sounds:playSound("minecraft:entity.player.levelup", player:getPos()) particles:newParticle("minecraft:totem_of_undying", player:getPos()) end -- Call from host (e.g., on key press) function events.TICK() if not host:isHost() then return end if wingKeybind:isPressed() then pings.setWingState(true) end end
Nameplate API requires Default+
-- Three nameplate locations -- nameplate.ABOVE: above-head nameplate -- nameplate.CHAT: name in chat -- nameplate.LIST: name in tab list -- Customize the above-head nameplate nameplate.ABOVE:setText('[{"text":"★ ","color":"gold"},{"text":"MyName","color":"white"}]') nameplate.ABOVE:setPos(0, 0.5, 0) nameplate.ABOVE:setScale(1.5, 1.5, 1.5) nameplate.ABOVE:setVisible(true) nameplate.ABOVE:setBackground(false) -- hide dark backdrop nameplate.ABOVE:setShadow(true) -- Chat nameplate nameplate.CHAT:setText('[{"text":"☆ "},{"text":"MyName"}]') -- Dynamic nameplate update every tick function events.TICK() local hp = math.floor(player:getHealth()) nameplate.ABOVE:setText('{"text":"HP: ' .. hp .. '","color":"red"}') end
Textures API
-- Create a new texture (filled with color) local tex = textures:newTexture("myTex", 64, 64) -- Pixel manipulation tex:setPixel(0, 0, vec(1, 0, 0, 1)) -- RGBA 0-1 local col = tex:getPixel(0, 0) -- Fill rectangle tex:fill(0, 0, 32, 32, vec(0, 1, 0, 1)) -- green -- Apply changes to GPU tex:update() -- call after pixel edits -- Copy and retrieve textures local copy = textures:copy("texCopy", tex) local found = textures:get("myTex") -- Grab a vanilla resource texture local stone = textures:fromVanilla("minecraft:textures/block/stone.png") -- Read from base64 string local tex2 = textures:read("decodedTex", base64str) -- Apply texture to a model part models.MyModel.Head:setPrimaryTexture("CUSTOM", tex) -- Animated texture example (in TICK) local t = 0 function events.TICK() t = t + 1 for x = 0, 15 do for y = 0, 15 do local v = (math.sin(x * 0.5 + t * 0.1) + 1) / 2 tex:setPixel(x, y, vec(v, 0.5, 1, 1)) end end tex:update() end
Config API host only
Save and load data between game sessions. Data is stored in a file in the avatar folder.
-- Save a value (any Lua primitive or table) config:save("wingEnabled", true) config:save("volume", 0.75) config:save("theme", {r=1, g=0.5, b=0}) -- Load a value (nil if not saved) local enabled = config:load("wingEnabled") -- Load all saved values local all = config:load() -- Remove a value config:save("wingEnabled", nil) -- Change save file name (default = avatar name) config:setName("myAvatarData") -- Typical pattern: load on init, save on change local wingEnabled = config:load("wing") or false function pings.toggleWing() wingEnabled = not wingEnabled models.MyModel.Wings:setVisible(wingEnabled) if host:isHost() then config:save("wing", wingEnabled) end end
Raycast API
-- Raycast a block local blockState, hitPos, hitSide = raycast:block( player:getPos() + vec(0, player:getEyeHeight(), 0), -- from eye pos player:getPos() + player:getLookDir() * 10, -- to 10 blocks ahead "COLLIDER", -- BlockRaycastType "NONE" -- FluidRaycastType ) if blockState then print(blockState:getId(), hitPos, hitSide) end -- Raycast an entity local entity, hitPos = raycast:entity( player:getPos() + vec(0, player:getEyeHeight(), 0), player:getPos() + player:getLookDir() * 15 ) if entity then print("Hit: ", entity:getName()) end -- AABB raycast (custom bounding boxes) local hit = raycast:aabb( startPos, endPos, {{-0.5, 0, -0.5, 0.5, 2, 0.5}} -- {minX,minY,minZ,maxX,maxY,maxZ} )
Networking API requires High+ permission
-- Check if networking is allowed if not net:isNetworkingAllowed() then return end -- GET request local future = net.http:request("https://api.example.com/data") :method("GET") :send() -- POST with body and headers local future2 = net.http:request("https://api.example.com/submit") :method("POST") :header("Content-Type", "application/json") :body('{"key":"value"}') :send() -- Handle the future result in TICK function events.TICK() if future and future:isDone() then if future:hasError() then print("Error!") else local resp = future:getValue() print(resp:getResponseCode()) -- resp:getData() returns an InputStream end future = nil end end
Render Tasks
Render Tasks attach to ModelParts and can render text, items, blocks, sprites, or entities at the part's location.
| Task Type | Key Methods | Description |
|---|---|---|
newText(name) | :setText(json), :setAlignment(), :setShadow(), :setBackground() | Render a JSON text component |
newItem(name) | :setItem(id), :setDisplayMode(mode) | Render an item in any display mode |
newBlock(name) | :setBlock(id) | Render a full block |
newSprite(name) | :setTexture(tex,w,h), :setUV(u,v), :setSize(w,h) | Render a 2D texture quad |
newEntity(name) | :setNbt(str), :setHeadRotation() | Render any entity by NBT |
All tasks share :setPos(), :setRot(), :setScale(), :setVisible(), :setLight(), :setOverlay(), :setColor().
Emissive Textures
Emissive textures glow at full brightness regardless of world lighting.
skin.png ← base texture skin_e.png ← emissive layer (suffix _e before .png)
EYES: part:setPrimaryRenderType("EYES"). The default compatibility mode renders emissives as fullbright instead of actual bloom.Render Types
| Type | Description |
|---|---|
SOLID | Default opaque rendering |
CUTOUT | Alpha cutout (on/off transparency, no blending) |
CUTOUT_CULL | Cutout with back-face culling |
TRANSLUCENT | Alpha blending — required for setOpacity() |
TRANSLUCENT_CULL | Translucent with culling |
EYES | Fullbright, no shading — for emissive glow/bloom |
EMISSIVE | Emissive rendering with normal alpha blending |
EMISSIVE_SOLID | Emissive opaque |
END_PORTAL | The animated end portal effect |
GLINT | Enchantment glint overlay |
GLINT2 | Alternate enchantment glint |
LINES | Renders as wireframe lines |
Parent Types
Parent types attach a model part to a specific vanilla skeleton position. Set in Blockbench (part name) or via Lua.
| Parent Type | Attaches To |
|---|---|
Head | Player head bone |
Body | Player body bone |
LeftArm | Left arm bone |
RightArm | Right arm bone |
LeftLeg | Left leg bone |
RightLeg | Right leg bone |
LeftItemPivot | Left hand item position |
RightItemPivot | Right hand item position |
HelmetItemPivot | Head item (helmet slot item) position |
LeftElytra | Left elytra wing |
RightElytra | Right elytra wing |
Cape | Cape bone |
Camera | Follows the camera |
World | Fixed in world space (doesn't follow player) |
NONE | Not attached to any parent (default) |
-- Set parent type via Lua models.MyModel.WingL:setParentType("LeftElytra") models.MyModel.HUDElement:setParentType("Camera") -- Or name the Blockbench group starting with the parent type: -- Group named "Head" → auto-attaches to the head bone
Permissions System
Viewers control how much resource they grant to each avatar they see. You as the creator need to write your avatar to work gracefully within limits.
| Level | Summary | Features Unlocked |
|---|---|---|
| Blocked | Avatar completely disabled | None. Avatar not rendered or scripted. |
| Low | Minimal trust | Basic model rendering. Most Lua blocked. Ideal for tiny avatars. |
| Default | Standard trust | Vanilla model edits, animations, sounds, particles. No nameplate. |
| High | High trust | Everything in Default + nameplate, networking, custom render layers. |
| Max | Maximum trust | All features, unlimited resources. |
Configurable Limits per Level
| Setting | Controls |
|---|---|
| Init Instructions | Max Lua ops during script load phase |
| Tick Instructions | Max Lua ops per TICK event |
| World Tick Instructions | Max Lua ops per WORLD_TICK event |
| Render Instructions | Max Lua ops per RENDER event |
| World Render Instructions | Max Lua ops per WORLD_RENDER event |
| Animation Instructions | Max Lua ops per animation keyframe code |
| Max Complexity | Max rendered faces (1 cube = 6 faces; hidden parts don't count) |
| Animations Complexity | Max total animation channels playing at once |
| Max Particles / sec | Cap on particles spawned |
| Max Sounds / sec | Cap on sounds played |
| Avatar Sounds Volume | Volume multiplier for this avatar's sounds |
| Max Texture Size | Max pixel dimensions of runtime-created textures |
| Buffer Size / Count | Byte buffer limits for data processing |
| Max Sockets | Max concurrent WebSocket connections |
| Vanilla Model Change | Toggle/untoggle (Default+) |
| Nameplate Change | Toggle/untoggle (High+) |
| Networking | Toggle/untoggle (High+) |
| Custom Sounds | Toggle/untoggle (Default+) |
| Custom Skull | Toggle/untoggle — avatar renders on skull blocks |
| Cancel Sounds | Toggle/untoggle — ON_PLAY_SOUND can cancel sounds |
| Cancel Damage | Toggle/untoggle — DAMAGE event can cancel effects |
| Render Offscreen | Toggle — render even when not in viewer's FOV |
| Custom Render Layers | Toggle — custom GLSL shaders via render layers |
FAQ
avatar.json file to be detected. Even an empty {} works. Also check that your OS isn't hiding extensions — the file must be avatar.json, not avatar.json.txt.animations.ModelName.animName:play()
ModelName with the .bbmodel filename (no extension) and animName with the animation name inside Blockbench._e before the extension (e.g., skin_e.png). Set all non-glowing pixels to transparent black #00000000. For shader bloom use part:setPrimaryRenderType("EYES").setRot sets an absolute rotation — completely overrides the Blockbench rotation. setOffsetRot adds to the Blockbench rotation. Use setOffsetRot if you want to rotate relative to the original position in your model.host API only does something when the code runs on the avatar owner's machine. On viewers, host calls silently do nothing. Use pings to sync state, not host directly.local kb = keybinds:newKeybind("My Key", "key.keyboard.g") kb:setOnPress(function() pings.doThing(true) end) kb:setOnRelease(function() pings.doThing(false) end) function pings.doThing(active) models.MyModel.Part:setVisible(active) end
part:setPrimaryRenderType("TRANSLUCENT") part:setOpacity(0.5)
Resources & Links
| Resource | Description |
|---|---|
| ◈ Official Wiki | Full API reference maintained by the FiguraMC team |
| ◌ Discord | Community support, showcases, announcements |
| ⬡ Blockbench | Free 3D modeling software for Figura avatars |
| › Lua Quickstart | Beginner Lua guide by Manuel, tailored for Figura |
| ◎ Figs Offline Wiki | Web-accessible mirror of the Figura wiki |
| ✦ VSCode VSDocs | Autocomplete extension for Figura API in VS Code |
| ⊛ Figura VS Code Ext | VS Code extension with model path autocomplete |
| ▷ Video Tutorials | Chloe's comprehensive Figura tutorial series on YouTube |
| ⊞ Blockbench Plugin | Blockbench plugin for the Figura model format |
| ⇗ Weblate | Help translate Figura to your language |
| ↓ Modrinth | Download Figura from Modrinth |
| ♥ Open Collective | Support Figura development financially |
★ First Avatar — Complete Script
A minimal but complete avatar script covering the most common patterns: hide vanilla model, drive animations from player state, and set up a keybind with ping sync.
-- ───────────────────────────────────────────── -- SETUP — runs once when avatar loads -- ───────────────────────────────────────────── vanilla_model.ALL:setVisible(false) -- hide entire vanilla player vanilla_model.ARMOR:setVisible(true) -- but keep armor visible -- ───────────────────────────────────────────── -- STATE -- ───────────────────────────────────────────── local wingsOpen = false -- ───────────────────────────────────────────── -- PINGS — synced to all viewers -- ───────────────────────────────────────────── function pings.setWings(state) wingsOpen = state models.MyModel.Wings:setVisible(state) if state then animations.MyModel.wings_open:play() else animations.MyModel.wings_close:play() end end -- ───────────────────────────────────────────── -- KEYBIND (host only) -- ───────────────────────────────────────────── if host:isHost() then local wingKey = keybinds:newKeybind("Toggle Wings", "key.keyboard.g") wingKey:setOnPress(function() pings.setWings(not wingsOpen) end) end -- ───────────────────────────────────────────── -- TICK — game logic every 1/20 second -- ───────────────────────────────────────────── function events.TICK() -- Drive walk/idle animation from player movement if player:isMoving(true) then animations.MyModel.walk:play() animations.MyModel.idle:stop() else animations.MyModel.idle:play() animations.MyModel.walk:stop() end end -- ───────────────────────────────────────────── -- RENDER — every rendered frame (use delta!) -- ───────────────────────────────────────────── function events.RENDER(delta) -- Copy vanilla head rotation to custom head local rot = player:getRot(delta) models.MyModel.Head:setOffsetRot(rot.x, 0, 0) -- Smooth body yaw local yaw = player:getBodyYaw(delta) models.MyModel.Body:setOffsetRot(0, -yaw, 0) end
Animations — All Patterns
State Machine (walk / run / idle / fall)
local A = animations.MyModel -- shorthand function events.TICK() local onGround = player:isOnGround() local moving = player:isMoving(true) local sprinting = player:isSprinting() local falling = player:isFalling() local gliding = player:isGliding() local crouching = player:isCrouching() -- Stop all before starting the right one A.idle:stop() A.walk:stop() A.run:stop() A.fall:stop() A.glide:stop() A.crouch:stop() if gliding then A.glide:play() elseif not onGround and falling then A.fall:play() elseif crouching then A.crouch:play() elseif sprinting then A.run:play() elseif moving then A.walk:play() else A.idle:play() end end
Speed Walk Animation by Velocity
function events.TICK() local vel = player:getVelocity() local speed = math.sqrt(vel.x^2 + vel.z^2) -- horizontal speed animations.MyModel.walk:setSpeed(speed * 10) -- scale anim speed if speed > 0.01 then animations.MyModel.walk:play() else animations.MyModel.walk:stop() end end
Blend Two Animations by Health
function events.TICK() local hp = player:getHealth() local maxHp = player:getMaxHealth() local t = hp / maxHp -- 0 = dead, 1 = full animations.MyModel.healthy:setWeight(t) animations.MyModel.hurt:setWeight(1 - t) animations.MyModel.healthy:play() animations.MyModel.hurt:play() end
One-Shot Attack Animation on Key
local attackAnim = animations.MyModel.attack attackAnim:setLoopMode("ONCE") if host:isHost() then local atkKey = keybinds:newKeybind("Attack", "key.keyboard.f") atkKey:setOnPress(function() pings.playAttack() end) end function pings.playAttack() attackAnim:restart() -- restart each time end
Model Control
Bobbing / Floating Effect
local t = 0 function events.RENDER(delta) t = t + delta * 0.1 local bob = math.sin(t) * 2 -- ±2 units vertical models.MyModel:setPos(0, bob, 0) end
Tail Wag — Oscillating Part
local t = 0 function events.RENDER(delta) t = t + delta * 0.15 local wag = math.sin(t) * 25 -- ±25 degrees models.MyModel.Tail:setOffsetRot(0, wag, 0) end
Scale Pulse (Heartbeat)
local t = 0 function events.RENDER(delta) t = t + delta * 0.08 local s = 1 + math.sin(t) * 0.05 -- ±5% scale models.MyModel:setScale(s, s, s) end
Part Follows Look Direction (Eye Movement)
function events.RENDER(delta) local rot = player:getRot(delta) -- Clamp pitch so eyes don't over-rotate local pitch = math.clamp(rot.x, -30, 30) local yaw = math.clamp(rot.y, -45, 45) -- relative to body models.MyModel.EyeL:setOffsetRot(pitch, yaw, 0) models.MyModel.EyeR:setOffsetRot(pitch, yaw, 0) end
Show/Hide Part Based on Held Item
function events.TICK() local item = player:getHeldItem(false) -- main hand local id = item:getId() -- Show sword model only when holding a sword local holdingSword = id:find("sword") ~= nil models.MyModel.SwordProp:setVisible(holdingSword) -- Show magic staff when holding a stick models.MyModel.Staff:setVisible(id == "minecraft:stick") end
Swap Armor Textures Based on Equipment
function events.TICK() local helmet = player:getItem(5) -- slot 5 = helmet if helmet:getId() == "minecraft:netherite_helmet" then models.MyModel.Hat:setPrimaryTexture("CUSTOM", textures["hat_nether"]) else models.MyModel.Hat:setPrimaryTexture("PRIMARY") -- restore default end end
UV Scroll (Animated Texture)
local scroll = 0 function events.RENDER(delta) scroll = (scroll + delta * 0.01) % 1 models.MyModel.Portal:setUV(scroll, 0) end
Render Task — Floating Item Over Head
-- Set up once on load local task = models.MyModel.Head:newItem("crownItem") task:setItem("minecraft:golden_helmet") task:setDisplayMode("GUI") task:setPos(0, 12, 0) task:setScale(0.6, 0.6, 0.6) -- Slowly spin it local t = 0 function events.RENDER(delta) t = t + delta * 3 task:setRot(0, t, 0) end
World-Space Part (Fixed to World, Not Player)
-- A part anchored to world position (e.g. orbiting rune) models.MyModel.Rune:setParentType("World") local t = 0 function events.RENDER(delta) t = t + delta * 0.05 local r = 1.5 local pos = player:getPos(delta) models.MyModel.Rune:setPos( pos.x + math.cos(t) * r, pos.y + 1, pos.z + math.sin(t) * r ) models.MyModel.Rune:setRot(0, -math.deg(t), 0) end
Player Data — Practical Patterns
Color Model by Health (green → yellow → red)
function events.TICK() local t = player:getHealth() / player:getMaxHealth() -- t=1: green. t=0.5: yellow. t=0: red local r = math.clamp(2 - t * 2, 0, 1) local g = math.clamp(t * 2, 0, 1) models.MyModel:setColor(r, g, 0) end
Glow Brighter in the Dark
function events.TICK() local pos = player:getPos() local light = world:getLightLevel(pos) -- 0-15 -- In dark (light=0) → full bright; in light (light=15) → dim glow local glow = math.map(light, 0, 15, 15, 0) models.MyModel.GlowPart:setLight(math.floor(glow), 15) end
Scale Avatar by Saturation (food buff)
local currentScale = 1 function events.RENDER(delta) local sat = math.clamp(player:getSaturation() / 20, 0, 1) local targetScale = math.lerp(0.9, 1.1, sat) currentScale = math.lerp(currentScale, targetScale, 0.05) -- smooth models.MyModel:setScale(currentScale, currentScale, currentScale) end
React to Potion Effects
function events.TICK() if not host:isHost() then return end local effects = host:getStatusEffects() local hasSpeed = false local hasInvis = false for _, e in pairs(effects) do if e.name == "minecraft:speed" then hasSpeed = true end if e.name == "minecraft:invisibility" then hasInvis = true end end models.MyModel:setOpacity(hasInvis and 0.3 or 1.0) animations.MyModel.speedLines:setVisible(hasSpeed) end
Detect Creative vs Survival
function events.TICK() local gm = player:getGamemode() -- Show creative "aura" in creative mode models.MyModel.CreativeAura:setVisible(gm == "CREATIVE") end
World Queries
Change Model by Biome
function events.TICK() local biome = world:getBiome(player:getPos()) local id = biome:getId() -- Underwater helmet models.MyModel.Helm:setVisible(player:isUnderwater()) -- Snow effect in cold biomes models.MyModel.SnowLayer:setVisible(biome:hasSnow()) -- Desert glow in hot biomes models.MyModel.SunGlow:setVisible(biome:isHot()) end
Check What Block You're Standing On
function events.TICK() local pos = player:getPos() -- Block directly below the player local below = world:getBlockState(vec( math.floor(pos.x), math.floor(pos.y - 0.1), math.floor(pos.z) )) -- Different footstep particles per surface if player:isMoving(true) and player:isOnGround() then local id = below:getId() if id == "minecraft:grass_block" then particles:newParticle("minecraft:happy_villager", pos) elseif id == "minecraft:sand" then particles:newParticle("minecraft:cloud", pos) end end end
Day/Night Appearance Cycle
function events.TICK() local tod = world:getDayTime() -- 0-24000 local isNight = tod > 13000 and tod < 23000 local rain = world:getRainGradient(1) > 0.5 models.MyModel.NightForm:setVisible(isNight) models.MyModel.DayForm:setVisible(not isNight) models.MyModel.Umbrella:setVisible(rain) -- Moon phase ear shape local moonPhase = world:getMoonPhase() -- 0-7 models.MyModel.Ears:setScale(1 + moonPhase * 0.05, 1, 1) end
Scan Blocks Around Player (Aura)
local nearLava = false function events.TICK() local pos = player:getPos() nearLava = false -- Scan 3x3x3 area around player local blocks = world:getBlocks(pos - vec(2,2,2), pos + vec(2,2,2)) for _, b in ipairs(blocks) do if b:getId():find("lava") then nearLava = true break end end models.MyModel.FireAura:setVisible(nearLava) end
HUD & UI
Custom Health Bar (Render Task Text)
-- Attach a health bar text above head local hpTask = models.MyModel.Head:newText("hpBar") hpTask:setPos(0, 15, 0) hpTask:setScale(0.4, 0.4, 0.4) hpTask:setBackground(false) hpTask:setShadow(true) function events.TICK() local hp = player:getHealth() local max = player:getMaxHealth() local bars = math.floor(hp / max * 10) local bar = string.rep("█", bars) .. string.rep("░", 10 - bars) local col = hp > max * 0.5 and "green" or (hp > max * 0.25 and "yellow" or "red") hpTask:setText('{"text":"'..bar..'","color":"'..col..'"}') end
Camera-Facing HUD Element
-- Part with parent type Camera always faces viewer models.MyModel.HUD:setParentType("Camera") models.MyModel.HUD:setPos(0, 0, -1) -- 1 unit in front of camera models.MyModel.HUD:setScale(0.1, 0.1, 0.1)
Show Title on Level Up
local lastLevel = -1 function events.TICK() if not host:isHost() then return end local lvl = player:getExperienceLevel() if lastLevel >= 0 and lvl > lastLevel then host:setTitle('{"text":"Level Up! ★","color":"gold","bold":true}') host:setSubtitle('{"text":"Level ' .. lvl .. '","color":"yellow"}') host:setTitleTimes(5, 50, 10) end lastLevel = lvl end
Sounds & Particle Effects
Footstep Sounds
local stepTimer = 0 function events.TICK() stepTimer = stepTimer - 1 if player:isMoving(true) and player:isOnGround() and stepTimer <= 0 then local spd = player:isSprinting() and 6 or 10 -- faster steps when sprinting stepTimer = spd sounds:playSound( "minecraft:entity.player.swim", player:getPos(), 0.4, -- volume math.random() * 0.4 + 0.8 -- random pitch ) end end
Particle Trail While Moving
function events.TICK() if not player:isMoving() then return end local pos = player:getPos() + vec(0, 0.5, 0) -- Spread particles slightly for i = 1, 3 do local spread = vec( (math.random() - 0.5) * 0.3, math.random() * 0.2, (math.random() - 0.5) * 0.3 ) particles:newParticle("minecraft:end_rod", pos + spread) end end
Splash Effect on Landing
local wasFalling = false function events.TICK() local falling = player:isFalling() local onGround = player:isOnGround() if wasFalling and onGround then -- Just landed! local pos = player:getPos() for i = 1, 20 do local angle = i / 20 * math.pi * 2 local vel = vec(math.cos(angle) * 0.3, 0.3, math.sin(angle) * 0.3) particles:newParticle("minecraft:cloud", pos, vel) end sounds:playSound("minecraft:block.stone.hit", pos, 0.8, 0.8) end wasFalling = falling and not onGround end
Cancel Explosion Sounds (example of ON_PLAY_SOUND)
function events.ON_PLAY_SOUND(id, pos, vol, pitch, loop, category) -- Cancel all explosion sounds if id:find("explode") or id:find("explosion") then return true -- returning true cancels the sound end -- Replace creeper hiss with a meow if id == "minecraft:entity.creeper.primed" then sounds:playSound("minecraft:entity.cat.ambient", pos, vol, pitch) return true -- cancel original end end
Keybind + Ping — Sync to All Viewers
Hold-to-Activate with Smooth Lerp
local powerActive = false local powerLerp = 0 function pings.setPower(active) powerActive = active end if host:isHost() then local key = keybinds:newKeybind("Power Mode", "key.keyboard.v") key:setOnPress(function() pings.setPower(true) end) key:setOnRelease(function() pings.setPower(false) end) end function events.RENDER(delta) local target = powerActive and 1 or 0 powerLerp = math.lerp(powerLerp, target, 0.1) -- Scale and color modulated by power level local s = 1 + powerLerp * 0.3 models.MyModel:setScale(s, s, s) models.MyModel:setColor(1, 1 - powerLerp * 0.5, 1 - powerLerp) end
Multi-State Toggle (Cycle through modes)
local modes = {"normal", "stealth", "power"} local modeIdx = 1 function pings.setMode(idx) modeIdx = idx local m = modes[idx] models.MyModel.NormalForm:setVisible(m == "normal") models.MyModel.StealthForm:setVisible(m == "stealth") models.MyModel.PowerForm:setVisible(m == "power") end if host:isHost() then keybinds:newKeybind("Cycle Mode", "key.keyboard.m") :setOnPress(function() local next = (modeIdx % #modes) + 1 pings.setMode(next) host:setActionbar('{"text":"Mode: '..modes[next]..'","color":"aqua"}') end) end -- Initialize default pings.setMode(1)
Action Wheel — Full Setup
Multi-Page Wheel with Scroll Slot
-- ── PAGE 1: Avatar Controls ────────────────── local page1 = action_wheel:newPage("controls") action_wheel:setPage(page1) -- Toggle action (remembers state, shows different title) local wingToggle = page1:newAction() :title("Wings: OFF") :toggleTitle("Wings: ON") :item("minecraft:feather") :color(0.8, 0.8, 0.8) :toggleColor(0.5, 0.9, 1.0) :onToggle(function(state) pings.setWings(state) end) -- Scroll wheel for glow color local glowColors = { vec(1,0,0), vec(0,1,0), vec(0,0,1), vec(1,1,0), vec(1,0,1), vec(0,1,1) } local colorIdx = 1 page1:newAction() :title("Glow Color") :item("minecraft:glowstone_dust") :onScroll(function(dir) colorIdx = ((colorIdx - 1 + math.floor(dir)) % #glowColors) + 1 pings.setGlowColor(colorIdx) end) :onLeftClick(function() colorIdx = (colorIdx % #glowColors) + 1 pings.setGlowColor(colorIdx) end) -- Switch to page 2 page1:newAction() :title("→ Emotes") :item("minecraft:map") :onLeftClick(function() action_wheel:setPage("emotes") end) -- ── PAGE 2: Emotes ─────────────────────────── local page2 = action_wheel:newPage("emotes") local emoteList = { {"Wave", "minecraft:hand", "wave"}, {"Dance", "minecraft:music_disc_cat", "dance"}, {"Sit", "minecraft:oak_stairs", "sit"}, } for _, e in ipairs(emoteList) do local name, item, anim = e[1], e[2], e[3] page2:newAction() :title(name) :item(item) :onLeftClick(function() pings.playEmote(anim) end) end page2:newAction() :title("← Back") :item("minecraft:arrow") :onLeftClick(function() action_wheel:setPage("controls") end) -- Ping handlers function pings.playEmote(name) animations.MyModel[name]:restart() end function pings.setGlowColor(idx) colorIdx = idx local c = glowColors[idx] models.MyModel.Glow:setColor(c.x, c.y, c.z) end
Nameplate
Dynamic Nameplate with Stats
-- Rich formatted name with health indicator local updateTimer = 0 function events.TICK() updateTimer = updateTimer - 1 if updateTimer > 0 then return end updateTimer = 5 -- update every 5 ticks local hp = math.floor(player:getHealth()) local maxHp = math.floor(player:getMaxHealth()) local heartColor = hp > maxHp * 0.5 and "red" or "dark_red" local name = player:getName() nameplate.ABOVE:setText(toJson({ {text="❤ ", color=heartColor}, {text=hp.."/"..maxHp.." ", color="white"}, {text=name, color="aqua", bold=true} })) end
Chat Prefix (Role Badge)
-- Show a colored prefix in chat messages nameplate.CHAT:setText(toJson({ {text="[★] ", color="gold", bold=true}, {text=player:getName(), color="white"} })) -- Tab list prefix nameplate.LIST:setText(toJson({ {text="◈ ", color="aqua"}, {text=player:getName(), color="white"} }))
Textures — Runtime Pixel Art
Dynamic Color Fill Based on Game State
-- Create a small texture and assign to a model part local tex = textures:newTexture("healthTex", 16, 4) models.MyModel.HealthBar:setPrimaryTexture("CUSTOM", tex) local lastHp = -1 function events.TICK() local hp = player:getHealth() if hp == lastHp then return end -- only update when changed lastHp = hp local filled = math.floor((hp / player:getMaxHealth()) * 16) for x = 0, 15 do local color = x < filled and vec(0.9, 0.1, 0.1, 1) -- filled: red or vec(0.2, 0.2, 0.2, 1) -- empty: dark gray for y = 0, 3 do tex:setPixel(x, y, color) end end tex:update() end
Plasma / Animated Noise Texture
local ptex = textures:newTexture("plasma", 32, 32) models.MyModel.GlowPart:setPrimaryTexture("CUSTOM", ptex) models.MyModel.GlowPart:setPrimaryRenderType("EYES") local t = 0 function events.TICK() t = t + 0.05 for x = 0, 31 do for y = 0, 31 do local v = math.sin(x * 0.4 + t) + math.sin(y * 0.4 + t * 1.3) v = (v + 2) / 4 -- normalize to 0-1 ptex:setPixel(x, y, vec(v, 0.3, 1 - v, 1)) end end ptex:update() end
Camera & Visual FX
Camera Shake on Damage
local shakeTime = 0 local shakeIntensity = 0 function events.DAMAGE(type, source, attacker, amount) shakeTime = 10 shakeIntensity = math.clamp(amount * 0.5, 1, 5) end function events.RENDER(delta) if shakeTime > 0 then shakeTime = shakeTime - delta local i = shakeIntensity * (shakeTime / 10) renderer:setOffsetCameraRot( (math.random() - 0.5) * i, (math.random() - 0.5) * i, 0 ) else renderer:setOffsetCameraRot(0, 0, 0) end end
FOV Zoom on Right Click (Sniper)
local zoomed = false local fovLerp = 1 function pings.setZoom(z) zoomed = z end if host:isHost() then local zoomKey = keybinds:newKeybind("Zoom", "key.mouse.right") zoomKey:setOnPress(function() pings.setZoom(true) end) zoomKey:setOnRelease(function() pings.setZoom(false) end) end function events.RENDER(delta) local target = zoomed and 0.2 or 1.0 -- 0.2 = 20% FOV (strong zoom) fovLerp = math.lerp(fovLerp, target, 0.15) renderer:setFov(fovLerp) end
Outline Color Based on Team
function events.TICK() local team = player:getTeamInfo() if team then -- Use the team color for glow outline local c = team.color -- returns a color string like "red" renderer:setOutlineColor( c == "red" and 1 or 0, c == "green" and 1 or 0, c == "blue" and 1 or 0, 1 ) end end
First-Person Arm Sway
local swayX, swayY = 0, 0 local lastRot = vec(0, 0) function events.RENDER(delta) local rot = player:getRot(delta) local dX = rot.x - lastRot.x local dY = rot.y - lastRot.y lastRot = rot -- Smooth sway swayX = math.lerp(swayX, -dX * 0.3, 0.1) swayY = math.lerp(swayY, -dY * 0.3, 0.1) if renderer:isFirstPerson() then models.MyModel.ArmR:setOffsetPos(swayY, swayX, 0) end end
Config & Persistent Save
Save All Avatar Settings
-- Default settings local settings = { wingEnabled = false, glowColor = 1, volume = 1.0, mode = "normal", } -- Load saved settings on startup (host only) if host:isHost() then local saved = config:load() -- returns all saved keys if saved then for k, v in pairs(saved) do if settings[k] ~= nil then settings[k] = v end end end end -- Helper: save all and apply via ping local function applyAndSave() pings.applySettings(settings.wingEnabled, settings.glowColor, settings.mode) if host:isHost() then for k, v in pairs(settings) do config:save(k, v) end end end function pings.applySettings(wings, color, mode) settings.wingEnabled = wings settings.glowColor = color settings.mode = mode models.MyModel.Wings:setVisible(wings) -- etc. end -- Apply saved settings once loaded applyAndSave()
Raycast — Practical Uses
Laser Pointer (Beam to First Hit)
-- Beam that extends from hand to the first block/entity hit local laser = models.MyModel.Hand:newSprite("laser") laser:setTexture(textures["laser"], 1, 16) function events.TICK() local eyePos = player:getPos() + vec(0, player:getEyeHeight(), 0) local dir = player:getLookDir() local hit, hitPos = raycast:block(eyePos, eyePos + dir * 20) if not hit then local ent, entPos = raycast:entity(eyePos, eyePos + dir * 20) hitPos = entPos or (eyePos + dir * 20) end local dist = (hitPos - eyePos):length() laser:setScale(0.1, dist * 16, 0.1) -- stretch to hit point end
Ground Detection (Shadow / Landing Marker)
function events.TICK() local pos = player:getPos() -- Cast straight down to find ground local hit, groundPos = raycast:block(pos, pos - vec(0, 20, 0)) if hit then local dist = pos.y - groundPos.y -- Scale shadow by distance (fades when high) local alpha = math.clamp(1 - dist / 20, 0, 1) models.MyModel.GroundShadow:setOpacity(alpha) models.MyModel.GroundShadow:setPos( pos.x - groundPos.x, groundPos.y + 0.05, pos.z - groundPos.z ) end end
Networking — HTTP Requests
net:isNetworkingAllowed() first.Fetch a Remote JSON Config
local remoteFuture = nil function events.ENTITY_INIT() if not net:isNetworkingAllowed() then print("Networking not allowed by viewer") return end if not net:isLinkAllowed("https://raw.githubusercontent.com/...") then return end remoteFuture = net.http:request("https://raw.githubusercontent.com/your/repo/main/config.json"):send() end function events.TICK() if not remoteFuture then return end if not remoteFuture:isDone() then return end if remoteFuture:hasError() then print("Fetch failed!") remoteFuture = nil return end local resp = remoteFuture:getValue() if resp:getResponseCode() == 200 then -- Read all bytes from stream and parse as JSON local stream = resp:getData() local dataFuture = stream:readAsync(99999) -- Handle dataFuture in next tick cycle... end remoteFuture = nil end
Advanced Patterns
Share Data Between Avatars
-- Store data others can read avatar:store("team", "blue") avatar:store("level", 42) avatar:store("form", "dragon") -- Read another player's stored data function events.TICK() local players = world:getPlayers() for name, ply in pairs(players) do if ply:hasAvatar() then local theirTeam = ply:getVariable("team") if theirTeam == "blue" then print(name .. " is on the blue team!") end end end end
Multiple Files — Modular Structure
-- main.lua — entry point local anim = require("modules/animations") local fx = require("modules/effects") local ui = require("modules/ui") -- Setup vanilla_model.ALL:setVisible(false) anim.init() ui.init() function events.TICK() anim.tick() fx.tick() end function events.RENDER(delta) anim.render(delta) end
-- Return a module table local M = {} local A = animations.MyModel function M.init() A.idle:setLoopMode("LOOP") A.walk:setLoopMode("LOOP") end function M.tick() if player:isMoving(true) then A.walk:play() A.idle:stop() else A.idle:play() A.walk:stop() end end function M.render(delta) local rot = player:getRot(delta) models.MyModel.Head:setOffsetRot(rot.x, 0, 0) end return M
Detect When Player Enters Water
local wasInWater = false function events.TICK() local inWater = player:isInWater() if inWater and not wasInWater then -- Just entered water pings.splash() elseif not inWater and wasInWater then -- Just exited water pings.drip() end wasInWater = inWater -- Swap model for underwater form models.MyModel.LandForm:setVisible(not inWater) models.MyModel.WaterForm:setVisible(inWater) end
Avatar Communicates Avatar Data to Others
-- Get avatar info print("Avatar size: " .. avatar:getSize() .. " bytes") print("Complexity: " .. avatar:getComplexity()) print("Instructions used: " .. avatar:getCurrentInstructions()) print("Owner UUID: " .. avatar:getUUID()) print("Can edit nameplate: " .. tostring(avatar:canEditVanillaModel())) -- Share with others via store() avatar:store("complexity", avatar:getComplexity())
EZAnims + GSAnimBlend — Full Setup
A real-world avatar example with smooth character animations. Soft blend across all states, custom curve for death, custom state triggered by the Action Wheel, and flight detection.
Estrutura do Projeto
avatar/ script.lua -- script principal EZAnims.lua GSAnimBlend.lua model.bbmodel -- animações: idle, walk, sprint, jump...
Example 1 — Minimal Setup with Blend
-- 1. GSAnimBlend SEMPRE primeiro local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") -- 2. Registrar o modelo local m = anims:addBBModel(animations.model) -- 3. Global blend for all EZAnims animations m:setBlendTimes(6, 3) -- exclusivas=6t, inclusivas=3t -- 4. Animações específicas com parâmetros individuais animations.model.death:setBlendTime(20):setBlendCurve("easeInQuad") animations.model.hurt:setBlendTime(2, 4) animations.model.idle:setBlendCurve("easeInOutCubic") animations.model.sprint:setBlendCurve("easeOutBack")
Example 2 — Custom State with Action Wheel
Toggles between normal and "sword" mode via Action Wheel. When activated, animations like idle_sword, walk_sword substituem as padrão.
local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local m = anims:addBBModel(animations.model) m:setBlendTimes(6) local modeSword = false -- Ping to sync with other players function pings.setSword(b) modeSword = b m:setState(b and "sword" or nil) end -- Action Wheel local page = action_wheel:newPage() action_wheel:setPage(page) page:newAction() :title("Espada") :item("minecraft:iron_sword") :toggleItem("minecraft:diamond_sword") :onToggle(function(b) pings.setSword(b) end)
Example 3 — genBlendVanilla for Smooth Transition
When idle starts, the head and arms smoothly transition from the vanilla pose to the animated pose instead of snapping.
local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local m = anims:addBBModel(animations.model) m:setBlendTimes(8) -- Model parts with vanilla-based animation local bodyParts = { models.model.Head, models.model.Body, models.model.LeftArm, models.model.RightArm, models.model.LeftLeg, models.model.RightLeg, } -- Aplicar genBlendVanilla nas animations de movimento local vanillaCallback = GSBlend.callback.genBlendVanilla(bodyParts) animations.model.idle:setOnBlend(vanillaCallback) animations.model.walk:setOnBlend(vanillaCallback) animations.model.sprint:setOnBlend(vanillaCallback) animations.model.crouch:setOnBlend(vanillaCallback) -- Custom Bezier curve for idle (smooth with slight overshoot) animations.model.idle:setBlendCurve( GSBlend.callback.genBezier(0.34, 1.56, 0.64, 1) )
Example 4 — Overrider for Cutscene
local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local m = anims:addBBModel(animations.model) m:setBlendTimes(6) -- When cutscene_start plays, ALL EZAnims animations stop m:addAllOverrider(animations.model.cutscene_inicio) local emCutscene = false function pings.startCutscene() emCutscene = true animations.model.cutscene_inicio:play() end function pings.endCutscene() emCutscene = false animations.model.cutscene_inicio:stop() end
SquAPI — Complete Furry Avatar
Avatar example with ears, tail, moving eyes, smooth head, random blink, and squash & stretch. All modules working together.
Example 1 — Basic Setup (Ear + Tail + SmoothHead)
local squapi = require("SquAPI") ─── Ears ──────────────────────────────────────────── squapi.ear:new( models.model.Head.LeftEar, models.model.Head.RightEar, 1, -- rangeMultiplier false, -- horizontalEars 2, -- bendStrength true, -- doEarFlick 350, -- earFlickChance 0.1, -- stiffness 0.8 -- bounce ) ─── Tail (4 segments) ──────────────────────────────── squapi.tail:new({ models.model.Body.Tail, models.model.Body.Tail.TailSeg1, models.model.Body.Tail.TailSeg1.TailSeg2, models.model.Body.Tail.TailSeg1.TailSeg2.TailTip, }, 18, 6, 1.2, 2, 2.5, 0, 0, 1, 0.005, 0.9, 90, -80, 40) ─── Smooth Head ─────────────────────────────────────── squapi.smoothHead:new( {models.model.Head}, 1, -- strength 0.08, -- tilt sutil 1, -- speed true, -- keepOriginalHeadPos true -- fixPortrait )
Example 2 — Eyes + Random Blink
local squapi = require("SquAPI") ─── Eyes following gaze ───────────────────────────── squapi.eye:new(models.model.Head.EyeLeft, 0.3, 1.2, 0.5, 0.6) squapi.eye:new(models.model.Head.EyeRight, 0.3, 1.2, 0.5, 0.6) ─── Automatic random blink ─────────────────────────── -- A animação "blink" deve ser Play Once e ter a animação de piscar squapi.randimation:new( animations.model.blink, 80, -- intervalo mínimo (ticks) 300, -- intervalo máximo true -- does not blink while sleeping ) ─── Idle micro-movement (subtle head shake) ────────── squapi.randimation:new(animations.model.headTwitch, 200, 600)
Example 3 — Squash & Stretch + BounceWalk
local squapi = require("SquAPI") ─── Squash & Stretch no pulo/pouso ───────────────────── squapi.squishy:new( models.model, -- raiz do modelo 0.35, -- springStrength 0.25, -- dampening -0.6, -- crouchBounce (achata ao agachar) vec(0.65, 0.55, 0.65), -- lowerLimit vec(1.2, 1.45, 1.2) -- upperLimit ) ─── Bounce while walking ──────────────────────────── squapi.bounceWalk:new(models.model, 1.2) -- 1.2x more pronounced
Example 4 — Complete Furry Avatar (all together)
-- ──────────────────────────────────────────────────────── -- Furry Avatar: SquAPI + EZAnims + GSAnimBlend -- ──────────────────────────────────────────────────────── -- Ordem de require importa! local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local squapi = require("SquAPI") ── EZAnims ────────────────────────────────────────── local m = anims:addBBModel(animations.model) m:setBlendTimes(7, 3) animations.model.death:setBlendTime(18):setBlendCurve("easeInCubic") animations.model.hurt:setBlendTime(2, 5) ── SquAPI: Ears ───────────────────────────────────── squapi.ear:new( models.model.Head.LeftEar, models.model.Head.RightEar, 1, false, 2, true, 300, 0.1, 0.82 ) ── SquAPI: Tail ───────────────────────────────────── squapi.tail:new({ models.model.Body.Tail, models.model.Body.Tail.Seg1, models.model.Body.Tail.Seg1.Seg2, models.model.Body.Tail.Seg1.Seg2.Tip, }, 16, 5, 1.2, 2, 2.5) ── SquAPI: Smooth Head ────────────────────────────── squapi.smoothHead:new( {models.model.Head}, 1, 0.08, 1, true, true, {animations.model.attack}, -- endireita ao atacar 0.6, 0.4 ) ── SquAPI: Eyes + Blink ───────────────────────────── squapi.eye:new(models.model.Head.EyeL, 0.3, 1.2, 0.5, 0.6) squapi.eye:new(models.model.Head.EyeR, 0.3, 1.2, 0.5, 0.6) squapi.randimation:new(animations.model.blink, 80, 280, true) ── SquAPI: Squash & Bounce ────────────────────────────── squapi.squishy:new(models.model, 0.3, 0.25, -0.5) squapi.bounceWalk:new(models.model, 1)
PhysBone + Swinging Physics — Accessory Physics
Practical physics examples for hair, earrings, chains, and capes. PhysBone for long continuous bones; SwingingPhysics for items hanging from a point.
Example 1 — Hair with PhysBone
local physBone = require("physBoneAPI") -- Preset for default hair physBone:setPreset("cabelo", 1, 22, -- mass, length 0.75, 0.18, -- gravity, airResistance 1, nil, -- simSpeed, equilibrium 0, nil, -- springForce, force nil, vec(0.5, 1, 1), -- rotMod, vecMod nil, -- rollMod 0, 18, 0, 0.5, 0.15 -- nodeStart, nodeEnd, density=0 (sem debug), radius, bounce ) function events.ENTITY_INIT() -- Aplicar em múltiplos grupos de cabelo com o mesmo preset models.model.Head.HairLeft:newPhysBone("physBone"):updateWithPreset("cabelo") models.model.Head.HairRight:newPhysBone("physBone"):updateWithPreset("cabelo") models.model.Head.HairBack:newPhysBone("physBone"):updateWithPreset("cabelo") -- Franja com menos movimento lateral (vecMod.x menor) local franja = models.model.Head.Franja:newPhysBone("physBone") franja:updateWithPreset("cabelo") :setVecMod(0.2, 1, 0.8) -- barely sways sideways :setGravity(0.4) -- lighter end
Example 2 — Chain Earring (SwingingPhysics)
local SP = require("swinging_physics") -- Corrente de 4 elos pendurada na orelha esquerda local e1 = SP.swingOnHead(models.model.Head.ChainL.Elo1, 90, {-60,60,-45,45,-30,30}) local e2 = SP.swingOnHead(models.model.Head.ChainL.Elo1.Elo2, 90, nil, models.model.Head.ChainL.Elo1, 1) local e3 = SP.swingOnHead(models.model.Head.ChainL.Elo1.Elo2.Elo3, 90, nil, models.model.Head.ChainL.Elo1, 2) local gem = SP.swingOnHead(models.model.Head.ChainL.Elo1.Elo2.Elo3.Gema, 90, nil, models.model.Head.ChainL.Elo1, 3) -- Brinco simples na orelha direita com limites globais local brincoD = SP.swingOnHead( models.model.Head.BrincoR, 270, {-40, 40, -30, 30, -20, 20}, nil, 0, true -- globalLimits: does not clip into head when looking up )
Example 3 — Cape with PhysBone + Pendant with Swing
local physBone = require("physBoneAPI") local SP = require("swinging_physics") ── PhysBone: Cape on back ─────────────────────────── physBone:setPreset("capa", 1, 32, 0.5, 0.1, 1, nil, 0, nil, nil, vec(1, 1, 0.25), -- vecMod: barely sways forward nil, 0, 28, 0, 0.5, 0 ) function events.ENTITY_INIT() models.model.Body.Cape:newPhysBone("physBone"):updateWithPreset("capa") end ── SwingingPhysics: Pendant on neck ───────────────── SP.swingOnBody( models.model.Body.Pendant, 0, {-30, 30, -25, 25, -20, 20} )
Example 4 — Enable/Disable Physics via Ping
local SP = require("swinging_physics") local brinco = SP.swingOnHead(models.model.Head.Earring, 90) local pingente = SP.swingOnBody(models.model.Body.Pendant, 0) local physicsOn = true function pings.togglePhysics(b) physicsOn = b brinco:setEnabled(b) pingente:setEnabled(b) end -- Action Wheel toggle local page = action_wheel:newPage() action_wheel:setPage(page) page:newAction() :title("Physics") :item("minecraft:diamond") :onToggle(function(b) pings.togglePhysics(b) end)
Confetti — Efeitos de Partículas
Practical examples: trail while walking, explosion on death, magic particles, and presence effect toggled by the Action Wheel.
Example 1 — Magic Trail While Walking
local confetti = require("confetti") -- Registrar: mesh do Blockbench chamado "StarParticle" confetti.registerMesh("star", models.model.StarParticle, 35) local trilhaAtiva = false function pings.setTrilha(b) trilhaAtiva = b end function events.TICK() if not trilhaAtiva then return end -- Spawns 2 particles per tick while moving local vel = player:getVelocity() if vel:length() > 0.05 then for _ = 1, 2 do confetti.newParticle( "star", player:getPos() + vec( (math.random() - 0.5) * 0.4, math.random() * 0.8, (math.random() - 0.5) * 0.4 ), vec( (math.random() - 0.5) * 0.08, 0.05 + math.random() * 0.1, (math.random() - 0.5) * 0.08 ), { emissive = true, rotationOverTime = math.random() * 20 - 10, scaleOverTime = -0.035, scale = 1, acceleration = vec(0, -0.003, 0), } ) end end end
Example 2 — Explosion on Death
local confetti = require("confetti") confetti.registerMesh("spark", models.model.SparkParticle, 60) local estavaMorto = false function events.TICK() local morto = player:isDead() if morto and not estavaMorto then -- Explode 40 faíscas no momento da morte for _ = 1, 40 do local angle = math.random() * math.pi * 2 local speed = 0.15 + math.random() * 0.35 confetti.newParticle( "spark", player:getPos() + vec(0, 1, 0), vec( math.cos(angle) * speed, 0.1 + math.random() * 0.4, math.sin(angle) * speed ), { emissive = true, acceleration = vec(0, -0.02, 0), friction = 0.92, rotationOverTime = math.random() * 30 - 15, ticker = function(p) confetti.defaultTicker(p) -- Encolhe no fim da vida p.scale = math.clamp(p.lifetime / 20, 0, 1) end } ) end end estavaMorto = morto end
Example 3 — Partícula de Sprite Animada (Fogo)
local confetti = require("confetti") -- Sprite de fogo: 3 frames de 4x5px a partir do UV (0,10) confetti.registerSprite("fire", textures["model.fx"], vec(0,10,4,5)) function events.TICK() -- Fogo ao redor dos pés confetti.newParticle( "fire", player:getPos() + vec( math.random() * 1.2 - 0.6, 0.05, math.random() * 1.2 - 0.6 ), vec(0, 0.04, 0), { billboard = true, scale = 0.5 + math.random() * 0.4, frame = 0, -- variável customizada para frame atual ticker = function(p) -- Não precisa de defaultTicker: não quer movimento físico p.lifetime = p.lifetime - 1 -- Avança o frame a cada 2 ticks if world:getTime() % 2 == 0 then p.options.frame = (p.options.frame + 1) % 3 end p.task:setUVPixels(p.bounds.x + p.options.frame * 4, p.bounds.y) end } ) end
Example 4 — Confetti via Action Wheel
local confetti = require("confetti") confetti.registerMesh("confete", models.model.Confete, 80) function pings.dispararConfetti() -- 60 pedaços de confete em arco for _ = 1, 60 do confetti.newParticle( "confete", player:getPos() + vec(0, 1.5, 0), vec( (math.random() - 0.5) * 0.5, 0.3 + math.random() * 0.5, (math.random() - 0.5) * 0.5 ), { acceleration = vec(0, -0.025, 0), friction = 0.97, rotation = vec(math.random()*360, math.random()*360, 0), rotationOverTime = vec(math.random()*20-10, math.random()*20-10, 0), } ) end end local page = action_wheel:newPage() action_wheel:setPage(page) page:newAction() :title("Confetti!") :item("minecraft:firework_rocket") :onLeftClick(function() pings.dispararConfetti() end)
Animated Text + Chat Bubbles + Speech Bubble
Visual examples: animated nameplate with HP, wave text, configured chat bubble, and typing bubble.
Example 1 — Nameplate with HP and Wave
local animText = require("animatedText") -- Criar nameplate acima da cabeça animText.new( "nameplate", models.model.Head, vec(0, 22, 0), vec(0.45, 0.45, 1), "BILLBOARD" ) -- Atualiza o texto ao HP mudar local lastHp = -1 function events.TICK() local hp = math.floor(player:getHealth()) if hp ~= lastHp then lastHp = hp local cor = hp > 10 and "green" or hp > 5 and "yellow" or "red" animText.setText("nameplate", { {text = "◈ ", color = "aqua"}, {text = player:getName(), color = "white", bold = true}, {text = " ❤", color = cor}, {text = tostring(hp), color = cor, bold = true}, }) end end -- Onda senoidal em cada caractere local t = 0 function events.TICK() t = t + 1 end function events.RENDER(delta, ctx) if ctx ~= "FIRST_PERSON" and ctx ~= "RENDER" then return end local tasks = animText.getTask("nameplate").textTasks for i, char in pairs(tasks) do local wave = math.sin((t + delta) / 8 + i * 0.55) * 0.4 animText.transform("nameplate", vec(0, wave, 0), nil, nil, char) end end
Example 2 — Text with Rainbow Effect
local animText = require("animatedText") -- Criar o texto animText.new("rainbow", models.model.Head, vec(0,24,0), vec(0.5,0.5,1), "BILLBOARD", {text = "✦ FIGURA ✦"} ) -- Reconstruir o texto com cores por caractere (rainbow) local rainbow = {"red", "gold", "yellow", "green", "aqua", "blue", "light_purple"} local chars = {"✦"," ","F","I","G","U","R","A"," ","✦"} local jsonArr = {} for i, c in ipairs(chars) do jsonArr[i] = {text = c, color = rainbow[(i-1)%#rainbow+1], bold = true} end animText.setText("rainbow", jsonArr) -- Outline em todos os chars for _, v in pairs(animText.getTask("rainbow").textTasks) do v.task:outline(true):outlineColor(vec(0,0,0)) end
Example 3 — Chat Bubbles Configurado
local chat_bubble = require("modules/chat_bubble/chat_bubble") -- Configurações personalizadas chat_bubble.bubbleLifetime = 160 -- 8s no total chat_bubble.fadeTime = 40 -- 2s de fade chat_bubble.maxMessageLength = 72 -- até 72 caracteres chat_bubble.localIndicator = "." -- mensagem começando com "." não vai ao chat
Example 4 — Tudo Junto: Nameplate + Chat + Speech
-- Nameplate animada local animText = require("animatedText") -- Chat Bubble (texto ao enviar mensagem) local chatBubble = require("modules/chat_bubble/chat_bubble") -- Speech Bubble (ícone ao digitar) — não precisa de require, auto-init ── Nameplate ──────────────────────────────────────── animText.new("name", models.model.Head, vec(0,22,0), vec(0.4,0.4,1), "BILLBOARD", { {text = "♦ ", color = "gold"}, {text = player:getName(), color = "white", bold = true}, {text = " ♦", color = "gold"}, }) ── Chat Bubble ────────────────────────────────────── chatBubble.bubbleLifetime = 140 chatBubble.fadeTime = 30 chatBubble.localIndicator = "." ── Nameplate wave effect (render) ─────────────────── local t = 0 function events.TICK() t = t + 1 end function events.RENDER(delta, ctx) if ctx ~= "RENDER" then return end for i, c in pairs(animText.getTask("name").textTasks) do animText.transform("name", vec(0, math.sin((t+delta)/8+i*0.6)*0.35, 0), nil, nil, c) end end
Full Avatar — Todas as Libs Integradas
Reference script that combines EZAnims, GSAnimBlend, SquAPI, PhysBone, SwingingPhysics, Confetti and AnimatedText in a único avatar coeso, com Action Wheel para controlar estados e efeitos.
models.* e animations.* for your model.-- ═══════════════════════════════════════════════════════ -- REQUIRES — ordem importa: GSAnimBlend primeiro -- ═══════════════════════════════════════════════════════ local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local squapi = require("SquAPI") local physBone = require("physBoneAPI") local SP = require("swinging_physics") local confetti = require("confetti") local animText = require("animatedText") local chatBubble = require("modules/chat_bubble/chat_bubble") vanilla_model.ALL:setVisible(false) -- ═══════════════════════════════════════════════════════ -- EZANIMS + GSANIMBLEND -- ═══════════════════════════════════════════════════════ local m = anims:addBBModel(animations.model) m:setBlendTimes(7, 3) animations.model.death:setBlendTime(18):setBlendCurve("easeInCubic") animations.model.hurt:setBlendTime(2, 5) -- genBlendVanilla nas animations de movimento local bodyParts = { models.model.Head, models.model.Body, models.model.LeftArm, models.model.RightArm, models.model.LeftLeg, models.model.RightLeg, } local vanillaBlend = GSBlend.callback.genBlendVanilla(bodyParts) animations.model.idle:setOnBlend(vanillaBlend) animations.model.walk:setOnBlend(vanillaBlend) -- ═══════════════════════════════════════════════════════ -- SQUAPI -- ═══════════════════════════════════════════════════════ squapi.ear:new(models.model.Head.LeftEar, models.model.Head.RightEar, 1, false, 2, true, 300, 0.1, 0.82) squapi.tail:new({ models.model.Body.Tail, models.model.Body.Tail.Seg1, models.model.Body.Tail.Seg1.Seg2, models.model.Body.Tail.Seg1.Seg2.Tip, }, 16, 5, 1.2, 2, 2.5) squapi.smoothHead:new({models.model.Head}, 1, 0.08, 1, true, true, {animations.model.attack}, 0.6, 0.4) squapi.eye:new(models.model.Head.EyeL, 0.3, 1.2, 0.5, 0.6) squapi.eye:new(models.model.Head.EyeR, 0.3, 1.2, 0.5, 0.6) squapi.randimation:new(animations.model.blink, 80, 280, true) squapi.squishy:new(models.model, 0.3, 0.25, -0.5) squapi.bounceWalk:new(models.model, 1) -- ═══════════════════════════════════════════════════════ -- PHYSBONE + SWINGING -- ═══════════════════════════════════════════════════════ physBone:setPreset("cabelo", 1,22,0.75,0.18,1,nil,0,nil,nil,vec(0.5,1,1),nil,0,18,0,0.5,0.15) function events.ENTITY_INIT() models.model.Head.HairL:newPhysBone("physBone"):updateWithPreset("cabelo") models.model.Head.HairR:newPhysBone("physBone"):updateWithPreset("cabelo") end local brinco = SP.swingOnHead(models.model.Head.Earring, 90, {-50,50,-40,40,-25,25}, nil, 0, true) -- ═══════════════════════════════════════════════════════ -- CONFETTI -- ═══════════════════════════════════════════════════════ confetti.registerMesh("spark", models.model.SparkFX, 50) local trilha = false function events.TICK() if trilha and player:getVelocity():length() > 0.05 then confetti.newParticle("spark", player:getPos()+vec(0,0.5,0), vec((math.random()-0.5)*0.1, 0.08+math.random()*0.08, (math.random()-0.5)*0.1), {emissive=true, scaleOverTime=-0.03, acceleration=vec(0,-0.005,0)}) end end -- ═══════════════════════════════════════════════════════ -- ANIMATED TEXT + CHAT BUBBLES -- ═══════════════════════════════════════════════════════ animText.new("np", models.model.Head, vec(0,22,0), vec(0.42,0.42,1), "BILLBOARD", { {text = "◈ ", color = "aqua"}, {text = player:getName(), color = "white", bold = true}, }) chatBubble.bubbleLifetime = 140 chatBubble.localIndicator = "." -- Onda na nameplate local tick = 0 events.TICK:register(function() tick = tick + 1 end) events.RENDER:register(function(d, ctx) if ctx ~= "RENDER" then return end for i, c in pairs(animText.getTask("np").textTasks) do animText.transform("np", vec(0, math.sin((tick+d)/8+i*0.6)*0.35, 0), nil, nil, c) end end) -- ═══════════════════════════════════════════════════════ -- ACTION WHEEL -- ═══════════════════════════════════════════════════════ local page = action_wheel:newPage() action_wheel:setPage(page) -- Toggle estado "sword" page:newAction():title("Espada"):item("minecraft:iron_sword") :onToggle(function(b) pings.setSword(b) end) -- Toggle trilha de partículas page:newAction():title("Trilha"):item("minecraft:blaze_powder") :onToggle(function(b) pings.setTrilha(b) end) -- Toggle física dos acessórios page:newAction():title("Physics"):item("minecraft:diamond"):toggled(true) :onToggle(function(b) pings.setFisica(b) end) -- ── PINGS ────────────────────────────────────────────── function pings.setSword(b) m:setState(b and "sword" or nil) end function pings.setTrilha(b) trilha = b end function pings.setFisica(b) brinco:setEnabled(b) end
GSAnimBlend by GrandpaScout
Adiciona blending suave ao início e ao fim de animations do Figura, criando transições fluidas entre estados. Funciona sobrescrevendo os métodos nativos de Animation com versões que suportam interpolação temporal, curvas de easing e callbacks customizados por frame.
✔ What you can do
:setBlendTime()genBlendVanilla para transição suave entre pose vanilla e pose animadagenBezier para criar curvas de easing cúbicas personalizadas:setBlendTimes():play(true) e :stop(true) para pular o blend instantaneamente quando quiser✘ What you CANNOT do
done=true continuem executando — esse frame é apenas de limpezaInstalação e Versões
Baixe o ZIP do repositório, abra a pasta script/ e escolha a versão:
| Version | File | Description |
|---|---|---|
| Default | script/default/GSAnimBlend.lua | Versão completa com todas as features. Recomendada. |
| Lite | script/lite/GSAnimBlend.lua | Reduced version. May have unresolved bugs — use only if instructions are an issue. |
| Tiny | script/tiny/GSAnimBlend.lua | Minimal version. Same caveats as Lite. |
-- Simple use: just to get access to :setBlendTime() require("GSAnimBlend") -- Uso avançado: guardar referência para acessar curvas, callbacks, oldF local GSBlend = require("GSAnimBlend")
Uso Básico — setBlendTime e setBlendCurve
Após o require, todos os objetos Animation ganham novos métodos. O mais importante é :setBlendTime() — por padrão o blend é 0 então nada acontece até você configurar.
require("GSAnimBlend") -- Blend in and out with the same duration (in ticks — 20 ticks = 1 second) animations.MeuModelo.Idle:setBlendTime(5) -- 0.25s animations.MeuModelo.Idle:setBlendTime(10) -- 0.5s -- Blend in e out com tempos diferentes animations.MeuModelo.Ataque:setBlendTime(3, 8) -- 3 ticks in, 8 ticks out -- Mudar a curva de easing do blend animations.MeuModelo.Idle:setBlendCurve("linear") animations.MeuModelo.Walk:setBlendCurve("easeInOutCubic") animations.MeuModelo.Ataque:setBlendCurve("easeOutBack") -- Também aceita alias no estilo método encadeado animations.MeuModelo.Idle :blendTime(5) :blendCurve("smoothstep")
Referência Completa de Curvas de Easing
All curves below are available as strings in :setBlendCurve() ou diretamente como funções em GSBlend.curve.nomeDaCurva.
| Nome da Curva | Comportamento |
|---|---|
linear | Velocidade constante — sem aceleração. Padrão. |
smoothstep | Suave no início e no fim. Bom uso geral. |
easeInSine | Começa devagar, termina rápido. Curva senoidal. |
easeOutSine | Começa rápido, termina devagar. |
easeInOutSine | Suave nos dois extremos com aceleração senoidal. |
easeInQuad | Começa devagar (aceleração quadrática). |
easeOutQuad | Termina devagar (desaceleração quadrática). |
easeInOutQuad | Suave nos dois extremos — quadrática. |
easeInCubic | Aceleração cúbica — início muito lento. |
easeOutCubic | Desaceleração cúbica — fim muito suave. |
easeInOutCubic | Transição suave forte — boa para animations de personagem. |
easeInQuart | Potência 4 — início extremamente lento. |
easeOutQuart | Potência 4 — fim extremamente suave. |
easeInOutQuart | Potência 4 nos dois extremos. |
easeInQuint | Potência 5 — início quase parado. |
easeOutQuint | Potência 5 — fim quase parado. |
easeInOutQuint | Potência 5 nos dois extremos. |
easeInExpo | Aceleração exponencial — começa parado e explode. |
easeOutExpo | Desaceleração exponencial — chega rápido e para suavemente. |
easeInOutExpo | Exponencial nos dois extremos. |
easeInCirc | Aceleração circular — começa como queda. |
easeOutCirc | Desaceleração circular — termina em arco. |
easeInOutCirc | Circular nos dois extremos. |
easeInBack | Goes slightly backward before advancing (negative overshoot at start). |
easeOutBack | Passa do ponto e volta (overshoot no final). Ótimo para animations que "encaixam". |
easeInOutBack | Overshoot nos dois extremos. |
easeInElastic | Oscillates at start like a stretched spring being released. |
easeOutElastic | Oscila no final como mola comprimida parando. |
easeInOutElastic | Mola nos dois extremos. |
easeInBounce | Bouncing effect before starting. |
easeOutBounce | Efeito de quicar ao chegar — como uma bola caindo. |
easeInOutBounce | Bounce nos dois extremos. |
Curvas Customizadas
You can create any curve as a Lua function that takes a value x from 0 to 1 and returns the modified progress. Ideally retorne 0 quando x=0 e 1 quando x=1.
-- Curva de onda: blend in, out, e de volta — efeito de pulsar local function wave(x) return (math.cos(x * math.pi * 3) - 1) / -2 end animations.MeuModelo.Idle:setBlendCurve(wave) -- Curva que fica em 0 e vai para 1 de repente na metade local function step(x) return x >= 0.5 and 1 or 0 end -- Usando GSBlend.curve para referenciar curvas prontas local GSBlend = require("GSAnimBlend") local minhaCurva = GSBlend.curve.easeOutBack animations.MeuModelo.Idle:setBlendCurve(minhaCurva)
Animation Method Reference
| Método | Parâmetros | Descrição |
|---|---|---|
:setBlendTime(t) | t: number | Sets blend in and out with the same duration in ticks. |
:setBlendTime(ti, to) | ti, to: number | Sets blend in and blend out with different durations. |
:getBlendTime() | — | Retorna {blendTimeIn, blendTimeOut}. |
:setBlendCurve(c) | c: string|function | Sets the easing curve. String = preset curve name; function = custom curve. |
:setOnBlend(f, p) | f: function, p: number | Define um callback para executar a cada frame do blend. p é a prioridade (padrão 0). Prioridade mais alta = executado depois (pode sobrescrever). |
:getBlendCallback(p) | p: number | Retorna o callback registrado na prioridade p. |
:isBlending() | — | Retorna true se a animação estiver em meio a um blend no momento. |
:play(instant?) | instant: boolean | Inicia a animação. Se instant=true, pula o blend in. |
:stop(instant?) | instant: boolean | Para a animação. Se instant=true, pula o blend out. |
:pause() | — | Pausa a animação e congela o blend no estado atual. |
:restart(blend?) | blend: boolean | Reinicia a animação. blend=false pula o blend. |
:setPlaying(s, instant?) | s: boolean, instant: boolean | Atalho para :play() / :stop(). Controla com boolean. |
:setBlend(w) | w: number | Sets the blend weight manually (0–1). Typically used inside callbacks. |
:getBlend() | — | Retorna o peso de blend atual gerenciado pela lib (não o valor nativo). |
:getPlayState() | — | Retorna "PLAYING", "PAUSED" ou "STOPPED". |
:isPlaying() | — | Retorna true se estiver tocando (inclui durante blend in). |
:isPaused() | — | Retorna true se pausada. |
:getTime() | — | Returns the current animation time in seconds. |
:setLength(l) | l: number | Resets the animation length, updating the lib's internal data. |
:newCode(t, code) | t: number, code: string | Adiciona uma instruction keyframe em tempo t (segundos) com código Lua. |
:blendTime(…) | igual a setBlendTime | Alias encadeável — retorna self. |
:blendCurve(…) | igual a setBlendCurve | Alias encadeável — retorna self. |
:onBlend(…) | igual a setOnBlend | Alias encadeável — retorna self. |
:playing(s, instant?) | igual a setPlaying | Alias encadeável. |
Blend Callbacks — Per-Frame Execution
Callbacks are functions executed every frame while a blend is occurring. The default callback (priority 0) simply aplica o peso linear. Você pode substituir ou adicionar comportamentos.
local GSBlend = require("GSAnimBlend") local animBlend = GSBlend.oldF.blend -- função nativa de blend do Figura -- Callback recebe (state, data) — veja os tipos abaixo local function meuCallback(state, data) -- Aplica o peso de blend normalmente animBlend(state.anim, math.lerp(state.from, state.to, state.progress)) -- Código extra: partículas de fogo quando blend > 50% if state.rawProgress > 0.5 then particles:newParticle( "flame", player:getPos():add(math.random() * 2 - 1, 1, math.random() * 2 - 1) ) end -- Limpeza quando o blend terminar if state.done then -- Faça cleanup aqui — este é o último frame do callback end end -- Prioridade 0 = substitui o callback padrão animations.MeuModelo.Idle:setOnBlend(meuCallback, 0) -- Prioridade 1 = roda DEPOIS do padrão (não substitui) animations.MeuModelo.Idle:setOnBlend(meuCallback, 1)
Objeto state (CallbackState)
| Campo | Tipo | Descrição |
|---|---|---|
state.anim | Animation | The animation currently blending. |
state.time | number | How long the blend has been running (in ticks). |
state.max | number | Tempo máximo do blend (em ticks). |
state.progress | number | Progresso modificado pela curva — use este para lerp visual. |
state.rawProgress | number | Progresso bruto de 0 a 1 — sem a curva aplicada. |
state.from | number | Peso de blend inicial. |
state.to | number | Peso de blend final. |
state.starting | boolean | true se a animação está sendo iniciada; false se está parando. |
state.done | boolean | true no último frame do blend — use para cleanup. |
Generators — Ready-made Callbacks with Configuration
Algumas funções em GSBlend.callback começam com gen — são generators. Eles retornam um callback em vez de serem callbacks por si só. Você os chama com argumentos e usa o retorno como callback.
| Generator | Descrição |
|---|---|
GSBlend.callback.base | Default callback. Simply applies linear blend weight. Already used automatically at priorite 0. |
GSBlend.callback.genBlendVanilla(parts) | Transiciona suavemente entre a rotação vanilla e a rotação da animação. Ideal para parts como Head, LeftArm, Body. Recebe uma tabela de ModelParts. |
GSBlend.callback.genBlendTo(anim) | Blends to another animation — instead of going to 0, goes to the target animation's values. |
GSBlend.callback.genBlendOut(anims) | Stops a list of animations at the same time this one blends out. |
GSBlend.callback.genDualBlend(cbIn, cbOut) | Usa um callback diferente para blend in e outro para blend out. |
GSBlend.callback.genTimeline(tl) | Executa uma timeline de keyframes com callbacks e curvas individuais. Ignora curvas globais em favor das da timeline. |
GSBlend.callback.genBezier(x1,y1,x2,y2) | Cria uma curva (não callback) Bezier cúbica customizada usando 4 pontos de controle, igual ao CSS cubic-bezier(). |
local GSBlend = require("GSAnimBlend") ── genBlendVanilla: smooth transition from vanilla pose → animated ── animations.MeuModelo.Walk:setOnBlend( GSBlend.callback.genBlendVanilla({ models.MeuModelo.Head, models.MeuModelo.Body, models.MeuModelo.LeftArm, models.MeuModelo.RightArm, models.MeuModelo.LeftLeg, models.MeuModelo.RightLeg, }) ) ── genBezier: custom cubic-bezier curve (like CSS) ── local minhaCurva = GSBlend.callback.genBezier(0.68, -0.55, 0.27, 1.55) animations.MeuModelo.Idle:setBlendCurve(minhaCurva) ── genDualBlend: different callback for in and out ── animations.MeuModelo.Ataque:setOnBlend( GSBlend.callback.genDualBlend( GSBlend.callback.base, -- callback ao iniciar GSBlend.callback.genBlendVanilla({ models.MeuModelo.RightArm }) -- callback ao parar ) )
GSBlend.oldF — Funções Nativas do Figura
GSAnimBlend sobrescreve alguns métodos de Animation. Para acessar a implementação original do Figura dentro de um callback, use GSBlend.oldF:
local GSBlend = require("GSAnimBlend") -- Apply blend directly via Figura (bypassing the lib layer) GSBlend.oldF.blend(animations.MyModel.Idle, 0.5) -- Ver o que está disponível em oldF -- /figura run GSBlend = require("GSAnimBlend") -- /figura run printTable(GSBlend.oldF)
Funções Internas da Lib
| Função | Descrição |
|---|---|
GSBlend.blend(anim, time, from, to, starting) | Inicia um blend manualmente em anim. Raramente necessário — use :play()/:stop(). |
GSBlend.stopBlend(anim, starting) | Para imediatamente um blend em andamento. Roda o callback uma última vez com done=true. |
GSBlend.newAnimData(obj, proxy) | Creates/returns the internal AnimData object of an animation. Internal use. |
Compatibility with EZAnims
GSAnimBlend and EZAnims are totalmente compatíveis e projetados para trabalhar juntos. O método :setBlendTimes() da instância EZAnims configura automaticamente o blend de todas as animations gerenciadas.
require("GSAnimBlend") -- must be required BEFORE EZAnims local anims = require("EZAnims") local myModel = anims:addBBModel(animations.model) -- Define blend para todos as animations gerenciadas pelo EZAnims de uma vez myModel:setBlendTimes(6) -- 6 ticks para exclusivas e inclusivas myModel:setBlendTimes(6, 3) -- 6 exclusivas, 3 inclusivas myModel:setBlendTimes() -- zera os blend times -- Você ainda pode configurar animations individuais manualmente animations.model.death:setBlendTime(15):setBlendCurve("easeInQuad")
require("GSAnimBlend") antes de require("EZAnims"). O GSAnimBlend precisa ter injetado seus métodos antes que o EZAnims configure as animações./figura run GSBlend = require("GSAnimBlend")/figura run printTable(GSBlend)/figura run printTable(GSBlend.callback)/figura run printTable(GSBlend.curve)EZAnims v3.0.0 by JimmyHelp
Automatically detects Blockbench animations by name and plays them according to player state — no manual configanual de lógica de estado. Nomeie as animations seguindo a lista abaixo e a lib cuida de tudo.
vanilla_model.✔ What you can do
idle, idle2, idle3…)idle_sword, walk_sword…):setBlendTimes()✘ What you CANNOT do
vanilla_model diretamenteoutro funcione perfeitamente em todos os casos — pode haver artefatos visuais:setBlendTimes() sem ter GSAnimBlend instalado — a função não terá efeitoInstalação
Adicionar o arquivo
Coloque EZAnims.lua na pasta do seu avatar.
Name the animations in Blockbench
Open Blockbench and name your animations following the list below (idle, walk, sprint…). You do not need all of them — only the oue você criou.
Done!
You do not even need to add Lua code if you do not want advanced features. EZAnims automatically searches for animations com os nomes certos no seu avatar.
-- This alone is sufficient for basic use require("EZAnims") -- To use advanced functions, store the reference: local anims = require("EZAnims") local myModel = anims:addBBModel(animations.nomeDoSeuModelo)
Lista Completa — Animações Exclusivas
Exclusivas: apenas uma dessas toca por vez. São os estados principais do player.
| Nome | When playing | Notas |
|---|---|---|
idle | Parado no chão | Estado base |
walk | Andando para frente | — |
walkback | Andando para trás | — |
sprint | Correndo | — |
jumpup | Subindo de um pulo intencional | Requer ter pulado com espaço |
jumpdown | Descendo de um pulo intencional | Requer ter pulado com espaço |
walkjumpup | Subindo de pulo enquanto andava | Fallback: jumpup |
walkjumpdown | Descendo de pulo enquanto andava | Fallback: jumpdown |
fall | Caindo de grande altura | Ativa quando velocidade Y ≤ fallVel (padrão -0.6) |
sprint | Correndo | — |
sprintjumpup | Subindo de pulo enquanto corria | Fallback: jumpup |
sprintjumpdown | Descendo de pulo enquanto corria | Fallback: jumpdown |
crouch | Agachado parado | Base para todos os estados agachados |
crouchwalk | Andando agachado para frente | Fallback: crouch |
crouchwalkback | Andando agachado para trás | Fallback: crouch |
crouchjumpup | Pulando agachado (subindo) | Fallback: jumpup |
crouchjumpdown | Pulando agachado (descendo) | Fallback: jumpdown |
elytra | Voando com elytra | — |
elytradown | Mergulhando com elytra | Olhando para baixo durante voo |
trident | Impulso inicial com tridente Riptide | — |
sleep | Dormindo na cama | — |
swim | Nadando (posição deitada na água) | — |
sit | Sentado em veículo | Compat com mods de sentar |
sitmove | Se movendo sentado | Fallback: sit |
sitmoveback | Movendo para trás sentado | Fallback: sit |
sitjumpup | Pulando sentado (subindo) | Fallback: jumpup |
sitjumpdown | Pulando sentado (descendo) | Fallback: jumpdown |
sitpass | Passageiro sentado (carruagem etc.) | — |
crawl | Engatinhando em movimento | — |
crawlstill | Engatinhando parado | Fallback: crawl |
fly | Voando em criativo (estado base) | Todos os voos em criativo voltam para este |
flywalk | Voando e se movendo para frente | Fallback: fly |
flywalkback | Voando e se movendo para trás | Fallback: fly |
flysprint | Voando rápido em criativo | Fallback: fly |
flyup | Voando e subindo | Fallback: fly |
flydown | Voando e descendo | Fallback: fly |
climb | Escalando escada/trepadeira | Estado base de escalada |
climbstill | Segurando na escada parado | Fallback: climb |
climbdown | Descendo escada | Fallback: climb |
climbcrouch | Agachado na escada | Fallback: climb |
climbcrouchwalking | Andando agachado na escada | Fallback: climb |
water | Submerso em água (estado base) | Todos os estados na água voltam para este |
waterwalk | Nadando para frente | Fallback: water |
waterwalkback | Nadando para trás | Fallback: water |
waterup | Subindo na água | Fallback: water |
waterdown | Descendo na água | Fallback: water |
watercrouch | Agachado na água | Fallback: water |
watercrouchwalk | Andando agachado na água | Fallback: water |
watercrouchwalkback | Andando agachado para trás na água | Fallback: water |
watercrouchup | Subindo agachado na água | Fallback: water |
watercrouchdown | Descendo agachado na água | Fallback: water |
hurt | Levando dano | — |
death | Morrendo | — |
Lista Completa — Animações Inclusivas
Inclusivas: tocam ao mesmo tempo que as exclusivas, baseadas no item na mão. Use sufixo R para mão direita, L para mão esquerda.
| Nome | When playing | Notas |
|---|---|---|
attackR / attackL | Atacando com o respectivo braço | Deve estar em loop mode ONCE |
mineR / mineL | Quebrando bloco | Deve estar em loop mode ONCE |
eatR / eatL | Comendo | — |
drinkR / drinkL | Bebendo poção/água | — |
blockR / blockL | Bloqueando com escudo | — |
bowR / bowL | Puxando um arco | — |
loadR / loadL | Carregando uma besta | — |
crossR / crossL | Segurando besta já carregada | — |
spearR / spearL | Preparando para jogar tridente | — |
spyglassR / spyglassL | Usando luneta | — |
hornR / hornL | Tocando chifre de cabra | — |
brushR / brushL | Usando escova arqueológica | — |
holdR / holdL | Segurando qualquer item (sem usar) | Estado genérico de segurar |
Múltiplas Animações, Intro, Outro e Estados
── Multiple animations of the same type ─────────────── -- The number goes right after the base name -- idle, idle2, idle3 → as três serão selecionadas aleatoriamente ao ficar parado ── Intro (animation that loops in) ───────────────── -- Use uma única animação com instruction keyframe no fim para definir o loop: -- (no final da timeline, adicione keyframe de instrução com o código abaixo) -- this:setTime(loopStart) -- onde loopStart é o tempo em segundos do loop ── Outro (plays when the state ENDS) ───────────────── -- Nome: statename_outro Ex: idle_outro, walk_outro -- Se usar com setState(): idle_sword_outro (outro vai por último) ── Custom state (setState) ─────────────────────────── -- Nome: statename_sufixo Ex: idle_sword, walk_sword, sprint_sword -- Ao chamar setState("sword"), as versões _sword tocam no lugar das normais -- Animações sem versão _sword continuam usando a versão padrão ── Multiple + State + Outro ────────────────────────── -- idle2_sword_outro → 2ª animação de idle, com estado "sword", é outro
API Global — Funções da Instância Principal
These functions are called on the instance returned by require("EZAnims"), not on the individual bbmodel.
| Função | Parâmetros | Descrição |
|---|---|---|
anims:addBBModel(…) | animations.modelo, … | Registers one or more bbmodels as a single instance. Returns the control object of the modelo. Cada instância adicional tem custo alto de instruções — prefira adicionar múltiplos bbmodels ao mesmo addBBModel(). |
anims:setFallVel(v) | v: number | Define a velocidade de queda (negativa) necessária para ativar a animação fall. Padrão: -0.6. Ex: -1.0 significa que só cai quando a velocidade Y é ≤ -1. |
anims:setOneJump(b) | b: boolean | Se true, usa jumpup/jumpdown mesmo quando não há sprintjumpup/crouchjumpup. |
anims:isFlying() | — | Retorna true se o player está voando em criativo. Usa a mesma detecção das animations de voo. Requer que exista ao menos uma animação de voo. |
anims:isJumping() | — | Retorna true se o player está em um pulo intencional. Usa a mesma detecção das animations de jump. |
anims:disableAutoSearch() | — | Desativa a busca automática de animations por nome. Deve ser chamado antes de addBBModel(). Economiza instruções no init — mas requer uso de :setAnims() para registrar as animations manualmente. |
API da Instância do Modelo — Retorno de addBBModel()
These functions are called on the object returned by anims:addBBModel().
| Função | Parâmetros | Descrição |
|---|---|---|
:setState(s) | s: string | Define um estado customizado. Animações com o sufixo _s tocam no lugar das padrão. Passar string vazia ou nenhum argumento reseta o estado. |
:getAnimationStates() | — | Retorna uma tabela com o estado atual de todas as animations (true/false). |
:getAnimationStates(nome) | nome: string | Retorna true/false indicando se a animação do tipo nome está tocando agora. Ex: :getAnimationStates("idle"). |
:addExcluOverrider(…) | Animation, … | Quando qualquer uma das animations passadas estiver tocando, para todas as animations exclusivas. |
:addIncluOverrider(…) | Animation, … | Quando qualquer uma das animations passadas estiver tocando, para todas as animations inclusivas. |
:addAllOverrider(…) | Animation, … | Quando qualquer uma das animations passadas estiver tocando, para todas as animations (exclusivas e inclusivas). |
:setExcluOff(b) | b: boolean | Liga (false) ou desliga (true) todas as animations exclusivas. |
:setIncluOff(b) | b: boolean | Liga (false) ou desliga (true) todas as animations inclusivas. |
:setAllOff(b) | b: boolean | Liga (false) ou desliga (true) todas as animations (ambas as categorias). |
:setBlendTimes(e, i) | e: number, i?: number | Define blend times (ticks) para exclusivas (e) e inclusivas (i). Se apenas e for passado, aplica aos dois. Sem argumentos, zera ambos. Requer GSAnimBlend. |
:setRestart(nome, b) | nome: string, b: boolean | Define se uma animação que normalmente reinicia (como attackR) deve ou não reiniciar quando o estado se repete. |
:setAnims(tabela, hasfly?) | tabela: table, hasfly?: bool | Registra animations manualmente, sem a busca automática. Requer disableAutoSearch() antes. hasfly=true é obrigatório se houver animations de voo criativo. Ver exemplo completo abaixo. |
Advanced Examples
local anims = require("EZAnims") -- Two models on the same instance (synchronized) local myModel = anims:addBBModel(animations.corpo, animations.acessorios) -- Estado com espada: idle_sword, walk_sword etc. tocam no lugar dos normais local comEspada = false function pings.toggleEspada() comEspada = not comEspada myModel:setState(comEspada and "sword" or nil) end -- Overrider: durante uma cutscene, para tudo myModel:addAllOverrider(animations.corpo.cutscene_inicio) -- Detectar estado de voo para usar na Action Wheel function events.TICK() if anims:isFlying() then -- fazer algo quando estiver voando end end
local anims = require("EZAnims") -- 1. Desativar busca automática ANTES de addBBModel anims:disableAutoSearch() local m = anims:addBBModel(animations.model) -- 2. Registrar apenas as animations que você tem -- (Remova ou deixe nil as que não existem — economiza instruções) m:setAnims({ idle = { animations.model.idle, animations.model.idle2 }, walk = { animations.model.walk }, sprint = { animations.model.sprint }, crouch = { animations.model.crouch }, jumpup = { animations.model.jump }, jumpdown= { animations.model.jump }, hurt = { animations.model.hurt }, death = { animations.model.death }, attackR = { animations.model.attack }, holdR = { animations.model.hold }, fly = { animations.model.fly }, }, true) -- true = tem animations de voo criativo
Integration with GSAnimBlend
-- IMPORTANTE: GSAnimBlend SEMPRE primeiro local GSBlend = require("GSAnimBlend") local anims = require("EZAnims") local m = anims:addBBModel(animations.model) -- Blend para TODAS as animations gerenciadas pelo EZAnims m:setBlendTimes(6) -- 6 ticks (0.3s) para exclusivas e inclusivas m:setBlendTimes(8, 4) -- 8 exclusivas, 4 inclusivas -- Blend personalizado para animations específicas (opcional) animations.model.death:setBlendTime(20):setBlendCurve("easeInQuad") animations.model.hurt:setBlendTime(2) -- genBlendVanilla em animations de movimento para transição suave local bodyParts = { models.model.Head, models.model.Body, models.model.LeftArm, models.model.RightArm, models.model.LeftLeg, models.model.RightLeg, } animations.model.idle:setOnBlend(GSBlend.callback.genBlendVanilla(bodyParts)) animations.model.walk:setOnBlend(GSBlend.callback.genBlendVanilla(bodyParts))
Troubleshooting
setBlendTimes() requer que o GSAnimBlend esteja instalado e carregado antes do EZAnims. Se o GSAnimBlend não foi encontrado pela lib, a função existe mas não tem efeito.anims:disableAutoSearch() + :setAnims({...}) para registrar manualmente apenas as animations que você usa. A busca automática percorre todo o avatar e tem custo alto._outro são funcionais mas imperfeitas — podem causar pequenos "flicks" na transição. Para uma solução mais robusta, considere usar GSAnimBlend com blend out configurado.addBBModel() adiciona um custo significativo em instruções. Use instâncias separadas apenas quando os modelos precisam de ambientes (estados, overriders) completamente diferentes. Se possível, adicione ambos na mesma instância com anims:addBBModel(m1, m2).SquAPI v1.2.1 por MrSirSquishy
Modular collection of physical and cosmetic behaviors for avatars. Each module is a separate file in SquAPI_modules/ — use apenas o que precisar. Requer SquAPI.lua, SquAssets.lua e os módulos desejados na mesma pasta.
✔ What you can do
:enable()/:disable()/:toggle()✘ What you CANNOT do
SquAssets.lua presente — a maioria depende delearm ou leg sem ter VanillaElement.lua também presenteSetup
avatar/
SquAPI.lua
SquAPI_modules/
SquAssets.lua
VanillaElement.lua -- required for arm and leg
ear.lua
tail.lua
eye.lua
smoothHead.lua
squishy.lua
bewb.lua
bounceWalk.lua
arm.lua
leg.lua
crouch.lua
randimation.lua
animateTexture.lua
hoverPoint.lua
FPHand.lua
taur.lualocal squapi = require("SquAPI") -- Cada módulo fica acessível como squapi.nomeDoModulo -- ex: squapi.ear, squapi.tail, squapi.eye, squapi.smoothHead...
ear — Ear Physics
Ears respond to movement, camera rotation, and have random twitch animation. Supports horizontal ears (elven-style) and asymmetric pairs.
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
leftEar | ModelPart | — | Orelha esquerda (obrigatório) |
rightEar | ModelPart? | nil | Orelha direita (nil = somente esquerda) |
rangeMultiplier | number | 1 | Quanto as orelhas rotacionam com a cabeça |
horizontalEars | boolean | false | True para orelhas que apontam para os lados |
bendStrength | number | 2 | Força ao mover o personagem |
doEarFlick | boolean | true | Random twitch animation |
earFlickChance | number | 400 | Maximum interval in ticks between twitches (lower = more frequent) |
earStiffness | number | 0.1 | Rigidez da mola interna |
earBounce | number | 0.8 | Fator de bounce (amortecimento) |
local minhaOrelha = squapi.ear:new( models.model.root.Head.LeftEar, models.model.root.Head.RightEar, 1, false, 2, true, 400, 0.1, 0.8 ) -- Orelha élfica horizontal (stick out sideways) squapi.ear:new(models.model.root.Head.LeftEar, nil, 1, true) minhaOrelha:disable() -- desativa minhaOrelha:toggle() -- liga/desliga
tail — Tail Physics
Tail with any number of segments. Each segment must be nested inside the previous one in Blockbench. Supports multiple de-synced tails.
| Parâmetro | Padrão | Descrição |
|---|---|---|
tailSegmentList | — | Table with all tail segments (required) |
idleXMovement | 15 | Amplitude do balanço lateral |
idleYMovement | 5 | Amplitude do balanço vertical |
idleXSpeed | 1.2 | Velocidade do balanço lateral |
idleYSpeed | 2 | Velocidade do balanço vertical |
bendStrength | 2 | Força ao se mover |
velocityPush | 0 | Dobrar ao mover frente/trás — bom para caudas apontando para baixo |
initialMovementOffset | 0 | Initial sway offset — de-syncs multiple tails |
offsetBetweenSegments | 1 | How much each segment lags behind the previous one |
stiffness | 0.005 | Rigidez da mola interna |
bounce | 0.9 | Bounce (amortecimento) |
flyingOffset | 90 | Rotação da cauda ao voar/nadar/riptide |
downLimit | -90 | Downward rotation limit per segment |
upLimit | 45 | Upward rotation limit per segment |
squapi.tail:new({ models.model.root.Body.tail, models.model.root.Body.tail.tailSeg1, models.model.root.Body.tail.tailSeg1.tailSeg2, models.model.root.Body.tail.tailSeg1.tailSeg2.tailTip, }, 15, 5, 1.2, 2, 2, 0, 0, 1, 0.005, 0.9, 90, -90, 45) -- Duas caudas desincronizadas: initialMovementOffset diferente squapi.tail:new({models.model.tail1, models.model.tail1.tip}, 15, 5, 1.2, 2, 2, 0, 0) squapi.tail:new({models.model.tail2, models.model.tail2.tip}, 15, 5, 1.2, 2, 2, 0, 20)
eye — Eye Movement
Eyes move smoothly following the gaze direction. Create one instance per eye.
| Parâmetro | Padrão | Descrição |
|---|---|---|
element | — | O ModelPart do olho (obrigatório) |
leftDistance | 0.25 | Distância máxima para a esquerda |
rightDistance | 1.25 | Distância máxima para a direita |
upDistance | 0.5 | Distância máxima para cima |
downDistance | 0.5 | Distância máxima para baixo |
switchValues | false | True se os olhos ficam nas laterais da cabeça (troca eixos) |
-- Olho esquerdo e direito separados squapi.eye:new(models.model.root.Head.EyeLeft, 0.25, 1.25, 0.5, 0.5) squapi.eye:new(models.model.root.Head.EyeRight, 0.25, 1.25, 0.5, 0.5) -- Escala dos olhos (lerp para o valor) minhaOrelha:setEyeScale(1.5)
smoothHead — Cabeça Suave
The head rotates smoothly with slight lateral tilt. Supports neck, torso and automatic straightening during animções.
| Parâmetro | Padrão | Descrição |
|---|---|---|
element | — | ModelPart ou tabela de parts (head, neck, torso…) |
strength | 1 | Multiplicador de rotação. Tabela para força por segmento. |
tilt | 0.1 | Força da inclinação lateral ao olhar de lado |
speed | 1 | Velocidade de rotação para o alvo |
keepOriginalHeadPos | true | Seguir posição vanilla (ex: abaixar ao agachar). Número = part do pescoço que se move. |
fixPortrait | true | Corrigir o retrato do avatar se encontrar um group "head" |
animStraightenList | nil | Table of animations that straighten the head when playing |
straightenMultiplier | 0.5 | Força do endireitamento |
straightenSpeed | 0.5 | Velocidade do endireitamento |
blendToConsiderStopped | 0.1 | Blend below this value = animation considered stopped |
-- Head only squapi.smoothHead:new({models.model.root.Head}) -- Neck + head with different strength per segment squapi.smoothHead:new( {models.model.root.Neck, models.model.root.Neck.Head}, {0.3, 0.7}, -- pescoço roda 30%, cabeça 70% 0.1, 1, true, true ) -- Endireitar durante animação de ataque squapi.smoothHead:new( {models.model.root.Head}, 1, 0.1, 1, true, true, {animations.model.attack}, 0.5, 0.5 )
squishy — Squash & Stretch
Applies squash on jump and stretch on fall, with bounce on landing. Classic cartoon animation effect.
| Parâmetro | Padrão | Descrição |
|---|---|---|
model | — | Raiz do modelo a ser squishado (obrigatório) |
springStrength | 0.3 | Força para retornar ao tamanho normal |
dampening | 0.3 | Amortecimento (como resistência do ar) |
crouchBounce | -0.5 | Bounce ao agachar/desagachar (negativo = achata, positivo = estica) |
lowerLimit | vec(0.6,0.5,0.6) | Escala mínima permitida |
upperLimit | vec(1.25,1.5,1.25) | Escala máxima permitida |
squapi.squishy:new( models.model, -- raiz do modelo 0.3, 0.3, -0.5, vec(0.6, 0.5, 0.6), vec(1.25, 1.5, 1.25) )
bewb — Breast Physics
Adds spring physics to the character's chest, with idle sway and movement response.
| Parâmetro | Padrão | Descrição |
|---|---|---|
element | — | ModelPart do elemento (obrigatório) |
bendability | 2 | How much it moves while walking |
stiff | 0.05 | Rigidez da mola |
bounce | 0.9 | Fator de bounce |
doIdle | true | Balanço idle (tipo respiração) |
idleStrength | 4 | Amplitude do balanço idle |
idleSpeed | 1 | Velocidade do balanço idle |
downLimit | -10 | Rotação mínima |
upLimit | 25 | Rotação máxima |
squapi.bewb:new(models.model.root.Body.Chest, 2, 0.05, 0.9, true, 4, 1, -10, 25)
arm — Arm with Vanilla Movement
Aplica a rotação vanilla do braço a um ModelPart customizado. Requer VanillaElement.lua.
squapi.arm:new( models.model.root.RightArm, -- element 1, -- strength (1) true, -- isRight (false = esquerdo) true -- keepPosition (true) )
leg — Leg with Vanilla Movement
Aplica rotação e posição vanilla da perna a um ModelPart customizado. Requer VanillaElement.lua.
squapi.leg:new( models.model.root.RightLeg, 1, -- strength true, -- isRight true -- keepPosition )
bounceWalk — Passo Saltitante
Moves the model smoothly up and down while walking, creating a more animated walking effect.
squapi.bounceWalk:new(models.model, 1) -- model, bounceMultiplier
randimation — Random Animation
Plays an animation at random intervals. Perfect for eye blinks, ear twitches, idle micro-movements.
| Parâmetro | Padrão | Descrição |
|---|---|---|
animation | — | A animação a reproduzir (obrigatório) |
minTime | 100 | Tempo mínimo entre reproduções (ticks) |
maxTime | 300 | Tempo máximo entre reproduções (ticks) |
stopOnSleep | false | True for blinks — does not blink during sleep |
-- Piscada aleatória squapi.randimation:new(animations.model.blink, 80, 250, true) -- Micro-movimento idle squapi.randimation:new(animations.model.idleTwitch, 200, 500)
animateTexture — Textura Animada
Animates a texture in a loop by moving the UV horizontally (or vertically). Useful for blinking eyes, flames, water, etc.
squapi.animateTexture:new( models.model.root.Head.EyeL, -- element 4, -- numberOfFrames 0.25, -- framePercent (1/4 da textura por frame) 2, -- slowFactor (1 = veloz, 2 = metade da velocidade) false -- vertical (false = frames horizontais) )
hoverPoint — Ponto Flutuante
Makes an element float in the world with spring physics — responds to player acceleration and has optional block collision.
| Parâmetro | Padrão | Descrição |
|---|---|---|
element | — | ModelPart a flutuar (obrigatório) |
elementOffset | vec(0,0,0) | Posição relativa ao player |
springStrength | 0.2 | Força de retorno à posição original |
mass | 5 | Mass — heavier = accelerates/decelerates more slowly |
resistance | 1 | Air resistance — speed decays faster |
rotationSpeed | 0.05 | Velocidade de rotação ao alvo |
rotateWithPlayer | true | Rotate with the player |
doCollisions | false | Block collision (experimental system) |
squapi.hoverPoint:new( models.model.root.FloatCrystal, vec(0, 2, 0), -- offset: 2 blocos acima do player 0.2, 5, 1, 0.05, true, false )
FPHand — Mão em Primeira Pessoa
Positions and scales a custom element in first-person hand. Requires the FP hands modification option to be enabledda nas configurações do Figura.
squapi.FPHand:new( models.model.root.FPRightArm, -- element vec(0, 0, 0), -- position offset 1, -- scale false -- onlyVisibleInFP (oculta quando não FP) )
crouch — Crouch Animation
Automatically plays crouch/uncrouch and crawl animations. It is a direct function, not a module with :new().
squapi.crouch(
animations.model.crouch, -- crouchAnim (hold on last frame, override)
animations.model.uncrouch, -- unCrouchAnim (play once, override) — nil se não tiver
animations.model.crawl, -- crawl — nil se não tiver
nil -- uncrawl
)taur — Centaur Body Physics
Makes the centaur's rear body respond physically to terrain slope and movement.
squapi.taur:new( models.model.root.TaurBody, -- corpo traseiro (obrigatório) models.model.root.FrontLegs, -- pernas dianteiras (nil) models.model.root.BackLegs -- pernas traseiras (nil) )
Controle Universal — enable/disable/toggle
All modules that return an object support these methods:
minhaOrelha:enable() -- enables the module minhaOrelha:disable() -- disables the module minhaOrelha:toggle() -- alterna minhaOrelha:setEnabled(true) -- define explicitamente
:disable() no setup e :enable() quando necessário — por exemplo, para habilitar o smoothHead apenas enquanto uma animação NÃO está tocando.PhysBone API por ChloeSpacedOut
Simulates physical bones in real time — hair, ears, tail tips, ropes, chains. The system é adicionado diretamente como método :newPhysBone() em qualquer ModelPart do Blockbench.
events.ENTITY_INIT para garantir que o modelo esteja disponível. O ID do physbone é gerado como physBone<NomeDoGroup> — ex: group Hair → physBone.physBoneHair.Setup e Uso Básico
local physBone = require("physBoneAPI") function events.ENTITY_INIT() -- :newPhysBone("physBone") adiciona o physbone ao grupo local hair = models.model.root.Head.Hair:newPhysBone("physBone") hair:setLength(20) :setGravity(0.8) :setAirResistance(0.2) :setVecMod(0.6, 1, 1) :setBounce(0.3) :setNodeEnd(18) :setNodeDensity(3) :setNodeRadius(0.5) end
Referência Completa de Parâmetros
| Método | Padrão | Descrição |
|---|---|---|
:setMass(n) | 1 | Bone mass — affects inertia. Higher = slower to accelerate/stop. |
:setLength(n) | 16 | Comprimento total do bone em unidades Blockbench. |
:setGravity(n) | 1 | Força da gravidade (positivo = cai, 0 = sem gravidade, negativo = flutua). |
:setAirResistance(n) | 0.2 | Air resistance — dampens movement. Higher = more rigid. |
:setSimSpeed(n) | 1 | Velocidade geral da simulação. |
:setSpringForce(n) | 0 | Força de mola que atrai de volta à posição de equilíbrio. |
:setEquilibrium(vec3) | vec(0,0,0) | Posição de equilíbrio da ponta do bone. |
:setForce(vec3) | vec(0,0,0) | Constant force applied to the bone (e.g. directional wind). |
:setRotMod(vec3) | vec(1,1,1) | Multiplicador de rotação por eixo XYZ. |
:setVecMod(x,y,z) | vec(1,1,1) | Modificador da direção de movimento por eixo. Reduzir X ou Z limita balanço lateral/frontal. |
:setRollMod(n) | 1 | Multiplicador de rotação em roll (torção). |
:setNodeStart(n) | 0 | Ponto de início dos nós de debug. |
:setNodeEnd(n) | comprimento | Ponto de fim dos nós de debug. |
:setNodeDensity(n) | 2 | Nós visuais por segmento. 0 = desativa visualização de debug. |
:setNodeRadius(n) | 0.5 | Raio dos nós de debug visíveis. |
:setBounce(n) | 0 | Elasticidade ao colidir ou chegar ao limite. 0 = sem bounce. |
Presets
Presets allow reusing configurations across multiple bones. Define once with physBone:setPreset() e aplique com :updateWithPreset().
-- Definir preset (pode ser fora de ENTITY_INIT) physBone:setPreset("cabelo_longo", 1, -- mass 24, -- length 0.8, -- gravity 0.15,-- airResistance 1, -- simSpeed nil, -- equilibrium 0, -- springForce nil, -- force nil, -- rotMod vec(0.5, 1, 1), -- vecMod nil, -- rollMod 0, 20, 3, 0.5, 0.2 -- nodeStart, nodeEnd, density, radius, bounce ) function events.ENTITY_INIT() -- Aplicar preset em múltiplos bones models.model.root.Head.HairLeft:newPhysBone("physBone"):updateWithPreset("cabelo_longo") models.model.root.Head.HairRight:newPhysBone("physBone"):updateWithPreset("cabelo_longo") models.model.root.Head.Franja:newPhysBone("physBone"):updateWithPreset("cabelo_longo") -- Acessar por nome depois de criado physBone.physBoneHairLeft:setNodeDensity(0) -- esconde debug dos nós end
newPhysBoneFromValues — Criação Manual
For advanced cases where you need to pass all values directly without chaining setters:
function events.ENTITY_INIT() physBone:newPhysBoneFromValues( models.model.root.Body.Cape, -- path 1, -- mass 30, -- length 0.5, -- gravity 0.1, -- airResistance 1, -- simSpeed nil, -- equilibrium 0, -- springForce nil, -- force nil, -- rotMod vec(1,1,0.3), -- vecMod: limita movimento frontal nil, -- rollMod 0, 28, 0, 0.5, 0, -- nodeStart, nodeEnd, density, radius, bounce nil, -- id "Cape" -- nome personalizado → physBone.physBoneCape ) end
setNodeDensity(0) nos bones de produção para remover os cubinhos de debug azuis visíveis. Use setNodeDensity(2) temporariamente para visualizar a física durante o desenvolvimento.Swinging Physics por Manuel
Simulates pendulum physics for accessories, earrings, pendants and chains. Parts respond to player movement and rotationlayer com realismo físico. Suporta encadeamento de elos e limites de rotação customizados.
✔ What you can do
✘ What you CANNOT do
swingOnBody para membros com forças extras — o resultado pode não ser ideal para braços/pernasSetup
local SwingingPhysics = require("swinging_physics")
swingOnHead — Balançar na Cabeça
For accessories attached to the head. Responds to camera rotation and movement.
SwingingPhysics.swingOnHead(part, dir, limits, root, depth, globalLimits)| Parâmetro | Tipo | Descrição |
|---|---|---|
part | ModelPart | A part que vai balançar (obrigatório) |
dir | number | Direção de onde a part "pende". 0=frente, 90=esquerda, 180=trás, 270=direita, 45=diagonal |
limits | table? | Limites de rotação: {xMin, xMax, yMin, yMax, zMin, zMax}. Nil = sem limites. |
root | ModelPart? | Para correntes: a part raiz da corrente (o elo anterior) |
depth | number? | Depth in the chain (0=first link). Increasing adds progressive friction. |
globalLimits | boolean? | True = limits in global space (negates vanilla rotation before applying). Avoids clipping ao olhar para cima/baixo. |
-- Brinco simples local brinco = SwingingPhysics.swingOnHead( models.model.root.Head.Earring, 90, {-45, 45, -30, 30, -20, 20} ) -- Brinco com limites globais (não vai para dentro da cabeça ao olhar para cima) local brinco2 = SwingingPhysics.swingOnHead( models.model.root.Head.Earring2, 90, {-45, 45, -30, 30, -20, 20}, nil, 0, true -- globalLimits = true ) -- Corrente de 3 elos local elo1 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1, 0) local elo2 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1.Chain2, 0, nil, models.model.root.Head.Chain1, 1) local elo3 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1.Chain2.Chain3, 0, nil, models.model.root.Head.Chain1, 2)
swingOnBody — Balançar no Corpo
For accessories attached to the body. Responds to body movement and tilt.
-- Pingente no peito local pingente = SwingingPhysics.swingOnBody( models.model.root.Body.Pendant, 0, -- dir: 0 = frente {-25, 25, -20, 20, -15, 15} -- limits ) -- Acessório nas costas local capa = SwingingPhysics.swingOnBody(models.model.root.Body.Cape, 180)
Direções Comuns (dir)
| Valor | Significado | Uso típico |
|---|---|---|
0 | Frente | Colar, pingente no peito |
45 | Frente-esquerda | Ornamento diagonal |
90 | Esquerda | Brinco esquerdo, ornamento lateral |
135 | Trás-esquerda | — |
180 | Trás | Capa, rabo de cavalo |
-90 / 270 | Direita | Brinco direito |
SwingHandler — Controle em Runtime
Both functions return a SwingHandler object with the following methods:
| Método | Descrição |
|---|---|
:setEnabled(bool) | Liga (true) ou desliga (false) a física. Quando desligado, a part fica parada. |
:getEnabled() | Returns whether physics is active. |
:setPart(modelPart) | Troca a part sendo controlada. |
:getPart() | Returns the current part. |
:setDir(number) | Altera a direção em runtime. |
:getDir() | Returns the current direction. |
:setLimits(table) | Altera os limites de rotação em runtime. |
:getLimits() | Returns the current limits. |
:setRoot(modelPart) | Sets the root for chains at runtime. |
:getRoot() | Returns the current root. |
:setDepth(number) | Altera a profundidade (atrito) em runtime. |
:getDepth() | Returns the current depth. |
-- Desativar física ao entrar em cutscene brinco:setEnabled(false) -- Mudar direção e limites dinâmicamente brinco:setDir(180) brinco:setLimits({-20, 20, -15, 15, -10, 10})
Animated Text v0.0 por Katt
Creates groups of TextTasks where each character has independent position, rotation and scale. Allows effects like waves, rotação por caractere, texto que flutua e outras animations avançadas de texto.
setText() a cada frame — a função precisa recriar todos os TextTasks e isso é custoso. Use apenas quando o texto realmente mudar.Setup
local animatedText = require("animatedText")
new() — Create Animated Text
animatedText.new(name, parent, offset, scale, parentType, json)| Parâmetro | Tipo | Descrição |
|---|---|---|
name | string | ID único para acessar o texto depois (obrigatório) |
parent | ModelPart | Parent part — cannot be changed after creation |
offset | Vector3 | Posição relativa ao parent |
scale | Vector3 | Escala dos caracteres e espaçamento |
parentType | string | Tipo de parent type (ex: "BILLBOARD", "HEAD") |
json | table/string | Text content — can be a simple string or JSON table. Optional (can be definido depois com setText()). |
-- String simples animatedText.new("titulo", models.model.root.Head, vec(0,20,0), vec(0.4,0.4,1), "BILLBOARD", "Olá!") -- JSON formatado com múltiplos estilos animatedText.new("nameplate", models.model.root.Head, vec(0,22,0), vec(0.45,0.45,1), "BILLBOARD", { {text = "◈ ", color = "aqua"}, {text = player:getName(), color = "white", bold = true}, {text = " :java:", font = "figura:badges"}, }) -- Sem JSON no new() — definir depois animatedText.new("hp", models.model.root.Head, vec(0,18,0), vec(0.3,0.3,1), "BILLBOARD")
setText() — Atualizar Conteúdo
Replaces all text content. Recreates TextTasks internally — do not call every frame.
-- Updates whenever HP changes local lastHp = -1 function events.TICK() local hp = math.floor(player:getHealth()) if hp ~= lastHp then lastHp = hp animatedText.setText("hp", { {text = "❤ ", color = "red"}, {text = tostring(hp), color = "white", bold = true}, }) end end
transform() — Animate per Character
Applies position, rotation and/or scale to a specific character. Called inside a per-character loop in RENDER.
animatedText.transform(name, pos, rot, scale, char)| Parâmetro | Tipo | Descrição |
|---|---|---|
name | string | ID do texto |
pos | Vector3? | Offset de posição relativo ao anchor do caractere. nil = sem mudança. |
rot | Vector3? | Rotação do caractere. nil = sem rotação. |
scale | Vector3? | Escala adicional (somada à escala base). nil = sem mudança. |
char | table | Objeto do caractere — obtido iterando getTask().textTasks |
local tickTime = 0 function events.TICK() tickTime = tickTime + 1 end function events.RENDER(delta, context) if context ~= "FIRST_PERSON" and context ~= "RENDER" then return end -- Onda senoidal vertical for i, char in pairs(animatedText.getTask("nameplate").textTasks) do local wave = math.sin((tickTime + delta) / 8 + i) * 0.5 animatedText.transform("nameplate", vec(0, wave, 0), nil, nil, char) end -- Rotação por caractere (efeito espiral) for i, char in pairs(animatedText.getTask("titulo").textTasks) do local rot = math.sin((tickTime + delta) * 0.1 + i * 0.5) * 15 animatedText.transform("titulo", nil, vec(0, 0, rot), nil, char) end end
getTask() — Acessar Informações
Returns the internal table of an animated text. Useful for iterating TextTasks and applying additional styles.
local task = animatedText.getTask("nameplate") -- task.offset → Vector3 with the original offset -- task.scale → Vector3 com a escala base -- task.root → ModelPart raiz do texto -- task.textTasks → tabela de {anchor: Vector3, task: TextTask} -- Adicionar outline em todos os caracteres for _, v in pairs(animatedText.getTask("nameplate").textTasks) do v.task:outline(true) end
remove() — Remover Texto
animatedText.remove("nameplate") -- remove todos os TextTasks do grupo
Confetti Particle System
Particle system based on ModelParts and Sprites, fully customizable. Supports 3D mesh, 2D sprites, billboard, emissividade, físicas de aceleração, fricção, escala e rotação ao longo do tempo — com callbacks por frame e por tick.
✔ What you can do
✘ What you CANNOT do
particles:newParticle() do Figura para isso)Setup
local confetti = require("confetti")
registerMesh() — Registrar Partícula de Mesh
Registers a Blockbench ModelPart as a particle type. The ModelPart serves as a template — it does not need to be visibvel no modelo.
confetti.registerMesh(name, mesh, lifetime)| Parâmetro | Tipo | Descrição |
|---|---|---|
name | string | Unique ID to spawn this type later |
mesh | ModelPart | O ModelPart usado como template da partícula |
lifetime | number? | Default lifetime in ticks (can be overridden on spawn) |
registerSprite() — Registrar Partícula de Sprite
Registers a texture as a 2D sprite for flat particles.
confetti.registerSprite(name, sprite, bounds, lifetime, pivot)| Parâmetro | Tipo | Descrição |
|---|---|---|
name | string | ID único |
sprite | Texture | A textura do sprite (ex: textures["model.mytexture"]) |
bounds | Vector4 | Região da textura: vec(u, v, width, height) em pixels |
lifetime | number? | Lifetime padrão em ticks |
pivot | Vector2? | Ponto de pivô do sprite |
-- Mesh particle: model.Spark é um group no Blockbench confetti.registerMesh("spark", models.model.Spark, 40) -- Sprite: região da textura (u=0, v=5, w=4, h=4), lifetime=30 confetti.registerSprite("leaf", textures["model.mytexture"], vec(0,5,4,4), 30) -- Sprite de fogo animado (sem lifetime = não expira sozinha) confetti.registerSprite("fire", textures["model.mytexture"], vec(0,10,4,5))
newParticle() — Spawnar Partícula
confetti.newParticle(name, pos, vel, options)| Campo de options | Tipo | Descrição |
|---|---|---|
lifetime | number? | Lifetime inicial em ticks. Sobrescreve o padrão do registro. |
acceleration | Vector3 ou number? | Aceleração em espaço mundo (vec) ou na direção atual de movimento (number). Negativo = freia. |
friction | number? | Multiplicador de velocidade por tick. 1 = sem fricção; <1 = desacelera; >1 = acelera. |
scale | Vector3 ou number? | Escala inicial. |
scaleOverTime | Vector3 ou number? | Mudança de escala por tick. |
rotation | Vector3 ou number? | Rotação inicial. |
rotationOverTime | Vector3 ou number? | Mudança de rotação por tick. |
billboard | boolean? | Sempre encarar a câmera. |
emissive | boolean? | Brilhar no escuro (apenas mesh). |
ticker | function? | Função chamada a cada tick. Recebe o objeto particle. Chame confetti.defaultTicker(p) para comportamento padrão. |
renderer | function? | Função chamada a cada frame. Recebe (particle, delta, context, matrix). Chame confetti.defaultRenderer(p, delta, ctx, mx) para comportamento padrão. |
-- Faísca simples com gravidade function events.TICK() confetti.newParticle( "spark", player:getPos() + vec(0, 1, 0), vec((math.random()-0.5)*0.3, 0.2+math.random()*0.2, (math.random()-0.5)*0.3), { acceleration = vec(0, -0.04, 0), emissive = true, lifetime = 50, } ) end -- Folhas caindo com rotação confetti.newParticle( "leaf", player:getPos() + vec(math.random()*2-1, 4, math.random()*2-1), vec((math.random()-0.5)*0.2, -math.random()*0.3, (math.random()-0.5)*0.2), { rotationOverTime = 10 + math.random() * 5 } ) -- Faísca que encolhe no fim da vida confetti.newParticle("spark", player:getPos(), vec(0,0.3,0), { emissive = true, friction = 0.83, lifetime = 60, ticker = function(p) confetti.defaultTicker(p) p.scale = math.clamp(math.map(p.lifetime, p.options.lifetime, 2, 4, 0), 0, 1) end })
Bounce with Manual Collision
confetti.newParticle("spark", player:getPos(), vec(0.2,0.3,0.2), { acceleration = vec(0,-0.04,0), emissive=true, lifetime=80, ticker = function(p) confetti.defaultTicker(p) local x,y,z = p.velocity:unpack() -- Verifica colisão e reflete a velocidade if world.getBlockState(p._position+vec(x,0,0)):isSolidBlock() then p.velocity.x = -x end if world.getBlockState(p._position+vec(0,y,0)):isSolidBlock() then p.velocity.y = -y * 0.7 -- amortece vertical end if world.getBlockState(p._position+vec(0,0,z)):isSolidBlock() then p.velocity.z = -z end p.position = p._position + p.velocity end })
Partícula Permanente (Lifetime Infinito)
function events.ENTITY_INIT() confetti.newParticle("spark", player:getPos(), vec(0.1,0.1,0), { emissive = true, friction = 0.99, ticker = function(p) confetti.defaultTicker(p) p.lifetime = 2 -- mantém viva para sempre -- Acelera em direção ao player p.options.acceleration = (player:getPos():add(0,1,0) - p.position):normalized() * 0.05 p.velocity = p.velocity:normalized() * math.min(p.velocity:length(), 0.8) end }) end
Funções Utilitárias
| Função | Descrição |
|---|---|
confetti.defaultTicker(particle) | Default tick behavior: applies velocity, acceleration, friction, scale/rotation over time e conta o lifetime. |
confetti.defaultRenderer(particle, delta, context, matrix) | Comportamento padrão de render: posiciona, rotaciona e escala a mesh/sprite com interpolação por delta. |
confetti.newParticleUnchecked(name, pos, vel, options) | Error-check-free version — slightly faster. Use when the type has already been verified. |
Chat Bubbles por ???
Displays chat bubbles above the character when you send a message. Supports up to 3 simultaneous bubbles, fade out, limite de comprimento e um indicador local para mensagens que não vão ao chat global.
chatBubble.bbmodel) e texturas (corner.png, edge.png, middle.png, tip.png). Mantenha toda a pasta modules/chat_bubble/ intacta.Setup
avatar/
modules/
chat_bubble/
chat_bubble.lua
chatBubble.bbmodel
textures/
corner.png
edge.png
middle.png
tip.pnglocal chat_bubble = require("modules/chat_bubble/chat_bubble") -- Ajuste o caminho se você colocou o módulo em outro lugar
Configuração
All options are direct fields on the object chat_bubble. Configure logo após o require.
| Campo | Padrão | Descrição |
|---|---|---|
chat_bubble.bubbleLifetime | 100 | Duração total da bolha em ticks (incluindo o fade out) |
chat_bubble.fadeTime | 20 | Duração do fade out em ticks |
chat_bubble.maxMessageLength | 64 | Maximum message length before being cut |
chat_bubble.localIndicator | "\" | Prefix that prevents the message from going to global chat. Messages starting with this chartere só aparecem na bolha. |
local chat_bubble = require("modules/chat_bubble/chat_bubble") -- Customize behavior chat_bubble.bubbleLifetime = 150 -- 7.5 segundos chat_bubble.fadeTime = 30 -- 1.5s de fade chat_bubble.maxMessageLength = 80 -- mensagens mais longas chat_bubble.localIndicator = "." -- usar "." como indicador local em vez de ""
Comportamento
- Até 3 bolhas simultâneas — as mais antigas saem quando a 4ª aparece
- As bolhas se redimensionam automaticamente ao texto usando
client.getTextDimensions() - Messages starting with
localIndicatoraparecem na bolha mas não são enviadas ao chat global - The system automatically detects new messages via Figura chat events
- Bubbles stay on camera with
Billboard— sempre encaram o player
localIndicator (padrão: \) para enviar mensagens que só você e quem estiver olhando para o seu avatar vê. Útil para emotes em texto e roleplay.Speech Bubble Ícone de Chat
Displays an animated speech bubble icon above the character while chat is open. Three dots pulse when typing normal text, and a slash icon / appears when typing a command.
Setup
avatar/
modules/
speech_bubble/
speechBubble.lua
speechBubble.bbmodel
speech_bubble.png
dot.png
slash.png-- Não precisa de require — o script já se auto-configura -- Basta incluir speechBubble.lua na pasta do avatar -- e ter o bbmodel e as texturas na mesma pasta.
Como Funciona
- O script monitora
host:isChatOpen()no eventoRENDER - When chat opens, it syncs via
pings.set_speechBubble(true) - Monitora
host:getChatText()para detectar se começa com/ - The three dots animate with offset sine functions to create a "pulsing" effect
- While typing
/, os pontinhos ficam menores e um sprite de barra aparece - The bubble moves smoothly with a vertical sine wave
Customização
The code is simple enough to edit directly. The main lines to adjust:
-- Icon position: setPos(x, y+wave, z) -- y=4 = height above head; adjust for your avatar :setPos(14, 4 + math.sin(...)/3, 0) -- Velocidade da onda: dividir world.getTime() por número maior = mais lento math.sin((world:getTime()+delta)/7) -- 7 = velocidade base da onda -- Velocidade dos pontinhos: valor menor = mais rápido math.sin((world:getTime()+delta)/4) -- 4 = velocidade dos dots
ActionLists v1.5.0
Adds a new action type to the Action Wheel: a scrollable list of items, navigated with scroll and confirmed with clicque. Criado por @sh1zok_was_here.
require("ActionLists") -- no need to store the return value
Creating an ActionList
local page = action_wheel:newPage() action_wheel:setPage(page) -- Usa :newActionList() no lugar de :newAction() local lista = page:newActionList() :title("Escolha um Emote") :item("minecraft:book") :actionList({ {title = "Aceno", value = "wave"}, {title = "Dança", value = "dance"}, {title = "Sentado", value = "sit"}, {title = "Bravo", value = "angry"}, {title = "Triste", value = "sad"}, {title = "Corado", value = "blush"}, }) :onLeftClick(function(self, action, index) local value = action.value pings.playEmote(value) end)
Aparência e Customização
lista
:title("Minha Lista") -- título acima da lista
:item("minecraft:compass") -- item do botão
:color(0.5, 0.5, 0.5) -- cor do botão
:hoverColor(1, 1, 1) -- cor ao hover
:actionListColor(0.6, 0.6, 0.6) -- cor dos itens não selecionados
:selectedActionColor(1, 1, 0) -- cor do item selecionado
:visualSize(5) -- quantos itens aparecem de uma vez (7)
:selectedActionIndex(1) -- item selecionado inicialEventos
lista:onLeftClick(function(self, action, index) -- self = o objeto lista -- action = a tabela da opção selecionada {title=..., value=...} -- index = índice numérico da opção pings.selectOption(action.value) end) lista:onRightClick(function(self, action, index) -- clique direito na seleção atual end) lista:onScroll(function(self, dir) -- scroll manual (a lib já cuida do scroll automático) end)
Items with Custom Appearance
-- Cada opção pode ter item, cor e título de hover próprios lista:actionList({ { title = "Aceno", activeTitle = "▶ Aceno", -- título quando selecionado item = "minecraft:feather", color = vec(0.8, 0.8, 1), value = "wave", }, { title = "Dança", item = "minecraft:music_disc_cat", color = vec(1, 0.8, 0.8), value = "dance", }, })
Figura ExtraBone Mod — Client-Side
Bridges Figura with Emotecraft, adding a special Groups and bones system that allows blending automático de animations do Emotecraft no seu avatar Figura. Quando um emote é ativado, os bones do modelo recebem os dados de blend do Emotecraft e sua Lua script pode reagir a isso.
✔ What you can do
✘ What you CANNOT do
ExtraBone.getBlend() retorna apenas o valor de blend (Y) como vec(0, blend, 0)Requisitos
| Componente | Versão |
|---|---|
| Minecraft | 1.20.1, 1.21.1, 1.21.8 (Fabric / Forge / NeoForge) |
| Figura | 0.1.5 ou superior |
| Emotecraft | 2.4.12 ou superior |
| Instalação no servidor | Não necessária — client-side only |
Como Funciona
The mod adds special groups to the Figura model that map to the Emotecraft bone system. When an Emotecraft emote Emotecraft é reproduzido, ele preenche esses bones com dados de animação. A API Lua ExtraBone expõe esses dados para que seu script possa ler e reagir.
The bone structure added to the model (when "Support Blend" is active in the Wizard) is:
root/ Neck/ -- controls head/neck rotation (body) Head/ -- cabeça vanilla Body/ chest/ -- torso superior (body/torso dependendo da versão MC) LeftShoulder/ -- ombro esquerdo (body) LeftArm/ LArmLower/ -- antebraço esquerdo (leftArm) RightShoulder/ -- ombro direito (body) RightArm/ RArmLower/ -- antebraço direito (rightArm) LeftLeg/ LLegLower/ -- canela esquerda (leftLeg) RightLeg/ RLegLower/ -- canela direita (rightLeg)
API Lua — ExtraBone
O mod registra o objeto global ExtraBone nos scripts Lua. Ele tem um único método:
| Função | Parâmetros | Retorno | Descrição |
|---|---|---|---|
ExtraBone.getBlend(uuid, bone) | uuid: string, bone: string | Vector3 | Retorna vec(0, blendValue, 0) com o valor de blend atual do Emotecraft para aquela parte do corpo do player especificado pelo UUID. |
Nomes de bone aceitos
| String | Parte do corpo | Alias aceito |
|---|---|---|
"head" | Cabeça | — |
"body" | Corpo / tronco | — |
"torso" | Torso (MC 1.21.1+) | — |
"left_arm" | Braço esquerdo | "leftArm" |
"right_arm" | Braço direito | "rightArm" |
"left_leg" | Perna esquerda | "leftLeg" |
"right_leg" | Perna direita | "rightLeg" |
Script Gerado Automaticamente (extrabone_lib)
When creating an avatar through the Wizard with the option "Support Blend" enabled, Figura ExtraBone already injects a script de inicialização automaticamente. Ele usa uma lib interna chamada extrabone_lib que associa cada group do modelo ao bone correto:
local BoneInit = require("extrabone_lib") -- Detects MC version to use "torso" or "body" local function isVersionAtLeast(current, minimum) -- compara versões string por string end local version = client:getVersion() local chestType = isVersionAtLeast(version, "1.21.1") and "torso" or "body" -- Mapeia cada group do modelo para o bone correspondente do Emotecraft BoneInit({ { models.model_blend.root.Neck, "body" }, { models.model_blend.root.Body.chest, chestType }, { models.model_blend.root.LeftShoulder, "body" }, { models.model_blend.root.RightShoulder, "body" }, { models.model_blend.root.LeftShoulder.LeftArm.LArmLower, "leftArm" }, { models.model_blend.root.RightShoulder.RightArm.RArmLower, "rightArm"}, { models.model_blend.root.LeftLeg.LLegLower, "leftLeg" }, { models.model_blend.root.RightLeg.RLegLower, "rightLeg"}, })
Example 1 — Detectar Emote e Ocultar Partes do Modelo
When an Emotecraft emote is active, the body blend becomes greater than zero. Use this to hide parts of your modelo customizado e deixar o Emotecraft assumir o controle visual.
local BoneInit = require("extrabone_lib") local chestType = client:getVersion():find("1.21") and "torso" or "body" BoneInit({ { models.model_blend.root.Neck, "body" }, { models.model_blend.root.Body.chest, chestType }, { models.model_blend.root.LeftShoulder, "body" }, { models.model_blend.root.RightShoulder, "body" }, { models.model_blend.root.LeftShoulder.LeftArm.LArmLower, "leftArm" }, { models.model_blend.root.RightShoulder.RightArm.RArmLower, "rightArm" }, { models.model_blend.root.LeftLeg.LLegLower, "leftLeg" }, { models.model_blend.root.RightLeg.RLegLower, "rightLeg" }, }) local myUuid = player:getUUID() function events.TICK() -- Lê o blend do corpo — > 0 significa que um emote está ativo local blendBody = ExtraBone.getBlend(myUuid, "body").y if blendBody > 0.05 then -- Emote ativo: oculta o modelo customizado para evitar conflito models.model.root:setVisible(false) models.model_blend.root:setVisible(true) else -- Sem emote: mostra o modelo customizado normalmente models.model.root:setVisible(true) models.model_blend.root:setVisible(false) end end
Example 2 — Smooth Blend with GSAnimBlend
Uses the Emotecraft blend value to make smooth transitions between the custom model and the blend model, using math.lerp.
local BoneInit = require("extrabone_lib") local chestType = client:getVersion():find("1.21") and "torso" or "body" BoneInit({ { models.model_blend.root.Neck, "body" }, { models.model_blend.root.Body.chest, chestType }, { models.model_blend.root.LeftShoulder, "body" }, { models.model_blend.root.RightShoulder, "body" }, { models.model_blend.root.LeftShoulder.LeftArm.LArmLower, "leftArm" }, { models.model_blend.root.RightShoulder.RightArm.RArmLower, "rightArm" }, { models.model_blend.root.LeftLeg.LLegLower, "leftLeg" }, { models.model_blend.root.RightLeg.RLegLower, "rightLeg" }, }) local uuid = player:getUUID() local currentBlend = 0 local SPEED = 0.15 -- velocidade de transição function events.RENDER(delta) local targetBlend = ExtraBone.getBlend(uuid, "body").y -- Lerp suave entre o blend atual e o alvo currentBlend = math.lerp(currentBlend, targetBlend, SPEED) -- Usa o blend como opacidade: modelo custom some, blend_model aparece local opacity = math.clamp(currentBlend, 0, 1) models.model.root:setOpacity((1 - opacity) * 255) models.model_blend.root:setOpacity(opacity * 255) end
Example 3 — Verificar Emote de Outro Player
You can read the blend from qualquer player no mundo, não apenas o seu próprio, usando o UUID deles.
function events.TICK() local players = world:getPlayers() for name, ply in pairs(players) do local uuid = ply:getUUID() local blend = ExtraBone.getBlend(uuid, "body").y if blend > 0.1 then -- Esse player está fazendo um emote do Emotecraft log(name .. " está em emote (blend body: " .. blend .. ")") end end end
Ideas for What to Create
💡 Ideas with ExtraBone
⚠ Limitações a considerar
FiguraSVC Mod — Simple Voice Chat
Unofficial addon that exposes events from Simple Voice Chat para scripts Lua do Figura. Permite detectar quando o microfone do player está ativo, ler os dados de áudio em tempo real (PCM) e criar animations de fala, efeitos de volume e modificações de pitch.
✔ What you can do
MICROPHONE✘ What you CANNOT do
voice.amplify() — a função existe no código mas está comentadaRequisitos
| Componente | Versão |
|---|---|
| Minecraft | 1.20.1+ (Fabric / Quilt / Forge) |
| Figura | 0.1.5 ou superior |
| Simple Voice Chat | Qualquer versão compatível |
| Instalação no servidor | Não necessária — client-side only |
Eventos Disponíveis
O FiguraSVC adiciona 3 novos eventos ao objeto events do Figura:
| Evento | When triggered | Parâmetros | Acesso |
|---|---|---|---|
events.HOST_MICROPHONE | Cada vez que o microfone do host capta som | audio: SoundEventData | HOST ONLY |
events.MICROPHONE | Quando outro player fala (recebido pelo cliente) | playername: string, audio: SoundEventData | TODOS |
events.SVC.MICROPHONE legacy | Evento antigo (v1.x) — apenas para quando o host fala | pcm: table | HOST ONLY |
svc tem funções para analisá-los (nível de volume, pitch, smoothing).API Lua — svc / voice
O mod registra o objeto svc (e voice como alias) nos scripts Lua:
| Função | Parâmetros | Retorno | Descrição |
|---|---|---|---|
svc.getLevel(pcm) | pcm: table|SoundEventData | number (0–1) | Calcula o nível de volume do audio como porcentagem de 0 a 1. Aceita raw PCM table ou o SoundEventData diretamente. |
svc.smoothing(level, sensitivity) | level: number, sensitivity: number | number | Applies EMA (Exponential Moving Average) to smooth abrupt volume variations. Sensitivity 0–1: mais próximo de 1 = mais responsivo, mais próximo de 0 = mais suave. |
svc.changePitch(pcm, factor) | pcm: table, factor: number | table (PCM) | Changes audio pitch. Factor > 1 = higher, < 1 = lower. Returns new PCM modificado. |
Checagem de Segurança
Always check if the mod is loaded before use. Without this check, the script will break for clients without FiguraSVC instalado:
-- Standard check: only registers events if the mod is installed if client:isModLoaded("figurasvc") and host:isHost() then function events.HOST_MICROPHONE(audio) -- Só roda no host e somente se o mod estiver presente end end -- Para ouvir outros players (todos os clientes) if client:isModLoaded("figurasvc") then function events.MICROPHONE(playername, audio) -- Roda em todos os clientes que têm o mod end end
Example 1 — Simple Talk Animation (Basic Method)
The official example from the mod page. Uses a ping to ensure the animation is visible to all players, with fallback quando o mod não está instalado.
local microphoneOffTime = 0 local isMicrophoneOn = false -- Ping: plays the talking animation (visible to all) function pings.talking(state) animations.model.talk:setPlaying(state) end function events.TICK() local previous = isMicrophoneOn microphoneOffTime = microphoneOffTime + 1 isMicrophoneOn = microphoneOffTime <= 2 -- ativo se falou nos últimos 2 ticks if previous ~= isMicrophoneOn then pings.talking(isMicrophoneOn) end end -- Só registra se o mod estiver presente if client:isModLoaded("figurasvc") and host:isHost() then function events.HOST_MICROPHONE(audio) microphoneOffTime = 0 -- reseta o contador: ainda está falando end end
Example 2 — Mouth Animated by Volume
Uses real-time PCM data to calculate volume and open/close the character's mouth proportionally.
local micOff = 0 local volume = 0 -- current smoothed volume -- Posição original da queixada (para resetar ao parar de falar) local jawOriginX = models.model.Head.LowerJaw:getRot().x function pings.talking(state) if not state then -- Reseta a queixada ao parar de falar models.model.Head.LowerJaw:setRot(jawOriginX, 0, 0) end end function events.TICK() micOff = micOff + 1 local isTalking = micOff <= 2 if not isTalking then volume = 0 pings.talking(false) end end if client:isModLoaded("figurasvc") and host:isHost() then function events.HOST_MICROPHONE(audio) micOff = 0 -- Obtém o nível de volume (0 a 1) local rawLevel = svc.getLevel(audio) -- Suaviza o volume com EMA (sensitivity 0.3 = bastante suave) volume = svc.smoothing(rawLevel, 0.3) -- Mapeia o volume para rotação da queixada -- volume 0 = fechada, volume 1 = aberta 35 graus local jawRot = jawOriginX + math.clamp(volume, 0, 1) * 35 models.model.Head.LowerJaw:setRot(jawRot, 0, 0) end end
Example 3 — Ouvir Outros Players Falando
O evento MICROPHONE é disparado para todos os clientes quando outro player fala. Use para criar reações visuais ao ouvirmos outros players.
local playersVolumeMap = {} -- {uuid: volume} if client:isModLoaded("figurasvc") then function events.MICROPHONE(playername, audio) local level = svc.getLevel(audio) playersVolumeMap[playername] = svc.smoothing(level, 0.4) end end function events.TICK() for name, vol in pairs(playersVolumeMap) do if vol > 0.05 then -- Do something when "name" is speaking -- Ex: show an icon, react with an animation, etc. end end end
Example 4 — Pitch Shifter (Modified Voice)
Changes the pitch of the audio emitted by the avatar in real time. Factor > 1 = higher pitch, < 1 = lower pitch.
local pitchFactor = 1.5 -- 1.5 = higher pitch; 0.7 = lower pitch if client:isModLoaded("figurasvc") and host:isHost() then function events.HOST_MICROPHONE(audio) -- Modify pitch and return new PCM -- The event return value replaces the sent audio return svc.changePitch(audio, pitchFactor) end end -- Pitch toggle via Action Wheel local pitchOn = false local page = action_wheel:newPage() action_wheel:setPage(page) page:newAction() :title("High Voice") :item("minecraft:bell") :onToggle(function(b) pitchOn = b pitchFactor = b and 1.6 or 1.0 end)
Example 5 — DAF (Complete Framework)
The DAF (Derg Auditory Framework) is a modular approach included in the mod's examples. It abstracts the microphone detection logic and makes development with FiguraSVC easier:
-- Include DAF.lua in your avatar folder local DAF = require("DAF") -- init() sets up all event handlers and returns true if everything is OK if DAF.init() then local voice = DAF.events -- Modify audio before sending (optional) voice.setAudioModifer(function(audio) return svc.changePitch(audio, 1.2) end) -- Animation based on real-time audio data local jawOrigin = models.model.Head.Jaw:getRot().x voice.setAnimation(function(audio) local level = svc.getLevel(audio) local smooth = svc.smoothing(level, 0.35) local rot = jawOrigin + smooth * 30 models.model.Head.Jaw:setRot(rot, 0, 0) end) -- Reset when done speaking voice.setOnMicrophoneOff(function() models.model.Head.Jaw:setRot(jawOrigin, 0, 0) end) -- Fallback for clients without FiguraSVC (simple ping) voice.setFallbackPing(pings.talking) end -- Fallback ping (visible to everyone without the mod) function pings.talking(state) animations.model.talk:setPlaying(state) end
Ideas for What to Create
💡 Ideas with FiguraSVC
⚠ Cautions
client:isModLoaded("figurasvc") before registering eventssetText() or heavy operations inside HOST_MICROPHONE — it fires every audio frame