Erstellen eines Pseudo-3D-Rennspiels: Implementieren der Hügel und Beenden des Spiels

Teil 3. Hügel



Im vorherigen Teil haben wir ein einfaches pseudo-dreidimensionales Rennspiel erstellt , in dem gerade Straßen und Kurven realisiert wurden.

Dieses Mal werden wir uns um die Hügel kümmern; Zum Glück ist es viel einfacher als kurvige Straßen zu erstellen.

Im ersten Teil haben wir das Gesetz ähnlicher Dreiecke verwendet, um eine dreidimensionale perspektivische Projektion zu erstellen:


... was uns dazu brachte, die Gleichungen für die Projektion der Koordinaten der 3D-Welt in die Koordinate des 2D-Bildschirms zu erhalten.


... aber seitdem haben wir nur mit geraden Straßen gearbeitet, die Weltkoordinaten brauchten nur die z- Komponente , weil sowohl x als auch y gleich Null waren.

Dies passt gut zu uns, da es zum Hinzufügen von Hügeln ausreicht, den Straßensegmenten die entsprechende Koordinate y ungleich Null zu geben , wonach die vorhandene Funktion auf render()magische Weise funktioniert.


Ja, das ist genug, um die Hügel zu erreichen. Fügen Sie einfach die y- Komponente zu den Weltkoordinaten jedes Straßensegments hinzu .

Änderungen in der Straßengeometrie


Wir werden die bestehenden Verfahren modifizieren , addSegmentso dass die Funktion , die sie aufrufen , übergeben kann p2.world.y und p1.world.y entsprechen würde p2.world.y des vorherigen Segments:

function addSegment(curve, y) {
  var n = segments.length;
  segments.push({
     index: n,
        p1: { world: { y: lastY(), z:  n   *segmentLength }, camera: {}, screen: {} },
        p2: { world: { y: y,       z: (n+1)*segmentLength }, camera: {}, screen: {} },
     curve: curve,
     color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
  });
}

function lastY() {
  return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y;
}

Fügen Sie Konstanten hinzu, um niedrige ( LOW), mittlere ( MEDIUM) und hohe ( HIGH) Hügel zu bezeichnen:

var ROAD = {
  LENGTH: { NONE: 0, SHORT:  25, MEDIUM:  50, LONG:  100 },
  HILL:   { NONE: 0, LOW:    20, MEDIUM:  40, HIGH:   60 },
  CURVE:  { NONE: 0, EASY:    2, MEDIUM:   4, HARD:    6 }
};

Ändern Sie die vorhandene Methode addRoad()so, dass sie das Argument y erhält , das zusammen mit den Glättungsfunktionen für den allmählichen Auf- und Abstieg vom Hügel verwendet wird:

function addRoad(enter, hold, leave, curve, y) {
  var startY   = lastY();
  var endY     = startY + (Util.toInt(y, 0) * segmentLength);
  var n, total = enter + hold + leave;
  for(n = 0 ; n < enter ; n++)
    addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
  for(n = 0 ; n < hold  ; n++)
    addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
  for(n = 0 ; n < leave ; n++)
    addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY, (enter+hold+n)/total));
}

Ähnlich wie in Teil 2 addSCurves()können wir außerdem alle Methoden festlegen, die wir zum Konstruieren der Geometrie benötigen, zum Beispiel:

function addLowRollingHills(num, height) {
  num    = num    || ROAD.LENGTH.SHORT;
  height = height || ROAD.HILL.LOW;
  addRoad(num, num, num,  0,  height/2);
  addRoad(num, num, num,  0, -height);
  addRoad(num, num, num,  0,  height);
  addRoad(num, num, num,  0,  0);
  addRoad(num, num, num,  0,  height/2);
  addRoad(num, num, num,  0,  0);
}

Änderungen an der Aktualisierungsmethode


