Profile

Defense the Bucheon Univ.

OVERVIEW
Khan Academy 플랫폼으로 만든 클릭 기반 디펜스 게임
STACK
Javascript
DATE
2019/06/30
LAST UPDATE
2022/06/22 07:58
Status
Complete

Development URL

Repository

개요

몰려드는 적으로 부터 건물을 지키는 마우스 클릭 기반 디펜스 게임 입니다.

개발 배경

Khan Academy 에서는 이미지와 같이 미리 정의된 자바스크립트 기반 드로잉 함수들과 IDE 그리고, 실시간으로 출력되는 결과창을 제공합니다.
Khan Academy 플렛폼을 이용하여 자유로운 프로그램을 작성하는 학과 수업 과제의 결과물입니다.
그래픽 드로잉 메서드들이 모두 미리 정의가 되어 있었다는 점.
소스 코드의 출력물을 컴파일, 빌드 과정없이 실시간으로 확인할 수 있다는 점.
두 가지를 고려하여 과제로 게임 제작이 적합하다고 판단하였습니다.
일반적인 게임 로직인 무한 루프 안에 오브젝트 드로잉 - 오브젝트 상태 검사 - 오브젝트 상태 변경 을 반복하도록 프로그램을 구조화 하였습니다.
또한 게임을 구성하는 각 객체들의 생성자 함수와 Prototype 함수들을 정의하고,
객체들의 Controller 와 GameController 객체를 활용하여 게임 로직을 구현하였습니다.

개발 후기 (수정)

이전에 사용했던 자바스크립트는 html과 함께 동작하는 자바스크립트 였습니다.
이벤트에 따라 dom 객체를 조작하는 자바스크립트만 사용해봤기에 자바스크립트 문법의 코어 구조에 대해서는 알고 있던 것이 전혀 없었습니다.
하지만 프로젝트를 진행하면서 prototype 를 활용한 자바스크립트에서 객체지향 프로그래밍 방법을 적용해 보고, 생성자 함수, 프로토타입, 프로토타입 체이닝, 실행 컨텍스트 등 자바스크립트의 코어 구조에 대해 접할 수 있었던 좋은 기회였습니다.
7천여 줄의 소스코드를 한 줄씩 작성해가며 완성된 프로그램이 잘 동작하는 것을 보고 보람을 느꼈습니다.
다만 아쉬웠던 것은 객체간 의존성을 느슨하게 설계하지 못했고, 프로그램의 전체 상태 관리를 많은 flag 에 의존한 것과 if 문의 다중첩을 빈번히 사용한 점에서 코드 스멜을 남기게 되었습니다.
내가 작성한 소스코드를 다시 보게 되었을 때, 이해하기 힘든 로직을 발견하였고
이로 인해 클린 코드로 작성하는 것의 중요성을 알게 된 프로젝트였습니다.

주요 기능 ( 게임 설명 )

몰려오는 적들로 부터 Bucheon Univ. 를 지키세요!
적 위에 에임을 조정하고 클릭하면 총알이 발사 됩니다.
머리를 조준하면 더 많은 데미지가 들어갑니다.
상점 - House 에서 Gun man, Repair Man 을 고용하세요.
상점 - House 에서 Upgrade wall, Repair House 를 통해 베이스를 더 견고하게 하세요.
상점 - Guns 에서 다양한 총을 구매하세요.
Tower 는 베이스를 지키기 위한 최종 병기입니다.
총의 종류별로 다양한 업그레이드를 할 수 있습니다.
시간이 지나면 더 강력한 적과 많은 적이 나옵니다.
기지의 체력이 0으로 떨어지면 패배합니다.

프로그램의 주요 로직

게임의 메인 루프 로직

