Merge pull request #39 from minwook-shin/ko_translation

[WIP] Add korean translations
This commit is contained in:
Jen Looper
2020-11-29 11:19:28 -05:00
committed by GitHub
30 changed files with 5550 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
# Space 게임 제작하기 파트 1: 소개
![video](../images/pewpew.gif)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 게임 개발의 상속과 구성
이전 강의에서는, 프로젝트의 범위가 매우 작았으므로, 만든 앱의 디자인 아키텍처에 대해 걱정할 필요가 없었습니다. 그러나, 애플리케이션의 크기와 범위가 커지면, 아키텍처 결정이 더 힘듭니다. JavaScript에서 더 큰 응용 프로그램을 만드는 데는 두 가지 주요 방식이 있습니다: *composition* 또는 *inheritance*. 둘 다 장점과 단점이 존재하지만 게임의 맥락에서 설명할 것 입니다.
✅ 가장 유명한 프로그래밍 책 중에는 [design patterns](https://en.wikipedia.org/wiki/Design_Patterns)과 관련이 있습니다.
게임에서는 화면에 존재하는 객체인 `game objects`가 있습니다. `x``y` 좌표를 갖는 것이 특징인, 데카르트 좌표계에 있음을 의미합니다. 게임을 개발할 때 만드는, 모든 게임 객체를 가지고 있는 게임에는 공통적인 표준 속성이 있습니다:
- **location-based** 전부는 아니지만, 대부분의 게임 요소는 위치 기반입니다. `x``y`, 위치를 가지고 있음을 의미합니다.
- **movable** 새 위치로 이동할 수 있는 객체입니다. 이는 일반적으로 영웅, 몬스터 혹은 NPC(non player character)이지만, 예를 들어, 나무와 같은 정적 객체는 아닙니다.
- **self-destructing** 이런 객체는 삭제 작업을 위해 설정되기 전 일정한 시간에만 존재합니다. 일반적으로 이 객체가 더 이상 렌더링하지 않도록 게임 엔진에 알리기 위해서는 `dead` 또는 `destroyed` 논리 자료형으로 표시됩니다.
- **cool-down** 'Cool-down'은 짧은-수명 객체의 일반적인 속성입니다. 일반적인 예시는 몇 milli 초 동안만 보이는 폭발과 같은 텍스트 또는 그래픽 이펙트입니다.
✅ Pac-Man과 같은 게임을 생각해보세요. 이 게임에서 위 나열된 4가지 객체 타입을 구별할 수 있나요?
### 행동 표현
설명한 모든 것은 게임 객체가 가질 수 있는 동작입니다. 그럼 어떻게 인코딩 될까요? 이 동작을 클래스 혹은 객체와 관련된 메소드로 나타낼 수 있습니다.
**Classes**
아이디어로는 클래스에 특정 동작을 추가하기 위해 `inheritance`과 함께 `classes`를 사용하는 것입니다.
✅ 상속은 이해해야 할 중요한 컨셉입니다. [MDN's article about inheritance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)로 더 배워보세요.
코드를 통해 표현되는, 게임 객체는 일반적으로 다음과 같습니다:
```javascript
//set up the class GameObject
class GameObject {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
}
//this class will extend the GameObject's inherent class properties
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
}
//this movable object can be moved on the screen
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//this is a specific class that extends the Movable class, so it can take advantage of all the properties that it inherits
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
}
}
//this class, on the other hand, only inherits the GameObject properties
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
}
}
//a hero can move...
const hero = new Hero();
hero.moveTo(5,5);
//but a tree cannot
const tree = new Tree();
```
✅ 잠시 시간을 내어 Pac-Man 영웅 (예를 들어, Inky, Pinky 혹은 Blinky)과 JavaScript로 작성되는 방법을 다시 구상하십시오.
**Composition**
객체 상속을 처리하는 다른 방법으로는 *Composition* 을 사용하는 것입니다. 그러면, 객체는 다음과 같이 동작을 표현합니다:
```javascript
//create a constant gameObject
const gameObject = {
x: 0,
y: 0,
type: ''
};
//...and a constant movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//then the constant movableObject is composed of the gameObject and movable constants
const movableObject = {...gameObject, ...movable};
//then create a function to create a new Hero who inherits the movableObject properties
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
}
//...and a static object that inherits only the gameObject properties
function createStatic(x, y, type) {
return {
...gameObject
x,
y,
type
}
}
//create the hero and move it
const hero = createHero(10,10);
hero.moveTo(5,5);
//and create a static tree which only stands around
const tree = createStatic(0,0, 'Tree');
```
**어떤 패턴을 사용해야 하나요?**
어떤 패턴을 선택하는 지는 여러분에게 달려 있습니다. JavaScript는 이러한 패러다임을 모두 지원합니다.
--
게임 개발에서 흔히 볼 수 있는 또 다른 패턴으로는 게임의 사용자 경험과 성능 문제를 다룹니다.
## Pub/sub 패턴
✅ Pub/Sub은 'publish-subscribe'를 의미합니다
이 패턴은 애플리케이션에서 각자 다른 부분이 서로 알고 있으면 안된다는 아이디어를 다룹니다. 왜 그럴까요? 여러 부분이 분리 되어있다면 보통 무슨 일이 일어나는지 쉽게 볼 수 있습니다. 필요한 경우에는 바로 동작을 변경하기도 더 쉽습니다. 어떻게 해야 할까요? 몇 가지 컨셉을 정하고 지킵시다.
- **message**: 메시지는 일반적으로 선택적 payload (명확한 메시지 내용 데이터 조각)와 함께 제공되는 텍스트 문자열입니다. 게임의 일반적인 메시지는 `KEY_PRESSED_ENTER` 일 수 있습니다.
- **publisher**: 이 요소는 메시지를 *publishes*하고 모든 구독자에게 보냅니다.
- **subscriber**: 이 요소는 특정 메시지를 *listens* 하고 메시지를 수신한 결과로 레이저 발사와 같은 일부 작업을 수행합니다.
implementation은 크기가 매우 작지만 매우 강한 패턴입니다. 구현 방법은 다음과 같습니다:
```javascript
//set up an EventEmitter class that contains listeners
class EventEmitter {
constructor() {
this.listeners = {};
}
//when a message is received, let the listener to handle its payload
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//when a message is sent, send it to a listener with some payload
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
}
}
}
```
위의 코드를 사용하기 위해서 매우 작은 implementation을 만들 수 있습니다:
```javascript
//set up a message structure
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
};
//invoke the eventEmitter you set up above
const eventEmitter = new EventEmitter();
//set up a hero
const hero = createHero(0,0);
//let the eventEmitter know to watch for messages pertaining to the hero moving left, and act on it
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
//set up the window to listen for the keyup event, specifically if the left arrow is hit, emit a message to move the hero left
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
});
```
위에서 우리는 키보드 이벤트 `ArrowLeft`를 연결하고, `HERO_MOVE_LEFT` 메시지를 보냅니다. 우리는 그 메시지를 듣고 최종적으로 `hero`을 움직입니다. 이 패턴의 강점은 이벤트 리스너와 영웅이 서로 알지 못한다는 점입니다. `ArrowLeft``A`키로 다시 매핑할 수도 있습니다. 추가적으로 eventEmitter의 `on` 함수를 조금 수정하여 `ArrowLeft`로 완전히 다른 작업을 할 수 있습니다:
```javascript
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
```
게임의 몸집이 커질 때 더 복잡해진다면, 이 패턴은 복잡성을 동일하면서 코드도 깨끗하게 유지합니다. 이 패턴을 채택하는 것은 정말 추천드립니다.
---
## 🚀 도전
pub-sub 패턴이 어떻게 게임을 발전시킬 수 있는지 생각해보세요. 어떤 부분이 이벤트를 어떻게 발생하고, 반응해야 하나요? 이제는 창의력을 발휘하고, 새로운 게임과 그 부분에 대해 어떻게 작동하는지 생각해볼 수 있는 기회입니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[reading about it](https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber)으로 Pub/Sub에 대해 조금 더 배워봅시다.
## 과제
[Mock up a game](assignment.md)

View File

@@ -0,0 +1,216 @@
# Space 게임 제작하기 파트 2: Canvas에 영웅과 몬스터 그리기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
## Canvas
canvas는 내용이 없는 게 기본인 HTML 요소입니다; 빈 상태입니다. 그리는 작업으로 추가해야 합니다.
✅ MDN애서 [Canvas API에 대하여 더](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) 읽어보세요.
보통 페이지 본문의 일부로 선언되는 방법은 다음과 같습니다:
```html
<canvas id="myCanvas" width="200" height="100"></canvas>
```
위에서 우리는 `id`, `width``height`를 설정합니다.
- `id`: 상호 작용을 해야 할 순간에 참조할 수 있도록 지정하세요.
- `width`: 요소의 너비입니다.
- `height`: 요소의 높이입니다.
## 간단한 geometry 그리기
캔버스는 데카르트 좌표계로 사물을 그립니다. 따라서 x-축과 y-축을 이용하여 무언가의 위치를 나타냅니다. 위치 `0,0`은 죄측 상단이며 우측 하단은 캔버스의 너비와 높이라고 말한 위치입니다.
![the canvas's grid](canvas_grid.png)
> Image from [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)
캔버스 요소에 그리려면 다음 단계를 거쳐야 합니다:
1. 캔버스 요소에 **참조를 가져옵니다**.
1. 캔버스 요소에 있는 Context 요소로 **참조를 가져옵니다**.
1. 컨텍스트 요소를 사용하여 **그리는 작업을 수행**합니다.
위 단계의 코드는 일반적으로 다음과 같습니다:
```javascript
// draws a red rectangle
//1. get the canvas reference
canvas = document.getElementById("myCanvas");
//2. set the context to 2D to draw basic shapes
ctx = canvas.getContext("2d");
//3. fill it with the color red
ctx.fillStyle = 'red';
//4. and draw a rectangle with these parameters, setting location and size
ctx.fillRect(0,0, 200, 200) // x,y,width, height
```
✅ Canvas API는 2D 모양에 가장 초점이 맞추어져 있습니다, 그러나 웹사이트에서 3D 요소를 그려야 된다면, [WebGL API](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API)를 사용해야 할 수도 있습니다.
Canvas API를 사용하면 다음과 같은 모든 종류를 그릴 수 있습니다:
- **기하학 모양**, 직사각형을 그리는 방법은 이미 보여 주었지만, 더 많이 그릴 수 있습니다.
- **텍스트**, 원하는 폰트와 색상으로 텍스트를 그릴 수 있습니다.
- **이미지**, 예를 들면 .jpg 혹은 .png와 같은 이미지 어셋을 바탕으로 이미지를 그릴 수 있습니다.
✅ 시도 해보세요! 직사각형을 어떻게 그리는지 알고 있으면, 패이지에 원을 그릴 수 있나요? CodePen에서 흥미로운 캔버스 그림을 보세요. 여기 [particularly impressive example](https://codepen.io/dissimulate/pen/KrAwx)이 있습니다.
## 이미지 어셋 불러오고 그리기
`Image` 객체를 만들고 `src` 속성을 설정하여 이미지 어셋을 불러옵니다. 그런 다음 `load` 이벤트를 수신하여 사용할 준비가 되었는지 알 수 있습니다. 코드는 다음과 같습니다:
### 어셋 불러오기
```javascript
const img = new Image();
img.src = 'path/to/my/image.png';
img.onload = () => {
// image loaded and ready to be used
}
```
### Load asset 패턴
위의 내용을 이 구조로 래핑하는 것이 좋습니다, 그래서 쓰기 더 쉽고 완전히 불러올 수 있을 때만 조작하려고 시도합니다:
```javascript
async function loadAsset(path) {
return new Promise((resolve) => {
const img = new Image();
img.src = path;
img.onload = () => {
// image loaded and ready to be used
}
resolve(img);
})
}
// use like so
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
}
```
게임 어셋을 화면에 그리려면, 코드는 다음과 같습니다:
```javascript
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
ctx.drawImage(heroImg, canvas.width/2,canvas.height/2);
ctx.drawImage(monsterImg, 0,0);
}
```
## 이제 게임 제작을 시작할 시간입니다
### 무엇을 만드나요
Canvas 요소가 있는 웹 페이지를 만듭니다. 검은 화면 `1024 * 768`을 렌더링해야 합니다. 두 가지 이미지를 제공받았습니다:
- Hero ship
![Hero ship](solution/assets/player.png)
- 5*5 monster
![Monster ship](solution/assets/enemyShip.png)
### 개발 시작하기 위한 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Visual Studio Code에서 폴더의 복사본을 엽니다. 로컬 개발 환경을 설정해야 합니다, NPM과 Node가 설치되어있는 Visual Studio Code를 사용하는 것이 좋습니다. 컴퓨터에 `npm`이 설정되어 있지 않은 경우에, [방법은 다음과 같습니다](https://www.npmjs.com/get-npm).
`your_work` 폴더로 이동하여 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하세요. 지금은 비어있는 페이지지만, 곧 바뀔 것 입니다
> Note: 화면에서 변경점을 보고싶다면, 브라우저를 새로 고치세요.
### 코드 추가하기
필요한 코드를 `your-work/app.js`에 추가하여 아래 문제를 해결합니다
1. 검은 바탕으로 canvas **그리기**
> tip: `/app.js`의 TODO 아래에 적절히 두 줄을 추가하며, `ctx` 요소를 검은 색으로 설정하고 상단/좌측 좌표를 0,0으로 설정하고 높이와 너비를 캔버스와 동일하게 설정합니다.
2. 텍스쳐 **불러오기**
> tip: `await loadTexture`를 사용하여 플레이어와 적 이미지를 추가하고 이미지 경로를 전달합니다. 아직 화면에서 볼 수 없습니다!
3. 화면 중앙 하단의 중간에 영웅 **그리기**
> tip: `drawImage` API 를 사용하여 화면에 heroImg를 그립니다, `canvas.width / 2 - 45`와 `canvas.height - canvas.height / 4)`로 설정합니다.
4. 5*5 몬스터를 **그리기**
> tip: 이제 주석을 풀고 화면에 적을 그립니다. 그런 다음, `createEnemies` 함수로 가서 작성합니다.
먼저, 약간의 constants를 설정합니다:
```javascript
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
```
그런 다음, 화면에 몬스터 배열을 그리는 반복문을 만듭니다:
```javascript
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
## 결과
완성된 결과는 아래와 같이 보이게 됩니다:
![Black screen with a hero and 5*5 monsters](partI-solution.png)
## 솔루션
먼저 직접 해결해보고 문제가 발생한다면, [solution](solution/app.js)을 보세요
---
## 🚀 도전
2D-중심의 Canvas API로 그리는 방식에 대해 배웠습니다; [WebGL API](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API)를 살펴보고, 3D 개체를 그려보세요.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[reading about it](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)을 통해 Canvas API에 대해 자세히 알아보세요.
## 과제
[Play with the Canvas API](assignment.md)

View File

@@ -0,0 +1,389 @@
# Space 게임 제작하기 파트 3: 모션 추가하기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
외계인이 화면을 돌아다니기 전까지는 게임이 재미 없습니다! 이 게임에서는, 두 가지 타입의 동작을 씁니다:
- **키보드/마우스 동작**: 사용자가 키보드 또는 마우스와 상호작용하여 화면에서 개체를 움질일 때.
- **게임으로 움직이는 동작**: 게임이 일정 시간 간격으로 객체를 움직일 때.
그러면 화면에서 물건을 어떻게 움직일까요? 그것은 모두 직교 좌표에 관한 것입니다: 객체의 위치 (x, y)를 변경 한 뒤에 화면을 다시 그립니다.
일반적으로 화면에서 *이동*을 하려면 다음 단계가 필요합니다:
1. 객체의 **새로운 위치 설정하기**; 이는 객체가 움직인 것으로 인식하는 데 필요합니다.
2. **화면 비우기**, 화면은 그려지는 사이에 비워져야 합니다. 배경색으로 채운 사각형을 그려서 지울 수 있습니다.
3. 새로운 위치에서 **개체를 다시 그리기**. 최종적으로 한 위치에서 다른 위치로 객체를 이동합니다.
코드에서 다음과 같이 보일 수 있습니다:
```javascript
//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ 영웅을 초당 수 많은 프레임으로 다시 그리게 될 때 성능 비용이 발생하는 이유를 알 수 있나요? [alternatives to this pattern](https://www.html5rocks.com/en/tutorials/canvas/performance/)에 대하여 읽어보세요.
## 키보드 이벤트 제어하기
특정 이벤트를 코드에 연결하여 이벤트를 처리합니다. 키보드 이벤트는 전체 윈도우에서 연결되는 반면에 `click`과 같은 마우스 이벤트는 클릭하는 특정 요소에 연결할 수 있습니다. 이 프로젝트에서는 키보드 이벤트를 사용합니다.
이벤트를 처리하려면 윈도우의 `addEventListener ()` 메소드를 사용하고 두 개의 입력 파라미터를 제공해야 합니다. 첫 번째 파라미터는 이벤트의 이름입니다, 예시를 들자면 `keyup`과 같습니다. 두 번째 파라미터는 이벤트가 발생함에 따라 호출될 함수입니다.
여기는 예시입니다:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
```
키 이벤트의 경우에는 어떤 키를 눌렀는지 확인할 때 쓸 수 있는 이벤트로 두 가지 속성이 있습니다:
- `key`, 눌린 키의 문자열 표현입니다, 예를 들어 `ArrowUp` 입니다.
- `keyCode`, 숫자 표현입니다. 예를 들어 `37``ArrowLeft`에 해당합니다.
✅ 키 이벤트 조작은 게임 개발 외부에서 유용합니다. 이 기술의 다른 사용법은 무엇일까요?
### 특별한 키: a caveat
윈도우에 영향을 주는 몇 가지 *특별한* 키가 있습니다. 즉 `keyup` 이벤트를 듣고 이 특별한 키를 사용하면 영웅을 이동하여 가로 스크롤도 할 수 있다는 것을 의미합니다. 따라서 게임을 제작할 때는 이 내장 브라우저 동작을 *차단* 할 수 있습니다. 다음과 같은 코드가 필요합니다:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener('keydown', onKeyDown);
```
위 코드는 화살표-키와 스페이스 키의 *기본* 동작을 막습니다. *차단* 메커니즘은 `e.preventDefault()`를 호출할 때 발생합니다.
## 게임의 움직임
각 틱 또는 시간 간격에서 객체의 위치를 업데이트하는 `setTimeout()` 또는 `setInterval()` 함수 같은 타이머를 사용하여 스스로 움직일 수 있습니다. 다음과 같이 보입니다:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
})
```
## 게임 루프
게임 루프는 기본적으로 일정한 간격마다 호출되는 함수인 개념입니다. 사용자에게 보여줄 모든 것이 루프에 그려지므로 이것을 게임 루프라고 합니다. 게임 루프는 게임의 일부인 모든 게임 객체를 사용하여, 모종의 이유로 더 이상 게임의 일부가 아니지 않는 이상 다 그립니다. 예를 들면 객체가 레이저에 맞아 폭발한 적이 있다면 더 이상 현재 게임 루프의 일부가 아닙니다 (다음 단원에서 자세히 알아 볼 것입니다).
다음은 일반적으로 코드로 표현된 게임 루프의 모습입니다:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
캔버스를 다시 그리기 위해 위의 루프가 `200` milliseconds 마다 호출됩니다. 게임에 가장 적합한 간격을 고를 수 있습니다.
## Space 게임 계속하기
기존 코드를 가져와 확장합니다. 파트 I 에서 작성한 코드로 시작하거나 [Part II- starter](your-work)의 코드를 사용합니다.
- **영웅을 움직이기**: 화살표 키를 사용하여 영웅을 이동할 수 있도록 코드를 추가합니다.
- **적을 움직이기**: 적들이 주어진 속도로 상단에서 하단으로 이동할 수 있도록 코드를 추가합니다.
## 권장 단계
`your-work` 하위 폴더에 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
타이핑하여 `your_work` 폴더에서 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하면 영웅과 모든 적을 렌더링 해야하지만; 아무것도 움직이지 않습니다 - 아직!
### 코드 추가하기
1. `영웅``적` 그리고 `게임 객체`에 대한 **전용 객체를 추가합니다**. `x` 혹은 `y` 속성이 필요합니다. ([Inheritance or composition](../README.md) 파트를 기억하세요).
*힌트* `game object``x``y`가 있으면서 canvas에 그릴 수 있는 능력이 되어야 합니다.
>tip: 생성자가 아래와 같이 이루어진 새로운 GameObject 클래스를 추가하여, 시작한 뒤에 canvas로 그립니다:
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
this.y = y;
this.dead = false;
this.type = "";
this.width = 0;
this.height = 0;
this.img = undefined;
}
draw(ctx) {
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
}
}
```
이제, GameObject를 확장하여 영웅과 적을 생성합니다.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 98), (this.height = 50);
this.type = "Enemy";
let id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y)
clearInterval(id);
}
}, 300)
}
}
```
2. **키-이벤트 핸들러를 추가**하여 키 탐색을 처리합니다 (Hero를 상/하 좌/우로 이동).
*기억합시다* 데카르트 시스템이고, 좌측 상단은 `0,0`입니다. 또한 *기본 동작*을 중지하는 코드를 추가해야 합니다
>tip: onKeyDown 함수를 만들고 윈도우에 붙입니다.
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
이 지점에서 브라우저 콘솔을 확인해봅니다, 그리고 로깅되는 키 입력을 봅니다.
3. [Pub sub pattern](../README.md)으로 **구현합니다**, 이는 남은 파트를 따라가면서 코드를 깨끗하게 유지할 수 있습니다.
이 마지막 파트를 진행하면, 다음을 할 수 있습니다:
1. 윈도우에 **Add an 이벤트 리스너를 추가합니다**:
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
1. publish하고 메시지를 subscribe할 **EventEmitter 클래스를 생성합니다**:
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach((l) => l(message, payload));
}
}
}
```
1. EventEmitter를 설정하고 **constants를 추가합니다**:
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **게임을 초기화합니다**
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -=5 ;
})
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **게임 루프를 설정합니다**
window.onload 함수를 리팩터링하여 게임을 초기화하고 적절한 간격으로 게임 루프를 설정합니다. 레이저 빔도 추가합니다:
```javascript
window.onload = async () => {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
heroImg = await loadTexture("assets/player.png");
enemyImg = await loadTexture("assets/enemyShip.png");
laserImg = await loadTexture("assets/laserRed.png");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. 일정 간격으로 움직이는 적에 대한 **코드를 작성합니다**
`createEnemies()`함수를 리팩터링하여 적을 생성하고 새로운 gameObjects 클래스로 푸시합니다:
```javascript
function createEnemies() {
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
const enemy = new Enemy(x, y);
enemy.img = enemyImg;
gameObjects.push(enemy);
}
}
}
```
그리고 비슷한 과정으로 영웅에 대한 `createHero()` 함수를 추가합니다.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
그리고 마지막으로, 그리기를 시작할 `drawGameObjects()` 함수를 추가합니다:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
적들이 영웅 spaceship의 앞으로 나아가려고 합니다!
---
## 🚀 도전
보다가, 함수와 변수 및 클래스를 추가하기 시작하면 코드가 '스파게티 코드'로 변할 수 있습니다. 코드를 더 읽기 쉽게 구성하려면 어떻게 해야 될까요? 코드가 여전히 하나의 파일에 있어도, 어울리는 시스템을 기획하시기 바랍니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
프레임워크를 사용하지 않고 게임을 작성하는 동안, 게임 개발을 위한 JavaScript-기반 canvas 프레임워크가 많이 존재하고 있습니다. 시간을 내어 [about these](https://github.com/collections/javascript-game-engines)를 보시기 바랍니다.
## 과제
[Comment your code](assignment.md)

View File

@@ -0,0 +1,297 @@
# Space 게임 제작하기 파트 4: 레이저 추가하고 충돌 감지하기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
이 강의에서는 JavaScript로 레이저를 쏘는 방법을 배웁니다! 게임에 다음 두 가지를 추가합니다:
- **레이저**: 이 레이저는 영웅 우주선에서 수직 위쪽으로 발사되며
- **충돌 감지**, *쏘는* 기능 구현의 부분으로 몇 가지 멋진 게임 규칙을 추가할 예정입니다:
- **레이저로 적 때리기**: 레이저에 맞으면 적은 사망합니다
- **레이저로 화면 상단 도달하기**: 화면의 상단 부분을 맞으면 레이저는 파괴됩니다
- **적과 영웅 충돌하기**: 적과 영웅이 부딪히면 파괴됩니다
- **적이 화면 하단 도달하기**: 적이 화면 하단에 부딪히면 적과 영웅이 파괴됩니다
짧게 요약해보면, 여러분은 -- *영웅* -- 모든 적들이 화면 아래로 내려오기 전에 레이저로 모든 적을 때려야 합니다.
✅ 지금까지 작성된 최초의 컴퓨터 게임에 대해 약간 알아보새요. 어떻게 작동하나요?
함께 영웅이 됩시다!
## 충돌 감지하기
충돌은 어떻게 감지할까요? 게임 객체를 움직이는 직사각형으로 생각해야 합니다. 왜 물어볼까요? 게임 객체를 그리는 데 사용되는 이미지는 직사각형이기 때문입니다: `x`, `y`, `width` 그리고 `height`가 있습니다.
만약 두 직사각형이, 즉 영웅과 적이 *교차*하면, 충돌합니다. 그런 뒤에 발생할 일은 게임의 룰에 달려 있습니다. 따라서 충돌 감지를 구현하려면 다음이 필요합니다:
1. 게임 객체의 직사각형 표현을 얻는 방법은, 다음과 같습니다:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
2. 비교 함수는, 다음 함수와 같습니다:
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## 어떻게 파괴할까요
게임에서 물건을 파괴하려면 특정 간격으로 연결되는 게임 루프에서 이 아이템을 더 이상 그리지 않아야 한다고 게임에 알려야 합니다. 이 방법은 다음과 같이, 어떤 일이 발생했을 때 게임 객체를 *dead*으로 표시합니다:
```javascript
// collision happened
enemy.dead = true
```
그러고 다음과 같이, 화면을 다시 그리기 전에 *dead* 객체를 정렬합니다:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
```
## 어떻게 레이저를 발사할까요
레이저를 발사하는 것은 키-이벤트에 반응하고 특정 방향으로 움직이는 객체를 만드는 것으로 바뀝니다. 따라서 다음 단계를 해야합니다:
1. **레이저 객체 생성**: 영웅 함선의 상단에서, 생성되고 화면 상단으로 올라가기 시작합니다.
2. **키 이벤트에 코드 첨부**: 레이저를 쏘는 플레이어로 특정할 키보드의 키를 선택해야 합니다.
3. 키를 누를 때, **레이저처럼 보이는 게임 객체를 생성합니다**.
## 레이저 쿨다운
레이저는 *space*와 같은 키를 누를 때마다 발사되어야 합니다. 게임이 짧은 시간에 너무 많은 레이저를 생성하는 것을 막으려면 이를 해결해야 합니다. 해결 방법은 레이저를 자주 발사하도록 보장하는 타이머인, *cooldown*을 구현하는 것입니다. 다음과 같이 구현할 수 있습니다:
```javascript
class Cooldown {
constructor(time) {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}
}
class Weapon {
constructor {
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// produce a laser
this.cooldown = new Cooldown(500);
} else {
// do nothing - it hasn't cooled down yet.
}
}
}
```
✅ *cooldowns*에 대해 복습하려면 space 게임 시리즈의 1강을 참조하세요.
## 무엇을 만드나요
이전 강의에 존재한 기존 코드 (정리하고 리팩토링함)를 가져와서, 확장합니다. 파트 II에서 코드를 시작하거나 [Part III- starter](/your-work) 코드를 사용합니다.
> tip: 작업할 레이저는 이미 어셋 폴더에 있으므로 코드에서 참조합니다
- **충돌 감지를 추가합니다**, 레이저가 무언가 부딪칠 때 다음 규칙이 적용되어야 합니다:
1. **레이저가 적 때리기**: 레이저에 맞으면 적은 사망합니다
2. **레이저로 화면 상단 도달하기**: 화면의 상단 부분을 맞으면 레이저는 부서집니다
3. **적과 영웅 충돌하기**: 적과 영웅이 부딪히면 파괴됩니다
4. **적이 화면 하단 도달하기**: 적이 화면 하단에 부딪히면 적과 영웅이 파괴됩니다
## 권장 단계
`your-work` 하위 폴더에 생성된 파일을 찾습니다. 이는 다음을 포함하고 있어야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
타이핑하여 `your_work` 폴더에서 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하세요, 지금 바로 영웅과 모든 적을 렌더링해야합니다, 하지만 다 움직이지 않습니다 - 아직 :).
### 코드 추가하기
1. ***충돌을 처리하기 위해 게임 객체의 사각형 표현을 설정합니다** 아래 코드를 쓰면 `GameObject`의 사각형 표현을 얻을 수 있습니다. GameObject 클래스를 편집하여 확장합니다:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
2. **충돌을 확인하는 코드를 추가합니다** 이것은 두 개의 직사각형이 교차되는가에 대한 여부를 테스트하는 새로운 함수입니다:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **레이저 발사 기능 추가**
1. **키-이벤트 메시지 추가하기**. *space* 키는 영웅 함선 바로 위에 레이저를 만들어줘야 합니다. Messages 객체에 세 개의 상수를 추가합니다:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **space 키 제어하기**. `window.addEventListener` 키업 함수로 spaces를 제어합니다:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **리스너 추가하기**. `initGame()` 함수를 편집해서 space 바를 눌렀을 때 hero가 발사할 수 있도록 합니다:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
새로운 `eventEmitter.on ()` 함수를 추가해서 적이 레이저와 부딪칠 때 동작하도록 합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **객체 움직이기**, 레이저가 화면 상단으로 조금씩 이동하고 있는지 확인합니다. 저번처럼, `GameObject`를 확장하는 새로운 Laser 클래스를 만듭니다:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x,y);
(this.width = 9), (this.height = 33);
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
}, 100)
}
}
```
1. **충돌 제어하기**, 레이저에 대한 충돌 규칙을 구현합니다. 적에 충돌하는 객체를 테스트하는 `updateGameObjects ()` 함수를 추가합니다:
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// laser hit something
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
`window.onload`의 게임 루프에 `updateGameObjects()`를 추가해야 합니다.
4. 레이저의 **cooldown을 구현합니다**, 그래서 자주 발사할 수 있습니다.
마지막으로, cooldown을 할 수 있도록 Hero 클래스를 편집합니다:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 99), (this.height = 75);
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
}, 200);
}
canFire() {
return this.cooldown === 0;
}
}
```
여기에서 핵심은, 게임이 몇 가지 기능을 가지고 있다는 사실입니다! 화살표 키로 탐색하고, 스페이스 바로 레이저를 발사할 수 있으며, 적을 치면 사라지게 합니다. 잘 하셨습니다!
---
## 🚀 도전
폭발을 추가합니다! [the Space Art repo](../solution/spaceArt/readme.txt)에서 게임 어셋을 살펴보고 레이저가 외계인을 칠 때 폭발하도록 추가해보세요
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
지금까지 게임의 간격을 실험 해보세요. 바꾸면 어떻게 되나요? [JavaScript timing events](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/)에 대하여 더 읽어보시기 바랍니다.
## 과제
[Explore collisions](assignment.md)