In dem von uns erstellten Arcade-Spiel werden wir nicht versuchen, die Realität zu simulieren, sodass die Hügel den Spieler oder die Spielwelt in keiner Weise beeinflussen, was bedeutet, dass update()keine Änderungen an der Methode erforderlich sind.

Hill-Rendering


render()Auch bei der Methode sind keine Änderungen erforderlich, da die Projektionsgleichungen ursprünglich so geschrieben wurden, dass die Straßensegmente mit y- Koordinaten ungleich Null korrekt projiziert werden .

Parallax Scrolling Hintergrund


Zusätzlich zum Hinzufügen von y- Koordinaten zu allen Straßensegmenten besteht die einzige Änderung in der Implementierung der vertikalen Verschiebung der Hintergrundebenen zusammen mit den Hügeln (genau wie sie sich horizontal entlang der Kurven bewegen). Wir implementieren dies mit einem anderen Argument für die Hilfsfunktion Render.background.

Der einfachste Mechanismus ist die übliche Hintergrundverschiebung relativ zur Position playerY(die aus den Weltpositionen y des aktuellen Spielersegments interpoliert werden sollte ).

Dies ist nicht das realistischste Verhalten, da es sich wahrscheinlich lohnt, die Steigung des aktuellen Abschnitts der Straße des Spielers zu berücksichtigen. Dieser Effekt ist jedoch einfach und eignet sich gut für eine einfache Demo.

Fazit


Das ist alles, jetzt können wir die gefälschten Kurven mit echten Hügeln ergänzen:


Die Arbeit, die wir im ersten Teil geleistet haben, einschließlich der Infrastruktur für das Hinzufügen real projizierter 3D-Hügel, habe ich Ihnen zuvor noch nicht erzählt.

Im letzten Teil des Artikels werden wir Sprites sowie Bäume und Werbetafeln entlang der Straßenränder hinzufügen. Wir werden auch andere Autos hinzufügen, gegen die es möglich sein wird, zu konkurrieren, Kollisionen zu erkennen und den „Kreisrekord“ des Spielers zu korrigieren.

Teil 4. Bereit Version



In diesem Teil werden wir hinzufügen:

  • Werbetafeln und Bäume
  • Andere Autos
  • Kollisionserkennung
  • Rudimentäre KI von Autos
  • Schnittstelle mit Runden-Timer und Rundenrekord

... und dies wird uns ein ausreichendes Maß an Interaktivität bieten, um unser Projekt schließlich als "Spiel" zu bezeichnen.

Hinweis zur Codestruktur


, /, Javascript.

. () , ...

… , , , , .



In Teil 1 haben wir vor Beginn des Spielzyklus ein Sprite-Blatt hochgeladen, das alle Autos, Bäume und Werbetafeln enthält.

Sie können ein Sprite-Blatt in jedem Bildeditor manuell erstellen. Es ist jedoch besser, die Speicherung von Bildern und die Berechnung von Koordinaten einem automatisierten Werkzeug anzuvertrauen. In meinem Fall wurde das Sprite-Blatt durch eine kleine Rake-Aufgabe mit der Ruby Gem-Sprite-Factory generiert .

Diese Aufgabe generiert kombinierte Spritesheets aus separaten Bilddateien und berechnet auch die Koordinaten x, y, w, h, die in einer Konstanten gespeichert werden SPRITES:

var SPRITES = {
  PALM_TREE:   { x:    5, y:    5, w:  215, h:  540 },
  BILLBOARD08: { x:  230, y:    5, w:  385, h:  265 },

  // ... etc

  CAR04:       { x: 1383, y:  894, w:   80, h:   57 },
  CAR01:       { x: 1205, y: 1018, w:   80, h:   56 },
};

Hinzufügen von Werbetafeln und Bäumen


Fügen Sie jedem Straßensegment ein Array hinzu, das Sprites von Objekten entlang der Straßenränder enthält.

