Advertisement
  1. Game Development
  2. Complete Games

Erstellen Sie ein von Megaman inspiriertes Spiel in Construct 2

Scroll to top
Read Time: 26 min

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

Ich werde Sie durch die Entwicklung eines von Megaman inspirierten Shooter/Platformer-Spiels führen. Wir werden uns mehr auf die Schießaspekte des Gameplays als auf die Plattform konzentrieren. In diesem Tutorial werde ich Construct 2 als Werkzeug verwenden, um das Spiel zu erstellen, aber ich werde die Logik mithilfe von Pseudocode erklären, damit Sie diesem Tutorial in einer beliebigen Sprache oder Engine Ihrer Wahl folgen können.


Klicken Sie auf das Spiel, um es zu fokussieren, und verwenden Sie dann die Pfeiltasten, um sich zu bewegen und zu springen, und Z, um zu schießen.

Um mich auf die Gameplay-Implementierung zu konzentrieren, werde ich nicht jede Construct 2-Funktion erklären. Ich gehe davon aus, dass Sie die Grundlagen wie das Laden eines Sprites, grundlegende Kollisionen oder das Spielen von Sounds kennen. Nachdem dies gesagt ist, fangen wir an, das Spiel zu machen.


Bereiten Sie das Kunstwerk vor

Das Wichtigste zuerst, wir brauchen Sprites für unser Spiel. Zum Glück hat opengameart uns mit ihrer wundervollen Sammlung legaler Spielekunst bedeckt. Wir brauchen vier Spritesätze; Ein Held, ein Feind und Kacheln für Plattformen.

Um sie in Construct 2 zu verwenden, schneide ich die Sprites des Helden mit GIMP in einzelne Frames.


Grundbewegung

Ich werde das Plattformverhalten von Construct 2 für den Rest des Tutorials verwenden, damit ich mich auf den Schieß- und KI-Teil des Spiels konzentrieren kann, der der Hauptfokus dieses Tutorials war.

Construct 2s platform behaviour
Construct 2 Plattformverhalten

Grundbewegung

Wenn Sie in einer anderen Sprache arbeiten oder Ihre eigene grundlegende Plattformerbewegung implementieren möchten, anstatt das integrierte Verhalten zu verwenden. Sie müssen den Code in diesem Abschnitt nur verwenden, wenn Sie das integrierte Verhalten von Construct 2 nicht verwenden möchten.

Zunächst müssen wir drei Möglichkeiten betrachten, wie sich unser Held bewegen kann. nach rechts gehen, nach links gehen oder springen. In jedem Frame aktualisieren wir die Spielsimulation.

1
number moveSpeed = 50;
2
3
function update()
4
{
5
 moveHero();
6
}

Um den Charakter des Spielers zu aktualisieren, implementieren wir grundlegende Bewegungen wie folgt:

1
function moveHero()
2
{
3
 // player is pressing this key down

4
 if (keyDown(KEY_LEFT))
5
 {
6
 hero.x -= moveSpeed * deltaTime;
7
 hero.animate("walkLeft");
8
 }
9
10
if (keyDown(KEY_RIGHT))
11
 {
12
 hero.x += moveSpeed * deltaTime;
13
 hero.animate("walkRight");
14
 }
15
16
if (keyDown(KEY_UP))
17
 {
18
 hero.jump();
19
 hero.animate("jump");
20
 }
21
22
// a key that was just unpressed

23
 if (keyReleased(KEY_LEFT))
24
 {
25
 hero.animate("standLeft");
26
 }
27
28
if (keyReleased(KEY_RIGHT))
29
 {
30
 hero.animate("standRight");
31
 }
32
}

Ich benutze eine andere Funktion, um zu springen, weil beim Springen nicht nur der y-Wert geändert, sondern auch die Schwerkraft berechnet wird. Wir werden auch eine Funktion haben, die abhört, ob gerade eine Taste losgelassen wurde oder nicht, um unsere Heldenanimation auf eine stehende Animation zurückzusetzen.

Lassen Sie uns darüber sprechen, wie man den Spieler zum Springen bringt. Der Held muss wissen, ob er gerade springt oder nicht und ob er gerade fällt oder nicht. Wir deklarieren also zwei neue Variablen: isJumping und isFalling. Standardmäßig sind beide falsch, was bedeutet, dass der Held auf einer Plattform steht.

Um einen Sprung auszuführen, müssen wir zuerst prüfen, ob beide Werte falsch sind oder nicht, und dann den isJump auf true setzen.

1
Function jump()
2
{
3
 if (!isJumping && !isFalling)
4
 {
5
 isJumping = True
6
 }
7
}

Damit der Held springen kann, benötigen wir eine Variable namens JumpPower und Gravity. Der Standardwert der Sprungkraft ist -20 und die Schwerkraft ist 1. Die Logik besteht darin, den Wert der Sprungkraft zur Y-Position des Helden und die Schwerkraft zum Wert der Sprungkraft hinzuzufügen.

