🤖

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

⚠️

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

이미지 로딩 중...

Phaser 텍스트와 UI 요소 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 4. · 18 Views

Phaser 텍스트와 UI 요소 완벽 가이드

Phaser 게임 엔진에서 텍스트를 표시하고 스타일링하는 방법부터 버튼, HUD, 점수판까지 게임 UI의 핵심 요소를 다룹니다. 실무에서 바로 활용할 수 있는 예제와 함께 배워봅니다.


목차

  1. 텍스트_생성과_스타일
  2. 웹폰트_사용하기
  3. 비트맵_텍스트
  4. 버튼_만들기
  5. 간단한_HUD_구현
  6. 점수판_만들기

1. 텍스트 생성과 스타일

김개발 씨는 첫 번째 Phaser 게임을 만들고 있었습니다. 캐릭터도 움직이고, 배경도 그럴듯하게 완성했습니다.

그런데 문득 깨달았습니다. "게임 제목이랑 점수는 어떻게 화면에 표시하지?"

Phaser에서 텍스트를 화면에 표시하려면 this.add.text() 메서드를 사용합니다. 마치 워드 프로세서에서 글자를 입력하고 폰트와 크기를 지정하는 것과 같습니다.

위치, 내용, 스타일을 한 번에 지정할 수 있어서 게임 화면에 필요한 모든 텍스트를 손쉽게 배치할 수 있습니다.

다음 코드를 살펴봅시다.

// Phaser 씬의 create() 메서드 내부
// 기본 텍스트 생성 - x, y 좌표와 표시할 문자열
const title = this.add.text(400, 50, 'My First Game', {
    fontFamily: 'Arial',
    fontSize: '48px',
    fontStyle: 'bold',
    color: '#ffffff',
    stroke: '#000000',
    strokeThickness: 4,
    shadow: { offsetX: 2, offsetY: 2, color: '#333333', blur: 5, fill: true }
});

// 텍스트 중앙 정렬을 위한 origin 설정
title.setOrigin(0.5, 0.5);

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 회사에서 간단한 미니게임 프로젝트를 맡게 되었고, Phaser 엔진을 처음 사용해보게 되었습니다.

캐릭터 스프라이트를 화면에 띄우는 것까지는 튜토리얼을 보며 어렵지 않게 해냈습니다. 그런데 막상 게임 제목을 화면 상단에 표시하려고 하니 막막했습니다.

HTML의 div 태그처럼 그냥 텍스트를 넣으면 되는 걸까요? 선배 개발자 박시니어 씨가 김개발 씨의 화면을 슬쩍 보더니 말했습니다.

"Phaser에서는 this.add.text() 메서드를 사용하면 돼요. 캔버스 위에 직접 텍스트를 그리는 방식이에요." 그렇다면 this.add.text()는 정확히 어떻게 동작하는 걸까요?

쉽게 비유하자면, 이것은 마치 빈 도화지에 글씨를 쓰는 것과 같습니다. 어디에 쓸지 위치를 정하고, 무슨 내용을 쓸지 정하고, 어떤 펜으로 어떤 색깔로 쓸지 정합니다.

this.add.text()도 마찬가지로 x좌표, y좌표, 텍스트 내용, 그리고 스타일 객체를 인자로 받습니다. 스타일 객체 안에는 다양한 속성을 넣을 수 있습니다.

fontFamily로 글꼴을 지정하고, fontSize로 크기를 정합니다. color는 글자 색상이고, strokestrokeThickness로 테두리를 줄 수 있습니다.

여기서 주목할 점은 shadow 속성입니다. 게임에서 텍스트가 배경과 잘 구분되도록 그림자 효과를 주는 경우가 많은데, Phaser는 이를 내장 기능으로 지원합니다.

offsetX, offsetY로 그림자 위치를, blur로 흐림 정도를 조절할 수 있습니다. 텍스트를 생성한 후에는 setOrigin() 메서드로 기준점을 설정하는 것이 중요합니다.

기본값은 (0, 0)으로 왼쪽 상단이 기준점입니다. 화면 중앙에 텍스트를 배치하고 싶다면 setOrigin(0.5, 0.5)로 설정하면 텍스트의 정중앙이 기준점이 됩니다.

