Unity: Come creare Tilemap 2D programmaticamente
Per video tutorial clicca qui
Se avete bisogno di creare Tilemap diverse di varie dimensioni controllare la seconda parte di questo tutorial
componente Tilemap è stato introdotto in Unity 2017.2 e significativamente facilitato il processo di sviluppo del gioco 2D. Con la versione 2018.3 è stata introdotta la Tilemap isometrica che fornisce un ottimo supporto per i giochi 2.5 D. Recentemente ho avuto l’opportunità di lavorare a stretto contatto con questo componente e sono stato sfidato con il compito di creare piastrelle a livello di codice. Nel mio ultimo gioco ho un sacco di livelli e tutti hanno la stessa scheda Tilemap-based. Ma la scheda stessa ha una configurazione unica di livello. Ovviamente, essendo uno sviluppatore professionista non volevo creare 60 scene di gioco e dipingere tutti i livelli a mano, ma piuttosto avere un meccanismo per riempire la scheda con gli elementi appropriati a seconda dell’input dato. Se siete curiosi di sapere come il risultato finale si presenta come qui è link al gioco.
Il codice sorgente è disponibile su GitHub, per favore, trova il link alla fine di questo tutorial.
Io uso l’ultima unità disponibile 2019.2.0.1 f. Per poter lavorare con questo tutorial si dovrebbe avere almeno la versione 2018.3. Useremo griglia isometrica ma tecnica descritta è applicabile a qualsiasi tipo.
Prima di iniziare, suggerisco vivamente di leggere questi brillanti ambienti 2D isometrici con il blog Tilemap per ottenere una comprensione di base della Tilemap isometrica.
Poiché stiamo lavorando in un ambiente 2D è necessario impostare un nuovo progetto 2D (fare riferimento a 2DAnd3DModeSettings)
Ci sono 3 componenti principali necessari per raggiungere il nostro obiettivo: un tavolo da gioco in cui si svolgerà il gioco, un luogo dove mantenere una descrizione del livello e un codice che si collega l’uno all’altro.
Parte 1. Creazione della scheda
Iniziamo importando le risorse immagine necessarie. Userò le stesse immagini che ho usato per il mio gioco:
Manterremo le immagini nella cartella Tiles, semplicemente trascinandole lì.
Nota! Dovresti avere una dimensione corretta” Pixel per unità ” impostata per ogni immagine a riquadri (per ulteriori dettagli vedi Ambienti 2D isometrici con Tilemap). Per quelle immagini il valore è 1096.
È una buona pratica per separare i diversi livelli su una scena dedicata al gioco di oggetti con nomi descrittivi. Immagina la tipica schermata di gioco che potrebbe contenere una zona di gioco, un’interfaccia utente, posizionamenti di annunci e così via.
Seguendo questo approccio creiamo un nuovo oggetto di gioco vuoto chiamatoGameZone
in cui è possibile posizionare il tabellone e tutti gli elementi di gioco.
Ora è il momento di creare delle vere e piastrelle dalle immagini che abbiamo importato in precedenza
Premere Finestra -> 2D -> Affianca Palette
Affianca Palette verrà aperta la vista. Fare clic su “Crea nuova tavolozza” e creare una tavolozza:
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.
Trova GameZone
Nella vista Gerarchia, fare clic con il tasto destro del mouse → Oggetto 2D → Tilemap isometrica. Verrà creato un nuovo oggetto di gioco Grid
.
Le dimensioni della scheda sarà 11×11 piastrelle e siamo in grado di iniziare a dipingere. Selezionare lo strumento Pennello Casella (4 ° elemento da sinistra) nella vista” Tile Palette”, quindi selezionare” clean ” tile. Dipingilo nella vista Scena.
Dopo aver finito con la pittura si dovrebbe comprimere manualmente tilemap limiti. Per questo è necessario selezionare Tilemap nella Gerarchia di visualizzazione, premere su impostazioni (l’ingranaggio) pulsante Tilemap componente e selezionare “Compress Tilemap Limiti”
Ben fatto, il gioco è pronto per essere utilizzato! Torneremo ad esso nella parte 3.
Parte 2. Dati di livello
Poiché vogliamo riutilizzare la stessa scena e lo stesso tabellone di gioco, i dati di livello dovrebbero essere conservati da qualche parte. Una soluzione semplice per questo è un semplice file json che descriverà come ogni livello dovrebbe essere costruito.
È importante capire cosa stiamo cercando di ottenere, ecco perché devo dire alcune parole sulla meccanica. Nel gioco ci sono oggetti che si muovono lungo il percorso dall’inizio alla fine (più o meno come in Zuma) e l’obiettivo del giocatore è quello di distruggere tutti loro. In questo tutorial creeremo questo percorso che sarà unico per ogni livello.
Ok, torniamo al progetto.
Esistono diversi modi per accedere a dati esterni in un runtime da uno script. Qui useremo Cartelle Risorse
Creiamo una nuova cartella — File — e una sottocartella Risorse. Questo è il posto che vogliamo mantenere i dati, in modo da creare un nuovo file — Livelli.json e posizionarlo lì.
Per gli scopi del tutorial avremo solo due campi per descrivere ogni livello:
- numero — un int per identificare un livello
- percorso — il percorso basato su tessere che vogliamo creare a livello di codice. Matrice di valori in cui il primo valore è il punto iniziale e l’ultimo valore è il punto finale.
Questo è il file che userò. Non preoccuparti di quei valori in path, ci arriveremo più tardi.
{
"levels":
},
{
"number": 2,
"path":
},
{
"number": 3,
"path":
}
]
}
Creiamo un’altra cartella — Script — e ora inizia finalmente la codifica.
Vogliamo accedere ai dati dal file nel codice, quindi abbiamo bisogno di una classe modello per questo. È tempo di creare il nostro primo script – LevelsData
. Non è pensato per essere istanziato da Unity, quindi i metodi MonoBehaviour
e Start
Update
dovrebbero essere rimossi. Dal file sopra possiamo vedere che l’elemento radice è un array
di livelli in cui ogni livello dovrebbe avere un int
numero di campo e un int array
percorso di campo. Inoltre, non dimenticare di inserire l’annotazione.
public class LevelsData
{
public LevelData levels;
public class LevelData
{
public int number;
public int path;
}
}
Bello, ora abbiamo il file e il modello. Il prossimo passo è trasformare l’uno in un altro. Creiamo un altro script- GameZone
— e collegarlo all’oggettoGameZone
sulla scena. Questo script verrà utilizzato in seguito per impostare l’intero tabellone di gioco.
Segui il principio di responsabilità singola creiamo un altro script — LevelsDataLoader
— che farà tutta la trasformazione. Collegalo anche all’oggettoGameZone
.
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);
}
}
Questa classe caricherà i dati e li restituirà come un dizionario in cui la chiave è il numero di livello e i dati sono i dati di livello stessi.
Ora dovremmo essere in grado di accedere ai dati nello script GameZone
.
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!");
}
}
Torna a Unity, premi il pulsante Play e controlla la console – dovresti vedere il messaggio ” 3 livelli sono stati memorizzati nel dizionario!”
Parte 3. Collegamento della Scheda e dei Dati
Complimenti, avete raggiunto l’ultima e più interessante parte di questo tutorial. Come collegheremo effettivamente la scheda e i dati? Continuate a leggere per scoprirlo!
Prima di tutto, mettiamo horizontal
e start_stop
piastrelle create nella prima parte del tutorial nella cartella Risorse sotto la cartella Piastrelle. Quindi aggiungere un nuovo script – TilesResourcesLoader
— una classe helper statica per caricare i riquadri da quella cartella in un 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));
}
}
Come ultimo passo dovremmo posizionare quelle tessere sulla scheda all’avvio della scena. Torniamo allo scriptGameZone
. Prima di tutto dobbiamo simulare la selezione del livello, nel gioco reale di solito accade ogni volta che un utente preme un pulsante di livello. Per semplicità aggiungiamo un livello di campo pubblico a GameZone
e cambiamo il valore a 1 per start. Ti mostrerò prima lo script finale:
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, è un sacco di azione! Lascia che ti spieghi.
Nel metodo SetupTiles
dovremmo prima ottenere la tilemap stessa perché abbiamo bisogno di conoscere le posizioni delle tessere per cambiarla. Per ottenere ciò stiamo usando il metodo tilemap.cellBounds.allPositionsWithin
che restituisce tutte le posizioni delle tessere a partire dal primo — nella configurazione data è una tessera più bassa.
Fare riferimento alla seguente immagine in cui ogni numero rappresenta l’indice nell’elencolocalTilesPositions
.
Ti ricordo i valori che noi usiamo nel percorso Levels.json
? Come avrai già intuito, quei valori sono gli indici delle tessere nell’array. Tutto quello che devi fare ora è avere un’immagine cheatsheet per aiutarti a costruire i livelli. Questo è quello che ho usato durante lo sviluppo:
In questo esempio abbiamo una linea orizzontale nel percorso, si prega di fare riferimento alla SetupPath
metodo. La parte fondamentale è il seguente ciclo:
foreach (var localPosition in localTilesPositions.GetRange(first, Math.Abs(first - last)))
{
baseLevel.SetTile(localPosition, pathHorizontalTile);
}
Qui iteriamo su localTilesPositions
per trovare quelli per impostare il tile — orizzontale desiderato in questo caso.
Nota! GetRange
metodo ha due parametri — index e count.
Per contrassegnare le posizioni di inizio e fine del percorso viene utilizzato il riquadro start_stop
.
Ecco il risultato del nostro lavoro:
Ora si tenta di modificare il livello di campo del numero di GameZone
script da 1 a 2 o 3 e vedrete che il percorso è stato caricato correttamente per un certo livello.