Angry Chickens




Być może znasz grę, w której strzela się z procy ptakami, by zniszczyć cele.
Stwórzmy dzisiaj prostą wersję tej gry z wielkanocnymi kurczakami w roli głównej oraz wielkanocnymi zajączkami w roli ofiar.
Możesz grać myszą lub ekranem dotykowym. Wypróbuj tę grę na szczycie tej strony!
Jedna linia kodu zawiera 'Easter Egg' (jajko wielkanocne - nieudokumentowaną funkcję) - czy umiesz ją znaleźć?

Pełny kod gry jest poniżej. Oto krótki opis:

[1-9] ustawiamy HTML5 Canvas w lewym górnym rogu ekranu
[10-15] ustawiamy funkcje obsługi zdarzeń myszki i ekranu dotykowego
[17-20] dźwięki i obrazy. 'img' zawiera szerokie tło (fatalną przeróbkę zdjęcia Wyspy Wielkanocnej autorstwa Bjørna Christiana Tørrissena), a poniżej ramki animacji sprite.
[21-22] wymiary canvas
[23-24] współrzędne procy
[25] rozmiar sprite'ów (kurczaków i zajęcy)
[26-30] pomocnicze zmienne
[31-38] tworzymy puste obiekty 'target' (cel) i dodajemy je do tablicy 'targets'. Każdy cel ma swoje własne współrzędne i stan (alive (żywy): prawda/fałsz)
[39-40] uruchamiamy grę i inicjujemy główną pętlę animacji

[42-77] Oto dusza naszej gry - główna pętla:
[43] Czyścimy canvas.
[44] Rysujemy tło. Kopiujemy prostokąt o wymiarach równych wymiarom Canvas. W ten sposób gracz nie widzi ramek animacji sprite'ów na dole.
Efekt przesuwania ekranu uzyskujemy przy pomocy parametru screenOffset (offset ekranu). Określa on po prostu przesunięcie w poziomie od pozycji wyjściowej. W każdej ramce kopiujemy tylko tę część szerokiej bitmapy tła, która zaczyna się od wartości offsetu.
Tło w żaden sposób nie wpływa na grę, więc możesz po prostu skasować tę linię, jeżeli jest zbyt skomplikowana.
[45] Jeżeli proca jest naciągnięta, rysujemy (bardzo prymitywny) pasek gumy od czubka procy do środka kurczaka.
[51-58] Jeżeli nasz kurczaczek już leci, obliczamy jego nowe współrzędne oraz offset ekranu.
Używamy szalonego wzoru w [54] jako substytutu porządnych obliczeń balistycznych. Pozycja kurczaka jest obliczana poprzez dodanie do miejsca wystrzału siły naciągnięcia procy w momencie wystrzału (podzielonej przez 10).
Współrzędna y jest dodatkowo powiększana o kwadrat licznika ramek (podzielonego przez 4), by uwzględnić rosnący wpływ grawitacji.
Te arbitralne parametry osiągnąłem metodą prób i błędów. Eksperymentuj z innymi wartościami, by zmienić trajektorię naszego kamikadze.
[56] Zwiększamy licznik - mówi on nam, ile ramek minęło od momentu wystrzału.
[57] Jeżeli kurczak spadnie pewną odległość poniżej ekranu, pora na nowego.
[59] Rysujemy kurczę - jest ono kopiowane z dołu obrazu źródłowego. Ma dwie ramki animacji - jedną dla fazy lotu, drugą dla pozostałych.
[60-62] Rysujemy zające. Mają dwie ramki: żywy i martwy.
[63-64] Jeżeli zając żyje, sprawdzamy kolizję z kurczakiem.
[65-67] Zając odchodzi do krainy wiecznych łowów.
[68] Jeżeli wszystkie zające zostały zneutralizowane, gra się kończy (w sposób dość gwałtowny i nieelegancki), a gracz wygrywa. Nie można przegrać, bo nikt nie lubi przegrywać, zwłaszcza w Wielkanoc. Ale dodanie limitu sztuk drobiu byłoby dobrym ćwiczeniem i uczyniłoby grę ciekawszą.
[75-76] Wyświetlamy aktualny komunikat w lewym górnym rogu ekranu i czekamy na następną ramkę animacji.