Wir machen das bei jedem Tick. Vielleicht ist dies nicht die realistischste Schwerkraftphysik, die es gibt, aber Spiele müssen nicht realistisch sein, sie müssen nur glaubwürdig sein, deshalb haben einige Spiele einen super menschlichen Sprung und einen Doppelsprung. Der folgende Code gehört in die Update-Funktion.

1
If (isJumping || isFalling)
2
{
3
 hero.y += hero.jumpPower;
4
 hero.jumpPower += hero.gravity;
5
}
6
7
// eventually jump power will be greater than zero, and that means the hero is falling

8
9
if (hero.jumpPower >= 0)
10
{
11
 isJumping = False;
12
 isFalling = True;
13
}
14
15
// to make a stop to the fall, do something when the hero overlaps the platform

16
17
if (hero.isOverlapping(platform1))
18
{
19
 // platform1 is the platform that our hero can step on

20
 // resets the variable to their default value

21
 isJumping = False;
22
 isFalling = False;
23
 hero.jumpPower = -20;
24
}
25
26
// and then there's the free fall, when player falls over the edge of a platform

27
if (!hero.isOverlapping(platform1) && hero.jumpPower < 0 && !isJumping)
28
{
29
 // !hero.isOverlapping(platform1) checks whether or not our hero is standing on a platform

30
 // and if jumpPower is less than zero and the player is not currently jumping, then that means

31
 // he's falling

32
33
// setting these two values like this will make the player fall.

34
 hero.jumpPower = 0;
35
 isFalling = true;
36
}

Das integrierte Plattformverhalten von Construct 2 repliziert den obigen Beispielcode, der nur denjenigen hilft, die in einer anderen Sprache arbeiten.


Implementierung des Shootings

Jetzt kommt der Schießteil des Spiels. In der Megaman-Serie gibt es drei Arten von Schüssen: normale Schüsse, geladene Schüsse und Boss-Energieschüsse.

Normale Aufnahmen sind selbsterklärend. Geladene Schüsse sind Schüsse, die zuerst geladen werden, bevor sie veröffentlicht werden. Es gibt zwei Arten von geladenen Schüssen: halb aufgeladen und voll aufgeladen. Diese aufgeladenen Angriffe sind stärker als normale Schüsse, wobei die voll aufgeladenen am stärksten werden.

Boss-Energieschüsse sind Schüsse mit Kraft, die der Spieler nach dem Sieg über jeden Boss erhalten hat. Der Schaden ist der gleiche wie normal, aber sie haben spezielle Eigenschaften, die normale Schüsse nicht haben.

Nachdem wir den Typ jedes Schusses kennen, beginnen wir mit der Erstellung. Lassen Sie uns zuerst die Logik dahinter sehen, wie wir jeden Schuss verwenden. Hier nehmen wir an, dass die Z-Taste auf der Tastatur verwendet wird, um einen Schuss abzufeuern. Wir werden zwei verschiedene Verhaltensweisen implementieren:

  • Normale Schüsse: Der Spieler drückt z und lässt es dann sofort los (tippen Sie auf die Schaltfläche). Die Kugel wird bei jedem Tippen einmal abgefeuert. Die Animation wird geändert, um eine Animation aufzunehmen, bevor sofort zur stehenden Animation gewechselt wird.

  • Geladene Schüsse: Der Spieler drückt Z. Die erste normale Kugel wird abgefeuert. Die Animation ändert sich zu Aufnahme, bevor sofort zur stehenden Animation gewechselt wird. Wenn Z weiterhin gedrückt wird, wird zusätzlich zur Wiedergabeanimation (Stehen, Gehen) ein Ladeeffekt hinzugefügt. Wenn die Z-Taste seit dem ersten Aufladen in weniger als 5 Sekunden losgelassen wird, wird eine halb aufgeladene Kugel abgefeuert. Wenn die Z-Taste nach 5 Sekunden losgelassen wird, wird eine voll aufgeladene Kugel abgefeuert.
  • Boss-Energieschüsse: Unser Held muss zuerst die Kugel ausrüsten, die er nach dem Sieg über einen Boss erhalten hat. Nach dem Ausrüsten drückt der Spieler einen weiteren Knopf, um diese Kugel abzuschießen. Dieses Aufzählungsverhalten variiert und muss für jedes Aufzählungszeichen eindeutig codiert werden.

Beginnen wir jetzt mit dem Code. Da unser Held nach links und rechts schießen kann, müssen wir wissen, in welche Richtung er gerade blickt. Deklarieren wir eine neue Variable namens "Facing", die einen Zeichenfolgenwert speichert, ob der Held nach links oder rechts zeigt.

1
String facing = "right"; // which direction the hero is currently facing

