🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

코인과 아이템 시스템 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 5. · 14 Views

코인과 아이템 시스템 완벽 가이드

게임 개발에서 필수적인 코인 수집과 아이템 시스템을 Phaser를 활용하여 구현하는 방법을 다룹니다. 스프라이트 배치부터 점수 시스템, 파워업 효과까지 실무에서 바로 적용할 수 있는 내용을 담았습니다.


목차

  1. 코인_스프라이트_배치
  2. 코인_수집_처리
  3. 점수_시스템
  4. 파워업_아이템
  5. 아이템_효과_구현
  6. 수집_이펙트와_사운드

1. 코인 스프라이트 배치

김개발 씨는 첫 번째 플랫포머 게임을 만들고 있었습니다. 캐릭터도 움직이고, 점프도 되는데 뭔가 허전합니다.

"게임에 수집할 코인이 있어야 재미있을 텐데..." 막상 코인을 화면에 배치하려니 어디서부터 시작해야 할지 막막했습니다.

코인 스프라이트 배치는 게임 월드에 수집 가능한 오브젝트를 생성하고 위치시키는 작업입니다. 마치 보드게임판 위에 금화 토큰을 놓는 것과 같습니다.

Phaser에서는 **그룹(Group)**을 사용하여 여러 코인을 효율적으로 관리하고, 물리 엔진을 적용하여 충돌 감지까지 처리할 수 있습니다.

다음 코드를 살펴봅시다.

// 코인 그룹 생성 - 물리 엔진이 적용된 정적 그룹
this.coins = this.physics.add.staticGroup();

// 반복문으로 코인 배치 - x좌표 100부터 50픽셀 간격으로
for (let x = 100; x <= 700; x += 50) {
    // 각 코인을 그룹에 추가하고 위치 지정
    this.coins.create(x, 300, 'coin');
}

// 코인에 애니메이션 적용 - 반짝이는 효과
this.anims.create({
    key: 'spin',
    frames: this.anims.generateFrameNumbers('coin', { start: 0, end: 5 }),
    frameRate: 10,
    repeat: -1  // 무한 반복
});

// 모든 코인에 애니메이션 재생
this.coins.children.iterate((coin) => coin.anims.play('spin'));

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 회사에서 간단한 웹 게임 프로젝트를 맡게 되었는데, 이전까지는 웹 페이지만 만들어봤지 게임은 처음이었습니다.

튜토리얼을 따라 캐릭터를 움직이게 만들었지만, 이제 코인을 배치해야 할 차례였습니다. 선배 개발자 박시니어 씨가 다가와 물었습니다.

"코인 하나씩 일일이 만들 생각이야?" 김개발 씨가 고개를 끄덕이자 박시니어 씨가 웃으며 말했습니다. "그러면 코인이 100개가 되면 어떡하려고?" 그렇다면 스프라이트 그룹이란 정확히 무엇일까요?

쉽게 비유하자면, 스프라이트 그룹은 마치 바둑알 통과 같습니다. 바둑알을 하나씩 따로 관리하면 번거롭지만, 통에 담아두면 한꺼번에 꺼내고, 정리하고, 세는 것이 쉬워집니다.

스프라이트 그룹도 마찬가지로 같은 종류의 게임 오브젝트를 묶어서 효율적으로 관리합니다. 스프라이트 그룹이 없던 시절에는 어땠을까요?

개발자들은 코인 하나하나를 별도의 변수로 관리해야 했습니다. coin1, coin2, coin3...

이런 식으로 말입니다. 코드가 길어지고, 실수하기도 쉬웠습니다.

더 큰 문제는 충돌 감지였습니다. 플레이어가 코인과 부딪혔는지 확인하려면 모든 코인에 대해 일일이 검사해야 했습니다.

바로 이런 문제를 해결하기 위해 staticGroup이 등장했습니다. staticGroup을 사용하면 코인처럼 움직이지 않는 오브젝트를 한데 묶어 관리할 수 있습니다.

또한 물리 엔진이 자동으로 적용되어 충돌 감지도 간단해집니다. 무엇보다 반복문 하나로 수십, 수백 개의 코인을 손쉽게 배치할 수 있다는 큰 장점이 있습니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 **physics.add.staticGroup()**을 보면 물리 엔진이 적용된 정적 그룹을 생성합니다.

정적이라는 것은 코인 자체가 움직이지 않는다는 의미입니다. 중력의 영향도 받지 않습니다.