실제 현업에서는 게임 로딩 화면, 메뉴 화면, 게임 오버 화면 등 다양한 곳에서 텍스트를 사용합니다. 특히 모바일 게임에서는 다양한 해상도에 대응해야 하므로, 텍스트 위치와 크기를 동적으로 계산하는 경우가 많습니다.

주의할 점도 있습니다. Phaser의 텍스트는 매 프레임마다 다시 그려지기 때문에, 텍스트가 많거나 자주 변경되면 성능에 영향을 줄 수 있습니다.

점수판처럼 자주 업데이트되는 텍스트는 뒤에서 배울 비트맵 텍스트를 고려해보는 것이 좋습니다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"생각보다 간단하네요. 스타일 객체만 잘 활용하면 예쁜 텍스트를 만들 수 있겠어요!"

실전 팁

💡 - fontSize는 반드시 문자열로 '48px' 형태로 지정해야 합니다

  • 그라데이션 효과가 필요하면 fill 속성에 배열로 여러 색상을 전달하세요
  • 텍스트 내용을 동적으로 변경하려면 setText() 메서드를 사용합니다

2. 웹폰트 사용하기

김개발 씨의 게임이 점점 모양새를 갖춰가고 있었습니다. 그런데 기획자가 피드백을 남겼습니다.

"폰트가 너무 밋밋해요. 우리 게임 분위기에 맞는 특별한 폰트를 쓸 수 없을까요?"

Phaser에서 웹폰트를 사용하려면 폰트가 완전히 로드된 후에 텍스트를 생성해야 합니다. WebFont Loader 라이브러리를 활용하거나, CSS의 @font-face와 document.fonts.ready를 조합하여 폰트 로딩을 제어할 수 있습니다.

폰트가 로드되기 전에 텍스트를 생성하면 기본 폰트로 표시되는 문제가 발생합니다.

다음 코드를 살펴봅시다.

// index.html에 Google Fonts 링크 추가 후
// 또는 CSS에서 @font-face로 커스텀 폰트 정의

class GameScene extends Phaser.Scene {
    preload() {
        // 폰트 로딩 완료를 기다리기 위한 처리
        this.load.script('webfont', 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js');
    }

    create() {
        // WebFont Loader로 폰트 로드
        WebFont.load({
            google: { families: ['Press Start 2P', 'Noto Sans KR'] },
            active: () => {
                // 폰트 로딩 완료 후 텍스트 생성
                this.add.text(400, 300, '게임 시작', {
                    fontFamily: '"Press Start 2P"',
                    fontSize: '32px',
                    color: '#ffff00'
                }).setOrigin(0.5);
            }
        });
    }
}

김개발 씨는 기획자의 요청을 듣고 구글 폰트에서 멋진 레트로 스타일 폰트를 찾았습니다. "Press Start 2P"라는 폰트였는데, 8비트 게임 느낌이 물씬 났습니다.

HTML 파일에 구글 폰트 링크를 추가하고, fontFamily에 폰트 이름을 지정했습니다. 그런데 이상하게도 화면에는 여전히 기본 폰트가 표시되었습니다.

"왜 안 되는 거지?" 김개발 씨는 머리를 긁적였습니다. 박시니어 씨가 지나가다 화면을 보고 웃으며 말했습니다.

"아, 그건 폰트 로딩 타이밍 문제예요. 웹폰트는 비동기로 로드되거든요." 웹폰트의 가장 까다로운 점이 바로 이것입니다.

폰트 파일은 네트워크를 통해 다운로드되어야 하고, 이 과정은 시간이 걸립니다. Phaser의 create() 함수가 실행되는 시점에 폰트가 아직 로드되지 않았다면, 브라우저는 대체 폰트를 사용하게 됩니다.

마치 택배를 주문했는데, 택배가 도착하기 전에 이미 파티를 시작해버린 것과 같습니다. 택배가 필요했던 물건은 쓸 수 없게 되죠.

이 문제를 해결하려면 WebFont Loader 라이브러리를 사용하는 것이 가장 확실합니다. 이 라이브러리는 구글에서 제공하며, 폰트 로딩 상태를 추적할 수 있게 해줍니다.

WebFont.load() 함수의 active 콜백은 모든 폰트가 성공적으로 로드된 후에 실행됩니다. 이 콜백 안에서 텍스트를 생성하면 폰트가 확실히 적용됩니다.

한 가지 주의할 점은 fontFamily를 지정할 때 따옴표를 두 겹으로 사용해야 한다는 것입니다. CSS에서 공백이 포함된 폰트 이름은 따옴표로 감싸야 하는데, JavaScript 문자열 안에서 이를 표현하려면 작은따옴표와 큰따옴표를 조합해야 합니다.

한글 폰트를 사용할 때는 더욱 주의가 필요합니다. 한글 웹폰트는 영문 폰트보다 파일 크기가 훨씬 크기 때문에 로딩 시간이 오래 걸릴 수 있습니다.

"Noto Sans KR"같은 한글 폰트는 서브셋 버전을 사용하거나, 로딩 화면을 보여주는 것이 좋습니다. 만약 WebFont Loader 없이 순수하게 처리하고 싶다면, document.fonts.ready 프로미스를 활용할 수도 있습니다.

이는 모든 폰트가 로드되면 resolve되는 프로미스입니다. 실무에서는 게임 로딩 단계에서 폰트를 미리 로드해두고, 메인 게임 씬에서는 이미 로드된 폰트를 바로 사용하는 패턴이 일반적입니다.

실전 팁

💡 - 한글 폰트는 파일 크기가 크므로 서브셋 버전 사용을 권장합니다

