티스토리 뷰

- create function : 게임이 실행됐을 때 첫 번째로 실행되는 function

- update function : create function 이후에 계속해서 실행되는 function

 


● LoadingScene

- LoadingScene은 게임에서 쓰일 asset들을 load 할 scene이다.
- load 해 놓은 asset은 전역적으로 사용 가능하다.
- '루트폴더/src/scenes' 폴더를 만든 후 해당 폴더 안에 scene 파일을 만들면 된다.
- scenes 폴더 내에 LoadingScene.js를 생성해보자(나는 저번 글에서 이미 생성함)

// asset import
import Phaser from "phaser";
import fontPng from "../assets/font/font.png";
import fontXml from "../assets/font/font.xml";

import bgImg1 from "../assets/images/background.png";
import bgImg2 from "../assets/images/background-2.png";
import bgImg3 from "../assets/images/background-3.png";
import beamImg from "../assets/images/beam.png";

import explosionImg from "../assets/spritesheets/explosion.png";
import playerImg from "../assets/spritesheets/player.png";
import expUpImg from "../assets/spritesheets/expUp.png";
import mobImg1 from "../assets/spritesheets/mob1.png";
import mobImg2 from "../assets/spritesheets/mob2.png";
import mobImg3 from "../assets/spritesheets/mob3.png";
import mobImg4 from "../assets/spritesheets/mob4.png";
import lionImg from "../assets/spritesheets/lion.png";
import catnipImg from "../assets/spritesheets/catnip.png";
import clawWhiteImg from "../assets/spritesheets/claw-white.png";
import clawYellowImg from "../assets/spritesheets/claw-yellow.png";

import beamOgg from "../assets/sounds/beam.ogg";
import scratchOgg from "../assets/sounds/scratch.ogg";
import hitMobOgg from "../assets/sounds/hitMob.ogg";
import growlOgg from "../assets/sounds/growl.ogg";
import explosionOgg from "../assets/sounds/explosion.ogg";
import hurtOgg from "../assets/sounds/hurt.ogg";
import expUpOgg from "../assets/sounds/expUp.ogg";
import nextLevelOgg from "../assets/sounds/nextLevel.ogg"
import gameOverOgg from "../assets/sounds/gameover.ogg";
import gameClearOgg from "../assets/sounds/gameClear.ogg";
import pauseInOgg from "../assets/sounds/pauseIn.ogg";
import pauseOutOgg from "../assets/sounds/pauseOut.ogg";

// 사용할 모든 asset(image, sprite image, audio, font 등)을 load해 놓는다.
export default class LoadingScene extends Phaser.Scene {
    constructor() {
        // super에 파라미터로 넘겨주는 string이 해당 scene(= 여기서는 LoadingScene)의 identifier(식별자)가 된다.
        super("bootGame");
    }