Jedes Sprite besteht sourceaus der Sammlung SPRITESzusammen mit einem horizontalen Versatz offset, der so normalisiert ist, dass -1 den linken Straßenrand angibt und +1 den rechten Rand bedeutet, sodass wir uns nicht auf den Wert verlassen können roadWidth.

Einige Sprites werden absichtlich platziert, andere werden zufällig ausgewählt.

function addSegment() {
  segments.push({
    ...
    sprites: [],
    ...
  });
}

function addSprite(n, sprite, offset) {
  segments[n].sprites.push({ source: sprite, offset: offset });
}

function resetSprites() {

  addSprite(20,  SPRITES.BILLBOARD07, -1);
  addSprite(40,  SPRITES.BILLBOARD06, -1);
  addSprite(60,  SPRITES.BILLBOARD08, -1);
  addSprite(80,  SPRITES.BILLBOARD09, -1);
  addSprite(100, SPRITES.BILLBOARD01, -1);
  addSprite(120, SPRITES.BILLBOARD02, -1);
  addSprite(140, SPRITES.BILLBOARD03, -1);
  addSprite(160, SPRITES.BILLBOARD04, -1);
  addSprite(180, SPRITES.BILLBOARD05, -1);

  addSprite(240, SPRITES.BILLBOARD07, -1.2);
  addSprite(240, SPRITES.BILLBOARD06,  1.2);

  
  for(n = 250 ; n < 1000 ; n += 5) {
    addSprite(n, SPRITES.COLUMN, 1.1);
    addSprite(n + Util.randomInt(0,5), SPRITES.TREE1, -1 - (Math.random() * 2));
    addSprite(n + Util.randomInt(0,5), SPRITES.TREE2, -1 - (Math.random() * 2));
  }

  ...
}

Hinweis: Wenn wir ein echtes Spiel erstellen würden, könnten wir einen Straßeneditor schreiben, um visuell eine Karte mit Hügeln und Kurven zu erstellen, sowie einen Mechanismus zum Anordnen von Sprites entlang der Straße hinzufügen ... aber für unsere Aufgaben können wir dies einfach programmgesteuert tun addSprite().

Maschinen hinzufügen


Zusätzlich zu den Sprites von Objekten an den Straßenrändern werden wir eine Sammlung von Autos hinzufügen, die jedes Segment belegen, sowie eine separate Sammlung aller Autos auf der Autobahn.

var cars      = [];  // array of cars on the road
var totalCars = 200; // total number of cars on the road

function addSegment() {
  segments.push({
    ...
    cars: [], // array of cars within this segment
    ...
  });
}

Durch die Speicherung von zwei Fahrzeugdatenstrukturen können wir alle Fahrzeuge in einer Methode einfach iterativ umgehen update()und sie bei Bedarf von einem Segment in ein anderes verschieben. Gleichzeitig können wir so render()nur Maschinen auf sichtbaren Segmenten ausführen .

Jede Maschine erhält eine zufällige horizontale Verschiebung, z-Position, Sprite-Quelle und Geschwindigkeit:

function resetCars() {
  cars = [];
  var n, car, segment, offset, z, sprite, speed;
  for (var n = 0 ; n < totalCars ; n++) {
    offset = Math.random() * Util.randomChoice([-0.8, 0.8]);
    z      = Math.floor(Math.random() * segments.length) * segmentLength;
    sprite = Util.randomChoice(SPRITES.CARS);
    speed  = maxSpeed/4 + Math.random() * maxSpeed/(sprite == SPRITES.SEMI ? 4 : 2);
    car = { offset: offset, z: z, sprite: sprite, speed: speed };
    segment = findSegment(car.z);
    segment.cars.push(car);
    cars.push(car);
  }
}

Hill Rendering (Rückkehr)


In den vorherigen Abschnitten habe ich über das Rendern von Straßensegmenten gesprochen, einschließlich Kurven und Hügeln, aber es gab einige Codezeilen, die ich nicht berücksichtigt habe. Sie betrafen eine Variable maxy, die am unteren Bildschirmrand beginnt, aber beim Rendern jedes Segments abnimmt, um festzustellen, welchen Teil des Bildschirms wir bereits gerendert haben:

