Articles

Unity: Sådan oprettes 2D Tilemap programmatisk

til video tutorial Klik her

Hvis du har brug for at oprette forskellige Tilemaps i forskellige størrelser tjek den anden del af denne tutorial

Tilemap komponent blev introduceret i Unity 2017.2 og væsentligt lettet 2D spil udviklingsprocessen. Med den version 2018.3 isometrisk Tilemap blev indført yde stor støtte til 2.5 D spil. For nylig havde jeg mulighed for at arbejde tæt sammen med denne komponent og blev udfordret med opgaven at skabe fliser programmatisk. I mit sidste spil har jeg mange niveauer, og alle har det samme Tilemap-baserede bord. Men bestyrelsen selv har niveau-unik opsætning. Det er klart, at jeg som professionel udvikler ikke ønskede at oprette 60 spilscener og male alle niveauer for hånd, men snarere have en mekanisme til at fylde brættet med de rigtige elementer afhængigt af det givne input. Hvis du er nysgerrig, hvordan slutresultatet ser ud her er link til spillet.

kildekoden er tilgængelig på GitHub, find venligst linket i slutningen af denne vejledning.

Jeg bruger den seneste tilgængelige enhed 2019.2.0.1 f. For at arbejde med denne tutorial skal du have mindst version 2018.3. Vi vil bruge isometrisk gitter, men beskrevet teknik gælder for enhver type.

før jeg starter, foreslår jeg stærkt at læse gennem dette strålende isometriske 2D-miljøer med Tilemap-blogindlæg for at få en grundlæggende forståelse af isometrisk Tilemap.

da vi arbejder i et 2D-miljø, skal du oprette et nyt 2d-projekt (se 2DAnd3DModeSettings)

Der er 3 hovedkomponenter, der kræves for at nå vores mål: et spil bord, hvor spillet vil finde sted, et sted, hvor man kan holde et niveau beskrivelse og nogle kode, der forbinder den ene til den anden.

Del 1. Oprettelse af bestyrelsen

lad os starte med at importere nødvendige billedaktiver. Jeg bruger de samme billeder, som jeg brugte til mit spil:

Used to create a base tilemap level

Used as a path

Used to mark the start and end points of a sti

Vi vil holde billeder i mappen fliser, blot trække og slippe dem der.

Tiles setup

bemærk! Du skal have en korrekt “billedstørrelse pr. enhed” indstillet pr. hvert flisebillede (for yderligere detaljer se isometriske 2D-miljøer med Tilemap). For disse billeder er værdien 1096.

Billedopsætning

det er en god praksis at adskille forskellige lag på en scene i dedikerede spilobjekter med beskrivende navne. Forestil dig den typiske spilskærm, der kan indeholde et spilområde, brugergrænseflade, annonceplaceringer og så videre.

efter denne tilgang lad os oprette et nyt tomt spilobjekt med navnetGameZone hvor brættet og alle spilelementer kunne placeres.

nu er det tid til at oprette faktiske fliser fra de billeder, vi importerede tidligere

tryk på Vindue -> 2D -> Flisepalette

flisepalette

flisepaletvisning åbnes. Klik på “Opret ny Palette” og opret en palette:

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.

FindGameZone i Hierarkivisning skal du højreklikke på venstre 2D-objekt isometrisk TileMap. Et nyt spilobjekt Grid vil blive oprettet.

bestyrelsen størrelse vil være 11h11 fliser, og vi kan begynde at male. Vælg boksen børste værktøj (4.element fra venstre) i visningen “Tile Palette”, og vælg derefter “clean” flise. Mal det i Scenevisningen.

Board

når du er færdig med maleriet, skal du manuelt komprimere TileMap grænser. Til dette skal du vælge Tilemap i Hierarkivisningen, trykke på en Indstillinger (gear) – knap på TileMap-komponenten og vælge “Komprimer TileMap Bounds”

iv

Komprimer TileMap Bounds

godt klaret, spillebrættet er klar til brug! Vi vender tilbage til det i Del 3.

Del 2. Niveaudata

da vi ønsker at genbruge den samme scene og det samme spilbræt, skal niveaudataene opbevares et eller andet sted. En enkel løsning til det er en simpel json-fil, der beskriver, hvordan hvert niveau skal bygges.

det er vigtigt at forstå, hvad vi forsøger at opnå, derfor må jeg sige et par ord om mekanikken. I spillet er der objekter, der bevæger sig langs stien fra start til slut (stort set som i Yuma), og spillerens mål er at ødelægge dem alle. I denne vejledning opretter vi denne sti, som vil være unik for hvert niveau.

