Flash

Flash Spieleentwicklung Teil 1 Bricks (Flash Tutorial)

Tutorial erstellt von Labrar in Flash 8, letzte Änderung am 09.06.2008

Flash-Spieleentwicklung Teil 1: Bricks


Hallo!
Ich werde euch hier, wie es meine Zeit zulässt, immer wieder mal einen weiteren Tutorialteil der Flashspieleentwicklung bereitstellen. Jeder Teil für sich erklärt die Erstellung eines Spieles. Ich werde den Schwierigkeitsgrad in jedem weiteren Teil steigern. Arbeitet die Tutorials also der Reihe nach durch.
Im ersten Teil werden wir das allseits bekannte Spiel "Bricks" erstellen. Bilder sind nicht nötig, da die komplette Flash8 fla-Datei zum Download im Downloadbereich bereitsteht.

Für dieses Tutorial solltet ihr wissen, wie man ...

- dynamische Textfelder aufzieht und deren Schrift einbettet
- MovieClips erstellt(F8)
- diese in der Zeitleiste tweent
- Instanznamen vergibt
- und diese in der Bibliothek verknüpft (Rechtsklick in der Bibliothek und verknüpfen)

Wichtig!
Wir programmieren nur in Schlüsselbildern in der Zeitleiste. Also darauf achten, dass im Actionscriptfenster (F9) immer "Aktionen – Bild" steht.


Ansonsten in Flash einfach F1 für die Hilfe drücken. Ist da alles super erklärt.

Ich überlasse euch die grafische Umsetzung und beschränke mich auf Kreise, Quadrate, Rechtecke und Linien, die ich mit den Bordmitteln von Flash erstellt habe. Natürlich könnt ihr Hochglanzgrafiken importieren und verwenden.

Kurze Erklärung zum Spiel (siehe Downloadbereich - Linksklick zum Beginnen und Schießen)

Im Prinzip geht es darum, Steine mit einem Ball abzuschießen, bis keine mehr da sind. Dazu haben wir einen Schläger, der mit der Maus gesteuert wird. Manche Steine müssen zweimal getroffen werden, bis sie verschwinden, manche beinhalten eine Überraschung, die dann (in diesem Beispiel als Fragezeichen) herabfallen und mit dem Schläger aufgefangen werden können.
Doch Vorsicht. Nicht jedes Fragezeichen verleiht unserem Schläger Raketen, mit denen zusätzlich Steine abgeschossen werden können. Manche sind auch ziemlich gemein und erschweren das Spiel.

Kurze Erläuterung zu den wichtigsten Actionscriptbefehlen, die wir verwenden, werden immer nach dem Befehl zu lesen sein. Allerdings gehe ich davon aus, dass ihr zumindest in anderen Programmiersprachen schon etwas Erfahrung habt. Befehle, die nicht erklärt werden, bitte ich in Flash über die Hilfe( F1) abzufragen.

Fangen wir an: MovieClips einrichten

Ich habe eine weisse Bühne mit einer Größe von 540 auf 400 Pixel gewählt. Als Framerate wählen wir 60 FPS.

Als erstes sollte der Ball (schwarzer Kreis) und der Schläger (schwarzes Rechteck) erstellt werden. Natürlich sollte beides nicht überdimensional groß sein, der Ball z.B. 15 Pixel im Durchmesser und der Schläger
vielleicht 80 auf 10 Pixel. Beides wird in einen MovieClip konvertiert (F8), wobei der Ball nun den Bühneninstanznamen "mc" und der Schläger den Bühneninstanznamen "pouk" je ohne " bekommt.

Die Instaznamen könnt ihr natürlich frei wählen. Ich habe mich für diese entschieden. Achtet darauf, dass falls ihr andere verwendet, dies auch bei der Programmierung berücksichtigt wird.

Den Schläger platzieren wir am unteren Rand des Spielfeldes. Wo auf der x-Achse, ist egal, da wir das später mit AS (Actionscript) festlegen. Die Ballposition wird später komplett über AS gesteuert.

Wer mit x-Achse nichts anfangen kann: Flash arbeitet wie jedes 2D Grafikprogramm mit einer x-Achse (von links nach rechts, horizontal) und einer y-Achse (von oben nach unten, vertikal).

Damit sich unser Ball bewegt, müssen wir eigentlich nur stetig seine x- und y-Position erhöhen bzw. verringern. Hierfür eignet sich die onEnterFrame-Schleife, da diese erst dann beendet wird, wenn wir sie mit "delete onEnterFrame" wieder löschen.

Die onEnterFrame-Schleife

Eine z.B. for- oder while-Schleife läuft nur solange durch, wie eine Bedingung erfüllt ist. Eine onEnterFrame-Schleife aber wiederholt den virtuellen Frame (Schlüsselbild auf der Zeitleiste) eines MovieClips immer und immer wieder. Zur Verdeutlichung könnt ihr ja einen MovieClip erstellen, und hier ein zweites Schlüsselbild auf die Bildposition 3 setzten. Gebt jetzt bei Schlüsselbild 1

Code:
zahl=0;

ein und bei Schlüsselbild 3

Code:
zahl++; trace(zahl); gotoAndPlay(2);
.
Testet den MovieClip und ihr seht, dass Bild 2 und 3 immer wiederholt werden und sich somit der Wert von "zahl" ständig erhöht. Eine onEnterFrame-Schleife nimmt uns das mühselige Erstellen von Schlüsselbildern ab. Folgende Programmierung hat also gleichen Effekt:

Code:
zahl=0;
_root.onEnterFrame = function()
{
  zahl++;
  trace(zahl);
}

