Skip to content

Lua API

Fightura ลงทะเบียน global Lua ชื่อ fightura ให้ avatar Figura ทุกตัว มีฟีเจอร์: query สถานะ Epic Fight, อ่าน joint pose, และ override ชื่อ bone ตอน runtime

if fightura:isAvailable() then
-- entity นี้มี Epic Fight patch ติดอยู่
end

true ถ้า entity มี LivingEntityPatch ของ Epic Fight ติดอยู่ ตรวจตัวนี้เป็นอันดับแรกทุกครั้ง — ถ้าไม่มี method อื่น ๆ จะคืน nil หรือ false

true ตอน player อยู่ใน battle stance ของ Epic Fight (ชักดาบ ฯลฯ)

true ตลอด animation การโจมตีของ Epic Fight

true ถ้ามี pose snapshot ปัจจุบันของ frame นี้ ใช้ใน events.RENDER ก่อนเรียก joint query

function events.WORLD_TICK()
if fightura:isAttacking() then
-- เล่น animation reaction ที่ทำเอง
end
end

อ่านจาก pose snapshot ล่าสุดของ Epic Fight (update ทุก frame ที่ entity ถูก render)

getJointRotation(name: string) -> Vector3 | nil

Section titled “getJointRotation(name: string) -> Vector3 | nil”

คืน rotation ของ joint เป็น ZYX Euler ในหน่วย radians คืน nil ถ้าไม่พบ joint ใน armature ปัจจุบัน

function events.RENDER(delta)
if not fightura:hasPose() then return end
local rot = fightura:getJointRotation("Hand_L")
if rot then
models.MyArm.LowerArm:setRot(
math.deg(rot.x), math.deg(rot.y), math.deg(rot.z)
)
end
end

getJointMatrix(name: string) -> Matrix4 | nil

Section titled “getJointMatrix(name: string) -> Matrix4 | nil”

คืน 4×4 matrix ของ joint เต็มรูปแบบ (เป็น FiguraMat4) เหมาะกับ rig ที่ใช้ matrix

local headMat = fightura:getJointMatrix("Head")
if headMat then
models.MyHat:setMatrix(headMat)
end

คืนรายการชื่อ joint ทั้งหมดใน armature ของ entity ตอนนี้ ใช้สำรวจว่ามีอะไรบ้าง — mob/player คนละตัวอาจมี rig ต่างกัน

for _, joint in pairs(fightura:getJoints()) do
print(joint)
end

ถ้า avatar ใช้ชื่อ part ที่ Fightura ไม่รู้จัก ผูกครั้งเดียวตอน load ดู รูปแบบการตั้งชื่อ สำหรับรายการ alias เต็ม

ผูกชื่อ part (หรือชื่อ parentType) กับ Epic Fight joint Alias ฝั่ง case-insensitive ส่วนชื่อ joint ต้องตรงเป๊ะ (Head, Chest, Arm_L, Hand_L, Thigh_L, Leg_L, …)

fightura:mapBone("MyTailRoot", "Torso")
fightura:mapBone("LSleeveCuff", "Hand_L")

Override มีผลเฉพาะ avatar ตัวนี้ ไม่ leak ไปยังผู้เล่นอื่น

ลบ override อันเดียว

fightura:clearBone("MyTailRoot")

ลบ override ทั้งหมดของ avatar นี้

คืนรายการ alias built-in ทั้งหมดที่ Fightura รู้จัก (ในรูปแบบ lowercase) ใช้ตรวจว่าชื่อไหนไม่ต้อง mapBone

ทำให้แขนขางอได้

Section titled “ทำให้แขนขางอได้”

Figura ยิงทุก vertex ของ part ผ่าน matrix ตัวเดียวกัน part ที่ผูกกับ Arm_R จึงหมุนได้แค่รอบไหล่ — ปั้นแขนเป็นชิ้นเดียวแล้วมันจะแข็งทื่อตรงข้อศอก Fightura แก้ด้วยการเบลนด์ทีละ vertex ระหว่าง joint กับ joint ลูกที่งอได้: Arm_L/Arm_R ไปหา Hand_L/Hand_R และ Thigh_L/Thigh_R ไปหา Leg_L/Leg_R

ไม่ต้องเขียนสคริปต์อะไรเลย แขนขาที่ geometry เลยข้อต่อไปจะงอเองอัตโนมัติ ทุกอย่างข้างล่างมีไว้เผื่อกรณีที่อยากได้อย่างอื่นนอกเหนือจากค่าเริ่มต้น