    // asset들을 load하는 부분
    preload() {
        // ("식별자", import한 asset이름) --> "큰따옴표"안에 있는 string이 해당 asset의 identifier(식별자)가 된다.

        // IMAGES
        this.load.image("background1", bgImg1);
        this.load.image("background2", bgImg2);
        this.load.image("background3", bgImg3);
        this.load.image("beam", beamImg);

        // SPRITESHEETS
        this.load.spritesheet("player", playerImg, {
            frameWidth: 32,   // 개발자가 직접 설정해주는 값(오브젝트 크기 조절)
            frameHeight: 36,  // 개발자가 직접 설정해주는 값(오브젝트 크기 조절)
        });

        this.load.spritesheet("mob1", mobImg1, {
            frameWidth: 28,
            frameHeight: 28,
        });

        this.load.spritesheet("mob2", mobImg2, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("mob3", mobImg3, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("mob4", mobImg4, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("lion", lionImg, {
            frameWidth: 48,
            frameHeight: 64,
        });

        this.load.spritesheet("explosion", explosionImg, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("claw_white", clawWhiteImg, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("claw_yellow", clawYellowImg, {
            frameWidth: 32,
            frameHeight: 32,
        });

        this.load.spritesheet("catnip", catnipImg, {
            frameWidth: 64,
            frameHeight: 64,
        });
        
        this.load.spritesheet("exp-up", expUpImg, {
            frameWidth: 16,
            frameHeight: 16,
        });

        // AUDIOS
        this.load.audio("audio_beam", beamOgg);
        this.load.audio("audio_scratch", scratchOgg);
        this.load.audio("audio_hitMob", hitMobOgg);
        this.load.audio("audio_growl", growlOgg);
        this.load.audio("audio_explosion", explosionOgg);
        this.load.audio("audio_expUp", expUpOgg);
        this.load.audio("audio_hurt", hurtOgg);
        this.load.audio("audio_nextLevel", nextLevelOgg);
        this.load.audio("audio_gameOver", gameOverOgg);
        this.load.audio("audio_gameClear", gameClearOgg);
        this.load.audio("audio_pauseIn", pauseInOgg);
        this.load.audio("audio_pauseOut", pauseOutOgg);

        // FONT
        this.load.bitmapFont("pixelFont", fontPng, fontXml);
    }

    // load 시간이 많이 걸리지 않아 LoadingScene이 화면에 직접적으로 보일 일은 없지만
    // 혹시 모르니 “Loading game…”이라는 문구를 적어주었다.
    // create function 처음에 최초 1회만 실행된다. / update fimction은 계속해서 실행된다.
    create() {
        this.add.text(20, 20, "Loading game...");
        
        // create가 됐을 때 PlayingScene을 start 한다.
        this.scene.start("playGame"); // playGame = PlayingScene.js의 identifier(식별자)

        // MOBS
        this.anims.create({
            key: "mob1_anim",
            frames: this.anims.generateFrameNumbers("mob1"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "mob2_anim",
            frames: this.anims.generateFrameNumbers("mob2"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "mob3_anim",
            frames: this.anims.generateFrameNumbers("mob3"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "mob4_anim",
            frames: this.anims.generateFrameNumbers("mob4"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "lion_anim",
            frames: this.anims.generateFrameNumbers("lion"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "lion_idle",
            frames: this.anims.generateFrameNumbers("lion", {
                start: 0,
                end: 0,
            }),
            frameRate: 1,
            repeat: 0,
        });

        // PLAYERS
        this.anims.create({
            key: "player_anim",
            frames: this.anims.generateFrameNumbers("player"),
            frameRate: 12,
            repeat: -1,
        });

        this.anims.create({
            key: "player_idle",
            frames: this.anims.generateFrameNumbers("player", {
                start: 0, // 0번째 순서의 이미지로 시작(이미지가 자연스럽게 이어져 보이도록하기 위해)
                end: 0,   // 0번째 순서의 이미지로 끝(이미지가 자연스럽게 이어져 보이도록하기 위해)
            }),
            frameRate: 1,
            repeat: 0,
        });

        // EFFECT
        this.anims.create({
            key: "explode",
            frames: this.anims.generateFrameNumbers("explosion"),
            frameRate: 20,
            repeat: 0,
            hideOnComplete: true,
        });

        // ATTACKS
        this.anims.create({
            key: "scratch_white",
            frames: this.anims.generateFrameNumbers("claw_white"),
            frameRate: 20,
            repeat: 0,
            hideOnComplete: true,
        });

        this.anims.create({
            key: "scratch_yellow",
            frames: this.anims.generateFrameNumbers("claw_yellow"),
            frameRate: 20,
            repeat: 0,
            hideOnComplete: true,
        });

        this.anims.create({
            key: "catnip_anim",
            frames: this.anims.generateFrameNumbers("catnip"),
            frameRate: 20,
            repeat: -1,
        });

        // EXP UP ITEMS
        this.anims.create({
            key: "red",
            frames: this.anims.generateFrameNumbers("exp-up", {
                start: 0,
                end: 0,
            }),
            frameRate: 20,
            repeat: 0,
        });

        this.anims.create({
            key: "blue",
            frames: this.anims.generateFrameNumbers("exp-up", {
                start: 1,
                end: 1,
            }),
            frameRate: 20,
            repeat: 0,
        });

        this.anims.create({
            key: "yellow",
            frames: this.anims.generateFrameNumbers("exp-up", {
                start: 2,
                end: 2,
            }),
            frameRate: 20,
            repeat: 0,
        });

        this.anims.create({
            key: "green",
            frames: this.anims.generateFrameNumbers("exp-up", {
                start: 3,
                end: 3,
            }),
            frameRate: 20,
            repeat: 0,
        });
    }
}

- constructor의 super 안에 담긴 string은 해당 scene의 identifier(식별자)가 된다.
- 해당 identifier를 이용해 scene을 실행, 정지, 멈출 수 있다.
※ 주의 : class 이름이 아니라 identifier를 이용하는 것이다. 혼동하지 말자

 

cf)

[spriteimage 애니메이션 생성]

import playerImg from "../assets/spritesheets/player.png";

export default class LoadingScene extends Phaser.Scene {
    constructor() {
        super("bootGame");
    }
        
    preload() {
        // SPRITESHEETS
        this.load.spritesheet("player", playerImg, {
            frameWidth: 32,   // 개발자가 직접 설정해주는 값(오브젝트 크기 조절)
            frameHeight: 36,  // 개발자가 직접 설정해주는 값(오브젝트 크기 조절)
        });
    }

    create() {
        this.anims.create({
            key: "player_idle",
            frames: this.anims.generateFrameNumbers("player", {
                start: 0, // 0번째 순서의 이미지로 시작(이미지가 자연스럽게 이어져 보이도록하기 위해)
                end: 0,   // 0번째 순서의 이미지로 끝(이미지가 자연스럽게 이어져 보이도록하기 위해)
            }),
            frameRate: 1,
            repeat: 0,
        });
    }
}
========================================================================================================
1. key: 해당 애니메이션의 key(= identifier, 식별자)가 된다.
--> 해당 key 값을 가지고 애니메이션을 컨트롤

2. frames: 'this.anims.generateFrameNumbers("load한 spritesheet의 key")'로 입력
--> 어떤 것을 애니메이션의 프레임으로 사용할 것인지 지정
--> start, end를 설정해 같은 spritesheet로 여러 애니메이션을 만들 수 있다.

3. frameRate: 초당 프레임 수를 설정(디폴트 : 24) = 1초에 어느정도 반복 할 것이냐

4. repeat: 애니메이션 반복 횟수(-1 = 무한 반복)

5. hideOnComplete: true로 설정하면 애니메이션이 끝난 후 이미지가 화면에서 사라진다.(디폴트 = false)

https://paullab.co.kr/

 

바울랩, 위니브

제주 ICT 코딩 컴퓨터학원, 연구원, 출판사

paullab.co.kr

- start와 end 옵션은 위 사이트의 건물 이미지 움직임처럼 구현하기 위해서 설정한 옵션이다.

- 이미지를 잘라내 빠르게 보여줘서 마치 이미지가 움직이는 것처럼 보이게 하는 것이다.


PlayingScene

- PlayingScene을 만들기 전에 그 안에 들어가는 background 관련 기능과 player를 먼저 만들어야한다.

- 루트폴더/src/characters 폴더를 만든 후 해당 폴더 안에 player, mob 등의 캐릭터 클래스 파일들을 만들어보자

1.
- characters 폴더안에 Player.js 파일을 만들자

[src/characters/Player.js]

import Phaser from "phaser";
import Config from "../Config";

// Player가 Arcade 물리엔진의 영향을 받도록 extends
export default class Player extends Phaser.Physics.Arcade.Sprite {
    // 어느 scene에 Player가 나올지 정해줘야 하므로 argument로 scene을 받는다.
    // 참고로 scene의 constructor는 parameter가 없다.
    constructor(scene) {
        // 화면의 가운데에 player를 추가
        // 마지막 "player" string이 identifier(식별자)이다. by LoadingScene.js
        super(scene, Config.width / 2, Config.height / 2, "player");

        // scene.add.existing : scene에 오브젝트 추가
        scene.add.existing(this);

        // scene.physics.add.existing : scene의 물리엔진에 오브젝트를 추가
        scene.physics.add.existing(this);

        // scale 프로퍼티를 조절해 크기 조절(디폴트: 1)
        this.scale = 2;

        // depth를 조절해 어떤 오브젝트가 앞에 오고 뒤에 올지 설정
        // CSS의 z-index와 비슷한 개념(디폴트: 0)
        this.setDepth(20); // 10단위로 조절하는게 좋다.

        // 해당 오브젝트가 물리적으로 어느정도의 면적을 차지할 지 설정하는 함수(디폴트는 이미지 사이즈)
        // 하지만 디폴트는 추후 몹을 추가했을 때 잘 부딪히는 느낌이 드므로 원본 이미지보다 약간 작게 설정
        this.setBodySize(28, 32);
    }
}

 

2.

- 루트폴더/src/utils 폴더를 만들자

- 해당 폴더에는 각종 유용한 기능을 하는 함수들의 코드를 집어넣는다.
- 개발자가 유용하거나 자주 사용할 함수를 직접 작성해서 모아놓는 곳이다.
- utils 폴더 내에 backgroundManager.js를 생성

[src/utils/backgroundManager.js]

import Config from "../Config";

/**
 * scene의 배경화면을 설정하는 함수
 * @param {Phaser.Scene} scene
 * @param {string} backgroundTexture
 */
export function setBackground(scene, backgroundTexture) {
    // tileSprite : 게임 화면이 background image보다 큰 경우 background image를 타일처럼 붙여서 보여주는 이미지
    // (CSS의 background-repeat: repeat; 같은 느낌)
    scene.m_background = scene.add.tileSprite(
        0,
        0,
        Config.width,
        Config.height,
        backgroundTexture
    ).setOrigin(0, 0); // setOrigin : tileSprite 자체의 원점(0, 0) 위치를 설정해주는 함수
}

- 붉은색이 게임 화면이고 하늘색이 background image 일때 background image가 작으면 게임 화면을 채우기 위해 바둑판식으로 background image를 다닥다닥 붙여 게임 화면을 채우는 것이다.

https://rexrainbow.github.io/phaser3-rex-notes/docs/site/tilesprite/

 

Tile sprite - Notes of Phaser 3

Tile sprite Introduction Display of repeating texture, built-in game object of phaser. Usage Load texture scene.load.image(key, url); Reference: load image Add tile sprite object var image = scene.add.tileSprite(x, y, width, height, textureKey); Add tile s

rexrainbow.github.io

 

 

3.

- src/scenes 폴더 내에 PlayingScene.js 파일 생성
- 위에서 만든 Player, setBackground function import
cf) LoadingScene의 create function이 실제 플레이 될 애니메이션 위주로 add했다면 PlayingScene의 create는 사운드 위주로 add 돼있다.

import Phaser from "phaser";
import Player from "../characters/Player";
import { setBackground } from "../utils/backgroundManager";

export default class PlayingScene extends Phaser.Scene {
    constructor() {
        super("playGame");
    }

    create() {
        // 사용할 sound들을 추가해놓는 부분
        // load function : 어떤 scene에서든 전역적으로 asset을 사용할 수 있도록 load (LoadingScene.js 참고)
        // add function  : 해당 scene에서 asset을 사용할 수 있도록 scene의 멤버 변수로 추가할 때 사용
        // cf) this.xxxx 에서 xxxx 자리에 적힌 것들이 멤버 변수들이다.
        this.sound.pauseOnBlur  = false;
        this.m_beamSound        = this.sound.add("audio_beam");
        this.m_scratchSound     = this.sound.add("audio_scratch");
        this.m_hitMobSound      = this.sound.add("audio_hitMob");
        this.m_growlSound       = this.sound.add("audio_growl");
        this.m_explosionSound   = this.sound.add("audio_explosion");
        this.m_expUpSound       = this.sound.add("audio_expUp");
        this.m_hurtSound        = this.sound.add("audio_hurt");
        this.m_nextLevelSound   = this.sound.add("audio_nextLevel");
        this.m_gameOverSound    = this.sound.add("audio_gameOver");
        this.m_gameClearSound   = this.sound.add("audio_gameClear");
        this.m_pauseInSound     = this.sound.add("audio_pauseIn");
        this.m_pauseOutSound    = this.sound.add("audio_pauseOut");

        // player를 m_player라는 멤버 변수로 추가
        // new Player(this) --> (Player class의 인스턴스 생성, 자기 자신(PlayingScene)을 scene으로 전달)
        this.m_player = new Player(this); // characters/Player.js

        // PlayingScene의 background 설정
        // 여기서 "background1"은 LoadingScene에서 load한 asset이다. --> this.load.image("background1", bgImg1);
        setBackground(this, "background1"); // backgroundManager.js의 함수
    }
}

● webpack 확장자명 추가

- 근데 이렇게 신나게 작성한 후 npm start를 하면 정작 아래와 같은 오류가 발생한다.

- 이것은 webpack 때문 발생하는 오류인데 정확히는 .ogg 확장자 파일을 처리하지 못 해서 그런것이다.


- webpack/base.js 파일을 보자, 현재 아래와 같다.

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

module.exports = {
  mode: "development",
  devtool: "eval-source-map",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: [/\.vert$/, /\.frag$/],
        use: "raw-loader"
      },
      {
        test: /\.(gif|png|jpe?g|svg|xml)$/i,
        use: "file-loader"
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin({
      root: path.resolve(__dirname, "../")
    }),
    new webpack.DefinePlugin({
      CANVAS_RENDERER: JSON.stringify(true),
      WEBGL_RENDERER: JSON.stringify(true)
    }),
    new HtmlWebpackPlugin({
      template: "./index.html"
    })
  ]
};

- 아래 부분을 수정해야 파일을 제대로 로드할 수 있다.(확장자에 ogg가 없기 때문에 오류 발생)

as-is --> test: /\.(gif|png|jpe?g|svg|xml)$/i,
to-be --> test: /\.(gif|png|jpe?g|svg|xml|mp3|ogg)$/i,

- 수정 후 서버를 다시 돌려보자, 아래의 화면이 나오면 정상적으로 된 것이다.

 

cf) 현재 폴더 구조

'흥미 > phaser' 카테고리의 다른 글

#13 움직이는 Player 2(무한 배경 구현)  (0) 2023.05.23
#12 움직이는 Player 1  (0) 2023.05.16
#10 Scene 만들기1  (0) 2023.05.12
#9 템플릿 다운  (0) 2023.05.07
#8 캐릭터 움직이기 tutorial  (0) 2023.05.06
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함