Advertisement
  1. Game Development
  2. Game Mechanics

Wie man Monster Loot Drops codiert

Scroll to top
Read Time: 16 min

() translation by (you can also view the original English article)

Ein gewöhnlicher Mechaniker in Action-Spielen ist, dass Feinde beim Tod einen Gegenstand oder eine Belohnung abgeben.  Der Charakter kann diese Beute sammeln, um einen Vorteil zu erlangen.  Es ist ein Mechaniker, der in vielen Spielen, wie RPGs, erwartet wird, da es dem Spieler einen Anreiz gibt, die Feinde loszuwerden - ebenso wie eine kleine Explosion von Endorphinen, wenn er herausfindet, was die unmittelbare Belohnung dafür ist. 

Es ist ein Mechaniker, der in vielen Spielen, wie RPGs, erwartet wird, da es dem Spieler einen Anreiz gibt, die Feinde loszuwerden - ebenso wie eine kleine Explosion von Endorphinen, wenn er herausfindet, was die unmittelbare Belohnung dafür ist. 

Die Beispiele, die ich benutze, um dies zu demonstrieren, wurden mit Construct 2 erstellt, einem HTML5-Tool zur Erstellung von Spielen, sind aber in keiner Weise spezifisch dafür.  Sie sollten in der Lage sein, die gleiche Mechanik zu implementieren, unabhängig von Ihrer Programmiersprache oder Ihrem Programmierwerkzeug.

Die Beispiele wurden in r167.2 erstellt und können in der kostenlosen Version der Software geöffnet und bearbeitet werden.  Sie können die neueste Version von Construct 2 hier herunterladen (seit ich diesen Artikel geschrieben habe, mindestens zwei neuere Versionen wurden veröffentlicht) und mit den Beispielen nach Belieben herumspielen.  Die Beispiel-CAPX-Quelldateien sind diesem Tutorial in der ZIP-Datei beigefügt.

Der grundlegende Mechaniker 

Nach dem Tod eines Feindes (wenn seine HP kleiner oder gleich Null ist) wird eine Funktion aufgerufen.  Die Rolle dieser Funktion besteht darin, zu bestimmen, ob ein Tropfen vorhanden ist oder nicht, und falls ja, welche Art von Tropfen es sein sollte. 

Die Funktion kann auch die Erstellung der visuellen Darstellung des Tropfens übernehmen, indem er sie auf den ehemaligen Bildschirmkoordinaten des Feindes erzeugt. 

Betrachten Sie das folgende Beispiel:

Klicke auf dieSchaltfläche "100 Bestien töten".  Dadurch wird ein Stapelprozess ausgeführt, der 100 zufällige Bestien erzeugt, sie tötet und das Ergebnis für jedes Tier anzeigt (dh ob das Tier einen Gegenstand fallen lässt und, wenn ja, welche Art von Gegenstand).  Statistiken am unteren Rand des Bildschirms zeigen an, wie viele Biester Gegenstände fallen lassen und wie viele Gegenstände jeder Gegenstand fallen gelassen wurden. 

Dieses Beispiel ist ausschließlich Text, um die Logik hinter der Funktion zu zeigen und um zu zeigen, dass diese Mechanik auf jede Art von Spiel angewendet werden kann, egal ob es sich um einen Jump'n'Run oder einen Top-Down-Shooter handelt ein RPG. 

Schauen wir uns an, wie diese Demo funktioniert.  Zuerst sind die Bestien und Tropfen jeweils in Arrays enthalten.  Hier ist das Biest-Array:

Index (X) 
Name (Y-0) 
Abfallrate (Y-1) 
Artikel-Seltenheit (Y-2) 
Wildschwein  100  100 
Goblin  75  75 
Knappe  65  55 
ZogZog  45  100 
Eule  15  15 
Mastodon  35  50 

Und hier ist das Tropfen-Array: 

Index (X) 
Name (Y-0) 
Artikel-Seltenheit (Y-1) 
Lollipop  75 
Gold  50 
Felsen  95 
Juwel  25 
Weihrauch  35
Ausrüstung  15 

Der X-Wert (die Index-Spalte) für das Array fungiert als eindeutige Kennung für das Biest oder den Elementtyp.  Zum Beispiel ist das Biest von Index 0 ein Wildschwein.  Das Item von Index 3 ist ein Juwel

