Membuat Game Balap Pseudo-3D: Menerapkan Bukit dan Menyelesaikan Game

Bagian 3. Bukit



Di bagian sebelumnya, kami menciptakan game balap pseudo-tiga dimensi yang sederhana , mewujudkan jalan lurus dan kurva di dalamnya.

Kali ini kita akan menjaga bukit; untungnya, itu akan jauh lebih mudah daripada menciptakan jalan yang melengkung.

Pada bagian pertama, kami menggunakan hukum segitiga yang sama untuk membuat proyeksi perspektif tiga dimensi:


... yang mengarahkan kami untuk mendapatkan persamaan untuk memproyeksikan koordinat dunia 3d ke dalam koordinat layar 2d.


... tetapi sejak itu kami hanya bekerja dengan jalan lurus, koordinat dunia hanya membutuhkan komponen z , karena x dan y sama dengan nol.

Setelan ini kita baik, karena untuk menambah bukit itu cukup bagi kita untuk memberikan ruas jalan yang sesuai nol koordinat y , setelah fungsi yang ada akan render()secara ajaib bekerja.


Ya, itu cukup untuk mendapatkan bukit. Tambahkan saja komponen y ke koordinat dunia dari setiap ruas jalan .

Perubahan geometri jalan


Kami akan memodifikasi metode yang ada addSegmentsehingga fungsi yang memanggilnya dapat melewati p2.world.y , dan p1.world.y akan sesuai dengan p2.world.y dari segmen sebelumnya:

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;
}

Tambahkan konstanta untuk menunjukkan bukit rendah ( LOW), sedang ( MEDIUM) dan tinggi ( HIGH):

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 }
};

Ubah metode yang ada addRoad()sehingga menerima argumen y , yang akan digunakan bersama dengan fungsi kelancaran untuk naik dan turun bertahap dari bukit:

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));
}

Lebih jauh, mirip dengan apa yang kita lakukan di bagian 2 addSCurves(), kita dapat memaksakan metode apa pun yang kita butuhkan untuk membangun geometri, misalnya:

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);
}

Perubahan pada metode pembaruan


Dalam permainan arcade yang kami buat, kami tidak akan mencoba mensimulasikan kenyataan, jadi bukit tidak mempengaruhi pemain atau dunia game dengan cara apa pun, yang berarti bahwa update()perubahan tidak diperlukan dalam metode ini .

Rendering bukit


Metode ini render()juga tidak memerlukan perubahan, karena persamaan proyeksi awalnya ditulis untuk memproyeksikan ruas jalan dengan benar dengan koordinat y bukan nol .

Latar Belakang Menggulir Parallax


Selain menambahkan koordinat y ke semua ruas jalan , satu-satunya perubahan adalah implementasi perpindahan vertikal lapisan latar belakang bersama dengan bukit-bukit (sama seperti mereka bergerak secara horizontal bersama dengan kurva). Kami menerapkan ini dengan argumen lain ke fungsi helper Render.background.

Mekanisme paling sederhana akan menjadi perpindahan latar belakang yang biasa relatif terhadap posisi playerY(yang harus diinterpolasi dari posisi dunia y dari segmen pemain saat ini).

Ini bukan perilaku yang paling realistis, karena mungkin perlu mempertimbangkan kemiringan segmen jalan pemain saat ini, tetapi efek ini sederhana dan bekerja cukup baik untuk demo sederhana.

Kesimpulan


Itu saja, sekarang kita bisa melengkapi kurva palsu dengan bukit nyata:


Pekerjaan yang kami lakukan pada bagian pertama, termasuk infrastruktur untuk menambahkan bukit 3d nyata yang diproyeksikan, saya hanya tidak memberi tahu Anda tentang ini sebelumnya.

Di bagian akhir artikel kami akan menambahkan sprite, serta pohon dan papan iklan di sepanjang tepi jalan. Kami juga akan menambahkan mobil-mobil lain yang memungkinkan untuk bersaing, pengakuan tabrakan dan memperbaiki "catatan lingkaran" pemain.