다음으로 for 반복문에서는 x좌표를 100부터 700까지 50픽셀 간격으로 늘려가며 코인을 배치합니다. create() 메서드가 실제로 코인을 생성하고 그룹에 추가하는 역할을 합니다.

그 아래에서는 애니메이션을 정의합니다. **anims.create()**로 spin이라는 이름의 애니메이션을 만들고, 0번부터 5번 프레임까지 초당 10프레임 속도로 재생하도록 설정합니다.

repeat: -1은 무한 반복을 의미합니다. 마지막으로 **children.iterate()**를 사용하여 그룹 내 모든 코인에 애니메이션을 적용합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 레벨 에디터에서 코인 위치 데이터를 JSON으로 저장해두고, 게임 시작 시 해당 데이터를 불러와 코인을 배치하는 방식을 많이 사용합니다.

이렇게 하면 기획자가 직접 레벨을 디자인할 수 있고, 개발자는 코드를 수정하지 않아도 됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 staticGroup 대신 일반 group을 사용하는 것입니다. 일반 그룹은 물리 엔진이 적용되지 않아 충돌 감지가 작동하지 않습니다.

또한 너무 많은 코인을 한꺼번에 생성하면 성능 문제가 발생할 수 있으므로, 화면에 보이는 영역만 활성화하는 최적화가 필요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 그룹으로 묶어서 관리하는 거군요!" 스프라이트 그룹을 제대로 이해하면 수십 개의 코인도 몇 줄의 코드로 깔끔하게 관리할 수 있습니다.

여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.

실전 팁

💡 - staticGroup은 움직이지 않는 오브젝트에, group은 움직이는 오브젝트에 사용합니다

  • 코인 위치는 하드코딩보다 JSON 데이터로 관리하면 유지보수가 쉬워집니다
  • 애니메이션 프레임은 스프라이트시트로 미리 준비해야 합니다

2. 코인 수집 처리

김개발 씨는 화면에 반짝이는 코인들을 배치하는 데 성공했습니다. 그런데 캐릭터가 코인 위를 지나가도 아무 일도 일어나지 않습니다.

"코인이 사라지면서 수집되어야 하는데..." 충돌 감지와 수집 처리, 어떻게 해야 할까요?

코인 수집 처리는 플레이어가 코인과 접촉했을 때 이를 감지하고 적절한 동작을 실행하는 과정입니다. 마치 자판기에 동전을 넣으면 센서가 이를 감지하고 음료를 내보내는 것과 같습니다.

Phaser에서는 overlap 함수로 두 오브젝트의 겹침을 감지하고, 콜백 함수에서 수집 로직을 처리합니다.

다음 코드를 살펴봅시다.

// 플레이어와 코인 그룹 간의 겹침 감지 설정
this.physics.add.overlap(
    this.player,           // 첫 번째 오브젝트: 플레이어
    this.coins,            // 두 번째 오브젝트: 코인 그룹
    this.collectCoin,      // 겹칠 때 호출될 콜백 함수
    null,                  // 처리 콜백 (필터링용, 보통 null)
    this                   // 콜백 함수의 컨텍스트
);

// 코인 수집 콜백 함수
collectCoin(player, coin) {
    // 코인을 비활성화하고 화면에서 숨김
    coin.disableBody(true, true);

    // 점수 증가 (다음 섹션에서 자세히 다룸)
    this.score += 10;

    // 남은 코인 수 확인
    if (this.coins.countActive(true) === 0) {
        console.log('모든 코인을 수집했습니다!');
    }
}

김개발 씨는 코인 배치를 끝내고 뿌듯한 마음으로 게임을 실행했습니다. 캐릭터를 조작해 코인 위로 이동시켰는데, 캐릭터가 코인을 그냥 통과해버립니다.

마치 유령처럼요. "아, 충돌 처리를 안 했구나!" 박시니어 씨가 모니터를 힐끗 보더니 힌트를 줍니다.

"Phaser에서는 충돌을 두 가지 방식으로 처리해. collideroverlap.

코인은 어떤 걸 써야 할까?" 그렇다면 overlapcollider의 차이는 무엇일까요? 쉽게 비유하자면, collider는 벽돌과 부딪히는 것이고 overlap은 자동문 센서를 지나는 것입니다.

collider를 사용하면 두 오브젝트가 서로 밀어내며 물리적으로 충돌합니다. 반면 overlap은 겹침만 감지할 뿐, 물리적인 반발력이 없습니다.