Diese Arrays dienen als Nachschlagetabellen für uns und enthalten den Namen oder Typ jedes Tieres oder Gegenstands sowie andere Werte, anhand derer wir die Seltenheit oder die Fallrate bestimmen können.  Im Tier-Array gibt es zwei weitere Spalten nach dem Namen: 

Droprate ist die Wahrscheinlichkeit, dass das Biest einen Gegenstand fallen lässt, wenn er getötet wird.  Zum Beispiel hat der Eber eine Chance von 100%, einen Gegenstand fallen zu lassen, wenn er getötet wird, wohingegen die Eule eine Chance von 15% haben wird, dasselbe zu tun. 

Rarität definiert, wie ungewöhnlich die Gegenstände sind, die von diesem Biest fallen gelassen werden können.  Zum Beispiel wird ein Wildschwein wahrscheinlich Gegenstände mit einem Seltenheitswert von 100 fallen lassen. Wenn wir nun die Tropfen-Anordnung überprüfen, können wir sehen, dass der Felsen der Gegenstand mit der größten Seltenheit ist (95).  (Trotz des Seltenheitswerts aufgrund der Art und Weise, wie ich die Funktion programmiert habe, ist der Gegenstand umso häufiger, je größer die Seltenheit ist.  Er hat mehr Chancen, die Steine ​​fallen zu lassen als ein Gegenstand mit einem niedrigeren Seltenheitswert.)

Und das ist für uns aus einer Game-Design-Perspektive interessant.  Für die Balance des Spiels wollen wir nicht, dass der Spieler zu früh auf zu viel Ausrüstung oder zu viele High-End-Gegenstände zugreifen kann - sonst könnte der Charakter zu früh überwältigt werden, und das Spiel wird weniger interessant zu spielen sein . 

Diese Tabellen und Werte sind nur Beispiele, und Sie können und sollten mit ihnen spielen und sie an Ihr eigenes Spielsystem und Universum anpassen.  Alles hängt von der Balance Ihres Systems ab.  Wenn Sie mehr über das Thema Balancieren erfahren möchten, empfehle ich Ihnen diese Serie von Tutorials: Balancing Turn-Based RPGs

Schauen wir uns nun den (Pseudo-) Code für die Demo an: Zuerst die Benutzeraktion:

1
CONSTANT BEAST_NAME = 0
2
CONSTANT BEAST_DROPRATE = 1
3
CONSTANT BEAST_RARITY = 2
4
CONSTANT DROP_NAME = 0
5
CONSTANT DROP_RATE = 1
6
//Those constants are used for a better readability of the arrays
7
8
On start of the project, fill the arrays with the correct values
9
array aBeast(6,3)   //The array that contains the values for each beast
10
array aDrop(6,2)    //The array that contains the values for each item
11
array aTemp(0)      //A temporary array that will allow us what item type to drop
12
array aStats(6)     //The array that will contain the amount of each item dropped
13
14
On button clicked
15
    Call function "SlainBeast(100)"
16
17
Function SlainBest (Repetitions)
18
    int BeastDrops = 0 //The variable that will keep the count of how many beasts did drop item
19
    Text.text = ""
20
    aStats().clear //Resets all the values contained in this array to make new statistics for the current batch
21
    Repeat Repetitions times
22
        int BeastType
23
        int DropChance
24
        int Rarity
25
        BeastType = Random(6) //Since we have 6 beasts in our array
26
        Rarity = aBeast(BeastType, BEAST_RARITY) //Get the rarity of items the beast should drop from the aBeast array
27
        DropChance = ceil(random(100)) //Picks a number between 0 and 100)
28
        Text.text = Text.text & loopindex & " _ " & aBeast(BeastType,BEAST_NAME) & "is slain"
29
        
30
        If DropChance > aBeast(BeastType,BEAST_DROPRATE)
31
            //The DropChance is bigger than the droprate for this beast
32
            Text.text = Text.text & "." & newline
33
            //We stop here, this beast is considered to not have dropped an item.
34
        
35
        If DropChance <= aBeast(BeastType,BEAST_DROPRATE)
36
           Text.text = Text.Text & " dropping " //We will put some text to display what item was dropped
