웹개발 자료실/three.js 프론트개발 Code

「three.js」클릭으로 풍선 제거하기

Recstasy 2023. 1. 13. 14:42

 

 

 

 

Raycaster()를 이용해 3D 오브젝트를 생성&제어도 중요하지만 제거하는 일 역시 중요하다. CRUD(Create, Read, Update, Delete)는 모든 웹앱의 기본이다. 이번 포스팅에서는 Raycaster()를 이용한 간단한 게임을 제작해보자.

 

 

 


| 구상

1) 랜덤으로 만들어진 풍선(sphere)들이 y축으로 올라간다.
2) 사용자가 풍선을 클릭한다.
3) 풍선들이 사라진다.

4) y값이 50보다 큰 풍선은 사라진다. 

5) 없어진 풍선이 5개가 넘어가면 게임은 종료된다.

 

 

 

 


| 기본 구조

scene, camera, light, renderer, Loop가 담긴 기본 구조를 생성한다. 

스토리는 대략 다음과 같다.

 

1. balloon Class생성 : balloon인스턴스 생성

2. balloon Class 메서드 : 풍선의 동작 제어(y축), 제거될 풍선인지 판단

3. mainLoop 구문 생성 : 풍선이 계속 생성됨

4. mouse클릭 이벤트 : 마우스의 위치와 풍선의 좌표가 겹치면 해당 풍선은 삭제 됨

 

let scene, camera, renderer, rayCast, control, mouse, light;

let life = 5;  //풍선 5개 이상 제거시 게임 종료
let flag = false; //게임 시작과 종료 결정
let balloons = [];
let ADD = 0.01;
let ele = document.getElementById('wd-balloon');

document.getElementById('wd-start-btn')
    .addEventListener( 'click', (e) => {
        flag = true;
        e.target.remove();

        init()
        mainLoop()
    });
    
    
 let init = function () {

    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x000000 );

    camera = new THREE.PerspectiveCamera( 75, ele.clientWidth / ele.clientHeight, 0.01, 1000 );
    camera.position.set( 0, 80, 50 );

    let grid = new THREE.GridHelper( 100, 20 );
    scene.add( grid );

    light = new THREE.DirectionalLight( 0xffffff, 1 );
    scene.add( light );

    rayCast = new THREE.Raycaster();

    mouse = new THREE.Vector2();
    mouse.x = mouse.y = -1;

    renderer = new THREE.WebGLRenderer();
    renderer.setSize( ele.clientWidth, ele.clientHeight );

    ele.appendChild( renderer.domElement );
    control = new OrbitControls( camera, renderer.domElement );
        
    renderer.domElement.addEventListener( 'click', onMouseClick, false );
}

 

 

 

 

 


| Balloon Class

Balloon 클래스는 sphere를 생성하는 코드와 sphere의 움직임을 제어한다.

 

class Balloon {

    constructor() 
    {
        
        let x = randomRange( -30, 30 );
        let z = randomRange( 20, -20 );

        let geometry = new THREE.SphereGeometry( 3, 30, 30 );
        let material = new THREE.MeshPhongMaterial({
            color: Math.random() * 0xffffff,
            shininess: 100
        });

        let b = new THREE.Mesh( geometry, material );
        b.position.set( x, 0, z );

        this.object = b;
        scene.add( b );

        this.ADD = randomRange( 0.05, 0.15 );
        this.over = false;
        this.TOP = 50;
            
    } //constructor End
    
    advance() {

        this.object.position.y += this.ADD;
    
        if ( this.object.position.y > this.TOP ) 
        {
            this.over = true;
        }
    } //advance() End        
}

let randomRange = function ( from, to ) {

    let x = ( to - from ) * Math.random();
    return x + from;

}

 

 

Balloon.advance()는 풍선을 하늘로 계속 올린다. position.y 값에 this.ADD를 계속 더하기 때문이다. 단, this.TOP값이 50이므로 position.y가 50이 되면 'this.over'가 true로 바뀐다. Ballon인스턴스는 계속 balloons배열에 추가되는데, this.over = true가 5개 이상 된다면 게임은 종료된다.

 

 

 

 


| 루프 구문

루프 구문에서 중요한 부분은 풍선이 생성되는 속도를 제어하는 Math.random()값이다. 아래 코드와 같이 rand값이 0.03이하일 때만 balloons배열에 new Balloon()인스턴스가 추가된다.

 

let mainLoop = function () {

    if ( !flag ){
        gameOver();
        console.log('게임 오버');
    }
    else
    {
        if( life <= 0 )
        {
            flag = false;
        }

        let rand = Math.random();

        if (rand < 0.03) 
        {
            balloons.push( new Balloon() );
        }

        balloons.forEach(( b, idx ) => {

                b.advance();

                if ( b.over ) 
                {
                    life =- 1;
                    scene.remove( b.object );
                }
        })

        requestAnimationFrame( mainLoop );
        renderer.render( scene, camera );
     }     

}

 

 

 

 

 


| 마우스 이벤트

let onMouseClick = function (e) {

    let gapX = e.clientX - e.offsetX;
    let gapY = e.clientY - e.offsetY;

    mouse.x = ((e.clientX - gapX) / (ele.clientWidth)) * 2 - 1;
    mouse.y = -((e.clientY - gapY) / (ele.clientHeight)) * 2 + 1;

    rayCast.setFromCamera( mouse, camera );

    let intersects = rayCast.intersectObjects( scene.children );

    if ( intersects.length == 0 ) return;

    let hit = intersects[0].object;
        
    balloons.forEach( ( b, idx ) => {
        if ( b.object == hit ) {
            scene.remove( b.object )
        }
    });
}

 

마우스 이벤트는 생성된 오브젝트(balloons) 배열에서 사용자가 클릭한 오브젝트를 찾아서 제거한다. 화면에서 Object3D를 제거하는 방법은 간단하다. 'scene.remove()'메서드를 이용하면 된다.

 

 

위의 이벤트 함수를 전체 코드에 넣으면, 간단한 클릭 게임이 완성된다. 만일 풍선이 아닌 특정 오브젝트(캐릭터)와 독특한 로직(클릭시 점수 2점 깎임)이 추가된다면 좀더 완성도 높은 게임을 제작할 수 있다.