Keterbatasan game 8-bit dan rekreasi tepatnya di Unity

gambar

Game retro dengan mekanik sederhana dan grafik piksel dapat membangkitkan kenangan hangat dari pemain berpengalaman, dan pada saat yang sama cukup mudah diakses oleh audiens yang lebih muda. Saat ini, banyak permainan yang disebut retro, tetapi usaha dan perencanaan diperlukan untuk menciptakan gaya nostalgia. Itu sebabnya kami mengundang orang-orang dari Mega Cat Studios untuk membantu kami mendiskusikan topik ini. Dalam posting ini, kami akan membahas semua yang Anda butuhkan untuk membuat grafik gaya permainan NES yang otentik, termasuk opsi Unity penting, struktur grafik, dan palet warna.

Buat seni bergaya NES yang otentik


Untuk memulai, kami akan membahas dasar-dasar pembuatan grafik untuk game yang memenuhi batasan Sistem Hiburan Nintendo klasik. Generasi konsol ini memberlakukan batasan serius pada artis yang ingin mereproduksi grafis otentiknya. Ini adalah batasan pada palet yang digunakan dan pada ukuran dan jumlah objek di layar. Selain itu, penting untuk mempertimbangkan bahwa resolusi konsol ini adalah 256 ร— 240 piksel.

Palet


Saat membuat grafik yang kompatibel dengan NES, artis harus mempertimbangkan banyak batasan. Pertama, batasan terpenting adalah bagaimana palet warna digunakan dalam gambar. NES unik karena semua warna palet yang mungkin "kabel" ke konsol. NES memilih warna mana yang akan digunakan dalam gambar dengan mengirimkan set nilai ke NES GPU, setelah itu GPU mengembalikan warna yang cocok dengan nilai-nilai tersebut. Berikut ini adalah gambar palet warna NES:


Warna-warna ini tidak dapat diubah karena mereka adalah bagian dari konsol itu sendiri. Semua game NES menggunakan kombinasi warna-warna ini yang membentuk gambar.

Sub palet


Untuk membuat kombinasi yang digunakan dalam gim, dibuat sub-palet yang dilampirkan ke sprite dalam game atau gambar latar belakang. NES membagi palet menjadi sub-palet yang dapat ditugaskan untuk sprite dan latar belakang. Setiap sub-palet berisi satu warna umum, yang digunakan di semua sub-palet, dan tiga warna unik. Ia dapat memuat empat sub-palet untuk latar belakang dan empat sub-palet untuk sprite. Dalam kasus sprite, warna keseluruhan pada awal setiap sub-palet dianggap transparan.


Berikut ini adalah contoh dari serangkaian sub-palet yang digunakan dalam game. Bagian atas menunjukkan sub-palet latar belakang, bagian bawah menunjukkan sub-palet sprite. Dalam contoh ini, hitam adalah warna umum yang digunakan di semua sub-palet. Karena warna keseluruhan dianggap transparan dalam sprite, warna hitam kedua diperlukan dalam sub-palet untuk sprite, yang digunakan sebagai warna yang terlihat.

Menugaskan Sub-Palet


Pembatasan penggunaan palet menjadi lebih ketat ketika artis beralih ke bagaimana palet digunakan dalam permainan. Untuk menjelaskan ini, Anda perlu berbicara lebih banyak tentang bagaimana konsol retro menyimpan dan menampilkan gambar. Grafik dari setiap konsol retro disimpan di dalam game sebagai ubin 8 ร— 8 piksel. Berkat ini, seniman dapat menghemat ruang dengan menggunakan kembali ubin untuk objek yang berbeda. (Misalnya, bagian jalan dapat digunakan kembali dengan membuat tebing atau bangunan dari mereka). Penting juga bahwa informasi warna tidak disimpan dengan grafik. Semua ubin disimpan dalam palet monokrom. Berkat ini, ketika ubin ditampilkan dalam game, Anda dapat menetapkan sub-palet padanya dan secara bersamaan menampilkannya di layar dengan sub-palet yang berbeda. Ini penting saat membuat ulang grafis konsol retro pada platform modern, karena ini memengaruhi cara kami menetapkan palet ke grafis.

NES menetapkan palet ke sprite dan latar belakang secara berbeda. Dia menugaskan palet sprite taylovo. Ini berarti bahwa dengan setiap ubin 8 ร— 8 dalam sprite, satu dari empat sub-palet sprite dapat dikaitkan.


