정보실

웹학교

정보실

javascript Space Invaders를 코딩하는 방법

본문

1978 년에 출시 된 가장 인기 있는 게임은 Space Invaders였습니다. 불과 4 년 만에 38 억 달러를 벌었다! 우주 침략자들은 1976 년에 출시 된 아타리 게임 브레이크 아웃에서 많은 영감을 얻은 토모로 히로 니시 카도에 의해 개발되었습니다.이 게임은 2 주 전에 제 블로그를 코딩하려고 생각했지만 새로운 개념을 가져 오기에는 탁구와 너무 비슷하다고 생각했습니다. 그것은 시리즈에서 아직 다루지 않은; 대신 봉쇄를 했습니다.


나는 지구상의 거의 모든 사람들이 어느 시점에서 그것을 플레이 했기 때문에 Space Invaders를 선택했습니다.


https://codeheir.com/2019/03/17/how-to-code-space-invaders-1978-7/ 


우리가 코딩해야 할 첫 번째 것은 외계인이지만, 먼저 고려해야 할 규칙이 거의 없습니다.

  • 외계인이 일제히 움직입니다.
  • 벽에 부딪히면 아래쪽으로 이동하여 반대 방향으로 이동합니다.
  • 최하위 외계인 만 특정 열을 쏠 수 있습니다.

외계인이 함께 움직이게! 


aliens-moving.gif?w=1060 


귀여운 작은 외국인 클래스 


외계인 클래스의 임무는 지정된 위치에 외계인을 표시하는 것이므로 해당 외계인의 x, y 위치 및 이미지를 가져옵니다. 내 게임에서는 외계인 이미지를 하나만 사용하지만 원하는 만큼 계속 사용할 수 있습니다!


class Alien {
    constructor(x, y, image) {
 
        this.x = x;
        this.y = y;
        this.image = image;
    }
 
    draw() {
        image(this.image, this.x, this.y, this.image.width/30, this.image.height/30);
    }
 
}


예, 여기 게임에 사용한 외계인 이미지가 있습니다. P5에서 이미지를 스케일링하는 방법을 보여주기 위해 어리석게 큰 외계인을 선택했습니다. draw () 함수의 코드 줄을 살펴보면 image () 함수에 2 개의 선택적 인수를 추가했음을 알 수 있습니다.이 예제에서는 이미지의 너비와 높이입니다. 이미지의 너비와 높이를 30으로 나누었습니다. 자세한 내용은 여기를 참조하십시오.


invader1.png?w=1060 


외계인의 이미지와 행 수를 생성자 인수로 사용하는 Invaders라는 새 클래스를 만들어 보겠습니다. 행의 수는 나중에 게임을 더 어렵게 만들 수 있어야 합니다.


class Invaders {
    constructor(alienImage, rowsCount) {
        this.alienImage = alienImage;
        this.rowsCount = rowsCount;
        this.direction = 0;
        this.y = 40;
        this.aliens = this.initialiseAliens();
        this.bullets = [];
     
        this.speed = 0.2;
 
    }
 
 
    update() {
        for (let alien of this.aliens) {
            if (this.direction == 0) {
                alien.x+= this.speed;
            } else if (this.direction == 1) {
                alien.x-= this.speed;
            }
        }
 
    
 
        if (this.hasChangedDirection()) {
            this.moveAlienDown();
        }
         
    }
 
 
 
    hasChangedDirection() {
        for (let alien of this.aliens) {
            if (alien.x >= width - 40) {
                this.direction = 1;
                return true;
            } else if (alien.x <= 20) {
                this.direction = 0;
                return true;
            }
        }
        return false;
    }
 
    moveAlienDown() {
        for (let alien of this.aliens) {
            alien.y += 10;
        }
 
    }
 
 
    initialiseAliens() {
        let aliens = [];
        let y = 40;
        for (let i = 0; i < this.rowsCount; i++) {
            for (let x = 40; x < width - 40; x += 30) {
                aliens.push(new Alien(x, y, this.alienImage));
            }
            y += 40;
        }
        return aliens;
    }
 
    draw() {
        for (let alien of this.aliens) {
            alien.draw();
        }
 
    }
 
  
 
}


하단 외계인 촬영. 


외계인 제거 


최하위 외계인을 촬영하려면 먼저 게임에서 외계인을 제거하는 방법을 추가해야 합니다. 테스트 목적으로 클릭 핸들러를 추가하여 외계인을 삭제하겠습니다.


Invaders 클래스에서 충돌을 확인하기 위한 새로운 기능을 추가하고 충돌이 있는 경우 게임에서 외계인을 제거하십시오.


