JavaScript로 게임 만들기 - 3

Fantazindy·2022년 4월 9일
1

JSFightGame

목록 보기
3/12

각 캐릭터의 공격 범위를 만들자
공격범위의 attackBox를 만들고 캐릭터 색 구분을 효과적으로 하기 위해 color도 추가했다

class Sprite {
  constructor({ position, velocity, color }) {
    this.position = position;
    this.velocity = velocity;
    this.height = 150;
    this.lastKey;
    this.attackBox = {
      position: this.position,
      width: 100,
      height: 50,
    };
    this.color = color;
  }

draw에 attackBox를 그려주도록 하고 player와 enemy객체에 color도 추가

  draw() {
    c.fillStyle = this.color;
    c.fillRect(this.position.x, this.position.y, 50, this.height);

    //attack box
    c.fillStyle = "blue";
    c.fillRect(
      this.attackBox.position.x,
      this.attackBox.position.y,
      this.attackBox.width,
      this.attackBox.height
    );
  }
const player = new Sprite({
  position: {
    x: 0,
    y: 0,
  },
  velocity: {
    x: 0,
    y: 0,
  },
  color: "green",
});

const enemy = new Sprite({
  position: {
    x: 400,
    y: 100,
  },
  velocity: {
    x: 0,
    y: 0,
  },
  color: "red",
});


이제 attackBox가 상대에게 닿았을 때의 코드를 작성해보자 animate함수 내에

  if (
    player.attackBox.position.x + player.attackBox.width >=
    enemy.position.x
  ) {
    console.log("hit");
  }

를 추가하고 보면

콘솔에 hit가 지속 출력된다.
그런데 문제가 있는데 점프를 하고 있어 attackBox가 닿지 않는데도 x축만을 인식해서 clg문을 지속해서 출력하고 있었다.
if문에 조건을 좀 더 추가해보자.
그 전에 width 값 사용을 위해 Sprite클래스 내 프로퍼티로 추가하자

