항해 플러스 코육대 slingshot 프로젝트 회고

shleecloud·2023년 10월 3일
0

들어가며

최근 블로그 포스팅이 뜸했다. Canvas와 Three.js 강의를 들으며 다른 플랫폼의 블로그에 글을 연재하느라 바빴다. 매일 올리다보니 Velog는 나중에 올리자 싶었는데 벌써 두 달이 지났다. 생각보다 강의 분량이 많고 내용도 낯설면서 어려운데 매일 이해한 다음 글까지 쓰려니 쉽지 않았다. 그리고 최근에 강의를 다 들어서 무언가 만들고 싶다는 생각이 들던 때 마침 항해99에서 재밌는 이벤트를 진행한다고 했다.

코육대! 코딩 육상 선수권대회의 줄임말이다. 추석 기간에 풀어지고 있지만 말고 개발 근육을 깨워보자! 라는 취지였다. 기왕 연습의 성과를 만드는데 무언가 목표가 있다면 더 좋지 아니한가. 곧바로 참가신청을 했다. 참가상으로 오프라인 네트워크 행사 참여가 있더라. 평소에 항해99에 대해 궁금한 마음도 있었고 상품도 매력적인데.. 이건 못 참지!

근데 추석 연휴에 일정이 꽉꽉 차있어서 마지막 날 오후만 사용할 수 있었다. 이게 너무 아쉽다.
핵심 기능 뿐이지만 만들긴 다 만들었다! 😎

http://shleecloud.github.io/slingshot
https://github.com/shleecloud/slingshot

logo

제 1회 항해 플러스 코육대
https://hanghaeplus-coyukdae.oopy.io/

송편 터트리기! Slingshot

많은 선택지가 있었다. 우선 출전 종목! 계산기 만들기, 행맨 게임 같은 다양한 종류가 있었는데 Canvas를 활용하는 종목을 원했고 물리엔진이 들어가길 원했다. 나는 종목 중 송편 터트리기 라는 슬링샷 게임을 만드는 종목에 참여했다.

다음으로는 Canvas로 구현하느냐, 아니면 다른 물리엔진을 사용하느냐. Canvas로도 수학식으로 계산 할 수도 있지만.. 나는 Matter-js 물리엔진 라이브러리를 메인으로 구현하기로 했다.

근데 이미 몇 주 동안 Three.Js를 위주로 디깅하고 있어서 Matter-js는 거의 처음부터 해야되는 상황이다. 일단 간단한 예제를 찾아봤다. 공식 문서에서 slingshot 예제가 있어서 많은 부분을 참고했다. 하지만 구현이 허술해서 보강하는 작업이 필요했다. 근데 하다보니 계속 아쉽고 보강에 보강을 하다보니 시간이 다 가서 핵심 기능만 겨우 구현됐다.

송편 미션 링크
https://hanghaeplus-coyukdae.oopy.io/f88e0dd1-e4ca-478b-80c1-505d6ca8e428

demo

기능

1. 배경 만들기

보일러 플레이트를 깔았다. Matter-js에서 지원하는 문법대로 따라가면 된다. World라던가 Body, Render, Runner 를 선언하고 할당하는 보일러 플레이트다.

그 다음 플랫폼을 만들고 바닥을 깔고(나중에 제거했다) 더미 송편을 쌓아놨다. 간단하게 코드를 보면 특정 좌표에 원하는 크기를 인자값으로 전달해서 네모를 만들거나 피라미드를 만드는 과정이다. 인자값이 많아보이는데 공식문서와 IDE에서 알려주는대로 차근차근 넣으면 된다. 대부분 좌표나 크기 값이다.

여기서 collisionFilter 옵션이 중요하다. 이걸 설정하지 않은 상태로 두면 후에 기술할 마우스 이벤트를 모든 객체가 받아들이게 된다. 송편을 잡고 흔들 수 있고 이미 던진 돌맹이를 들고 흔들어서 송편을 다 떨어트릴 수 있게 된다. 이를 방지하기 위한 옵션이다. 공식문서는 설명이 부실해서 다른 정리글을 참고했다.

this.platformUp = Matter.Bodies.rectangle(750, 200, 150, this.thickness / 5, {
    isStatic: true,
    label: 'platform',
    collisionFilter: {
        category: staticCategory, // <<<<<<<
    },
});
this.pyramidUp = Matter.Composites.pyramid(662, 270, 6, 4, 0, 0, function (x, y) {
    return Matter.Bodies.trapezoid(x, y, 30, 30, 0.33, {
        label: 'target',
        collisionFilter: {
            category: staticCategory, // <<<<<<<
        },
    });
});

//...
Matter.Composite.add(this.engine.world, [
    // this.ground,
    this.wall,
    this.platformUp,
    this.platformDown,
    this.pyramidUp,
    this.pyramidDown,
]);

2. 마우스 이벤트 충돌 필터

mouseConstraint 객체가 마우스 이벤트를 할당하는데, 여기서 collisionFilter 옵션으로 어떤 물체와 상호작용 할 지 지정해준다. 아무것도 지정하지 않으면 위에서 언급한대로 모든 객체를 잡고 상호작용 할 수 있다.

