Lua API
Fightura ลงทะเบียน global Lua ชื่อ fightura ให้ avatar Figura ทุกตัว มีฟีเจอร์: query สถานะ Epic Fight, อ่าน joint pose, และ override ชื่อ bone ตอน runtime
if fightura:isAvailable() then -- entity นี้มี Epic Fight patch ติดอยู่endQuery สถานะ
Section titled “Query สถานะ”isAvailable() -> boolean
Section titled “isAvailable() -> boolean”true ถ้า entity มี LivingEntityPatch ของ Epic Fight ติดอยู่ ตรวจตัวนี้เป็นอันดับแรกทุกครั้ง — ถ้าไม่มี method อื่น ๆ จะคืน nil หรือ false
isEpicFightMode() -> boolean
Section titled “isEpicFightMode() -> boolean”true ตอน player อยู่ใน battle stance ของ Epic Fight (ชักดาบ ฯลฯ)
isAttacking() -> boolean
Section titled “isAttacking() -> boolean”true ตลอด animation การโจมตีของ Epic Fight
hasPose() -> boolean
Section titled “hasPose() -> boolean”true ถ้ามี pose snapshot ปัจจุบันของ frame นี้ ใช้ใน events.RENDER ก่อนเรียก joint query
function events.WORLD_TICK() if fightura:isAttacking() then -- เล่น animation reaction ที่ทำเอง endendอ่าน joint pose
Section titled “อ่าน joint pose”อ่านจาก 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) ) endendgetJointMatrix(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)endgetJoints() -> string[]
Section titled “getJoints() -> string[]”คืนรายการชื่อ joint ทั้งหมดใน armature ของ entity ตอนนี้ ใช้สำรวจว่ามีอะไรบ้าง — mob/player คนละตัวอาจมี rig ต่างกัน
for _, joint in pairs(fightura:getJoints()) do print(joint)endBone binding
Section titled “Bone binding”ถ้า avatar ใช้ชื่อ part ที่ Fightura ไม่รู้จัก ผูกครั้งเดียวตอน load ดู รูปแบบการตั้งชื่อ สำหรับรายการ alias เต็ม
mapBone(alias: string, joint: string)
Section titled “mapBone(alias: string, joint: string)”ผูกชื่อ 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 ไปยังผู้เล่นอื่น
clearBone(alias: string)
Section titled “clearBone(alias: string)”ลบ override อันเดียว
fightura:clearBone("MyTailRoot")clearBones()
Section titled “clearBones()”ลบ override ทั้งหมดของ avatar นี้
getSupportedBones() -> string[]
Section titled “getSupportedBones() -> string[]”คืนรายการ 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 ของเขา) ชุดที่มาแทนลำตัวเลยขยับเหมือนลำตัวที่มันไปแทน ไม่หักกลางเอว
setSkin(part: string, enabled: boolean)
Section titled “setSkin(part: string, enabled: boolean)”ปิดการงอของ part ที่ปกติจะงอ หรือเปิดกลับ
fightura:setSkin("RightArm", false)bindSkin(part: string, joint: string)
Section titled “bindSkin(part: string, joint: string)”ให้เบลนด์ไปหา joint อื่นแทน joint ลูกปกติ สำหรับ rig ที่อยากให้ part ตามอย่างอื่น จุดงอจะไปตกที่ตำแหน่งพักของ joint นั้นเหมือนเดิม
fightura:mapBone("Tentacle", "Arm_R")fightura:bindSkin("Tentacle", "Hand_R")setSkinSplit(part: string, split: number)
Section titled “setSkinSplit(part: string, split: number)”ย้ายจุดงอไปที่อื่นแทนตำแหน่ง joint ระบุเป็นระยะจาก pivot ของ part ลงไปตามกระดูก หน่วย pixel ของโมเดล ข้อศอกของผู้เล่นอยู่ที่ 4.8
setSkinBand(part: string, band: number)
Section titled “setSkinBand(part: string, band: number)”ความกว้างของช่วงเบลนด์ทั้งสองฝั่งของจุดงอ หน่วย pixel ค่าเริ่มต้น 2 ใส่ 3 ขึ้นไปจะนุ่มขึ้น
fightura:setSkinSplit("Tentacle", 8)fightura:setSkinBand("Tentacle", 4)ทุก method รับอาร์กิวเมนต์ครบทุกตัวโดยตั้งใจ เพราะ Figura ใช้บัฟเฟอร์อาร์กิวเมนต์ตัวเดียวซ้ำต่อ method และไม่เคลียร์ช่องที่ไม่ได้ส่งมา method ที่มีอาร์กิวเมนต์ท้ายแบบไม่บังคับจะได้ค่าตกค้างจากคนเรียกก่อนหน้าแบบเงียบ ๆ
หน้าตาที่ได้
Section titled “หน้าตาที่ได้”ด้านนอกของรอยพับจะโค้งครบมุมข้อต่อ ส่วนด้านในจะบีบเข้าหากันแบบที่ข้อต่อซึ่งใช้การเบลนด์ทุกตัวเป็นกัน — รวมถึงแขนขาของ Epic Fight เองด้วย และจะเห็นชัดที่สุดตอนมุมแคบ ๆ ราว 90° ขึ้นไป ถ้าอยากให้นุ่มลงให้เพิ่ม setSkinBand เพื่อกระจายรอยพับไปตามความยาวแขนมากขึ้น
ดูผลยังไงให้เห็น
Section titled “ดูผลยังไงให้เห็น”ท่ายืนเฉย ๆ แทบไม่งอข้อศอกเลย เลยเข้าใจผิดได้ง่ายว่าตั้งค่าไม่ติดทั้งที่ถูกหมดแล้ว ตัวเลขข้างล่างวัดจากอนิเมชันของ Epic Fight เอง เป็นมุมระหว่างท่าของ Arm_R กับ Hand_R:
| ท่า | มุมข้อศอก |
|---|---|
| ยืนเฉย ๆ | 7° |
| ถือดาบยาวยืน | 15° |
| เดิน | 18° |
| ฟันดาบปกติ | 37° |
| ย่อ | 59° |
| วิ่ง | 82° |
| ถือดาบใหญ่ | 88° |
| ว่ายน้ำ | 104° |
ทดสอบตอนว่ายน้ำหรือวิ่ง ที่ 7° ทั้งแขนขยับไม่ถึงหนึ่งพิกเซล
clearSkin(part: string)
Section titled “clearSkin(part: string)”ลบ skin override อันเดียว
clearSkins()
Section titled “clearSkins()”ลบ skin override ทั้งหมดของ avatar นี้
Epic Fight events
Section titled “Epic Fight events”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() endend)
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)| Event | Args | ยิงเมื่อ |
|---|---|---|
attackStart | motion: string | Epic Fight animation โจมตีเริ่ม |
attackEnd | (none) | Animation โจมตีจบ |
hurtStart | (none) | Entity เข้าสู่สถานะ hurt |
knockDown | (none) | Entity ถูกล้ม |
modeChange | newMode, oldMode: string | playerMode เปลี่ยน (BATTLE / MINING / DEFAULT) |
motionChange | newMotion, oldMotion: string | Living motion ปัจจุบันเปลี่ยน |
Event ยิงตอน client tick ส่วน method แบบ poll (isAttacking(), isEpicFightMode()) ยังใช้ได้ตามปกติเหมาะกับ logic ที่รันทุก frame
Pattern พื้นฐาน
Section titled “Pattern พื้นฐาน”สคริปต์เริ่มต้นที่เหมาะสม:
-- ผูกชื่อ 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 endend
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)) endendสิ่งที่ตั้งใจไม่ expose
Section titled “สิ่งที่ตั้งใจไม่ expose”เพื่อให้ surface เล็ก เราไม่ใส่:
- การแก้ armature โดยตรง (อ่านอย่างเดียว)
- trigger Epic Fight skill
- sniff network packet
- query สถานะ vanilla model (ใช้ Figura
vanilla_modelAPI แทน)
ถ้าต้องการอะไรนอกเหนือจากนี้ เปิด issue ที่ Discord
- แก้ปัญหา — ทำยังไงเมื่อ method คืน
nilทั้งที่ควรมีข้อมูล