  • fontFamily 값에 폴백 폰트를 지정하면 로딩 실패 시 대체 폰트가 표시됩니다
  • 개발 중에는 폰트 캐시 때문에 변경사항이 안 보일 수 있으니 강력 새로고침을 사용하세요

3. 비트맵 텍스트

게임이 어느 정도 완성되어 갈 무렵, 김개발 씨는 이상한 현상을 발견했습니다. 점수가 올라갈 때마다 게임이 살짝 버벅거리는 것이었습니다.

프로파일러를 돌려보니 텍스트 렌더링에서 병목이 발생하고 있었습니다.

비트맵 텍스트는 미리 만들어진 글꼴 이미지를 사용하여 텍스트를 표시하는 방식입니다. 일반 텍스트가 매번 글자를 그려내는 것과 달리, 비트맵 텍스트는 이미 그려진 글자 이미지를 조합하기만 하면 됩니다.

마치 도장을 찍듯이 빠르게 텍스트를 표시할 수 있어서 자주 변경되는 점수나 타이머에 적합합니다.

다음 코드를 살펴봅시다.

// preload에서 비트맵 폰트 로드
preload() {
    // .png 이미지와 .xml 또는 .fnt 파일이 필요
    this.load.bitmapFont('pixelFont',
        'assets/fonts/pixel.png',
        'assets/fonts/pixel.xml'
    );
}

create() {
    // 비트맵 텍스트 생성 - 일반 텍스트와 사용법 유사
    this.scoreText = this.add.bitmapText(10, 10, 'pixelFont', 'SCORE: 0', 32);

    // 동적으로 텍스트 변경
    this.score = 0;
}

update() {
    // 점수 업데이트 - 매우 빠른 성능
    this.score += 1;
    this.scoreText.setText('SCORE: ' + this.score);
}

김개발 씨는 당황했습니다. 분명히 간단한 텍스트를 업데이트하는 것뿐인데, 왜 성능 문제가 생기는 걸까요?

박시니어 씨가 설명해주었습니다. "일반 텍스트는 매번 동적으로 렌더링되거든요.

브라우저가 폰트 정보를 읽고, 글자 모양을 계산하고, 픽셀을 채워 넣어야 해요. 프레임마다 이 과정을 반복하면 부하가 쌓이죠." 일반 텍스트의 동작 방식을 이해하려면, 마치 서예가가 매번 붓으로 글씨를 새로 쓰는 것을 상상해보면 됩니다.

아무리 빠른 서예가라도 1초에 60번씩 글씨를 새로 쓰라고 하면 지칠 수밖에 없습니다. 반면 비트맵 텍스트는 다릅니다.

이것은 마치 고무 도장 세트와 같습니다. 미리 A부터 Z까지, 0부터 9까지 모든 글자가 새겨진 도장이 준비되어 있습니다.

글자를 표시할 때는 해당하는 도장을 꺼내서 찍기만 하면 됩니다. 기술적으로 말하면, 비트맵 폰트는 모든 글자가 이미지로 저장된 스프라이트 시트입니다.

XML이나 FNT 파일에는 각 글자의 위치 정보가 담겨 있습니다. Phaser는 이 정보를 바탕으로 필요한 글자 이미지를 빠르게 조합합니다.

비트맵 폰트를 사용하려면 두 가지 파일이 필요합니다. 하나는 글자들이 담긴 PNG 이미지 파일이고, 다른 하나는 각 글자의 위치와 크기 정보가 담긴 XML 또는 FNT 파일입니다.

이런 비트맵 폰트 파일은 직접 만들기 어렵습니다. 다행히 BMFont, Littera, Hiero 같은 무료 도구들이 있어서 원하는 폰트를 비트맵 폰트로 변환할 수 있습니다.

preload()에서 this.load.bitmapFont()로 폰트를 로드하면, create()에서 this.add.bitmapText()로 사용할 수 있습니다. 사용법은 일반 텍스트와 거의 비슷합니다.

성능 차이는 극명합니다. 일반 텍스트를 매 프레임마다 업데이트하면 눈에 띄는 성능 저하가 발생하지만, 비트맵 텍스트는 거의 부하가 없습니다.

다만 비트맵 텍스트에는 제한이 있습니다. 폰트 이미지에 포함되지 않은 글자는 표시할 수 없습니다.

한글을 사용하려면 한글 글자가 모두 포함된 비트맵 폰트가 필요한데, 완성형 한글은 11,172자나 되어서 파일 크기가 매우 커집니다. 그래서 실무에서는 영문과 숫자, 특수문자 정도만 비트맵 텍스트로 사용하고, 한글은 일반 텍스트를 사용하는 경우가 많습니다.

실전 팁

💡 - 무료 비트맵 폰트 생성 도구로 BMFont, Littera, Hiero 등이 있습니다

