Joonas' Note
HTML5 + 자바스크립트로 만든 핑퐁 게임 본문
- 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> |
공(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; }; | |
} |
객체 충돌 코드
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)); | |
} |
빠른 과제 완성을 위해 대충 작성. 원의 중점이랑 사각형 정보로 적절하게 계산하면 충돌 감지를 할 수 있다.
(이 부분때문인지는 모르겠는데, 뒤에서 말할 버그가 생긴다)
버그 수정
프레임(은 아니고 사실 interval인데)마다 공이 (벡터 * 속도때문에) 픽셀이 끊겨서 이동하기 때문에 Racket 안에 공이 들어가버리는 끔찍한 버그가 생긴다. 그게 또 벽이랑 셋이서 겹치면 무한 진동을 하는데....
어떻게 해결할까 고민하다가, 얘가 bound 근처에서 일정 시간동안 (오차범위를 두고) 계속 머무르고 있으면 격한 좌표 이동을 줘서 (정확히는 큰 힘으로 밀어버렸다; force/push a ball against bound) 무한 진동을 막았다.
1인 모드(컴퓨터와 대전)
되게 있어보이는데 사실 별거없다. [Easy, Normal, Hard] 일 때, 공의 위치를 따라갈 확률을 [0.3, 0.5, 0.9] 로 두고 각 프레임마다 적용했다. Racket 움직임을 가속도로 조정해서, Easy일 때 30% 확률로 따라가면 적당히 바보짓하면서 공을 쫓아가는 것처럼 보인다. Hard는 90% 확률로 쫓아가지만 사실 공의 속도때문에 못 따라가서 사람이 이길 수 있다. 각 퍼센티지는 직접 조정해가면서 플레이하고 책정 (....)
화면 전환 구성
예전에 웹 게임 엔진들 구경하다가 패턴을 하나 찾았는데, Phaser랑 pixi.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; | |
//... | |
} | |
} | |
} |
'개발' 카테고리의 다른 글
메뉴 결정 도우미 (2) | 2017.12.10 |
---|---|
Ubuntu 16.04 LTS에서 MongoDB 설치 오류 정리 (2) | 2017.11.24 |
Django에서 1267, Illegal mix of collations 해결법 (0) | 2017.11.17 |
외부 메일 서버 사용하기 - Sendgrid (0) | 2017.10.30 |
MongoDB 설치 후 저장 디렉토리 변경 주의사항 (0) | 2017.10.29 |