HTML5GAME :: HTML5GAME

달력

92025  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

10 년 전에 출시 된 이전 게임 인 Mass Attack이라는 프로토 타입을 만들려고했습니다. 게시물 Mass Attack과 같은 플래시 게임을 만들고 Tween을 사용하여 Mass Attack과 같은 HTML5 게임을 만듭니다 

필자는 Phaser CE v2.8.7 및 Phaser v3.0.0을 사용하여 프로토 타입을 다시 작성했으며 이는 아마도 Phaser 3 작동 첫 번째 프로토 타입 일 것입니다.


2.8.7 버전부터 시작해 보겠습니다.


클릭하여 길게 누르면 구가 생기고, 놓으면 구가 튀어 나오고 그에 따라 움직입니다.


소스 코드는 아직 댓글을 달지 않았습니다. 특히 시리즈의 다른 게시물을 읽는다면 매우 정직합니다.

var game;

 

var gameOptions = {

    maxDiameter: 50,

    ballGrowingSpeed: 0.5,

    balanceFriction: 400

}

 

window.onload = function(){

    game = new Phaser.Game(320, 480, Phaser.CANVAS);

    game.state.add("PlayGame", playGame, true);

}

 

var playGame = function (){};

 

playGame.prototype = {

    preload: function (){

        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

        game.scale.pageAlignHorizontally = true;

        game.scale.pageAlignVertically = true;

game.stage.disableVisibilityChange = true;

        game.stage.backgroundColor = 0x222222;

        this.load.image("ball", "ball.png");

        this.load.image("balance", "balance.png");

    },

    create: function (){

        this.growBall = false;

        this.canPlay = true;

        this.balance = [];

        for(var i = 0; i < 2; i++){

            this.balance[i] = game.add.group();

            this.balance[i].weight = 0;

            var balanceSprite = game.add.sprite(game.width / 2 * i, 240, "balance");

            balanceSprite.anchor.set(0, 0.5);

            this.balance[i].add(balanceSprite);

        }

        game.input.onDown.add(this.placeBall, this);

        game.input.onUp.add(this.dropBall, this);

    },

    placeBall: function(e){

        if(!this.growBall && this.canPlay){

            this.ball = game.add.sprite(e.x, 0, "ball");

            this.ball.anchor.set(0.5);

this.ball.width = 1;

            this.ball.height = 1;

            this.ball.balance = Math.floor(this.ball.x / (game.width / 2));

            this.balance[this.ball.balance].add(this.ball);

            this.ball.y = 30 - this.balance[this.ball.balance].y

     this.growBall = true;

        }

    },

    dropBall: function(){

        if(this.growBall){

            this.growBall = false;

            this.canPlay = false;

            var ballDestination = game.height / 2 - this.balance[this.ball.balance].getChildAt(0).height / 2 - this.ball.height / 2;

            this.balance[this.ball.balance].weight += (4 / 3) * Math.PI * Math.pow((this.ball.width / 2), 3);

            var ballTween = game.add.tween(this.ball).to({

y: ballDestination

}, 2000, Phaser.Easing.Bounce.Out, true);

            ballTween.onComplete.add(this.adjustBalances, this)

        }

    },

    adjustBalances: function(){

        var weightDifference = (this.balance[0].weight - this.balance[1].weight) / gameOptions.balanceFriction;

        var maxDifference = game.height / 3;

        if(weightDifference > maxDifference){

weightDifference =maxDifference;

}

if(weightDifference < -maxDifference){

weightDifference = -maxDifference;

}

        for(var i = 0; i < 2; i++){

            var balanceTween = game.add.tween(this.balance[i]).to({

                y: weightDifference - (2 * i * weightDifference)

            }, 2000, Phaser.Easing.Quadratic.Out, true);

            balanceTween.onComplete.add(function(){

                this.canPlay = true;

            }, this);

        }

    },

    update: function(){

        if(this.growBall && this.ball.width < gameOptions.maxDiameter){

            this.ball.width += gameOptions.ballGrowingSpeed;

            this.ball.height += gameOptions.ballGrowingSpeed;

        }

    }

}


Phaser 3 버전은 어떻습니까?



게임이 똑같아 보이더라도 코드를 작동시키기 위해 코드를 다양하게 변경해야했습니다. Phaser 3 버전을 가능한 한 Phaser CE 버전에 가깝게 유지하려고 했으므로 최적화되지 않았으며 API 문서가 거의 없습니다. 어쨌든 여러 가지 차이점을 나열 해 보겠습니다.


GAME CONSTRUCTOR : 생성자에는 이제 단 하나의 인수 만 있습니다.이 인수는 빠른 사용자 정의를 허용하는 객체입니다.


SCALING : 나는 게임을 확장 할 수 없었습니다. 또한 배경색으로 카메라를 사용해야 했습니다. 어쩌면 내가 뭔가를 놓친 것 같습니다.


GAME STATUS : 이제 장면이라고 부르며,이 기능을 거의 사용하지 않았더라도 빠른 설정이 가능합니다.


THIS INSTANCE : 콜백 함수에서 this를 추적 할 수 없어서 _this 변수로 저장해야 했습니다.


INPUT LISTENERS : 이름이 바뀌었지만 모두 똑같은 것처럼 보입니다.