Karakter ninja ini menggunakan dua sub-palet untuk meningkatkan kedalaman warna. Di sebelah kanan Anda dapat melihat bahwa ubin itu dibagi menjadi 8 ร— 8 ubin yang terpisah. Dalam bentuk yang terbagi, terlihat bahwa pirus cahaya dan merah tua yang digunakan dalam pedang dan ikat kepala unik untuk ubin ini, dan kontur ungu dan hitam gelap digunakan dalam tiga ubin yang tersisa.

Latar belakang tunduk pada batasan yang jauh lebih ketat. Latar belakang palet ditugaskan untuk 16 ร— 16 fragmen. Binding sub-palet latar belakang seluruh layar disebut Tabel Atribut (tabel atribut). Justru karena tabel ini sebagian besar gambar retro aktif menggunakan segmen ubin berulang. Segmen tersebut biasanya terdiri dari 16 ร— 16 ubin, itulah sebabnya mereka ditempatkan di tabel atribut. Terlepas dari kenyataan bahwa ini disebabkan oleh keterbatasan perangkat keras, ubin latar 16x16 seperti itu menjadi ciri khas grafis retro dan sekarang mutlak diperlukan untuk rekonstruksinya dalam permainan modern.


Berikut adalah contoh latar belakang kota yang indah dalam gaya RPG, dibuat dengan mempertimbangkan batasan-batasan ini. Gambar di sebelah kanan menunjukkan bahwa itu dibagi menjadi blok 16 ร— 16 piksel, dan palet ditetapkan untuk setiap blok. Untuk menghemat ruang, elemen-elemen seperti genteng, rumput, dan batu bata di jembatan dibuat dari bagian yang berulang dari balok-balok ini. Ubin atap bangunan kecil menggunakan ubin yang sama, tetapi mereka diberi sub-palet yang berbeda, memberikan penampilan yang unik.

Hamparan sprite


Meskipun seniman dapat menggunakan sub-palet yang berbeda untuk setiap ubin sprite 8 ร— 8, mereka mungkin memiliki situasi di mana perlu untuk memberikan kedalaman warna yang lebih besar pada sprite. Dalam hal ini, Anda dapat menggunakan overlay sprite. Sprite overlay adalah pembagian sprite menjadi dua sprite terpisah dan menempatkan mereka di atas satu sama lain. Hal ini memungkinkan seniman untuk menghindari kondisi menggunakan satu sub-palet per 8 ร— 8 ubin. Berkat ini, seniman sebenarnya bisa menggandakan jumlah warna yang dapat digunakan dalam satu area 8 ร— 8. Satu-satunya kelemahan serius dari pendekatan ini adalah keterbatasan rendering sprite. NES hanya mampu menampilkan 64 8 ร— 8 ubin sprite sekaligus, dan hanya delapan ubin sprite dalam satu garis horizontal. Jika batas ini tercapai, semua ubin lainnya tidak akan ditampilkan di layar. Itulah sebabnya di banyak game untuk NES dengan sejumlah besar sprite di layar, mereka mulai berkedip-kedip. Dalam hal ini, satu-satunya cara untuk menampilkan semua sprite adalah dengan menampilkannya dalam bingkai bergantian. Pembatasan tersebut harus diperhitungkan ketika melapiskan lapisan sprite, karena ini menggandakan tidak hanya jumlah warna, tetapi juga jumlah ubin sprite dalam satu garis horizontal.


Berikut adalah contoh Layering Sprite dalam aksi. Versi tricolor asli dari sprite bajak laut hantu ditampilkan di sebelah kiri. Artis membaginya menjadi dua bagian - tubuh / topi dan wajah / tangan, dan kemudian menetapkan mereka palet yang berbeda. Di sebelah kanan adalah hasil superposisi dari dua elemen satu sama lain.

Untuk menyiasati keterbatasan tabel atribut, Anda bisa mengimplementasikan lapisan sprite menggunakan latar belakang. Trik ini biasanya digunakan untuk gambar statis, seperti layar plot dan potret karakter, yang memberi mereka kedalaman warna yang jauh lebih besar. Untuk mewujudkan ini, artis harus menggambar bagian dari gambar sebagai latar belakang, dan kemudian menerapkan sprite di atasnya untuk mengisi bagian yang tersisa.


