본문 바로가기
프로젝트/탄막 피하기 멀티게임 (웹)

Node.js와 Socket.io를 이용한 멀티플레이 탄막피하기 게임 개발기 (2) | 21-05-10

by Godgil 2021. 5. 10.

지난 글 

[프로젝트/학교 수업] - Node.js와 Socket.io를 이용한 멀티플레이 탄막피하기 게임 개발기 (1) | 21-05-06

 

다음 글

[프로젝트/학교 수업] - Node.js와 Socket.io를 이용한 멀티플레이 탄막피하기 게임 개발기 (3)

 

개발 상황

이전 진행상황은 클라이언트가 게임에 접속한 뒤, 키보드 방향키를 통해 공을 움직이면 그 공이 socket.io서버를 통해 연동이 되어 실시간으로 플레이어의 위치가 전송되는 것 까지 개발했다. 학기중이라 바빠서 이것만 할 수는 없어 진행이 조금 느리다.

 

현재까지 변경된 점은 크게 세 가지 이다.

첫째, 적들의 생성

둘째, 적들의 이동

셋째, 적들과의 충돌판정

 

변경된점에 맞춰 코드를 올리고 코드 분석을 해 보려 한다.

마찬가지로 짧은 기간동안 javascript를 배우고 코딩을 한 거라 코드의 질이 좀 떨어질 수 있다. 이는 차차 개선해 나갈 생각이다.

 

플레이 영상

 

서버 사이드

전체 코드

//server.js
const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);


server.listen(8000, () => {
    console.log("서버가 대기중입니다.");
})

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/views/index.html')

})

function getPlayerColor(){
    return "#" + Math.floor(Math.random() * 16777215).toString(16);
}

const canvasWidth = 1024;
const canvasHeight = 768;

const startX = canvasWidth/2;
const startY = canvasHeight/2;

var enemyRadius = 10;

class PlayerBall{
    constructor(socket){
        this.socket = socket;
        this.x = startX;
        this.y = startY;
        this.color = getPlayerColor();
        this.state = 1;
    }

    get id() {
        return this.socket.id;
    }
}


var balls = [];
var ballMap = {};

function joinGame(socket){
    let ball = new PlayerBall(socket);

    balls.push(ball);
    ballMap[socket.id] = ball;

    return ball;
}

function endGame(socket){
    for( var i = 0 ; i < balls.length; i++){
        if(balls[i].id == socket.id){
            balls.splice(i,1);
            break
        }
    }
    delete ballMap[socket.id];
}

let enemyGenerator;