37
           //On the other hand, DropChance is less or equal the droprate for this beast
38
            aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop
39
                For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array
40
                    aDrop(a,DROP_RATE) >= Rarity //When the item drop rate is greater or equal the expected Rarity
41
                        Push aTemp,a //We put the current a index in the temp array. We know that this index is a possible item type to drop
42
                int DropType
43
                DropType = random(aTemp.width) //The DropType is one of the indexes contained in the temporary array
44
                Text.text = Text.text & aDrop(DropType, DROP_NAME) & "." & newline //We display the item name that was dropped
45
                //We do some statistics
46
                aStats(DropType) = aStats(DropType) + 1
47
                BeastDrops = BeastDrops + 1
48
            TextStats.Text = BeastDrops & " beasts dropped items." & newline
49
            For a = 0 to aStats.width //Display each item amount that was dropped
50
            and aStats(a) > 0
51
                TextStats.Text = TextStats.Text & aStats(a) & " " & aDrop(a,DROP_NAME) & " "
52
    

Klicken Sie auf die Schaltfläche "100 Bestien töten".  Dieser Knopf ruft eine Funktion mit einem Parameter von 100 auf, nur weil 100 sich wie eine gute Anzahl von Feinden zum Töten anfühlt.  In einem echten Spiel ist es wahrscheinlicher, dass Sie die Bestien natürlich nacheinander töten werden.

Daraus wird die Funktion SlainBeast aufgerufen.  Sein Zweck besteht darin, einen Text anzuzeigen, um dem Benutzer eine Rückmeldung zu geben, was passiert ist.  Zuerst werden die Variablen BeastDrops und aStats bereinigt, die für die Statistik verwendet werden. In einem echten Spiel ist es unwahrscheinlich, dass du diese brauchst.  Es reinigt auch den Text, so dass neue 100 Zeilen angezeigt werden, um die Ergebnisse dieses Stapels zu sehen.  In der Funktion selbst werden drei numerische Variablen erstellt: BeastType, DropChance und Rarity.

BeastType ist der Index, den wir verwenden, um auf eine bestimmte Zeile im aBeast-Arrayzu verweisen.  Es ist im Grunde die Art von Biest, die der Spieler zu Gesicht bekommen und töten musste. Die Rarität wird ebenfalls vom aBeast-Array übernommen.Es ist die Seltenheit des Gegenstandes, den dieses Biest fallen lassen sollte, der Wert des Item-Seltenheit-Feldes im aBeast-Array.

Schließlich ist DropChance eine Zahl, die wir zufällig zwischen 0 und 100 wählen.  (Die meisten Programmiersprachen haben eine Funktion, um eine Zufallszahl aus einem Bereich zu erhalten oder zumindest eine Zufallszahl zwischen 0 und 1 zu erhalten um 100.)

An dieser Stelle können wir unsere erste Information im Text-Objekt anzeigen: Wir wissen bereits, welche Art von Bestie hervorgebracht und getötet wurde.  Also verketten wir uns mit dem aktuellen Wert von Text.text den BEAST_NAME des aktuellen BeastType, den wir zufällig ausgewählt haben, aus dem aBeast-Array heraus. A

ls nächstes müssen wir bestimmen, ob ein Gegenstand fallen gelassen werden soll.  Dazu vergleichen wir den DropChance-Wert mit dem BEAST_DROPRATE-Wert aus dem aBeast-Array.  Wenn DropChance kleiner oder gleich diesem Wert ist, löschen wir ein Element. 

(Ich entschied mich für den "weniger als oder gleich" -Ansatz, der von diesen Live-Rollenspielern mit dem D & D King Arthur: Pendragon-Regelwerk bezüglich Würfelwürfen beeinflusst wurde, aber Sie könnten die Funktion sehr genau anders herum programmieren Sie entscheiden, dass Tropfen nur dann auftreten, wenn "größer oder gleich".  Es ist nur eine Frage der numerischen Werte und der Logik.  Sie bleiben jedoch während des gesamten Algorithmus konsistent und ändern die Logik nicht auf halbem Wege - andernfalls könnten Sie enden Probleme beim Debuggen oder Warten.)

So bestimmen zwei Zeilen, ob ein Artikel gelöscht wird oder nicht.  Zuerst: 