Dalam potret bajak laut hantu, lapisan sprite juga digunakan, memberikan kedalaman yang lebih besar. Tengkorak hijaunya ditampilkan di layar sebagai sprite, dan kerah dan topinya adalah bagian dari latar belakang. Hal ini memungkinkan seniman untuk menggunakan lebih banyak warna di segmen 16 ร— 16 untuk sepenuhnya menghindari keterbatasan tabel atribut.

Bank Grafik


Untuk menjelaskan batasan penting NES lainnya, pertama-tama kita harus kembali ke fakta bahwa grafik disimpan dalam ubin. Ubin grafis disimpan pada halaman 256 ubin, dan ubin dari halaman ini tidak dapat dimuat ke VRAM di tempat yang berbeda, sehingga menjadi sulit untuk menggabungkan dan menggabungkan ubin dari halaman yang berbeda. Konsol NES VRAM mampu menampilkan 512 ubin seperti itu secara bersamaan. Selain itu, ia membagi ubin menjadi dua untuk sprite dan latar belakang. Ini berarti bahwa pada saat yang sama konsol hanya dapat menampilkan 256 ubin sprite dan 256 ubin latar belakang. Jika seorang seniman ingin menampilkan berbagai macam sprite dan elemen latar belakang, pembatasan seperti itu sangat menghalangi dirinya.


Berikut ini adalah representasi grafis dari latar belakang dan ubin sprite dari game yang dimuat ke VRAM. Konsol menyimpan latar belakang dan sprite pada halaman terpisah.

Untuk mengatasi batasan ini, NES menggunakan fitur yang memungkinkan seorang artis untuk membagi setiap halaman menjadi halaman parsial yang disebut bank. Oleh karena itu, meskipun NES tidak dapat memuat petak individual dari berbagai titik data grafik, NES mampu memuat bagian halaman yang berbeda pada waktu yang berbeda. Di sebagian besar permainan, bank tersebut berukuran 1 KB dan 2 KB. Bank 1 KB setara dengan satu halaman keempat, atau 64 ubin, dan bank 2 KB setara dengan setengah halaman, atau 128 ubin. Artis harus memutuskan apakah dia ingin memesan setiap jenis bank untuk elemen sprite atau latar belakang, karena itu perlu untuk menggunakan kedua jenis. Ini berarti bahwa tidak mungkin memiliki 1 KB bank untuk sprite dan latar belakang. Satu halaman harus menggunakan 1 bank KB, dan yang lainnya 2 KB. Sebagai aturan, sebagian besar permainan menggunakan 1 KB bank untuk sprite, dan 2 KB untuk latar belakang, karena ubin latar belakang biasanya lebih statis dan memerlukan lebih sedikit variabilitas dan penggantian sambil jalan.


Gambar kalengan yang kita lihat di atas. Sisi kiri menunjukkan latar belakang yang menggunakan 2 bank KB, yaitu dibagi menjadi dua, dan sisi kanan menunjukkan sprite yang dibagi menjadi 1 bank KB. Setiap bank dapat diganti secara bebas dengan cepat.

Kegunaan bank 1K untuk sprite cukup penting. Jika sprite karakter memiliki sejumlah besar animasi yang tidak sesuai pada satu halaman, dan pada saat yang sama diperlukan untuk memuat sprite lain, maka tindakan individu dapat diunduh ke 1 bank KB, dan kemudian diganti dengan mereka tergantung pada apa yang terjadi di layar. Ini juga meningkatkan variabilitas sprite yang dapat digunakan di satu area permainan. Misalnya, jika seorang pemain harus memenuhi enam jenis musuh di tingkat permainan, tetapi hanya pemain dan tiga jenis sprite lain yang ditempatkan pada halaman sprite, maka ketika satu jenis musuh menghilang dari layar, permainan dapat mengganti salah satu bank musuh dengan jenis musuh baru.