มีเงื่อนไขข้อเดียวคือ geometry ต้องเลยจุดข้อต่อไปทั้งสองฝั่ง — part ที่จบตรงข้อศอกพอดีถือเป็นท่อนบนธรรมดา จะปล่อยให้แข็งไว้ตามเดิม ข้อนี้เองที่ทำให้ avatar ซึ่งแบ่งเป็นท่อนอยู่แล้วเรนเดอร์เหมือนเดิมเป๊ะ ส่วนจุดงออ่านจาก armature ของ Epic Fight โดยตรง — ข้อศอกอยู่ที่ 17.5 px หัวเข่าอยู่ที่ 6.2 — ไม่ต้องวัดหรือปรับเอง

cube ก็งอได้ Fightura จะซอยหน้าที่พาดผ่านข้อต่อให้ถี่ขึ้นตอนส่งไปวาด แขนเสื้อที่ปั้นเป็นกล่องเดียวเลยโค้งจริง ไม่ใช่กล่องบิด รอยซอยใช้มุมร่วมกันจึงไม่มีตะเข็บโผล่ และซอยเฉพาะในช่วงเบลนด์เท่านั้น — mesh ที่ละเอียดพอจะงอเองอยู่แล้วจะไม่ถูกซอยเพิ่มเลย ทั้งหมดนี้ไม่กิน complexity limit ของ avatar เพราะ Figura หักไปก่อนที่ Fightura จะเห็น part แล้ว

รอยพับวิ่งผ่าน joint ข้อพับของ EF เองElbow_L/Elbow_R สำหรับแขน Knee_L/Knee_R สำหรับขา joint พวกนี้พักอยู่ตำแหน่งเดียวกับ joint ที่อยู่ถัดลงไปเป๊ะ แต่ถูกอนิเมทแยกกัน และ EF ผูกวงแหวน vertex ตรงรอยพับเข้ากับมันโดยตรง การวิ่งผ่านมันทำให้รอยพับไปอยู่ตรงที่คนทำอนิเมชันกำหนดไว้ ไม่ใช่ตรงที่การเบลนด์สองปลายบังเอิญคำนวณได้ — อันนี้แหละที่ทำให้ข้อศอกไม่โผล่ทะลุแขนเสื้อ

ลำตัวก็เบลนด์ด้วย ระหว่าง Torso กับ Chest แต่เป็นการเอนไม่ใช่พับ — Epic Fight ถ่วงน้ำหนักลำตัว vanilla คร่อมสอง joint ตลอดทั้งตัว ตั้งแต่ 0.23 ที่เอวถึง 0.77 ที่ไหล่ ไม่เคยยกให้ฝั่งไหนเต็ม 100% ลำตัวจึงขยับพร้อมกันทั้งชิ้น Fightura ใช้ตัวเลขชุดเดียวกันเป๊ะ (อ่านจาก chest transformer ของเขา) ชุดที่มาแทนลำตัวเลยขยับเหมือนลำตัวที่มันไปแทน ไม่หักกลางเอว

ปิดการงอของ part ที่ปกติจะงอ หรือเปิดกลับ

fightura:setSkin("RightArm", false)

ให้เบลนด์ไปหา joint อื่นแทน joint ลูกปกติ สำหรับ rig ที่อยากให้ part ตามอย่างอื่น จุดงอจะไปตกที่ตำแหน่งพักของ joint นั้นเหมือนเดิม

fightura:mapBone("Tentacle", "Arm_R")
fightura:bindSkin("Tentacle", "Hand_R")

ย้ายจุดงอไปที่อื่นแทนตำแหน่ง joint ระบุเป็นระยะจาก pivot ของ part ลงไปตามกระดูก หน่วย pixel ของโมเดล ข้อศอกของผู้เล่นอยู่ที่ 4.8

ความกว้างของช่วงเบลนด์ทั้งสองฝั่งของจุดงอ หน่วย pixel ค่าเริ่มต้น 2 ใส่ 3 ขึ้นไปจะนุ่มขึ้น

fightura:setSkinSplit("Tentacle", 8)
fightura:setSkinBand("Tentacle", 4)

ทุก method รับอาร์กิวเมนต์ครบทุกตัวโดยตั้งใจ เพราะ Figura ใช้บัฟเฟอร์อาร์กิวเมนต์ตัวเดียวซ้ำต่อ method และไม่เคลียร์ช่องที่ไม่ได้ส่งมา method ที่มีอาร์กิวเมนต์ท้ายแบบไม่บังคับจะได้ค่าตกค้างจากคนเรียกก่อนหน้าแบบเงียบ ๆ

