← Log

I am Ironman: 콘텐츠 모션과 애니메이션

I am Ironman 게임 개발기· 4 / 7
  1. 1 — I am Ironman: 프로젝트 소개
  2. 2 — I am Ironman: 프로젝트 세팅
  3. 3 — I am Ironman: 게임 화면 구현
  4. 4 — I am Ironman: 콘텐츠 모션과 애니메이션
  5. 5 — I am Ironman: 콘텐츠 물리 충돌 처리
  6. 6 — I am Ironman: 게임 씬 구성
  7. 7 — I am Ironman: 게임 배포와 프로젝트 점검

콘텐츠의 인스턴스를 생성하는 것만으로는 게임을 ‘플레이’ 할 수 없다. 콘텐츠의 상황별 모션과 애니메이션, 컨트롤 등 다양한 메커니즘이 설정되어 있어야 기본적인 플레이 화면이 구현된다.

기본적인 모션과 애니메이션, 컨트롤 로직을 정리해본다.

애니메이션

콘텐츠 모드에 따라 이미지를 교체하며 애니메이션 효과를 표현했다.

* 애니메이션 효과 적용
준비한 캐릭터의 이미지 스프라이트 파일에는 캐릭터의 연속된 동작이 처리되어 있지 않아 이미지 스프라이트 애니메이션을 적용할 수 없었다.
대안책으로 모션마다 이미지를 따로 준비해 이미지를 체인지하는 방식으로 애니메이션 효과를 적용했다.

애니메이션 모습

아이언맨 모드 변경(5) 울트론 모드 변경(2) 울트론2 모드 변경(2)

애니메이션 구현 코드

  • 모드 변경과 이미지 전환

    // managers/characters/ironmanManager.ts
    ...
    
    // 아이언맨 모드 변경 처리
    public transform(mode: IronmanMode, callback?: () => void) {
      // 아이언맨 모드 변경
      this.setTexture(mode);
    
      // 모드별 사운드 재생
      if (mode !== IronmanMode.NORMAL || IronmanMode.REPULSOR)
        this.ironman.playSound(mode); // 콘텐츠 인스턴스에 설정되어 있는 사운드 재생
    
      // 모드 유지 타이머 설정
      this.timerHandler.handleIronmanMode(mode, () => {
        this.setTexture(
          mode === IronmanMode.GATHER ? IronmanMode.BEAM : IronmanMode.NORMAL
        );
        if (callback) callback(); // () => 빔 발사 후 제거
      });
    }
    
    // 아이언맨 모드 변경
    private setTexture(mode: IronmanMode) {
      if (this.ironman) {
        // 이미지 키값 정리
        const textures = {
          [IronmanMode.NORMAL]: ImageTexture.IRONMAN_NORMAL,
          [IronmanMode.REPULSOR]: ImageTexture.IRONMAN_REPULSOR,
          [IronmanMode.HIT]: ImageTexture.IRONMAN_HIT,
          [IronmanMode.GATHER]: ImageTexture.IRONMAN_GATHER,
          [IronmanMode.BEAM]: ImageTexture.IRONMAN_BEAM,
        };
    
        // 이미지 변경
        this.ironman.setTexture(textures[mode]);
    
        // 아이언맨 사이즈 재설정
        this.ironman.setSize(this.ironman.frame.width, this.ironman.frame.height);
    
        // 아이언맨 모드 업데이트
        this.ironman.mode = mode;
      }
    }

    메인 캐릭터인 아이언맨은 기본, 공격, 피격, 스킬 등 모드에 따라 각기 다른 이미지를 사용한다.

    모드 변경이 필요할 경우 아이언맨 매니저 클래스의 transform() 메서드를 호출해 이미지를 변경하며, 대부분의 경우 기본 모드로 자동 복귀할 수 있도록 있도록 타이머 이벤트를 함께 등록한다.

    적 NPC와 발사 무기도 동일한 로직을 적용하기 위해, 각각의 매니저 클래스에 동일한 기능의 메서드를 정의해 두었다.

모션과 컨트롤: 이동

콘텐츠별로 이동 방식은 사용자 입력 또는 자동 로직으로 나뉜다.