TWEENS : 정의를 편하게 할수 있도록 되었습니다.


GROUP : 그들은 고통 스러웠습니다. 나는 그 (것)들을 움직일 수 없었다, 나는 x 또는 y 위치를 TWEEN 할 수없고, 평형 운동을 창조하기 위하여 많은 workaround 및 어리석은 수학을해야했다. 어쩌면 나는 무엇인가 놓쳤다.


SPRITES : 나는 비행 중에 폭과 높이를 변경할 수 없었기 때문에 크기를 조정하는 대신 스프라이트 크기를 조정해야했습니다.


소스 코드를보고, 약간의 변화를보고 두려워하지 말고, 여기에 새로운 언어를 배우려고 할 때 도움이 될 10 가지 팁이 있습니다.


var game;

var _this;

 

var gameOptions = {

    maxDiameter: 1,

    ballGrowingSpeed: 0.015,

    balanceFriction: 400

}

 

var config = {

    type: Phaser.CANVAS,

    width: 320,

    height: 480

};

 

window.onload = function(){

    game = new Phaser.Game(config);

    game.scene.add("PlayGame", playGame, true);

}

 

var playGame = function (){

    _this = this;

};

 

playGame.prototype = {

    preload: function (){

        var camera = _this.cameras.add(0, 0, game.width, game.height);

        camera.setBackgroundColor("0x222222");

        _this.load.image("ball", "ball.png");

        _this.load.image("balance", "balance.png");

    },

    create: function (){

        _this.growBall = false;

        _this.canPlay = true;

        _this.balance = [];

        for(var i = 0; i < 2; i++){

            _this.balance[i] = _this.add.group();

            _this.balance[i].weight = 0;

            _this.balance[i].saveYPosition = 0;

            var balanceSprite = _this.add.sprite(config.width / 2 * i, 240, "balance");

            balanceSprite.setOrigin(0, 0.5);

            _this.balance[i].add(balanceSprite);

        }

        _this.input.events.on("POINTER_DOWN_EVENT", _this.placeBall);

        _this.input.events.on("POINTER_UP_EVENT", _this.dropBall);

    },

    placeBall: function(e){

        if(!_this.growBall && _this.canPlay){

            var side = Math.floor(e.x / (config.width / 2));

            _this.ball = _this.add.sprite(e.x, 30, "ball");

            _this.ball.balance = side;

     _this.ball.scaleX = 0.1;

            _this.ball.scaleY = 0.1;

            _this.balance[_this.ball.balance].add(_this.ball);

     _this.growBall = true;

        }

    },

    dropBall: function(){

        if(_this.growBall){

            _this.growBall = false;

            _this.canPlay = false;

            var ballDestination =  config.height / 2 + _this.balance[_this.ball.balance].saveYPosition - _this.balance[_this.ball.balance].children.entries[0].height / 2 - _this.ball.height * _this.ball.scaleY / 2;

            _this.balance[_this.ball.balance].weight += (4 / 3) * Math.PI * Math.pow((_this.ball.width * _this.ball.scaleX / 2), 3);

            var ballTween = _this.tweens.add({

                targets: _this.ball,

                y: ballDestination,

                duration: 2000,

                ease: "Bounce",

                onComplete: _this.adjustBalances

            });

        }

    },

    adjustBalances: function(){

        var weightDifference = (_this.balance[0].weight - _this.balance[1].weight) / gameOptions.balanceFriction;

        var maxDifference = config.height / 3;

        if(weightDifference > maxDifference){

weightDifference = maxDifference;

}

if(weightDifference < -maxDifference){

weightDifference = -maxDifference;

}

        for(var i = 0; i < 2; i++){

            var difference = - _this.balance[i].saveYPosition + weightDifference - (2 * i * weightDifference)

            _this.balance[i].saveYPosition += difference;

            var balanceTween = _this.tweens.add({

                targets: _this.balance[i].children.entries,

                y: "+=" + difference.toString(),

                duration: 2000,

                ease: "Quad",

                onComplete: function(){

                    _this.canPlay = true;

                }

            })

        }

    },

    update: function(){

        if(_this.growBall && _this.ball.scaleX < gameOptions.maxDiameter){

            _this.ball.scaleX += gameOptions.ballGrowingSpeed;

            _this.ball.scaleY += gameOptions.ballGrowingSpeed;

        }

    }

}


여전히 많은 부분이 있지만, 일반적인 "Hello World"보다 훨씬 깁니다. 프로토 타입을 개선하기위한 제안이 있다면 환영합니다. 두 프로토 타입의 소스 코드를 다운로드하십시오.


Posted by 마스터킹
|

2 년 전 Phaser를 사용하여 1 + 2 = 3 게임의 100 줄 html5 프로토 타입을 게시했습니다.


몇 달 후, 프로토 타입을 선택하고, 일종의 스토리와 132 레벨 (예, 132)을 추가하고 Softgames가 후원 한 Matt Vs Math를 발표했습니다.


프로토 타입은 주석 처리되지 않은 상태로 남아 있었고 일부 독자는 대부분 내가 어려움이 증가하는 무작위 질문을 생성 할 수 있었던 방법을 알기 위해 주로 의견을 추가하라고했습니다.


