Unity: programmatisch maken van 2D Tilemap
Voor video tutorial Klik hier
Als u verschillende Tilemaps van verschillende groottes moet maken, bekijk dan het tweede deel van deze tutorial
Tilemap component werd geà ntroduceerd in Unity 2017.2 en heeft het 2D spelontwikkelingsproces aanzienlijk vergemakkelijkt. Met de versie 2018.3 werd isometrische Tilemap geïntroduceerd die grote ondersteuning biedt voor 2.5 D-games. Onlangs kreeg ik de kans om met dit onderdeel nauw samen te werken en werd uitgedaagd met de taak om programmatisch tegels te maken. In mijn laatste spel heb ik veel levels en ze hebben allemaal hetzelfde Tilemap-gebaseerde bord. Maar het bord zelf heeft niveau-unieke setup. Uiteraard, als een professionele ontwikkelaar wilde ik niet om 60 game scènes te maken en schilderen alle niveaus met de hand, maar eerder een mechanisme om het bord te vullen met de juiste elementen, afhankelijk van de gegeven input. Als je nieuwsgierig bent hoe het eindresultaat eruit ziet hier is link naar het spel.
broncode is beschikbaar op GitHub, vind de link aan het einde van deze tutorial.
Ik gebruik de laatst beschikbare Unity 2019.2.0.1 f. Om te werken met deze tutorial moet u ten minste versie 2018.3. We zullen isometrisch raster gebruiken, maar de beschreven techniek is van toepassing op elk type.
voordat u begint, raad ik ten zeerste aan om deze briljante isometrische 2D-omgevingen te lezen met Tilemap-blog om een basiskennis van isometrische Tilemap te krijgen.
omdat we in een 2D-omgeving werken, moet u een nieuw 2D-project opzetten (refereer naar 2DAnd3DModeSettings)
Er zijn 3 hoofdcomponenten nodig om ons doel te bereiken: een spelbord waar het spel zal plaatsvinden, een plaats waar een niveau beschrijving en een aantal code die de ene verbindt met de andere te houden.
deel 1. Het Board
aanmaken laten we beginnen met het importeren van de benodigde image assets. Ik zal dezelfde beelden gebruiken die ik voor mijn spel gebruikte:
We houden afbeeldingen in de map Tiles, slepen ze daar gewoon neer.
note! U moet een juiste “Pixel Per eenheid” grootte ingesteld per elke tegel afbeelding (voor meer details zie isometrische 2D-omgevingen met Tilemap). Voor deze afbeeldingen is de waarde 1096.
het is een goede gewoonte om verschillende lagen op een scène te scheiden in specifieke spelobjecten met beschrijvende namen. Stel je het typische spelscherm voor dat een Gamezone, gebruikersinterface, advertentieplaatsingen en ga zo maar door kan bevatten.
volgens deze aanpak maken we een nieuw leeg spelobject genaamd GameZone
waar het bord en alle spelelementen kunnen worden geplaatst.
Nu is het tijd voor het maken van de werkelijke tegels van de beelden die we eerder ingevoerde
Druk op Window -> 2D> Tegel Palet
de Tegel Palet weergeven wordt geopend. Klik op” Create new Palette ” (nieuw palet aanmaken) en maak een palet aan:
Drag and drop tile images one by one to create an actual tiles.
With those tiles we can finally create the game board which will be filled with elements programmatically on a level startup later.
zoek GameZone
in Hiërarchieweergave, klik met de rechtermuisknop op → 2D Object → isometrische Tilemap. Een nieuw spelobject Grid
zal worden aangemaakt.
De grootte van het bord zal 11×11 tegels zijn en we kunnen beginnen met schilderen. Selecteer het kader penseel Gereedschap (4e element van links) in de” tegel palet “weergave, selecteer vervolgens” schoon ” tegel. Schilder het in de Scèneweergave.
goed gedaan, het spelbord is klaar om te worden gebruikt! We komen er in deel 3 op terug.
deel 2. Level data
omdat we dezelfde scene en hetzelfde spelbord willen hergebruiken, moeten de level data ergens bewaard worden. Een eenvoudige oplossing voor dat is een eenvoudig json-bestand dat zal beschrijven hoe elk niveau moet worden gebouwd.
Het is belangrijk om te begrijpen wat we proberen te bereiken, daarom moet ik een paar woorden zeggen over de mechanica. In het spel zijn er objecten die langs het pad van het begin tot het einde bewegen (vrijwel zoals in Zuma) en het doel van de speler is om ze allemaal te vernietigen. In deze tutorial zullen we dit pad maken dat uniek is voor elk niveau.
OK, terug naar het project.
Er zijn meerdere manieren om toegang te krijgen tot externe gegevens in een runtime vanuit een script. Hier gebruiken we Resources mappen
laten we een nieuwe map — bestanden — en een submap Resources maken. Dat is de plaats waar we de gegevens willen bewaren, dus maak een nieuw bestand-Levels.JSON en plaats het daar.
voor de tutorial doeleinden hebben we slechts twee velden om elk niveau te beschrijven:
- getal — een int om een niveau
- pad te identificeren-het op tegels gebaseerde pad dat we programmatisch willen maken. Array van waarden waarbij de eerste waarde het beginpunt is en de laatste waarde het eindpunt.
Dit is het bestand dat ik zal gebruiken. Maak je geen zorgen over die waarden in path, we komen er later op terug.
{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}
laten we een andere map maken — Scripts — en nu begint de codering eindelijk.
we willen toegang krijgen tot de gegevens uit het bestand in de code, daarom hebben we er een modelklasse voor nodig. Het is tijd om ons allereerste script aan te maken — LevelsData
. Het is niet bedoeld om te worden geïnstitutionaliseerd door Unity, dus MonoBehaviour
en Start
Update
methoden moeten worden verwijderd. Uit het bovenstaande bestand kunnen we zien dat het root element een array
van niveaus is waarbij elk niveau één int
veldnummer en één int array
veldpad zou moeten hebben. Vergeet ook niet om annotatie te plaatsen.
public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}
Nice, nu hebben we het bestand en het model. De volgende stap is om de ene in de andere te transformeren. Laten we een ander script maken — GameZone
— en het koppelen aan het GameZone
object op de scène. Dit script zal later gebruikt worden om het hele spelbord op te zetten.
volg Single responsibility principe Laten we nog een ander script maken — LevelsDataLoader
— dat alle transformatie zal doen. Voeg het ook toe aan het GameZone
object.
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);
}
}
Deze klasse zal de gegevens laden en retourneren als een woordenboek waar de sleutel het niveau nummer is en de gegevens de niveau gegevens zelf.
nu zouden we toegang moeten hebben tot de gegevens in GameZone
script.
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!");
}
}
schakel terug naar Unity, druk op de afspeelknop en controleer de console — u zou het bericht ” 3 niveaus zijn opgeslagen in het woordenboek!”
Part 3. Het verbinden van het Forum en de Data
Gefeliciteerd, je hebt het laatste en meest interessante deel van deze tutorial bereikt. Hoe verbinden we het bord met de data? Blijf lezen om het uit te vinden!
allereerst plaatsen we horizontal
en start_stop
tiles aangemaakt in het eerste deel van de tutorial in de map hulpbronnen onder de map tegels. Voeg dan een nieuw script toe – TilesResourcesLoader
— een statische helperklasse om tegels uit die map in een runtime te laden.
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));
}
}
als laatste stap zouden we deze tiles op het bord moeten plaatsen bij het opstarten van de scene. Laten we teruggaan naar het GameZone
script. Allereerst moeten we de niveauselectie simuleren, in het echte spel gebeurt het meestal wanneer een gebruiker op een niveauknop drukt. Laten we omwille van de eenvoud een publiek veldniveau toevoegen aan GameZone
en deze waarde wijzigen in 1 Voor start. Ik zal je eerst het laatste script laten zien:
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, dat is veel actie! Laat me je er doorheen leiden.
in de SetupTiles
methode zouden we eerst de tilemap zelf moeten krijgen omdat we de posities van tiles moeten weten om het te veranderen. Om dit te bereiken gebruiken we de tilemap.cellBounds.allPositionsWithin
methode die alle posities van tiles retourneert vanaf de allereerste — in de gegeven configuratie is het een down-most tile.
refereer naar de volgende afbeelding waar elk getal de index vertegenwoordigt in de localTilesPositions
lijst.
herinnert u zich de waarden die we gebruiken in het pad inLevels.json
? Zoals je misschien al geraden hebt zijn deze waarden de indexen van de tiles in de array. Alles wat je nu hoeft te doen is om een cheatsheet afbeelding om u te helpen de bouw van niveaus. Die heb ik gebruikt tijdens de ontwikkeling.:
In dit voorbeeld maken we een horizontale lijn in het pad, raadpleeg dan de SetupPath
methode. Het belangrijkste deel is de volgende lus:
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}
Hier herhalen we localTilesPositions
om degenen te vinden die de gewenste tile — horizontaal in dit geval instellen.
opmerking! GetRange
methode heeft twee parameters — index en aantal.
om de begin-en eindposities van het pad te markeren wordt de tegel start_stop
gebruikt.
Hier is het resultaat van ons harde werk:
Nu proberen om het niveau te veranderen veld nummer van de GameZone
script van 1 naar 2 of 3 en je zal zien dat het pad correct is geladen voor het gegeven niveau.