코인은 플레이어를 밀어낼 필요가 없으니 overlap이 적합합니다. overlap을 사용하지 않고 직접 충돌을 검사하던 시절에는 어땠을까요?

매 프레임마다 플레이어와 모든 코인의 좌표를 비교해야 했습니다. 두 사각형이 겹치는지 계산하는 수학 공식을 직접 구현해야 했고, 성능 최적화도 개발자 몫이었습니다.

코드는 복잡해지고, 버그가 생기기 쉬웠습니다. 바로 이런 수고를 덜어주는 것이 Phaser의 물리 엔진입니다.

physics.add.overlap을 한 번 호출하면 엔진이 알아서 매 프레임 충돌을 검사합니다. 또한 그룹 단위로 검사하기 때문에 코인이 아무리 많아도 한 줄로 처리됩니다.

무엇보다 콜백 함수로 수집 로직을 깔끔하게 분리할 수 있다는 장점이 있습니다. 위의 코드를 자세히 살펴보겠습니다.

**physics.add.overlap()**의 첫 번째와 두 번째 인자는 충돌을 감지할 두 대상입니다. 여기서는 플레이어 스프라이트와 코인 그룹이죠.

세 번째 인자는 겹침이 감지되었을 때 호출될 콜백 함수입니다. 네 번째 인자인 processCallback은 충돌 전에 필터링할 때 사용하는데, 보통은 null을 넣습니다.

다섯 번째 인자는 콜백 함수 내에서 this가 가리킬 컨텍스트입니다. collectCoin 함수 내부를 보면, **disableBody(true, true)**가 핵심입니다.

첫 번째 true는 물리 바디를 비활성화하고, 두 번째 true는 화면에서 숨깁니다. 이렇게 하면 코인이 사라지면서도 메모리에서 완전히 삭제되지는 않아 나중에 재사용할 수 있습니다.

**countActive(true)**는 현재 활성화된 코인 수를 반환합니다. 이 값이 0이 되면 모든 코인을 수집한 것이므로, 스테이지 클리어 같은 이벤트를 발생시킬 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 대부분의 게임에서는 코인 수집 시 즉시 점수만 올리는 것이 아니라, 이펙트 재생, 사운드 출력, UI 업데이트 등 여러 작업을 동시에 수행합니다.

이럴 때는 이벤트 시스템을 활용하여 코인 수집 이벤트를 발행하고, 각 시스템이 이를 구독하는 패턴을 사용합니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수는 콜백 함수에서 this 컨텍스트를 놓치는 것입니다. overlap의 다섯 번째 인자에 this를 전달하지 않으면, collectCoin 함수 내에서 this.score에 접근할 때 undefined 에러가 발생합니다.

또한 disableBody 대신 destroy()를 사용하면 오브젝트 풀링의 이점을 잃게 됩니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

코드를 수정하고 게임을 실행하니 드디어 캐릭터가 코인을 지나갈 때 코인이 사라집니다. 김개발 씨의 얼굴에 미소가 번집니다.

"이제 게임 같아 보이기 시작했어!" overlap을 제대로 이해하면 코인뿐만 아니라 열쇠, 보석, 하트 등 다양한 수집 아이템을 손쉽게 구현할 수 있습니다. 여러분도 직접 해보세요.

실전 팁

💡 - overlap은 통과 가능한 아이템에, collider는 벽처럼 막아야 하는 오브젝트에 사용합니다

  • disableBody를 사용하면 오브젝트 풀링으로 성능을 최적화할 수 있습니다
  • 콜백 함수의 this 컨텍스트를 반드시 전달해야 합니다

3. 점수 시스템

코인이 수집되기 시작하니 김개발 씨는 한 가지 고민에 빠졌습니다. "점수가 올라가는 건 알겠는데, 플레이어한테 어떻게 보여주지?" 콘솔에만 찍히는 점수로는 게임의 재미를 느낄 수 없습니다.

화면에 점수를 표시하고, 실시간으로 업데이트하는 방법을 알아봅시다.

점수 시스템은 플레이어의 성과를 숫자로 표현하고 화면에 표시하는 기능입니다. 마치 볼링장의 전광판이 매 프레임마다 점수를 갱신하는 것과 같습니다.

Phaser에서는 Text 객체를 사용하여 점수를 표시하고, 코인 수집 시 해당 텍스트를 업데이트합니다.

다음 코드를 살펴봅시다.

