Avagy hogyan írassunk ki Unity-ben egyiptomi hieroglifákat.
Egyik diákom egy elég specifikus kéréssel fordult hozzám a minap. Megkérdezte, hogy megoldható-e hogy székely-magyar rovásírást jelenítsünk meg Unity-ben.
Azt a választ adtam amit mindig adok a “megoldható-e” kezdetű kérdésekre: “Természetesen megoldható. Minden megoldható!” Azt viszont, hogy hogyan, sajnos nem tudtam megmondani ott helyben mivel a probléma eléggé specifikus és még nem akadt dolgom vele. Viszont mivel érdekelt a kérdés, aznap este nekiálltam kísérletezgetni. Nem csak a rovásírás érdekelt, de bármiféle speciális karakterkészlet megjelenítése, mint például az egyiptomi hieroglifák, vagy a sumér ékírás. Szerencsére rövid próbálgatás után meg is találtam a megoldást, és úgy döntöttem írok is róla egy cikket.
Kezdjük viszont egy kis elmélettel:
Karakterkészletek
A számítógép hárdverén nem tudunk karaktereket ábrázolni csak számokat, binárisan eltárolva. Ezért van szükségünk a karakterkészletekre / karaktertáblákra, amik olyan táblázatok, amik számokat rendelnek az abc egy egy betűjéhez valamint minden szám vagy bármi egyéb speciális karakterhez is. Ekkor ha egy stringet (szöveget) szeretnénk eltárolni, nincs szükségünk másra, mint számok egy tömbjére (sorozatára) valamint a megfelelő karaktertáblára.
A legrégebbi széles körben elterjedt számítógépes karakterkódolási rendszer az ASCII (American Standard Code for Information Interchange) (Ejtsd: Aszki). Az ASCII karakterek 7-biten reprezentálhatók, azaz kevesebb mint egy bájttal. Ez 128 azaz karakter kódolását teszi lehetővé, amelyek között megtalálhatók az angol ábécé nagy- és kisbetűi, számok, írásjelek és egyéb speciális karakterek. Az ASCII viszont nem támogatja a nem angol betűket, így nem alkalmas minden nyelv igényeinek kielégítésére.
A Unicode egy karakterkódolási rendszer, amely célja a világ összes írásrendszerének és karakterének lefedése. A Unicode karakterkészletét több mint 1 millió elem alkotja, amik őrzik a különböző nyelvek írásjeleit, matematikai szimbólumokat, emojikat, és egyéb speciális karaktereket is. Sőt a Unicode tábla nem is csak karaktereket tartalmaz, hanem úgynevezett kódpontokat, amik lehetnek karakterek vagy valamiféle módosítók is, mint például egy ékezet.
Az magyar “ü” betű például kifejezhető egyetlen kódponttal is: (U+00FC), de megadható kettő kódpont összekapcsolásából is:
u
Karakter kódpont: U+0075
Kis “u” betű
+ ◌̈
Ékezet módosító kódpont: U+0308
Kettő pont ékezet
= ü
Karakter (2 kódpontból): U+0075 U+0308
Kis “ü” betű
👋
Emoji kódpont: U+1F44B
Integető kéz
+ 🎨
Szín módosító kódpont: U+1F3FD
Enyhín barna szín
= 👋🏽
Emoji (2 kódpontból): U+1F44B U+1F3FD
Enyhín barna integető kéz
Ahhoz, hogy ennyi kódpontot le tudjon fedni a karaktertábla, messze nem elégséges 1 bájt, helyette a Unicode jelenleg 4 bájtig bezárólag támogat kódolást, ami nagyjából 1.1 millió különböző karaktert képes leírni. (Azért nem többet, mert bizonyos bitek egyéb információkat kódolnak, például, hogy egy adott byte hányadik helyén áll a kódponton belül.) Viszont ez a bő egymillió épp elégnek is tűnik. Még messze nem használtuk ki az összes szabad mezőt, ezért a kódtábla folyamatosan fejlődik és még bőven van is hely hozzá. Jelenleg 144 697 elemből áll.
UTF Karakterkódolás
Mindazonáltal, ha egy karakter négyszer annyi bájton van tárolva, az azt is jelenti, hogy egy szövegfájl mérete is az ASCII-hoz képest négyszeresére fog nőni ezzel a kódolással annak ellenére, hogy egy átlag angol nyelvű szöveghez gyakran elégséges az egy bájtos ASCII.
A Unicode ezért több kódolási sémát is kínál különböző előnyökkel és gyengeségekkel. Ezeket az Unicode Transformation Format-nak röviden UTF kódolásoknak nevezzük:
UTF-8
Változó hosszúságú karaktereket használ. Egy UTF-8 karakter mérete 1-től 4 bájtig terjedhet. Ha a karakter első bájt-ja 0
-ával kezdődik, akkor egy bájtos a karakter. Az első byte maradék 7 bitje kódolja le a karaktert, ezért a szabvány visszafelé teljesen kompatibilis az ASCII-vel, azaz egy ASCII-ban írt szöveg tökéletesen olvasható lesz UTF-8 kódolással is. A a kódpont a 110
számsorozattal kezdődik, akkor tudható hogy a karakter két bájton lesz tárolva, 1110
jelenti a 3 és 11110
a 4 bájtos elemeket . Ha egy bájt 10
-zel kezdődik, akkor tudható, hogy az egy több byte-os kódpont későbbi 8 bitjét indítja:
Byte 1 | Byte 2 | Byte 3 | Byte 4 | Tartomány (Hexadecimális) | kódpont-szám |
0xxxxxxx | ————— | ————— | ————— | 0 - 7F | = 128 |
110xxxxx | 10xxxxxx | ————— | ————— | 80 - 7FF | 2 047 |
1110xxxx | 10xxxxxx | 10xxxxxx | ————— | 0800 - FFFF | 63 487 |
11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx | 10000 - 10FFFF | 1 048 575 |
Összes kódpontok száma: | 1 114 237 |
Az UTF-8 a legkompaktabb olyan karakterkészlet, ami tartalmazza a Unicode minden lehetséges elemét. Ha csak ASCII karaktereket használunk, akkor egy UTF-8 szöveg nem fog több helyet foglalni, mint az ASCII megfelelője és ha egyéb karakterekkel is bővítjük, mint pl. a magyar á, é, í, ó, ö, ő, ú, ü, ű vagy ezek nagybetűs változatai, akkor csak ezek a “ritkább” karakterek fognak több bájtot elfoglalni, az a, b, c, 1, 2, 3 továbbra is 1 byte-os marad.
UTF-16 és UTF-32
Mindazonáltal az UTF-8 nem minden tulajdonsága ideális. Gondoljuk végig: Egy UTF-8 szövegnek nem tudjuk a mérete alapján megmondani a karakterszámát és nem tudunk tetszőlegesen egy megadott indexű karakteréhez ugrani addig, amíg nem megyünk végig az összes elemen. Így habár az adat méretét csökkentettük, ha műveleteket is végzünk rajta, akkor a számítás jóval hosszabb is lehet, mint a fix karakterhosszú ASCII-n.
Erre megoldás az UTF-16 és UTF-32, amik fixen 16 és 32 biten ábrázolnak karaktereket. Az UTF-16 több mint kétezer karaktere elég arra, hogy lefedje az összes használt ábécét a földön, de arra nem hogy tartalmazza az emoji-kat, a holt nyelvek betűit, vagy olyan nem ábécét használó írásmódok minden jelét, mit például a kínai. Ezek csak a legbővebb UTF-64 kódolással érhetők el, ami fixem 4 byte-os.
Kódolás | ASCII | UTF-8 | UTF-32 | UTF-64 |
Karakterek száma | 128 | 1 114 237 | 2 047 | 1 114 237 |
Kompaktság | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐ |
Fix karakterhossz | ✅ | ⛔ | ✅ | ✅ |
Műveletek sebessége gyors | ✅ | ⛔ | ✅ | ✅ |
Kompatibilis visszafelé ⬅️ | ✅ | ✅ | ⛔ | ⛔ |
A 32 bit feletti tartományban már minden bizonnyal meg fogjuk találni a keresett igen speciális betűket. Erre a keresésre ideális lehet például a compart.com.
Pl. Székely-Magyar rovásírás: 10C80 - 10CFF
Pl. Egyiptomi hieroglifák: 13000 - 1342F
Betűtípusok
Ha van egy karaktertáblánk (Pl.: Unicode) és hozzá egy kódolásunk (Pl.: UTF-8), akkor egy szöveget bármikor tudunk bájtsorozatba kódolni és abból vissza szöveggé, ám ez nem azt jelenti, hogy meg is tudjuk jeleníteni a szöveget, ehhez még egy táblára is szükségünk van, ami megmondja, hogy egyes karaktereket / kódpontokat hogyan rajzoljunk ki. Erre használatosak a betűtípus vagy font fájlok, amik .ttf vagy .otf formátumúak.
Ezek beszerezhetők sok forrából a neten. Javaslom erre a Google Fonts, vagy a daFont oldalakat.
Itt meg kell jegyezni, hogy a font-ok is digitális szellemi tartamak. Csak akkor használjatok egy betűtípust, ha meggyőződtetek, arról, hogy azt joggal teszitek. Ezért kényelmes a Google Fonts használata, hiszen ott garantáltan minden font jogdíjmentes.
Itt kell figyelni arra, hogy a Font karakterkészlete tartalmazza is a szükséges betűket, amiket használni szeretnénk. Ez az erősen ékezethasználó magyar nyelvnél kifejezetten idegesítő tud lenni, hisz nem minden első sorban angol szövegre optimalizált betűtípus támogatja a mi karaktereinket: Ki ne látott volna már olyan nem “hobbi grafikus” által készült hírdetést, kiírást, ahol a csilli-vili betűtipusból rondán kilógnak az Arial Ű-k és Ő-k. Az “Árvíztűrő Tükörfúrógép” a legyakrabban használt rövid magyar szövek, amivel tesztelhető a betűtípus és a mi anyanyelvünk kompatibilitása.
Bár bosszantó lehet, mikor egy tökéletes betűtípus nem ideális a magyar nyelvhez, ez semmi ahhoz, amit az olyan igazán speciális betűkészletek, mint például bizonyos holt nyelvek írásmódjai igényelnek. Ahhoz, hogy olyan font-ot találjunk, ami támogatja a szükséges karaktereket, már nem elég a böngészés és próbálgatás. Érdemes konkrétan rákeresni, olyan betűtípusokra, amik kifejezetten az adott karakterszetthez készültek:
Székely-Magyar rovásírás: Releases · OldHungarian/old-hungarian-font (github.com)
Egyiptomi hieroglifák: Noto Sans Egyptian Hieroglyphs - Google Fonts
A Unity és a Font-ok
A Unity azonba nem képes magában kezelni a fontokat, a megismert .ttf és .otf formátumokban. Ehehz szükség van a TextMeshPro csomag telepítésére a Package Manageren keresztül és szükséges készíteni minden használni kívánt betűtípusból egy TextMeshPro FontAsset fájlt. Ez pár pillanat alatt elvégezhető a Font Asset Creator segítségével: (Window / TextMeshPro / FontAssetCreator)
Sajnos ezen a ponton sem fogunk tudni olyan könnyen boldogulni a hieroglifáinkkal, és rúnáinkkal, hiszen a Unity Editor felülete csak két byte-ig támogat karaktereket, 4 helyett. Szóval nem fogunk tudni ennyire sajátos szövegeket bevinni és szerkeszteni a Unity ablakában. Ezt a problémát saját kóddal fogjuk tudni áthidalni.
Tutorial
És most vére térjünk át a lépésenkénti leírása:
Speciális Font beszerzése
Egy általános internektkereséssel találd meg és töltsd le azt a font-ot, ami támogatja a szükséges karaktereket. Pl.:
Székely-Magyar rovásírás: Releases · OldHungarian/old-hungarian-font (github.com)
Egyiptomi hieroglifák: Noto Sans Egyptian Hieroglyphs - Google Fonts
Add hozzá a font fájlt a Unity projektedhez.
Unicode karaktertartomány megtalálása.
Keressük ki, hogy mi a megfelelő UTF hexadecimális tartomány mondjuk innen: compart.com
Pl. Székely-Magyar rovásírás: 10C80 - 10CFF
Pl. Egyiptomi hieroglifák: 13000 - 1342F
TextMeshPro Font Asset Készítése
(Ha még nem tettük ezt meg, akkor mindenek előtt telepítsük a TextMeshPro csomagot a PackageManager-ben.)
Készítsük el a TextMeshPro Font Asset-et.: Window / TextMeshPro / FontassetCreator
- Készítsünk szimplán font Asset-et a fontból alapértelmezett beállításokkal és mentsük el.
- Készítsük ezt el újra, a következő beállításokkal
- SourceFontFile: Az eredeti font
- Character Set: Unicode Range (Hex)
- Select Font Asset: Adjuk meg a korábban elkészített
- Character sequences (Hex): Adjuk hozzá a megfelelő karakterkészlet kezdő és záró elemének UTF32 indexét hexadecimálisan kötőjellel elválasztva. Pl.: Egyiptomi hieroglifák: 13000 - 1342F. (Lásd fent)
- Ha szükséges, növeljük meg az Atlas Resolution-t.
- Írjuk felül a korábbi Font Asset-et (mentsünk rá)
Készen is lennénk azzal, hogy legyártsunk egy olyan Unity Font Asset-et, ami képes megjeleníteni a hieroglifákat. Ha kimásolunk valahonnan egy hieroglafikus szöveget és egy TextMeshPro komponenshez beillesztjük valamint kiválasztjuk a most elkészített font fájlt, akkor megjelenik a kívánt ősi szöveg a UI-on. Probléma, hogy ez az Editorban nem fog látszani, szóval nehezen fogjuk tudni szerkeszteni. Valamint előkerül a probléma, hogy hogyan viszünk be szövegeket a magyar/angol billenytűzetünkön, ami a kiválasztott speciális írásrendszerünkben fog megjelenni.
Ehhez némi kódolásra lesz szükségünk:
Karaktertábla
Készítsünk egy “szótárt”, ami latin betűket vagy még általánosabban szövegeket alakít át UTF 32 karakteré. Ehhez használjunk C# generikus dictionary-t, aminek kulcsa egy string
: a megfelelő latin szöveg, amit cserélni szeretnénk és értéke pedig az egy int
: az indexe a kívánt Unitcode kódblokknak, amire cserélni fogunk.
Azért használunk int-et és nem chart-t, mert a C#-ban a char
nem képes UTF32 kódolású karaktert tárolni, csak UTF16-ot, hiszen az int 32, míg a char
csak 16 bites.
Bővebben: Egyéb adatszerkezetek, Típusok mérete C#-ban
Itt elkezdtem felvenni két példát:
Dictionary<string, int>
oldEgyptianHieroglyphs =
new Dictionary<string, int>
{
{"a", 0x1313F},
{"b", 0x130C0},
{"r", 0x1308B},
{"n", 0x1309C},
{"m", 0x13153},
//...
};
Dictionary<string, int>
oldHungarianLetters =
new Dictionary<string, int>
{
{"a", 0x10C80},
{"b", 0x10C82},
{"g", 0x10C84},
//...
};
Konvertáló függvény
Írjunk konvertáló függvényt, ami tetszőleges szöveget átalakít úgy, hogy kicseréli a korábban definiált “szótár” latin betűs elemeit, a speciális karakterekre.
A konvertáláshoz a string char.ConvertFromUtf32(int utfValue);
függvényt fogom használni.
static string ConvertText(string text, Dictionary<string, int> characterMap)
{
string convertedText = text;
foreach ((string originalChar, int utfValue) in characterMap)
{
string specialChar = char.ConvertFromUtf32(utfValue);
convertedText = convertedText.Replace(originalChar, specialChar);
string originalCharUpper = originalChar.ToUpper();
convertedText = convertedText.Replace(originalCharUpper, specialChar);
};
return convertedText;
}
Teljes MonoBehaviour szkript
…, ami automatikusan felülírja TextMeshPro Text elemet mezőjét:
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class TextConverter : MonoBehaviour
{
[SerializeField] TMP_Text textMesh;
[SerializeField] CharacterSets characterSet;
[SerializeField] string text;
enum CharacterSets { OldHungarian, OldEgyptianHieroglyphs }
Dictionary<string, int> oldHungarianLetters = new Dictionary<string, int>
{
{"a", 0x10C80},
{"b", 0x10C82},
{"g", 0x10C84},
//...
};
Dictionary<string, int> oldEgyptianHieroglyphs = new Dictionary<string, int>
{
{"a", 0x1313F},
{"b", 0x130C0},
{"r", 0x1308B},
{"n", 0x1309C},
{"m", 0x13153},
//...
};
Dictionary<string, int> GetCharacterSet(CharacterSets characterSet) =>
characterSet switch
{
CharacterSets.OldHungarian => oldHungarianLetters ,
CharacterSets.OldEgyptianHieroglyphs => oldEgyptianHieroglyphs,
_ => oldHungarianLetters ,
};
void OnValidate() => SetText(text);
public void SetText(string text)
{
this.text = text;
if (textMesh != null)
textMesh.text = ConvertText(text, GetCharacterSet(characterSet));
}
public static string ConvertText(string text, Dictionary<string, int> characterMap)
{
string convertedText = text;
foreach ((string originalChar, int utfValue) in characterMap)
{
string specialChar = char.ConvertFromUtf32(utfValue);
convertedText = convertedText.Replace(originalChar, specialChar);
string originalCharUpper = originalChar.ToUpper();
convertedText = convertedText.Replace(originalCharUpper, specialChar);
};
return convertedText;
}
}
Marosi Csaba
+36 20 359 74 22
Ha érdekel a kódolás, játékfejlesztés fontold meg a jelentkezést egyik tanfolyamomra→