    this.velocity = velocity;
    this.width = 50
    this.height = 150;
    this.lastKey;
    this.attackBox = {
    ...
    
      draw() {
    c.fillStyle = this.color;
    c.fillRect(this.position.x, this.position.y, this.width, this.height);
	...

그리고 조건을 바꿔주면

  if (
    player.attackBox.position.x + player.attackBox.width >= enemy.position.x &&
    player.attackBox.position.x <= enemy.position.x + enemy.width &&
    player.attackBox.position.y + player.attackBox.height >= enemy.position.y &&
    player.attackBox.position.y <= enemy.position.y + enemy.height
  ) {
    console.log("hit");
  }


그런데 공격 키를 누를때만 피격판정이 있어야 하기 때문에 isAttacking이라는 프로퍼티를 만들자

class Sprite {
  ...
    };
    this.color = color;
    this.isAttacking;
  }

공격시 딜레이를 주는 attack메소드도 만들고

  attack() {
    this.isAttacking = true;
    setTimeout(() => {
      this.isAttacking = false;
    }, 100);
  }

조건문에도 isAttacking을 추가한 뒤

  if (
    player.attackBox.position.x + player.attackBox.width >= enemy.position.x &&
    player.attackBox.position.x <= enemy.position.x + enemy.width &&
    player.attackBox.position.y + player.attackBox.height >= enemy.position.y &&
    player.attackBox.position.y <= enemy.position.y + enemy.height &&
    player.isAttacking
  ) {
    console.log("hit");
  }

이벤트리스너에 스페이스바 키로 공격메소드를 실행하도록 한다

window.addEventListener("keydown", (event) => {
  //player key
  switch (event.key) {
    ...
    case "w":
      player.velocity.y = -15;
      break;
    case " ":
      player.attack();
      break;


스페이스바 입력시에만 공격 메소드가 실행된다.
그런데 콘솔을 보면 hit가 중복되서 출력되는데 이러면 피격이 여러번 되는 문제가 생긴다.
앞선 if문 안에

  if (
    player.attackBox.position.x + player.attackBox.width >= enemy.position.x &&
    player.attackBox.position.x <= enemy.position.x + enemy.width &&
    player.attackBox.position.y + player.attackBox.height >= enemy.position.y &&
    player.attackBox.position.y <= enemy.position.y + enemy.height &&
    player.isAttacking
  ) {
    player.isAttacking = false;
    console.log("hit");
  }

동작시 isAttacking을 false가 되도록 하여 중첩피격을 방지하자
이제 공격중일때만 attackBox가 보이도록 draw메소드의 attackbox부분을 if문으로 감싸면

    if (this.isAttacking) {
      c.fillStyle = "blue";
      c.fillRect(
        this.attackBox.position.x,
        this.attackBox.position.y,
        this.attackBox.width,
        this.attackBox.height
      );
    }

이제 enemy의 히트박스 위치를 수정해야한다.
attackBox 항시 보이도록 잠시 if문을 지우고 attackBox프로퍼티의 position을 객체로 만들어 x,y값을 넣자

class Sprite {
  constructor({ position, velocity, color }) {
    ...
    this.attackBox = {
      position: this.position,
      width: 100,
      height: 50,
    };
    ...
  }


attackBox의 위치가 이상한데 포지션을 따로 다시 잡아줘야 한다.
update메소드에서 다시 잡아주면 된다

  update() {
    this.draw();
    this.attackBox.position.x = this.position.x;
    this.attackBox.position.y = this.position.y;

    ...


그런데 enemy의 attackBox가 반대로 돌아가 있는 문제가 있다.
이를 해결하기 위해 attackBox프로퍼티 내에 offset을 추가하자

class Sprite {
  constructor({ position, velocity, color, offset }) {
    ...
    this.attackBox = {
      position: {
        x: this.position.x,
        y: this.position.y,
      },
      offset,
      width: 100,
      height: 50,
    };
...

const player = new Sprite({
  ...
  offset: {
    x: 0,
    y: 0,
  }
const enemy = new Sprite({
  ...
  offset: {
    x: 50,
    y: 0,
  }
});

update메소드 내에 attackBox를 그려줄 때 이 offset을 적용하면

  update() {
    this.draw();
    this.attackBox.position.x = this.position.x - this.attackBox.offset.x;
    this.attackBox.position.y = this.position.y;


해결이 된다.

그런데 앞서 설정한 조건문의 내용이 너무 길다.
해당 조건문은 두 캐릭터가 attackBox에 접촉했을때 실행되는 조건이 있는데 이를 하나로 묶어보자
{ rectangle1, rectangle2} 를 파라미터로 받는 rectangularCollision 라는 함수를 만들고
return값으로 해당 조건을 넣어준다

function rectangularCollision({ rectangle1, rectangle2}) {
  return (
    rectangle1.attackBox.position.x + rectangle1.attackBox.width >= rectangle2.position.x &&
    rectangle1.attackBox.position.x <= rectangle2.position.x + rectangle2.width &&
    rectangle1.attackBox.position.y + rectangle1.attackBox.height >= rectangle2.position.y &&
    rectangle1.attackBox.position.y <= rectangle2.position.y + rectangle2.height
  )
}

player와 enemy대신 파라미터로 넘겨준 rectangle1, rectangle2를 전달해줘야한다.
그리고 기존의 조건문 안에 파라미터로 player, enemy를 받는 rectangularCollision함수를 넣으면

  if (
    rectangularCollision({ rectangle1: player, rectangle2: enemy }) &&
    player.isAttacking
  ) {
    player.isAttacking = false;
    console.log("hit");
  }

코드를 훨씬 깔끔하게 관리할 수 있다.
enemy가 공격할 경우의 if문을 하나 더 만들고 key switch문을 작성한 뒤

  if (
    rectangularCollision({ rectangle1: enemy, rectangle2: player }) &&
    enemy.isAttacking
  ) {
    enemy.isAttacking = false;
    console.log("hit");
  }
  
  ...
      case "ArrowDown":
      enemy.isAttacking = true;
  }

불필요한 clg문들을 지우고 실행해 보면

잘 작동한다.

이제 다시 if문을 추가해 공격시에만 attackBox가 보이도록 하자.

다음 포스팅에선 체력바 인터페이스를 만들어볼 예정이다.

profile
풀스택 개발자를 목표로 성장중입니다!

1개의 댓글

comment-user-thumbnail
2023년 2월 27일

상속으로 Player와 Enemy 클래스를 따로 구분해서 구현하면 더 쉽고 관리하기 편해요!

답글 달기