2
function moveHero()
3
{
4
 // player is pressing this key down

5
 if (keyDown(KEY_LEFT))
6
 {
7
 hero.x -= moveSpeed * deltaTime;
8
 hero.animate("walkLeft");
9
10
 facing = "left";
11
 }
12
13
if (keyDown(KEY_RIGHT))
14
 {
15
 hero.x += moveSpeed * deltaTime;
16
 hero.animate("walkRight");
17
18
facing = "right";
19
 }
20
21
// ... the continuation of moveHero() function goes here

22
}
23
24
function update()
25
{
26
 // ...the update code that we previously wrote goes here...

27
28
// player press this key once

29
 if (keyPressed(KEY_Z))
30
 {
31
 if (facing == "right")
32
 {
33
 hero.animate("Shoot");
34
35
// we will add shooting function here later

36
 }
37
 else if (facing == "left")
38
 {
39
 hero.animate("Shoot");
40
 hero.mirrorSprite(); // this function flips the sprite horizontally

41
 }
42
 }
43
44
if (keyReleased(KEY_Z))
45
 {
46
 if (facing == "right")
47
 {
48
 hero.animate("standRight");
49
 }
50
 else if (facing == "left")
51
 {
52
 hero.animate("standLeft");
53
 hero.mirrorSprite(); // we need to call this again because the sprite was mirrored

54
 // if we don't mirror the sprite again, standLeft will look like standRight

55
 }
56
 }
57
}
move events in construct 2move events in construct 2move events in construct 2
Verschieben von Ereignissen in Construct 2

Bevor wir eine Kugel abschießen, müssen wir uns die Eigenschaften der Kugel ansehen:

  • Kraft: Angriffskraft der Kugel, der Schaden, den sie dem Feind zufügt

  • Geschwindigkeit: wie schnell die Kugel geht

  • Winkel: Der Schusswinkel bestimmt, in welche Richtung die Kugel geht.

Diese Eigenschaften unterscheiden sich für jedes Aufzählungszeichen. Insbesondere wird die Leistungseigenschaft unterschiedlich sein. Die Winkeleigenschaft ist normalerweise nur einer von zwei Werten. ob die Kugel rechts oder links abgefeuert wird, es sei denn, es handelt sich um eine Boss-Energiekugel, die in einem einzigartigen Winkel abgefeuert werden kann.

Schussvariationen werden später besprochen, daher werde ich jetzt nur grundlegende Aufnahmen behandeln. Das Folgende ist der Code, der eine Kugel abschießt.

1
// first, we create a function that creates a new bullet

2
Function shoot(string pathToSprite, number bulletPower, number bulletSpeed, number bulletAngle)
3
{
4
 myBullet = new Bullet(pathToSprite);
5
 myBullet.power = bulletPower;
6
7
// the bullet class or object has two private variables that moves it according to its angle

8
 // more explanation to these two lines need more math, so I choose not to explain

9
 // I assume your engine of choice have a way to move an object according to its angle

10
 ySpeed = Math.sin(bulletAngle) * bulletSpeed;
11
 xSpeed = Math.cos(bulletAngle) * bulletSpeed;
12
}
13
14
// this is Bullet class' function that's called every frame, this moves the bullet according to its angle

15
function moveBullet()
16
{
17
 x += xSpeed * deltaTime;
18
 y += ySpeed * deltaTime;
19
}
20
21
// and this is the modification to our previous update() function

22
function update()
23
{
24
 // ...the update code that we previously wrote goes here...

25
26
// player press this key once

27
 if (keyPressed(KEY_Z))
28
 {
29
 if (facing == "right")
30
 {
31
 hero.animate("Shoot");
32
 hero.shoot("path/to/sprite.png", 10, 400, 0);
33
34
}
35
 else if (facing == "left")
36
 {
37
 hero.animate("Shoot");
38
 hero.mirrorSprite(); // this function flips the sprite horizontally

39
 hero.shoot("path/to/sprite.png", 10, 400, 180); // the angle is 180 so that the bullet goes left

40
 }
41
 }
42
43
// .. the continuation of update code goes here...

44
}
add bullet behaviour to the bullets
Hinzufügen des Aufzählungsverhaltens zu Aufzählungssprites
shooting code spawn new bullet objectshooting code spawn new bullet objectshooting code spawn new bullet object
Der Schießcode, in dem wir ein neues Kugelobjekt erzeugen

Geladene Schüsse

Einige Kugeln können stärker sein als andere. Um einen geladenen Schuss zu erstellen, benötigen wir eine Variable namens "ChargedTime", die jede Sekunde erhöht, in der der Spieler Z gedrückt hält, und beim Abfeuern der Kugel auf Null zurückkehrt. Die Änderungen am Update-Code lauten wie folgt:

1
// player just released z key

2
if (keyReleased(KEY_Z))
3
{
4
 if (chargedTime > 0 && chargedTime <= 5)
5
 {
6
 if (facing == "right")
7
 {
8
 hero.animate("Shoot");
9
 hero.shoot("path/to/halfChargedBullet.png", 20, 400, 0);
10
 chargedTime = 0;
11
12
}
13
 else if (facing == "left")
14
 {
15
 hero.animate("Shoot");
16
 hero.mirrorSprite(); // this function flips the sprite horizontally

17
 hero.shoot("path/to/halfChargedBullet.png", 20, 400, 180);
18
 chargedTime = 0;
19
20
}
21
 }
22
 else if (chargedTime > 5)
23
 {
24
 if (facing == "right")
25
 {
26
 hero.animate("Shoot");
27
 hero.shoot("path/to/fullChargedBullet.png", 40, 400, 0);
28
 chargedTime = 0;
29
30
}
31
 else if (facing == "left")
32
 {
33
 hero.animate("Shoot");
34
 hero.mirrorSprite(); // this function flips the sprite horizontally

35
 hero.shoot("path/to/fullChargedBullet.png", 40, 400, 180);
36
 chargedTime = 0;
37
38
}
39
 }
40
41
if (facing == "right")
42
 {
43
 hero.animate("standRight");
44
 }
45
 else if (facing == "left")
46
 {
47
 hero.animate("standLeft");
48
 hero.mirrorSprite(); // we need to call this again because the sprite was mirrored

49
 // if we don't mirror the sprite again, standLeft will look like standRight

50
 }
51
}
52
53
// player is pressing this key down