Bagian 4. Versi siap



Pada bagian ini kami akan menambahkan:

  • Papan iklan dan pohon
  • Mobil lain
  • Pengakuan Tabrakan
  • AI mobil belum sempurna
  • Antarmuka dengan timer putaran dan catatan putaran

... dan ini akan memberi kita tingkat interaktivitas yang memadai untuk akhirnya menyebut proyek kita sebagai "permainan."

Catatan tentang struktur kode


, /, Javascript.

. () , ...

… , , , , .



Di bagian 1, sebelum awal siklus permainan, kami mengunggah sprite sheet yang berisi semua mobil, pohon, dan papan iklan.

Anda dapat secara manual membuat lembar sprite dalam editor gambar apa pun, tetapi lebih baik untuk mempercayakan penyimpanan gambar dan perhitungan koordinat ke alat otomatis. Dalam kasus saya, lembar sprite dihasilkan oleh tugas Rake kecil menggunakan pabrik sprite Ruby Gem .

Tugas ini menghasilkan gabungan spritesheets dari file gambar terpisah, dan juga menghitung koordinat x, y, w, h, yang akan disimpan dalam konstanta 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 },
};

Menambahkan papan iklan dan pohon


Tambahkan ke setiap segmen jalan array yang akan berisi sprite objek di sepanjang tepi jalan.

Setiap sprite terdiri dari yang sourcediambil dari koleksi SPRITES, bersama-sama dengan offset horizontal offset, yang dinormalisasi sehingga -1 menunjukkan tepi kiri jalan, dan +1 berarti tepi kanan, yang memungkinkan kita untuk tidak bergantung pada nilai roadWidth.

Beberapa sprite ditempatkan dengan sengaja, yang lain diacak.

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));
  }

  ...
}

Catatan: jika kita membuat game nyata, kita bisa menulis editor jalan untuk membuat peta dengan bukit dan kurva secara visual, serta menambahkan mekanisme untuk mengatur sprite di sepanjang jalan ... tetapi untuk tugas kita, kita bisa melakukannya secara terprogram addSprite().

Menambahkan mesin


Selain sprite objek di tepi jalan, kami akan menambahkan koleksi mobil yang akan menempati setiap segmen bersama dengan koleksi terpisah dari semua mobil di jalan raya.

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
    ...
  });
}

Penyimpanan dua struktur data mobil memungkinkan kita untuk dengan mudah berkeliling semua mobil dalam suatu metode update(), memindahkannya dari satu segmen ke segmen lainnya jika perlu; pada saat yang sama, ini memungkinkan kami untuk mengeksekusi render()hanya mesin pada segmen yang terlihat.

Setiap mesin diberi perpindahan horizontal acak, posisi z, sumber sprite, dan kecepatan:

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);
  }
}

Rendering bukit (kembali)


Di bagian sebelumnya, saya berbicara tentang render segmen jalan, termasuk kurva dan bukit, tetapi ada beberapa baris kode di dalamnya yang tidak saya pertimbangkan. Mereka memperhatikan variabel yang maxydimulai dari bagian bawah layar, tetapi menurun ketika merender setiap segmen untuk menentukan bagian mana dari layar yang telah kami render:

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;
}

Ini akan memungkinkan kami untuk memotong segmen yang akan ditutupi oleh bukit yang telah diberikan.

Dalam algoritme tradisional artis, rendering biasanya terjadi dari belakang ke depan, sementara segmen yang lebih dekat menimpa yang jauh. Namun, kami tidak dapat menghabiskan waktu merender poligon, yang pada akhirnya akan ditimpa, sehingga menjadi lebih mudah untuk me-render dari depan ke belakang dan memotong segmen yang jauh yang dicakup oleh segmen yang telah diberikan di dekat segmen jika koordinat yang diproyeksikannya lebih kecil maxy.