// create() 함수에서 점수 초기화 및 텍스트 생성
this.score = 0;
this.scoreText = this.add.text(16, 16, 'Score: 0', {
    fontFamily: 'Arial',
    fontSize: '32px',
    color: '#ffffff',
    stroke: '#000000',      // 외곽선 색상
    strokeThickness: 4      // 외곽선 두께
});

// 텍스트를 카메라에 고정 (스크롤해도 따라옴)
this.scoreText.setScrollFactor(0);

// 코인 수집 시 점수 업데이트
collectCoin(player, coin) {
    coin.disableBody(true, true);
    this.score += 10;
    // 텍스트 내용 업데이트
    this.scoreText.setText('Score: ' + this.score);
}

김개발 씨는 코인 수집이 잘 되는 것을 확인하고 기뻐했지만, 곧 허전함을 느꼈습니다. 게임을 하면서 점수가 얼마인지 알 수가 없었거든요.

브라우저 개발자 도구를 열어 콘솔을 확인해야만 점수를 볼 수 있었습니다. 박시니어 씨가 지나가다 한마디 합니다.

"게임에서 피드백은 정말 중요해. 플레이어가 코인을 먹었는데 아무 반응이 없으면 재미가 반감되거든." 그렇다면 점수 표시를 어떻게 구현해야 할까요?

쉽게 비유하자면, 점수 텍스트는 게임 화면 위에 붙인 포스트잇과 같습니다. 게임 월드가 아무리 넓어도, 카메라가 어디를 비추든 포스트잇은 항상 화면의 같은 위치에 보입니다.

이것을 UI 레이어 또는 **HUD(Head-Up Display)**라고 부릅니다. 점수 시스템이 없는 게임은 어떨까요?

플레이어는 자신이 얼마나 잘하고 있는지 알 수 없습니다. 성취감을 느끼기 어렵고, 게임을 계속할 동기가 사라집니다.

점수는 단순한 숫자가 아니라 플레이어에게 보상감을 주는 핵심 요소입니다. 많은 게임 디자이너들이 점수 시스템을 즉각적인 피드백의 대표적인 예로 꼽습니다.

Phaser에서 텍스트를 화면에 표시하는 것은 간단합니다. **this.add.text()**로 텍스트 객체를 생성합니다.

첫 번째와 두 번째 인자는 x, y 좌표입니다. 세 번째 인자는 표시할 문자열이고, 네 번째 인자는 스타일 설정입니다.

폰트, 크기, 색상 외에도 stroke로 외곽선을 추가하면 어떤 배경에서도 글자가 잘 보입니다. 여기서 핵심은 **setScrollFactor(0)**입니다.

플랫포머 게임에서는 캐릭터를 따라 카메라가 이동합니다. 일반 게임 오브젝트는 카메라와 함께 움직이지만, setScrollFactor(0)을 설정하면 카메라가 어디를 비추든 텍스트는 화면의 고정된 위치에 머물게 됩니다.

마치 TV 화면 모서리에 항상 표시되는 방송국 로고처럼요. 코인을 수집할 때 점수를 업데이트하는 것도 간단합니다.

setText() 메서드로 텍스트 내용을 바꿔주면 됩니다. 문자열 연결 연산자(+)를 사용하여 Score: 라는 레이블과 실제 점수 값을 합칩니다.

이 한 줄이면 점수가 실시간으로 화면에 반영됩니다. 실제 현업에서는 좀 더 화려하게 구현합니다.

점수가 올라갈 때 숫자가 통통 튀는 애니메이션을 넣거나, 잠시 크기가 커졌다 작아지는 효과를 주기도 합니다. 또한 점수가 특정 값을 넘으면 색상이 바뀌거나 이펙트가 터지는 연출도 흔합니다.

이런 **주스(Juice)**라 불리는 요소들이 게임의 재미를 크게 높여줍니다. 하지만 주의할 점도 있습니다.

텍스트 업데이트는 생각보다 비용이 큰 작업입니다. 매 프레임마다 setText를 호출하면 성능 문제가 생길 수 있습니다.

따라서 점수가 실제로 변할 때만 업데이트하는 것이 좋습니다. 또한 긴 텍스트나 복잡한 스타일은 비트맵 폰트를 사용하는 것이 성능에 유리합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 점수 텍스트를 추가하고 게임을 실행하니, 코인을 먹을 때마다 왼쪽 상단의 점수가 올라갑니다.

"오, 이제 진짜 게임 같다!" 김개발 씨는 계속해서 코인을 모으며 점수가 오르는 것을 즐깁니다. 점수 시스템을 제대로 구현하면 플레이어에게 즉각적인 피드백을 줄 수 있습니다.