1
DropChance > aBeast(BeastType,BEAST_DROPRATE)

Hier ist DropChance größer als DropRate und wir denken, dass dies bedeutet, dass kein Element gelöscht wird.  Von da an wird nur noch ein schließendes "." (Punkt), der den Satz "[BeastType] wurde getötet" beendet hat,  bevor wir zum nächsten Feind in unserem Stapel übergehen. 

Andererseits:

1
DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Hier ist DropChance kleiner oder gleich der DropRate für den aktuellen BeastType, und wir denken, dass dies bedeutet, dass ein Element gelöscht wird.  Um dies zu tun, führen wir einen Vergleich zwischen der Rarität des Elements, das der aktuelle BeastType "lassen" darf, und den verschiedenen Seltenheitswerten durch, die wir in der aDrop-Tabelle eingerichtet haben. 

Wir durchlaufen die aDrop-Tabelle und prüfen jeden Index, um zu sehen, ob sein DROP_RATE größer oder gleich Rarity ist.  (Denken Sie daran, dass der Wert des Rarity-Werts umso höher ist, je höher der Wert ist.) Für jeden Index, der mit dem Vergleich übereinstimmt, wird dieser Index in ein temporäres Array, aTemp, geschoben.

Am Ende der Schleife sollten wir mindestens einen Index im aTemp-Array haben.  (Wenn nicht, müssen wir unsere aDrop- und aBeast-Tische neu gestalten!).  Wir erstellen dann eine neue numerische Variable DropType, die zufällig einen der Indizes aus dem aTemp-Array auswählt. 

Das wird der Gegenstand sein, den wir fallen lassen. Wir fügen den Namen des Elements zu unserem Text-Objekt hinzu und machen den Satz zu etwas wie "BeastType wurde getötet und löschte einen DROP_NAME". Für dieses Beispiel fügen wir einige Zahlen zu unseren verschiedenen Statistiken hinzu (im aStats-Array und in BeastDrops). 

Schließlich, nach den 100 Wiederholungen, zeigen wir diese Statistiken, die Anzahl der Bestien (von 100), die Gegenstände fallen lassen, und die Anzahl der Gegenstände an, die fallengelassen wurden.

Ein anderes Beispiel: Objekte visuell löschen 

Betrachten wir ein anderes Beispiel: 

Drücke Space, um einen Feuerball zu erstellen, der den Gegner töten wird. 

Wie du sehen kannst, wird ein zufälliger Gegner (aus einem Bestiarium von 11) erschaffen.  Der Spielercharakter (links) kann einen Projektilangriff verursachen.  Wenn das Projektil den Feind trifft, stirbt der Feind. 

Von dort aus bestimmt eine ähnliche Funktion wie im vorherigen Beispiel, ob der Gegner einen Gegenstand fallen lässt oder nicht und bestimmt, was der Gegenstand ist.  Dieses Mal erstellt es auch die visuelle Darstellung des gelöschten Elements und aktualisiert die Statistiken am unteren Bildschirmrand. 

Hier ist eine Implementierung im Pseudocode: 

1
CONSTANT ENEMY_NAME = 0
2
CONSTANT ENEMY_DROPRATE = 1
3
CONSTANT ENEMY_RARITY = 2
4
CONSTANT ENEMY_ANIM = 3
5
CONSTANT DROP_NAME = 0
6
CONSTANT DROP_RATE = 1
7
//Constants for the readability of the arrays

8
9
int EnemiesSpawned = 0
10
int EnemiesDrops = 0
11
12
array aEnemy(11,4)
13
array aDrop(17,2)
14
array aStats(17)
15
array aTemp(0)
16
17
On start of the project, we roll the data in aEnemy and aDrop
18
Start Timer "Spawn" for 0.2 second
19
20
Function "SpawnEnemy"
21
    int EnemyType = 0
22
    EnemyType = random(11) //We roll an enemy type out of the 11 available

23
    Create object Enemy //We create the visual object Enemy on screen

24
    Enemy.Animation = aEnemy(EnemyType, ENEMY_ANIM)
25
    EnemiesSpawned = EnemiesSpawned + 1
26
    txtEnemy.text = aEnemy(EnemyType, ENEMY_NAME) & " appeared"