Rendering baliho, pohon, dan mobil


Namun, traversal berulang dari ruas jalan dari depan ke belakang tidak akan berfungsi ketika merender sprite, karena mereka sering tumpang tindih satu sama lain, dan karenanya harus dirender menggunakan algoritme artis.

Ini memperumit metode kami render()dan memaksa kami untuk memotong segmen jalan dalam dua tahap:

  1. depan ke belakang untuk perenderan jalan
  2. mundur untuk rendering sprite


Selain sprite sebagian tumpang tindih, kita perlu berurusan dengan sprite yang "sedikit menonjol" karena cakrawala di bagian atas bukit. Jika sprite cukup tinggi, maka kita harus melihat bagian atasnya, bahkan jika ruas jalan tempat ia berada berada di belakang bukit, dan karenanya tidak dirender.

Kita dapat memecahkan masalah terakhir dengan menyimpan nilai maxysetiap segmen sebagai garis clipdi tahap 1. Kemudian kita dapat memotong sprite dari segmen ini di sepanjang garis clipdalam tahap 2.

Sisa dari rendering logic menentukan bagaimana skala dan posisi sprite berdasarkan pada koefisien scaledan koordinat screensegmen jalan (dihitung berdasarkan tahap 1), karena pada tahap kedua metode ini render()kita memiliki hal-hal berikut:

// 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);
  }

}

Tabrakan dengan papan iklan dan pohon


Sekarang kita dapat menambahkan dan membuat sprite objek di sepanjang tepi jalan, kita perlu mengubah metode update()untuk menentukan apakah pemain telah menemukan salah satu sprite ini di segmennya saat ini:

Kami menggunakan metode bantu Util.overlap()untuk menerapkan pengenalan umum dari persimpangan empat persegi panjang. Jika persimpangan terdeteksi, kami menghentikan mobil:

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;
    }
  }
}

Catatan: jika Anda mempelajari kode sebenarnya, Anda akan melihat bahwa sebenarnya kita tidak menghentikan mobil, karena itu tidak akan dapat bergerak ke samping untuk menghindari rintangan; sebagai peretasan sederhana, kami memperbaiki posisi mereka dan membiarkan mobil "tergelincir" ke sisi sekitar sprite.

Tabrakan dengan mobil


Selain tabrakan dengan sprite di sepanjang tepi jalan, kita perlu mengenali tabrakan dengan mobil lain, dan jika persimpangan terdeteksi, kita memperlambat pemain dengan "mendorong" dia kembali ke belakang mobil yang dia tabrak:

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;
    }
  }
}

Pembaruan Mesin


Agar mobil lain bergerak di sepanjang jalan, kami akan memberi mereka AI paling sederhana:

  • naik dengan kecepatan konstan
  • secara otomatis berkeliling pemain ketika menyalip
  • secara otomatis berkeliling mobil lain ketika menyalip

Catatan: kita tidak perlu khawatir tentang membelokkan mobil lain di sepanjang kurva di jalan, karena kurva tidak nyata. Jika kita membuat mobil hanya bergerak di sepanjang ruas jalan, mereka akan secara otomatis melewati kurva.

Semua ini terjadi selama siklus permainan update()selama panggilan updateCars()di mana kami menggerakkan setiap mobil maju dengan kecepatan konstan dan beralih dari satu segmen ke yang lain jika mereka telah bergerak jarak yang cukup selama bingkai ini.

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);
    }
  }
}

Metode ini updateCarOffset()menyediakan implementasi "kecerdasan buatan" , yang memungkinkan mesin berputar di sekitar pemain atau mesin lain. Ini adalah salah satu metode paling kompleks dalam basis kode, dan dalam gim nyata seharusnya jauh lebih kompleks sehingga mesin tampak jauh lebih realistis daripada dalam demo sederhana.