오늘 필자는 최신 Phaser CE 버전 (2.8.5)으로 업데이트 된 원본 프로토 타입을 다시 작성하고 게임이 끝나면 다시 시작할 수있는 게임을 다시 작성합니다.




표시된 식에 따라 1, 2 또는 3의 버튼을 클릭하거나 터치하십시오. 너 가장 좋은 점수는 뭐니? 모바일 장치가있는 경우 이 링크에서 직접 재생할 수 있습니다.


마지막으로 이것은 소스 코드이며 주석을 달고 업데이트합니다.


// the game itself

var game; 

var gameOptions = {

// 합계의 최대 길이

maxSumLen: 5, 

// 높은 점수를 저장하는 데 사용되는 로컬 저장소 이름

localStorageName: "oneplustwo",

// 질문에 대답 할 수있는 시간 (밀리 초)

timeToAnswer: 3000,

// 난이도를 높이기 위해 필요한 점수

nextLevel: 400

}

// 일단 창이 완전히 로드되면 ...

window.onload = function() {

// CANVAS 렌더링을 사용하여 500x500 픽셀 게임 만들기

game = new Phaser.Game(500, 500, Phaser.CANVAS);

// "PlayGame" 상태를 만들고 시작하십시오.

game.state.add("PlayGame", playGame, true);

}

// "PlayGame"상태

var playGame = function(game){}

