HTML5 + 자바스크립트로 만든 핑퐁 게임

Joonas' Note

HTML5 + 자바스크립트로 만든 핑퐁 게임 본문

개발

HTML5 + 자바스크립트로 만든 핑퐁 게임

2017. 10. 30. 01:44 joonas 읽는데 6분
  • Project 3 - Ping-Pong game

학교 웹 프로그래밍 수업시간에 과제로 했던 걸 올려본다.


간단한 CSS만 적고, Javascript 랑 HTML5 로만 작성했다.


HTML5 Canvas에 대해 알아보라고 핑퐁(Ping-Pong) 게임을 만들어보라고 하셨는데, 재밌어서 기능을 계속 붙이다가 만족할 정도로 만들었다. 기간은 4~5일쯤 걸린 듯.


피드백을 받기 위해 동아리방에서 몇 명에게 부탁했는데, 의외로 재밌었는지 잠깐 유행을 탔다. (아아.. 공부가 얼마나... 말잇못)

게임 시작 후 공이 바로 생겨서 힘들다는 등 피드백이 많이 도움이 되었다.


과제내용은 대략 이랬다.


Github Page로 올리니까 좀 그럴싸해서 좋았음.

<시작 화면(왼)과, 1인 모드에서 난이도 선택(우)>



핑퐁 게임 링크

https://joonas-yoon.github.io/web-programming/project-3/


index.html에서 보이는 코드

링크에서는 귀찮아서 전부 index.html에 때려박았는데, 함수들만 따로 빼면 아래처럼 가능하다.

<canvas id="pingping"></canvas>
<script type="text/javascript" src="..." />
<script type="text/javascript">
var game = new Game('pingpong');
game.start();
game.pushBall(1);
</script>
view raw index.html hosted with ❤ by GitHub


공(Ball), 막대(Racket) 생성자 코드

Ball은 정해준 bound 내에서 임의로 생성되게 했다. 매번 그리기 위한 draw() 함수가 있고.. 흔하게 생긴 웹 게임 형태로 작성하고 싶었다.

Racket도 객체로 설계해서, 2 Players가 아니라 4 Players 게임까지도 생각했음. (근데 혼돈파괴일꺼같아서 안함..)

function Ball(boundWidth, boundHeight){
this.x = Math.random()*(boundWidth/5) + (2*boundWidth/5);
this.y = Math.random()*(boundHeight/2) + (boundHeight/4);
this.dx = Math.random() > 0.5 ? -1 : 1;
this.dy = Math.random() > 0.5 ? -1 : 1;
this.rad = 10; this.color = "#fff"; this.speed = 2;
this.set = function(newX, newY){ this.x = newX; this.y = newY; };
this.setD = function(_dx, _dy){ this.dx = _dx; this.dy = _dy; };
this.randomDirection = function(){ ... };
this.update = function(){
var nextX = this.x + this.dx, nextY = this.y + this.dy;
if(nextX < this.rad || nextX > boundWidth – this.rad) this.dx *= -1;
if(nextY < this.rad || nextY > boundHeight – this.rad) this.dy *= -1;
this.x += this.dx * this.speed;
this.y += this.dy * this.speed;
};
this.draw = function(context){ ... };
this.collide = function(racket){ ... };
}
function Racket(x, size, color, boundHeight){
this.x = x;
this.y = boundHeight / 2;
.... (many attributes) ...
this.draw = function(context){ ... };
this.update = function(){ ... };
this.accelate = function(dy){ this.dy += dy; };
}
view raw Ball+Racket.js hosted with ❤ by GitHub


객체 충돌 코드

function intersects(circle, rect){
var circleDistance = {};
circleDistance.x = Math.abs(circle.x - rect.left);
circleDistance.y = Math.abs(circle.y - rect.top);
if (circleDistance.x > (rect.width/2 + circle.rad)) { return false; }
if (circleDistance.y > (rect.height/2 + circle.rad)) { return false; }
if (circleDistance.x <= (rect.width/2)) { return true; }
if (circleDistance.y <= (rect.height/2)) { return true; }
var cornerDistance_sq = Math.pow(circleDistance.x - rect.width/2, 2) +
Math.pow(circleDistance.y - rect.height/2, 2);
return (cornerDistance_sq <= Math.pow(circle.r, 2));
}
view raw intersects.js hosted with ❤ by GitHub

빠른 과제 완성을 위해 대충 작성. 원의 중점이랑 사각형 정보로 적절하게 계산하면 충돌 감지를 할 수 있다.

(이 부분때문인지는 모르겠는데, 뒤에서 말할 버그가 생긴다)


버그 수정

프레임(은 아니고 사실 interval인데)마다 공이 (벡터 * 속도때문에) 픽셀이 끊겨서 이동하기 때문에 Racket 안에 공이 들어가버리는 끔찍한 버그가 생긴다. 그게 또 벽이랑 셋이서 겹치면 무한 진동을 하는데....

어떻게 해결할까 고민하다가, 얘가 bound 근처에서 일정 시간동안 (오차범위를 두고) 계속 머무르고 있으면 격한 좌표 이동을 줘서 (정확히는 큰 힘으로 밀어버렸다; force/push a ball against bound) 무한 진동을 막았다.


1인 모드(컴퓨터와 대전)

되게 있어보이는데 사실 별거없다. [Easy, Normal, Hard] 일 때, 공의 위치를 따라갈 확률을 [0.3, 0.5, 0.9] 로 두고 각 프레임마다 적용했다. Racket 움직임을 가속도로 조정해서, Easy일 때 30% 확률로 따라가면 적당히 바보짓하면서 공을 쫓아가는 것처럼 보인다. Hard는 90% 확률로 쫓아가지만 사실 공의 속도때문에 못 따라가서 사람이 이길 수 있다. 각 퍼센티지는 직접 조정해가면서 플레이하고 책정 (....)


화면 전환 구성

예전에 웹 게임 엔진들 구경하다가 패턴을 하나 찾았는데, Phaserpixi.js에서 state로 화면 scene을 쓰는게 좋아보여서 흉내냈다.


this.state = function(stageName){
this.init(stageName);
if(stageName == 'PLAY_SOLO' || stageName == 'PLAY_DUO'){
this.balls = [];
// ...
} else {
// ...
}
};

키보드 이벤트 충돌 처리

재밌었던게 많았는데 그 중 하나가, 키보드 이벤트 충돌을 처리하는 거였다.


키보드를 동시에 2개 이상 누르면 브라우저가 마지막으로 누른거(정확히 기억 안남)만 인식해서 나머지가 무시되는데, 2인 플레이를 구현하려니까 이게 큰 문제였다.


검색하니까 금방 나와서 해결했다. 키를 눌렀음을 저장하는 dict를 만들고 어떤 키를 누르면(key down) true, 떼면(key up) false로 저장해서 그 dict만 참조하는 거다. 비트마스킹 하는 방식이랑 꽤 비슷해서 신기했는데 이렇게 하면 키를 여러개 눌러도 정보가 남아있으니 해결된다.

this.keypress_table = {};
this.fireKeydown = function(event){
var _this = game;
_this.keypress_table[event.code.toString()] = true;
if(_this.stage == 'MAIN_MENU'){
switch(event.code){
case 'Digit1':
_this.state('SELECT_COMPUTER');
break;
//...
}
}
}

Comments