Der Funktion, die dem onEnterFrame-Handler angehängt werden muss, kann auch mit Parametern ausgeführt werden. Wir tun das in unserem Beispiel aber nicht.

So wie sich also auch der Wert "zahl" in der Erklärung erhöht, können wir auch den Wert der x- und y-Position unseres Balles erhöhen/verringern. Der Punkt mit x = 0 und y = 0 befindet sich immer in der linken oberen Ecke eines MovieClips bzw. der Bühne.

Was unseren Ball betrifft, erzielen wir aber bessere Ergebnisse, wenn wir seinen 0-Punkt (Registrierungspunkt) in seine Mitte verschieben. Da wir den Punkt selbst nicht verschieben können, dafür aber den Ball, tun wir dieses: ein Doppelklick auf euren Ball, bis ihr im MC MovieClip seid. Jetzt verschiebt die Ballgrafik so, dass das Registrierungskreuzchen in der Mitte des Balles sitzt.

Los geht’s mit ActionScript

Wir programmieren mit ein paar Ausnahmen ausschließlich im ersten und einzigen Schlüsselbild unseres Filmes. Zunächst sorgen wir für die Ballbewegung. Hierfür legen wir die Ballgeschwindigkeit fest:

Code:
speed = 10;

Unser Ball soll sich also 10 Pixel/Frame bewegen (ich habe ja eine Bildrate von 60 eingestellt). Des weiteren brauchen wir ja auch eine Querstreuung. Der Ball soll ja auch nach links und rechts hüpfen, und zwar je Frame 5 Pixel nach links oder rechts:

Code:
speed = 10;
streu = 5;
drift = streu;
newSpeed = speed;

Wir haben also zwei feste Werte (speed und streu) und zwei variable Werte (drift und newSpeed), die zwar jetzt noch die selben Werte haben, die aber im Laufe des Spiels geändert werden.

Zunächst versuchen wir, den Ball nur vom Schläger an die Decke und wieder zurückhüpfen zu lassen. Positioniert also manuell kurz euren Schläger auf der x-Achse, so dass er unter dem Ball sitzt. Der Ball muss ja zunächst einmal auf den Schläger fallen (nur im Moment, später wird er von unten 'raufgeschossen), d.h. der y-Wert des Balles wird erhöht. Da wir uns im _root, der Wurzel, der untersten Ebene unseres Filmes befinden, werden wir auch das _root als onEnterFrame-Träger verwenden.

Code:
_root.onEnterFrame = function()
{
  mc._y += newSpeed;
}

Was passiert? Testet den Film! Der Ball fällt mit 10 Pixeln/Frame durch den Schläger durch und ins Nirvana. Man müsste also hergehen und newSpeed praktisch ins Negative umdrehen, wenn der Ball den Schläger erreicht hat. Dann (sozusagen mit -newSpeed) bewegt sich der Ball wieder nach oben.

Bounding-Box und hitTest

Jeder MC ist unabhängig von seiner visuellen Form ein Rechteck (die Bounding-Box), das sich nach den äussersten Punkten der sichtbaren Form richtet. Also ist auch unser 15 Pixel breiter Ball zwar sichtbar eine runde Scheibe, für Flash aber nur ein Quadrat 15 auf 15 Pixel.
Zum Glück bietet uns Flash nun hier einen einfachen Abfragebefehl: hitTest. hitTest prüft, ob eine MC-Box (d.h. Bounding-Box des MC) mit einer anderen in Kontakt tritt, und gibt bei Berührung "true" (wahr) zurück:

Code:
_root.onEnterFrame = function()
{
  mc._y += newSpeed;

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
  }
}

Was passiert jetzt? Testen. Der Ball springt vom Schläger ab und schießt ab durch die Decke ins Nirvana. Schon besser, aber auch noch nicht sinvoll für unser Spiel. Also müssen wir newSpeed wieder in eine positive Zahl umwandeln, wenn der Ball am oberen Rand unseres Spielfeldes angekommen ist. Hierzu bedienen wir uns einer einfachen if-Abfrage, die lediglich die y-Position des Balles mit 0(also der Decke) vergleicht und entsprechend reagiert.

Code:
_root.onEnterFrame = function()
{
  mc._y+=newSpeed;

  if(mc._y <= 0)
  {
    newSpeed = speed;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
  }
}

Testen. Ja, so soll's für den Anfang sein.

Da unser Ball später aber auch nach links bzw. rechts hüpfen wird und hierfür die Werte von drift verwendet werden, müssen wir auch hier Maßnahmen ergreifen, die ein Heraushüpfen aus der rechten oder linken Spielfeldseite verhindern und den Ball wieder zurück katapultieren. - Also das Gleiche nochmal mit der x-Achse:

Code:
_root.onEnterFrame = function()
{
  mc._x += drift;
  mc._y += newSpeed;

  if(mc._y <= 0)
  {
    newSpeed = speed;
  }

  if(mc._x > Stage.width)
  {
    drift =- streu;
  }

  if(mc._x <= 0)
  {
    drift = streu;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed =- speed;
  }
}

Wieder testen. So, der Ball hat jetzt schon eine Seitendrift.

Die Schlägersteuerung

Flash bietet uns die Möglichkeit, via startDrag MovieClips mit der Maus zu bewegen. startDrag ist eine Funktion, die 5 Parameter erwartet:

- auf Mauspunkt (wahr oder falsch)
- linker Grenzbereich,
- oberer Grenzbereich
- rechter Grenzbereich
- unterer Grenzbereich