모션(컨트롤) 설정

  • 아이언맨: 상하좌우(방향키 컨트롤 이동)
  • : 게임 화면의 우 → 좌(자동 이동)
  • 리펄서 무기: 아이언맨의 손 위치로부터 좌 → 우(자동 이동)
  • 적 리펄서 무기: 울트론1의 손 위치로부터 우 → 좌(자동 이동)
  • 유니빔 무기: 아이언맨의 명치 위치에 고정(아이언맨 이동 팔로우)

모션 구현 코드

  • 방향키 입력 이벤트 설정

    // handlers/keyHandler.ts
    import Phaser from 'phaser';
    import IronmanControlManager from '../managers/charaters/ironmanControlManager';
    ...
    
    export default class KeyHandler {
      private scene: Phaser.Scene;
      private ironmanControlManager: IronmanControlManager;
      private ironman: Ironman;
      private cursors: Phaser.Types.Input.Keyboard.CursorKeys;
      ...
    
      constructor(
        scene: Phaser.Scene,
        ironmanControlManager: IronmanControlManager
        ...
    
      ) {
        this.scene = scene;
        this.ironmanControlManager = ironmanControlManager;
        this.ironman = this.ironmanManager.get();
        this.cursors = this.scene.input.keyboard!.createCursorKeys();
        ...
        
      }
    
      ...
    
      // 아이언맨 위치 업데이트
      public updateIronmanPosition() {
        if (!this.ironman?.active) return;
    
        this.handleKeyCursor();
      }
    
      // Cursors 키 이벤트
      private handleKeyCursor() {
        if (this.cursors.left.isDown) // 왼쪽 방향키
          this.ironmanControlManager.updatePosition(false, false);
        if (this.cursors.right.isDown) // 오른쪽 방향키
          this.ironmanControlManager.updatePosition(false, true);
        if (this.cursors.up.isDown) // 위쪽 방향키
          this.ironmanControlManager.updatePosition(true, false);
        if (this.cursors.down.isDown) // 아래쪽 방향키
          this.ironmanControlManager.updatePosition(true, true);
      }
    }

    키 이벤트 설정 클래스에서는 방향키 입력에 따라 플레이어 캐릭터의 위치를 업데이트하는 메서드를 호출한다. 이 클래스의 외부로 공유 할 updateIronmanPosition 메서드를 씬의 update 메서드에 등록하여 매 프레임마다 방향키 입력을 감지하고 캐릭터의 위치를 실시간으로 업데이트한다.

    * createCursorKeys()
    input.keyboard의 'on' 메서드는 keydown과 keyup 이벤트를 통해 키 입력을 감지하지만, 
    방향키를 누르고 있는 동안 지속적으로 상태를 확인하지 않는다.
    따라서 'createCursorKeys' 메서드를 이용해 매 프레임마다 키 입력을 감지하는 게 효율적이다.
  • 캐릭터 위치 업데이트

    // managers/characters/ironmanControlManager.ts
    ...
    
    // 아이언맨 이동
    public updatePosition(isUpdown: boolean, isPlus: boolean) {
      const speed =
        this.scene.game.canvas.width *
        (isPlus ? speedConfig.ironman : -speedConfig.ironman);
    
      // 아이언맨 위치 변경
      isUpdown
        ? this.ironman.setY(this.ironman.y + speed)
        : this.ironman.setX(this.ironman.x + speed);
    
      // 이동 제한 범위 설정
      this.setMovementBounds();
    }

    방향키 입력 이후 호출된 매니저 클래스의 updatePosition 메서드에서 캐릭터의 이동 속도를 반영하여 위치를 실시간으로 업데이트한다.

    * 메인 캐릭터 한정으로 생성/관리와 컨트롤 기능의 클래스를 분리해두었다.
  • 그 외 콘텐츠 모션

    // managers/characters/enemyManager.ts
    ...
    
    // 빌런 이동
    public updatePosition() {
      if (this.enemies!.getLength() <= 0) return;
    
      this.enemies.children.entries.forEach((child) => {
        const enemy = child as Enemy;
    
        const speed = this.scene.game.canvas.width * enemy.getSpeed();
        const type = enemy.getType();
    
        // 빌런 위치 변경
        enemy.setX(enemy.x - speed);
    
        // 화면을 벗어날 경우
        if (enemy.x < 0) {
          ...
        }
      });
    }
    // managers/weapons/repulsorManager.ts
    ...
    
    // 리펄서 이동
    public updatePosition() {
      if (this.repulsors.getLength() <= 0) return;
    
      this.repulsors!.children.entries.forEach((child) => {
        const repulsor = child as Repulsor;
    
        const speed = this.scene.game.canvas.width * repulsor.getSpeed();
    
        // 리펄서 위치 변경
        repulsor.setX(repulsor.x + speed);
    
        // 일정 간격/화면을 벗어나면 리펄서 제거
        if (
          repulsor.x > this.scene.game.canvas.width ||
          repulsor.x >
            repulsor.getInitialPos().x +
              this.scene.game.canvas.width * intervalConfig.repulsorFireDistance
        )
          ...
    
      });
    }

    적과 무기 콘텐츠의 이동 메서드 또한 씬의 update 메서드에 등록해 매 프레임마다 x의 위치를 업데이트한다. 각 콘텐츠가 게임 화면을 벗어나면 해당 인스턴스는 제거된다.

    그 외 다른 콘텐츠들도 비슷한 로직을 갖는다.