  • 한글 비트맵 폰트는 파일 크기가 크므로 자주 쓰는 글자만 포함시키세요
  • 비트맵 텍스트도 setOrigin(), setTint() 등의 메서드를 지원합니다

4. 버튼 만들기

게임 개발이 순조롭게 진행되던 중, 기획자가 찾아왔습니다. "시작 화면에 게임 시작 버튼이랑 설정 버튼을 넣어주세요." 김개발 씨는 고개를 끄덕였지만, 속으로는 걱정이 되었습니다.

Phaser에는 버튼 컴포넌트가 따로 없었기 때문입니다.

Phaser 3에는 내장된 버튼 UI가 없어서 직접 만들어야 합니다. 이미지나 텍스트에 setInteractive() 메서드로 상호작용을 활성화하고, pointerover, pointerout, pointerdown, pointerup 이벤트를 처리하면 완전한 버튼을 구현할 수 있습니다.

마우스 호버 효과와 클릭 피드백까지 추가하면 사용자 경험이 크게 향상됩니다.

다음 코드를 살펴봅시다.

create() {
    // 버튼 배경 이미지 생성
    const button = this.add.image(400, 300, 'button_normal')
        .setInteractive({ useHandCursor: true });  // 마우스 커서 변경 포함

    // 버튼 텍스트 추가
    const buttonText = this.add.text(400, 300, 'START', {
        fontSize: '28px', color: '#ffffff'
    }).setOrigin(0.5);

    // 마우스 호버 시 효과
    button.on('pointerover', () => {
        button.setTexture('button_hover');
        button.setScale(1.1);
    });

    button.on('pointerout', () => {
        button.setTexture('button_normal');
        button.setScale(1.0);
    });

    // 클릭 시 실행될 동작
    button.on('pointerdown', () => {
        button.setTexture('button_pressed');
        this.scene.start('GameScene');
    });
}

김개발 씨는 문서를 뒤져봤지만, Phaser 3에는 Button 클래스가 없었습니다. Phaser 2에는 있었는데 3으로 넘어오면서 사라진 것입니다.

"왜 버튼을 없앤 거죠?" 김개발 씨가 투덜거렸습니다. 박시니어 씨가 웃으며 대답했습니다.

"프레임워크를 가볍게 유지하려고 그랬대요. 대신 이벤트 시스템이 잘 되어 있어서 직접 만들기 어렵지 않아요." 버튼의 본질은 무엇일까요?

사용자가 클릭할 수 있고, 클릭하면 무언가가 실행되는 것입니다. 여기에 마우스를 올렸을 때 시각적 피드백이 있으면 더 좋습니다.

Phaser에서는 어떤 게임 오브젝트든 setInteractive() 메서드를 호출하면 사용자 입력에 반응하게 만들 수 있습니다. 이미지, 텍스트, 도형 등 무엇이든 버튼이 될 수 있습니다.

setInteractive()를 호출할 때 useHandCursor: true 옵션을 주면, 마우스를 올렸을 때 손가락 모양 커서로 바뀝니다. 이것만으로도 사용자에게 "이건 클릭할 수 있어요"라는 신호를 줄 수 있습니다.

이벤트는 on() 메서드로 등록합니다. 마우스 관련 이벤트는 크게 네 가지입니다.

pointerover는 마우스가 올라갔을 때, pointerout은 마우스가 벗어났을 때, pointerdown은 마우스 버튼을 눌렀을 때, pointerup은 마우스 버튼을 뗐을 때 발생합니다. 좋은 버튼은 시각적 피드백이 있어야 합니다.

마우스를 올렸을 때 버튼이 살짝 커지거나 색이 바뀌면 사용자는 직관적으로 반응을 인식합니다. 클릭했을 때는 버튼이 눌린 것처럼 보이는 이미지로 바꾸면 더 자연스럽습니다.

위 코드에서는 세 가지 버튼 이미지를 사용합니다. button_normal은 평상시 모습, button_hover는 마우스를 올렸을 때, button_pressed는 클릭했을 때의 모습입니다.

setScale()로 크기를 조절하는 것도 좋은 피드백 방법입니다. 호버 시 1.1배로 키우면 버튼이 살짝 튀어나오는 듯한 효과를 줍니다.

실무에서는 버튼을 재사용 가능한 클래스로 만들어두는 것이 좋습니다. 게임에는 많은 버튼이 필요하고, 매번 같은 코드를 반복하면 유지보수가 어려워집니다.

한 가지 주의할 점은 모바일 환경입니다. 터치 디바이스에서는 pointerover 이벤트가 다르게 동작할 수 있습니다.

터치 시 pointerover와 pointerdown이 거의 동시에 발생하기 때문입니다.

실전 팁

💡 - 버튼 클래스를 만들어 재사용하면 코드 중복을 줄일 수 있습니다