단순한 숫자 표시를 넘어 다양한 연출을 추가해보세요.

실전 팁

💡 - setScrollFactor(0)으로 UI 요소를 화면에 고정할 수 있습니다

  • 외곽선(stroke)을 추가하면 다양한 배경에서 텍스트 가독성이 높아집니다
  • 점수 변경 시에만 setText를 호출하여 성능을 최적화하세요

4. 파워업 아이템

점수 시스템까지 구현한 김개발 씨에게 기획자가 새로운 요청을 합니다. "단순히 코인만 먹는 건 심심해요.

플레이어를 강하게 만드는 파워업 아이템도 넣어주세요!" 무적 상태, 속도 증가, 점프력 향상... 어떻게 구현해야 할까요?

파워업 아이템은 플레이어의 능력을 일시적으로 또는 영구적으로 변화시키는 수집품입니다. 마치 마라톤에서 에너지 젤을 먹으면 힘이 솟는 것과 같습니다.

코인과 비슷하게 그룹으로 관리하되, 종류에 따라 다른 효과를 적용하는 것이 핵심입니다.

다음 코드를 살펴봅시다.

// 파워업 아이템 그룹 생성
this.powerUps = this.physics.add.group();

// 다양한 파워업 생성 함수
createPowerUp(x, y, type) {
    const powerUp = this.powerUps.create(x, y, type);
    powerUp.setData('type', type);  // 아이템 종류 저장
    powerUp.setBounceY(0.3);        // 약간의 통통 튀는 효과
    return powerUp;
}

// 파워업 배치 예시
this.createPowerUp(400, 200, 'star');      // 무적 아이템
this.createPowerUp(600, 200, 'boots');     // 속도 증가
this.createPowerUp(800, 200, 'spring');    // 점프력 증가

// 파워업 수집 감지 설정
this.physics.add.overlap(
    this.player,
    this.powerUps,
    this.collectPowerUp,
    null,
    this
);

김개발 씨는 기획서를 받아들고 고민에 빠졌습니다. 코인은 그냥 점수만 올리면 되는데, 파워업은 종류마다 다른 효과를 줘야 합니다.

"이걸 어떻게 구분하지?" 박시니어 씨가 커피를 건네며 말합니다. "Phaser의 setData 기능을 써봐.

각 스프라이트에 커스텀 데이터를 붙일 수 있어." 그렇다면 setData란 무엇일까요? 쉽게 비유하자면, setData는 물건에 이름표를 붙이는 것과 같습니다.

똑같이 생긴 상자가 여러 개 있어도, 이름표를 보면 안에 뭐가 들었는지 알 수 있죠. 파워업 스프라이트에 type이라는 이름표를 붙여서 star인지, boots인지, spring인지 구분합니다.

파워업을 코인과 같은 그룹에 넣으면 안 될까요? 물론 넣을 수는 있지만, 관리가 복잡해집니다.

수집 콜백에서 매번 "이게 코인이야 파워업이야?" 확인해야 합니다. 코인은 정적 그룹(staticGroup)으로 충분하지만, 파워업은 통통 튀거나 움직이는 경우가 많아 동적 그룹(group)이 적합합니다.

분리해서 관리하는 것이 코드 가독성과 유지보수에 좋습니다. 위의 코드를 살펴보겠습니다.

**physics.add.group()**은 staticGroup과 달리 중력과 물리 법칙이 적용되는 그룹입니다. 파워업이 하늘에서 떨어지거나 통통 튀는 효과를 낼 수 있습니다.

createPowerUp 함수에서는 스프라이트를 생성하고 setData로 타입 정보를 저장합니다. **setBounceY(0.3)**은 바닥에 닿았을 때 30% 높이로 튀어오르게 합니다.

파워업 배치 예시를 보면 세 종류의 아이템을 각각 다른 위치에 생성합니다. star는 무적 아이템, boots는 속도 증가, spring은 점프력 증가입니다.

물론 이 이름들은 미리 로드한 이미지 키와 일치해야 합니다. 마지막으로 overlap 설정은 코인과 동일한 패턴입니다.

플레이어와 파워업 그룹 간의 겹침을 감지하고, collectPowerUp 콜백을 호출합니다. 실제 효과 적용은 다음 섹션에서 자세히 다룹니다.

실제 현업에서는 파워업 시스템을 더 정교하게 설계합니다. 아이템 데이터를 JSON 파일로 분리하여 기획자가 직접 수치를 조정할 수 있게 하거나, 희귀도에 따라 드롭 확률을 다르게 설정하기도 합니다.

