Articles

Unity: Hogyan hozzunk létre 2D Tilemap programozottan

A Video tutorial kattintson ide

Ha létre kell hozni a különböző Tilemaps különböző méretű nézd meg a második része ennek a tutorial

Tilemap komponens került bevezetésre Unity 2017.2 és jelentősen megkönnyítette a 2D játék fejlesztési folyamat. A 2018.3-as verzióval izometrikus Tilemap került bevezetésre, amely nagy támogatást nyújt a 2.5 D játékokhoz. Nemrégiben lehetőségem nyílt arra, hogy szorosan együttműködjek ezzel az összetevővel, és kihívást jelentett a csempék programozási létrehozásának feladata. Az utolsó játékomban sok szintem van, és mindegyiknek ugyanaz a Tilemap-alapú táblája van. De maga a tábla szint-egyedi beállítással rendelkezik. Nyilvánvaló, hogy profi fejlesztőként nem akartam 60 játékjelenetet létrehozni és kézzel festeni az összes szintet, hanem egy mechanizmussal rendelkeztem, amely az adott bemenettől függően kitölti a táblát a megfelelő elemekkel. Ha kíváncsi, hogy a végeredmény úgy néz ki, mint itt link a játék.

A forráskód elérhető a GitHub-on, kérjük, keresse meg a linket az oktatóanyag végén.

a legújabb elérhető Unity 2019.2.0.1 f-et használom. Annak érdekében, hogy ezzel a bemutatóval dolgozzon, legalább 2018.3-as verzióval kell rendelkeznie. Izometrikus rácsot fogunk használni, de a leírt technika bármilyen típusra alkalmazható.

megkezdése előtt azt javasoljuk, hogy olvassa el ezt a ragyogó izometrikus 2D környezetben Tilemap blogbejegyzést, hogy egy alapvető ismereteket izometrikus Tilemap.

mivel 2D környezetben dolgozunk, be kell állítania egy új 2D projektet (lásd: 2dand3dmodesettings)

3 fő összetevő szükséges a célunk eléréséhez: a játéktábla, ahol a játék kerül sor, egy hely, ahol tartani egy szinten leírás és néhány kódot, amely összeköti az egyik a másikra.

1.rész. A tábla létrehozása

kezdjük a szükséges Képeszközök importálásával. Ugyanazokat a képeket fogom használni, mint a játékomhoz:

Used to create a base tilemap level

Used as a path

Used to mark the start and end points of a útvonal

a képeket a csempék mappában tartjuk, egyszerűen húzza oda őket.

csempék beállítása

megjegyzés! Meg kell egy helyes “Pixel egységenként” méret beállítása minden egyes csempe kép (további részletekért lásd izometrikus 2D környezetek Tilemap). Ezeknél a képeknél az érték 1096.

képbeállítás

jó gyakorlat, ha a jelenet különböző rétegeit leíró nevekkel ellátott dedikált játékobjektumokra különítjük el. Képzelje el a tipikus játékképernyőt, amely tartalmazhat játékzónát, felhasználói felületet, hirdetéselhelyezéseket stb.

ezt a megközelítést követve hozzunk létre egy új üres játékobjektumot, melynek neve GameZone ahol a tábla és az összes játékelem elhelyezhető.

most itt az ideje, hogy hozzon létre tényleges csempe a képeket importáltunk korábban

nyomja meg a Window -> 2D -> csempe paletta

csempe paletta

csempe paletta nézet nyílik meg. Kattintson az “új paletta létrehozása” gombra, és hozzon létre egy palettát:

New Palette

Drag and drop tile images one by one to create an actual tiles.

Tiles setup

With those tiles we can finally create the game board which will be filled with elements programmatically on a level startup later.

keresse meg aGameZone a hierarchia nézetben kattintson a jobb egérgombbal a 2D objektumra, az izometrikus Tilemapre. Új játékobjektum jön létre Grid.

a tábla mérete 11×11 csempe lesz, és elkezdhetjük a festést. Válassza ki a Box Brush eszközt (balról 4.elem) a “Tile Palette” nézetben, majd válassza a “clean” tile lehetőséget. Festse a jelenet nézetben.

tábla

miután végzett a festéssel, manuálisan tömörítse a TileMap határokat. Ehhez ki kell választania a TileMap-ot a hierarchia nézetben, nyomja meg a Beállítások (fogaskerék) gombot a Tilemap-összetevőn, majd válassza a “Tilemap-határok tömörítése”

