JiWoo.
내가 만들어가는 이야기
JiWoo.
전체 방문자
오늘
어제
  • 분류 전체보기 (81)
    • 개발 일지 (33)
      • 트러블 슈팅 (4)
      • 소개 포트폴리오 (9)
      • 데일리 커밋 봇 (3)
      • 쁘걸 팬사이트 (3)
      • 회사 홈페이지 (6)
      • 게임 룰렛 ( 돌림판 ) (2)
      • 도트 팔레트 추출 (3)
    • 생각하는 시간 (37)
      • 브라우저 (1)
      • 자바스크립트 (11)
      • 타입스크립트 (7)
      • 리액트 (8)
      • 알고리즘 (2)
      • 깃 (5)
      • 노드 (2)
      • 인프라 (0)
      • 유니티 (0)
    • 취미 (5)
      • 커스텀 키보드 (5)
    • 기록 (6)
      • 회고 (1)
      • 쉬어가요 (5)

블로그 메뉴

  • 깃허브
  • 노션

포트폴리오

  • BraveGirls Fan Site
  • Minecraft SkinClouds
  • Jiwoo.So _

공지사항

최근 글

hELLO · Designed By 정상우.
JiWoo.

내가 만들어가는 이야기

돌림판 동작 구현 #2
개발 일지/게임 룰렛 ( 돌림판 )

돌림판 동작 구현 #2

2025. 5. 20. 03:46

이전 내용처럼 코드를 조금 더 다듬고 정리하고 디자인까지 바꾼 후 글을 작성하러 왔다.

 

처음 결과물은 만들어 낸 것도 계속되는 이슈 속에서 탄생한 뿌듯한 결과물이였지만,

아무래도 만들어 본 적 없던 알고리즘의 동작이라 자잘한 이슈가 발생했다.

 

그 말은 즉, 만들기에만 급했던 결과물이고 불안정한 코드임을 의미했다.

다른 기능을 추가하기에도 재사용성이 좋은 메서드가 많이 없어 뭔가 추가할 때도 애를 먹었다.

 

그래서 동작은 그대로 유지하면서 코드를 싹 갈아엎자고 생각했다.


코드를 살펴보면서 ..

일주일만에 결과물을 내려다 보니까 생각보다 꼼꼼히 보지 못한 동작이 많았던 거 같다.

그래서 나는 다음 2주 동안 코드를 살펴보고 테스트 중에 발생했던 이슈들을 고쳐보기로 마음 먹었다.

 

아니지,, 이슈들을 무조건 고쳐내기 위해 노력했다.

하지만 해당 이슈들은 재사용성에 신경 쓰지 못하고 내버려뒀던 코드에서 부수적인 문제가 발생했던 것이다.

 

먼저, 각 기능 별로 재사용이 가능한 메서드들로 역할 분배를 하면 괜찮을 거 같았다.


처음으로 진행한 것

나는 코드를 갈아엎기 위해 제일 핵심 동작을 맞고 있는 Spinner 동작에 대한 코드를 살펴보기로 했다.

살펴보면서 코드의 역할을 나누기 위해서 어떤 역할들을 맡고 있는 것이 좋은 지 생각했다.

Spinner 의 동작

1. 아이템 별 돌림판 영역 그려내는 기능 ( 폰트 / 돌림판 크기 변경, 아이템 추가, 수정, 삭제 등 발생 시 .. )

2. 돌림판 재구성 기능

3. 돌림판 돌리기

4. 돌림판 멈추기


코드로 옮겨보자

기존에는 각 window 객체 위에 메서드 구현을 진행했다.

하지만, 이 상태라면 전역 객체를 오염시키고 혹시나 같은 변수명으로 덮어씌워지는 일도 발생하지 않겠는가 ..?

 

그러면 이 문제를 어떻게 해결하는 게 좋을까?

new 생성자를 통해 객체를 관리하는 것

내가 생각한 방법은 spinner 에 대한 동작을 담은 new 생성자 객체를 만들어 내는 것이다.

그러면 전역에 spinner 라는 객체를 생성하고 그 객체를 통해 돌림판을 통제하면 되는 것이다.

 

먼저, Spinner 객체를 작성하기 전에 베이스가 되는 객체를 만들어야한다.


돌림판을 그리기 전 베이스 데이터 준비 ( Product 객체 )

베이스가 될 데이터는 Product 라는 아이템 관리 객체가 필요하다.

Product 객체의 역할은 등록된 아이템 정보와 크기를 저장하고, 아이템을 쉽게 관리하기 위해 도와주는 key 또한 관리할 예정이다.

class Product {
  contructor() {
    this.key = 3; // 유효한 2개의 아이템을 제외한 key의 크기
    this.items = null; // map 객체를 통한 데이터 조회 담당
    this.views = []; // 돌림판 결과물을 출력하는데 필요한 배열
  }
}