54
if (keyDown(KEY_Z))
55
{
56
 // this is the function that adds the value of chargedTime every second

57
 // this keyDown block of code will be run every frame, which is less than a second

58
 // your engine of choice should have a way to tell whether a second has passed or not

59
 addChargedTime();
60
}
chapter 1 - add charged timechapter 1 - add charged timechapter 1 - add charged time
chapter 1 - shoot full charged bulletchapter 1 - shoot full charged bulletchapter 1 - shoot full charged bullet
chapter 1 - shoot half charged bulletchapter 1 - shoot half charged bulletchapter 1 - shoot half charged bullet

Unser neuer Heldencharakter bewegt sich nach links, rechts und springt gemäß unserer Eingabe und schießt auch Kugeln, egal ob normal, halb aufgeladen oder voll aufgeladen.


Feinde implementieren

Wir haben jetzt einen kontrollierbaren Helden. Nennen wir es der Einfachheit halber Xeon. Er kann einige grundlegende Bewegungen wie Gehen, Springen und Schießen ausführen. Das ist großartig! Aber was nützt es, ohne etwas zu schießen, oder? Deshalb werden wir diesmal unseren ersten Feind machen.

Lassen Sie uns die Attribute unseres Feindes entwerfen, bevor wir beginnen, sie zu codieren.

  • Gesundheit: Wie viele Gesundheit unser Feind hat, bestimmt, wie viele Schüsse (und welche Art) benötigt werden, um ihn zu zerstören.

  • Macht: Angriffskraft des Feindes, wie viel Schaden es unserem Spieler zufügt.

  • ShotAngle: In welche Richtung der Feind auf die Kugel schießt, kann sie links oder rechts oder wo immer wir wollen sein.

Das ist so ziemlich das, was wir für unseren Feind brauchen. Jetzt machen wir den Feind zur Klasse/zum Objekt.

Die Klasse/das Objekt des Feindes ist so ziemlich die gleiche wie die Klasse/das Objekt des Spielers, außer dass der Feind nicht auf Eingaben des Spielers hört. Aus diesem Grund müssen wir die Teile ersetzen, in denen der Held auf Spielereingaben und feindliche KI/Logik hört.


Feindlichen Angriff AI

Lassen Sie uns zunächst die grundlegende Angriff AI des Feindes behandeln. Der Feind schießt auf den Spieler, wenn er den Spieler sieht.

Um festzustellen, ob der Feind den Spieler "sieht", müssen wir eine Variable für das feindliche Objekt definieren, die als Verblendung bezeichnet wird. Dabei handelt es sich um eine Zeichenfolge, in der einer der beiden Werte "links" oder "rechts" gespeichert ist.

Der Feind braucht auch eine Art Sichtweite, weshalb wir eine weitere Variable namens Reichweite erstellen werden. Befindet sich der Spieler in dieser Reichweite, bedeutet dies, dass der Feind den Spieler "sieht". Der Pseudocode lautet wie folgt:

1
function boolean checkSees()
2
{
3
 if (facing == "left" && hero.x >= enemy.x -- range)
4
 {
5
 return true;
6
 }
7
8
if (facing == "right" && hero.x <= enemy.x + range)
9
 {
10
 return true;
11
 }
12
13
return false;
14
}
checkSees functioncheckSees functioncheckSees function

Funktion checkSees()

Vielleicht haben Sie etwas in diesem Pseudocode bemerkt: Es berücksichtigt nicht die y-Position des Helden, sodass der Feind immer noch auf den Helden schießt, selbst wenn er sich auf Plattformen mit unterschiedlichen Höhen befindet.

Im Moment wird dies ausreichen, da das Erstellen eines Sichtlinienalgorithmus außerhalb des Rahmens dieses Lernprogramms liegt. In Ihrem eigenen Spiel möchten Sie möglicherweise eine Y-Toleranz in der obigen Funktion hinzufügen, um zu überprüfen, ob die y-Position des Helden zwischen zwei Punkten liegt, die die Höhe des Feindes definieren.


Feinde schießen lassen

Der Pseudocode für das Schießen des Feindes lautet wie folgt:

1
// can be in update() or somewhere else that's executed every frame

2
function update()
3
{
4
 if (checkSees())
5
 {
6
 shoot("path/to/bulletSprite.png", enemyPower, 400, shotAngle);
7
 }
8
}