또한 파워업끼리 조합하면 특별한 효과가 나오는 시너지 시스템도 인기 있는 패턴입니다. 하지만 주의할 점도 있습니다.

파워업 종류가 많아지면 콜백 함수의 조건문도 길어집니다. if-else가 10개가 넘어가면 유지보수가 힘들어지죠.

이럴 때는 **전략 패턴(Strategy Pattern)**을 적용하여 각 파워업 타입별 효과를 별도의 객체나 함수로 분리하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

파워업 그룹을 만들고 세 종류의 아이템을 배치했습니다. 게임을 실행하니 코인과 다르게 파워업들이 바닥에서 통통 튀고 있습니다.

"오, 확실히 특별해 보이네!" 파워업 아이템 시스템을 잘 설계하면 게임에 다양성과 전략적 요소를 더할 수 있습니다. 기본 구조를 익힌 후에는 여러분만의 창의적인 아이템을 만들어보세요.

실전 팁

💡 - setData로 스프라이트에 커스텀 속성을 저장할 수 있습니다

  • 코인과 파워업은 별도 그룹으로 분리하여 관리하세요
  • 파워업 종류가 많아지면 전략 패턴으로 리팩토링을 고려하세요

5. 아이템 효과 구현

파워업을 수집하는 것까지는 성공했습니다. 하지만 김개발 씨의 캐릭터는 별을 먹어도, 부츠를 먹어도 아무 변화가 없습니다.

"효과를 어떻게 적용하지? 그리고 시간이 지나면 원래대로 돌아와야 하는데..." 이제 진짜 게임 로직을 구현할 시간입니다.

아이템 효과 구현은 파워업 수집 시 플레이어의 속성을 변경하고, 일정 시간 후 원래대로 복구하는 과정입니다. 마치 에너지 드링크를 마시면 일시적으로 힘이 나지만, 시간이 지나면 효과가 사라지는 것과 같습니다.

**타이머(Timer)**를 활용하여 지속 시간을 관리하는 것이 핵심입니다.

다음 코드를 살펴봅시다.

// 파워업 수집 콜백
collectPowerUp(player, powerUp) {
    const type = powerUp.getData('type');
    powerUp.disableBody(true, true);

    switch(type) {
        case 'star':
            this.activateInvincibility();
            break;
        case 'boots':
            this.activateSpeedBoost();
            break;
        case 'spring':
            this.activateJumpBoost();
            break;
    }
}

// 무적 효과 활성화
activateInvincibility() {
    this.isInvincible = true;
    this.player.setTint(0xffff00);  // 노란색 틴트

    // 5초 후 효과 해제
    this.time.delayedCall(5000, () => {
        this.isInvincible = false;
        this.player.clearTint();
    });
}

김개발 씨는 collectPowerUp 함수를 작성하며 난감해했습니다. "타입에 따라 다른 효과를 줘야 하는데, 이걸 전부 if문으로 처리하면 너무 지저분해지지 않을까?" 박시니어 씨가 코드를 보더니 조언합니다.

"세 가지 정도면 switch문이 깔끔해. 그리고 각 효과는 별도의 함수로 빼는 게 좋아.

나중에 효과를 수정하거나 추가할 때 훨씬 편하거든." 그렇다면 효과를 어떻게 적용할까요? 쉽게 비유하자면, 파워업 효과는 버프 마법을 거는 것과 같습니다.

마법사가 주문을 외우면 대상에게 효과가 적용되고, 일정 시간이 지나면 마법이 풀립니다. 코드에서도 마찬가지로 플레이어 속성을 변경하고, 타이머로 복구 시점을 예약합니다.

지속 시간 관리 없이 효과만 적용하면 어떻게 될까요? 한 번 속도가 빨라지면 영원히 빠른 상태가 됩니다.

게임 밸런스가 무너지고, 플레이어는 금방 지루해집니다. 따라서 대부분의 파워업은 일시적인 효과를 가지며, 이를 관리하는 타이머 시스템이 필수입니다.

위의 코드를 자세히 살펴보겠습니다. collectPowerUp에서는 먼저 **getData('type')**으로 아이템 종류를 확인합니다.

그리고 switch문으로 분기하여 각 타입에 맞는 활성화 함수를 호출합니다. 코인 수집과 마찬가지로 disableBody로 아이템을 비활성화하는 것도 잊지 않습니다.