checkCollision(x, y) {
  for (let i = this.aliens.length - 1; i &gt;= 0; i--) {
      let currentAlien = this.aliens[i];
      // the numbers are hard-coded for the width of the image
      if (dist(x, y, currentAlien.x + 11.5, currentAlien.y + 8) < 10) {
          this.aliens.splice(i, 1);
          return true;
      }
  }
  return false;
}


그리고 스케치에서 단순히 mousePressed() 핸들러를 추가 할 수 있습니다.


function mousePressed() {
    invaders.checkCollision(mouseX, mouseY);
}


SHOOTING 


이제 우리는 각 칼럼에서 맨 아래로 외계인을 가져 와서 벌레들을 쏴야 합니다. 새 코드에 주석을 추가했습니다.


class Invaders {
    constructor(alienImage, rowsCount) {
        this.alienImage = alienImage;
        this.rowsCount = rowsCount;
        this.direction = 0;
        this.y = 40;
        this.aliens = this.initialiseAliens();
        this.bullets = [];
     
        this.speed = 0.2;
       
        // to make sure the aliens dont spam
        this.timeSinceLastBullet = 0;
 
    }
 
 
    update() {
        for (let alien of this.aliens) {
            if (this.direction == 0) {
                alien.x+= this.speed;
            } else if (this.direction == 1) {
                alien.x-= this.speed;
            }
        }
 
    
 
        if (this.hasChangedDirection()) {
            this.moveAlienDown();
        }
 
 
 
        if (this.aliens.length == 0) {
            this.nextLevel();
        }
       
       
       if (this.timeSinceLastBullet &gt;= 40) {
          let bottomAliens = this.getBottomAliens();
 
          if (bottomAliens.length) {
              this.makeABottomAlienShoot(bottomAliens);
          
        }
        this.timeSinceLastBullet++;
       
       
      // to move the bullets
      this.updateBullets();
         
    }
   
 
 
 
    hasChangedDirection() {
        for (let alien of this.aliens) {
            if (alien.x &gt;= width - 40) {
                this.direction = 1;
                return true;
            } else if (alien.x <= 20) {
                this.direction = 0;
                return true;
            }
        }
        return false;
    }
 
    moveAlienDown() {
        for (let alien of this.aliens) {
            alien.y += 10;
        }
 
    }
   
   // to make sure only the bottom row will shoot
   getBottomAliens() {
        let allXPositions = this.getAllXPositions();
 
        let aliensAtTheBottom = [];
        for (let alienAtX of allXPositions) {
            let bestYPosition = 0;
            let lowestAlien;
 
            for (let alien of this.aliens) {
                if (alien.x == alienAtX) {
 
                    if (alien.y &gt; bestYPosition) {
                        bestYPosition = alien.y;
                        lowestAlien = alien;
                    }
 
                }
            }
            aliensAtTheBottom.push(lowestAlien);
        }
 
        return aliensAtTheBottom;
    }
 
 
 
 
    nextLevel() {
        this.speed += 0.5;
        this.aliens = this.initialiseAliens();
    }
 
 
        // get all the x positions for a single frame
    getAllXPositions() {
        let allXPositions = new Set();
        for (let alien of this.aliens) {
            allXPositions.add(alien.x);
        }
        return allXPositions
    }
     
    initialiseAliens() {
        let aliens = [];
        let y = 40;
        for (let i = 0; i < this.rowsCount; i++) {
            for (let x = 40; x < width - 40; x += 30) {
                aliens.push(new Alien(x, y, this.alienImage));
            }
            y += 40;
        }
        return aliens;
    }
 
    draw() {
       
        // draw the bullets first so they're underneath
      for (let bullet of this.bullets) {
          rect(bullet.x, bullet.y,  3, 10);
      }
       
      for (let alien of this.aliens) {
          alien.draw();
      }
       
   
 
    }
   
    checkCollision(x, y) {
      for (let i = this.aliens.length - 1; i &gt;= 0; i--) {
          let currentAlien = this.aliens[i];
 
          if (dist(x, y, currentAlien.x + 11.5, currentAlien.y + 8) < 10) {
              this.aliens.splice(i, 1);
              return true;
          }
      }
      return false;
    }
   
   
    makeABottomAlienShoot(bottomAliens) {
      let shootingAlien = random(bottomAliens);
 
      let bullet = new AlienBullet(shootingAlien.x + 10, shootingAlien.y + 10);
     
      this.bullets.push(bullet);
      this.timeSinceLastBullet = 0;
    }
   
 
     updateBullets() {
        for (let i = this.bullets.length - 1; i &gt;= 0; i-- ) {
            this.bullets[i].y  += 2;
        }
    }
 
}


또한 Bullet 클래스와 Alien Bullet 클래스를 추가했습니다. 이는 플레이어를 위한 몇 가지 기본 기능이 필요하기 때문입니다. 플레이어가 쏠 때 총알은 반대 방향으로 이동합니다.


BULLET 


class Bullet {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
 