모션과 컨트롤: 공격

공격 모션은 특정 키 입력 또는 타이머 이벤트로 트리거된다.

모션(컨트롤) 설정

  • 리펄서 발사: Space-Bar 키 입력
  • 유니빔 발사: B 키 입력
  • 적 리펄서 발사: 타이머 이벤트(자동 생성)

모션 구현 코드

  • 특정 키 입력 이벤트 설정

    // handlers/keyHandler.ts
    ...
    
    constructor(
      scene: Phaser.Scene,
      ironmanControlManager: IronmanControlManager
      ...
    ) {
      ...
    
      this.createKeyEvents();
    }
    
    // 키 함수 할당
    private createKeyEvents() {
      this.scene.input.keyboard!.on('keydown-SPACE', this.handleKeySpace, this);
      this.scene.input.keyboard!.on('keydown-B', this.handleKeyB, this);
    }
    
    // Space bar 키 이벤트
    private handleKeySpace() {
      this.ironmanControlManager.attackRepulsor();
    }
    
    // B 키 이벤트
    private handleKeyB() {
      this.ironmanControlManager.attackBeam();
    }

    Space-BarB키의 입력 이벤트에 공격 모션을 설정해두었다. 해당 메서드들은 씬의 update 메서드에는 별도로 등록할 필요 없다.

  • 공격 이벤트

    // managers/weapons/repulsorManager.ts
    ...
    
    // 리펄서 발사
    public fire() {
      if (
        this.repulsors.getLength() >= maxConfig.repulsor ||
        this.scene.time.now <=
          this.lastRepulsorFireTime + intervalConfig.repulsorFireTime
      )
        return;
    
      // 리펄서 생성 위치 지정
      const posX = this.ironman.x + this.ironman.displayWidth * 0.62;
      const posY = this.ironman.y + this.ironman.displayHeight * -0.33;
    
      const repulsor = new Repulsor(
        this.scene,
        posX,
        posY,
        ImageTexture.REPULSOR
      );
    
      // 리펄서 발사 시간 스템프
      this.lastRepulsorFireTime = this.scene.time.now;
    
      // 그룹 추가
      this.repulsors.add(repulsor);
      this.weapons.add(repulsor);
    
      // 리펄서 사운드 재생
      // 아이언맨 공격 감지 핸들러 등록
    }
    // managers/weapons/beamManager.ts
    ...
    
    // 빔 발사
    public fire() {
      // 발사
      const beam = new Beam(this.scene, 0, 0, ImageTexture.BEAM);
    
      this.beam = beam;
      this.beams.add(this.beam);
      this.weapons!.add(this.beam);
    
      // 아이언맨 공격 감지 핸들러 등록
    }

    각 무기 발사 메서드가 호출되면 발사 주체(캐릭터)의 위치를 기반으로 해당 무기의 인스턴스를 생성해 화면에 구현하고, 기능별 그룹에 추가해 이동과 충돌 등 부수 기능에 활용된다.

모션과 애니메이션 구현 화면

리펄서 발사 유니빔 발사 울트론 리펄서 발사

참고 자료

  • [1] Getting Started with Phaser 3
목차