tömörítse a Tilemap határokat

jól sikerült, a játéktábla készen áll a használatra! Erre a 3. részben térünk vissza.

2.rész. Szintadatok

mivel ugyanazt a jelenetet és ugyanazt a játéktáblát akarjuk újra felhasználni, a szintadatokat valahol meg kell őrizni. Egy egyszerű megoldás, hogy egy egyszerű json fájl, amely leírja, hogy minden szinten kell építeni.

fontos megérteni, hogy mit próbálunk elérni, ezért kell néhány szót mondanom a mechanikáról. A játékban vannak olyan tárgyak, amelyek az út mentén mozognak a kezdetektől a végéig (nagyjából úgy, mint a Zuma-ban), és a játékos célja, hogy elpusztítsa mindet. Ebben az oktatóanyagban létrehozzuk ezt az utat, amely minden szinten egyedi lesz.

Oké, vissza a projekthez.

számos módja van, hogyan lehet hozzáférni egy külső adatok egy futásidejű egy script. Itt erőforrások mappákat fogunk használni

hozzunk létre egy új mappát — Files — és egy al-mappa Resources. Ez az a hely, ahol meg akarjuk tartani az adatokat, ezért hozzon létre egy új fájlt-szintek.json és tegye oda.

az oktatói célokra csak két mező lesz az egyes szintek leírására:

  • szám – egy int egy szint azonosítására
  • útvonal — a csempe alapú útvonal, amelyet programozottan akarunk létrehozni. Olyan értékek tömbje, ahol az első érték a kezdőpont, az utolsó pedig a végpont.

ezt a fájlt fogom használni. Ne aggódj ezen értékek path, mi jön rá később.

{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}

hozzunk létre egy másik mappát — szkripteket—, és most végre elindul a kódolás.

az adatokat a kódban lévő fájlból szeretnénk elérni, ezért szükségünk van egy modellosztályra. Itt az ideje, hogy elkészítsük az első szkriptünket — LevelsData. Ez nem azt jelentette, hogy példányosítani Unity, így MonoBehaviour és StartUpdate módszerek el kell távolítani. A fenti fájlból láthatjuk, hogy a gyökér elem egy array szintek, ahol minden szintnek rendelkeznie kell egy int mezőszámmal és egy int array mező elérési útjával. Ne felejtsd el feltenni a megjegyzést.


public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}

szép, most már megvan a fájl és a modell. A következő lépés az, hogy átalakítsuk egymást. Hozzunk létre egy másik szkriptet – GameZone — és csatoljuk a jelenet GameZone objektumához. Ezt a szkriptet később felhasználják az egész játéktábla beállításához.

kövesse az egyetlen felelősség elvét hozzunk létre még egy szkriptet — LevelsDataLoader — amely elvégzi az összes átalakítást. Csatolja a GameZone objektumhoz is.

public class LevelsDataLoader : MonoBehaviour
{
private const string LevelsPath = "Levels";
public Dictionary<int, LevelsData.LevelData> ReadLevelsData()
{
var jsonFile = Resources.Load(LevelsPath, typeof(TextAsset)) as TextAsset;
if (jsonFile == null)
{
throw new ApplicationException("Levels file is not accessible");
}
var loadedData = JsonUtility.FromJson<LevelsData>(jsonFile.text);
return loadedData.levels.ToDictionary(level => level.number, level => level);
}
}

Ez az osztály betölti az adatokat, és szótárként adja vissza, ahol a kulcs a szintszám, az adatok pedig maga a szintadatok.

most már elérhetjük az adatokat aGameZone szkriptben.

public class GameZone : MonoBehaviour
{
private Dictionary<int, LevelsData.LevelData> _levelsData;
private LevelsDataLoader _dataLoader;private void Awake()
{
_dataLoader = GetComponent<LevelsDataLoader>();
}private void Start()
{
_levelsData = _dataLoader.ReadLevelsData();Debug.Log(_levelsData.Count + " levels have been stored in the dictionary!");
}
}

váltson vissza a Unity — ra, nyomja meg a play gombot és ellenőrizze a konzolt-látnia kell a “3 szint van tárolva a szótárban!”

3.rész. A tábla és az adatok összekapcsolása