io.on('connection', function(socket) {
    console.log(`${socket.id}님이 입장하셨습니다.`);

    socket.on('disconnect', function(reason){
        console.log(`${socket.id}님이 ${reason}의 이유로 퇴장하셨습니다. `)
        endGame(socket);
        io.sockets.emit('leave_user', socket.id);
        if(balls.length == 0){
            clearInterval(enemyGenerator)
        }
    });

    let newBall = joinGame(socket);

    socket.emit('user_id', socket.id);
 
    for (var i = 0 ; i < balls.length; i++){
        let ball = balls[i];
        socket.emit('join_user', {
            id: ball.id,
            x: ball.x,
            y: ball.y,
            color: ball.color,
        });
    }

    socket.broadcast.emit('join_user',{
        id: socket.id,
        x: newBall.x,
        y: newBall.y,
        color: newBall.color,
    });

    socket.on('send_location', function(data) {
            socket.broadcast.emit('update_state', {
                id: data.id,
                x: data.x,
                y: data.y,
            })
    })

    let host = balls[0].id;
    socket.on('start', function(data){
        if(host == data.id){
            enemyGenerator = setInterval(function() {
                if(balls.length){ 
                    var decideWall = Math.floor(Math.random()*4);
                    if( decideWall == 0){
                        var randomStartY = Math.floor(Math.random() * 768)
                        var randomDestinationY = Math.floor(Math.random() * 768)
                        io.sockets.emit('enemy_generator', {
                            wall : 0,
                            startingX:  enemyRadius,
                            startingY:  randomStartY,
                            destinationX:  canvasWidth+enemyRadius,
                            destinationY: randomDestinationY,
                        })
                    }
                    else if( decideWall == 1){
                        var randomStartY = Math.floor(Math.random() * 768)
                        var randomDestinationY = Math.floor(Math.random() * 768)
                        io.sockets.emit('enemy_generator', {
                            wall : 1,
                            startingX:  canvasWidth+enemyRadius,
                            startingY:  randomStartY,
                            destinationX:  enemyRadius,
                            destinationY: randomDestinationY,
                        })
                    }
                    else if( decideWall == 2){
                        var randomStartX = Math.floor(Math.random() * 1024);
                        var randomDestinationX = Math.floor(Math.random() * 1024);
                        io.sockets.emit('enemy_generator', {
                            wall : 2,
                            startingX:  randomStartX,
                            startingY:  enemyRadius,
                            destinationX:  randomDestinationX,
                            destinationY: canvasHeight+enemyRadius,
                        })
                    }
                    else if( decideWall == 3){
                        var randomStartX = Math.floor(Math.random() * 1024);
                        var randomDestinationX = Math.floor(Math.random() * 1024);
                        io.sockets.emit('enemy_generator', {
                            wall : 3,
                            startingX:  randomStartX,
                            startingY:  canvasHeight+enemyRadius,
                            destinationX:  randomDestinationX,
                            destinationY: enemyRadius,
                        })
                    }
                }
                }, 1000)

        }
        
    })


    socket.on('collision_detect', function(data){
        for( var i = 0 ; i < balls.length; i++){
            if(balls[i].id == data.id){
                balls[i].state = 0;
                break;
            }
        }
        socket.broadcast.emit('collision_update', {id : data.id})
    })

})

앞 포스트에서 설명한 부분은 최대한 제외하고, 변경된 부분만 설명하려한다. 

더보기

먼저 express를 통해 html파일을 읽어오도록 바꾸었다. 

그러기 위해서는 express를 먼저 다운받아야 한다.

npm install express

터미널에 위 코드를 입력해준다. 

저번과 달리 express를 사용하므로 당연히 서버를 여는 코드도 바뀌게 된다.

const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);


server.listen(8000, () => {
    console.log("서버가 대기중입니다.");
})

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/views/index.html')

})

 크게 바뀐 부분은 없지만, 아직 index.html파일을 읽어오지 못했을때의 에러처리를 하지 못했다. 이 부분을 다음에는 신경써서 고쳐보려고 한다.

 

아래 코드는 적들을 생성해주는 역할을 한다. 복잡해 보이지만 반복이 많아서 어렵진 않다.

    let host = balls[0].id;
    socket.on('start', function(data){
        if(host == data.id){
            enemyGenrator = setInterval(function() {
                if(balls.length){ 
                    var decideWall = Math.floor(Math.random()*4);
                    if( decideWall == 0){
                        var randomStartY = Math.floor(Math.random() * 768)
                        var randomDestinationY = Math.floor(Math.random() * 768)
                        io.sockets.emit('enemy_generator', {
                            wall : 0,
                            startingX:  enemyRadius,
                            startingY:  randomStartY,
                            destinationX:  canvasWidth+enemyRadius,
                            destinationY: randomDestinationY,
                        })
                    }
                    else if( decideWall == 1){
                        var randomStartY = Math.floor(Math.random() * 768)
                        var randomDestinationY = Math.floor(Math.random() * 768)
                        io.sockets.emit('enemy_generator', {
                            wall : 1,
                            startingX:  canvasWidth+enemyRadius,
                            startingY:  randomStartY,
                            destinationX:  enemyRadius,
                            destinationY: randomDestinationY,
                        })
                    }
                    else if( decideWall == 2){
                        var randomStartX = Math.floor(Math.random() * 1024);
                        var randomDestinationX = Math.floor(Math.random() * 1024);
                        io.sockets.emit('enemy_generator', {
                            wall : 2,
                            startingX:  randomStartX,
                            startingY:  enemyRadius,
                            destinationX:  randomDestinationX,
                            destinationY: canvasHeight+enemyRadius,
                        })
                    }
                    else if( decideWall == 3){
                        var randomStartX = Math.floor(Math.random() * 1024);
                        var randomDestinationX = Math.floor(Math.random() * 1024);
                        io.sockets.emit('enemy_generator', {
                            wall : 3,
                            startingX:  randomStartX,
                            startingY:  canvasHeight+enemyRadius,
                            destinationX:  randomDestinationX,
                            destinationY: enemyRadius,
                        })
                    }
                }
                }, 1000)

        }
        
    })