  • 클릭 시 효과음을 추가하면 피드백이 더 명확해집니다
  • 모바일에서는 터치 영역을 충분히 크게 만드세요 (최소 44x44 픽셀 권장)

5. 간단한 HUD 구현

게임의 핵심 로직이 완성되자, 기획자가 다음 요구사항을 전달했습니다. "화면 상단에 체력바랑 스테미나바를 넣어주세요.

아, 그리고 현재 스테이지 번호도요." 김개발 씨는 이제 본격적인 HUD(Head-Up Display)를 만들어야 할 때가 왔다고 생각했습니다.

HUD는 게임 화면 위에 항상 표시되는 사용자 인터페이스입니다. 체력, 점수, 미니맵, 아이템 슬롯 등이 대표적인 HUD 요소입니다.

Phaser에서는 **컨테이너(Container)**를 활용하여 관련 UI 요소들을 그룹화하고, **setScrollFactor(0)**으로 카메라 이동과 무관하게 화면에 고정시킬 수 있습니다.

다음 코드를 살펴봅시다.

create() {
    // HUD 컨테이너 생성 - 관련 요소들을 그룹화
    this.hudContainer = this.add.container(0, 0);

    // 체력바 배경
    const hpBarBg = this.add.rectangle(120, 30, 200, 20, 0x333333);
    // 체력바 (빨간색)
    this.hpBar = this.add.rectangle(120, 30, 200, 20, 0xff0000);
    this.hpBar.setOrigin(0.5, 0.5);

    // 스테이지 텍스트
    this.stageText = this.add.text(700, 20, 'STAGE 1', {
        fontSize: '24px', color: '#ffffff'
    });

    // 컨테이너에 요소들 추가
    this.hudContainer.add([hpBarBg, this.hpBar, this.stageText]);

    // 카메라가 움직여도 HUD는 고정
    this.hudContainer.setScrollFactor(0);
    // HUD를 최상위 레이어로
    this.hudContainer.setDepth(1000);
}

// 체력 업데이트 함수
updateHP(current, max) {
    const ratio = current / max;
    this.hpBar.setScale(ratio, 1);
}

HUD라는 용어는 원래 전투기 조종사의 헬멧 바이저에 정보를 표시하는 장치에서 유래했습니다. 게임에서는 플레이어가 게임을 하면서 항상 볼 수 있는 정보 표시 영역을 의미합니다.

김개발 씨가 만드는 게임은 사이드 스크롤 액션 게임이었습니다. 캐릭터가 움직이면 카메라도 따라 움직입니다.

그런데 HUD까지 카메라를 따라 움직이면 안 됩니다. HUD는 화면의 고정된 위치에 있어야 합니다.

"마치 자동차 계기판 같은 거죠." 박시니어 씨가 비유했습니다. "자동차가 아무리 빨리 달려도 속도계는 항상 같은 위치에 있잖아요." Phaser에서 이를 구현하는 핵심 메서드가 **setScrollFactor()**입니다.

기본값은 1로, 카메라가 100픽셀 움직이면 오브젝트도 100픽셀 움직입니다. 0으로 설정하면 카메라가 아무리 움직여도 오브젝트는 제자리에 있습니다.

Container를 사용하는 것도 중요한 패턴입니다. HUD에는 여러 요소가 들어갑니다.

체력바, 스테미나바, 점수 텍스트, 미니맵 등등. 이것들을 각각 관리하면 코드가 복잡해집니다.

Container는 마치 서랍과 같습니다. 관련된 물건들을 한 서랍에 넣어두면 찾기도 쉽고 정리도 편합니다.

Container에 넣은 요소들은 Container를 기준으로 상대 좌표가 적용되고, Container를 움직이면 안의 요소들도 함께 움직입니다. **setDepth()**도 놓치면 안 됩니다.

게임에는 배경, 캐릭터, 이펙트 등 다양한 레이어가 있습니다. HUD는 항상 최상위에 표시되어야 하므로 높은 depth 값을 설정합니다.

위 코드에서는 1000을 사용했는데, 다른 게임 오브젝트들보다 충분히 높은 값이면 됩니다. 체력바를 구현하는 방법도 살펴봅시다.

가장 간단한 방법은 **setScale()**을 사용하는 것입니다. 체력이 50%로 줄어들면 x 스케일을 0.5로 설정하면 막대가 반으로 줄어듭니다.

다만 이 방식에는 주의할 점이 있습니다. 스케일의 기준점이 중요합니다.

setOrigin(0.5, 0.5)로 설정하면 중앙을 기준으로 줄어들어서 양쪽으로 줄어드는 것처럼 보입니다. 왼쪽에서 오른쪽으로 줄어들게 하려면 setOrigin(0, 0.5)로 설정해야 합니다.

실무에서는 HUD 전용 씬을 따로 만들어서 게임 씬 위에 오버레이하는 방식도 자주 사용합니다. 이렇게 하면 HUD 로직과 게임 로직을 완전히 분리할 수 있습니다.

실전 팁

💡 - setScrollFactor(0)은 카메라 영향을 받지 않게 하는 핵심 설정입니다