for(n = 0 ; n < drawDistance ; n++) {

  ...

  if ((segment.p1.camera.z <= cameraDepth) || // behind us
      (segment.p2.screen.y >= maxy))          // clip by (already rendered) segment
    continue;

  ...

  maxy = segment.p2.screen.y;
}

Auf diese Weise können wir Segmente beschneiden, die von bereits gerenderten Hügeln bedeckt sind.

Beim traditionellen Algorithmus des Künstlers erfolgt das Rendern normalerweise von hinten nach vorne, während engere Segmente die fernen überlappen. Wir können jedoch keine Zeit damit verbringen, Polygone zu rendern, die eventuell überschrieben werden. Daher wird es einfacher, von vorne nach hinten zu rendern und entfernte Segmente zuzuschneiden, die von bereits gerenderten Segmenten abgedeckt werden, wenn ihre projizierten Koordinaten kleiner sind maxy.

Werbetafeln, Bäume und Autos rendern


Das iterative Durchlaufen von Straßensegmenten von vorne nach hinten funktioniert jedoch beim Rendern von Sprites nicht, da sie sich häufig überlappen und daher mit dem Algorithmus des Künstlers gerendert werden müssen.

Dies verkompliziert unsere Methode render()und zwingt uns, Straßensegmente in zwei Schritten zu umgehen:

  1. von vorne nach hinten für die Straßenwiedergabe
  2. zurück vorwärts zum Rendern von Sprites


Zusätzlich zu teilweise überlappenden Sprites müssen wir uns mit Sprites befassen, die aufgrund des Horizonts auf der Spitze des Hügels „leicht hervorstehen“. Wenn das Sprite hoch genug ist, sollten wir seinen oberen Teil sehen, auch wenn sich das Straßensegment, auf dem es sich befindet, auf der Rückseite des Hügels befindet und daher nicht gerendert wird.

Wir können das letzte Problem lösen, indem wir den Wert maxyjedes Segments clipin Schritt 1 als Linie speichern . Dann können wir die Sprites dieses Segments clipin Schritt 2 entlang der Linie zuschneiden .

Der Rest der Renderlogik bestimmt, wie das Sprite basierend auf dem Koeffizienten scaleund den Koordinaten screender Straßensegmente skaliert und positioniert wird (berechnet auf Stufe 1), aufgrund derer render()wir in der zweiten Stufe der Methode ungefähr Folgendes haben:

// back to front painters algorithm
for(n = (drawDistance-1) ; n > 0 ; n--) {
  segment = segments[(baseSegment.index + n) % segments.length];

  // render roadside sprites
  for(i = 0 ; i < segment.sprites.length ; i++) {
    sprite      = segment.sprites[i];
    spriteScale = segment.p1.screen.scale;
    spriteX     = segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth * width/2);
    spriteY     = segment.p1.screen.y;
    Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite.source, spriteScale, spriteX, spriteY, (sprite.offset < 0 ? -1 : 0), -1, segment.clip);
  }

  // render other cars
  for(i = 0 ; i < segment.cars.length ; i++) {
    car         = segment.cars[i];
    sprite      = car.sprite;
    spriteScale = Util.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent);
    spriteX     = Util.interpolate(segment.p1.screen.x,     segment.p2.screen.x,     car.percent) + (spriteScale * car.offset * roadWidth * width/2);
    spriteY     = Util.interpolate(segment.p1.screen.y,     segment.p2.screen.y,     car.percent);
    Render.sprite(ctx, width, height, resolution, roadWidth, sprites, car.sprite, spriteScale, spriteX, spriteY, -0.5, -1, segment.clip);
  }

}

Kollisionen mit Werbetafeln und Bäumen


Jetzt, da wir Objekt-Sprites entlang der Straßenränder hinzufügen und rendern können, müssen wir die Methode ändern, update()um festzustellen, ob der Spieler auf eines dieser Sprites in seinem aktuellen Segment gestoßen ist:

Wir verwenden eine Hilfsmethode, Util.overlap()um die allgemeine Erkennung des Schnittpunkts von Rechtecken zu implementieren. Wenn eine Kreuzung erkannt wird, halten wir das Auto an:

if ((playerX < -1) || (playerX > 1)) {
  for(n = 0 ; n < playerSegment.sprites.length ; n++) {
    sprite  = playerSegment.sprites[n];
    spriteW = sprite.source.w * SPRITES.SCALE;
    if (Util.overlap(playerX, playerW, sprite.offset + spriteW/2 * (sprite.offset > 0 ? 1 : -1), spriteW)) {
      // stop the car
      break;
    }
  }
}

Hinweis: Wenn Sie den tatsächlichen Code studieren, werden Sie feststellen, dass wir das Auto tatsächlich nicht anhalten, da es sich dann nicht seitwärts bewegen kann, um Hindernissen auszuweichen. Als einfachen Hack legen wir ihre Position fest und lassen das Auto seitlich um das Sprite herum „rutschen“.

Kollisionen mit Autos


Zusätzlich zu Kollisionen mit Sprites entlang der Straßenränder müssen wir Kollisionen mit anderen Autos erkennen. Wenn eine Kreuzung erkannt wird, verlangsamen wir den Spieler, indem wir ihn hinter die Maschine zurückschieben, mit der er kollidierte:

for(n = 0 ; n < playerSegment.cars.length ; n++) {
  car  = playerSegment.cars[n];
  carW = car.sprite.w * SPRITES.SCALE;
  if (speed > car.speed) {
    if (Util.overlap(playerX, playerW, car.offset, carW, 0.8)) {
      // slow the car
      break;
    }
  }
}

Maschinen-Update


Damit sich andere Autos auf der Straße bewegen, geben wir ihnen die einfachste KI:

  • mit konstanter Geschwindigkeit fahren
  • Gehen Sie beim Überholen automatisch um den Spieler herum
  • Beim Überholen automatisch um andere Autos herumfahren

Hinweis: Wir müssen uns keine Sorgen machen, andere Autos entlang einer Kurve auf der Straße zu drehen, da die Kurven nicht real sind. Wenn wir die Autos nur entlang der Straßensegmente bewegen, fahren sie automatisch entlang der Kurven.

All dies geschieht während des Spielzyklus update()während eines Anrufs, updateCars()bei dem wir jedes Auto mit konstanter Geschwindigkeit vorwärts bewegen und von einem Segment zum nächsten wechseln, wenn es sich in diesem Frame um eine ausreichende Strecke bewegt hat.

function updateCars(dt, playerSegment, playerW) {
  var n, car, oldSegment, newSegment;
  for(n = 0 ; n < cars.length ; n++) {
    car         = cars[n];
    oldSegment  = findSegment(car.z);
    car.offset  = car.offset + updateCarOffset(car, oldSegment, playerSegment, playerW);
    car.z       = Util.increase(car.z, dt * car.speed, trackLength);
    car.percent = Util.percentRemaining(car.z, segmentLength); // useful for interpolation during rendering phase
    newSegment  = findSegment(car.z);
    if (oldSegment != newSegment) {
      index = oldSegment.cars.indexOf(car);
      oldSegment.cars.splice(index, 1);
      newSegment.cars.push(car);
    }
  }
}

Die Methode updateCarOffset()bietet die Implementierung von "künstlicher Intelligenz" , die es der Maschine ermöglicht, um den Spieler oder andere Maschinen herumzugehen. Dies ist eine der komplexesten Methoden in der Codebasis, und in einem echten Spiel sollte es viel komplexer sein, damit die Maschinen viel realistischer erscheinen als in einer einfachen Demo.

