[My toy] 움직이는 공 1 - JavaScript

MandarinePunch·2022년 3월 22일
3

토이 프로젝트

목록 보기
4/6
post-thumbnail

공 튕기기 (공도 잡을 수 있어요!)


✍ 만들게 된 계기

Java공부를 하던 중, 가볍게 재밌는 프로젝트를 만들고 싶어 Interactive Developer 김종민님의 유튜브를 찾아봤다. 사실 canvas도 주먹구구식으로 찾아본 것이 전부라 조금 더 알고싶은 마음에서도 있었다.
그렇게 첫 영상을 시청했는데.. 프로그래밍 방식이 굉장히 좋아보였다!
현재 객체지향 언어를 공부 중이지만, 실상 프로그래밍 방식은 객체지향과는 거리가 멀게 구현하고 있었는데,
김종민님은 스크립트 언어인 JS를 객체지향 방식으로 구현하고 있었다.
굉장히 어려워 보였는데, 코드가 확실히 깔끔해서 그 방식을 따라해보기로 했다.


🔨 작업 코드

우선 캔버스를 미리 세팅해준다.

class App {
  constructor() {
    // canvas를 생성해주고
    this.canvas = document.createElement("canvas");
    // body에 추가한다.
    document.body.appendChild(this.canvas);

    this.ctx = this.canvas.getContext("2d");
    // 디바이스에 따라 선명도를 올려주기 위해 사용
    this.pixelRatio = window.devicePixelRatio > 1 ? 2 : 1;

    // 창 사이즈가 변할 때마다 캔버스를 변화시켜줘야 하기에 선언
    window.addEventListener("resize", this.resize.bind(this), false);
    this.resize();

    // 움직이는 공을 위한 애니메이션 함수
    window.requestAnimationFrame(this.animate.bind(this));
  }

  resize() {
    // resize때마다 canvas의 width, height를 창 사이즈로 만들어 주기 위함.
    this.stageWidth = document.body.clientWidth;
    this.stageHeight = document.body.clientHeight;

    this.canvas.width = this.stageWidth * this.pixelRatio;
    this.canvas.height = this.stageHeight * this.pixelRatio;
    // 선명도를 좋게 해주기 위함
    this.ctx.scale(this.pixelRatio, this.pixelRatio);
  }

  animate() {
    window.requestAnimationFrame(this.animate.bind(this));
  }
}
// 캔버스 실행
window.onload = () => {
  new App();
};

김종민님의 영상을 보면 거의 이런 식으로 캔버스를 미리 세팅한다.
처음에는 잘 이해가 안갔는데, 몇 번 해보니 이 방식이 편하다는 것을 알았다.

- 공 만들기

캔버스는 만들어졌으니 공을 만들어주도록 하자
우선, 공이 나타나는 위치를 먼저 지정해준다.


export class Ball {
  // 처음 공의 위치를 화면 내에 랜덤하게 줄 예정이기에, 현재화면의 width와 height를 가져온다.
  constructor(stageWidth, stageHeight, radius, speed) {
    this.radius = radius;
    // 공이 움직이는 속도
    this.vx = speed;
    this.vy = speed;

    // 우선 공의 지름을 잡는다.
    const diameter = this.radius * 2;
    // 공이 화면 밖에 생성되면 안되기 때문에 원의 중앙(x, y)을 잡아준다.
    this.x = this.radius + Math.random() * (stageWidth - diameter);
    this.y = this.radius + Math.random() * (stageHeight - diameter);
  }
}

공의 위치를 잡았으니 이제 공을 만들어보자.

import { Ball } from "./ball.js";

class App {
  constructor() {
	...
    // 공을 만들어 준다.
    this.ball = new Ball(this.stageWidth, this.stageHeight, 60, 10);
    window.requestAnimationFrame(this.animate.bind(this));
  }
  ...
}
export class Ball {
  constructor(stageWidth, stageHeight, radius, speed) {
	...
  }
  // 공을 그릴 함수
  draw(ctx) {
    ctx.fillStyle = "yellow";
    ctx.beginPath();
    // 현재 중심 (x, y)에서 원을 만든다.
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
  }
}

짠! 노란 공이 그려졌다. 이제 애니메이션을 이용해서 공을 움직여 보도록 하자.

- 공 움직이기

export class Ball {
  constructor(stageWidth, stageHeight, radius, speed) {
    ...
  }

  draw(ctx, stageWidth, stageHeight) {
    // 지속적으로 값이 증가함으로써 공이 움직이는 것처럼 보일 예정
    this.x += this.vx;
    this.y += this.vy;
	// 공이 화면에 닿으면 튀게끔 함수를 만듦
    this.bounceWindow(stageWidth, stageHeight);

    ctx.fillStyle = "yellow";
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
  }

  bounceWindow(stageWidth, stageHeight) {
    const minX = this.radius;
    const maxX = stageWidth - this.radius;
    const minY = this.radius;
    const maxY = stageHeight - this.radius;
	// 창 끝에 닿으면
    if (this.x <= minX || this.x >= maxX) {
      // 증가 값을 음수로 만들어 반대로 이동하게 한다.
      this.vx *= -1;
      this.x += this.vx;
    }
    if (this.y <= minY || this.y >= maxY) {
      this.vy *= -1;
      this.y += this.vy;
    }
  }
}
import { Ball } from "./ball.js";

class App {
  constructor() {
	...
    // 공을 만들어 준다.
    this.ball = new Ball(this.stageWidth, this.stageHeight, 60, 10);

    window.requestAnimationFrame(this.animate.bind(this));
  }

  ...

  animate() {
    window.requestAnimationFrame(this.animate.bind(this));
	// 만들어진 공을 그린다.
    this.ball.draw(this.ctx, this.stageWidth, this.stageHeight);
  }
}

- 버그 발견 🔍

공이 튀긴하는데.. 남겨진 잔상이 없어지지 않아서 이런 현상이 발생하는 것 같다.
animate함수에 애니메이션을 실행할때마다 canvas를 clear하도록 수정해준다.

- 버그 수정 ✅

  animate() {
    window.requestAnimationFrame(this.animate.bind(this));
    // 애니메이션 함수 호출시 캔버스 clear
    this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);

    this.ball.draw(this.ctx, this.stageWidth, this.stageHeight);
  }

그 결과..

공이 튀는 듯한 모션으로 바뀌었다! 이제 가운데 블록을 만들어 블록에 닿아도 튕겨 나가게 만들어 줘야하는데 글이 많이 길어질 것 같아 다음편에 이어서 쓰겠다. 😀

profile
개발을 좋아하는 귤나라 사람입니다.

0개의 댓글