27
    Enemy.Name = aEnemy(EnemyType, ENEMY_NAME)
28
    Enemy.Type = EnemyType
29
30
Keyboard Key "Space" pressed
31
    Create object Projectile from Char.Position
32
33
Projectile collides with Enemy
34
    Destroy Projectile
35
    Enemy start Fade
36
    txtEnemy.text = Enemy.Name & " has been vanquished."
37
38
Enemy Fade finished
39
    Start Timer "Spawn" for 2.5 seconds //Once the fade out is finished, we wait 2.5 seconds before spawning a new enemy at a random position on the screen

40
    Function "Drop" (Enemy.Type, Enemy.X, Enemy.Y, Enemy.Name)
41
42
Function Drop (EnemyType, EnemyX, EnemyY, EnemyName)
43
    int DropChance = 0
44
    int Rarity = 0
45
    DropChance = ceil(random(100))
46
    Rarity = aEnemy(EnemyType, ENEMY_RARITY)
47
    txtEnemy.text = EnemyName & " dropped "
48
    
49
    If DropChance > aEnemy(EnemyType, ENEMY_DROPRATE)
50
        txtEnemy.text = txtEnemy.text & " nothing."
51
        //Nothing was dropped

52
    If DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE)
53
        aTemp.clear/set size to 0
54
        For a = 0 to aDrop.Width
55
        and aDrop(a, DROP_RATE) >= Rarity
56
            aTemp.Push(a) //We push the current index into the aTemp array as possible drop index

57
        
58
        int DropType = 0
59
        DropType = Random(aTemp.Width) //We pick what is the drop index amongst the indexes stored in aTemp

60
        aStats(DropType) = aStats(DropType) + 1
61
        EnemiesDrops = EnemiesDrops + 1
62
        Create Object Drop at EnemyX, EnemyY
63
        Drop.AnimationFrame = DropType
64
        txtEnemy.Text = txtEnemy.Text & aDrop.(DropType, DROP_NAME) & "." //We display the name of the drop

65
    txtStats.text = EnemiesDrops & " enemies on " & EnemiesSpawned & " dropped items." & newline
66
    For a = 0 to aStats.width
67
    and aStats(a) > 0
68
        txtStats.text = txtStats.Text & aStats(a) & " " & aDrop(a, DROP_NAME) & " "
69
        
70
Timer "Spawn"
71
    Call Function "SpawnEnemy"

Sehen Sie sich die Inhalte der Tabellen aEnemy und aDrop an: 

Index (X) 
Name (Y-0) 
Abfallrate (Y-1) 
Artikel-Seltenheit (Y-2) 
Animationsname (Y-3) 
Heiler Weiblich  100  100  Heiler_F 
Heiler Männlich  75  75  Heiler_M 
Magierin Weiblich  65  55  Magier_F
Magier Männlich  45  100  Magier_M 
Ninja Weiblich  15  15  Ninja_F 
Ninja Männlich  35  50  Ninja_M 
Ranger Rüde  75  80  Ranger_M 
Stadtbewohner weiblich  75  15  Townfolk_F
Townfolk Männlich  95  95  Townfolk_M 
Kriegerin Weiblich  70  70  Krieger_F 
10  Krieger Männlich  45  55 Krieger_M 
Index (X) 
Name (Y-0) 
Artikel-Seltenheit (Y-1) 
Apfel  75 
Banane  50 
Karotten  95 
Traube  85 
Leerer Trank  80 
5 Blauer Trank  75 
Roter Trank  70 
Grüner Trank  60 
Rosa Herz  65 
Blaue Perle  15 
10  Felsen  100 
11  Handschuh  25 
12  Rüstung  30 
13  Juwel  35 
14  Magierhut  65 
15  Holzschild  85 
16  Eisenaxt  65

Gegensatz zum vorherigen Beispiel heißt das Array, das die Daten des Feindes enthält, aEnemy und enthält eine weitere Zeile mit Daten, ENEMY_ANIM, die den Namen der Animation des Feindes trägt.  Auf diese Weise können wir beim Auftauchen des Feindes nachsehen und die grafische Darstellung automatisieren. 