playGame.prototype = {

// 사전로드 상태

    preload: function(){

        // 모든 콘텐츠를 표시하면서 가능한 한 가장 큰 창 영역을 게임 커버 만들기

        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

        game.scale.pageAlignHorizontally = true;

        game.scale.pageAlignVertically = true;

// 초점을 잃을 때 게임을 일시 중지하지 않기.

game.stage.disableVisibilityChange = true; 

        // 배경색 변경

        game.stage.backgroundColor = 0x444444;

// preloading images

game.load.image("timebar", "timebar.png"); 

// preloading a spritesheet where each sprite is 400x50 pixels

game.load.spritesheet("buttons", "buttons.png", 400, 50);

 

    },

// when the state has been created...

    create: function(){

// it's not game over yet...

this.isGameOver = false; 

// current score is set to zero

this.score = 0;

// we'll also keep track of correct answers

this.correctAnswers = 0;  // topScore는 로컬 저장소에 이전에 저장된 값을 가져오고 그렇지 않으면 0을 가져옵니다.

this.topScore = localStorage.getItem(gameOptions.localStorageName) == null ? 0 : localStorage.getItem(gameOptions.localStorageName);

// sumsArray는 가능한 모든 질문이있는 배열입니다.

this.sumsArray = []   // 매번 무작위로 질문을 던지기보다는 모든 가능한 질문을 배열에 저장하는 것이 // 더 쉬워졌으며 매번 무작위로 질문을 뽑았습니다. 가능한 모든 질문을 생성하는 // 알고리즘이 필요합니다.

// 루프로 가능한 모든 질문을 만들기 시작하십시오.  // 1 (1 + 1과 같은 연산자 만)에서 maxSumLen // (이 경우 5, 1 + 1 + 1 + 1-1-1과 같이)까지 다양합니다.

for(var i = 1; i < gameOptions.maxSumLen; i++){

// sumsArray [i]를 3 개의 빈 배열로 정의

this.sumsArray[i]=[[], [], []];

// 각 합계의 가능한 결과 인 1에서 3으로 반복

for(var j = 1; j <= 3; j++){ 

// buildTrees는 스크립트의 핵심이며, 설명되어 있습니다.

this.buildThrees(j, 1, i, j);

}

}

// 가능한 모든 조합을 볼 수 있습니다.

console.log(this.sumsArray);

// questionText는 질문을 표시 할 텍스트 객체입니다.

this.questionText = game.add.text(250 , 160, "-", {

font: "bold 72px Arial"

});

// 설정 질문 등록 포인트

this.questionText.anchor.set(0.5);

// scoreText는 현재 점수를 유지합니다.

this.scoreText = game.add.text(10, 10, "-", {

font: "bold 24px Arial"

});

// 루프를 사용하여 세 가지 응답 버튼을 만듭니다.

for(i = 0;i < 3; i++){

// 응답 버튼의 생성, 프레임 "i"로 설정.

// 한 번 트리거 된 checkAnswer 콜백 함수를 호출합니다.

var numberButton = game.add.button(50, 250 + i * 75, "buttons", this.checkAnswer, this).frame = i;

}

// 타임 바 추가

var numberTimer =  game.add.sprite(50, 250, "timebar");

// 세 개의 대답 버튼을 덮는 그래픽 마스크 생성

this.buttonMask = game.add.graphics(50, 250);

this.buttonMask.beginFill(0xffffff);

this.buttonMask.drawRect(0, 0, 400, 200);

this.buttonMask.endFill();

numberTimer.mask = this.buttonMask;

// 다음 질문

this.nextNumber();

}, 

// buildThrees 메서드를 사용하면 가능한 모든 합계를 찾습니다.

// arguments:

// initialNumber: 첫 번째 숫자. 각 질문은 항상 양수로 시작합니다.

// currentIndex: 이미 합계에 배치 된 피연산자의 양입니다.

// limit: 질문에 허용 된 최대 피연산자 수

// currentString: 지금까지 생성 된 문자열

buildThrees: function(initialNummber, currentIndex, limit, currentString){

// 0을 제외하고 -3에서 3까지 가능한 피연산자

var numbersArray = [-3, -2, -1, 1, 2, 3];

// 0에서 numbersArray의 길이로 루핑하기

for(var i = 0; i < numbersArray.length; i++){

// "sum"은 첫 번째 숫자와 현재 numberArray 항목 사이의 합계입니다.

var sum = initialNummber + numbersArray[i];

// 출력 문자열은 현재 문자열을 현재 numbersArray 항목과 연결하여 // 생성됩니다. 항목이 0보다 큰 경우 "+"를 추가하고 그렇지 않으면 // 이미 "-"가 있습니다.

var outputString = currentString + (numbersArray[i] < 0 ? "" : "+") + numbersArray[i];

// 합계가 1에서 3 사이이고 우리가 원하는 피연산자 수에 도달하면 ...

if(sum > 0 && sum < 4 && currentIndex == limit){

// 그런 다음 출력 문자열을 sumsArray로 푸시합니다. // [피연산자 수][결과]

this.sumsArray[limit][sum - 1].push(outputString);

}

// 피연산자의 양이 우리가 원하는 양보다 여전히 적다면 ...

if(currentIndex < limit){

// 재귀 적으로 buildThrees를 호출하여 인수로 전달 :

// 현재 합계

// 새로운 양의 피연산자

// 우리가 원하는 피연산자의 양

// 현재 출력 문자열

this.buildThrees(sum, currentIndex + 1, limit, outputString);

}

}

},

// 이 방법은 다음 질문을한다.

nextNumber: function(){

// 점수 텍스트 업데이트

this.scoreText.text = "Score: " + this.score.toString() + "\nBest Score: " + this.topScore.toString();

// 우리가 이미 하나 이상의 질문에 대답했다면 ...

if(this.correctAnswers > 1){

// 정지 시간 트윈

this.timeTween.stop();

// 마스크 수평 위치 재설정

this.buttonMask.x = 50;

}

// 우리가 이미 적어도 한 가지 질문에 대답했다면 ...

if(this.correctAnswers > 0){

// 트위스트가 마스크를 밀어 내고 뒤에 무엇이 있는지 밝히기.

this.timeTween = game.add.tween(this.buttonMask).to({

x: -350

}, gameOptions.timeToAnswer, Phaser.Easing.Linear.None, true);

// 트윈이 끝날 때 트리거되는 콜백

this.timeTween.onComplete.add(function(){

// "gameOver"메서드 호출. "?" 표시 할 문자열입니다.

this.gameOver("?");

}, this);

}

// 0과 2 사이의 무작위 결과 그리기 (1에서 3까지)

this.randomSum = game.rnd.between(0, 2);

// 현재 점수에 따라 질문 길이 선택

var questionLength = Math.min(Math.floor(this.score / gameOptions.nextLevel) + 1, 4) 

// 질문 텍스트 업데이트 중

this.questionText.text = this.sumsArray[questionLength][this.randomSum][game.rnd.between( 0, this.sumsArray[questionLength][this.randomSum].length - 1)];

},

// 대답을 확인하는 메소드, 인수는 눌려진 버튼입니다.

checkAnswer: function(button)

// 아직 게임이 끝나지 않은 경우에만 답변을 확인합니다.

if(!this.isGameOver){

// 버튼 프레임은 randomSum과 같습니다. 대답은 정확합니다.

if(button.frame == this.randomSum) 

// 응답에 소요 된 시간에 따라 점수가 증가합니다.

     this.score += Math.floor((this.buttonMask.x + 350) / 4);

// 하나 더 정답

this.correctAnswers++;

// 다음 질문으로 이동

this.nextNumber();

     }

// 잘못된 답변

     else{

// 첫 번째 질문이 아니라면 ...

     if(this.correctAnswers > 1) {

// 트윈을 중지 시키십시오.

this.timeTween.stop();

     }

// "gameOver"메서드 호출. // "button.frame + 1"은 표시 할 문자열입니다.

     this.gameOver(button.frame + 1);

}

}

},

// 게임을 끝내는 방법. 인수는 쓸 문자열입니다.

gameOver: function(gameOverString){

// changing background color

game.stage.backgroundColor = "#ff0000";

// displaying game over text

this.questionText.text = this.questionText.text + " = " + gameOverString;

// now it's game over

this.isGameOver = true;

// updating top score in local storage

localStorage.setItem(gameOptions.localStorageName, Math.max(this.score, this.topScore));

// restart the game after two seconds

game.time.events.add(Phaser.Timer.SECOND * 2, function(){

game.state.start("PlayGame");

}, this);

}

}


Posted by 마스터킹
|

소개

저장소는 https://github.com/dci05049/Phaser-Multiplayer-Game-Tutorial/tree/master/Part2에서 확인할 수 있습니다.


이것은 클라이언트의 Phaser와 서버의 Node.js를 사용한 멀티 플레이어 게임 자습서 시리즈의 연속입니다. 첫 번째 부분


- 우리는 성공적으로 개발 환경을 설정하였습니다.

- 우리는 클라이언트와 서버를 Socket.io와 Express.js로 연결했습니다.

- 우리는 서버가 하나의 게임 상태를 갖도록 모든 클라이언트 동작을 동기화했습니다.