View File

@@ -0,0 +1,189 @@
# Space 게임 제작하기 파트 5: 점수내고 살기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
이 강의에서는 어떻게 게임에서 점수를 내고 생명을 구하는 가에 대하여 배웁니다.
## 화면에 텍스트 그리기
화면에 게임 점수를 표시하려면, 화면에 텍스트를 두는 방법을 알아야 합니다. 답변은 canvas 객체에 `fillText()` 메소드를 사용한다고 할 수 있습니다. 사용할 글꼴, 텍스트 색상과 정렬(왼쪽, 오른쪽, 가운데)처럼 다른 측면으로 제어할 수 있습니다. 다음은 화면에 텍스트를 그리는 코드입니다:
```javascript
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "right";
ctx.fillText("show this on the screen", 0, 0);
```
✅ [how to add text to a canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text)에 대하여 더 읽어보세요, 그리고 더 멋지게 보이도록 자유롭게 느껴보세요!
## 게임 컨셉에서 생명
게임에서 생명을 가진다는 개념은 숫자에 불과합니다. space 게임의 맥락에서는 배가 피해를 입었을 때마다 생명을 하나씩 빼는 것이 일반적입니다. 숫자 대신 miniships이나 하트와 같은 그래픽으로 표현할 수 있다면 좋습니다.
## 무엇을 만드나요
게임에 다음을 추가하겠습니다:
- **게임 점수**: 적의 배가 파괴될 때마다, 영웅은 점수를 받아야하고, 하나의 배마다 100점을 제안합니다. 게임 점수는 좌측 하단에 보여야 합니다.
- **생명**: 여러분의 배는 세 생명이 있습니다. 적의 배로 부딪칠 때마다 생명을 잃습니다. 생명 점수는 우측 하단에 보여야되고 ![life image](solution/assets/life.png)로 만들어야 합니다.
## 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
타이핑해서 `your_work` 폴더에 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하면, 바로 영웅과 모든 적을 렌더링해야하며, 왼쪽과 오른쪽 화살표를 누르면, 영웅이 움직이고 적을 격추할 수 있습니다.
### 코드 추가하기
1. `solution/assets/` 폴더에서 `your-work` 폴더로 **필요한 어셋을 복사합니다**; `life.png` 어셋을 추가합니다. window.onload 함수에 lifeImg를 추가합니다:
```javascript
lifeImg = await loadTexture("assets/life.png");
```
1. 어셋의 배열에 `lifeImg`를 추가합니다:
```javascript
let heroImg,
...
lifeImg,
...
eventEmitter = new EventEmitter();
```
2. **변수를 추가합니다**. 총 점수(0)과 남은 생명(3)을 나타내는 코드를 추가하고, 이 점수를 화면에 출력합니다.
3. **`updateGameObjects()` 함수를 확장합니다**. `updateGameObjects()` 함수를 확장하여 적 충돌을 제어합니다:
```javascript
enemies.forEach(enemy => {
const heroRect = hero.rectFromGameObject();
if (intersectRect(heroRect, enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
}
})
```
4. **`life`과 `points`를 추가하기**.
1. **변수를 초기화합니다**. `Hero` 클래스의 `this.cooldown = 0` 아래에 생명과 점수를 설정합니다:
```javascript
this.life = 3;
this.points = 0;
```
1. **화면에 점수를 그립니다**. 이 값을 화면에 그립니다:
```javascript
function drawLife() {
// TODO, 35, 27
const START_POS = canvas.width - 180;
for(let i=0; i < hero.life; i++ ) {
ctx.drawImage(
lifeImg,
START_POS + (45 * (i+1) ),
canvas.height - 37);
}
}
function drawPoints() {
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "left";
drawText("Points: " + hero.points, 10, canvas.height-20);
}
function drawText(message, x, y) {
ctx.fillText(message, x, y);
}
```
1. **게임 루프에 메소드를 추가합니다**. `updateGameObjects()` 아래의 window.onload 함수에 다음 함수를 추가해야 합니다:
```javascript
drawPoints();
drawLife();
```
1. **게임 규칙을 구현합니다**. 다음 게임 규칙을 구현합니다:
1. **모든 영웅과 적의 충돌**에 대해 생명을 깍습니다.
깍기 위해서 `Hero` 클래스를 확장합니다:
```javascript
decrementLife() {
this.life--;
if (this.life === 0) {
this.dead = true;
}
}
```
2. **적을 공격하는 모든 레이저는**, 게임 점수 100점을 올립니다.
올리기 위해서 `Hero` 클래스를 확장합니다:
```javascript
incrementPoints() {
this.points += 100;
}
```
Collision Event Emitter에 다음 함수를 추가합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
})
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
});
```
✅ JavaScript/Canvas를 사용하여 만든 다른 게임을 찾으려면 약간 알아보세요. 공통된 특징은 무엇일까요?
이 작업이 끝날 즈음, 우측 하단에 작은 '생명' 배, 좌측 하단에 점수를 보여줘야 하며, 적과 부딪칠 때마다 생명의 개수가 감소하고 적을 쏠 때마다 점수가 증가하는 것을 볼 수 있습니다. 잘 했습니다! 게임이 거의 완료되었습니다.
---
## 🚀 도전
코드는 거의 완성되었습니다. 다음 단계를 상상할 수 있나요?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
게임 점수와 생명을 늘리거나 줄일 수 있는 몇 가지 방법을 조사해보세요. [PlayFab](https://playfab.com)처럼 흥미로운 게임 엔진이 있습니다. 이 중 하나를 사용하면 어떻게 게임을 향상시킬 수 있을까요?
## 과제
[Build a Scoring Game](assignment.md)

View File

@@ -0,0 +1,222 @@
# Space 게임 제작하기 파트 6: 끝과 재시작
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
게임에서 *조건을 표현하고 종료*하는 여러 방식이 있습니다. 게임이 종료된 이유를 말하는 것은 게임 크리에이터의 일입니다. 지금까지 만든 space 게임에 대해 말하고 있다고 가정하면, 몇 가지 이유가 있습니다:
- **`N`개의 적 배가 파괴되었습니다**: 게임을 여러 레벨로 나누면 레벨을 완료하기 위해 `N`개의 적 배를 부숴야하는 경우가 매우 흔합니다.
- **배가 파괴되었습니다**: 배가 부서지면 지는 게임이 분명 있습니다. 또 다른 일반적인 접근 방식은 생명의 컨셉을 가지고 있다는 점입니다. 배가 부서질 때마다 생명이 깍입니다. 모든 목숨을 잃으면 게임에서 집니다.
- **`N` 점수를 모았습니다**: 또 다른 종료 조건은 점수를 모으는 것입니다. 점수를 얻는 방법으로 각자 배를 파괴하는 것처럼 다양한 활동에 점수를 할당하거나 아이템이 부서질 때마다 *떨구는* 아이템을 수집하는 것은 매우 일반적입니다.
- **레벨을 완료했습니다**: 적 배를 `X` 번 부시거나, `Y` 점수를 수집하거나 특정 아이템을 수집하는 것처럼 여러 조건들을 여기에 포함할 수 있습니다.
## 다시 시작하기
사람들이 게임을 즐기고 있다면 다시 플레이하고 싶어합니다. 어떤 이유든지 게임이 끝나면 다시 시작할 수 있는 대안을 줘야합니다.
✅ 어떤 조건에서 게임이 끝나는 지에 대하여 찾고, 다시 시작이라는 메시지가 어떻게 보일지 생각해보세요
## 무엇을 만드나요
게임에 다음 규칙을 추가합니다:
1. **게임에 우승합니다**. 모든 적의 배가 부서지면, 게임에서 승리합니다. 추가로 일종의 승리 메시지를 출력합니다.
1. **다시 시작합니다**. 모든 생명을 잃거나 게임에서 이긴다면, 게임을 다시 시작할 방법을 제공해야 합니다. 생각해보세요! 게임을 다시 초기화하고 이전 게임 상태를 깨끗이 지워야 합니다.
## 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
```
타이핑해서 `your_work` 폴더에 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력합니다. 게임은 플레이 가능한 상태여야 합니다.
> tip: Visual Studio Code에서 경고를 보이지 않게 하려면, `gameLoopId`를 (`let`없이) 그대로 호출하도록 `window.onload` 함수를 편집하고, 파일 최상단에 gameLoopId를 독립적으로 선언합니다: `let gameLoopId;`
### 코드 추가하기
1. **종료 조건을 추적합니다**. 다음 두 함수를 추가하여 적의 수를 추적하거나, 영웅의 배가 부서진 경우도 추적해주는 코드를 추가합니다:
```javascript
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
```
1. **메시지 핸들러에 로직을 추가합니다**. 이 조건을 제어하도록 `eventEmitter`를 편집합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
```
1. **새로운 메시지 타입을 추가합니다**. 상수 객체에 이 메시지를 추가합니다:
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
2. **재시작 코드를 추가합니다** 선택한 버튼을 누르면 게임을 다시 시작하는 코드입니다.
1. **`Enter` 누를 키를 수신합니다**. 누르는 것을 수신하도록 윈도우의 이벤트 리스너를 편집합니다:
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
1. **재시작 메시지 추가하기**. 메시지를 메시지 상수에 추가합니다:
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
1. **게임 규칙을 구현합니다**. 다음 게임 규칙을 구현합니다:
1. **플레이어 승리 조건입니다**. 적 배가 모두 파괴되면, 승리 메시지를 출력합니다.
1. 먼저, `displayMessage()` 함수를 만듭니다:
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
1. `endGame()` 함수를 만듭니다:
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// set a delay so we are sure any paints have finished
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
```
1. **로직을 다시 시작합니다**. 모든 생명을 잃거나 플레이어가 게임에서 이긴다면, 게임을 다시 시작할 수 있다고 출력합니다. 추가로 *restart* 키를 누르면 게임을 다시 시작합니다 (다시 시작하기 위해 매핑할 키를 고를 수 있습니다).
1. `resetGame()` 함수를 만듭니다:
```javascript
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
```
1. `initGame()`에서 게임을 다시 설정하기 위해 `eventEmitter`에 호출하도록 추가합니다:
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
1. EventEmitter에 `clear()` 힘수를 추가합니다:
```javascript
clear() {
this.listeners = {};
}
```
👽 💥 🚀 축하합니다, 대장! 게임이 완성되었습니다! 잘 하셨습니다! 🚀 💥 👽
---
## 🚀 도전
소리를 추가해보세요! 레이저가 때리거나, 영웅이 죽고 이길 때, 소리를 추가하여 게임 플레이를 향상시킬 수 있나요? [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play)에서 JavaScript로 소리를 재생하는 방법에 대하여 알아보세요
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
과제는 새로운 샘플 게임을 만드는 것이므로, 어떤 타입의 게임을 만들 수 있는지 알아보고 흥미로운 게임을 찾아보세요.
## 과제
[Build a Sample Game](assignment.md)