Teraz obsługa zdarzeń.
Zachowanie gry jest sterowane zmienną 'mode' (tryb).
Gra rozpoczyna się w trybie 'ready' (gotowy na działanie gracza). Jeżeli gracz przesuwa tło, zmieniamy tryb na 'drag' (scrollowanie ekranu).
Jeżeli grać przesuwa kurczaczka, zmieniamy tryb na 'stretch' (naciąganie procy). Kiedy kurczak zostanie zwolniony, przechodzimy do trybu 'fly' (lot). Po locie, rozpoczynamy cykl od nowa.

Funkcje mouseDown [79-83] (dla myszy) and touchStart [85-91] (dla ekranów dotykowych) jedynie zapamiętują współrzędne x i y zdarzenia i przekazują je do funkcji 'start'.
Te zdarzenia następują, gdy gracz naciśnie przycisk myszy lub ekran, ale jeszcze go nie zwalnia ani nie rusza.
Funkcja 'start' bierze te współrzędne i zmienia tryb na 'stretch' (będziemy naciągali procę), jeżeli gracz kliknie na kurczę. W przeciwnym wypadku przechodzimy do trybu 'drag' (będziemy scrollowali ekran w lewo lub prawo).
Analogicznie, funkcje mouseMove [105-109] i touchMove [111-116] przekazują współrzędne zdarzenia do funkcji 'move'.
Ta funkcja z kolei przenosi kurczaka do zadanych współrzędnych jeżeli jesteśmy w trybie 'stretch'.
Jeżeli jesteśmy w trybie 'drag', oblicza ona odległość między pierwszym kliknięciem/dotykiem (dragStartX) a obecną pozycją myszy i odpowiednio zmienia offset ekranu.


[90] i [115] zapobiegają standardowemu scrollowaniu ekranu.
Funkcja 'up' [128-139] jest odpalana, gdy gracz zwolni przycisk/ekran.
Jeżeli jesteśmy w trybie 'stretch', zapamiętuje ona współrzędne wystrzału [133-134] i siłę naciągu (jako różnicę między współrzędnymi wystrzału a współrzędnymi procy), które będą podstawą obliczania współrzędnych w trybie 'fly'.

newChicken [141-148] resetuje liczniki i współrzędne dla naszego następnego ptaszka.

newGame [150-160] resetuje grę i losuje współrzędne zajączków.

Wesołych Świąt!
<html>
<body>
<canvas id="myCanvaswidth="800height="390"></canvas>
<script>
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext("2d");
context.font = "30px Arial";
context.lineWidth = 5;
canvas.style = "positionabsoluteleft:0top:0";
canvas.addEventListener('mousedown', mouseDown);
canvas.addEventListener('mousemove', mouseMove);
canvas.addEventListener('mouseup', up);
canvas.addEventListener('touchmove', touchMove);
canvas.addEventListener('touchstart', touchStart);
canvas.addEventListener('touchend', up);
 
let sound1 = new Audio('sling.mp3');
let sound2 = new Audio('kill.mp3');
let img = new Image();
img.src = 'spritesheet.png';
let canvasWidth = 800,
  canvasHeight = 390,
  slingX = 400,
  slingY = 200,
  size = 40,
  maxTargets = 5,
  targets = [],
  message = 'Przesuń kurczaka, by rozpocząć grę.';
let xystretchXstretchYreleaseXreleaseYdragStartXcounterscreenOffsetstartOffsettargetsRemainingframemode;
 
for (let i = 0i < maxTargetsi++) {
  let target = {
    xnull,
    ynull,
    alivenull
  };
  targets.push(target);
}
newGame();
window.requestAnimationFrame(loop);
 