activateInvincibility 함수가 핵심입니다. 먼저 this.isInvincible = true로 무적 상태 플래그를 설정합니다.

이 플래그는 적과 충돌할 때 체크하여 데미지를 무시할지 결정합니다. **setTint(0xffff00)**는 스프라이트에 노란색 색조를 입혀서 플레이어에게 무적 상태임을 시각적으로 알려줍니다.

**time.delayedCall()**이 타이머의 역할을 합니다. 첫 번째 인자는 대기 시간(밀리초)이고, 두 번째 인자는 시간이 지난 후 실행할 콜백 함수입니다.

5000ms, 즉 5초 후에 무적 플래그를 false로 되돌리고 틴트를 제거합니다. 속도 증가와 점프력 증가도 비슷한 패턴으로 구현합니다.

속도 증가는 player.setVelocityX의 값을 조절하는 속도 배율 변수를 만들어 관리합니다. 점프력 증가는 점프 시 적용되는 setVelocityY 값을 높이면 됩니다.

둘 다 delayedCall로 원래 값으로 복구하는 타이머를 설정합니다. 실제 현업에서는 더 복잡한 상황을 고려합니다.

같은 파워업을 연속으로 먹으면 어떻게 될까요? 지속 시간이 리셋되어야 할까요, 아니면 중첩되어야 할까요?

서로 다른 파워업을 동시에 적용할 수 있을까요? 이런 규칙들을 명확하게 정의하고 구현해야 합니다.

보통은 버프 관리자(Buff Manager) 클래스를 별도로 만들어 이런 로직을 캡슐화합니다. 하지만 주의할 점도 있습니다.

delayedCall의 콜백 함수에서 화살표 함수를 사용한 것에 주목하세요. 일반 함수를 사용하면 this가 달라져서 에러가 발생합니다.

또한 씬이 전환되거나 게임이 일시정지될 때 타이머 처리도 고려해야 합니다. Phaser의 타이머는 기본적으로 씬의 생명주기를 따르므로, 씬이 종료되면 자동으로 정리됩니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 효과 로직을 구현하고 게임을 실행합니다.

별을 먹으니 캐릭터가 노랗게 빛나고, 5초 후 원래대로 돌아옵니다. "완벽해!" 김개발 씨는 다른 파워업들도 테스트하며 기뻐합니다.

아이템 효과 시스템을 이해하면 다양한 버프와 디버프를 자유자재로 구현할 수 있습니다. 독창적인 파워업을 만들어 게임에 재미를 더해보세요.

실전 팁

💡 - 타이머 콜백에서는 반드시 화살표 함수를 사용하여 this 컨텍스트를 유지하세요

  • 파워업 중첩 규칙을 명확히 정의하고 구현하세요
  • setTint로 시각적 피드백을 주면 플레이어 경험이 향상됩니다

6. 수집 이펙트와 사운드

게임의 기능은 완성되었지만, 뭔가 2% 부족합니다. 코인을 먹어도 그냥 사라지고, 파워업을 먹어도 조용합니다.

"AAA 게임들은 아이템을 먹을 때 화려한 이펙트랑 시원한 효과음이 나던데..." 게임에 생명을 불어넣는 마지막 단계, 이펙트와 사운드를 추가해봅시다.

수집 이펙트와 사운드는 아이템 획득 시 시각적, 청각적 피드백을 제공하는 요소입니다. 마치 슬롯머신에서 잭팟이 터지면 화려한 불빛과 경쾌한 소리가 나는 것과 같습니다.

파티클 시스템오디오 재생을 조합하여 수집의 쾌감을 극대화합니다.

다음 코드를 살펴봅시다.

// preload에서 리소스 로드
this.load.image('particle', 'assets/particle.png');
this.load.audio('coinSound', 'assets/coin.wav');
this.load.audio('powerUpSound', 'assets/powerup.wav');

// create에서 파티클 이미터 설정
this.coinParticles = this.add.particles(0, 0, 'particle', {
    speed: 100,
    scale: { start: 0.5, end: 0 },
    blendMode: 'ADD',
    lifespan: 500,
    emitting: false  // 수동으로 방출
});

// 코인 수집 시 이펙트와 사운드
collectCoin(player, coin) {
    // 파티클 방출 위치 설정 후 폭발
    this.coinParticles.emitParticleAt(coin.x, coin.y, 10);
    // 사운드 재생
    this.sound.play('coinSound', { volume: 0.5 });

    coin.disableBody(true, true);
    this.score += 10;
    this.scoreText.setText('Score: ' + this.score);
}