variables 설명

  • this.key : 초반 돌림판 형태를 유지하고 첫 예시 화면을 만들기 위해 key 1, 2 번을 제외하고 3번부터 등록이 되도록 설정
    • default : 3
  • this.items : map 객체를 통해 중복되지 않는 key를 관리하고 등록, 수정, 삭제를 담당
  • this.views : 돌림판에 그려질 데이터 배열을 관리하며 당첨 결과를 도출하는데 활용

아이템 관리 메서드

class Product {
  contructor() {
    this.key = 3;
    this.items = null;
    this.views = [];
  }
  
  setItem() {}
  
  init() {}
}

아이템 추가

const INIT_PRODUCTS = [
  ["1", { name: "쁘밍", size: 1, color: "#FFFFFF" }],
  ["2", { name: "룰렛", size: 1, color: "#FFFFFF" }],
];

class Product {
  contructor() {
    this.key = 3;
    this.items = null;
    this.views = [];
  }
  
  setItem(key = this.key, name = "", size = 1, color = "#FFFFFF") {
    let currentKey = key;
    
    this.items.set(String(key), { name, size, color });
    this.key += 1;
    
    return currentKey;
  }
  
  init() {
    this.items = new Map(INIT_PRODUCTS);
  }
}

우선 init 메서드로 items 변수에 map 객체를 담아주고, setItem와 같은 메서드로 아이템을 관리할 수 있도록 준비합니다.

 

setItem의 매개변수로는 key, name, size, color 값을 불러오게 됩니다.

그렇게 받게 된 매개변수 값들을 items에 추가하여 get, delete 등 .. 다양한 메서드에서 관리가 가능하도록 처리합니다.

color는 값을 추가할 때마다 랜덤으로 색상을 생성할 예정입니다. ( 추후 자바스크립트 파일 추가 예정 )

 

key가 있어야 더 수월한 관리가 가능하고, name과 size가 변경되는 경우에 치환이 가능하도록 하는 것이 가능해 집니다.

: 수정이 필요한 경우 key를 가지고 새롭게 값을 치환해 주면 됩니다.

get, delete, reset, restore 메서드 .. 아이템 관리를 도와주는 도우미 만들기

const INIT_PRODUCTS = [
  ["1", { name: "쁘밍", size: 1, color: "#FFFFFF" }],
  ["2", { name: "룰렛", size: 1, color: "#FFFFFF" }],
];

class Product {
  contructor() {
    this.key = 3;
    this.items = null;
    this.views = [];
  }
  
  get size() {
    return this.items.values().reduce((a, b) => a + b.size, 0);
  }
  
  setItem(key = this.key, name = "", size = 1, color = "#FFFFFF") {
    let currentKey = key;
    
    this.items.set(String(key), { name, size, color });
    this.key += 1;
    
    return currentKey;
  }
  
  getItem(key) {
    return this.items.get(key);
  }
  
  deleteItem(key) {
    this.items.delete(key);
  }
  
  reset() {
    this.views.splice(0);
  }
  
  restore() {
    this.reset();
    
    for (const key of this.items.keys()) {
      const data = this.getItem(key);
      const object = { key, name: data.name };
      this.views.push(...Array.from({ length: data.size }).fill(object));
    }
  }
  
  init() {
    this.items = new Map(INIT_PRODUCTS);
  }
}

아마 갑자기 너무 많은 메서드들이 추가된 것 같아서 당황하실 수도 있습니다.

하지만, 괜찮습니다. 실제 동작 내용은 간단하고 짧은 처리를 담당하는 것이기 때문에 천천히 이해하면서 진행해 주세요.

 

그러면 이번에 추가된 메서드들은 어떤 역할을 할까?

  • get size() : getter 메서드는 map 객체가 가지고 있는 각 size의 합을 구하는 메서드입니다.
  • getItem(key) : key를 매개변수로 받고 map 객체의 get 메서드를 활용하여 찾고자 하는 데이터를 꺼내옵니다.
  • deleteItem(key) : 동일하게 key를 매개변수로 받고 map 객체의 delete 메서드를 활용하여 데이터를 제거합니다.
  • reset() : Array 객체의 splice 메서드를 통해 배열을 초기 상태로 만듭니다.
  • restore() : 돌림판을 다시 형상화하기 위해 views 배열을 초기화하고, map 객체에 주어진 데이터를 꺼내 다시 구성합니다.

전역 변수로 할당하기

이제 Product 객체를 전역 변수에 정의하여 동작을 담당하는 자바스크립트 파일에서 처리할 수 있도록 합니다.

class Product {
  ... variables, ... methods
}

const product = new Product();

const initial = () => {
  product.init();
  product.restore();
  // spinner.init(); Spinner 객체를 정의한 후, 추가될 영역
};

document.addEventListener("DOMContentLoaded", initial);

전역 변수로 할당되는 이유는 Product 객체를 새로 정의하게 되면 완전히 다른 값들을 가지는 객체가 됩니다.

따라서, 자바스크립트가 실행되는 동시에 Product 객체를 정의하고 다른 위치에 자바스크립트 파일에서도 공통적으로

사용이 가능하도록 합니다.


돌림판 관리 ( Spinner 객체 )

Spinner 인스턴스 객체를 간단하게 표현해 보자.