Gratulálunk, elérted az oktatóanyag utolsó és legérdekesebb részét. Hogyan kapcsoljuk össze a táblát és az adatokat? Olvass tovább, hogy megtudd!

először is helyezzük el a horizontalés start_stop az oktatóanyag első részében létrehozott csempéket az erőforrások mappába a csempe mappa alatt. Ezután adjon hozzá egy új szkriptet – TilesResourcesLoader — egy statikus segítő osztály, amely futásidejű csempéket tölt be a mappából.

public static class TilesResourcesLoader
{
private const string PathHorizontal = "horizontal";
private const string StartStop = "start_stop"; public static Tile GetPathHorizontalTile()
{
return GetTileByName(PathHorizontal);
} public static Tile GetStartStopTile()
{
return GetTileByName(StartStop);
} private static Tile GetTileByName(string name)
{
return (Tile) Resources.Load(name, typeof(Tile));
}
}

utolsó lépésként ezeket a csempéket a táblára kell helyezni a jelenet indításakor. Térjünk vissza a GameZone szkripthez. Először is szimulálnunk kell a szintválasztást, az igazi játékban ez általában akkor történik, amikor a felhasználó megnyom egy szintgombot. Az egyszerűség kedvéért adjunk hozzá egy nyilvános mezőszintet a GameZone – hez, és változtassuk meg az értékét 1-re a kezdéshez. Először megmutatom a végső szkriptet:

public class GameZone : MonoBehaviour
{
public int Level; private const int FieldLineSize = 11;
private const int FieldTotalTiles = FieldLineSize * FieldLineSize;
private Dictionary<int, LevelsData.LevelData> _levelsData; private void Start()
{
_levelsData = GetComponent<LevelsDataLoader>().ReadLevelsData();
SetupTiles();
} private void SetupTiles()
{
var baseLevel = GetComponentsInChildren<Tilemap>(); var localTilesPositions = new List<Vector3Int>(FieldTotalTiles);
foreach (var pos in baseLevel.cellBounds.allPositionsWithin)
{
Vector3Int localPlace = new Vector3Int(pos.x, pos.y, pos.z);
localTilesPositions.Add(localPlace);
} SetupPath(localTilesPositions, baseLevel);
} private void SetupPath(List<Vector3Int> localTilesPositions, Tilemap baseLevel)
{
var path = _levelsData.path;
var pathHorizontalTile = TilesResourcesLoader.GetPathHorizontalTile();
var first = path.First();
var last = path.Last();
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
} var startStopTile = TilesResourcesLoader.GetStartStopTile();
baseLevel.SetTile(localTilesPositions, startStopTile);
baseLevel.SetTile(localTilesPositions, startStopTile);
}
}

Wow, ez nagyon sok akció! Hadd mutassam meg.

a SetupTiles módszer először meg kell kap a tilemap magát, mert meg kell tudni, hogy a pozíciók csempe annak érdekében, hogy változtatni. Ennek eléréséhez a tilemap.cellBounds.allPositionsWithin metódust használjuk, amely visszaadja a csempék összes helyzetét az elsőtől kezdve — az adott konfigurációban ez egy lefelé mutató csempe.

lásd a következő képet, ahol minden szám az indexet képviseli alocalTilesPositions listában.

számozott tilemap

emlékszel az elérési úton használt értékekre Levels.json? Mint talán már kitalálta, ezek az értékek a tömbben lévő csempék indexei. Most csak annyit kell tennie, hogy van egy cheatsheet kép, amely segít a szintek felépítésében. Ezt használtam a fejlesztés során:

a csúnya számozott tilemap

ebben a példában vízszintes vonalat állítunk be az útvonalon, kérjük, olvassa el a SetupPath módszert. A legfontosabb rész a következő hurok:

foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}

itt ismétlődünk localTilesPositions hogy megtaláljuk azokat, amelyek ebben az esetben a kívánt csempe vízszintes beállítását teszik lehetővé.

Megjegyzés! GetRange a metódusnak két paramétere van: index és count.

az útvonal kezdő és végpozícióinak megjelöléséhez a start_stop csempét használjuk.

itt van a kemény munkánk eredménye:

>

végeredmény

most próbálja meg megváltoztatni a GameZone szkript szintszám mezőjét 1-ről 2-re vagy 3-ra, és látni fogja, hogy az elérési út megfelelően van betöltve az adott szinthez.

utána