Okay, tilbage til projektet.

der er flere måder, hvordan du får adgang til eksterne data i en runtime fra et script. Her bruger vi Ressourcemapper

lad os oprette en ny mappe — filer — og en Undermapperessourcer. Det er det sted, vi ønsker at holde data, så oprette en ny fil — niveauer.json og læg den der.

til vejledningsformål har vi kun to felter til at beskrive hvert niveau:

  • nummer — en int til at identificere et niveau
  • sti — den flisebaserede sti, som vi vil oprette programmatisk. Array af værdier, hvor første værdi er startpunktet, og den sidste værdi er slutpunktet.

Dette er den fil, jeg vil bruge. Du skal ikke bekymre dig om disse værdier i sti, vi kommer til det senere.

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

lad os oprette en anden mappe — Scripts — og nu starter kodningen endelig.

vi vil have adgang til dataene fra filen i koden, derfor har vi brug for en modelklasse til den. Det er på tide at oprette vores allerførste script — LevelsData. Det er ikke meningen at blive instantieret af Unity, så MonoBehaviourog StartUpdate metoder skal fjernes. Fra filen ovenfor kan vi se, at rodelementet er en array af niveauer, hvor hvert niveau skal have en int feltnummer og en int array feltsti. Glem heller ikke at sætte annotation.


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

Nice, nu har vi filen og modellen. Næste skridt er at omdanne den ene til den anden. Lad os oprette et andet script – GameZone — og vedhæft det til GameZone objekt på scenen. Dette script vil blive brugt senere til opsætning af hele spillepladen.

følg princippet om enkelt ansvar lad os oprette endnu et script — LevelsDataLoader — det vil gøre al transformation. Vedhæft det til GameZone objektet også.

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);
}
}

denne klasse indlæser dataene og returnerer dem som en ordbog, hvor nøglen er niveaunummeret, og dataene er selve niveaudataene.

nu skal vi kunne få adgang til dataene iGameZone 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!");
}
}

Skift tilbage til enhed, tryk på afspilningsknappen og kontroller konsollen — du skal se meddelelsen “3 niveauer er blevet gemt i ordbogen!”

Del 3. Tilslutning af tavlen og dataene

Tillykke, du har nået den sidste og mest interessante del af denne tutorial. Hvordan vil vi faktisk forbinde bestyrelsen og dataene? Fortsæt med at læse for at finde ud af det!

lad os først placerehorizontal ogstart_stop fliser oprettet i den første del af tutorial i mappen ressourcer under mappen fliser. Tilføj derefter et nyt script — TilesResourcesLoader — en statisk hjælperklasse til at indlæse fliser fra den mappe i en runtime.

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));
}
}

som det sidste trin skal vi placere disse fliser på tavlen ved scenestart. Lad os gå tilbage til GameZone script. Først og fremmest skal vi simulere niveauvalget, i det rigtige spil sker det normalt, når en bruger trykker på en niveauknap. For enkelhedens skyld lad os tilføje et offentligt feltniveau til GameZone og ændre den værdi til 1 For start. Jeg vil vise dig det endelige script først:

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);
}
}

det er en masse handling! Lad mig gå dig igennem det.

i SetupTiles – metoden skal vi først få tilemap selv, fordi vi skal kende placeringen af fliser for at ændre det. For at opnå dette bruger vi tilemap.cellBounds.allPositionsWithin metode, der returnerer alle positioner af fliser startende fra den allerførste — i den givne konfiguration er det en ned-mest flise.

se følgende billede, hvor hvert tal repræsenterer indekset ilocalTilesPositions listen.

nummereret tilemap

kan du huske de værdier, vi bruger i stien i Levels.json? Som du måske allerede har gættet, er disse værdier indekserne for fliserne i arrayet. Alt du skal gøre nu er at have en cheatsheet billede til at hjælpe dig konstruere niveauer. Det er den, jeg har brugt under udviklingen:

min grimme nummererede tilemap

i dette eksempel opretter vi en vandret linje i stien, se venligst SetupPath metode. Nøgledelen er følgende sløjfe:

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

Her gentager vi localTilesPositions for at finde dem til at indstille den ønskede flise — vandret i dette tilfælde.

Bemærk! GetRange metoden har to parametre — indeks og tæller.

for at markere sti start-og slutpositioner anvendesstart_stop flise.

Her er resultatet af vores hårde arbejde:

slutresultat

prøv nu at ændre niveaunummerfeltet for GameZone script fra 1 til 2 eller 3, og du vil se, at stien er indlæst korrekt for det givne niveau.

bagefter