클라이언트들이 접속했을때 호스트가 start 버튼을 누르는 즉시 적들이 생성되게 하기 위해, 'start'이벤트를 통해 클라이언트의 아이디를 받아왔다. 우리 게임에서 호스트는 플레이어 볼 배열의 0번째로 제일 처음 접속한 클라이언트가 된다. 여기서 socket.on('start')를 통해 받아오는 데이터는 start를 눌린 클라이언트의 socket ID이다. 아래 조건문을 통해 data.id와 host의 ID를 비교한 뒤 같을 경우 적들을 생성해준다.

적들이 생성되기 전에  decideWall변수를 통해 어느 벽에서 나올지가 결정이 된다. 0,1,2,3의 값으로 각각 왼쪽벽 오른쪽벽, 위쪽벽, 아래쪽 벽이다. 이렇게 벽을 먼저 정해주고 우리 게임의 canvas의 사이즈와 각각 벽의 조건에 맞게 random값으로 적의 생성 위치와 목적지를 생성해준다.

 

일단 지금까지의 개발단계에서 적들은 왼쪽벽에서 나오는 경우 오른쪽벽 방향으로만, 오른쪽 벽에서 나오는 경우 왼쪽 벽 방향으로만, 위쪽의 경우 아래방향, 아래쪽벽의 경우 윗방향으로만 움직인다.

 

이렇게 적들의 위치가 선정되면, 벽과 적의 위치 정보를 연결된 모든 클라이언트에게 보내준다. 연결된 모든 클라이언트에게 보내는 함수는 io.sockets.emit('이벤트', 데이터)이다.

 

적이 한번만 만들어지면 안되니까, 위 전체 과정을 setInterval함수를 통해 1000ms에 한번씩 반복하도록 진행한다.

즉, 적들은 1초에 한번씩 생성이 되어 움직이게 된다.

생성되는 주기를 바꾸게 되면 난이도 조절이 가능하다.

 

setInterval함수를 사용했으니, 사용이 끝나면 멈춰주어야한다. 따라서 이 조건은 disconnect 이벤트에 넣어놨다.

조건문이 추가되었다. 

    socket.on('disconnect', function(reason){
        console.log(`${socket.id}님이 ${reason}의 이유로 퇴장하셨습니다. `)
        endGame(socket);
        io.sockets.emit('leave_user', socket.id);
        if(balls.length == 0){
            clearInterval(enemyGenrator)
        }
    });

 만약에, balls 배열에 아무것도 들어있지 않은 경우 enemyGenrator를 clearinterval을 통해 멈춰준다.

 

서버사이드의 마지막으로 충돌판정을 받았을 때, 해당하는 플레이어를 없애주는 부분이다.

    socket.on('collision_detect', function(data){
        for( var i = 0 ; i < balls.length; i++){
            if(balls[i].id == data.id){
                balls[i].state = 0;
                break;
            }
        }
        socket.broadcast.emit('collision_update', {id : data.id})
    })

 이는 collision_detect 이벤트를 통해 진행된다. 여기서 받는 데이터는 플레이어가 적과 접촉되었을 때, 해당하는 플레이어의 socket id이다. 따라서 balls 배열에서 반복문을 돌면서 같은 id를 찾고, 이후에 state를 0으로 바꾸어준다. 이후에 연결된 다른 플레이어들에게도 이 플레이어가 충돌했다는 사실을 전달해야 하므로 socket.broadcast.emit('이벤트', 데이터)을 통해 전달한다. 물론 이때의 데이터는 충돌된 플레이어의 socket ID이다.

 

 