김개발 씨가 만든 게임을 옆자리 동료가 플레이해봤습니다. "음, 기능은 다 되는데...

좀 밋밋하지 않아? 이펙트 같은 거 없어?" 김개발 씨는 고개를 끄덕였습니다.

분명 뭔가 빠진 느낌이었습니다. 박시니어 씨가 지나가다 한마디 합니다.

"게임 업계에서는 이런 걸 **주스(Juice)**라고 불러. 게임의 핵심 기능은 아니지만, 있고 없고의 차이가 엄청나거든." 그렇다면 주스란 정확히 무엇일까요?

쉽게 비유하자면, 주스는 요리의 플레이팅과 같습니다. 같은 음식이라도 예쁜 접시에 담아 소스를 뿌리고 허브를 올리면 훨씬 맛있어 보이죠.

게임도 마찬가지입니다. 파티클, 사운드, 화면 흔들림 같은 요소들이 플레이어의 경험을 극적으로 향상시킵니다.

이펙트와 사운드가 없으면 어떨까요? 아무리 잘 만든 게임이라도 피드백이 없으면 플레이어는 행동의 결과를 느끼지 못합니다.

코인을 먹었는데 그냥 사라지면 "내가 먹은 거 맞아?" 싶습니다. 반면 반짝이는 파티클과 경쾌한 효과음이 터지면 "아, 먹었다!" 하는 확실한 느낌이 듭니다.

이 작은 차이가 게임의 몰입도를 결정합니다. 위의 코드를 살펴보겠습니다.

먼저 preload에서 파티클 이미지와 효과음을 로드합니다. 파티클은 보통 작은 원이나 별 모양의 이미지를 사용합니다.

효과음은 짧고 경쾌한 wav나 ogg 파일이 적합합니다. **this.add.particles()**로 파티클 이미터를 생성합니다.

speed는 파티클이 퍼져나가는 속도, scale은 크기 변화(시작 0.5에서 끝 0으로 점점 작아짐), blendMode: 'ADD'는 겹치는 부분이 더 밝아지는 효과, lifespan은 파티클이 존재하는 시간입니다. emitting: false로 설정하면 자동으로 방출하지 않고 우리가 원할 때만 방출합니다.

collectCoin 함수에서 **emitParticleAt()**을 호출합니다. 첫 번째와 두 번째 인자는 방출 위치(코인의 좌표), 세 번째 인자는 방출할 파티클 개수입니다.

10개의 파티클이 코인 위치에서 사방으로 퍼져나가며 사라집니다. **this.sound.play()**는 매우 직관적입니다.

첫 번째 인자는 preload에서 지정한 키, 두 번째 인자는 옵션 객체입니다. volume을 0.5로 설정하면 50% 볼륨으로 재생됩니다.

실제 현업에서는 이펙트를 더 정교하게 다듬습니다. 파티클 색상을 코인 색과 맞추거나, 점수에 따라 이펙트 크기를 다르게 하기도 합니다.

사운드도 여러 버전을 준비해서 랜덤하게 재생하면 반복감이 줄어듭니다. 또한 화면 흔들림(camera shake), 슬로우 모션 같은 연출도 자주 사용됩니다.

하지만 주의할 점도 있습니다. 이펙트가 너무 화려하면 오히려 게임 플레이를 방해할 수 있습니다.

특히 파티클을 과도하게 사용하면 성능 문제가 생깁니다. 또한 모바일에서는 사운드 재생에 제약이 있어서 터치 이벤트 후에만 오디오가 재생되도록 처리해야 합니다.

적절한 균형이 핵심입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

이펙트와 사운드를 추가한 후 게임을 실행합니다. 코인을 먹을 때마다 반짝이는 파티클이 터지고 "띵!" 하는 소리가 납니다.

동료가 다시 플레이해보더니 엄지를 치켜세웁니다. "오, 이제 진짜 게임 같네!" 수집 이펙트와 사운드는 게임의 완성도를 결정하는 마지막 퍼즐 조각입니다.

작은 디테일이 큰 차이를 만든다는 것을 기억하세요.

실전 팁

💡 - 파티클은 적당히 사용하고, 게임 플레이를 방해하지 않도록 주의하세요

  • 효과음은 여러 버전을 준비하여 랜덤 재생하면 자연스럽습니다
  • 모바일 환경에서는 오디오 정책을 고려하여 구현하세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Phaser#GameDev#Sprite#CollisionDetection#ItemSystem#Game

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.