View File

@@ -0,0 +1,31 @@
# 스페이스 게임 만들기
고급 JavaScript 기초를 가르치는 스페이스 게임
이 강의에서는 자신만의 스페이스 게임을 만드는 방법을 배웁니다. 만약 "Space Invaders" 게임을 해본 적이 있다면, 이 게임은 같은 아이디어를 가지고 있습니다: 우주선을 조종하고 위에서 내려오는 몬스터를 향해서 발사하는 것입니다. 완성된 게임의 모습은 다음과 같습니다.
![Finished game](images/pewpew.gif)
6개의 강의에서 다음을 학습합니다:
- **상호 작용** Canvas 요소를 사용한 화면 그림
- **이해** cartesian 좌표 시스템
- **학습** 유지 및 확장이 더 쉬운 건전한 게임 아키텍처를 만들기위한 Pub-Sub 패턴
- **이점** 게임 리소스를 불러오기 위한 Async/Await
- **제어** 키보드 이벤트
## 개요
- 이론
- [JavaScript를 사용한 게임 빌드 소개](1-introduction/README.md)
- 실습
- [canvas로 그리기](2-drawing-to-canvas/README.md)
- [화면에 요소 이동](3-moving-elements-around/README.md)
- [충돌 감지](4-collision-detection/README.md)
- [점수 유지](5-keeping-score/README.md)
- [게임 종료 및 재시작](6-end-condition/README.md)
## Credits
The assets used for this came from https://www.kenney.nl/.
If you are into building games, these are some seriously good assets, a lot is free and some are paid.