Wie Sie sehen können, ähnelt die feindliche shoot() der des Spielers. Es nimmt den Pfad, die Angriffskraft, die Geschossgeschwindigkeit und den Schusswinkel des Sprites als Parameter.


Feindliche Bewegungs-KI

Wann wechselt der Feind von links nach rechts? Für unseren Helden verwenden wir Spielereingaben, um die Richtung zu ändern, in die unser Held blickt. Für unseren Feind haben wir zwei Möglichkeiten: Verwenden Sie einen Timer, um alle paar Sekunden die Blickrichtung zu wechseln, während der Feind stillsteht, oder lassen Sie den Feind zu einem bestimmten Punkt gehen und dann seine Blickrichtung wechseln und dann zu einem anderen Punkt gehen um die Blickrichtung wieder zu ändern.

Diese zweite Methode kann als patrouillierende KI verwendet werden. Natürlich können wir den Feind nur in eine Richtung laufen lassen und niemals umkehren.

Der Pseudocode für das erste Verfahren lautet wie folgt:

1
function switchingAI()
2
{
3
 // elapsedTime() is a function that counts how many seconds has passed since its value is reset

4
 // I assume your engine of choice have this kind of functionality

5
 if (elapsedTime() > 4.0)
6
 {
7
 if (facing == "left")
8
 {
9
 facing = "right";
10
 shotAngle = 0;
11
 }
12
13
if (facing == "right")
14
 {
15
 facing = "left";
16
 shotAngle = 180;
17
 }
18
19
enemy.mirrorSprite(); // also flip the sprite horizontally

20
 resetTime(); // resets the time that counts in elapsedTime()

21
 }
22
}
switching AI functionswitching AI functionswitching AI function
KI-Funktionen wechseln

Feind patrouillierende AI

Um die patrouillierende AI zu erstellen, müssen wir zwei unsichtbare Objekte erstellen, die sich am Ende beider Wege der Patrouillenroute des Feindes befinden, und den Feind dazu bringen, sich auf eine andere Weise zu bewegen, wenn er mit ihnen kollidiert.

this is how it looks in Construct 2this is how it looks in Construct 2this is how it looks in Construct 2
Patrouillierende KI

Schreiben wir nun unseren Pseudocode für die patrouillierende AI des Feindes:

1
function patrollingAI()
2
{
3
 if (facing == "right")
4
 {
5
 walkRight(); // the same as the one in player object / class

6
7
if (collidesWith(rightPatrolBorder))
8
 {
9
 facing = "left";
10
 enemy.mirrorSprite();
11
 }
12
 }
13
14
 if (facing == "left")
15
 {
16
 walkLeft();
17
18
if (collidesWith(leftPatrolBorder))
19
 {
20
 facing = "right";
21
 enemy.mirrorSprite();
22
 }
23
 }
24
}
chapter 2 - patrolling AIchapter 2 - patrolling AIchapter 2 - patrolling AI

Danach patrouilliert der Feind zwischen zwei Punkten, wie wir es wollen.

Um festzulegen, welche AI der Feind verwendet, fügen wir eine weitere Variable mit einem Zeichenfolgentyp für unseren Feind hinzu: die feindliche AI. Das bestimmt, welche AI für jeden Frame verwendet werden soll, wie folgt:

1
if (enemyAI == "switching")
2
{
3
 switchingAI();
4
}
5
else if (enemyAI == "patrolling")
6
{
7
 patrollingAI();
8
}

Natürlich können Sie weitere feindliche AI-Typen hinzufügen, wenn Sie möchten.


Schussvariation

Lassen Sie uns fortfahren, wie wir Schussvariationen sowohl für den Spieler als auch für den Feind vornehmen können. Wir machen Schussvariationen, indem wir zwei Dinge ändern: den Schusswinkel und die Anzahl der Schüsse.

Auf diese Weise können wir einen einfachen Schuss mit einer Kugel oder einen Schuss mit drei Richtungen machen. Bevor wir dies tun, werden wir eine weitere Variable für das feindliche Objekt/die feindliche Klasse namens shotAI erstellen, bei der es sich um eine Zeichenfolge handelt. Wir werden dies in unseren checkSees() verwenden, um zu überprüfen, ob der Feind blockiert. Die Änderungen an diesem Codeblock sehen folgendermaßen aus:

1
// can be in update() or somewhere else that's executed every frame

2
function update()
3
{
4
 if (checkSees())
5
 {
6
 if (shotAI == "simple")
7
 {
8
 shoot("path/to/bulletSprite.png", enemyPower, 400, shotAngle);
9
 }
10
11
if (shotAI == "threeBullets")
12
 {
13
 shootThreeBullets();
14
 }
15
 }
16
}
chapter 2 - enemy shootingchapter 2 - enemy shootingchapter 2 - enemy shooting

Natürlich liegt der Name der AI und die Art des Schusses, den der Feind abfeuern würde, bei Ihnen.

Das ist nur ein Beispiel.Lassen Sie uns nun tiefer in das eintauchen, was sich in der Funktion shootThreeBullets() befindet.