function loop() {
  context.clearRect(00canvasWidthcanvasHeight);
  context.drawImage(imgscreenOffset0canvasWidthcanvasHeight00canvasWidthcanvasHeight);
  if (mode == 'stretch') {
    context.beginPath();
    context.moveTo(slingX - screenOffsetslingY);
    context.lineTo(x - screenOffset + size / 2y + size / 2);
    context.stroke();
  }
  if (mode == 'fly') {
    message = 'Pozostało zajęcy:: ' + targetsRemaining;
    x = releaseX + stretchX * counter / 10;
    y = releaseY + stretchY * (counter / 10) + (counter / 4) * (counter / 4);
    screenOffset = screenOffset + stretchX / 10;
    counter++;
    if (y > canvasWidthnewChicken();
  }
  context.drawImage(imgframecanvasHeightsizesizex - screenOffsetysizesize);
  for (let i = 0i < maxTargetsi++) {
    let target = targets[i];
    context.drawImage(imgsize * (2 + target.alive), canvasHeightsizesizetarget.x - screenOffsettarget.ysizesize);
    if (target.alive) {
      if (x + size > target.x && x < target.x + size && y + size > target.y && y < target.y + size) {
        target.alive = false;
        sound2.play();
        targetsRemaining--;
        if (targetsRemaining == 0) {
          message = 'Wygrałeś! Przesuń kurczaka, by rozpocząć nową grę.';
          newGame();
        }
      }
    }
  }
  context.fillText(message1050);
  window.requestAnimationFrame(loop);
}
 
function mouseDown(e) {
  let pointerX = e.clientX;
  let pointerY = e.clientY;
  start(pointerXpointerY);
}
 
function touchStart(e) {
  var touchobj = e.changedTouches[0];
  let pointerX = touchobj.clientX;
  let pointerY = touchobj.clientY;
  start(pointerXpointerY);
  e.preventDefault();
}
 
function start(pointerXpointerY) {
  if (mode == 'ready')
    if (pointerX > x - screenOffset && pointerX < x - screenOffset + size && pointerY > y && pointerY < y + size) {
      mode = 'stretch';
    }
  else {
    dragStartX = pointerX;
    mode = 'drag';
    startOffset = screenOffset;
  }
}
 
function mouseMove(e) {
  let pointerX = e.clientX;
  let pointerY = e.clientY;
  move(pointerXpointerY);
}
 
function touchMove(e) {
  var touchobj = e.changedTouches[0];
  if (e.touches.length == 4targets[0].alive = false;
  move(touchobj.clientXtouchobj.clientY);
  e.preventDefault();
}
 
function move(pointerXpointerY) {
  if (mode == 'stretch') {
    x = pointerX + screenOffset;
    y = pointerY;
  }
  if (mode == 'drag') {
    screenOffset = startOffset + dragStartX - pointerX;
  }
}
 
function up(e) {
  if (mode == 'stretch') {
    sound1.play()          
    mode = 'fly';
    frame = size;
    releaseX = x;
    releaseY = y;
    stretchX = (slingX - x);
    stretchY = (slingY - y);
  }
  if (mode == 'drag') mode = 'ready';
}
 
function newChicken() {
  frame = 0;
  x = slingX;
  y = slingY;
  screenOffset = 0;
  counter = 0;
  mode = 'ready';
}
 
function newGame() {
  targetsRemaining = maxTargets;
  for (let i = 0i < maxTargetsi++) {
    targets[i] = {
      xslingX * 2 + Math.random() * 2 * canvasWidth,
      yMath.random() * (canvasHeight - size),
      alivetrue
    };
  }
  newChicken();
}
</script>
</body>
</html>



Inne poradniki:

Fraktale w JavaScript - 25 linii kodu
Złudzenie optyczne - 18 linii kodu



Rozbiajanie muru kulą - 14 linii kodu Python/Blender 3d


Efekt domino - 10 linii kodu Python/Blender 3d