그러나 우리의 구현은 서버에 직접 클라이언트 위치를 전송하기 때문에 매우 순진하다는 것을 기억하십시오. 그리고 우리는 연결된 플레이어의 나머지 부분으로 그 위치를 다시 방송합니다.


이게 왜 위험한가요? 클라이언트 측은 쉽게 조작 할 수 있기 때문에 주로. 또한 누구나 자바 스크립트 파일을 변경할 수 있으므로 해커가 자바 스크립트 파일을 변경하여 게임에서 자신의 위치를 쉽게 조작 할 수 있으므로 다른 모든 플레이어의 게임 경험에 해를 끼칠 수 있습니다.


그러면 해결책은 무엇입니까? "서버 권위있는"게임을 만들 수 있습니다. 즉, 중요한 데이터는 모두 서버에 저장되고 계산됩니다. 우리는 위치 대신 서버에 입력을 보내 게임을 보다 안전하게 만들 수 있습니다. 그런 다음 플레이어의 새로운 위치를 계산하여 다른 플레이어에게 방송 할 수 있습니다. 그러나 한 가지 문제가 있습니다. 우리는 마우스 포인터를 따르는 데 물리학을 사용하고 있습니다. 화살표 키를 눌러 플레이어를 움직이는 것만 큼 간단하지 않습니다. 이것은 우리가 서버에서도 물리 시스템을 필요로 한다는 것을 의미합니다!.


서버의 물리학을 위해 우리는 p2 물리학을 사용할 것입니다. 클라이언트에서 p2 물리학을 사용한다는 사실은 클라이언트와 서버 계산이 유사하기 때문에 맨 위에있는 체리입니다.


p2 physics를 설치하려면 npm install p2 --save를 입력하십시오.

이제 코드 작성 준비가되었습니다.


클라이언트 측 : main.js

var socket; 
socket = io.connect();


canvas_width = window.innerWidth * window.devicePixelRatio;
canvas_height = window.innerHeight * window.devicePixelRatio;

game = new Phaser.Game(canvas_width,canvas_height, 
                       Phaser.CANVAS, 'gameDiv');

// 적 플레이어 목록
var enemies = [];

var gameProperties = { 
 gameWidth: 4000,
 gameHeight: 4000,
 game_elemnt: "gameDiv",
 in_game: false,
};

var main = function(game){
};

function onsocketConnected () {
 console.log("connected to server"); 
 createPlayer();
 gameProperties.in_game = true;
 // 서버를 초기 위치에 보내고 우리에게 연결되었음을 알려줍니다.
 socket.emit('new_player', {x: 0, y: 0, angle: 0});
}

// 서버가 클라이언트 연결 끊김을 알리면 연결되지 않은 적을 찾아서 
// 게임에서 제거합니다.
function onRemovePlayer (data) {
 var removePlayer = findplayerbyid(data.id);
 // Player not found
 if (!removePlayer) {
  console.log('Player not found: ', data.id)
  return;
 }
 
 removePlayer.player.destroy();
 enemies.splice(enemies.indexOf(removePlayer), 1);
}

function createPlayer () {
 player = game.add.graphics(0, 0);
 player.radius = 100;

 // set a fill and line style
 player.beginFill(0xffd900);
 player.lineStyle(2, 0xffd900, 1);
 player.drawCircle(0, 0, player.radius * 2);
 player.endFill();
 player.anchor.setTo(0.5,0.5);
 player.body_size = player.radius; 

 // draw a shape
 game.physics.p2.enableBody(player, true);
 player.body.clearShapes();
 player.body.addCircle(player.body_size, 0 , 0); 
 player.body.data.shapes[0].sensor = true;
}

// this is the enemy class. 
var remote_player = function (id, startx, starty, start_angle) {
 this.x = startx;
 this.y = starty;
 // 이것이 유일한 소켓 ID입니다. 
    // 우리는 그것을 적의 유일한 이름으로 사용한다.
 this.id = id;
 this.angle = start_angle;
 
 this.player = game.add.graphics(this.x , this.y);
 this.player.radius = 100;

 // set a fill and line style
 this.player.beginFill(0xffd900);
 this.player.lineStyle(2, 0xffd900, 1);
 this.player.drawCircle(0, 0, this.player.radius * 2);
 this.player.endFill();
 this.player.anchor.setTo(0.5,0.5);
 this.player.body_size = this.player.radius; 

 // draw a shape
 game.physics.p2.enableBody(this.player, true);
 this.player.body.clearShapes();
 this.player.body.addCircle(this.player.body_size, 0 , 0); 
 this.player.body.data.shapes[0].sensor = true;
}

// 서버는 새로운 적 플레이어가 서버에 연결할 때 알려줍니다.
// 우리는 우리 게임에서 새로운 적을 창조합니다.
function onNewPlayer (data) {
 //enemy object 
 var new_enemy = new remote_player(data.id, data.x, 
                                      data.y, data.angle); 
 enemies.push(new_enemy);
}