    draw() {
        fill(255);
        rect(this.x, this.y, 3, 10);
    }
}


ALIEN BULLET 


class AlienBullet extends Bullet {
    constructor(x, y) {
        super(x, y);
    }
 
    update() {
        this.y += 2;
    }
}


플레이어 추가! 


shootss.gif?w=1060 


사용자가 통제 할 플레이어 / 외국인 전투기를 추가해 보겠습니다. 계속해서 아래 이미지를 복사하십시오.


class Player {
    constructor(shooterImage) {
        this.image = shooterImage;
        this.x = width / 2;
        this.y = height -30;
 
        this.isMovingLeft = false;
        this.isMovingRight = false;
        this.bullets = [];
 
    }
 
    update() {
        if (this.isMovingRight) {
            this.x += 1;
        } else if (this.isMovingLeft) {
            this.x -= 1;
        }
 
        this.constrain();
 
        this.updateBullets();
    }
 
    updateBullets() {
         
        for (let i = this.bullets.length - 1; i &gt;= 0; i--) {
            this.bullets[i].update();
 
            if (this.hasHitAlien(this.bullets[i])) {
                this.bullets.splice(i, 1);
                break;
            } else if (this.bullets[i].isOffScreen()) {
                this.bullets.splice(i, 1);
                break;
 
            }
        }
    }
 
 
    hasHitAlien(bullet) {
        return invaders.checkCollision(bullet.x, bullet.y);
    }
 
 
    constrain() {
 
        if (this.x <= 0) {
            this.x = 0;
        } else if(this.x &gt; width - 23) {
            this.x = width - 23;
        }
    }
 
    draw() {
        image(this.image, this.x, this.y, this.image.width / 20, this.image.height/20);
 
        this.drawBullets();
    }
 
 
    drawBullets() {
        for (let bullet of this.bullets) {
            bullet.draw();
        }
    }
 
 
    drawLives() {
        fill(255);
        textSize(15);
        text("LIVES", 250, 25);
        for (let i = 0; i < this.lives; i++) {
            image(this.image, 300 + i * 30, 10, this.image.width / 20, this.image.height/20);
        }
    }
 
    drawScore() {
 
        text("SCORE", 50, 25);
 
        push();
        fill(100, 255, 100);
        text(this.score, 110, 25);
        pop();
    }
 
    moveLeft() {
        this.isMovingRight = false;
        this.isMovingLeft = true;
    }
    moveRight() {
        this.isMovingLeft = false;
        this.isMovingRight = true;
    }
 
    shoot() {
        this.bullets.push(new PlayerBullet(this.x + 12, this.y));
    }
 
}


그런 다음 플레이어를 위해 다른 글 머리 기호 클래스를 만들어야 합니다.이 클래스는 Bullet 클래스를 확장하므로 모든 그리기 동작을 가져옵니다.


class PlayerBullet extends Bullet {
    constructor(x, y) {
        super(x, y);
    }
 
    update() {
        this.y -= 6;
    }
}


그런 다음 sketch.js에서 마우스 누름 처리기를 추가하여 화살표 키나 스페이스 바를 누르면 동작, 이동 또는 촬영이 각각 수행됩니다.


// player moving and shooting
function keyPressed() {
  if (keyCode === RIGHT_ARROW || keyCode == 88) {
    player.moveRight();
  } else if (keyCode === LEFT_ARROW || keyCode == 90) {
    player.moveLeft();
  } else if (keyCode === 32) {
    player.shoot();
  }
}



해야 할 일 

done.gif?w=1060 


분명히 이것은 우리에게 Space Invaders 게임의 핵심 메커니즘을 제공하지만 잠재적으로 더 많은 것을 추가 할 수 있습니다. 내가 github에서 볼 수 있도록 코딩 한 것은 최대 점수를 매기는 시스템과 난이도 시스템입니다 (첫 번째 파도를 지우면 다음 파도가 더 빠릅니다). 뒤에 숨길 수 있는 파괴 가능한 암석을 추가 할 수도 있습니다!


  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 26회 ]  작성일19-08-25 11:53

웹학교