In unserem Projekt verwenden wir eine naive KI-Brute-Force, die jede Maschine zwingt:

  • Freuen Sie sich auf 20 Segmente
  • Wenn sie ein langsameres Auto vor sich findet, das ihren Weg kreuzt, dann gehe um sie herum
  • Biegen Sie rechts ab von Hindernissen auf der linken Straßenseite
  • Biegen Sie links von den Hindernissen auf der rechten Straßenseite ab
  • Drehen Sie sich so weit, dass Hindernisse in der verbleibenden Entfernung vermieden werden

Wir können auch mit Autos schummeln, die für den Spieler unsichtbar sind, so dass sie einfach nicht umeinander herumfahren und durchfahren können. Sie sollten nur innerhalb der Sichtbarkeit des Spielers „klug“ erscheinen.

function updateCarOffset(car, carSegment, playerSegment, playerW) {

  var i, j, dir, segment, otherCar, otherCarW, lookahead = 20, carW = car.sprite.w * SPRITES.SCALE;

  // optimization, dont bother steering around other cars when 'out of sight' of the player
  if ((carSegment.index - playerSegment.index) > drawDistance)
    return 0;

  for(i = 1 ; i < lookahead ; i++) {
    segment = segments[(carSegment.index+i)%segments.length];

    if ((segment === playerSegment) && (car.speed > speed) && (Util.overlap(playerX, playerW, car.offset, carW, 1.2))) {
      if (playerX > 0.5)
        dir = -1;
      else if (playerX < -0.5)
        dir = 1;
      else
        dir = (car.offset > playerX) ? 1 : -1;
      return dir * 1/i * (car.speed-speed)/maxSpeed; // the closer the cars (smaller i) and the greater the speed ratio, the larger the offset
    }

    for(j = 0 ; j < segment.cars.length ; j++) {
      otherCar  = segment.cars[j];
      otherCarW = otherCar.sprite.w * SPRITES.SCALE;
      if ((car.speed > otherCar.speed) && Util.overlap(car.offset, carW, otherCar.offset, otherCarW, 1.2)) {
        if (otherCar.offset > 0.5)
          dir = -1;
        else if (otherCar.offset < -0.5)
          dir = 1;
        else
          dir = (car.offset > otherCar.offset) ? 1 : -1;
        return dir * 1/i * (car.speed-otherCar.speed)/maxSpeed;
      }
    }
  }
}

In den meisten Fällen funktioniert dieser Algorithmus recht gut, aber bei einer großen Anzahl von Autos vorne können wir feststellen, dass sich die Autos von links nach rechts und zurück bewegen und versuchen, sich in die Lücke zwischen den beiden anderen Maschinen zu quetschen. Es gibt viele Möglichkeiten, die Zuverlässigkeit der KI zu verbessern. Sie können beispielsweise zulassen, dass Autos langsamer werden, wenn sie feststellen, dass nicht genügend Platz vorhanden ist, um Hindernissen auszuweichen.

Schnittstelle


Schließlich werden wir eine rudimentäre HTML-Oberfläche erstellen:

<div id = "hud">
  <span id = "speed" class = "hud"> <span id = "speed_value" class = "value"> 0 </ span> mph </ span>
  <span id = "current_lap_time" class = "hud"> Zeit: <span id = "current_lap_time_value" class = "value"> 0.0 </ span> </ span> 
  <span id = "last_lap_time" class = "hud"> Letzte Runde: <span id = "last_lap_time_value" class = "value"> 0.0 </ span> </ span>
  <span id = "fast_lap_time" class = "hud"> Schnellste Runde: <span id = "fast_lap_time_value" class = "value"> 0.0 </ span> </ span>
</ div>

... und fügen Sie CSS-Stil hinzu

