← Log

I am Ironman: 콘텐츠 물리 충돌 처리

I am Ironman 게임 개발기· 5 / 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: 게임 배포와 프로젝트 점검

게임 구현 중 가장 어려웠던 부분은 콘텐츠의 충돌 처리였다.

캐릭터를 스프라이트 형식의 이미지로 구현했지만, 캐릭터의 투명 배경까지 충돌 범위로 인식되어 물리적 충돌 범위가 사각형으로 설정되었다.
이로 인해 시각적으로는 캐릭터에 닿지 않았어도 충돌이 감지되는 문제가 발생했다.

이 문서에서는 이 현상을 ’ 비정상적 충돌 ‘이라고 부르겠다.

Ironman Collision 물리 충돌 가능 영역(보라색 라인)의 비정상적 충돌

해결 대안

이 비정상적 충돌을 해결하기 위한 과정은 다음과 같다.

문제 인식

위 ’ 비정상적 충돌 ‘을 해결하기 위해서는 이미지 자체를 물리 충돌 대상으로 설정하는 대신, 이미지의 시각적인 형태에 맞는 다각형의 물리 영역으로 재정의해야 한다.

그러나 현재 사용 중인 Phaser.js의 기본 엔진인 Arcade는 물리 영역을 다각형으로 조정할 수 있는 기능이 없으며, 다각형 그래픽 객체에 대해 물리 기능을 부여할 수도 없다.

* Arcade 엔진은 이미지의 물리 영역을 사각형과 원형으로만 조정할 수 있다.

대안 도출

이러한 내용을 바탕으로 두 가지 <대안> 을 도출했다.

  1. 게임 엔진 변경: Arcade 엔진을 Matter 엔진으로 변경하여 이미지의 물리 충돌 영역을 다각형으로 조정한다.
  2. 충돌 감지 영역 제작: 작은 사각형 그래픽을 여러 개 생성하여 이미지 크기에 맞게 조합한다.
* Matter 엔진
Phaser의 물리 엔진 중 하나로, Arcade 엔진보다 더 정밀하고 복잡한 물리적 시뮬레이션을 제공하며, 
이미지의 물리 영역을 다각형으로 조정할 수 있다.

대안 결정과 사유

결과적으로 충돌 감지 영역을 직접 제작하기로 결정했다.

Matter 엔진으로 변경하면 이미지의 물리 충돌 영역을 다각형으로 조정할 수 있어 더 세밀하게 영역을 설정할 수 있으며, 엔진이 제공하는 기능으로 안정성도 높아진다.

그러나 해당 물리 충돌 처리는 프로젝트의 후반 작업으로, 이미 구현된 코드와 로직, 스타일을 새로운 엔진에 맞게 변경해야 하는 부담이 있으며, 상대적으로 고성능 엔진 사용에 따른 성능 부담도 고려해야 했기 때문이다.

충돌 감지 영역 제작

결정한 대로 충돌 감지 영역을 직접 제작하는 과정은 다음과 같다.

로직 구상

  1. 콘텐츠별 이미지 형태에 맞게 여러 개의 사각형 존을 조합하여 충돌 감지 영역을 생성할 설정을 준비한다.
  2. 콘텐츠 인스턴스 생성 시, 준비된 충돌 감지 영역을 생성해 콘텐츠 인스턴스에 포함시킨다.
  3. 생성된 충돌 감지 영역을 충돌 시스템에 등록한다.
  4. 충돌 감지 영역은 부모 인스턴스의 이동에 따라 함께 이동하며, 부모 인스턴스가 제거되면 함께 제거된다.
* 충돌 감지 영역(Group 타입)은 총 8개의 사각형 존(Zone 타입)으로 구성했다.

