프로그래밍 공부하기

JSAnimation / BrawlStars GunFight! 본문

Project/Practice

JSAnimation / BrawlStars GunFight!

ihl 2021. 1. 18. 22:16

 

SubClassDanceParty

  2일간 BrawlStars GunFight!라는 주제로 간단한 게임을 만들었다. 각 기능과 관련 클래스를 정리해본다.

 

1. CSS

 1) calc

.topbar {
  --widthA: 100px;
  --widthB: calc(var(--widthA) / 2);
  width: var(--widthB);
}
height: calc(100% - 50px);

  css에서도 간단한 사칙연산을 수행할 수 있다.  단위가 맞지 않아도 사용 가능하며, var와 중첩하여 사용 가능하다. 위의 영상이 과거에 찍은 것이라 topbar의 크기만큼 스크롤이 생겨있는 모습을 볼 수 있는데 최근 calc를 활용하여 해결하였다.

 

2) animation

.enemy {
  animation-name: move;
  animation-duration: 12s; /*애니메이션 수행에 걸리는 시간*/
  animation-iteration-count: infinite; /*애니메이션 무한반복*/
  animation-direction: alternate; /*애니메이션 완료 시 다음에는 반대방향으로 수행*/
}

@keyframes move { /*from-to 혹은 0%-50%-100%... 로 단계를 설정 후 동작을 정의한다.*/
  from { margin-left: 90%; }
  to { margin-left: 0%; }
}

  css에서 HTML 요소에 애니메이션을 넣을 수 있다. .enemy에 animation의 이름과 애니메이션 수행과 관련된 설정을을 넣은 뒤 .enemy 바깥에서 keyframse 키워드를 사용하여 애니메이션동작과 이름을 정의한다. animation은 animation-name을 입력할 때 ,(컴마)를 통해 여러개를 넣을 수도 있다.

 

3) Absolute

<div class="Field">
    <img class="Hero" src="images/Hero.png" />
</div>

.Hero {
  position:absolute;
  bottom: 0;
  right: 0;
  width: 21rem;
}

    요소를 정렬할 때 absolute를 사용할 수 있다. absolute는 position: static이 아닌 부모를 기준으로 움직인다. 즉 부모 중 position: relative 혹은 absolute 혹은 fixed인 태그 중 가장 가까운 요소를 기준으로 배치하는 것이다. 부모 중에 그러한 요소가 없다면 가장 상위태그(body) 기준으로 정렬한다. bottom: 0이라는 것은 하단으로부터 0 떨어진 곳에 붙인다는 것으로 하단에 딱 붙여 넣겠다는 의미이며, right: 0이라는 것도 마찬가지로 우측에 딱 붙여서 넣겠다는 의미이다.

 

2. JS

1. 브롤러추가

  우측 상단의 브롤러추가를 선택하면 브롤러들이 나와서 좌우로 움직인다. 먼저 DancerClass(브롤러)클래스와 주요 메소드는 다음과 같다.

DancerClass

const enemisImg = ['images/Enemy_1.png', 'images/Enemy_2.png', 'images/Enemy_3.png'];

const createDancerElement = () => {
  let dancer = document.createElement('img');
  dancer.setAttribute("src", enemisImg[parseInt(Math.random() * 3)]); //랜덤 이미지
  dancer.setAttribute("height", 150);
  dancer.setAttribute("width", 150);
  dancer.className = 'enemy';

  dancer.addEventListener("click", function (e) { //브롤러 선택시
    e.stopPropagation();
    user.hitEnemy(true);
    dancer.setAttribute("src", "images/explosion.gif"); // 폭발하기
    setTimeout(dancer.remove.bind(this), 3000); //3초 후 브롤러 제거
  })
  return dancer;
}

class DancerClass {
  constructor(top, left, timeBetweenSteps) {
    this.$node = createDancerElement();
    this.timeBetweenSteps = timeBetweenSteps;
    //this.step();
    this.setPosition(top, left);
  }

  setPosition(top, left) {
    Object.assign(this.$node.style, {
      top: `${top}px`,
      left: `${left}px`
    });
  }
  
