Javascript Tips

캐릭터메신저구현기 부드러운 무빙 - requestAnimationFrame

탐훈 2025. 4. 4. 07:36
728x90

캐릭터 이동시 방향키로 이동을 하는데 

누르고 있을 경우에도 무빙을 원했다. 

 

keyDown, keyUp을 이용해

움직임 유지를 결정하고 

painting이 다시 일어날 때마다 

특정함수(캐릭터의움직임)을 계속해주는 requestAnimationFrame

setInerval 같은 녀석을 사용해본다.

출처 - https://inpa.tistory.com/entry/%F0%9F%8C%90-requestAnimationFrame-%EA%B0%80%EC%9D%B4%EB%93%9C

 


1. 구현

<html>
...생략
</html>


<script>
  const person = document.querySelector('.person');

  const p1 = new Person(person);

  document.addEventListener('keydown', (e) => {
    p1.move(e.key);
  });

  document.addEventListener('keyup', (e) => {
    p1.clearKey();
  })
</script>
class Person {
 ...생략
 
  move(key) {
    this.key = key;

    if (!this.key) cancelAnimationFrame(move);

    switch (key) {
      case 'ArrowUp' :
        this.setTop(-10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowDown' :
        this.setTop(10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowLeft' :
        this.setLeft(-10);
        this.target.style.left = this.getLeft();
        break;
      case 'ArrowRight' :
        this.setLeft(10);
        this.target.style.left = this.getLeft();
        break;
      default:
        break;
    }

    requestAnimationFrame(move);
  }
}

 

구현을 보면

keyDown에 move를 걸어놨다.

 

move 안에서 requestAnimationFrame을 호출하게 되는데

이렇게 되면 버튼을 누르고 있으면

move가 계속 호출되어

'requestAnimationFrame'도 계속 호출하게 된다.

 

의미가 없어진다.

 

painting이 일어나면서 requestAnimationFrame이 감지한 뒤 메소드를 실행시켜줘야하는데

이렇게 소스를 짜면

강제로 메소드를 실행시킨다.. 

 

움직이도 끊겨서 동작한다.


2. 수정된 전체 소스

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./index.css">
</head>
<body>
  <div class="background">
    <div class="content">
      <img src="./images//habboman.png" alt="" class="person">

      <img src="./images/object1.png" alt="" class="obj object1">
      <img src="./images/object2.png" alt="" class="obj object2">
    </div>
  </div>
</body>
</html>
<script src="./mixin.js"></script>
<script>
  const person = document.querySelector('.person');

  const p1 = new Person(person);

  document.addEventListener('keydown', (e) => {
    p1.setKey(e.key);
  });

  document.addEventListener('keyup', (e) => {
    p1.clearKey();
  })

  p1.move();
</script>
class Person {
  constructor(domNode) {
    const PARENT = domNode.parentNode

    this.key = null;
    this.target = domNode;
    this.top = this.target.offsetTop;
    this.left = this.target.offsetLeft;
    this.down = PARENT.clientHeight - (domNode.offsetTop + domNode.offsetHeight);
    this.right = PARENT.clientWidth - (domNode.offsetLeft + domNode.offsetWidth);
  }

  getTop() {
    return this.top + 'px';
  }

  getDown() {
    return this.down + 'px';
  }

  getRight() {
    return this.right + 'px';
  }

  getLeft() {
    return this.left + 'px';
  }

  setTop(top) {
    if (this.top <= -10 && top < 0) return; // 상단 끝
    if (this.top >= 710 && top > 0) return; // 단 끝

    if (top.includes?.('px')) {
      top = top.replace('px', '');
    }

    this.top += top;
  }

  setDown(down) {
    if (down.includes?.('px')) {
      down = down.replace('px', '');
    }

    this.down += down;
  }

  setRight(right) {
    if (right.includes?.('px')) {
      right = right.replace('px', '');
    }

    this.right += right;
  }

  setLeft(left) {
    if (this.left <= -23 && left < 0) return; // 왼쪽 끝

    if (this.left >= 667 && left > 0) return; // 오른쪽 끝

    if (left.includes?.('px')) {
      left = left.replace('px', '');
    }

    this.left += left;
  }

  clearKey() {
    this.key = null;
  }

  setKey(key) {
    console.log(key);
    this.key = key;
  }

  move() {
    if (!this.key) cancelAnimationFrame(window.raf);

    switch (this.key) {
      case 'ArrowUp' :
        this.setTop(-10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowDown' :
        this.setTop(10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowLeft' :
        this.setLeft(-10);
        this.target.style.left = this.getLeft();
        break;
      case 'ArrowRight' :
        this.setLeft(10);
        this.target.style.left = this.getLeft();
        break;
      default:
        break;
    }

    window.raf = requestAnimationFrame(this.move);
  }
}

 

이렇게 수정하는 경우 

requestAnimationFrame(this.move)를 할 경우

this.key를 못 찾는다.

 

따라서

스크립트 부분은 다음과 같이 변경해야한다.

  move() {
    if (!this.key) cancelAnimationFrame(window.raf);

    switch (this.key) {
      case 'ArrowUp' :
        this.setTop(-10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowDown' :
        this.setTop(10);
        this.target.style.top = this.getTop();
        break;
      case 'ArrowLeft' :
        this.setLeft(-10);
        this.target.style.left = this.getLeft();
        break;
      case 'ArrowRight' :
        this.setLeft(10);
        this.target.style.left = this.getLeft();
        break;
      default:
        break;
    }

    window.raf = requestAnimationFrame(this.move.bind(this));
  }

 

requestAnimationFrame은 window 함수이기 때문에

Person 클래스 내부의 this 에 붙은 필드값을 못찾는다.

 

그래서 bind 함수를 이용해

저 함수를 Person에 붙여서 사용할 수 있도록 처리하면 된다..