Posting blog ini sepenuhnya dikhususkan untuk sistem animasi karakter saya, diisi dengan tips dan cuplikan kode yang bermanfaat.
Selama dua bulan terakhir, saya telah menciptakan sebanyak 9 aksi pemain baru (hal-hal lucu seperti memblokir dengan perisai, menghindari lompatan dan senjata), 17 item yang bisa dipakai, 3 set baju besi (piring, sutra dan kulit) dan 6 jenis gaya rambut. Saya juga selesai membuat semua otomatisasi dan peralatan, jadi semuanya sudah digunakan dalam game. Dalam artikel saya akan memberi tahu bagaimana saya mencapai ini!
Saya harap informasi ini bermanfaat dan membuktikan bahwa tidak perlu menjadi jenius untuk secara mandiri membuat alat / otomasi tersebut.Deskripsi singkat
Awalnya, saya ingin memeriksa apakah mungkin untuk menggabungkan sprite yang ditumpangkan satu sama lain dengan animator yang disinkronkan untuk membuat karakter modular dengan gaya rambut yang dapat diganti, peralatan, dan barang yang bisa dipakai. Apakah mungkin untuk menggabungkan animasi piksel yang digambar dengan tangan dengan karakter yang benar-benar dapat disesuaikan.
Tentu saja, fungsi-fungsi tersebut secara aktif digunakan dalam game 3D dan 2D dengan sprite yang dibuat sebelumnya atau dalam game 2D dengan animasi skeletal, tetapi sejauh yang saya tahu, tidak banyak game yang menggabungkan animasi yang dibuat secara manual dan karakter modular (biasanya karena prosesnya ternyata merupakan terlalu monoton).
Saya menemukan GIF kuno ini pada bulan pertama saya bersama Unity. Faktanya, sprite modular ini adalah salah satu percobaan pertama saya dalam pengembangan game!Saya membuat prototipe menggunakan sistem animasi Unity, dan kemudian menambahkan satu kemeja, sepasang celana, satu gaya rambut, dan tiga item untuk menguji konsep tersebut. Ini membutuhkan 26 animasi terpisah.
Pada saat itu, saya membuat semua animasi saya di Photoshop dan tidak repot dengan otomatisasi prosesnya, jadi itu sangat membosankan. Lalu saya berpikir: "Jadi, ide dasarnya berhasil, nanti saya akan menambahkan animasi dan peralatan baru." Ternyata "nanti" adalah beberapa tahun kemudian.
Pada bulan Maret tahun ini, saya menggambar desain sejumlah besar baju besi (lihat posting saya sebelumnya), dan memperhatikan bagaimana proses ini dapat dibuat lebih nyaman. Saya terus menunda implementasi, karena bahkan dengan otomatisasi saya merasa gugup bahwa tidak ada yang akan berhasil.
Saya berharap bahwa saya harus meninggalkan kustomisasi karakter dan membuat satu-satunya karakter utama, seperti pada kebanyakan game dengan animasi manual. Tapi aku punya rencana aksi, dan sudah waktunya untuk memeriksa apakah aku bisa mengalahkan monster ini!
Spoiler: Semuanya ternyata hebat. Di bawah ini saya akan mengungkapkan rahasia saya ***Sistem sprite modular
I. Ketahui batasan Anda
Saya menghabiskan banyak pra-pengujian dan seni pemantauan waktu untuk mencari tahu berapa banyak mungkin mengambil pekerjaan tersebut, dan apakah atau tidak terjangkau bagi saya tingkat yang sama kualitas.
Saya menuliskan semua ide saya untuk animasi, menyatukannya dalam spreadsheet, dan mengaturnya sesuai dengan berbagai kriteria, seperti kegunaan, keindahan, dan penggunaan berulang. Untuk mengejutkan saya, pertama dalam daftar ini adalah subjek animasi lemparan (ramuan, bom, pisau, kapak, bola).
Saya datang dengan skor numerik untuk setiap animasi dan meninggalkan semuanya dengan kinerja buruk. Awalnya, saya berencana untuk membuat 6 set baju besi, tetapi segera menyadari bahwa itu terlalu banyak, dan membuang tiga jenis baju besi.
Aspek pelacakan waktu ternyata sangat penting, dan saya sangat merekomendasikan menggunakannya untuk menjawab pertanyaan seperti: "Berapa banyak musuh yang mampu saya hasilkan dalam permainan?". Setelah hanya beberapa tes, saya berhasil memperkirakan perkiraan yang cukup akurat. Dengan pekerjaan lebih lanjut tentang animasi, saya terus melacak waktu dan merevisi harapan saya.
Saya akan membagikan salinan jurnal kerja saya selama dua bulan terakhir. Harap dicatat bahwa waktu ini merupakan tambahan dari pekerjaan saya yang biasa, di mana saya menghabiskan 30 jam seminggu:https://docs.google.com/spreadsheets/d/1Nbr7lujZTB4pWMsuedVcgBYS6n5V-rHrk1PxeGxr6Ck/edit?usp=sharingII Mengubah palet untuk masa depan yang lebih cerah
Dengan menggunakan warna-warna dalam desain sprite dengan bijak, Anda dapat menggambar satu sprite dan membuat banyak variasi berbeda dengan mengubah palet. Anda dapat mengubah tidak hanya warna, tetapi juga membuat berbagai elemen on dan off (misalnya, mengganti warna dengan transparansi).
Setiap set baju besi memiliki 3 variasi, dan dengan mencampur bagian atas dan bawah, Anda bisa mendapatkan banyak kombinasi. Saya berencana untuk menerapkan sistem di mana Anda dapat mengumpulkan satu set baju besi untuk penampilan karakter, dan yang lain untuk karakteristiknya (seperti dalam Terraria).
Dalam prosesnya, saya terkejut dengan kombinasi yang aneh. Jika Anda menghubungkan bagian atas piring dengan bagian bawah sutra, Anda bisa mendapatkan sesuatu dengan gaya seorang penyihir perang.Yang terbaik adalah mengubah palet menggunakan warna yang menyandikan nilai dalam sprite sehingga Anda nantinya dapat menggunakannya untuk menemukan warna asli dari palet. Saya tahu saya sedikit menyederhanakan, jadi inilah video untuk Anda mulai:
Saya tidak akan menjelaskan semuanya secara terperinci, tetapi saya akan berbicara tentang cara menerapkan teknik ini dalam Unity, dan pro dan kontra mereka.
1. Cari tekstur untuk setiap palet
Ini adalah strategi terbaik untuk membuat variasi musuh, latar belakang, dan semua itu di mana banyak sprite memiliki palet / bahan yang sama. Bahan-bahan yang berbeda tidak dapat dikelompokkan ke dalam kelompok, bahkan jika mereka menggunakan sprite / atlas yang sama. Bekerja dengan tekstur cukup menyakitkan, tetapi Anda dapat mengubah palet secara real time dengan mengganti bahan menggunakan SpriteRenderer. SharedMaterial.SetTexture atau MaterialPropertyBlock jika Anda membutuhkan palet yang berbeda untuk setiap contoh materi. Berikut adalah contoh fungsi fragmen shader:
sampler2D _MainTex; sampler2D _PaletteTex; float4 _PaletteTex_TexelSize; half4 frag(v2f input) : SV_TARGET { half4 lookup = tex2D(_MainTex, input.uv); half4 color = tex2D(_PaletteTex, half2(lookup.r * (_PaletteTex_TexelSize.x / 0.00390625f), 0.5)); color.a *= lookup.a; return color * input.color; }
2. Array warna
Aku berhenti di keputusan ini, karena saya harus mengganti palet setiap kali mengubah tampilan karakter (misalnya, untuk menempatkan artikel), dan untuk membuat beberapa palet dinamis (untuk menampilkan yang dipilih warna pemain rambut dan kulit). Sepertinya saya bahwa pada saat runtime dan editor untuk keperluan ini akan lebih mudah untuk bekerja dengan array.
Kode:
sampler2D _MainTex; half4 _Colors[32]; half4 frag(v2f input) : SV_TARGET { half4 lookup = tex2D(_MainTex, input.uv); half4 color = _Colors[round(lookup.r * 255)]; color.a *= lookup.a; return color * input.color; }
Saya menyajikan palet saya sebagai jenis ScriptableObject dan menggunakan alat MonoBehaviour untuk mengeditnya. Setelah bekerja lama untuk mengedit palet dalam proses membuat animasi di Aseprite, saya menyadari alat apa yang saya butuhkan dan menulis skrip ini sesuai. Jika Anda ingin menulis alat Anda sendiri untuk mengedit palet, maka berikut adalah beberapa fungsi yang saya sarankan untuk diterapkan:
- Memperbarui palet pada berbagai bahan saat mengedit warna untuk menampilkan perubahan secara real time.
- Menetapkan nama dan mengubah urutan warna dalam palet (gunakan bidang untuk menyimpan indeks warna, bukan urutannya dalam array).
- Pilih dan edit beberapa warna sekaligus. (Kiat: Anda dapat menyalin dan menempel bidang Warna di Persatuan: cukup klik satu warna, salin, klik pada warna lain, tempel - sekarang semuanya sama!)
- Terapkan warna overlay ke seluruh palet
- Rekam palet dalam tekstur
3. Tekstur pencarian tunggal untuk semua palet
Jika Anda ingin mengganti palet dengan cepat, tetapi pada saat yang sama Anda perlu batch untuk mengurangi jumlah panggilan draw, maka Anda dapat menggunakan teknik ini. Ini mungkin berguna untuk platform seluler, tetapi menggunakannya cukup merepotkan.
Pertama, Anda harus mengemas semua palet menjadi satu tekstur besar. Kemudian Anda menggunakan warna yang ditentukan dalam komponen SpriteRenderer (warna titik AKA) untuk menentukan garis yang akan dibaca dari tekstur palet ke dalam shader. Yaitu, palet sprite ini dikendalikan melalui SpriteRenderer.color. Warna verteks adalah satu-satunya properti SpriteRenderer yang dapat diubah tanpa merusak kondisinya (asalkan semua bahannya sama).
Dalam kebanyakan kasus, yang terbaik adalah menggunakan saluran alfa untuk mengontrol indeks, karena Anda mungkin tidak akan membutuhkan banyak sprite dengan transparansi yang berbeda.
Kode:
sampler2D _MainTex; sampler2D _PaletteTex; float4 _PaletteTex_TexelSize; half4 frag(v2f input) : SV_TARGET { half4 lookup = tex2D(_MainTex, input.uv); half2 paletteUV = half2( lookup.r * _(PaletteTex_TexelSize.x / 0.00390625f), input.color.a * _(PaletteTex_TexelSize.y / 0.00390625f) ) half4 color = tex2D(_PaletteTex, paletteUV); color.a *= lookup.a; color.rgb *= input.color.rgb; return color; }
Keajaiban mengganti palet dan lapisan sprite. Begitu banyak kombinasi.III. Otomasi semuanya dan gunakan alat yang tepat.
Untuk mengimplementasikan fungsi ini, otomatisasi mutlak diperlukan, karena sebagai hasilnya saya mendapat sekitar 300 animasi dan ribuan sprite.
Langkah pertama saya adalah membuat eksportir untuk Aseprite untuk mengelola skema lapisan sprite gila saya menggunakan
antarmuka baris perintah yang nyaman. Ini hanya skrip perl yang mem-bypass semua layer dan label dalam file Aseprite saya dan mengekspor gambar dalam direktori dan struktur nama tertentu sehingga saya bisa membacanya nanti.
Lalu saya menulis importir untuk Unity. Aseprite menampilkan file JSON yang nyaman dengan data bingkai, sehingga Anda dapat membuat aset animasi secara terprogram. Pengolahan Aseprite JSON dan menulis jenis data terbukti cukup membosankan, jadi saya membawa mereka di sini. Anda dapat dengan mudah memuatnya ke Unity menggunakan JsonUtility.FromJson <AespriteData>, ingatlah untuk menjalankan Aseprite dengan opsi --format 'json-array'.
Kode:
[System.Serializable] public struct AespriteData { [System.Serializable] public struct Size { public int w; public int h; } [System.Serializable] public struct Position { public int x; public int y; public int w; public int h; } [System.Serializable] public struct Frame { public string filename; public Position frame; public bool rotated; public bool trimmed; public Position spriteSourceSize; public Size sourceSize; public int duration; } [System.Serializable] public struct Metadata { public string app; public string version; public string format; public Size size; public string scale; } public Frame[] frames; public Metadata meta; }
Di sisi Unity, saya punya masalah serius di dua tempat: memuat / mengiris sprite sheet dan membuat klip animasi. Contoh yang jelas akan banyak membantu saya, jadi di sini ada potongan kode dari importir saya sehingga Anda tidak terlalu menderita:
Kode:
TextureImporter textureImporter = AssetImporter.GetAtPath(spritePath) as TextureImporter; textureImporter.spriteImportMode = SpriteImportMode.Multiple; SpriteMetaData[] spriteMetaData = new SpriteMetaData[aespriteData.frames.Length];
Jika Anda belum melakukannya, percayalah - sangat mudah untuk mulai membuat alat Anda sendiri. Trik termudah adalah dengan menempatkan GameObject di tempat kejadian dengan MonoBehaviour terpasang, yang memiliki atribut [ExecuteInEditMode]. Tambahkan tombol dan Anda siap bertempur! Ingatlah bahwa alat pribadi Anda tidak harus terlihat bagus, mereka bisa murni utilitarian.
Kode:
[ExecuteInEditMode] public class MyCoolTool : MonoBehaviour { public bool button; void Update() { if (button) { button = false; DoThing(); } } }
Ketika bekerja dengan sprite, cukup mudah untuk mengotomatisasi tugas standar (misalnya, membuat tekstur palet atau mengganti warna dalam beberapa file sprite). Berikut adalah contoh dari mana Anda bisa mulai belajar bagaimana mengubah sprite Anda.
Kode:
string path = "Assets/Whatever/Sprite.png"; Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path); TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter; if (!textureImporter.isReadable) { textureImporter.isReadable = true; AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } Color[] pixels = texture.GetPixels(0, 0, texture.width, texture.height); for (int i = 0; i < pixels.Length; i++) {
Bagaimana Saya Mengatasi Peluang Mecanim: Keluhan
Seiring waktu, sistem sprite modular prototipe yang saya buat menggunakan Mecanim menjadi masalah terbesar ketika meningkatkan Unity, karena API terus berubah dan kurang didokumentasikan. Dalam kasus mesin keadaan sederhana, akan lebih bijaksana untuk dapat menanyakan status setiap klip atau mengubah klip saat runtime. Tapi tidak! Untuk alasan kinerja, Unity membuat klip di negara mereka dan memaksa kami untuk menggunakan sistem redefinisi yang ceroboh untuk mengubahnya.
Mecanim sendiri bukanlah sistem yang buruk, tetapi bagi saya tampaknya tidak berhasil mewujudkan fitur utama yang dinyatakan - kesederhanaan. Gagasan para pengembang adalah mengganti apa yang tampak rumit dan menyakitkan (scripting) dengan sesuatu yang sederhana (mesin visual state). Namun:
- Setiap mesin negara terbatas non-sepele dengan cepat berubah menjadi web liar node dan koneksi, logika yang tersebar di berbagai lapisan.
- Kasing penggunaan sederhana terhalang oleh persyaratan sistem umum. Untuk memainkan satu atau dua animasi, Anda perlu membuat controller baru dan menetapkan status / transisi. Tentu saja, ada pemborosan sumber daya yang berlebihan.
- Sangat lucu bahwa sebagai hasilnya, Anda masih harus menulis kode, karena agar mesin negara dapat melakukan sesuatu yang menarik, Anda memerlukan skrip yang memanggil Animator. SetBool dan metode serupa.
- Untuk beberapa penggunaan mesin negara dengan klip lain, Anda perlu menduplikat dan mengganti klip secara manual. Di masa depan, Anda harus membuat perubahan di beberapa tempat.
- Jika Anda ingin mengubah apa yang ada dalam keadaan saat runtime, maka Anda memiliki masalah. Solusinya adalah API yang buruk atau grafik gila dengan satu simpul untuk setiap animasi yang mungkin.
Kisah tentang bagaimana pengembang Firewatch masuk ke neraka scripting visual . Lucunya ketika pembicara menunjukkan contoh paling sederhana, mereka tetap terlihat gila. Penonton benar-benar mengerang pada jam 12:41 . Tambahkan biaya perawatan besar, dan Anda akan mengerti mengapa saya sangat tidak menyukai sistem ini.Banyak dari masalah ini bahkan bukan kesalahan pengembang Mecanim, tetapi hanya hasil alami dari ide-ide yang tidak kompatibel: Anda tidak dapat membuat sistem sederhana yang umum dan pada saat yang sama, dan menggambarkan logika menggunakan gambar lebih sulit dari sekedar kata / simbol (apakah ada yang ingat diagram alur UML?) . Saya ingat sebagian dari
laporan Zack McClendon di Praktik NYC 2018 , dan jika Anda punya waktu, saya sarankan Anda menonton seluruh video!
Namun, saya menemukan jawabannya. Visual scripting selalu dikecam oleh kutu buku agresif "tulis mesin Anda sendiri" yang tidak mengerti kebutuhan artis. Selain itu, tidak dapat dipungkiri bahwa sebagian besar kode tampak seperti jargon teknis yang tidak dapat dipahami.
Jika Anda sudah menjadi programmer kecil dan membuat game dengan sprite, maka Anda mungkin perlu berpikir dua kali. Ketika saya mulai, saya yakin bahwa saya tidak akan pernah bisa menulis sesuatu yang berhubungan dengan mesin lebih baik daripada pengembang Unity.
Dan tahukah Anda? Ternyata sprite animator hanyalah sebuah skrip yang mengubah sprite setelah beberapa detik. Meskipun demikian, saya masih harus menulis sendiri. Sejak itu saya telah menambahkan acara animasi dan fungsi lainnya ke proyek spesifik saya, tetapi versi dasar yang saya tulis setengah hari mencakup 90% dari kebutuhan saya. Ini terdiri dari hanya 120 baris dan dapat diunduh secara gratis dari sini:
https://pastebin.com/m9Lfmd94 . Terima kasih telah membaca artikel saya. Sampai ketemu lagi!