// 서버는 새로운 적의 움직임이 있음을 알려줍니다. 
// 이동 된 적을 찾고 서버와 적의 움직임을 동기화합니다.
function onEnemyMove (data) {
 console.log("moving enemy");
 
 var movePlayer = findplayerbyid (data.id); 
 
 if (!movePlayer) {
  return;
 }
 
 var newPointer = {
  x: data.x,
  y: data.y, 
  worldX: data.x,
  worldY: data.y, 
 }
 
 var distance = distanceToPointer(movePlayer.player, newPointer);
 speed = distance/0.05;
 
 movePlayer.rotation = movetoPointer(movePlayer.player, speed, 
                                        newPointer);
}

// 우리는 서버에서 계산 된 위치를 받고 플레이어 위치를 변경합니다.
function onInputRecieved (data) {
 
 // 우리는 새로운 위치를 가진 새로운 포인터를 만들고 있습니다.
 var newPointer = {
  x: data.x,
  y: data.y, 
  worldX: data.x,
  worldY: data.y, 
 }
 
 var distance = distanceToPointer(player, newPointer);
 //우리는 50ms마다 플레이어 위치를 얻고 있습니다. 
    // 현재 위치와 새 위치 사이를 보정합니다.
 speed = distance/0.05;
 
 // 새로운 위치로 이동하십시오.
 player.rotation = movetoPointer(player, speed, newPointer);

}

// 여기서 우리는 소켓 ID를 사용합니다.
// 적의 목록을 검색하여 찾습니다.
function findplayerbyid (id) {
 for (var i = 0; i < enemies.length; i++) {
  if (enemies[i].id == id) {
   return enemies[i]; 
  }
 }
}

main.prototype = {
 preload: function() {
  game.stage.disableVisibilityChange = true;
  game.scale.scaleMode = Phaser.ScaleManager.RESIZE;
  game.world.setBounds(0, 0, gameProperties.gameWidth, 
                                  gameProperties.gameHeight, 
                                  false, false, false, false);
  game.physics.startSystem(Phaser.Physics.P2JS);
  game.physics.p2.setBoundsToWorld(false, false, false, 
                                         false, false)
  game.physics.p2.gravity.y = 0;
  game.physics.p2.applyGravity = false; 
  game.physics.p2.enableBody(game.physics.p2.walls, false); 
  // physics start system
  //game.physics.p2.setImpactEvents(true);

    },
 
 create: function () {
  game.stage.backgroundColor = 0xE1A193;;
  console.log("client started");
  socket.on("connect", onsocketConnected); 
  
  // listen to new enemy connections
  socket.on("new_enemyPlayer", onNewPlayer);
  // listen to enemy movement 
  socket.on("enemy_move", onEnemyMove);
  // when received remove_player, remove the player passed; 
  socket.on('remove_player', onRemovePlayer); 
  // when the player receives the new input
  socket.on('input_recieved', onInputRecieved);
 },
 
 update: function () {
  // 플레이어 입력을 내 보낸다.
  
  // 플레이어가 만들어지면 플레이어를 움직인다.
  if (gameProperties.in_game) {
  
   // 우리는 새로운 마우스 포인터를 만들고 
            // 이 입력을 서버에 보냅니다.
   var pointer = game.input.mousePointer;
     
   // 새로운 위치 데이터를 서버에 보냅니다.
   socket.emit('input_fired', {
    pointer_x: pointer.x, 
    pointer_y: pointer.y, 
    pointer_worldx: pointer.worldX, 
    pointer_worldy: pointer.worldY, 
   });
  }
 }
}

var gameBootstrapper = {
    init: function(gameContainerElementId){
  game.state.add('main', main);
  game.state.start('main'); 
    }
};;

gameBootstrapper.init("gameDiv");

클라이언트 측에서 추가 한 새로운 기능은 onInputRecieved입니다. 튜토리얼의 파트 1에서는 클라이언트에서 플레이어 자체를 이동하고 현재 위치를 서버로 보냈습니다. 대신 서버에서 새로운 위치를 기다릴 것입니다. 우리는 50ms마다 데이터를받습니다. 따라서 새로운 속도를 계산하여 시작 위치와 끝 위치를 보간합니다. socket.on에 의한 onInputReceived ( 'input_recieved', onInputRecieved)를 기다리십시오.


서버 : 새 파일, playermovement.js

서버 폴더에 "physics"라는 새 폴더를 만듭니다. playermovement.js라는 새 javascript 파일을 만듭니다.

function movetoPointer (displayObject, speed, pointer, maxTime) 
{
 pointer = pointer;
 if (maxTime === undefined) { maxTime = 0; }
 var angle = angleToPointer(displayObject, pointer);
 if (maxTime > 0)
 {
  // 얼마나 많은 픽셀을 이동해야 하는지 알지만 
        // 속도는 얼마나 빠릅니까?
  speed = distanceToPointer(displayObject, pointer) / 
                                 (maxTime / 1000);
 }
 displayObject.playerBody.velocity[0] = Math.cos(angle) * 
                                          speed;
 displayObject.playerBody.velocity[1] = Math.sin(angle) * 
                                          speed;
 return angle;
}

function distanceToPointer (displayObject, pointer, world) {
    if (world === undefined) { world = false; }
    var dx = (world) ? displayObject.world.x - pointer.worldX 
      : displayObject.playerBody.position[0] - pointer.worldX;
    var dy = (world) ? displayObject.world.y - pointer.worldY 
      : displayObject.playerBody.position[1] - pointer.worldY;
    return Math.sqrt(dx * dx + dy * dy);
}