Analog dazu enthält aDrop jetzt 16 Elemente statt sechs, und jeder Index bezieht sich auf den Animationsrahmen des Objekts - aber ich hätte auch mehrere Animationen haben können, genau wie für die Gegner, wenn die abgeworfenen Gegenstände animiert werden sollten . 

Dieses Mal gibt es viel mehr Feinde und Gegenstände als im vorherigen Beispiel.  Sie sehen jedoch, dass die Daten zu Fallraten und Seltenheitswerten noch vorhanden sind.  Ein bemerkenswerter Unterschied ist, dass wir das Laichen der Feinde von der Funktion getrennt haben, die berechnet, ob es einen Abfall gibt oder nicht.  Das liegt daran, dass Gegner in einem echten Spiel wahrscheinlich mehr tun würden, als nur auf den Bildschirm zu warten, um getötet zu werden!

So, jetzt haben wir eine Funktion SpawnEnemy und eine andere Funktion DropDrop ist ziemlich ähnlich wie wir im vorherigen Beispiel den "Würfelwurf" unserer Gegenstandstropfen behandelt haben, aber dieses Mal einige Parameter: Zwei davon sind die X- und Y-Koordinaten des Gegners auf dem Bildschirm, denn dort sind wir will den Gegenstand spawnen, wenn es einen Tropfen gibt; Die anderen Parameter sind der EnemyType, so dass wir den Namen des Feindes in der aEnemy-Tabelle und den Namen des Zeichens als String nachschlagen können, um schneller das Feedback zu schreiben, das wir dem Spieler geben wollen. 

Die Logik der Drop-Funktion ist ansonsten ähnlich wie im vorherigen Beispiel; Was sich hauptsächlich ändert, ist die Art und Weise, wie wir Feedback anzeigen.  Anstatt nur Text anzuzeigen, spawnen wir diesmal auch ein Objekt auf dem Bildschirm, um dem Spieler eine visuelle Darstellung zu geben.

(Hinweis: Um die Feinde auf verschiedenen Positionen auf dem Bildschirm erscheinen zu lassen, verwendete ich ein unsichtbares Objekt, Spawn, als Referenz, das sich ständig nach links und rechts bewegt.  Wenn die SpawnEnemy-Funktion aufgerufen wird, erzeugt sie den Feind an den aktuellen Koordinaten des Spawn-Objekts , so dass die Feinde erscheinen und eine Vielzahl von horizontalen Standorten.)

Eine letzte Sache zu diskutieren ist, wann genau die Drop-Funktion aufgerufen wird.  Ich löse es nicht direkt nach dem Tod eines Feindes aus, sondern nach dem Verschwinden des Feindes (der Todesanimation des Feindes).  Sie können natürlich den Tropfen rufen, wenn der Feind noch auf dem Bildschirm sichtbar ist, wenn Sie bevorzugen; Noch einmal, das liegt wirklich an deinem Spieldesign. 

Fazit 

Auf der Design-Ebene gibt es einen Anreiz für den Spieler, Feinde zu konfrontieren und zu vernichten, wenn er Beute fallen lässt.  Die abgeworfenen Gegenstände ermöglichen es dem Spieler, Power-Ups, Statistiken oder sogar Tore zu geben, sei es direkt oder indirekt. 

Auf einer Implementierungsebene wird das Löschen von Elementen über eine Funktion verwaltet, die der Coder beim Aufruf entscheidet.  Die Funktion prüft die Seltenheit der Gegenstände, die je nach der Art des getöteten Feindes fallen gelassen werden sollen und kann bestimmen, wo sie auf dem Bildschirm erscheinen soll, wenn und wann sie benötigt wird.  Die Daten für die Gegenstände und Feinde können in Datenstrukturen wie Arrays gehalten und von der Funktion nachgeschlagen werden.

Die Funktion verwendet Zufallszahlen, um die Häufigkeit und den Typ der Tropfen zu bestimmen, und der Coder hat die Kontrolle über diese zufälligen Würfe und die Daten, die er nachschlägt, um das Gefühl dieser Tropfen im Spiel anzupassen. 

Ich hoffe, dir hat dieser Artikel gefallen und du hast ein besseres Verständnis dafür, wie du deine Monster dazu bringst, in deinem Spiel Beute zu landen. Ich freue mich darauf, deine eigenen Spiele mit dieser Mechanik zu sehen. 

Verweise     

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.