캔버스에 노출된 지 한 달이 넘었는데, 게임 프로세스를 완전히 구현한 것은 처음인데 수확량이 꽤 많습니다.
슈팅 게임 스크린샷
먼저 데모로 이동하세요: https://littleyljy.github.io/demo/shootgame/
게임 규칙플레이어는 항공기를 조종하여 총알을 발사하고 움직이는 몬스터를 파괴해야 합니다. 몬스터가 모두 바닥으로 이동하면 게임은 실패합니다.
게임은 여러 장면으로 나누어져 있습니다:
장면 전환을 달성하려면 먼저 모든 장면에 대해 없음을 표시한 다음 js를 통해 데이터 상태를 제어하여 시작, 재생, 실패, 성공, 모든 성공 및 중지를 제어하여 해당 장면 표시: 블록을 구현해야 합니다.
HTML과 CSS는 다음과 같습니다.
<div id=game data-status=start> <div class=game-panel> <section class=game-intro game-ui> <h1 class=section-title>슈팅 게임</h1> <p class=game- desc>이것은 중독성 있는 슈팅 게임입니다. ← 및 →를 사용하여 비행기를 조종하고, 공간을 사용하여 사격하고, Enter를 사용하여 게임을 일시 중지하세요. 함께 우주괴물을 물리치자! </p> <p class=game-level>현재 레벨: 1</p> <button class=js-play 버튼>게임 시작</button> </section> <section class=game-failed game-ui> <h1 class=section-title>게임 종료</h1> <p class=game-info-text>최종 점수: <span class=score></span></p> <button class=js-replay 버튼> 다시 시작</button> </section> <section class=game-success game-ui> <h1 class=section-title>게임 성공</h1> <p class=game-next-level game-info-text></p> <button class=js-next 버튼>게임 계속하기</button> </section> <section class=game-all-success game-ui> <h1 class=section-title>성공적으로 통과</h1> <p class=game- 다음 단계 game-info-text>모든 몬스터의 공격을 성공적으로 방어했습니다. </p> <button class=js-replay 버튼>다시 플레이</button> </section> <section class=game-stop game-ui> <h1 class=section-title>게임 일시 정지</h1> < 버튼 class=js-stop 버튼>게임 계속</button> </section> </div> <div class=game-info game-ui> <span class=title>점수:</span> <span class=score > </span> </div> <canvas id=canvas width=700 height=600> <!-- 애니메이션 드로잉 보드 --> </canvas> </div>
#게임{ 너비: 700px; 높이: 600px; 위치: 상대: 50%; 상단: 40px; 배경: 선형-그라디언트(-180deg, #07165C 97% );}.game-ui{ 디스플레이: 없음 패딩: 55px; 테두리 상자; 높이: 100%;}[data-status=start] .game-intro { 디스플레이: 블록; 패딩 상단: 180px; 배경: url(./img/bg.png) 반복 없음 430px 180px; 배경 크기: 200px;}[data-status=playing] .game-info { 디스플레이: 블록; 위치: 절대:0; 왼쪽:0; 패딩:20px;}[data-status=failed] .game-failed,[data-status=success] .game-success,[data-status=all-success] .game-all-success,[ data-status=stop] .game-stop{ 디스플레이: 블록 상단: 180px; url(./img/bg-end.png) 반복 없음 380px 190px 배경 크기: 250px;}객체지향
게임 전체에서는 몬스터(Enemy), 비행기(Plane), 총알(Bullets) 등을 객체로 취급할 수 있을 뿐만 아니라 게임 로직을 제어하는 구성 객체(CONFIG)와 게임 객체(GAME)도 처리할 수 있습니다.
게임 관련 구성/** * 게임 관련 구성* @type {Object} */var CONFIG = { status: 'start', // 게임은 기본적으로 시작 레벨: 1로 시작됩니다. // 게임 기본 레벨 totalLevel: 6, // 총 6개 Off numPerLine: 7, // 게임의 라인당 기본 몬스터 수 canvasPadding: 30, // 기본 캔버스 간격 bulletSize: 10, // 기본 총알 길이 bulletSpeed: 10, // 기본 총알 이동 속도 적속도: 2, // 기본 적 이동 거리 적크기: 50, // 기본 적 크기 적격: 10, // 적 사이의 기본 거리 적 아이콘: './img/enemy.png', / / 이미지 the 몬스터 적BoomIcon: './img/boom.png', // 몬스터가 죽은 이미지 적방향: 'right', // 기본 적은 시작 시 오른쪽으로 이동합니다 planeSpeed: 5, // 각 단계에서 평면이 이동하는 기본 거리 planeSize: { width: 60, height: 100 }, // 평면의 기본 크기, planeIcon: '. /img/plane.png' };상위 클래스 정의
몬스터(Enemy), 비행기(Plane), 총알(Bullet)은 모두 동일한 x, y, 크기, 속도 속성, move() 메소드를 갖기 때문에 부모 클래스인 Element를 정의하고, 부모 클래스를 상속받아 구현할 수 있습니다. 서브클래스 .
/*상위 클래스: xy 속도 포함 move() draw()*/var Element = function (opts) { this.opts = opts || {} //좌표, 크기, 속도 설정 this.x = opts.x; this.y = opts.y; this.size = opts.size; this.speed = opts.speed;};Element.prototype.move = function (x, y) { var addX = x || addY = y || 0; this.x += addX; this.y += addY;};//프로토타입 함수를 상속하는 함수 상속Prototype(subType, superType) { var proto = Object.create(superType.prototype); proto .constructor = subType; subType.prototype = proto;}
move(x, y) 메서드는 전달된 (x, y) 값을 기반으로 자체적으로 스택됩니다.
괴물을 정의하다몬스터에는 몬스터 상태, 이미지, 폭발 상태의 지속 시간을 제어하는 boomCount, draw(), down(), Direction() 및 booming() 메서드와 같은 고유한 속성이 포함됩니다.
/*Enemy*/var Enemy = function (opts) { this.opts = opts || {}; //상위 클래스 속성 호출 Element.call(this, opts) //특수 속성 상태 및 이미지 this.status '정상';//정상, 호황, noomed this.enemyIcon = opts.enemyBoomIcon = opts.enemyBoomIcon = 0;};//Element 메서드 상속 상속Prototype(Enemy, Element);//메서드: 적을 그립니다. Enemy.prototype.draw = function () { if (this.enemyIcon && this.enemyBoomIcon) { switch (this.status ) { 사례 '정상': var 적Icon = new Image(); 적Icon.src = this.enemyIcon(enemyIcon, this.x, this.y, this.size, this.size); break; 사례 '붐': var 적BoomIcon = new Image(); 적BoomIcon.src = ctx.drawImage(enemyBoomIcon, this.x, this.y, this.size, this.size); break; 케이스 '붐': ctx.clearRect(this.x, this.y, this.size, this.size); break; 기본값: break; } } return this;};//메서드: down 아래로 이동 Enemy.prototype.down = function() { this.move(0, this.size); ;//방법: 왼쪽 또는 오른쪽으로 이동 Enemy.prototype.direction = function (direction) { if (direction === 'right') { this.move(this.speed, 0) } else { this.move(-this.speed, 0); } return this;};//메서드: 적 폭발 Enemy.prototype.booming = function () { this.status = 'booming' += 1; (this.boomCount > 4) { this.status = 'boomed' } 이것을 반환합니다.}
글머리 기호에는 fly() 및 draw() 메서드가 있습니다.
/*Bullet*/var Bullet = function (opts) { this.opts = opts || Element.call(this, opts);};inheritPrototype(Bullet, Element);//메서드: 총알이 날아가게 하세요. . 프로토타입.플라이 = function () { this.move(0, -this.speed); return this;};//메서드: 총알 그리기 Bullet.prototype.draw = function () { ctx.beginPath(); ctx.stroveTo(this.x, this.y); ctx.lineTo(this.x, this.y - CONFIG.bulletSize); ; ctx.Stroke(); 이것을 반환합니다;};
항공기 개체에는 상태, 너비 및 높이, 이미지, 최대 및 최소 가로 좌표 값, 메서드 haveHit(), draw(), Direction(), Shoot() 및 drawBullets() 등 고유한 속성이 포함되어 있습니다.
/*Plane*/var Plane = function (opts) { this.opts = opts || {}; Element.call(this, opts); //고유한 속성 상태 및 이미지 this.status = 'normal'; = opts.width; this.height = opts.height; this.planeIcon = opts.planeIcon; this.minX = opts.minX = opts.maxX; //글머리 기호 관련 this.bulletSpeed = opts.bulletSpeed || CONFIG.bulletSize = opts.bulletSize || Element 메서드 상속 (평면, 요소) ;//방법: 총알이 대상에 닿았습니다. Plane.prototype.hasHit = function (enemy) { var bullets = this.bullets; for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i] var isHitPosX = (enemy.x < bullet.x) && (bullet.x < (enemy.x + 적.크기)); var isHitPosY = (enemy.y < bullet.y) && (bullet.y < (enemy.y + 적.size)); (isHitPosX && isHitPosY) { this.bullets.splice(i, 1); return true; } } return false;};//메서드: 평면 그리기 Plane.prototype.draw = function () { this.drawBullets(); var planeIcon = new Image(); planeIcon.src = this.planeIcon(planeIcon, this.x, this.y, this.width, this.height); return this;};//방법: 평면 방향 Plane.prototype.direction = function (direction) { var speed = this.speed var planeSpeed; ) { planeSpeed = this.x < this.minX ? 0 : -speed } else { planeSpeed = this.x > 0 : 속도 } console.log('planeSpeed:', planeSpeed); console.log('this.x:', this.x) console.log('this.minX:', this.minX); .maxX:', this.maxX); this.move(planeSpeed, 0); return this;//편리한 체인 호출};//메서드: 총알 발사 Plane.prototype.shoot = function () { var bulletPosX = this.x + this.width / 2; this.bullets.push(new Bullet({ x: bulletPosX, y: this.y, size: this.bulletSpeed })); this; };//메서드: 총알 그리기 Plane.prototype.drawBullets = function () { var bullets = this.bullets; var i = bullets.length; (i--) { var bullet = bullets[i]; bullet.fly(); if (bullet.y <= 0) { bullets.splice(i, 1) } bullet.draw();
키보드 이벤트의 상태는 다음과 같습니다.
왼쪽 버튼(keyCode=37)과 오른쪽 버튼(keyCode=39)을 눌렀을 때(keydown) 기체가 계속 움직여야 하고, 키업을 놓았을 때 움직이지 않기 때문입니다. 스페이스(keyCode=32) 또는 위쪽 화살표 키(keyCode=38)를 누르면(keydown) 총알이 발사되고, 놓으면 키업 발사가 중지됩니다. 또한 게임을 일시 중지하려면 Enter 키(keyCode=13)를 누르세요. 따라서 onkeydown 및 onkeyup이 키를 누르거나 떼는지 여부를 모니터링하려면 KeyBoard 개체를 정의해야 합니다.
왼쪽 키와 오른쪽 키는 서로 모순되기 때문에 안전을 위해 왼쪽 키를 누를 때 오른쪽 키를 false로 설정해야 합니다. 마우스 오른쪽 버튼을 클릭해도 마찬가지입니다.
//키보드 이벤트 var KeyBoard = function () { document.onkeydown = this.keydown.bind(this); document.onkeyup = this.keyup.bind(this);} //KeyBoard 객체 KeyBoard.prototype = {pressedLeft: false,pressedRight: false,pressedUp:false,holdLeft:false,holdRight:false,pressedSpace:false,pressedEnter:false,keydown:function(e) { var key = e.keyCode; 스위치(키) { 케이스 32://Space - 총알 발사 this.pressedSpace = true; break; 케이스 37://왼쪽 화살표 키 this.pressedLeft = true; false; this.heldRight = false; case 38://위쪽 화살표 키 - 글머리 기호 실행 this.pressedUp = true; = false; this.pressedRight = true; case 13://Enter 키 - 게임 일시 중지 this.pressedEnter = true } }, keyup: function (e) { var key = e .keyCode; 스위치(키) { 사례 32: this.pressedSpace = false; 사례 37: this.pressedLeft = false; this.pressedUp = false; 사례 39: this.pressedRight = false; 사례 13: this.pressedEnter = false;게임 로직
게임 객체(GAME)에는 init(초기화), binEvent(바인딩 버튼), setStatus(게임 상태 업데이트), play(게임 내), stop(일시 중지), end(종료) 등 전체 게임의 로직이 포함되어 있습니다. ., in 이것은 설명을 확장하지 않습니다. 몬스터 생성, 게임 요소 그리기 등의 기능도 포함되어 있습니다.
//전체 게임 객체 var GAME = { //일련의 논리 함수 // 게임 요소 함수}1. 초기화
초기화 함수는 주로 항공기의 초기좌표, 항공기의 이동범위, 몬스터의 이동범위를 정의하고, 스코어, 몬스터 배열을 초기화하고, KeyBoard 객체를 생성하고, 1회만 실행한다.
/** * 초기화 함수, 이 함수는 한 번만 실행됩니다. * @param {object} opts * @return {[type]} [description] */init: function (opts) { //Set opts var opts = Object.sign ( {}, opts, CONFIG);//모든 매개변수 병합 this.opts = opts; this.status = 'start' //항공기 객체의 초기 좌표 계산 this.planePosX = canvasWidth / 2 - opts.planeSize.width; this.planePosY = canvasHeight - opts.planeSize.height - opts.canvasPadding; //평면 제한 좌표 this.planeMinX = opts.canvasPadding; this.planeMaxX = canvasWidth - opts.canvasPadding - opts.planeSize. width; //적의 이동 영역을 계산합니다. this.enemyMinX = opts.canvasPadding; canvasWidth - opts.canvasPadding - opts.enemySize; //점수가 0으로 설정됩니다. this.enemies = []; this.bindEvent(); ) },2. 바인드 버튼 이벤트
여러 게임 장면에는 게임 시작(playBtn), 다시 시작(replayBtn), 게임의 다음 레벨(nextBtn), 계속하기 위한 게임 일시 중지(stopBtn) 버튼이 포함되어 있기 때문입니다. 다양한 버튼에 대해 다양한 이벤트를 수행해야 합니다.
우선 var self = this;를 정의하는 이유는 this의 사용법입니다. BindEvent 함수에서 이것은 GAME 개체를 가리키고, playBtn.onclick = function() {};에서는 playBtn을 가리킵니다. 왜냐하면 playBtn에는 play() 이벤트만 없기 때문입니다. GAME 개체가 있습니다. 따라서 GAME 객체를 self 변수에 할당한 후 playBtn.onclick = function() {};에서 play() 이벤트를 호출할 수 있습니다.
replayBtn 버튼은 실패 및 정리 시나리오 모두에 나타나므로 얻은 것은 모든 .js-replay의 컬렉션입니다. 그런 다음 forEach는 각 replayBtn 버튼을 반복하고 레벨과 점수를 재설정하고 play() 이벤트를 호출합니다.
binEvent: function () { var self = this; var playBtn = document.querySelector('.js-play'); var replayBtn = document.querySelectorAll('.js-replay'); var nextBtn = document.querySelector('. js-next'); var stopBtn = document.querySelector('.js-stop'); 게임 버튼 바인딩 시작 playBtn.onclick = function () { self.play() }; //게임 버튼 바인딩 다시 시작 replayBtn.forEach(function (e) { e.onclick = function () { self.opts.level = 1 ; self.play(); self.score = 0; totalScoreText.innerText = self.score }); 다음 레벨 게임 버튼 바인딩 nextBtn.onclick = function () { self.opts.level += 1; self.play() }; // 게임을 일시 중지하고 버튼 바인딩을 계속합니다. stopBtn.onclick = function () { self. ('재생 중'); self.updateElement() };3. 항공기 생성
createPlane: function () { var opts = this.opts; this.plane = new Plane({ x: this.planePosX, y: this.planePosY, width: opts.planeSize.width, height: opts.planeSize.height, minX : this.planeMinX, 속도: opts.planeSpeed, maxX: this.planeMaxX, planeIcon: opts.planeIcon });}4. 몬스터 그룹 생성
몬스터는 그룹으로 나타나며 각 레벨에 있는 몬스터의 수도 다르기 때문에 두 for 루프의 기능은 몬스터 행을 생성하고 레벨 수에 따라 몬스터의 레벨 행을 늘리는 것입니다. 또는 몬스터의 속도를 높여(속도: 속도 + i,) 각 레벨의 난이도를 높이는 등의 방법이 있습니다.
//적 생성 createEnemy: function (enemyType) { var opts = this.opts; var ones = this.enemies; var numPerLine = opts.numPerLine; var gap = opts .enemyGap; var 크기 = opts.enemySize; var 속도 = opts.enemySpeed; //적 레벨의 각 레벨에 대한 행을 추가합니다. for (var i = 0; i < level; i++) { for (var j = 0; j < numPerLine; j++) { //종합 요소의 매개변수 var initOpt = { x : 패딩 + j * (크기 + 간격), y: 패딩 + i * (크기 + 간격), 크기: 크기, 속도: 속도, 상태: 적 유형, 적 아이콘: opts.enemyIcon, 적BoomIcon: opts.enemyBoomIcon }; 적.push(new Enemy(initOpt)) } } 적 반환;5. 몬스터 업데이트
몬스터 배열의 x 값을 가져와서 캔버스 경계에 도달했는지 확인합니다. 경계에 도달하면 몬스터가 아래로 이동합니다. 동시에, 몬스터의 상태도 모니터링해야 하며, 일반 상태의 몬스터가 명중되었는지, 폭발 상태의 몬스터, 사라진 몬스터는 배열에서 제거되고 동시에 득점되어야 합니다.
//적 상태 업데이트 updateEnemeis: function () { var opts = this.opts; var plane = this.plane; var ones = this.enemies; var isFall = false; var onesX = getHorizontalBoundary(enemies); if (enemiesX.minX < this.enemyMinX ||적X.maxX >= this.enemyMaxX) console.log('enemiesX.minX',적X.minX);console.log('enemiesX.maxX',적X.maxX); opts.enemyDirection = opts.enemyDirection === '오른쪽' : '오른쪽 '; console.log('opts.enemyDirection', opts.enemyDirection); isFall = true } //적을 업데이트합니다. (i--) { var = 적[i]; if (isFall) { 적.down(); } 적.방향(opts.enemyDirection); 스위치 (enemy.status) { 케이스 '정상': if (plane.hasHit(enemy)) .booming(); } break; 케이스 '붐': 적.splice(i, 1); 기본값: 중단; } } }
getHorizontalBoundary 함수의 기능은 배열의 각 요소의 x 값을 순회하고, 더 크거나 작은 값을 필터링하고, 배열의 최대 및 최소 x 값을 얻는 것입니다.
//배열의 수평 경계 함수를 가져옵니다. getHorizontalBoundary(array) { var min, max; array.forEach(function (item) { if (!min && !max) { min = item.x; max = item.x; } else { if (item.x < min) { min = item.x; } if (item.x > max) { max = item.x } } }); 최대 }}6. 키보드 패널 업데이트
Enter 키를 누르면 stop() 기능이 실행되고, 왼쪽 버튼을 누르면 기체가 왼쪽으로 이동하고, 오른쪽 버튼을 누르면 기체가 오른쪽으로 이동하며, 스페이스 버튼을 누르면 기체의 총알 발사가 실행됩니다. 직선으로 연결하려면 여기에서 keyBoard를 설정하고 keyBoard.pressedSpace는 false입니다.
updatePanel: function () { var plane = this.plane; var keyBoard = this.keyBoard; if (keyBoard.pressedEnter) { this.stop() return; if (keyBoard.pressedLeft || keyBoard.heldLeft) 방향('왼쪽'); } if (keyBoard.pressedRight || keyBoard.heldRight) { plane.direction('오른쪽') } if (keyBoard.pressedUp || keyBoard.pressedSpace) { keyBoard.pressedUp = false;7. 모든 요소를 그립니다.
draw: function () { this.renderScore(); this.plane.draw(); this.enemies.forEach(function (enemy) { //console.log('draw:this.enemy',enemy); 적. 그리다(); }); },8. 모든 요소 업데이트
먼저 몬스터 배열의 길이가 0인지 확인합니다. 0이고 레벨이 totalLevel과 같으면 레벨이 통과되었음을 의미합니다. 그렇지 않으면 y 좌표이면 다음 레벨을 위한 게임 준비 화면이 표시됩니다. 몬스터 배열의 값이 항공기의 y 좌표에 몬스터의 높이를 더한 값보다 크면 게임이 실패했음을 나타냅니다.
캔버스 애니메이션의 원리는 캔버스를 지속적으로 그리고, 업데이트하고, 지우는 것입니다.
게임 일시 중지의 원칙은 requestAnimationFrame() 함수가 실행되는 것을 방지하는 것이지만 요소를 재설정하지는 않는 것입니다. 따라서 상태가 정지로 판단되면 해당 기능은 점프아웃됩니다.
//모든 요소의 상태 업데이트: function () { var self = this; var opts = this.enemies; if (enemies.length === 0) { if (opts.level = == opts.totalLevel) { this.end('all-success'); } else { this.end('success') } return; - 1].y >= this.planePosY - opts.enemySize) { this.end('failed'); return } //캔버스 지우기 ctx.clearRect(0, 0, canvasWidth, canvasHeight); canvas this .draw(); //요소 상태 업데이트 this.updatePanel(); //계속적으로 updateElement를 반복합니다. requestAnimationFrame(function () { if(self.status === '중지'){ return; }else{ self.updateElement() } });마지막에 쓰세요
위의 단계를 통해 게임의 기본 기능이 완료됩니다. 시작, 종료, 점수 계산 등을 포함한 기타 게임 프로세스 제어는 여기서 설명하지 않습니다.
최적화할 수 있는 것: 스페이스바를 누르고 있으면 총알이 연속으로 발사될 수 있습니다. 그러나 다시 방향키를 눌렀더니 더 이상 총알을 발사할 수 없다는 것을 알게 되었습니다. 움직일 수 있을 때 계속해서 총알을 발사할 수 있는 것이 가장 좋습니다.
또한 이 게임은 모바일 버전으로 확장 및 변경이 가능하며, 키보드 부분이 터치이벤트(터치스타트, 터치무브)로 변경되어 있어 캔버스 크기가 결정됩니다. , 터치엔드) 몬스터의 외형도 화면 상단에서 무작위로 떨어지는 것으로 변경 가능하며, 몬스터의 체력이 증가합니다(예: 4회 사격하면 사라집니다).
다운로드 주소: https://github.com/littleyljy/shoot
위의 내용은 이 기사의 전체 내용입니다. 모든 분들의 학습에 도움이 되기를 바랍니다. 또한 모든 분들이 VeVb Wulin Network를 지지해 주시길 바랍니다.