function angleToPointer (displayObject, pointer, world) {
      
        if (world === undefined) { world = false; }

        if (world)
        {
            return Math.atan2(pointer.worldY - 
                              displayObject.world.y, 
                              pointer.worldX - 
                              displayObject.world.x);
        }
        else
        {
            return Math.atan2(pointer.worldY - 
                      displayObject.playerBody.position[1], 
                      pointer.worldX - 
                      displayObject.playerBody.position[0]);
        }
}

// 우리는이 세 가지 기능을 제공한다.
module.exports = {
 movetoPointer: movetoPointer,
 distanceToPointer: distanceToPointer,
 angleToPointer: angleToPointer
}

Node.js에서 다른 사람들이 사용할 함수를 내보내려면 module.exports를 사용해야합니다. 그렇지 않으면 파일을 필요로 할 때 사용할 수 없습니다. 파일을 "요구"하면 export 된 함수를 사용할 수 있습니다 .


플레이어 이동 함수는 클라이언트에서 동일한 메커니즘을가집니다. 그러나 우리는 playerBody.position [0]과 같은 p2 구문을 사용합니다. 구문은 나중에 설명 하겠지만 x 및 y 위치를 지정하는 p2 방법 일뿐입니다.


서버 : App.js 

var express = require('express');
// 서버에 p2 물리 라이브러리가 필요합니다.
var p2 = require('p2'); 

var app = express();
var serv = require('http').Server(app);
// 서버에서 플레이어를 이동하는 데 필요한 기능을 얻습니다.
var physicsPlayer = require('./server/physics/playermovement.js');

app.get('/',function(req, res) {
 res.sendFile(__dirname + '/client/index.html');
});
app.use('/client',express.static(__dirname + '/client'));

serv.listen(process.env.PORT || 2000);
console.log("Server started.");

var player_lst = [];

// 물리 업데이트에 필요
var startTime = (new Date).getTime();
var lastTime;
var timeStep= 1/70; 

// 서버의 물리 세계. 이것은 모든 현상이 일어나는 곳입니다.
// 우리는 마우스 포인터를 따라 가기 때문에 중력을 0으로 설정합니다.
var world = new p2.World({
  gravity : [0,0]
});

// a player class in the server
var Player = function (startX, startY, startAngle) {
  this.x = startX
  this.y = startY
  this.angle = startAngle
  this.speed = 500;
  // We need to intilaize with true.
  this.sendData = true;
}

// 물리 처리기를 60fps라고 부릅니다. 여기서 물리 계산됩니다.
setInterval(physics_hanlder, 1000/60);

// Steps the physics world. 
function physics_hanlder() {
 var currentTime = (new Date).getTime();
 timeElapsed = currentTime - startTime;
 var dt = lastTime ? (timeElapsed - lastTime) / 1000 : 0;
    dt = Math.min(1 / 10, dt);
    world.step(timeStep);
}


// 새 플레이어가 연결되면 플레이어 개체의 새 인스턴스를 만들고 
// 새 플레이어 메시지를 클라이언트에 보냅니다.
function onNewplayer (data) {
 console.log(data);
 // 새 플레이어 인스턴스
 var newPlayer = new Player(data.x, data.y, data.angle);
 
 // 플레이어 본문의 인스턴스 만들기
 playerBody = new p2.Body ({
  mass: 0,
  position: [0,0],
  fixedRotation: true
 });
 
 // 플레이어 객체에 playerbody 추가
 newPlayer.playerBody = playerBody;
 world.addBody(newPlayer.playerBody);
 
 console.log("created new player with id " + this.id);
 newPlayer.id = this.id;  
 // 발신자를 제외한 모든 클라이언트에게 보낼 정보
 var current_info = {
  id: newPlayer.id, 
  x: newPlayer.x,
  y: newPlayer.y,
  angle: newPlayer.angle,
 }; 
 
 // 이미 연결되어있는 모든 사람에 대해 새 플레이어에게 보냅니다.
 for (i = 0; i < player_lst.length; i++) {
  existingPlayer = player_lst[i];
  var player_info = {
   id: existingPlayer.id,
   x: existingPlayer.x,
   y: existingPlayer.y, 
   angle: existingPlayer.angle,   
  };
  console.log("pushing player");
  // 보낸 사람 - 클라이언트에게만 메시지 보내기
  this.emit("new_enemyPlayer", player_info);
 }
 
 // 발신자를 제외한 모든 연결된 클라이언트에게 메시지 보내기
 this.broadcast.emit('new_enemyPlayer', current_info);
 

 player_lst.push(newPlayer); 
}


// 우리는 이것을 더 이상 사용하지 않고있다.
function onMovePlayer (data) {
 var movePlayer = find_playerid(this.id); 
 movePlayer.x = data.x;
 movePlayer.y = data.y;
 movePlayer.angle = data.angle; 
 
 var moveplayerData = {
  id: movePlayer.id,
  x: movePlayer.x,
  y: movePlayer.y, 
  angle: movePlayer.angle
 }
 
 // 발신자를 제외한 모든 연결된 클라이언트에게 메시지 보내기
 this.broadcast.emit('enemy_move', moveplayerData);
}