ด้านนอกของรอยพับจะโค้งครบมุมข้อต่อ ส่วนด้านในจะบีบเข้าหากันแบบที่ข้อต่อซึ่งใช้การเบลนด์ทุกตัวเป็นกัน — รวมถึงแขนขาของ Epic Fight เองด้วย และจะเห็นชัดที่สุดตอนมุมแคบ ๆ ราว 90° ขึ้นไป ถ้าอยากให้นุ่มลงให้เพิ่ม setSkinBand เพื่อกระจายรอยพับไปตามความยาวแขนมากขึ้น

ดูผลยังไงให้เห็น

Section titled “ดูผลยังไงให้เห็น”

ท่ายืนเฉย ๆ แทบไม่งอข้อศอกเลย เลยเข้าใจผิดได้ง่ายว่าตั้งค่าไม่ติดทั้งที่ถูกหมดแล้ว ตัวเลขข้างล่างวัดจากอนิเมชันของ Epic Fight เอง เป็นมุมระหว่างท่าของ Arm_R กับ Hand_R:

ท่ามุมข้อศอก
ยืนเฉย ๆ
ถือดาบยาวยืน15°
เดิน18°
ฟันดาบปกติ37°
ย่อ59°
วิ่ง82°
ถือดาบใหญ่88°
ว่ายน้ำ104°

ทดสอบตอนว่ายน้ำหรือวิ่ง ที่ 7° ทั้งแขนขยับไม่ถึงหนึ่งพิกเซล

ลบ skin override อันเดียว

ลบ skin override ทั้งหมดของ avatar นี้

Fightura ยิง Lua event เมื่อสถานะ Epic Fight ของ entity เปลี่ยน — ไม่ต้อง poll เอง แต่ละ event เป็น LuaEvent field บน fightura API ลงทะเบียน handler ด้วย :register(func)

fightura.attackStart:register(function(motion)
-- motion คือชื่อ living motion ตอนนี้ (เช่น "ATTACK")
animations.MyAttackReact:play()
end)
fightura.attackEnd:register(function()
animations.MyAttackReact:stop()
end)
fightura.modeChange:register(function(newMode, oldMode)
if newMode == "BATTLE" then
animations.DrawSword:play()
elseif newMode == "MINING" then
animations.SheathSword:play()
end
end)
fightura.hurtStart:register(function()
animations.HurtFlinch:play()
end)
fightura.knockDown:register(function()
animations.KnockedDown:play()
end)
fightura.motionChange:register(function(newMotion, oldMotion)
-- เช่น เปลี่ยนจาก "IDLE" ไป "WALK"
end)
EventArgsยิงเมื่อ
attackStartmotion: stringEpic Fight animation โจมตีเริ่ม
attackEnd(none)Animation โจมตีจบ
hurtStart(none)Entity เข้าสู่สถานะ hurt
knockDown(none)Entity ถูกล้ม
modeChangenewMode, oldMode: stringplayerMode เปลี่ยน (BATTLE / MINING / DEFAULT)
motionChangenewMotion, oldMotion: stringLiving motion ปัจจุบันเปลี่ยน

Event ยิงตอน client tick ส่วน method แบบ poll (isAttacking(), isEpicFightMode()) ยังใช้ได้ตามปกติเหมาะกับ logic ที่รันทุก frame

สคริปต์เริ่มต้นที่เหมาะสม:

-- ผูกชื่อ part ที่ไม่ standard ครั้งเดียว
fightura:mapBone("HornLeft", "Head")
fightura:mapBone("HornRight", "Head")
fightura:mapBone("TailRoot", "Torso")
function events.WORLD_TICK()
if fightura:isAvailable() and fightura:isAttacking() then
-- trigger animation reaction
end
end
function events.RENDER(delta)
if not fightura:hasPose() then return end
-- transform per-frame ขับโดย Epic Fight pose
local hand = fightura:getJointRotation("Hand_L")
if hand then
models.weapon_glow:setRot(math.deg(hand.x), math.deg(hand.y), math.deg(hand.z))
end
end

สิ่งที่ตั้งใจไม่ expose

Section titled “สิ่งที่ตั้งใจไม่ expose”

เพื่อให้ surface เล็ก เราไม่ใส่:

  • การแก้ armature โดยตรง (อ่านอย่างเดียว)
  • trigger Epic Fight skill
  • sniff network packet
  • query สถานะ vanilla model (ใช้ Figura vanilla_model API แทน)

ถ้าต้องการอะไรนอกเหนือจากนี้ เปิด issue ที่ Discord

  • แก้ปัญหา — ทำยังไงเมื่อ method คืน nil ทั้งที่ควรมีข้อมูล