class Spinner {
  contructor() {
    ... variables
  }
  
  draw() {} // 돌림판 형태 그리기
  
  rotate() {} // 돌림판 회전
  
  stop() {} // 돌림판 멈추기
  
  init() {}
}

돌림판을 그려내고 돌리고, 멈추는 기능들을 우선적으로 작성했다.

init 메서드는 Canvas 크기 변화나 데이터 변화에 따라 재조정을 하거나 돌림판 회전 크기를 조정하는데 사용할 예정이다.

draw 메서드

draw 메서드는 각 아이템 별 영역을 나누고 돌림판 형태를 그려내는 역할을 할 예정입니다.

또한, rotate를 진행하고 회전 크기에 따라 돌아가는 모습을 보여주기 위한 처리도 담당할 예정입니다.

const $canvas = document.getElementById("spinner");
const ctx = $canvas.getContext("2d");

class Spinner {
  contructor() {
    ... variables
  }
  
  ... other methods
  
  // 돌림판 형태 그리기
  draw(angle = 0) {
    const [cw, ch] = [$canvas.width / 2, $canvas.height / 2];
    const size = products.size; // Product 객체
    const radian = ((360 / size) * Math.PI) / 180; // 총 아이템 개수에 따른 평균 각도
  }
}

draw 메서드 # 2 : 아이템 영역 그리기

const $canvas = document.getElementById("spinner");
const ctx = $canvas.getContext("2d");

class Spinner {
  contructor() {
    ... variables
  }
  
  ... other methods
  
  // 돌림판 형태 그리기
  draw(angle = 0) {
    const [cw, ch] = [$canvas.width / 2, $canvas.height / 2];
    const size = products.size; // Product 객체
    const radian = ((360 / size) * Math.PI) / 180; // 총 아이템 개수에 따른 평균 각도
    
    for(const key of products.items.keys()) {
      const product = products.getItem(key); // (1)
      const arc = radian * product.size; // (2) 총 아이템 개수와 평균 각도를 곱하여 원의 크기를 정함
      
      // 각 아이템 크기 별로 arc 사이즈를 할당 (3)
      ctx.beginPath();
      ctx.fillStyle = product.color;
      ctx.moveTo(cw, ch);
      ctx.arc(cw, ch, cw - 3, angle, angle + arc);
      ctx.fill();
      ctx.closePath();
      
      // 테두리 그리기 (4)
      ctx.lineWidth = 0.5;
      ctx.strokeStyle = "#000000";
      ctx.stroke();
      
      angle += arc; // (5)
    }
  }
}

아이템 사이즈만큼 영역 그려내기 ( 코드에 첨부된 번호를 참고해 주세요. )

  • (1) : map 객체의 keys() 메서드를 통해 iterator 데이터를 가져와 각 key 마다 아이템 데이터를 받아옵니다.
  • (2) : 총 product 사이즈를 가져와 평균 radian 값을 받아온 아이템 사이즈만큼 곱해 원의 크기를 구합니다.
  • (3) : 평균 radian 값에 product 크기를 곱해 구한 원의 크기로 영역을 그립니다.
    • (3 - 1) : 채울 색상을 product 에서 꺼내고 fillStyle에 담습니다.
    • (3 - 2) : moveTo(x, y); 그리기 시작할 기준점을 정의합니다. ( 펜의 위치 )
    • (3 - 3) : canvas API의 arc 메서드를 통해 먼저 중심점을 지정하고 원의 반지름, 그리고 그려질 크기를 지정합니다.
      * arc(x 좌표, y 좌표, 반지름, 시작 각도, 끝 각도)
    • (3 - 4) : 마무리로 fill() 메서드를 통해 canvas에 도형을 그려낸다.
  • (4) : lineWidth의 값을 변경하고 strokeStyle로 색상을 정합니다. 이후 stroke() 메서드로 선을 그리도록 합니다.
  • (5) : (3 - 3) 에서 다룬 arc 메서드에서 시작 각도와 끝 각도에 대한 정보가 필요합니다.
            이 값을 현재 원의 크기만큼 더하고 다음 차례에서 시작 각도로 지정되도록 합니다.
            초기 : 0 + 50
            다음 : 50 + 100 ( 50도부터 100도의 원을 그리기 )

마무리

이번 글은 간단하게 각 아이템 생성, 수정, 삭제, 재배치 및 아이템 영역 그리기를 설명했습니다.

내용도 많고, 글도 길다보니 복잡하게 보일 수도 있지만 .. 어느정도 canvas API의 원리들을 이해하면 조금 더 쉬워집니다.

 

현재까지 각 아이템 별 영역만을 그렸고, 다음 글에서는 아이템 이름을 배치하는 방법을 설명할 것입니다.

지금 돌림판은 색만 있는 영역만이 나타납니다.

 

긴 글 읽어주셔서 감사합니다.

'개발 일지 > 게임 룰렛 ( 돌림판 )' 카테고리의 다른 글

돌림판을 만들게 된 이야기 #1  (2) 2025.04.25

    티스토리툴바