Würden wir den Mauspunkt auf wahr (true)stellen, würde unser Schläger mit seinem 0-Punkt (also linke obere Ecke) an unserer Mausspitze kleben. Das wollen wir nicht, also stellen wir diesen Parameter auf falsch (false).

Die Grenzbereiche helfen uns, den Bereich, in dem sich der Schläger bewegt, einzugrenzen. Den linken Grenzbereich stellen wir auf 0, also den linken Rand unseres Spielfeldes. So verhindern wir, dass der Schläger über den linken Spielrand hinausgezogen wird.

Code:
l = 0;

Da der Schläger nur auf der X Achse bewegt werden soll, können wir die obere und untere Grenze auf den bisherigen y-Stand begrenzen.

Code:
o = pouk._y;
u = pouk._y;

Nun muss noch der Weg nach rechts begrenzt werden, und zwar so, dass der Grenzpunkt der rechte Spielrand minus Schlägerbreite ist. Wieso minus Schlägerbreite? Der 0-Punkt unseres Schlägers ist der Punkt ganz links am Schläger. Würden wir also seine Weite nicht mit ein berechnen, würde später unser Schläger soweit nach rechts rutschen, dass seine linke Seite an der rechten Spielfeldseite stoppt und wir dabei unseren Schläger mit seiner ganzen Länge rechts ausserhalb des Spielfeldes finden würden. Auch hier bietet uns Flash einfache Möglichkeiten.

Um die Breite eines MoviClips in Pixeln abzufragen verwendet man den Befehl meinmc._width; und für die Höhe meinmc._height;. Genauso können wir auch die Bühnengröße in Breite und Höhe abfragen:

Code:
Stage.width;
Stage.height;         // bei Stage: width/height ohne Unterstrich


Beachtet den Unterschied mit dem Unterstrich. Stagedimensionen werden niemals Unterstriche vorangestellt.

Also können wir unseren Schläger so bewegen:

Code:
_root.onEnterFrame = function()
{
  mc._x += drift;
  mc._y += newSpeed;
  
  if(mc._y <= 0)
  {
    newSpeed = speed;
  }
  
  if(mc._x > Stage.width)
  {
    drift = -streu;
  }
  
  if(mc._x <= 0)
  {
    drift = streu;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
  }
}

o = pouk._y;
u = pouk._y;
l = 0;
r = Stage.width - pouk._width;

pouk.startDrag(false,l,o,r,u);


Wieder testen. Na also. Nur klebt der Schläger dennoch mit seinem 0-Punkt an unserer Mausspitze. Wir bauen also noch folgende Zeile an:

Code:
pouk._x =_xmouse - (pouk._width/2);

Also im Ganzen:

Code:
_root.onEnterFrame = function()
{
  mc._x += drift;
  mc._y += newSpeed;

  if(mc._y <= 0)
  {
    newSpeed = speed;
  }

  if(mc._x > Stage.width)
  {
    drift = -streu;
  }

  if(mc._x <= 0)
  {
    drift = streu;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
  }
}

o = pouk._y;
u = pouk._y;
l = 0;
r = Stage.width - pouk._width;

pouk.startDrag(false,l,o,r,u);
pouk._x = _xmouse - (pouk._width/2);


Testen. Sehr schön.

Seitendrift des Balls

Noch schöner wäre es aber, wenn wir den Seitendrift noch steuern könnten. Am besten in dem wir den Schläger so bewegen, dass der Ball weiter außen am Schläger bzw. innen auf trifft. So könnte sich der Seitendrift ja um so größer ausfallen, je weiter der Ball an die Außenseiten des Schlägers trifft.

Hierzu verwenden wir einfach mal den aus der Schule bekannten Dreisatz und Prozentrechnung. Also drift ist von Haus aus durch unsere Variable streu am Anfang auf 5 gestellt: 5 = 100%.
Die Länge unseres Schlägers ist ebenfalls = 100%. Da wir aber sowohl negativ Werte (Drift nach links) wie auch positiv Werte (Drift nach rechts) benötigen und diese anhand des Auftreffens auf den Schläger gemessen an der Schlägermitte haben wollen, müssen wir zunächst mit 200 % rechnen und dieser Zahl später 100 subtrahieren. Also:

Code:
pr = 200/(pouk._width)*(mc._x - pouk._x);
walk = pr - 100;
drift = (streu/100*walk)*2;

Verständlich? Im Ganzen:

Code:
_root.onEnterFrame = function()
{
  mc._x += drift;
  mc._y += newSpeed;

  if(mc._y <= 0)
  {
    newSpeed = speed;
  }

  if(mc._x > Stage.width)
  {
    drift = -streu;
  }

  if(mc._x <= 0)
  {
    drift = streu;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
    pr = 200/(pouk._width)*(mc._x - pouk._x);
    walk = pr - 100;
    drift = (streu/100*walk)*2;
  }
}
o = pouk._y;
u = pouk._y;
l = 0;
r = Stage.width - pouk._width;

pouk.startDrag(false,l,o,r,u);
pouk._x = _xmouse - (pouk._width/2);


Wieder testen. Super gell? Und vor allem simpel.

Die Steine

Jetzt kommen die abzuschießenden Steine ins Spiel.

Erstellt also 3 verschiedene Rechtecke (vielleicht 38 auf 16 Pixel). Ich würde zwei in der gleichen Farbe erstellen und eine in einer anderen. Wandelt diese in MovieClips um und packt diese jede für sich wieder in einen MovieClip, in dem ihr dann die Steine jeweils tweent. Ihr solltet also drei MovieClips mit jeweils einem MovieClipStein haben.