// 플레이어 위치를 확인하는 대신 사용자 입력을 확인합니다.
function onInputFired (data) {
 var movePlayer = find_playerid(this.id, this.room); 
 
 
 if (!movePlayer) {
  return;
  console.log('no player'); 
 }

 // sendData가 true이면 데이터를 클라이언트에 다시 보냅니다.
 if (!movePlayer.sendData) {
  return;
 }
 
 // 50ms마다 데이터를 전송합니다.
 setTimeout(function() {movePlayer.sendData = true}, 50);
 // 데이터를 보낼 때 sendData를 false로 설정합니다.
 movePlayer.sendData = false;
 
 // 클라이언트로부터의 새로운 입력으로 새로운 포인터 만들기
 // 서버에 플레이어 위치를 포함합니다.
 var serverPointer = {
  x: data.pointer_x,
  y: data.pointer_y,
  worldX: data.pointer_worldx,   
  worldY: data.pointer_worldy
 }
 
 // 플레이어로 부터 새로운 입력으로 플레이어를 이동.
 if (physicsPlayer.distanceToPointer(movePlayer, serverPointer) <= 30) 
 {
  movePlayer.playerBody.angle = 
  physicsPlayer.movetoPointer(movePlayer, 0, serverPointer, 1000);
 } else {
  movePlayer.playerBody.angle = 
  physicsPlayer.movetoPointer(movePlayer, 
                              movePlayer.speed, serverPointer); 
 }
 
 // 새로운 플레이어 위치가 클라이언트로 다시 전송됩니다.
 var info = {
  x: movePlayer.playerBody.position[0],
  y: movePlayer.playerBody.position[1],
  angle: movePlayer.playerBody.angle
 }

 // 보낸 클라이언트 (모든 클라이언트가 아닌)로 보냅니다.
 this.emit('input_recieved', info);
 
 // 발신자를 제외한 모든 사용자에게 다시 전송할 데이터
 var moveplayerData = {
  id: movePlayer.id, 
  x: movePlayer.playerBody.position[0],
  y: movePlayer.playerBody.position[1],
  angle: movePlayer.playerBody.angle,
 }
 
 // 발신자를 제외한 모든 사람에게 보내기
 this.broadcast.emit('enemy_move', moveplayerData);
}

// 클라이언트가 연결을 끊을 때 호출을하고 발신자를 제외한 
// 클라이언트에게 연결이 끊긴 플레이어를 제거하라고 알립니다.
function onClientdisconnect() {
 console.log('disconnect'); 

 var removePlayer = find_playerid(this.id); 
  
 if (removePlayer) {
  player_lst.splice(player_lst.indexOf(removePlayer), 1);
 }
 
 console.log("removing player " + this.id);
 
 // 발신자를 제외한 모든 연결된 클라이언트에게 메시지 보내기
 this.broadcast.emit('remove_player', {id: this.id});
 
}

// 고유 소켓 ID로 플레이어 찾기
function find_playerid(id) {

 for (var i = 0; i < player_lst.length; i++) {

  if (player_lst[i].id == id) {
   return player_lst[i]; 
  }
 }
 
 return false; 
}

 // io connection 
var io = require('socket.io')(serv,{});

io.sockets.on('connection', function(socket){
 console.log("socket connected"); 
 
 // listen for disconnection; 
 socket.on('disconnect', onClientdisconnect); 
 
 // listen for new player
 socket.on("new_player", onNewplayer);
 /*
 //we dont need this anymore
 socket.on("move_player", onMovePlayer);
 */
 //listen for new player inputs. 
 socket.on("input_fired", onInputFired);
});

우리는 먼저 물리 계산을 위해 p2 물리학을 요구합니다. 우리는 또한 플레이어 이동을 위한 playermovement 파일이 필요합니다. 우리는 새로운 p2 물리 세계를 만들고 physics_handler로 단계를 밟습니다.

문서 : http://schteppe.github.io/p2.js/docs/classes/Body.html.


우선 우리가 플레이어를 만들 때 플레이어 바디가 추가됩니다. Phaser에서는 p2.js와 같지만 구문은 다릅니다. 우리는 OnNewPlayer에 새로운 p2.Body가 있는 본문을 만듭니다. 우리는 OnMovePlayer를 제거하고 onInputFired를 추가했습니다. 가장 큰 차이점은 다음과 같습니다. 또한 새로운 위치를 발신자에게 보냅니다. 서버에서 몸체의 x 위치를 지정하려면 playerbody [0]을 수행하고 y 위치를 지정하려면 playerbody [1]을 지정합니다. 속도에 대해서도 마찬가지입니다. x는 playerbody.velocity [0], y velocity는 playerbody.velocity [1]입니다. world.addBody (playerbody)를 사용하여 플레이어 바디를 세계에 추가하는 것을 잊지 마십시오 !! 그렇지 않으면 플레이어의 물리 연산이 계산되지 않습니다.


이제 "node app"을 입력하여 실제 게임을보십시오!


새로운 튜토리얼에서 우리는 더 많은 게임 메 커닉 측면에 집중할 것입니다. 우리는 음식과 다른 플레이어를 먹는 agar.io 게임 메 커닉을 추가 할 것입니다. 레벨 업을위한 경험 막대도 추가 할 것입니다.

Posted by 마스터킹
|