1
Function shootThreeBullets()
2
{
3
 if (facing == "right")
4
 {
5
 shoot("path/to/bulletSprite.png", enemyPower, 400, 0); // this bullet goes straight to the right

6
 shoot("path/to/bulletSprite.png", enemyPower, 400, 330); // this goes up by 30 degrees

7
 shoot("path/to/bulletSprite.png", enemyPower, 400, 30); // this goes down by 30 degrees

8
 }
9
10
if (facing == "left")
11
 {
12
 shoot("path/to/bulletSprite.png", enemyPower, 400, 180); // this bullet goes straight to the left

13
 shoot("path/to/bulletSprite.png", enemyPower, 400, 210); // this goes up by 30 degrees

14
 shoot("path/to/bulletSprite.png", enemyPower, 400, 150); // this goes down by 30 degrees

15
 }
16
}

Wenn Sie sich nicht sicher sind, warum 0 nach rechts und 180 nach links geht, liegt die Richtung von 0 Grad direkt auf der rechten Seite des Bildschirms, 90 Grad auf der Unterseite des Bildschirms usw., bis sie trifft 360 Grad. Sobald Sie wissen, welcher Wert wohin geht, können Sie Ihre eigene Schussvariante erstellen.

Wir können auch eine shotAI-Variable für den Player erstellen, aber ich bevorzuge, dass wir sie selectedShot nennen, da unser Player die Kugel anstelle der von Anfang an programmierten Kugel auswählt.

Die Logik in Megaman ist, dass jedes Mal, wenn Megaman einen Boss besiegt, er die Macht dieses Bosses als neuen Schuss erhält. Ich werde versuchen, diese Logik neu zu erstellen. Dazu benötigen wir ein Array, das die Schüsse des Spielers enthält, einschließlich normaler Schüsse. Der Pseudocode lautet wie folgt:

1
var shotArr = ["normalShot", "boss1", "boss2"];
2
var shotIndex = 0;
3
var selectedShot = "normalShot";
4
5
function update()
6
{
7
 // this is the block of code in player's update function where the player shoots a bullet

8
9
// this function changes the bullet that the player shoots

10
 changeBullet();
11
12
// player press this key once

13
 if (keyPressed(KEY_Z))
14
 {
15
 if (facing == "right")
16
 {
17
 hero.animate("Shoot");
18
 if ( selectedShot == "normalShot")
19
 {
20
 hero.shoot("path/to/sprite.png", 10, 400, 0);
21
 }
22
 else if ( selectedShot == "boss1")
23
 {
24
 // add codes to shoot the kind of shot that the player received after defeating boss 1

25
 }
26
 }
27
 }
28
}
29
30
function changeBullet()
31
{
32
 // changes shotIndex based on button pressed

33
 if (keyPressed(KEY_E))
34
 {
35
 shotIndex += 1;
36
 }
37
38
if (keyPressed(KEY_Q))
39
 {
40
 shotIndex -= 1;
41
 }
42
43
// fix shotIndex if it's out of array's length

44
 if (shotIndex == shotArr.length)
45
 {
46
 shotIndex = 0;
47
 }
48
49
if (shotIndex < 0)
50
 {
51
 shotIndex = shotArr.length -- 1;
52
 }
53
54
selectedShot = shotArr[shotIndex];
55
}
two new variables

Wir müssen zwei neue Variablen im Auge behalten:

how to add an array to construct 2how to add an array to construct 2how to add an array to construct 2
So fügen Sie ein Array in Construct 2 hinzu
chapter 2 - function change bulletchapter 2 - function change bulletchapter 2 - function change bullet

Wir werden shotArr neue Elemente hinzufügen, wenn der Spieler einen Boss besiegt.


Aktualisieren der Player Bullets

Genau wie beim ShootThreeBullet() des Feindes können Sie kreativ sein und Ihre eigenen Schussvarianten erstellen. Da dies die Kugel des Helden ist, geben wir ihm etwas Besonderes.

Lassen Sie uns eine Art von Schuss machen, um gegen einen bestimmten Boss effektiv zu sein, damit er mehr Schaden verursacht. Zu diesem Zweck erstellen wir eine Variable für das Bullet-Objekt mit dem Namen strongAgainst, eine weitere Variable vom Typ String, die den Namen des Chefs enthält, gegen den dieses Bullet wirksam ist. Wir werden hinzufügen, dass dies mehr Schadensfunktionalität verursacht, wenn wir den Boss-Teil des Spiels besprechen.


Gesundheit und Tod

Hier beginnen alle Schussvariationen, die wir machen, wirklich eine Rolle zu spielen. Hier beschädigt und tötet unser Held den Feind und umgekehrt.

Lassen Sie uns zunächst eine Variable sowohl für den Helden als auch für das feindliche Objekt erstellen, die als Gesundheit bezeichnet wird und eine weitere Variable nur für den Helden namens Leben ist. Schauen wir uns den Pseudocode an:

1
if (bullet.collidesWith(hero))
2
{
3
 hero.health -= bullet.power;
4
 createExplosion(); // for now we don't have an explosion sprite, so this will act as a reminder

5
}
6
7
// check if the hero is dead