Die Tweens könnt ihr bei allen gleich machen. Ich habe das erste Schlüsselbild auf Bild 5 und Bild 10 kopiert, den Stein auf Bild 5 über die Eigenschaften (Flashbordmittel) stark erhellt und dann zwischen Bild 1 und Bild 5 und Bild 5 und Bild 10 einen Bewegungstween erstellt. Somit ensteht ein Aufblinken, was ja schließlich beim Treffen mit dem Ball passieren soll. Bild 1 jedes der drei MovieClips muss immer mit einem

Code:
stop();

programmiert werden.

Bild 10 wird nun bei jedem MovieClip etwas anders programmiert. Ich werde hier euch zuliebe schon die kompletten Codes darstellen, so dass ihr später nicht ständig zurück müsst, um die Codes zu ergänzen. Also bitte nicht wundern, wenn Variablen aufgezählt werden, die wir bisher noch gar nicht hatten ;)

Schlüsselbild 10 unseres "normalen" Spielsteines, welchen wir mit dem Verknüpfungsnamen 'colbrick' versehen werden, sieht so aus:

Code:
_root.point += 50;
_root.num++;
this.removeMovieClip();

_root kennen wir ja schon. Im _root werden wir später noch die Variablen point und num in's Leben rufen. point steht dann für die Punktzahl, die der Spieler erreicht hat. Wenn einer normaler Stein getroffen wird, gibt’s 50 Punkte dazu: _root.point += 50;

Da dieser Stein durch this.removeMovieClip(); gelöscht wird, sollte sich ja auch die Anzahl der abgeschossenen Steine erhöhen. Sonst weiß unser Flash ja nicht, wann das Level erfolgreich gemeistert wurde - dazu dient die Variable num: _root.num++; Simpel bisher oder?

Etwas anderst sieht es bei unserem andersfarbigen Stein aus, den wir übrigens mit dem Namen 'brick' verknüpfen. Dieser muss zweimal getroffen werden, bis er verschwindet und es gibt nur 5 Punkte zusätzlich:

Code:
hit++;
_root.point += 5;

if(hit==2)
{
  _root.num++;
  this.removeMovieClip();
}

Die Variable hit werden wir später im _root ins Leben rufen und mit dem Wert 0 füllen. Ansonsten erklärt sich der Code von selbst.

Die größte Herausforderung ist unser Überraschungsstein, der die selbe Farbe hat wie unser normaler erster Stein. Denn aus ihm fallen später die Fragezeichen heraus, die man dann mit dem Schläger fangen kann. Dieser Stein wird mit dem Namen 'trapbrick' verknüpft und erhält folgenden Code in Bild 10:

Code:
stop();
_root.symX = _root['b' + ebene]._x;
_root.symY = _root['b' + ebene]._y;

_root.attachMovie("symb", "symb" + ebene, (ebene + 500));

_root['symb' + ebene].fall(_root.symX, _root.symY);

_root.num++;

this.removeMovieClip();


Erklärung:
Alle Spielsteine werden später allesamt den Instanznamen 'b' gefolgt von der jeweiligen Ebenenzahl haben. Also b0, b1, b2 usw. Dies dient zum einen natürlich zur Identifizierzung und zum anderen natürlich auch zur Ebenenangabe, auf welcher der Stein erstellt, bzw. aus der Bibliothek attacht wird.

Merke:
Für jede dynamische Ebene (Level) je MovieClip oder Bühne kann nur ein MovieClip positioniert werden. Ein erneutes Erstellen eines MovieClips auf der selben Ebene würde den vorherigen überschreiben.


Gehen wir den Code Zeile für Zeile durch:
Im _root werden wir später die x- und y-Position der Fragezeichen festlegen, so dass diese auch wirklich den Anschein erwecken, aus dem jeweiligen Stein zu fallen. Also vergeben wir hier die momentane x- und y-Position des Steins als Startposition. symX und symY werden natürlich je nach Stein immer wieder überschrieben, was aber nicht schlimm ist, da wir das Fragezeichen ja direkt im Anschluss attachen (dynamisch aus der Bibliothek auf die Bühne holen) und an die entsprechenden Koordinaten verfrachten.
Das Fallen des Fragezeichens übernimmt dann ein später noch entstehender Prototype namens fall, der mit den x- und y-Koordinaten gestartet wird.

Ansonsten ja wie gehabt. Das Attachen möchte ich aber nochmal erklären.

Code:
_root.attachMovie("symb","symb"+ebene,(ebene+500));

In das _root wird ein MovieClip mit dem Verknüpfungsnamen 'symb' gezogen, welchem dann der Instanznamen 'symb'+ebene (z.B. symb0) gegeben wird. Damit der MovieClip in keinem Fall von einem andern Fragezeichen überschrieben werden kann und gewährleistet ist dass er eine eigene Ebene hat, wird also Ebene ebene+500 ("ebene" wird im _root später noch deklariert) hierfür verwendet.

Das Fragezeichen könnt ihr im Übrigen schon erstellen, Verküpfungsname 'symb'. Animiert  es, wie ihr wollt. Bitte überprüft, ob ihr Steine und Fragezeichen schon verknüpft habt,  denn jetzt geht’s ums Anordnen der verschiedenen Level (Steinanordung).

Zurück ins _root, ins erste und einzige Schlüsselbild.

Steinanordnung bei den Leveln