구현 코드

  • 콘텐츠 이미지별 존 위치 설정

    // config.ts
    ...
    
    // 충돌 감지 존 위치 요소
    export const collisionElementConfig = {
      heros: {
        ironman: {
          normal: [
            { x: -5, y: -44, w: 18, h: 12 },
            { x: -10, y: -32, w: 55, h: 12 },
            { x: 0, y: -19, w: 100, h: 14 },
            { x: -2, y: -7, w: 36, h: 10 },
            { x: 10, y: 5, w: 68, h: 14 },
            { x: -5, y: 15, w: 34, h: 6 },
            { x: -28, y: 23, w: 20, h: 10 },
            { x: -35, y: 39, w: 10, h: 22 },
          ],
          repulsor: [
            { x: 10, y: -46, w: 10, h: 6 },
            { x: 12, y: -34, w: 72, h: 18 },
            { x: -2, y: -18, w: 26, h: 14 },
            { x: -10, y: -4, w: 30, h: 14 },
            { x: -11, y: 8, w: 36, h: 10 },
            { x: -15, y: 18, w: 44, h: 10 },
            { x: -37, y: 28, w: 8, h: 10 },
            { x: -44, y: 41, w: 10, h: 16 },
          ],
          ...
    
        },
      },
      ...
    
    };

    사각형 충돌 감지 존의 위치와 크기를 정의하는 설정을 준비한다. 각 이미지마다 8개의 존이 준비된다.

  • 충돌 감지 영역 생성

    // objects/characters/ironman.ts
    ...
    
    // 충돌 감지 영역 생성
    private createCollisionZones() {
      // 그룹 생성
      this.collisionZones = new CollisionZoneGroup(this.scene, {
        classType: CollisionZone,
        runChildUpdate: true,
      });
    
      // 존 위치 요소
      const elements = collisionElementConfig.heros.ironman[this.mode];
    
      // 존 생성
      elements.forEach(({ x, y, w, h }) => {
        const collisionZone = new CollisionZone(
          this.scene,
          this.x + this.displayWidth * (x / 100),
          this.y + this.displayHeight * (y / 100),
          this.displayWidth * (w / 100),
          this.displayHeight * (h / 100)
        );
    
        // 그룹에 추가
        this.collisionZones.add(collisionZone);
      });
    }

    콘텐츠 클래스의 생성자에서 createCollisionZones 메서드를 호출하여, 인스턴스 생성 시 충돌 감지 영역이 함께 생성되고 인스턴스 내부에 포함되도록 한다.

  • 충돌 시스템 등록 핸들러

    // handlers/collisionHandler.ts
    import Phaser from 'phaser';
    import Enemy from '../objects/charaters/enemy';
    import Ironman from '../objects/charaters/ironman';
    ...
    
    export default class CollisionHandler {
      private scene: Phaser.Scene;
    
      constructor(scene: Phaser.Scene) {
        this.scene = scene;
      }
    
      // 아이언맨 피격 감지 핸들러
      public handleHit(
        ironman: Ironman,
        enemy: Enemy | UltronRepulsor,
        callback1: () => void,
        callback2: () => void
      ) {
    
        // 충돌 감지 핸들러 인스턴스 생성
        const colliderHandler = this.scene.physics.add.collider(
          ironman.collisionZones, // 충돌 대상1
          enemy.collisionZones, // 충돌 대상2
          () => callback1(), // 충돌 감지 후 동작
          () => callback2(), // 충돌 감지 조건
          this
        );
    
        // 충돌 핸들러 배열에 추가
        enemy.colliderHandlers.push(colliderHandler);
      }
    }

    충돌 이벤트를 등록하고 처리하는 핸들러는 별도의 클래스에 정의해두었다.

    핸들러의 매개변수로 전달받은 충돌 대상의 collisionZones를 물리 충돌 대상으로 이벤트에 등록한다. 등록된 이벤트는 충돌이 발생해도 제거되지 않고 계속 유지된다.

    생성된 충돌 시스템 인스턴스를 충돌 핸들러 배열에 추가하는 이유는, 충돌 대상이 제거될 때 해당 대상에 관련된 충돌 시스템도 함께 제거하기 위함이다.

    * Collider 클래스
    Phaser에서 제공하는 Collider는 그룹을 충돌 대상으로 등록할 수 있다. 
    등록된 그룹에 포함된 객체들은 자동으로 충돌 감지 대상으로 설정된다.
  • 충돌 시스템 등록

    // managers/characters/enemyManager.ts
    ...
    
    // 빌런 생성
    public create(enemytype: EnemyType) {
      ...
    
      // 빌런 인스턴스 생성
      const enemy = new Enemy(this.scene, 0, 0, imageTexture, enemytype);
    
      ...
    
      // 아이언맨 피격 감지 핸들러 등록
      this.collisionHandler.handleHit(
        this.ironman,
        enemy,
        () => {
          // 아이언맨 모드 변환(피격 모션)
          this.ironmanManager.transform(IronmanMode.HIT);
          // 아이언맨 데미지 처리
          this.healthManager.decrease();
        },
        () =>
          // 충돌 가능 상태 확인
          !this.stateManager.getState(StateName.IS_INVINCIBLE) &&
          !this.stateManager.getState(StateName.IS_BEAM_MODE_ACTIVE) 
      );
      ...
    
    }

    충돌 대상(적 빌런) 생성 시, 충돌 감지 핸들러에 대상으로써 등록한다.

구현 화면

Ironman Collision 물리 충돌 가능 영역 세분화

위 영상과 같이 콘텐츠의 형태에 최대한 맞는 충돌 감지를 구현했다.

물론 이미지 형태를 따라 다각형으로 정의하는 것보다 물리 영역의 정밀함은 다소 떨어지지만, 이미지의 너비와 높이를 전부 커버하는 기존의 물리 영역보다는 시각적으로 훨씬 나은 효과를 얻을 수 있다.

참고 자료

  • [1] Getting Started with Phaser 3
목차