Salah satu dari beberapa kelemahan serius menggunakan bank pada 1 KB untuk sprite dan 2 KB untuk latar belakang adalah cara konsol NES memproses animasi latar belakang. Untuk menghidupkan elemen latar belakang gim untuk NES, seorang artis harus membuat bank duplikat elemen latar belakang yang sama. Setiap bank duplikat baru akan berisi bingkai animasi berikutnya untuk masing-masing elemen animasi. Bank-bank ini saling menggantikan, menciptakan animasi. Jika seorang artis menggunakan bank berukuran setengah halaman untuk latar belakang, maka menyimpan semua bank duplikat ini mungkin membutuhkan banyak ruang. Salah satu cara mengatasi batasan ini adalah menempatkan semua elemen latar belakang animasi untuk seluruh permainan dalam satu bank. Tapi kemudian artis dihadapkan pada batasan lain: ia hanya memiliki 128 ubin yang tersisa untuk elemen statis dari setiap latar belakang. Setiap artis sendiri memutuskan metode penyimpanan mana yang terbaik untuknya.

Trik Lapisan


Banyak permainan pada zaman itu menggunakan trik untuk menciptakan efek seperti pengguliran latar belakang paralaks, tetapi juga menimbulkan tantangan bagi para seniman dan desainer. Kemudian konsol 16-bit memiliki dukungan untuk banyak lapisan latar belakang, tetapi NES tidak memiliki fitur ini. Semua latar belakang adalah gambar datar tunggal. Untuk menciptakan rasa kedalaman dan pelapisan, berbagai trik program digunakan. Misalnya, untuk membuat pengguliran paralaks, pengembang dapat mengatur register yang dilaporkan ketika garis horizontal tertentu (disebut garis pindai) digambar di layar.

Kemudian mereka dapat menggunakan register ini untuk mengontrol kecepatan dan arah menggulir layar. Berkat ini, Anda dapat membuat garis horizontal latar belakang, yang bergerak dengan kecepatan berbeda dari latar belakang lainnya. Bagi para seniman dan desainer, triknya adalah untuk mengingat bahwa latar belakang masih merupakan satu gambar datar. Jika sebuah platform atau elemen lain yang harus "di depan" dari latar belakang yang bergerak lambat ditempatkan di area ini, maka itu juga akan menggulir lebih lambat dari bagian gambar lainnya. Ini berarti bahwa desainer harus mengatur elemen latar belakang di tempat kejadian sehingga efeknya tidak terdistorsi.


Dalam contoh ini, area yang disorot dengan warna merah untuk mensimulasikan kedalaman dapat dibuat untuk menggulir lebih lambat daripada latar belakang lainnya. Antarmuka di bagian atas layar tidak menggulir, meskipun juga merupakan bagian dari gambar latar datar.

Ada satu trik lagi, berkat para seniman yang dapat "menggerakkan" salah satu elemen latar belakang ke depan. Di NES, pengembang dapat membuat prioritas sprite kurang dari nol. Jika ini dilakukan, sprite akan ditampilkan di bawah semua piksel buram latar belakang. Prioritas sprite juga dapat diubah dan diaktifkan dengan cepat, yang mana elemen-elemen individu dapat, jika perlu, mengubah prioritas sprite.

Alur kerja yang menyatu untuk nuansa retro terbaik


Unduh proyek contoh dan mulailah bekerja bersama kami!

Mega Cat Studios dari Pittsburgh, PA, telah mengubah penciptaan game retro menjadi bentuk seni. Bahkan, beberapa gim mereka bahkan dapat dibeli dengan kartrid dan dimainkan di konsol retro seperti Sega Genesis.



Krisis Medusa Kecil dan Kopi

Perubahan terbaru dalam alur kerja Unity telah mengubah mesin menjadi lingkungan yang sangat cocok untuk membuat game retro. 2D Tilemap telah ditingkatkan dan sekarang mendukung kartu ubin dari ubin persegi panjang, heksagonal, dan isometrik! Selain itu, Anda dapat menggunakan komponen Pixel Perfect Camera baru untuk mencapai gerakan holistik piksel demi piksel dan efek grafis. Anda bahkan dapat menggunakan tumpukan pasca pemrosesan untuk menambahkan semua jenis efek retro layar yang indah. Namun, sebelum melakukan semua ini, Anda harus mengimpor dan mengonfigurasi aset dengan benar.

Persiapan aset sprite


Agar aset menjadi jelas dan pixelated, mereka harus terlebih dahulu dikonfigurasikan dengan benar. Pilih setiap aset yang Anda gunakan di jendela Proyek, dan kemudian ubah parameter berikut di inspektur:

  • Ubah mode filter ke "Point"
  • Kompresi berubah menjadi "Tidak Ada"