  • HUD 전용 씬을 만들어 게임 씬 위에 오버레이하면 관리가 편합니다
  • 체력바의 origin을 (0, 0.5)로 설정하면 왼쪽에서 줄어드는 효과를 줄 수 있습니다

6. 점수판 만들기

게임 개발의 마지막 단계에 접어들었습니다. 기획자가 마지막 요청을 했습니다.

"게임 오버 화면에 점수판을 넣어주세요. 최고 점수도 저장되면 좋겠고, 점수가 올라갈 때 애니메이션이 있으면 더 좋겠어요."

점수판은 단순히 숫자를 표시하는 것을 넘어, 시각적 피드백데이터 영속성까지 고려해야 합니다. Tween을 활용하면 점수가 부드럽게 올라가는 애니메이션을 구현할 수 있고, localStorage를 사용하면 최고 점수를 브라우저에 저장할 수 있습니다.

다음 코드를 살펴봅시다.

create() {
    this.score = 0;
    this.displayScore = 0;
    this.highScore = localStorage.getItem('highScore') || 0;

    // 점수 텍스트
    this.scoreText = this.add.text(400, 200, 'SCORE: 0', {
        fontSize: '48px', color: '#ffffff', fontStyle: 'bold'
    }).setOrigin(0.5);

    // 최고 점수 텍스트
    this.highScoreText = this.add.text(400, 270, 'BEST: ' + this.highScore, {
        fontSize: '32px', color: '#ffff00'
    }).setOrigin(0.5);
}

addScore(points) {
    this.score += points;

    // 점수 카운팅 애니메이션
    this.tweens.addCounter({
        from: this.displayScore,
        to: this.score,
        duration: 500,
        ease: 'Power2',
        onUpdate: (tween) => {
            this.displayScore = Math.floor(tween.getValue());
            this.scoreText.setText('SCORE: ' + this.displayScore);
        },
        onComplete: () => {
            // 최고 점수 갱신 확인
            if (this.score > this.highScore) {
                this.highScore = this.score;
                localStorage.setItem('highScore', this.highScore);
                this.highScoreText.setText('BEST: ' + this.highScore);
                // 최고 점수 갱신 효과
                this.tweens.add({
                    targets: this.highScoreText,
                    scale: { from: 1.5, to: 1 },
                    duration: 300
                });
            }
        }
    });
}

점수판은 게임에서 가장 중요한 피드백 요소 중 하나입니다. 플레이어가 잘하고 있는지, 목표에 얼마나 가까워졌는지 알려주기 때문입니다.

김개발 씨는 처음에 단순하게 점수를 표시했습니다. 적을 처치하면 점수가 바로 올라갔습니다.

작동은 했지만, 뭔가 밋밋했습니다. 박시니어 씨가 조언했습니다.

"점수가 한 번에 확 바뀌면 플레이어가 변화를 인지하기 어려워요. 점진적으로 올라가는 애니메이션을 넣어보세요." 이것이 바로 카운팅 애니메이션입니다.

마치 슬롯머신의 숫자가 돌아가다가 멈추는 것처럼, 현재 점수에서 목표 점수까지 숫자가 빠르게 올라가는 효과입니다. Phaser의 **tweens.addCounter()**가 이 역할을 합니다.

Tween은 원래 게임 오브젝트의 속성을 부드럽게 변화시키는 도구인데, addCounter()는 순수한 숫자 값을 트윈할 수 있게 해줍니다. from에서 to까지 duration 밀리초 동안 값이 변합니다.

onUpdate 콜백은 매 프레임마다 호출되어 현재 값을 받아올 수 있습니다. 이 값으로 텍스트를 업데이트하면 숫자가 부드럽게 올라가는 것처럼 보입니다.

ease 옵션도 중요합니다. 'Power2'를 사용하면 처음에는 빠르게 올라가다가 끝에서 천천히 멈추는 효과가 납니다.

'Linear'를 사용하면 일정한 속도로 올라갑니다. 여러 가지를 테스트해보고 게임에 맞는 느낌을 찾아보세요.

최고 점수 저장은 localStorage를 활용합니다. 이것은 브라우저에 데이터를 저장하는 간단한 방법입니다.

키-값 쌍으로 저장하고, 브라우저를 닫아도 데이터가 유지됩니다. localStorage.setItem('키', 값)으로 저장하고, localStorage.getItem('키')로 불러옵니다.

값은 항상 문자열로 저장되므로, 숫자를 다룰 때는 parseInt()나 Number()로 변환해야 할 때도 있습니다. 최고 점수가 갱신되었을 때 특별한 효과를 주면 플레이어의 성취감이 더 커집니다.

위 코드에서는 BEST 텍스트가 1.5배로 커졌다가 원래 크기로 돌아오는 애니메이션을 추가했습니다. 실무에서는 더 정교한 점수 시스템을 구현하기도 합니다.

콤보 시스템, 점수 배율, 시간 보너스 등 다양한 요소가 점수에 영향을 미칩니다. 이런 복잡한 로직은 별도의 ScoreManager 클래스로 분리하는 것이 좋습니다.

서버에 점수를 저장하는 리더보드를 구현하려면 백엔드 연동이 필요합니다. 하지만 단순한 싱글플레이 게임이라면 localStorage만으로도 충분한 경우가 많습니다.

실전 팁

💡 - tweens.addCounter()의 ease 옵션으로 다양한 애니메이션 효과를 시도해보세요

  • localStorage는 문자열만 저장하므로 객체는 JSON.stringify()로 변환해야 합니다
  • 신기록 달성 시 효과음과 파티클 이펙트를 함께 추가하면 더 임팩트 있습니다

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

#Phaser#GameUI#Text#Button#HUD#BitmapText#Game,JavaScript,Phaser

댓글 (0)

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