Dalam proyek kami, kami menggunakan brute force AI naif, memaksa setiap mesin:

  • nantikan 20 segmen
  • jika dia menemukan mobil yang lebih lambat di depannya yang melintasi jalannya, maka berputarlah di sekelilingnya
  • belok kanan dari rintangan di sisi kiri jalan
  • belok kiri dari rintangan di sisi kanan jalan
  • belok cukup untuk menghindari rintangan di depan dalam jarak yang tersisa

Kita juga bisa menipu dengan mobil-mobil yang tidak terlihat oleh pemain, memungkinkan mereka untuk tidak saling berputar dan melewatinya. Mereka seharusnya tampak "pintar" hanya dalam jarak pandang pemain.

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;
      }
    }
  }
}

Dalam kebanyakan kasus, algoritma ini bekerja cukup baik, tetapi dengan kerumunan besar mobil di depan, kita dapat melihat bahwa mobil-mobil bergerak dari kiri ke kanan dan belakang, mencoba masuk ke celah antara dua mesin lainnya. Ada banyak cara untuk meningkatkan keandalan AI, misalnya, Anda dapat membuat mobil melambat jika mereka melihat tidak ada ruang yang cukup untuk menghindari rintangan.

Antarmuka


Akhirnya, kami akan membuat antarmuka HTML yang belum sempurna:

<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"> Waktu: <span id = "current_lap_time_value" class = "value"> 0.0 </span> </span> 
  <span id = "last_lap_time" class = "hud"> Putaran Terakhir: <span id = "last_lap_time_value" class = "value"> 0.0 </span> </span>
  <span id = "fast_lap_time" class = "hud"> Lap Tercepat: <span id = "fast_lap_time_value" class = "value"> 0.0 </span> </span>
</div>

... dan tambahkan gaya CSS ke dalamnya

#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); }


... dan kami akan menjalankan pembaruannya () selama siklus permainan:

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));

Metode helper updateHud()memungkinkan kita untuk memperbarui elemen DOM hanya ketika nilai berubah, karena pembaruan seperti itu bisa menjadi proses yang lambat dan kita tidak boleh melakukannya pada 60fps jika nilai itu sendiri tidak berubah.

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);
  }
}

Kesimpulan



Fuh! Bagian terakhir panjang, tapi kami masih selesai, dan versi selesai mencapai tahap di mana itu bisa disebut permainan. Dia masih jauh dari game yang sudah selesai , tapi itu masih sebuah game.

Sungguh menakjubkan bahwa kami benar-benar berhasil membuat game, meskipun sangat sederhana. Saya tidak berencana untuk membawa proyek ini ke kondisi lengkap. Ini harus dianggap hanya sebagai pengantar topik game balap pseudo-tiga dimensi.

Kode ini diposting oleh github , dan Anda dapat mencoba mengubahnya menjadi gim balap yang lebih maju. Anda juga dapat mencoba:

  • tambahkan efek suara ke mobil
  • tingkatkan sinkronisasi musik
  • mengimplementasikan layar penuh
  • ( , , , ..)
  • (, ..)
  • ,
  • , -
  • ,
  • ( , ..)
  • drawDistance
  • x,y
  • ( , )
  • koneksi garpu dan jalan
  • perubahan malam dan siang
  • kondisi cuaca
  • terowongan, jembatan, awan, dinding, bangunan
  • kota, gurun, samudera
  • Tambahkan Seattle dan Space Needle ke latar belakang
  • "Penjahat" - tambahkan pesaing untuk bersaing
  • mode permainan - lap tercepat, lomba satu lawan satu (mengambil koin?, menembak penjahat?)
  • banyak opsi penyesuaian gameplay
  • dll.
  • ...

Jadi kita selesai. β€œProyek akhir pekan” lain yang membutuhkan waktu lebih lama dari yang diharapkan, tetapi pada akhirnya hasilnya cukup bagus.

Referensi



Tautan ke demo yang dapat diputar:


All Articles