  render(target) {
    target.appendChild(this.$node);
  }
}

  DancerClass는 생성 시 createDancerElement() 함수를 사용하여 자신을 나타내는 HTML 요소인 $node를 만든다. $node를 만들 때 자신(브롤러)이 클릭되면 어떤 행동을 할지도 포함되어 있다(폭발 후 제거). 이 때 field 영역에 있는 $node가 클릭되면 이벤트 버블링에 의해 field에도 click 이벤트가 추가되므로 이를 막기 위해 propagation()함수를 사용하였다. 그 후 브롤러 소환할 때마다 새로운 DancerClass 인스턴스가 생성되며 render 함수를 통해 field 영역에 추가된다. 

 

 

2. 스코어링 및 궁극기 사용 처리

  스코어링 및 궁극기 사용을 위해 User 클래스를 만들어 이들을 관리하며 fieldArea와 브롤러에 Click이벤트를 달고 UserClass의 hitEnemy() 함수를 호출하도록 설계하였다.

 

  사용자가 브롤러를 클릭했을 때 빗나간 경우와 명중한 경우 두 가지 케이스로 나뉜다. 빗나간 경우는 fieldArea에 클릭 이벤트가 발생한 경우이며 이 경우 유저클래스의 hitEnemy(false)를 호출한다. 명중한 경우는 위의 브롤러추가에서 설명하였듯이 브롤러 $node를 생성할 때 처리되며 유저클래스의 hitEnemy(true)를 호출한다.

//init.js: 빗나간 경우
fieldArea.addEventListener("click", (e) => {
  user.hitEnemy(false);
});

//dancerClass.js: 명중한 경우
const createDancerElement = () => {
  let dancer = document.createElement('img');
  dancer.setAttribute("src", enemisImg[parseInt(Math.random() * 3)]); //랜덤 이미지
  //...생략

  dancer.addEventListener("click", function (e) { //브롤러 클릭시
    e.stopPropagation();
    user.hitEnemy(true);
    dancer.setAttribute("src", "images/explosion.gif"); // 폭발하기
    setTimeout(dancer.remove.bind(this), 3000); //3초 후 브롤러 제거
  })
  return dancer;
}

 

  스코어링 및 궁극기 관리를 위한 User 클래스는 다음과 같다.

User 클래스

if (typeof window === 'undefined') {
    var jsdom = require('jsdom');
    var { JSDOM } = jsdom;
    var { document } = (new JSDOM('')).window;
}

class User {
    constructor() {
        this.score = 0;
        this.specialSkillState = false;
        this.hitcount = 0;
    }

    plustScore() {
        this.score += 10;
        document.querySelector("#Point").textContent = this.score;
    }

    activeSpecialSkillState(isActive) {
        this.specialSkillState = isActive;
        let img = document.querySelector("#SkillImg");
        if (isActive) {
            img.setAttribute("src", "images/SpecialAttack_active.png");
        } else {
            img.setAttribute("src", "images/SpecialAttack_non_active.png")
        }
    }

    useSpecialSkill() {
        if (this.specialSkillState) {
            let enemyList = document.querySelectorAll(".enemy"); //필드상의 모든 브롤러 리스트
            enemyList.forEach((element) => {
                this.plustScore(); //점수추가
                element.click(); //클릭이벤트 발생(-> 폭발 -> 제거)
            })
            this.activeSpecialSkillState(false); //궁극기 상태 비활성화
        }
    }

    hitEnemy(isHit) {
        if (isHit) {
            this.hitcount++;
            this.plustScore();
            console.log(this.hitcount);
            if (this.hitcount >= 3) {
                this.activeSpecialSkillState(true);
            }
        } else {
            this.hitcount = 0;
        }
    }
}

  JS는 비교적 빨리 끝났는데 CSS에 시간을 많이 소모하였다. 스크롤도 그렇고, Absolute로 요소를 우측하단에 놓는 것도 그렇고 보기에 별 건 아닌데 해본 적이 없어서 내 상황에 맞는 최적의 방법을 찾고 적용하는 것이 쉽지 않았다, CSS 경험을 많이 해봐야할 것 같다.

 

'Project > Practice' 카테고리의 다른 글

TypeScript / Project Timer  (0) 2021.04.05
GraphQL / Tour Sight Search  (0) 2021.02.21
socket.io / 실시간 채팅  (0) 2021.02.09
DOM / Twittller  (0) 2020.12.30
JS / 계산기  (0) 2020.12.17
Comments