#hud                   { position: absolute; z-index: 1; width: 640px; padding: 5px 0; font-family: Verdana, Geneva, sans-serif; font-size: 0.8em; background-color: rgba(255,0,0,0.4); color: black; border-bottom: 2px solid black; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
#hud .hud              { background-color: rgba(255,255,255,0.6); padding: 5px; border: 1px solid black; margin: 0 5px; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
#hud #speed            { float: right; }
#hud #current_lap_time { float: left;  }
#hud #last_lap_time    { float: left; display: none;  }
#hud #fast_lap_time    { display: block; width: 12em;  margin: 0 auto; text-align: center; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
#hud .value            { color: black; font-weight: bold; }
#hud .fastest          { background-color: rgba(255,215,0,0.5); }


... und wir werden das Update () während des Spielzyklus ausführen:

if (position > playerZ) {
  if (currentLapTime && (startPosition < playerZ)) {
    lastLapTime    = currentLapTime;
    currentLapTime = 0;
    if (lastLapTime <= Util.toFloat(Dom.storage.fast_lap_time)) {
      Dom.storage.fast_lap_time = lastLapTime;
      updateHud('fast_lap_time', formatTime(lastLapTime));
      Dom.addClassName('fast_lap_time', 'fastest');
      Dom.addClassName('last_lap_time', 'fastest');
    }
    else {
      Dom.removeClassName('fast_lap_time', 'fastest');
      Dom.removeClassName('last_lap_time', 'fastest');
    }
    updateHud('last_lap_time', formatTime(lastLapTime));
    Dom.show('last_lap_time');
  }
  else {
    currentLapTime += dt;
  }
}

updateHud('speed',            5 * Math.round(speed/500));
updateHud('current_lap_time', formatTime(currentLapTime));

Mit der Hilfsmethode updateHud()können wir DOM-Elemente nur aktualisieren, wenn sich die Werte ändern, da eine solche Aktualisierung ein langsamer Prozess sein kann und wir sie nicht mit 60 fps durchführen sollten, wenn sich die Werte selbst nicht ändern.

function updateHud(key, value) { // accessing DOM can be slow, so only do it if value has changed
  if (hud[key].value !== value) {
    hud[key].value = value;
    Dom.set(hud[key].dom, value);
  }
}

Fazit



Fuh! Der letzte Teil war lang, aber wir waren noch fertig und die fertige Version erreichte das Stadium, in dem es als Spiel bezeichnet werden kann. Sie ist noch weit vom fertigen Spiel entfernt, aber es ist immer noch ein Spiel.

Es ist erstaunlich, dass wir es wirklich geschafft haben, ein Spiel zu erstellen, wenn auch so einfach. Ich habe nicht vor, dieses Projekt in einen vollständigen Zustand zu versetzen. Es sollte lediglich als Einführung in das Thema der pseudo-dreidimensionalen Rennspiele betrachtet werden.

Der Code wird von github veröffentlicht und Sie können versuchen, daraus ein fortgeschritteneres Rennspiel zu machen. Sie können auch versuchen:

  • Fügen Sie Autos Soundeffekte hinzu
  • Musiksynchronisation verbessern
  • Vollbild implementieren
  • ( , , , ..)
  • (, ..)
  • ,
  • , -
  • ,
  • ( , ..)
  • drawDistance
  • x,y
  • ( , )
  • Gabel- und Straßenverbindungen
  • die Veränderung von Tag und Nacht
  • Wetterverhältnisse
  • Tunnel, Brücken, Wolken, Mauern, Gebäude
  • Stadt, Wüste, Ozean
  • Fügen Sie Seattle und Space Needle zu den Hintergründen hinzu
  • "Schurken" - fügen Sie Konkurrenten hinzu, mit denen Sie konkurrieren können
  • Spielmodi - die schnellste Runde, eins zu eins Rennen (Münzen einsammeln?, auf Bösewichte schießen?)
  • Tonnenweise Anpassungsoptionen für das Gameplay
  • usw.
  • ...

Also sind wir fertig. Ein weiteres „Wochenendprojekt“, das viel länger dauerte als erwartet, aber am Ende war das Ergebnis ziemlich gut.

Verweise



Links zu spielbaren Demos:


All Articles