Ich hab mir jetzt nicht die Mühe gemacht, alle der drei im Beispiel verwendeten Levels zu strukturieren. Im ersten ordne ich die Steine im simplen Modulo verfahren an (weil es einfach ist), im zweiten mit einfachen Cosinus- und Sinusberechungen und im dritten mit Zufallszahlen.
Da dadurch nur Level 1 strukturiert aussieht, werde ich auch nur diesen Levelaufbau überflogen erklären. Ihr könnt natürlich andere Strukturen verwenden und so viele Level wie ihr wollt einbauen.
Ich habe hier alles dynamisch gemacht, weil's einfach schneller geht. Wenn ihr es richtig machen wollt, dann würde ich zunächst manuell eure Steine so platzieren, wie ihr es haben wollt, mir die Koordinaten heraus schreiben von jedem Stein, und dann diese Stein für Stein per AS vergeben.

Da ja jeder im Level 1 anfängt, solltet ihr also vor euren bisherigen Code vorerst die Zeile

Code:
lev=1;

setzen. Wir werden den Code später in Funktionen aufstrukturieren und somit aufsplitten. Auch unsere Level werden wir in Funktionen packen, die wir dann mit

Code:
_root['level'+lev]();

abrufen werden. Also heißt jede unserer Levelfunktionen 'level'+Levelzahl. Somit fängt unsere Funktion für level1 so an:

Code:
level1 = function(){

Wie gesagt, überfliege ich den gesamten Code nur (bzw. beschränke mich auf das Wichtige und lasse die Anordnung weg), da ihr sicherlich einen eigenen Levelaufbau wünscht.

Code:

level1 = function()
{
  brickzahl = 10;
  colzahl = 40;
  bricks = brickzahl + colzahl;

  for(i=0; i<brickzahl; i++)
  {
    attachMovie("brick", "b"+i, i);

    _root['b'+i].hit = 0;
    _root['b'+i]._y = 100;
    _root['b'+0]._x = 0;
    _root['b'+i]._x = _root['b'+(i-1)]._x + _root['b'+(i-1)]._width + (_root['b'+0]._width/2);
  }

  anycase  = random(brickzahl+colzahl);
  anycase2 = random(brickzahl+colzahl);
  anycase3 = random(brickzahl+colzahl);

  for(a=brickzahl; a<(brickzahl+colzahl); a++)
  {
    zh = random(colzahl - a);
    count = brickzahl + zh;
    
    if(a==count || a==anycase || a==anycase2 || a==anycase3)
    {
      attachMovie("trapbrick", "b"+a, a);
      _root['b'+a].ebene = a;
    }
    else
    {
      attachMovie("colbrick", "b"+a, a);
    }

    _root['b'+a].hit = 0;
    _root['b'+a]._y = 80;
    _root['b'+brickzahl]._x = 0;
  }

  //modulo
  lines = 4;
  rows = Math.ceil(colzahl/lines);
  st = brickzahl;
  
  for(h=0; h<lines; h++)
  {
    for(v=0; v<rows; v++)
    {
      _root['b'+st]._x = v * (_root['b'+st]._width + 4) + 70;
      _root['b'+st]._y = h * (_root['b'+st]._height + 4) + 20;
      st++;
    }
  }
  //moduloende
}

und darunter dann unser

Code:
_root['level'+lev]();

Wichtig in diesem Codeabschnitt sind brickzahl, colzahl, bricks und die anycase-Variablen. Der Rest sind euch sicher bekannte for-Schleifen, die die Steine entsprechend annordnen. Spielt einfach mal damit herum. Dabei lernt man am besten. Nur bitte bedenken, dass die Steine jetzt also b0,b1,b2,b3 usw. heißen.

brickzahl gibt den Mengenwert der Steine an, die zweimal getroffen werden müssen.
colzahl gibt den Mengenwert der restlichen Steine an, sowohl normale als auch Fragezeichensteine.
bricks ergibt den Gesamtmengenwert der Steine, welcher später mit unserer Variable num (erinnert ihr euch) verglichen wird. Ist num = bricks heißt das "Level geschafft".
anycase/2/3 sind dann Zufallswerte aus den Zahlen, die brickzahl und colzahl addiert ergeben. Ich hätte auch random(bricks) schreiben können, aber dann wäre der Lerneffekt nicht so präsent.

Die anycase-Werte sind die Werte, in denen in jedem Fall ein Fragezeichensteinchen anstelle eines normalen Steinchens attacht wird. D.h. wenn anycase z.B. 30 ist, so wäre dann b30 ein Fragezeichensteinchen.

Die Variablen zh und count tun das Gleiche. Da hier aber nicht gewährleistet ist, dass in jedem Fall ein Fragezeichensteinchen attachet wird, verwende ich hier die 3  anycases, die sich von (Spiel-)Level zu Level in der Anzahl verringern. Soll ja schwerer werden ;)

Wenn ihr jetzt testet, ist unser erstes Level schon aufgebaut. Blöderweise fliegt unser Ball durch die Steine hindurch. Da er aber schließlich an ihnen abprallen soll, müssen wir folgende Zeilen in unsere onEnterFrame-Schleife von vorhin einbauen:

Code:
for(i=0; i<bricks; i++)
{
  if(mc.hitTest(_root['b'+i]))
  {
    _root['b'+i].play();

    if(mc._y < _root['b'+i]._y)
    {
      newSpeed = -speed;
    }
    else
    {
      newSpeed = speed;
    }
  }
}


Die onEnterFrame-Schleife nun also im Ganzen:

Code:

_root.onEnterFrame = function()
{
  mc._x += drift;
  mc._y += newSpeed;

  for(i=0; i<bricks; i++)
  {
    if(mc.hitTest(_root['b'+i]))
    {
      _root['b'+i].play();

      if(mc._y<_root['b'+i]._y)
      {
        newSpeed = -speed;
      }
      else
      {
        newSpeed = speed;
      }
    }
  }
  
  if(mc._y <= 0)
  {
    newSpeed = speed;
  }

  if(mc._x > Stage.width)
  {
    drift =- streu;
  }

  if(mc._x <= 0)
  {
    drift = streu;
  }

  if(mc.hitTest(pouk))
  {
    newSpeed = -speed;
    pr = 200/(pouk._width)*(mc._x - pouk._x);
    walk = pr - 100;
    drift = (streu/100*walk)*2;
  }
}

o = pouk._y;
u = pouk._y;
l = 0;
r = Stage.width - pouk._width;

pouk.startDrag(false,l,o,r,u);
pouk._x = _xmouse - (pouk._width/2);

D.h. also: Es wird geprüft, ob der Ball einen Stein trifft und wo er ihn trifft, an der Ober- oder an der Unterseite. Entsprechend wird newSpeed negativ oder positiv. Der drift muss nicht verändert werden. - Bitte testen.

So. Wer anständig mitgemacht hat und auch mit dem Kopf dabei war, der könnte jetzt theoretisch das Spiel fertig programmieren ;). Für alle anderen ...

Weiter im Text - der prototype "fall"

Wenn jetzt eine Fragezeichensteinchen getroffen wird, soll ja auch das nun entstandene Fragezeichen fallen, damit es aufgefangen werden kann (oder auch nicht). Hierfür rufen wir den "prototype" fall ins Leben:

Code:
MovieClip.prototype.fall = function(x,y)
{
  this._x = x;
  this._y = y;
  this._xscale = 50;
  this._yscale = 50;

  this.onEnterFrame = function()
  {
    this._y += 5;

    if(this._y > (Stage.height + this._height))
    {
      delete this.onEnterFrame;
      this.removeMovieClip();
    }

    if(this.hitTest(pouk))
    {
      cases();
      delete this.onEnterFrame;
      this.removeMovieClip();
    }
  }
}


this steht in diesem Fall für den entsprechenden Fragezeichen-MovieClip, welcher diesen Prototypen ausführt. Die Parameter x und y wurden ja im Fragezeichensteinchen bei Bild 10 übergeben. Ansonsten ist das ganze wohl selbsterklärend.

cases() - Wenn ein Fragezeichenstein aufgefangen wird

Das Fragezeichen erhöht seinen y-Wert solange, bis es entweder aus dem Spielfeldboden gefallen ist oder vom Schläger aufgefangen wird. In letzterem Fall wird die Funktion cases aufgerufen, die wir jetzt durchnehmen. Es sollte ja irgendetwas passieren, wenn man ein Fragezeichen fängt - ob nun etwas Gutes oder etwas Schlechtes. Hierfür also die Funktion cases, die so aufgebaut sein könnte:

Code:
cases = function()
{
  caseArray = new Array("small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "doubleshooter", "tripleshooter", "slow", "fast", "doublepoints", "triplepoints");

  newCase = caseArray[random(caseArray.length)];

  switch(newCase)
  {
    case "small":
      pouk._xscale = 50;
      gun.removeMovieClip();
      break;

    case "wide":
      pouk._xscale = 150;
      gun.removeMovieClip();
      break;

    case "shooter":
      attachMovie("singlegun", "gun", 700);
      break;

    case "doubleshooter":
      attachMovie("doublegun", "gun", 700);
      break;

    case "tripleshooter":
      attachMovie("triplegun", "gun", 700);
      break;

    case "slow":
      speed = speed/2;
      gun.removeMovieClip();
      break;

    case "fast":
      speed = speed*2;
      gun.removeMovieClip();
      break;

    case "doublepoints":
      points = points*2;
      break;

    case "triplepoints":
      points = points*2;
      break;
  }
}

Am Anfang erstelle ich also ein Array, welches die verschiedenen Fälle als Elemente in sich trägt. Wie ihr seht, kommen einige mehrfach drin vor, während einige nur einmal  vorkommen. Diese Elemente werden hier per Zufallszahl ausgewählt. Um das Spiel interessanter zu machen, habe ich also Superwaffen wie den doubleshooter und tripleshooter sowie die Punkteverdoppler nur einmal hineingepackt, damit deren Wahrscheinlichkeit des Auftretens verringert wird. Schließlich folgt eine switch-Abfrage, die dann gemäß dem durch Zufall erschienenen Element die ganzen Gemeinheiten bzw. Vorzüge ins Leben ruft.

Der Reihe nach:

pouk._xscale=50;
_xscale skaliert die Breite (x-Achse) des MovieClips prozentual. Sprich der Schläger schrumpft auf 50% seiner Normalbreite. Das Höhen-Pendant ist _yscale.

Gun.removeMovieClip(); löscht die Waffe (falls vorhanden). Man soll ja nur eines  haben. Fies, gell ;)

pouk._xscale=150;
Genauso wie oben, nur dass der Schläger um 150% breiter wird.

attachMovie("singlegun","gun",700);

singlegun wurde noch nicht erstellt. Das könnt ihr jetzt übrigens machen. Einfach eine kleine Linie konvertieren und den Verknüpfungsnamen 'singlegun' geben. Das gleiche auch mit doublegun und triplegun, z.B. mit zwei bzw. drei Linien.

Hier wird einfach die singlegun attachet. Genauso auch bei double- und triplegun. Das Schießen kommt gleich. Die restlichen Punkte sind, wenn ihr schon so weit seid,  selbsterklärend. Hier jetzt das Schießen der Raketen, sofern man welche hat:

Code:
MovieClip.prototype.shoot = function()
{
  ready = false;

  this.onEnterFrame = function()
  {
    this._y -= (speed*2);

    for(i=0; i<bricks; i++)
    {
      if(this.hitTest(_root['b'+i]))
      {
        _root['b'+i].play();
        delete this.onEnterFrame;
        this.removeMovieClip();
        ready = true;
      }
    }

    if(this._y < -gun._y)
    {
      delete this.onEnterFrame;
      this.removeMovieClip();
      ready = true;
    }
  }
}

Selbsterklärend bis auf "ready": es stellt sicher, dass immer nur ein Schuss pro gun  unterwegs ist und der nächste erst dann abgefeuert wird, wenn der vorige entweder eingeschlagen ist oder das Spielfeld nach oben verlassen hat.

So. Das Wichtigste unseres Games haben wir. Jetzt muss alles noch in Funktionen gepackt werden, damit man es immer wieder aufrufen kann; ein paar Parameter müssen noch gesetzt werden und schon seid ihr fertig.

Hier nun der gesamte Code. Geht ihn auch wirklich Zeile für Zeile durch und ihr werdet sehen, dass ihr selbst die versteht, die wir nicht durch genommen haben.

Viel Spaß mit eurem ersten eigenen Spiel!

Code:

firstSet = function()
{
  speed = 10;
  streu = 5;
  drift = streu;
  newSpeed = speed;
  shoot = 0;
  ready = true;
}

starter = function()
{
  num = 0;
  point = 0;
  game = true;
  lives = 3;
  lev = 1;
  _root['level'+lev]();
  firstSet();
}

starter();