this.rock = Matter.Bodies.polygon(170, 450, 10, 20, { density: 0.01, label: 'rock' });
this.rock.collisionFilter.category = interactCategory; // <<<<<<<
this.elastic = Matter.Constraint.create({
    pointA: { x: 170, y: 450 },
    bodyB: this.rock,
    stiffness: 0.9,
    damping: 0.1,
    length: 0.5,
});
// * 마우스
this.mouse = Matter.Mouse.create(this.render.canvas);
this.mouseConstraint = Matter.MouseConstraint.create(this.engine, {
    mouse: this.mouse,
    collisionFilter: {
        mask: interactCategory, // <<<<<<<
    },
    constraint: {
        stiffness: 0.2,
    },
    render: {
        visible: false,
    },
});

3. Slingshot! 새총 만들기 🏹

  1. 돌맹이가 공중에 떠있고 마우스로 누르고 드래그를 할 수 있다.
  2. 원래 위치에서 일정 이상 드래그 한 상태로 마우스를 놓으면 enddrag 이벤트 리스너를 활성화한다.
  3. firing 트리거가 활성화 된 상태면 새로운 rock을 만들어 elastic, 스프링 새총에 할당한다.
    afterUpdate 이벤트 리스너는 매 프레임단위로 콜백 함수를 실행한다.
  4. 기존에 당겨졌던 rock은 elastic에서 벗어나 곧바로 받는 탄성대로 튀어나간다.
let firing = false;
Matter.Events.on(this.mouseConstraint, 'enddrag', function (event) {
    if (event.body.label !== 'rock') return;
    event.body.label = 'released rock';
    firing = true;
    event.body.collisionFilter.category = staticCategory;
});

Matter.Events.on(this.engine, 'afterUpdate', () => {
    if (firing) {
        this.rock = Matter.Bodies.polygon(170, 450, 10, 20, {
            density: 0.01,
            label: 'rock',
            collisionFilter: { category: interactCategory },
        });
        Matter.Composite.add(this.engine.world, this.rock);
        this.elastic.bodyB = this.rock;
        firing = false;
    }
});

아쉬운 점

시간이 부족해서 구현하지 못한 부분이 많다. 어떻게 반나절만에 만드려고 했을까? 😅

1. 게임의 프로세스가 없다

원래 게임의 단계가 첫 화면은 새 게임, 이어하기, 설정 버튼이 있고
새 게임 -> 송편 쌓기 -> 슬링샷 게임 -> 결과 화면
결과 화면은 현재 기록, 최고 기록 보여주고 다시하기 또는 처음으로 버튼
이전에 송편을 쌓았다면 이어하기 버튼이 활성화된다.

2. 이미지가 없다

  • 원래 새총 당기는 부분 아래에 새총 이미지를 배치해서 새총 느낌이 나게 하고 싶었다.
  • 컵처럼 생긴 타겟도 타원형으로 바꿔서 송편 이미지로 맵핑하려고 했다. 정사각형이 아니라 사다리꼴인 것도 그 이유다.

3. 최적화가 없다

객체가 화면 밖으로 사라지면 객체를 지워야 되는데 지금은 그 후처리가 안되어있다. 한 번 떨어지면 그 객체는 영원히 바닥으로 수직낙하한다. 그 외에 움직임을 멈춘 객체를 sleep 상태로 바꾸면 계산 효율을 더 높일 수 있다.

4. 불친절한 공식문서

마이너한 라이브러리라서 공식문서가 아쉽다. 공식문서인데 대략적인 내용만 설명하고 example 링크만 띡 있고 누르면 소스코드로 날려보낸다. 코드에서 필요한 인자값을 확인하고 그에 맞게 사용해야 된다. 아니면 무한 구글링이다. 심지어 어떤 인자값은 미리 지정한 enum 문자열을 받는데 그 목록에 대한 가이드라인도 없었다.

마치며

정신없는 하루였다. 연휴 마지막을 이렇게 바쁘게 보내다니. 시간이 촉박하니까 잠들어있던 집중력을 더 끌어냈다. 애초에 참여 취지였던 개발 근육은 확실하게 길러졌다. 이런 재밌는 프로그램을 준비하다니. 항해99! 깜찍하구만! 오프라인 네트워킹도 시간이 되면 참여하고 싶다. 나만의 작은 해커톤을 한 기분이 든다.

그동안 열심히 갈고 닦은 기술도 사용할 수 있어서 좋았다. 사실 강의 듣느라 실전은 거의 해보질 못했는데 이 정도로 만들 수 있었다니 매우 만족스럽다. 정신없이 한 만큼 완벽할 수는 없었지만 이런 Canvas 프로젝트에서 어느정도 개발 공수가 들어가는지 실제로 경험할 수 있어서 좋았다. 오프라인 행사에 다녀오면 후기글을 또 남겨볼까!

profile
블로그 옮겼습니다. https://shlee.cloud

0개의 댓글