>>>혹시나 이해 안되는 부분이나 부족한 부분이 있으면 코멘트 남겨주시면 감사하겠습니다.

 

클라이언트 사이드

전체 코드

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Professor VS Student</title>
    <style>
        * {padding : 0; margin: 0;}
        canvas {background: #eee; display: block; margin : 0 auto;}
    </style>
    
    <script src="/socket.io/socket.io.js"></script>

</head>
<body>
    <div style="text-align: center;">
        <font size="10em" color="green">
            Professor VS Student
        </font>
   </div>

    <canvas id = "myCanvas" width ="1024" height = "768"></canvas>
    <script>
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext("2d");
        var radius = 16
        var playerSpeed = 4

        function PlayerBall(id){
            this.id = id;
            this.color = "#FF00FF";
            this.x = 1024/2;
            this.y = 768/2;
            this.state = 1;
        }
        
        var balls  = [];
        var ballMap = {};
        var myId;

        var rightPressed = false;
        var leftPressed = false;
        var upPressed = false;
        var downPressed = false;

        document.addEventListener("keydown", keyDownHandler,false);
        document.addEventListener("keyup", keyUpHandler,false);

        function keyDownHandler(e){
            if (e.code == 'ArrowRight'){
                rightPressed = true;
            }
            if (e.code == 'ArrowLeft'){
                leftPressed = true;
            }
            if(e.code == "ArrowDown"){
                downPressed = true;
            }
            if(e.code == "ArrowUp"){
                upPressed = true;
            }
        }

        function keyUpHandler(e){
            if (e.code == "ArrowRight"){
                rightPressed = false;
            }
            if (e.code == "ArrowLeft"){
                leftPressed = false;
            }
            if(e.code == "ArrowDown"){
                downPressed = false;
            }
            if(e.code == "ArrowUp"){
                upPressed = false;
            }
        }

        function joinUser(id,color,x,y){
            let ball = new PlayerBall(id);
            ball.color = color;
            ball.x = x;
            ball.y = y;

            balls.push(ball);
            ballMap[id] = ball;

            return ball;
        }

        function leaveUser(id){
            for(var i = 0 ; i < balls.length; i++){
                if(balls[i].id == id){
                    balls.splice(i,1);
                    break;
                }
            }
            delete ballMap[id];
        }

        function updateState(id,x,y){
            let ball = ballMap[id];
            if(!ball){
                return;
            }
            ball.x = x;
            ball.y = y;

        }
        function sendData() {
            let curPlayer = ballMap[myId];
            let data = {};
            data = {
                id : curPlayer.id,
                x: curPlayer.x,
                y: curPlayer.y,
            };
            if(data){
                socket.emit("send_location", data);
            }
        }
        
        function collisionDetection(){
            let ball = ballMap[myId]
            for(var i = 0; i < enemys.length ; i++){
                if(  Math.sqrt((ball.x - enemys[i].startingX)**2 + (ball.y - enemys[i].startingY)**2) <= enemyRadius + radius){
                    ball.state = 0;
                    socket.emit('collision_detect', {id : myId});
                    break;
                }
            }
        }
        

        
        var socket = io();

        socket.on('user_id', function(data){
            myId = data;
        });
        socket.on('join_user', function(data){
            joinUser(data.id, data.color, data.x, data.y);
        })
        socket.on('leave_user', function(data){
            leaveUser(data);
        })
        socket.on('update_state', function(data){
            updateState(data.id, data.x, data.y);
        })
        
        socket.on('collision_update', function(data){
            for( var i = 0 ; i < balls.length; i++){
            if(balls[i].id == data.id){
                balls[i].state = 0;
                break;
            }
        }
        })

        
        var enemys = [];
        var enemyRadius = 10;
        var enemyAliveTime = 400;
        
        function EnemyBall(id){
            this.id = id;
            this.color = "#000000";
            this.startingX = 1024/2;
            this.startingY = 768/2;
            this.destinationX = 0;
            this.destinationY = 0;
            this.initialX = 0;
            this.initialY = 0;
            this.wall = 0;
        }


        socket.on('enemy_generator', function(data){
            let enemy = new EnemyBall()
            enemy.startingX = data.startingX;
            enemy.startingY = data.startingY;
            enemy.initialX = data.startingX;
            enemy.initialY = data.startingY;
            enemy.destinationX = data.destinationX;
            enemy.destinationY = data.destinationY;
            enemy.wall = data.wall;
            enemys.push(enemy);
            console.log(enemys);
        })

        function renderGame() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                collisionDetection();
                for (let i = 0; i < balls.length; i++) {
                    let ball = balls[i];
                    if (ball.state == 0){
                        continue
                    }
                    ctx.fillStyle = ball.color;
                    ctx.beginPath();
                    ctx.arc(ball.x, ball.y, radius, 0, Math.PI * 2, false);
                    ctx.closePath();
                    ctx.fill();
                    
                    ctx.beginPath();
                    ctx.font = '15px Arial';
                    ctx.fillText(`player ${i}`,ball.x-radius-7, ball.y-radius);
                    ctx.closePath();
                }

                for (let j = 0; j < enemys.length; j++){
                    let enemy = enemys[j];

                    ctx.fillStyle = enemy.color;
                    ctx.beginPath();
                    ctx.arc(enemy.startingX, enemy.startingY, enemyRadius, 0, Math.PI *2, false);
                    ctx.closePath();
                    ctx.fill();
                }

                // 적 위치 옮겨주는 코드
                for ( let k = 0 ; k < enemys.length; k++){
                    let enemy = enemys[k];
                    if(enemy.wall == 0){//leftSide
                        if (enemy.destinationY >= enemy.startingY){
                            var distanceX = enemy.destinationX - enemy.initialX;
                            var distanceY = enemy.destinationY - enemy.initialY;
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = enemy.destinationX - enemy.initialX;
                            var distanceY = enemy.initialY - enemy.destinationY;
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    else if (enemy.wall == 1){
                        if (enemy.destinationY >= enemy.startingY){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    else if (enemy.wall == 2){
                        if (enemy.destinationX >= enemy.startingX){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY += speedY;
                        }
                    }
                    else if (enemy.wall == 3){
                        if (enemy.destinationX >= enemy.startingX){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY -= speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    
                    if (enemy.startingX < -100 || enemy.startingX > 1400 || enemy.startingY < -100 || enemy.startingY > 1400){
                        enemys.splice(k,1);
                    }
                }

                // 플레이어 옮겨주는 코드
                let curPlayer = ballMap[myId];
                if (rightPressed){
                    curPlayer.x += playerSpeed;
                }
                if (leftPressed ){
                    curPlayer.x -= playerSpeed;
                }
                if(upPressed ){
                    curPlayer.y -= playerSpeed;
                }
                if(downPressed ){
                    curPlayer.y += playerSpeed;
                }

                sendData();
            }
        
        var isStart = false;
      
        function update() {
            renderGame();
        }

        setInterval(update, 10);



        function start(){
            isStart = true;
            socket.emit('start', { id: myId});
        }

        socket.on('start_game', function(){
            isStart = true;
        })
    </script>
    <script>

    </script>
    <div style="text-align: center;">
        <button style= "width: 150px; height: 50px;" id="my_btn" onclick="start()";><font size ="5em">Game Start</font></button>
    </div>
    <div style="text-align: center;">
        <font size="5em" color="green">
            <span>키보드 방향키를 통해 움직이세요. </span>
            <p>Player0가 Host입니다. Host가 시작버튼을 눌러야 게임이 시작됩니다.</p> 
        </font>
         
    </div>
</body>
</html>

마찬가지로 변경된 부분만 설명할 것이다.

더보기
        function PlayerBall(id){
            this.id = id;
            this.color = "#FF00FF";
            this.x = 1024/2;
            this.y = 768/2;
            this.state = 1;
        }

먼저 플레이어볼에 state라는 상태값이 추가되었다. 1이 기본값이고 1인경우 살아있음, 적과 충돌했을 경우 0이되어 더이상 화면에 나타나지 않는다.

 

        function collisionDetection(){
            let ball = ballMap[myId]
            for(var i = 0; i < enemys.length ; i++){
                if(  Math.sqrt((ball.x - enemys[i].startingX)**2 + (ball.y - enemys[i].startingY)**2) <= enemyRadius + radius){
                    ball.state = 0;
                    socket.emit('collision_detect', {id : myId});
                    break;
                }
            }
        }

이 부분은 충돌판정 부분이다.

우리 게임은 플레이어도 원이고 적들도 원이다. 따라서 간단하게 충돌판정이 되는데, 플레이어 볼의 반지름과 적 볼의 반지름의 합이 플레이어의 현재 위치와 적의 현재 위치사이의 거리보다 큰 경우 충돌이 일어났다고 볼 수 있다. 따라서 적들 전체에서 반복문을 돌면서 하나하나 다 확인해 준다.

이때, 적들이 1초마다 계속 생성이 되는데, 화면을 넘어간 부분까지 계속해서 충돌판정을 해 주게 되면 불필요한 오버헤드가 생긴다. 따라서 화면을 넘어간 적들을 지워줘야한다.

 

이는 renderGame()함수 내부에서 처리 할 수 있다.

 function renderGame() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                collisionDetection();
                for (let i = 0; i < balls.length; i++) {
                    let ball = balls[i];
                    if (ball.state == 0){
                        continue
                    }
                    ctx.fillStyle = ball.color;
                    ctx.beginPath();
                    ctx.arc(ball.x, ball.y, radius, 0, Math.PI * 2, false);
                    ctx.closePath();
                    ctx.fill();
                    
                    ctx.beginPath();
                    ctx.font = '15px Arial';
                    ctx.fillText(`player ${i}`,ball.x-radius-7, ball.y-radius);
                    ctx.closePath();
                }

                for (let j = 0; j < enemys.length; j++){
                    let enemy = enemys[j];

                    ctx.fillStyle = enemy.color;
                    ctx.beginPath();
                    ctx.arc(enemy.startingX, enemy.startingY, enemyRadius, 0, Math.PI *2, false);
                    ctx.closePath();
                    ctx.fill();
                }

                // 적 위치 옮겨주는 코드
                for ( let k = 0 ; k < enemys.length; k++){
                    let enemy = enemys[k];
                    if(enemy.wall == 0){//leftSide
                        if (enemy.destinationY >= enemy.startingY){
                            var distanceX = enemy.destinationX - enemy.initialX;
                            var distanceY = enemy.destinationY - enemy.initialY;
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = enemy.destinationX - enemy.initialX;
                            var distanceY = enemy.initialY - enemy.destinationY;
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    else if (enemy.wall == 1){
                        if (enemy.destinationY >= enemy.startingY){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    else if (enemy.wall == 2){
                        if (enemy.destinationX >= enemy.startingX){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY += speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY += speedY;
                        }
                    }
                    else if (enemy.wall == 3){
                        if (enemy.destinationX >= enemy.startingX){
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.destinationY - enemy.initialY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX += speedX;
                            enemy.startingY -= speedY;
                        }
                        else{
                            var distanceX = Math.abs(enemy.destinationX - enemy.initialX);
                            var distanceY = Math.abs(enemy.initialY - enemy.destinationY);
                            var speedY = distanceY/enemyAliveTime;
                            var speedX = distanceX/enemyAliveTime;
                            enemy.startingX -= speedX;
                            enemy.startingY -= speedY;
                        }
                    }
                    
                    if (enemy.startingX < -100 || enemy.startingX > 1400 || enemy.startingY < -100 || enemy.startingY > 1400){
                        enemys.splice(k,1);
                    }
                }

                // 플레이어 옮겨주는 코드
                let curPlayer = ballMap[myId];
                if (rightPressed){
                    curPlayer.x += playerSpeed;
                }
                if (leftPressed ){
                    curPlayer.x -= playerSpeed;
                }
                if(upPressed ){
                    curPlayer.y -= playerSpeed;
                }
                if(downPressed ){
                    curPlayer.y += playerSpeed;
                }

                sendData();
            }
        

 두번째 반복문을 통해 적들을 그려주고, 그 바로 밑에 세번째 반복문을 통해 적들의 위치를 옮겨준다. 위치를 옮길때는 방향을 잘 생각해서 적의 현재 위치를 잘 더하고 빼주어야 정상적으로 이동한다. 

이 게임에서는 enemyAliveTime이라는 변수를 통해 속도를 조절한다. 서버에서 받아온 데이터에서 초기 위치와 목적지의 위치가 나오기 때문에 거리를 구할 수 있고, enemyAliveTime이라는 변수를 통해 속도를 조절할 수 있다.

조건문을 통해 적들의 위치를 옮겨주면서 조건문을 통해 적의 x위치가 -100 이하이거나 1400이상이거나 적의 y위치가 -100이하이거나 1000이상인 경우 적을 그냥 적 배열에서 지워버린다. 이렇게 하면 오버헤드가 생기지 않는다.

또,  state가 0인경우 더이상 플레이어를 그리지 않는 조건을 추가해주었다. 이는 플레이어를 그리는 첫번째 조건문에서 state가 0인 경우 continue를 통해 구현했다.

 

        socket.on('enemy_generator', function(data){
            let enemy = new EnemyBall()
            enemy.startingX = data.startingX;
            enemy.startingY = data.startingY;
            enemy.initialX = data.startingX;
            enemy.initialY = data.startingY;
            enemy.destinationX = data.destinationX;
            enemy.destinationY = data.destinationY;
            enemy.wall = data.wall;
            enemys.push(enemy);
            console.log(enemys);
        })

 서버에서 적들을 생성했을때 받아오는 부분이다. 초기 위치는 initialX, initialY로 저장이 되고 적들의 현재 움직이고 있는 위치는 startingX, startingY로 표현이 된다.

 

        socket.on('collision_update', function(data){
            for( var i = 0 ; i < balls.length; i++){
            if(balls[i].id == data.id){
                balls[i].state = 0;
                break;
            }
        }
        })

 어떤 클라이언트가 적과 충돌하고 서버에 이벤트를 보낸 뒤, 서버에서 그 사실을 나머지 클라이언트에게 전달할 때 사용되는 함수이다. 해당 플레이어의 socket ID가 데이터로 전송이되어 왔으므로, 해당하는 플레이어를 찾아서 state를 0으로 바꿔준다.

 

 

>>> 혹시 부족한 부분이나 이해 안가는 코드 있으시면 코멘트 남겨주세요!! 

 

다음에 할 것

-스테이지 구현

-아이템 구현

 

마무리

--혹시 포스팅 보시다가 궁금한 부분 있으면 댓글 남겨 주시면 답변해 드리겠습니다.

 

--짧은 기간에 여러 블로그들과 공식문서를 보면서 코드를 구현해서, 코드 스타일이 정상적이지 않을 수도 있습니다. 혹시 비정상적인 코드가 있으면 피드백 주시면 감사하겠습니다. 

 

-- 전체 코드는 제 Github에서 보실 수 있습니다. 

댓글