Mode penyaringan lainnya menghasilkan sedikit kekaburan gambar, yang melanggar gaya piksel jernih yang kita perjuangkan. Saat menggunakan Kompresi, data gambar dikompresi, menghasilkan sedikit keakuratan. Ini penting untuk dipertimbangkan, karena beberapa piksel dapat berubah warna karena kompresi, berpotensi mengubah seluruh palet warna.

Semakin kecil ukuran dan jumlah warna dalam sprite, semakin banyak kompresi yang akan memengaruhinya. Berikut adalah contoh membandingkan kompresi normal (default) dan kurangnya kompresi.


Kompresi normal / Gambar tanpa kompresi - terlihat persis sama dengan aslinya

Aspek lain yang perlu dipertimbangkan adalah parameter Ukuran Maks dari gambar di Inspektur. Jika gambar sprite lebih besar pada sumbu apa pun daripada properti Ukuran Max (secara default adalah 2048), maka gambar tersebut akan secara otomatis disesuaikan dengan ukuran maksimum. Ini biasanya mengakibatkan hilangnya kualitas dan gambar mulai terlihat buram. Karena beberapa platform tidak dapat mendukung tekstur yang lebih besar dari 2048 di sepanjang sumbu apa pun, yang terbaik adalah tetap berada dalam kisaran itu.


Ukuran maks adalah 2048 / Dan sekarang ukuran maks adalah 4096

Gambar di atas menunjukkan sprite dari sprite sheet (sprite sheet) yang memiliki ukuran sumbu tunggal 2208 dengan ukuran maksimum 2048. Seperti yang Anda lihat, dengan meningkatkan properti Ukuran Max ke 4096, kami dapat memastikan ukuran gambar yang benar tanpa kehilangan kualitas.

Terakhir, saat menyiapkan sprite atau sprite sheet, Anda harus mengatur parameter mode unit pivot ke Piksel alih-alih Normal.


Karena ini, titik pivot gambar akan ditentukan berdasarkan piksel, dan tidak dalam interval yang mulus dari 0 hingga 1 di sepanjang setiap sumbu gambar. Jika titik jangkar dari sprite tidak selaras dengan pixel, maka kita akan kehilangan lokasi sprite karena ketepatan pixel. Poin jangkar untuk sprite dapat diatur dalam Editor Sprite, yang terbuka di Inspektur ketika aset sprite dipilih.


Menginstal Paket Pixel Perfect 2D


Setelah menyiapkan aset, kita dapat membuat piksel kamera sempurna. Hasil piksel-akurat akan terlihat jelas dan diucapkan. Tanda-tanda pixel art yang tidak akurat menjadi blur (distorsi) dan rectangularity dari beberapa pixel.

Paket 2D Pixel Perfect dapat diimpor menggunakan Package Manager mesin Unity. Klik pada menu Window di toolbar dan kemudian pilih Package Manager. Di jendela baru, klik Lanjut dan pilih kotak centang Tampilkan paket pratinjau. Pilih 2D Pixel Perfect dari daftar di sebelah kiri, lalu klik Install di sudut kanan atas jendela.


Itu saja. Anda sekarang siap untuk memulai dengan komponen kamera pixel-akurat.

Akurasi Pixel Tinggi


Komponen Pixel Perfect Camera ditambahkan ke komponen Camera mesin Unity dan melengkapinya.Untuk menambahkannya, buka kamera utama dan tambahkan komponen Pixel Perfect Camera ke dalamnya. Jika komponen Pixel Perfect Camera tidak ada dalam menu, maka ikuti langkah-langkah di atas untuk mengimpornya ke proyek.


Sekarang mari kita periksa opsi yang tersedia.


Pertama, saya sarankan menyalakan "Jalankan Dalam Mode Edit" dan mengatur rasio aspek ke "Aspek Gratis" di jendela Game sehingga Anda dapat dengan bebas mengubah jendela game. Komponen akan menampilkan pesan yang berguna di jendela permainan, yang menunjukkan apakah tampilan tersebut akurat piksel dalam resolusi saat ini.


