Perpustakaan Pengendali Karakter (CCL) adalah kerangka modular untuk membangun gerakan dan perilaku karakter melalui atribut dan skrip Luau. Arsitektur ini menggantikan mesin status Humanoid yang kaku dengan sistem yang fleksibel dan dapat diperluas untuk mekanika karakter.
Kemampuan
Kemampuan mengevaluasi apa yang dapat dilakukan karakter, seperti kemampuan untuk berlari, melompat, memanjat, dan berenang. Alih-alih bergantung pada seperangkat status karakter yang ditentukan oleh mesin yang tetap seperti yang ada di Enum.HumanoidStateType, kemampuan CCL secara dinamis menentukan apa yang dapat dilakukan karakter dan bagaimana ia harus merespons input pemain.
Secara struktural, sebuah kemampuan adalah tabel Luau yang mandiri yang terutama menentukan hal-hal berikut:
| Bidang Tabel | Tujuan |
|---|---|
| Name | Nama yang dialokasikan untuk bit label, sehingga kondisi dan konflik dapat merujuk pada kemampuan tersebut. Beberapa definisi kemampuan dapat menggunakan nama yang sama. Nama ModuleScript menentukan kunci konfigurasi yang unik. |
| Labels, TimedLabels | Bit bernama dalam masker 64-bit bersama yang bertindak sebagai bus koordinasi antara kemampuan; lihat label. |
| StartsWhen, RunsWhile | Kondisi yang mendefinisikan kapan untuk memulai kemampuan dan kapan untuk mempertahankannya, masing-masing; lihat kondisi. |
| Blocks, Stops, Suspends, ExclusiveGroup | Cara menangani konflik antara kemampuan yang tidak dapat aktif sekaligus. |
| Input | Input yang memicu kemampuan. CCL menyuntikkan ini ke dalam StartsWhen dan, ketika Anda menghilangkan RunsWhile, menggunakannya sebagai kondisi kelanjutan default; lihat input. |
| Config, State | Nilai konfigurasi default dan status yang direplikasi untuk setiap pendaftaran kemampuan. Callback membaca konfigurasi dari abilityCtx.Config dan membaca atau menulis status yang direplikasi melalui abilityCtx.State. |
| OnSetup, OnStart, OnStop, OnUpdate, OnTeardown | Callback siklus hidup di mana perilaku aktual kemampuan diprogram; lihat callback. |
Label
Sebuah label adalah bit bernama dalam masker 64-bit bersama yang bertindak sebagai bus koordinasi antara kemampuan. Pada dasarnya:
- Sebuah kemampuan yang aktif menyiarkan Labels dan TimedLabels ke masker dunia.
- Konflik kemampuan mendefinisikan kemampuan lain yang diblokir, dihentikan, atau ditangguhkan saat diaktifkan.
Dalam pengaturan berikut, label "CanFallDown" disiarkan ke masker dunia ketika Running aktif. Kemampuan FallingDown dengan kondisi StartsWhen = All( "CanFallDown", "Stunned" ) secara otomatis menjadi kandidat, tetapi "Stunned" juga harus disiarkan ke masker dunia sebelum FallingDown terjadi.
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Running: AvatarAbilities.AbilityDefinition = {
Name = Ability.Running,
Labels = { "CanFallDown" }, -- Label disiarkan saat kemampuan aktif
StartsWhen = Sensor.Ground,
RunsWhile = Sensor.Ground,
}local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local FallingDown: AvatarAbilities.AbilityDefinition = {
Name = Ability.FallingDown,
StartsWhen = All( "CanFallDown", "Stunned" ), -- Label yang diperlukan untuk memulai kemampuan
Blocks = { Ability.Running }
}Label juga dapat disiarkan atau dikonsumsi dengan cara berjangka menggunakan kamus TimedLabels.
| Kunci | Deskripsi |
|---|---|
| TimedLabels.OnStart | Kamus yang berisi label (kunci) dan durasi terkait. Label disiarkan saat kemampuan dimulai dan secara otomatis kedaluwarsa saat durasinya berakhir. Misalnya, OnStart = { Dashing = 1 } menyiarkan label Dashing selama 1 detik saat kemampuan dimulai. |
| TimedLabels.OnStop | Kamus yang berisi label (kunci) dan durasi terkait. Label disiarkan saat kemampuan dihentikan dan secara otomatis kedaluwarsa saat durasinya berakhir. Misalnya, OnStop = { DashCooldown = 2 } menyiarkan label DashCooldown selama 2 detik saat kemampuan dihentikan. |
| TimedLabels.Consumes | Daftar label yang akan dihapus (dikonsumsi) saat kemampuan diaktifkan. Misalnya, jika sebuah permainan pertarungan memungkinkan pemain untuk melakukan serangan balik setelah memblokir serangan lawan, kemampuan CounterAttack mungkin berisi StartsWhen = "AfterBlock" dan TimedLabels = { Consumes = { "AfterBlock" } } untuk mencegah pemicu ganda dari kemampuan CounterAttack. |
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
StartsWhen = All( Sensor.Ground, Not("DashCooldown") ),
RunsWhile = "Dashing",
TimedLabels = {
OnStart = { Dashing = 1 },
OnStop = { DashCooldown = 2 },
},
}Kondisi
Sebuah kondisi adalah satu atau lebih label, sensor, atau referensi input, yang digunakan oleh StartsWhen dan RunsWhile. Kondisi dikompilasi menjadi operasi bitmask saat runtime dan evaluasi adalah matematika integer — tidak ada penelusuran tabel dan tidak ada perbandingan string.
| Tujuan | Sintaksis | Contoh |
|---|---|---|
| Satu kondisi yang diperlukan. | StartsWhen = Sensor.Ground | |
| Logika AND untuk ketika semua label ada di masker dunia dan semua sensor aktif. | All() | StartsWhen = All( "CanFallDown", "Stunned" ) |
| Logika OR untuk ketika salah satu label ada di masker dunia atau salah satu sensor aktif. | Any() | StartsWhen = Any( "WallClimbing", "Climbing" ) |
| Negasi sehingga label tidak dapat ada di masker dunia dan sensor tidak dapat aktif. | Not() | RunsWhile = Not("Stunned") |
Evaluasi kondisional dapat digabungkan untuk logika yang lebih kompleks, seperti penggabungan All() ditambah Not() untuk menunjukkan bahwa sebuah sensor harus aktif sementara sebuah label harus tidak ada:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dive: AvatarAbilities.AbilityDefinition = {
Name = "Dive",
StartsWhen = All( Sensor.WaterSurface, Not("Recovering") ),
}Konflik
Beberapa kemampuan tidak dapat aktif ketika kemampuan lain aktif; misalnya, karakter tidak dapat melompat saat berenang, dan mereka tidak dapat berlari saat jatuh. Mesin menyelesaikan konflik ini secara deklaratif di dalam definisi kemampuan:
| Kunci Konflik | Tujuan |
|---|---|
| Blocks | Saat kemampuan pemilik aktif, kemampuan lain yang terdaftar tidak dapat dimulai. Misalnya, kemampuan ScopeAim mungkin berisi Blocks = { Ability.Running, Ability.Jumping } untuk mencegah karakter berlari atau melompat saat dengan hati-hati mengarahkan melalui bidikan senjata mereka. |
| Stops | Ketika kemampuan pemilik dimulai, kemampuan lain yang terdaftar dipaksa berhenti dan harus diaktifkan kembali. Misalnya, kemampuan Hover mungkin berisi Stops = { Ability.Running } untuk segera menghentikan gerakan berlari karakter saat mereka mulai melayang. |
| Suspends | Ketika kemampuan pemilik dimulai, kemampuan lain yang terdaftar ditangguhkan dan kemudian otomatis melanjutkan saat kemampuan pemilik berhenti. Misalnya, kemampuan sprint kustom mungkin berisi Suspends = { Ability.Running } sehingga berlari 🄐 ditangguhkan saat sprint dimulai, 🄑 diblokir di tengah sprint, dan 🄒 dilanjutkan saat sprint berhenti. |
Kunci konflik unik lainnya adalah ExclusiveGroup yang menempatkan beberapa kemampuan ke dalam satu grup, masing-masing dengan nilai Priority. Hanya satu kemampuan per grup yang dapat aktif dan prioritas yang lebih tinggi menang. Namun, jika seorang penantang menyatakan Stops yang menargetkan nama/label pemegang, itu menang terlepas dari prioritas.
Dalam pengaturan berikut, tiga kemampuan (Sprinting, Crouching, Stagger) ditambahkan ke grup eksklusif Locomotion. Sprinting memiliki prioritas tertinggi (200) sehingga menang atas Crouching (100) dan keduanya tidak pernah berjalan pada saat yang sama. Namun, Stagger secara paksa menghentikan sprinting (Stops = { Ability.Sprinting }), sehingga dapat menginterupsi dan mengalahkan Sprinting meskipun prioritasnya (150) lebih rendah.
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Ability = Identifiers.Ability
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Sprinting: AvatarAbilities.AbilityDefinition = {
Name = Ability.Sprinting,
ExclusiveGroup = { Name = "Locomotion", Priority = 200 },
}
local Crouching: AvatarAbilities.AbilityDefinition = {
Name = Ability.Crouching,
ExclusiveGroup = { Name = "Locomotion", Priority = 100 },
}
-- Kemampuan dengan prioritas lebih rendah dapat mengalahkan kemampuan dengan prioritas lebih tinggi dengan menghentikannya
local Stagger: AvatarAbilities.AbilityDefinition = {
Name = "Stagger",
ExclusiveGroup = { Name = "Locomotion", Priority = 150 },
Stops = { Ability.Sprinting },
}Input
Definisi Input dari sebuah kemampuan menentukan input yang digunakan untuk mencoba mengaktifkan kemampuan tersebut. Ini mengambil pasangan kunci-nilai yang mengonfigurasi perilaku input, slot aksi, dan ikon tombol sentuh opsional.
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Identifiers = AvatarAbilities.Identifiers
local Rule = AvatarAbilities.Rule
local Sensor = Identifiers.Sensor
local All, Not = Rule.All, Rule.Not
local Dash: AvatarAbilities.AbilityDefinition = {
Name = "Dash",
Input = { InputName = "Dash", Mode = "Press", ActionSlot = 5 }
}InputName adalah nama logis, bukan kunci. CCL menghasilkan sensor input dan selalu menambahkannya ke StartsWhen. Jangan tambahkan Rule.Input ke StartsWhen sendiri.
Mode mendefinisikan bagaimana input ini akan diinterpretasikan:
Mode Perilaku Kasus Penggunaan Press Aktivasi kemampuan dicoba saat input ditekan. Secara otomatis disuntikkan ke dalam kondisi StartsWhen. Tindakan diskrit seperti dash, serangan, dan lempar. Hold Kemampuan berjalan saat input ditahan; melepaskan menghentikannya ketika sensor input yang dihasilkan adalah kondisi RunsWhile. Tindakan berkelanjutan seperti sprint, aim, dan block. Toggle Setiap tekan membalikkan kemampuan aktif atau tidak aktif ketika sensor input yang dihasilkan adalah kondisi RunsWhile. Posisi atau gerakan yang dapat diaktifkan seperti membungkuk atau melayang. Repeat Seperti Hold, tetapi memicu ulang setiap siklus. Tindakan yang berhenti sendiri dan dapat memicu ulang saat ditahan. ActionSlot mendefinisikan slot aksi yang terkait dengan daftar InputActions dan InputBindings dalam Sistem Aksi Input.
Beberapa pengikatan input slot aksi telah ditentukan sebelumnya oleh Roblox dan, di masa depan, Pengelola Aksi Input akan memungkinkan Anda untuk mengonfigurasi ulang pengikatan input default untuk slot aksi sesuai keinginan. Mengatur ActionSlot ke 0 akan memilih slot kosong berikutnya yang tersedia. Di perangkat seluler, slot 1-7 terisi menjadi tombol di layar (lihat diagram di bawah).
Slot Keyboard & Mouse Gamepad Sentuh Penugasan Default 1 Space ButtonA ① Lompat 2 LeftShift ButtonL1 ② Berlari 3 LeftControl ButtonB ③ Merunduk 4 R ButtonX ④ 5 MouseLeftButton ButtonR2 ⑤ 6 Q ButtonY ⑥ 7 X ButtonR1 ⑦ 8 C ButtonL2 9 F DPadLeft 10 G DPadRight 11 V DPadDown 
CustomIcon, CustomIconActive, dan CustomIconInvalid menentukan ID aset Roblox untuk tombol sentuh saat kemampuan tidak aktif, aktif, atau tidak tersedia, masing-masing.
Ketika Anda menghilangkan RunsWhile, CCL menggunakan sensor input yang dihasilkan sebagai kondisi kelanjutan. Ketika Anda mendefinisikan RunsWhile, itu menggantikan default tersebut. Untuk kemampuan Hold atau Toggle yang harus berhenti saat inputnya menjadi tidak aktif, sertakan sentinel Rule.Input langsung dalam kondisi kustom. Rule.Input adalah nilai, bukan fungsi:
local AvatarAbilities = require("@rbx/AvatarAbilities")
local Rule = AvatarAbilities.Rule
local Sensor = AvatarAbilities.Identifiers.Sensor
local All, Input, Not = Rule.All, Rule.Input, Rule.Not
local Glide: AvatarAbilities.AbilityDefinition = {
Name = "Glide",
Input = { InputName = "Glide", Mode = "Hold", ActionSlot = 6 },
StartsWhen = Not(Sensor.Ground),
RunsWhile = All(Not(Sensor.Ground), Input),
}Sensor
Sebuah sensor adalah nilai bernama tentang dunia yang dibaca mesin untuk Anda. Anda biasanya akan membaca sensor daripada menulisnya. Untuk kenyamanan, beberapa sensor telah terdaftar sebelumnya:
| Sensor | Deskripsi |
|---|---|
| Sensor.Ground | Berdiri di atas permukaan |
| Sensor.IsMoving | Input gerakan sedang diterapkan |
| Sensor.MoveInput | Vektor gerakan itu sendiri |
| Sensor.Ceiling | Sesuatunya berada tepat di atas |
| Sensor.Climb | Permukaan yang dapat dipanjat berada dalam jangkauan |
| Sensor.Water / Sensor.WaterSurface | Di dalam air / di permukaan |
| Sensor.Sit | Duduk |
| Sensor.Tipped | Terjatuh |
| Sensor.Tool | Memegang Tool |
| Sensor.LookDirectionInput | Arah pandangan yang diperintahkan |
| Sensor.RotateToLookDirectionInput | Apakah karakter harus berputar ke arah pandangan yang diperintahkan |
Callback
Fungsi callback kemampuan memungkinkan Anda untuk memprogram perilaku spesifik:
Meskipun Anda mendaftarkan kemampuan kustom di server, callback mereka berjalan di kedua simulasi klien yang diprediksi dan simulasi server yang berwenang. Jaga perilaku callback tetap deterministik sehingga kedua simulasi menghasilkan hasil yang sama.
| Callback | Berjalan | Kasus Penggunaan |
|---|---|---|
| OnSetup(managerCtx, abilityCtx) | Sekali, saat kemampuan didaftarkan. | Menyimpan referensi, menginisialisasi status, dll. |
| OnStart(managerCtx, abilityCtx, hadLabel) | Setiap kali kemampuan diaktifkan. | Menerapkan efek seperti impuls. Fungsi hadLabel() melaporkan apakah label tertentu ada saat aktivasi dimulai, sebelum resolusi konflik. |
| OnUpdate(managerCtx, abilityCtx) | Setiap frame aktif. | Pekerjaan berkelanjutan seperti timer atau gaya per-frame. |
| OnStop(managerCtx, abilityCtx) | Setiap deaktivasi, sukarela atau terpaksa. | Membatalkan apa yang dilakukan OnStart(). |
| OnTeardown(managerCtx, abilityCtx) | Saat kemampuan dihapus. | Memutus koneksi, menghancurkan instance, dll. |
Parameter pertama dari setiap fungsi callback, managerCtx, adalah objek ManagerContext dengan properti karakter dan manajer yang dibagikan, termasuk:
- managerCtx.AbilityOwner — Karakter Model sehingga managerCtx.AbilityOwner.PrimaryPart adalah bagian akar.
- managerCtx.AbilityManager — Tampilan yang dipangkas dari manajer sehingga kemampuan dapat menambah, menghapus, dan menanyakan kemampuan dari dalam callback-nya sendiri.
- managerCtx.BodyParts — Bagian tubuh karakter, dengan pembantu untuk menghidupkan dan mematikan tabrakan per anggota tubuh.
- managerCtx.ControllerManager — ControllerManager karakter untuk kontrol fisika. Ini bisa nil ketika tidak ada kemampuan terdaftar yang memerlukan fisika.
- managerCtx.RootCFrame — CFrame bagian akar, diambil sekali di awal frame sehingga callback tidak perlu mengambilnya sendiri.
- managerCtx.RootLookVector — Arah bagian akar karakter menghadap.
- managerCtx.RootUpVectorY — Komponen Y dari vektor atas bagian akar.
- managerCtx.TaskSynchronize() — Menyinkronkan callback sebelum akses DataModel ketika dukungan callback paralel diaktifkan. Saat ini, OnUpdate tidak berjalan dalam konteks paralel, jadi fungsi ini tidak berpengaruh. Dukungan Luau Paralel penuh direncanakan untuk pembaruan mendatang.
Parameter kedua, abilityCtx, adalah objek AbilityContext dengan tabel yang dikelola mesin untuk pendaftaran kemampuan saat ini:
- abilityCtx.Config — Nilai konfigurasi hanya-baca untuk pendaftaran kemampuan ini.
- abilityCtx.State — Status yang dapat diubah yang direplikasi melalui DataModel. Otoritas Server mengembalikan nilai ini selama rollback dan resimulasi.
- abilityCtx.Local — Status sementara yang dapat diubah yang tidak direplikasi atau berpartisipasi dalam rollback.
Simpan data callback kustom di abilityCtx.State atau abilityCtx.Local. Menulis bidang kustom langsung ke abilityCtx adalah kesalahan.