var game = new GameController(new Player(), new EnemyHandler(), new Base()); game.StoreSync(); draw = function() { background(255, 255, 255); game.BaseHandler.Draw(); if(!game.Start()) { if(!game.Over()) { game.NextRound(); } else { game.OverFrame(); } } frameRate(24); };
JavaScript
draw = function() { } : 무한루프를 수행하는 draw 함수입니다.
GameController() : GameController 객체 생성자에 인자로 Player와 EnemyHandler, Base 를 받아 객체를 생성합니다.
GameController.prototype.Start 프로토타입은 게임이 첫 라운드인가? 의 여부에 따라 boolean 타입의 내부 flag 를 설정합니다.
따라서 게임이 시작되면 내부 flag 는 false 로 해당 if 문은 계속 참 조건을 가지게 됩니다.
GameController.prototype.Over 프로토타입은 Base의 체력이 0 이하 인가? 의 여부에 따라 boolean 타입의 내부 flag 를 설정합니다.
따라서 Base의 체력이 0이 아니라면 해당 if 문은 계속 참 조건을 가지게 됩니다.
GameController.prototype.NextRound 프로토타입은 실질적인 메인 루프로 라운드 카운터가 1씩 증가하며 라운드별 게임 난이도를 조절하게 됩니다.
NextRound 프로토타입이 수행하는 작업은 다음과 같습니다.
GameController 객체의 내부에 주입된 플레이어 객체, 적 객체, 기지 객체를 컨트롤 하는 Controll 프로토타입을 호출합니다.
각 객체 별 Controll 프로토타입은 무한루프 안에서 지속적으로 화면에 드로잉 됩니다.
객 객체들이 무한 루프의 프레임 별로 수행해야할 작업을 수행합니다.
GameController.prototype.NextRound = function() { ... else { //########## Game Main Loop ########## this.BaseControll(); this.EnemyControll(); this.PlayerControll(); this.TowerControll(); //Tower Controll has some problem that i couldn't solve this.BaseHandler.DrawGunMan(this.EnemyHandler); this.BaseHandler.RepairManAction(); } ... }
JavaScript
플레이어 객체, 적 객체, 기지 객체의 Controll 프로토 타입은 다음과 같습니다.
GameController.prototype.BaseControll = function() { this.BaseHandler.Draw(); // 기지 객체가 지속적으로 화면에 그려집니다. }; GameController.prototype.EnemyControll = function() { this.EnemyHandler.Generate(this.round); // 적 객체는 라운드 카운터에 따라 난이도 조절 후 생성됩니다. this.EnemyHandler.Attack(this.BaseHandler, this.PlayerHandler); // 생성된 적들은 기지 객체를 향해 공격을 수행합니다 그리고 플레이어는 이를 저지합니다. }; GameController.prototype.PlayerControll = function() { this.PlayerHandler.DrawMoney(); // 플레이어는 적을 처치하면 돈을 얻습니다. this.DrawPlayerGun(); // 마우스 클릭시 플레이어가 장착한 무기의 발사 이펙트가 그려집니다. this.PlayerHandler.Shooting(this.EnemyHandler); // 플레이어가 클릭한 위치가 적의 Hit box 안에 포함되어 있는지 검사합니다. };
JavaScript
추가적으로 기지를 방어하는 Tower의 Controll 프로퍼티와 GunMan, RepairMan 의 Contoll 로직을 수행합니다.

플레이어가 마우스 클릭시 총 발사 로직

if( this.gun_avail && (mouseIsPressed && mouseButton === LEFT) ) { // gun 을 사용할 수 있고 마우스 좌클릭 이벤트가 발생하면 this.DrawAim(true); // 총알 발사 이펙트를 드로잉 합니다. if(this.gun_type === 0 || this.gun_type === 2) { // 단발 이펙트가 필요한 권총이나 기관총 타입인 경우 } else if(this.gun_type === 1) { // 다발 이펙트가 필요한 샷건 타입인 경우 } else if(this.gun_type === 3) { // 폭팔 이벤트가 필요한 RPG 타입인 경우 } this.gun_loaded--; }
JavaScript
단발 이펙트 - 적 Hit Box 충돌 로직
if(this.gun_type === 0 || this.gun_type === 2) { EnemyHandler.GetShot(mouseX, mouseY, this.gun_dmg[this.gun_type]); // 총을 맞은 적은 gun type에 따른 데미지를 입게 됩니다. this.DrawMissFired(mouseX, mouseY); // 단발 이펙트를 드로잉 합니다 }
JavaScript
다발 이펙트 - 적 Hit Box 충돌 로직
else if(this.gun_type === 1) { for(var i = 0; i < this.shotgun_density; i++) { var x = parseInt(random(mouseX-this.shotgun_range, mouseX+this.shotgun_range), false); var y = parseInt(random(mouseY-this.shotgun_range, mouseY+this.shotgun_range), false); // 랜덤한 범위로 단발 이펙트가 shotgun_density 개수 만큼 생성됩니다. EnemyHandler.GetShot(x, y, this.gun_dmg[this.gun_type]); // 총을 맞은 적은 gun type에 따른 데미지를 입게 됩니다. this.DrawMissFired(x, y); // 다발 이펙트를 드로잉 합니다. } }
JavaScript
폭팔 이펙트 - 적 Hit Box 충돌 로직
else if(this.gun_type === 3) { var x = mouseX - this.rpg_range / 2; var y = mouseY - this.rpg_range / 2; for(var i = 0; i < this.rpg_range + 1; i++) { for(var l = 0; l < this.rpg_range + 1; l++) { EnemyHandler.GetShot(x+l, y+i, this.gun_dmg[this.gun_type]); // RPG 범위 내의 모든 적은 Hit 하게 됩니다. } } this.DrawRpgFired(mouseX, mouseY); // 폭팔 이펙트를 드로잉 합니다. }
JavaScript
생성되어 있는 적 중 어떤 적이 Hit 했는가?
EnemyHandler.prototype.GetShot = function(attackX, attackY, dmg) { // attackX, attackY 는 마우스 클릭 시 좌표 값 입니다. for(var i = 0; i < this.enemy_handler.length; i++) { // 생성되어 있는 적의 개수 만큼 반복하며 if(this.enemy_handler[i] !== null) { // 동적 생성된 적의 객체 리스트를 하나씩 검사합니다. if(this.enemy_handler[i].type === <적 타입> ) { // 객체 리스트의 i 번째 적의 type이 <적 타입> 이라면 if(gf.CheckOnRect( 마우스 클릭 좌표, 적 타입 별 Hit box ) { this.enemy_handler[i].health -= dmg; // 객체 리스트의 i 번째 적의 health 는 데미지 만큼 감소 시킨다. } } } } }
JavaScript