Sekarang Anda bisa melihat setiap parameter dan melihat bagaimana mereka memengaruhi penampilan game.

  • Assets Pixels Per Unit โ€” , . , , (pixels per unit, PPU), . , 16 16 , PPU 16 โ€” 1. PPU.
  • Reference Resolution โ€” . . -, . , Sega Genesis 320ร—224. Sega Genesis 320ร—224. , 16:9 320ร—180 398ร—224 ( ).
  • Upscale Render Texture โ€” . , , - . ยซUpscale Render Textureยป .


1. ( ) 2. Upscale Render Texture ( 45 , , ) 3. Upscale Render Texture ( 45 , , , .)

  • Pixel Snapping ( Upscale Render Texture) โ€” (sprite renderer) , PPU. , transform . - , - .
    • Contoh:


Pixel Snapping . (0, 0), โ€” (1.075, 0). . , . Pixel Snapping . โ€” (0, 0), (1.075, 0). .

  • Crop Frame (X Y) โ€” , , , .
  • Stretch Fill - selalu tersedia jika Anda mengaktifkan x dan y untuk Crop Frame. Opsi ini menyebabkan kamera untuk skala jendela permainan agar sesuai dengan layar sambil mempertahankan rasio aspek. Karena penskalaan dilakukan tidak hanya untuk kelipatan bilangan bulat dari resolusi referensi, dalam semua kasus lainnya, akurasi piksel akan hilang. Namun, keuntungan dari parameter ini adalah bahwa meskipun hilangnya akurasi piksel dalam banyak resolusi, tidak akan ada bilah hitam di tepinya dan layar akan sepenuhnya penuh. Meskipun peregangan sering menyebabkan kekaburan, peringatan normal tidak ditampilkan.


Karakter dan latar belakang, isian stretch blurry

Rekomendasi Kamera Perfect Pixel


Jika Anda membutuhkan tampilan yang akurat piksel dengan snap ke grid, maka saya sarankan yang berikut:

  • , ( 320ร—180).
  • Upscale Render Texture
    • , , 90, 180 270 , .
    • Upscale Render Texture - ; . , Pixel Perfect Camera Run in Edit Mode , . - , - .
  • Pixel Snapping
    • . , .
  • Crop Frame X / Y, Upscale Render Texture
    • - upscale render texture, X / Y - , .
  • Stretch Fill

Kami menyarankan pengaturan kamera sehingga dioptimalkan untuk rasio layar 16: 9, termasuk resolusi referensi, jika memungkinkan. Pada saat penulisan, sebagian besar pemain bermain di monitor dengan rasio aspek 16: 9 dan resolusi 1920 ร— 1080. Misalnya, resolusi referensi 320 ร— 180 memiliki rasio 16: 9, dan oleh karena itu, dengan resolusi 1920 ร— 1080 dan resolusi lain yang merupakan kelipatan 320 ร— 180, misalnya, 1280 ร— 720, tidak akan ada batas hitam di tepi layar.

Di bilah alat Unity, Anda dapat pergi ke Edit> Pengaturan Proyek> Player dan batasi rasio aspek yang didukung oleh permainan. Jika Anda menemukan bahwa konfigurasi tertentu terlihat bagus dalam rasio yang Anda butuhkan, tetapi tidak cocok dengan rasio individu, maka di sini Anda dapat mematikan rasio ini. Namun, perlu diingat bahwa tidak semua pengguna memiliki layar yang kompatibel dengan keterbatasan Anda, jadi ini tidak disarankan. Alih-alih, gunakan pemangkasan agar pengguna tersebut menampilkan batas hitam dan permainan tidak dimulai pada resolusi yang tidak cocok dengan layar mereka.

Kesimpulan


Ketika Anda mencoba membuat proyek yang otentik untuk konsol retro, Anda perlu mempertimbangkan banyak aspek teknis yang tidak dipikirkan siapa pun dalam pengembangan modern. Karena sifat rendering gambar dan jejak memori yang rendah dari mesin yang lebih tua, desainer harus berpikir kreatif dan menghindari batasan perangkat keras. Di era modern, kita perlu tahu tentang keterbatasan dan teknik ini untuk secara akurat menciptakan kembali tampilan dan desain game pada era itu. Dalam posting berikutnya, kita akan melihat batasan desain era game 16-bit, serta alur kerja Unity yang diperlukan untuk menciptakan kembali gaya nyata dari "TV lama".

Source: https://habr.com/ru/post/id462843/


All Articles