cases = function()
{
  caseArray = new Array("small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "small", "wide", "shooter", "doubleshooter", "tripleshooter", "slow", "fast", "doublepoints", "triplepoints");

  newCase = caseArray[random(caseArray.length)];

  switch(newCase)
  {
    case "small":
      pouk._xscale = 50;
      gun.removeMovieClip();
      break;

    case "wide":
      pouk._xscale = 150;
      gun.removeMovieClip();
      break;

    case "shooter":
      attachMovie("singlegun","gun",700);
      break;

    case "doubleshooter":
      attachMovie("doublegun","gun",700);
      break;

    case "tripleshooter":
      attachMovie("triplegun","gun",700);
      break;

    case "slow":
      speed = speed/2;
      gun.removeMovieClip();
      break;

    case "fast":
      speed = speed*2;
      gun.removeMovieClip();
      break;

    case "doublepoints":
      points = points*2;
      break;

    case "triplepoints":
      points = points*2;
      break;
  }
}

MovieClip.prototype.shoot = function()
{
  ready=false;

  this.onEnterFrame=function()
  {
    this._y-=(speed*2);

    for(i=0; i<bricks; i++)
    {
      if(this.hitTest(_root['b'+i]))
      {
        _root['b'+i].play();
        delete this.onEnterFrame;
        this.removeMovieClip();
        ready = true;
      }
    }

    if(this._y < -gun._y)
    {
      delete this.onEnterFrame;
      this.removeMovieClip();
      ready = true;
    }
  }
}

MovieClip.prototype.fall = function(x,y)
{
  this._x = x;
  this._y = y;
  this._xscale = 50;
  this._yscale = 50;

  this.onEnterFrame = function()
  {
    this._y += 5;
    if(this._y > (Stage.height + this._height))
    {
      delete this.onEnterFrame;
      this.removeMovieClip();
    }

    if(this.hitTest(pouk))
    {
      cases();
      delete this.onEnterFrame;
      this.removeMovieClip();
    }
  }
}

mc._y = Stage.height + 20;

run = function()
{
  gun.removeMovieClip();
  pouk._xscale = 100;
  firstSet();

  mc._y = pouk._y - (mc._height/2);
  mc._x = pouk._x + (pouk._width/2);

  _root.onEnterFrame = function()
  {
    tx.text = "You have "+(lives-1)+" left and "+point+" Points";

    gun._x = pouk._x + (pouk._width/2) - (gun._width/2);
    gun._y = pouk._y - gun._height/2;

    mc._x += drift;
    mc._y += newSpeed;

    for(i=0; i<bricks; i++)
    {
      if(mc.hitTest(_root['b'+i]))
      {
        _root['b'+i].play();
        if(mc._y < _root['b'+i]._y)
        {
          newSpeed = -speed;
        }
        else
        {
          newSpeed = speed;
        }
      }
    }

    if(mc._y <= 0)
    {
      newSpeed=speed;
    }

    if(mc._x > Stage.width)
    {
      drift = -streu;
    }

    if(mc._x <= 0)
    {
      drift = streu;
    }

    if(mc.hitTest(pouk))
    {
      newSpeed = -speed;
      pr = 200/(pouk._width)*(mc._x-pouk._x);
      walk = pr - 100;
      drift = (streu/100*walk)*2;
    }

    if(mc._y > (Stage.height + mc._height))
    {
      game = true;
      delete _root.onEnterFrame;
      lives--;
      tx.text = "You have "+(lives-1)+" left and "+point+" Points";

      if(lives == 0)
      {
        starter();
      }

      gun.removeMovieClip();
      pouk._xscale = 100;
    }

    if(num == bricks)
    {
      delete _root.onEnterFrame;
      point = point*lives;
      tx.text = "You have "+(lives-1)+" left and "+point+" Points";

      for(t=0; t<1000; t++)
      {
        _root['b'+t].removeMovieClip();
      }

      gun.removeMovieClip();
      lev++;
      num = 0;
      game = true;
      mc._y = Stage.height + 10;
      _root['level'+lev]();
      firstSet();
    }
  }
}

level1 = function()
{
  brickzahl = 10;
  colzahl = 40;
  bricks = brickzahl + colzahl;

  for(i=0; i<brickzahl; i++)
  {
    attachMovie("brick","b"+i,i);

    _root['b'+i].hit = 0;
    _root['b'+i]._y = 100;
    _root['b'+0]._x = 0;
    _root['b'+i]._x = _root['b'+(i-1)]._x + _root['b'+(i-1)]._width + (_root['b'+0]._width/2);
  }

  anycase  = random(brickzahl+colzahl);
  anycase2 = random(brickzahl+colzahl);
  anycase3 = random(brickzahl+colzahl);

  for(a=brickzahl; a<(brickzahl+colzahl); a++)
  {
    zh = random(colzahl-a);
    count = brickzahl + zh;

    if(a==count || a==anycase || a==anycase2 || a==anycase3)
    {
      attachMovie("trapbrick","b"+a,a);
     _root['b'+a].ebene = a;
    }
    else
    {
      attachMovie("colbrick", "b"+a, a);
    }

    _root['b'+a].hit = 0;
    _root['b'+a]._y = 80;
    _root['b'+brickzahl]._x = 0;

  }

  //modulo
  lines = 4;
  rows = Math.ceil(colzahl/lines);
  st = brickzahl;

  for(h=0; h<lines; h++)
  {
    for(v=0; v<rows; v++)
    {
      _root['b'+st]._x = v*(_root['b'+st]._width+4) + 70;
      _root['b'+st]._y = h*(_root['b'+st]._height+4) + 20;
      st++;
    }
  }
  //moduloende

}

level3 = function()
{
  brickzahl = 4;
  colzahl = 70;
  bricks = brickzahl + colzahl;

  for(i=0; i<brickzahl; i++)
  {
    attachMovie("brick","b"+i,i);

    _root['b'+i].hit = 0;
    _root['b'+i]._y = random(Stage.height - 200);
    _root['b'+0]._x = random(Stage.width - _root['b'+0]._width);
    _root['b'+i]._x = random(Stage.width - _root['b'+0]._width);
  }

  anycase  = random(brickzahl+colzahl);
  anycase2 = random(brickzahl+colzahl);

  for(a=brickzahl; a<(brickzahl+colzahl); a++)
  {
    zh = random(colzahl-a);
    count = brickzahl + zh;

    if(a==count || a==anycase || a==anycase2)
    {
      attachMovie("trapbrick","b"+a,a);
      _root['b'+a].ebene = a;
    }
    else
    {
      attachMovie("colbrick","b"+a,a);
    }

    _root['b'+a].hit = 0;
    _root['b'+a]._y = random(Stage.height-200);;
    _root['b'+brickzahl]._x = 0;
  }

  //modulo
  lines = Stage.width/_root['b'+0]._width - 2;
  rows = Math.ceil(colzahl/lines);
  st = brickzahl;

  for(h=0; h<lines; h++)
  {
    for(v=0; v<rows; v++)
    {
      _root['b'+st]._x = random(v*(_root['b'+st]._width  + 20)) + random(20);
      _root['b'+st]._y = random(h*(_root['b'+st]._height + 20)) + random(20);
      st++;
    }
  }
  //moduloende
}

level2 = function()
{
  brickzahl = 2;
  colzahl = 40;
  bricks = brickzahl+colzahl;

  for(i=0; i<brickzahl; i++)
  {
    attachMovie("brick","b"+i,i);

    _root['b'+i].hit = 0;
    _root['b'+i]._y = 100;
    _root['b'+0]._x = 100;
    _root['b'+1]._x = 410;
  }

  anycase  = random(brickzahl+colzahl);
  anycase2 = random(brickzahl+colzahl);
  anycase3 = random(brickzahl+colzahl);

  for(a=brickzahl; a<(brickzahl+colzahl); a++)
  {
    zh = random(colzahl-a);
    count = brickzahl + zh;

    if(a==count || a==anycase || a==anycase2 || a==anycase3)
    {
      attachMovie("trapbrick","b"+a,a);
      _root['b'+a].ebene = a;
    }
    else
    {
      attachMovie("colbrick","b"+a,a);
    }

    _root['b'+a].hit = 0;
    _root['b'+a]._y = 80;
    _root['b'+brickzahl]._x = 0;

  }

  for(h=brickzahl; h<bricks; h++)
  {
    w = Math.PI/((h/40));
    _root['b'+h]._x = Math.cos(w)/Math.PI * 700 + 255;
    _root['b'+h]._y = Math.sin(w)/Math.PI * 250 + 120;
  }
}

onMouseDown = function()
{
  if(game)
  {
    //lev = 1;

    newSpeed = -speed;
    game = false;
    run();
  }
  else
  {
    if(ready)
    {
      for(u=0; u<3; u++)
      {
        if(gun['gun'+u] != undefined)
        {
          _root.attachMovie("singlegun","bullet"+shoot,(shoot+1000));
          _root['bullet'+shoot]._x = gun._x + gun['gun'+u]._x;
          _root['bullet'+shoot]._y = gun._y + gun['gun'+u]._y;
          _root['bullet'+shoot].shoot();
          shoot++;
        }
      }
    }
  }
}

_root['level'+lev]();

o = pouk._y;
u = pouk._y;
l = 0;
r = Stage.width - pouk._width;

pouk.startDrag(false,l,o,r,u);
pouk._x = _xmouse - (pouk._width/2);



>> Allgemeine Fragen oder Probleme mit dem Tutorial? Hier gehts zum Forum!

Impressum / Datenschutzerklärung          © der-Webdesigner.net 2002 - 2008           top ▲