8
if (hero.health <= 0)
9
{
10
 hero.lives -= 1; // decreases hero's total number of lives.

11
 destroyHero();
12
}

Wir werden den gleichen Pseudocode für schädliche Feinde erstellen, wie folgt:

1
if (bullet.collidesWith(enemy))
2
{
3
 enemy.health -= bullet.power;
4
 createExplosion();
5
}
6
7
if (enemy.health <= 0)
8
{
9
 destroyEnemy();
10
}
bullet collides with player and enemybullet collides with player and enemybullet collides with player and enemy
Umgang mit Kugelkollisionen zwischen Spieler und Feind

Die Health Bar GUI

Wenn ich es dabei belasse, wäre es nicht interessant. Also mache ich ein Rechteck-Sprite in der oberen linken Ecke des Bildschirms, das als Gesundheitsleiste unseres Helden fungiert.

Die Länge dieser Gesundheitsanzeige wird sich abhängig von der aktuellen Gesundheit unseres Helden ändern. Die Formel zum Ändern der Länge des Gesundheitsbalkens lautet wie folgt:

1
// this is in the update function

2
healthBar.width = (hero.health / hero.maxHealth) * 100;
chapter 2 - health barchapter 2 - health barchapter 2 - health bar

Wir brauchen eine weitere Variable für unseren Helden namens maxHealth. Der volle Gesundheitswert unseres Helden. Im Moment kann dieser Wert nicht geändert werden, aber vielleicht können wir in Zukunft einen Gegenstand erstellen, der die Menge an maxHealth des Helden erhöht.


Erstellen Sie die Spielwelt

Nachdem wir unsere Helden-, Feind- und Schussvarianten erstellt haben, müssen wir mehrere Level und Bosse erstellen.

Mehrere Level zu haben bedeutet, dass der Spieler irgendwann im Spiel einen oder mehrere Kontrollpunkte erreicht, die das Spiel von Level 1-1 auf Level 1-2 auf Level 1-3 usw. umschalten, bis er den Boss erreicht.

Wenn der Spieler irgendwo in Level 1-2 stirbt, muss er nicht mehr vom Anfang von Level 1-1 zurückspielen. Wie mache ich das? Zuerst werden wir das Level erstellen, ich werde nicht viel über das Level-Design erklären, aber hier ist das Beispiel-Level in Construct 2.

sample level designsample level designsample level design
Sample Level Design

Das Bild in der oberen linken Ecke ist die HUD-Ebene. Es wird gescrollt und folgt dem Helden, wenn das Spiel gespielt wird.


Türen und Kontrollpunkte

Ein Sprite, auf das Sie achten sollten, ist das grüne Sprite im oberen rechten Teil des Levels. Es ist der Kontrollpunkt in diesem Level, wenn der Held damit kollidiert. Wir werden das Spiel auf Level 1-2 übertragen.

Um mehrere Ebenen verarbeiten zu können, benötigen wir drei Variablen: currentLevel, levelName und nextLevel.

Die Variable currentLevel wird im Heldenobjekt / in der Heldenklasse erstellt. Der Levelname wird im Objekt der Spielszene (Level) für jedes Level erstellt. Die Variable nextLevel wird im grünen Sprite-Objekt erstellt.

Die Logik lautet wie folgt: Wenn der Held mit dem grünen Sprite kollidiert (ich nenne es greenDoor), ändern wir unser Level in die Spielszene, in der levelName mit der nextLevel-Variablen identisch ist. Nachdem wir das Level geändert haben, ändern wir den Wert der currentLevel-Variablen des Helden auf den gleichen Wert wie den levelName der Spielszene. Hier ist der Pseudocode:

1
// this is inside game's update function

2
if (hero.collidesWith(greenDoor))
3
{
4
 changeLevelTo(greenDoor.nextLevel);
5
}
change level when touches green doorchange level when touches green doorchange level when touches green door
Ändern Sie die Ebenen, wenn wir die grüne Tür berühren

Ein neues Level initialisieren

Hier ist der Pseudocode, mit dem Sie umgehen können, wenn das nächste Level geladen und spielbereit ist.

1
// this is the function that's triggered when the new level is loaded

2
function onStart()
3
{
4
 hero.currentLevel = scene.LevelName;
5
 hero.x = startPos.x;
6
 hero.y = startPos.y;
7
}
chapter 3 - start of layoutchapter 3 - start of layoutchapter 3 - start of layout

Die Startpositionen des Spielers

Nachdem wir auf ein neues Level gewechselt sind, werde ich das orangefarbene Sprite hinter unserem Helden im obigen Level-Design-Bild erklären. Dieses orangefarbene Sprite ist ein Objekt, das ich startPos nenne. Es wird verwendet, um die Startposition jedes Levels zu markieren.

Wir beziehen uns auf dieses Objekt, als der Held gerade das Level geändert hat oder gestorben ist, damit wir wissen, wo wir ihn hervorbringen müssen.

Hier ist der Pseudocode für die Behandlung, wenn der Held stirbt:

1
// the function that's triggered when the hero is destroyed

2
Function onDestroyed()
3
{
4
 // revives hero if the hero still have lives.

5
 If (hero.lives > 0)
6
 {
7
 var newHero = new Hero();
8
 newHero.x = startPos.x;
9
 newHero.y = startPos.y;
10
 }
11
}

Jetzt können wir mehrere Level haben und den Helden auch nach seinem Tod wieder erscheinen lassen.

Sie können so viele Level erstellen, wie Sie möchten, oder sogar zwei greenDoor-Objekte in einem Level erstellen, von denen eines auf Level 1-1 zurückgeht, wenn der Spieler ein Rätsel falsch löst.


Bosskämpfe

Es ist endlich Zeit, den Chef selbst zu implementieren. Ein Boss-Level zu erreichen ist so einfach wie ein anderes Level zu machen, das einen Boss anstelle von regulären Feinden hervorbringt.

Der schwierige Teil ist das Erstellen einer KI für den Boss, da jeder Boss eine einzigartige KI hat. Um es vielen Bossen leicht zu machen, sie zu verstehen und zu duplizieren, werde ich die KI des Chefs von der Zeit abhängig machen, nachdem sie erzeugt wurden. Was bedeutet, dass sie x Sekunden lang A ausführen, dann y Sekunden lang zu B und dann z Sekunden lang C ausführen, bevor sie zu A zurückkehren. Der Pseudocode sieht ungefähr so aus:

1
// this code is inside the boss update function, so it's executed every frame

2
if (elapsedTime() > 2.0)
3
{
4
 // this if block is executed for 3 seconds, because the difference in time with the if block below

5
 // is three seconds.

6
 BossShot1(); // shot variation to be executed this time

7
}
8
else if (elapsedTime() > 5.0)
9
{
10
 bossShot2();
11
}
12
else if (elapsedTime() > 6.0)
13
{
14
 bossShot3();
15
}
16
17
if (elapsedTime() > 7.0)
18
{
19
 // reset the time so the boss executes the first action again

20
 resetsTime();
21
}
chapter 3 - boss AIchapter 3 - boss AIchapter 3 - boss AI

Die Definition der Boss-Schussfunktionen ist unten. Fühlen Sie sich frei, es zu ändern, um zu passen, was Sie für einen Bosskampf wollen:

1
function bossShot1()
2
{
3
 // a simple straight shot

4
 bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle); // shotAngle is 180

5
}
6
7
function bossShot2()
8
{
9
 // a three direction bullets shot

10
 bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle);
11
 bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle + 30);
12
 bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle - 30);
13
}
14
15
function bossShot3()
16
{
17
 // for this one, I'm going to make a circle shot, so the bullets will form a circle

18
 for (var i = 0; i <= 9; i++)
19
 {
20
 bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, 36 * i); // change the shotAngle

21
 }
22
}

Es liegt an Ihnen, der Routine des Chefs Schussvariationen hinzuzufügen. Die vom feindlichen Objekt des Bosses verwendeten Variablen sind dieselben wie die des feindlichen Objekts, außer dass die Bosse die Variablen feindAI und shotAI nicht verwenden, da beide abhängig von der verstrichenen Zeit im obigen if-Block behandelt werden.

Boss-Feinde haben auch eine Variable, die feindliche Objekte nicht haben. Es heißt RewardShot, eine Zeichenfolge. Diese Variable enthält den Namen eines Boss-Schusses, den der Spieler erhält, nachdem er den Boss besiegt hat (die in der Array-Variable shotArr).

Auf diese Weise kann der Spieler den Boss-Angriff wie zuvor erläutert "lernen". Um diesen Schuss-Typ zum Array der Spieler-Schüsse hinzuzufügen, müssen wir nach dem Tod des Feindes den folgenden Code hinzufügen:

1
function bossShot1()
2
{
3
    // a simple straight shot

4
    bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle); // shotAngle is 180

5
}
6
7
function bossShot2()
8
{
9
    // a three direction bullets shot

10
    bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle);
11
    bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle + 30);
12
    bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, shotAngle - 30);
13
}
14
15
function bossShot3()
16
{
17
    // for this one, I'm going to make a circle shot, so the bullets will form a circle

18
    for (var i = 0; i <= 9; i++)
19
    {
20
        bossEnemy.shoot("path/to/bullet/sprite.png", bossPower, 400, 36 * i); // change the shotAngle

21
    }
22
}

Um zusätzliche Aufzählungszeichen für Ihre Spieler zu implementieren, müssen Sie lediglich den entsprechenden Code für jede Belohnung hinzufügen, die Sie erstellt haben, genau wie zuvor.


Herzliche Glückwünsche!

Sie haben mein Tutorial zum Erstellen eines Megaman-ähnlichen Metroidvania-Plattformspiels in Construct 2 abgeschlossen. Der Rest baut Ihr Spiel nur auf dem auf, was Sie hier gelernt haben, um es zu Ihrem eigenen zu machen. Füge mehr Feindtypen, mehr Levels, Powerups und mehr hinzu. Viel Glück und hab Spaß.

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.