1. ES5 이미지 크롭 후 이동 [23.01.02]
0) 들어가기 전
- 플랫폼보다는 객체지향을 더 공부하기!!
- 우리조 1조임
- 1조 발표 : 파란 사각형이 객체, 방향키가 사용자 입력, Exit
- 꾸준함이 가장 중요하다.
1) 이미지 크롭하기(일부분을 잘라내기)
ctx.drawImage(img,106,296.5,106,148.25, 0, 0, 106, 148.25);
// sx는 source(원본의 x크기), dx(display x크기,실제로 보여지는 크기)
- 전체 화면에서 다른 부분을 크롭하면, 화면이 넘어가는 장면을 만들 수 있다.
- 하지만, 이렇게 화면에 2개가 이미지가 나오면 머리가 아프다.(좌표 등등)
2) 이미지 크롭의 좌표 문제 해결 방법
- 해결 방법: 이미지를 배열로 구성한다. 2차원 배열을 1차원 배열처럼 사용해서 좌표를 넣어주기!
- a. Boy 객체 예제
window.addEventListener("load", function(){
// =================== [1] [Boy 예제] ==========================
// ---------------------- 8-2. window의 의미 ---------------------------------
// 8-2. window의 적당한 위치 찾기!! 커다란 윈도우는 처음 pop이되고 영역이 되는 것을 브라우저라고 한다.
// window라는 객체가 있다.
// 브라우저는 플랫폼이다. api는 윈도우 객체이다.
// api는 플랫폼이 없으면 의미가 없다.
// window의 기생같은 자식 window를 embeded window라고 부른다.
// window는 출력 영역이 있으며, 입력이 가능한 영역이 존재하다.(입출력이 가능하다!!) - 클릭이 가능하거나,
// 문서에서 보여주고 있는 모든 것들은 window이다.따라서 canvas도 window이다!!!(개념적!!)
// window.onclick = function(){ // **** window 부분은 window만 클릭된다.
// console.log("window clicked"); // 실행 결과 후 따라서, window와 canvas는 부모와 자식간의 관계 느낌이다!!
// }
// ---------------------- 1. 이미지가 한 개인 경우 ---------------------------------
// 이것을 붙여줘야지 canvas의 메서드가 자동완성 한다.
/** @type {CanvasRenderingContext2D} */
var canvas = document.querySelector(".game-canvas");
// document.querySelector는 css에서 style을 부여할 때, 이름을 명시해줄때, id명을 주거나 태그를 줄 수 있다.
// ========== ========== 9. window 버블링 시스템 ========== ========== ==========
// **** 따라서, window에선 버블링이라는 기능이 있다.(계산기 성질과 같은 의미 - 버튼이 여러 개인 경우, 문제점 해결 )
// 자식에서부터 부모까지 다 실행한다.
// 따라서, 각각의 이벤트 함수를 최대한 적게 사용해야 한다.
canvas.onclick = function(){ // **** canvas는 window와 canvas가 동시에 클릭된다.
console.log("canvas clicked");
boy2.draw(ctx);
boy2.move(2);
boy2.clear();
}
var ctx = canvas.getContext('2d'); // 처음에 왜 안 읽혀졌지?
// ---------------------- 2. 이미지가 여러개 인 경우 ---------------------------------
// 이미지를 여러 개면, 각각의 이미지는 개별적으로 움직여야하고 각자 속성을 가지고 있어야 한다.
// 따라서, 구조화 시켜야한다.
// 3. 정면을 바라보고 있는 boy의 기본 형태(생성자 함수) 만들기!!
// 하지만, 스크립트 언어 특성상 내가 속성이 없는 경우에 속성이 붙어버려서 에러가 난다.
//
function Boy(x, y) { // *******
this.x = x || 200; // 6. 오버로드 생성자로 사용자가 넘겨줄 값이 있을 때, 가능하게 하려고 이렇게도 가능하다.
this.y = y || 100; // 사용자 입력이 없을 때는, x와 y는 || 뒤의 기본값이 들어간다.
this.ix = 1;
this.iy = 2;
this.sw = 106;
this.sh = 148.25;
this.sx = this.sw * this.ix;
this.sy = this.sh * this.iy;
}
// 내가 처음에 만든것!!(이젠 이렇게 만들지 말자, 개념적으로 아래 코드와 비교!!)
// boy1.prototype.draw = function(){
// var img = new Image();
// this.drawImage(img, this.sx, this.sy, this.sw, this.sh,
// 200, 100, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// }
// *******
Boy.prototype = { // 4. prototype으로 객체로 만들기!!(캡슐화!!!!********)
draw : function(ctx){
var img = new Image();
img.src = './image/boy.png';
//var o = this; // 굳이 이렇게 해야 하나?
img.onload = function(){ //이렇게 만들면 에러가 나는 이유?
// ctx.drawImage(img, this.sx, this.sy, this.sw, this.sh,
// 200, 100, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// 7. x 값을 초기화해서 10px로 이동시켜보기!!!!
ctx.drawImage(img, this.sx, this.sy, this.sw, this.sh,
this.x, this.y, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
//console.log(o); // 5. function 안의 function이며 이미지가 실행 다 되어서
// img가 this가 되어 실행해준다. 원래는 Boy의 sx여야한다. 그래서, 뭘해야 하지?
}.bind(this); // 여기서는 Boy에서 img로 이벤트가 위임 되었다!!(중요)
// 개념적인 부분 : 위임? 다시 이벤트 위임 공부, Function.prototype.call()은 모든 함수에서 call이 있어야 한다.
// 중요!! 함수가 호출한 주체가 this가 된다. apply, call
console.log(this);
},
// ======================== 7. 캐릭터를 다른곳에 이동시키기 ==========================
// 추가적으로 boy2를 만들려면 어떻게 해야하는지?
// 캐릭터를 이동시키려면 방향 코드가 필요하다.
// 시계 방향!!
move : function(dir){
switch(dir){
case 1: // 북쪽
this.y -= 10; // 브라우저 기준으로 시작을 생각해보면 된다.
break;
case 2: // 동쪽
this.x += 10;
break;
case 3: // 남쪽
this.y += 10;
break;
case 4: // 서쪽
this.x -= 10;
break;
}
},
clear : function(){
ctx.clear;
}
}
var boy1 = new Boy();
boy1.draw(ctx);
// 브라우저의 렌더링 처리는 그림을 그리고 이동하는 것은 효율성을 위해서 한번에 처리된다.
var boy2 = new Boy();
// boy2.draw(ctx);
// 이전에 for문으로 이동시키는 것과 onclick으로 이동시키는 것의 차이는?
// ======================== 8-1. 사용자 입력 받기 ==========================
// 사용자 입력이 있으면, window가 다운되지 않고 백그라운드가 돌아갈 수 있게 해야 한다.
// window에서는 사용자의 입력(event)을 수신하는 능력이 있다. 이벤트를 확인해서 처리하는 능력도 있다.
- b. Box 객체 예제
// ====================== [2] [Box 예제] ============================
// Box??
function Box() {
}
Box.prototype = {
test: function(x,y) { // x와 y를 var로 선언안해줘도 된다.
console.log(this); // 함수에서 바로 가져온다.
console.log(x);
console.log(y);
}
};
var box1 = new Box();
box1.test();
var f1 = box1.test;
// // f1(); // 여기서 앞에 window가 생략되어 있다.
// // var t = 3; // 에러가 안난다. 여기서, t가 window이다.
// // console.log(window.t);
// // // var obj = {kor:2};
// // // obj.f1(); // 에러가 난다.
// var obj = {kor:2};
// obj.f1 = box1.test; // box1의 test함수를 obj의 f1속성에 대입해준다.
// obj.f1(); // 그 obj의 f1을 바로 호출해서 에러가 안 난다.
var obj = {kor:2};
obj.onload = box1.test; // box1의 test함수를 obj의 onload속성에 대입해준다.
obj.onload(); // 그 obj의 onload를 바로 호출해서 에러가 안 난다.
// **** 중요 내용 정리!!! (call, apply, bind)
// // ====================================================================
// // (1) 중요!! 함수가 호출한 주체가 this가 된다. call
// var n1 = {id:1, title:'hello'};
// // 여기서 전달하는 객체가 저 안의 객체가 된다.
// obj.onload();
// obj.onload.call(n1);
// // console.log(n1);
// // ====================================================================
// // (2) apply는 argument를 배열로 집어 넣어준다.
var n1 = {id:1, title:'hello'};
// // 여기서 전달하는 객체가 저 안의 객체가 된다.(n1이 this가 된다.)
obj.onload();
obj.onload.apply(n1,['hi','okay']);
// ====================================================================
// (3) bind 중요!! 함수를 호출할 때, 바꿔치기를 해준다. 위임할 때, 나중에 호출
// 함수를 호출하면서, 나중에 밖에 있는 this를 Boy객체가 되도록 해준다
// ** bind 정리 : 밖에 있는 this(Boy)를 load에 bind 시켜줘서 안에 있는 this(img)에 위임시켜준다.
// Box.prototype = {
// test: function(){
// console.log(this);
// console.log(x);
// console.log(y);
// }
// };
// //var img = document.createElement('image'); // 이런 방식도 가능하다!!
// var img = new Image(); // image 객체 만들기
// img.src = './image/boy.png'; // image를 해당 경로로 가져오기
// //img.addEventListener("load", function(){ // 이렇게도 load 가능!!
// img.onload = function(){
// //ctx.drawImage(img,80,80); // 바로 실행하기!
// //ctx.drawImage(img,100,100,106,148.25);
// //ctx.drawImage(img,106,296.5,106,148.25, 0, 0, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// var ix = 1;
// var iy = 2;
// var sw = 106;
// var sh = 148.25;
// var sx = sw*ix;
// var sy = sh*iy;
// ctx.drawImage(img,sx,sy,sw,sh,
// 200, 100, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// // dx나 dy를 키우면 이미지를 키울 수 있다.
// ctx.drawImage(img,sx+sw,sy+sh,sw,sh,
// 300, 200, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// var moveImage = function(ix, iy){
// ctx.drawImage(img,sx+sw*ix,sy+sh*iy,sw,sh,
// 100*iy, 200, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
// }
// moveImage(2,3);
// }
// ---------------------- 3. 이미지가 여러개 인 경우 ---------------------------------
// 이미지를 여러 개면, 각각의 이미지는 개별적으로 움직여야하고 각자 속성을 가지고 있어야 한다.
// 따라서, 구조화 시켜야한다.
});
2. ES5 방 정리 하기 : [23.01.03]
- main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 순서가 중요하다.**** -->
<script src="./item/boy.js"></script>
<script src="./panel/game-canvas.js"></script>
<script src="./app.js"></script>
<!-- 문서가 다 읽혀지고 읽어지는 방법 : html, js에서 해결 가능(document.addEventListener 이용) -->
</head>
<body>
<!-- 주의사항 : css를 키우면 배율이 커진다. 그래서 속성을 키워야 한다. 즉, canvas를 키워야 한다.
보통의 경우 속성을 키우고 css로 줄이는 경우는 이미지가 깨지는것을 방지해준다. -->
<canvas class="game-canvas" width="500" height="1000"></canvas>
</body>
</html>
- app.js
window.addEventListener("load", function(){
var gameCanvas = new GameCanvas(); // 다른 파일을 가져오는 방식이 자바스크립트에서는 없다.
// 그래서 사용할 모듈을 html에게 요청한다.
// 에러 : GameCanvas 객체를 생성시, 참조는 소문자에 넣어준다.
gameCanvas.run();
});
- gameCanvas.js
// 역할을 주기 위해서 캡슐화를 하자
// GameCanvas는 run(). update(), draw()
// Boy는 move(d:number), draw()
// 호출할 때 전달, call과 apply
// 위임할 때 전달, bind
function GameCanvas() {
this.dom = document.querySelector(".game-canvas"); // 여기서 this는 GameCanvas의 new된 결과이다.
// dom이 canvas이다. canvas가 this의 멤버로 들어온 것이다.
// 원래 java에서는 canvas가 canvas를 갖는 것이 이상하다.
// 그래서, JS는 틀로 쓸 수 있는 것이 아니다.(프레임워크가 아니였다.)
// 앞으로는 this의 dom의 기능을 이용한다.
/** @type {CanvasRenderingContext2D} */
this.ctx = this.dom.getContext('2d'); // var ctx가 아니라 this.ctx이다.(Boy의 ctx이므로)
this.boy = new Boy(100, 100);
// ** 다른 애가 불러내서 위임되는 함수라고 부르며 "call back function"이라고 부른다.
// 사용자가 클릭해달라고 위임했기 때문이다. call back function은 지금 실행하는 것이 아니라 나중에 실행된다.(위임해야지 나중에 전화할 수 있기 때문이다.(나중에 실행))
// 전화하는 애가 주체가 된다.
// dom이 다시 호출해준다?
//this.dom.onclick = this.clickHandler; // ()처럼 함수를 호출한 결과 값 넣지 말기
this.dom.onclick = this.clickHandler.bind(this); // 이렇게 사용하면 위의 코드와 비교해서 dom은 이전의 생성자에서 넘겨받아서 this가 dom이 아니다.
//console.log(this)
}
GameCanvas.prototype = {
run : function(){
// 60프레임으로 화면을 다시 그리는 코드
this.update();
this.draw();
},
update : function(){
},
draw : function(){
this.boy.draw(this.ctx);
},
// ---------- event handler--------------------------------
clickHandler: function(){
this.boy.move(2);
// 화면 지우기
this.boy.draw(this.ctx); // 함수 호출자는 dom이다. 하지만, 우리는 this가 GameCanvas였으면 좋겠다. 서로 역할이 엇갈림.
// 그래서 우리는 보따리를 쌓아 줘야한다.(위에서 bind로 묶어줌.) *******
console.log(this); // 위의 dom이 앞에서 위임받아서 dom이 아니라 이전 생성자이다.
}
};
- boy.js
// 원래 웹에서는 파일명을 가장 앞에는 소문자로 구분하고 대쉬(-)로 구분한다
// 중요!!!!****
// 캡슐화 vs 캡슐(데이터를 구조화한 것)
// 캡슐화란 - 책임을 부여하는 것, 역할을 나누는 것, 기능을 부여하는 것
// 객체지향은 그림을 그릴 수 밖에 없고, 그 그림은 설계가 된다.
function Boy(x, y) { // *******
this.x = x || 200; // 6. 오버로드 생성자로 사용자가 넘겨줄 값이 있을 때, 가능하게 하려고 이렇게도 가능하다.
this.y = y || 100; // 사용자 입력이 없을 때는, x와 y는 || 뒤의 기본값이 들어간다.
this.ix = 1;
this.iy = 2;
this.sw = 106;
this.sh = 148.25;
this.sx = this.sw * this.ix;
this.sy = this.sh * this.iy;
}
Boy.prototype = { // 4. prototype으로 객체로 만들기!!(캡슐화!!!!********)
draw : function(ctx){
var img = new Image();
img.src = './image/boy.png';
img.onload = function(){ //이렇게 만들면 에러가 나는 이유?
ctx.drawImage(img, this.sx, this.sy, this.sw, this.sh,
this.x, this.y, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기)
}.bind(this); // 여기서는 Boy에서 img로 이벤트가 위임 되었다!!(중요)
// 개념적인 부분 : 위임? 다시 이벤트 위임 공부, Function.prototype.call()은 모든 함수에서 call이 있어야 한다.
// 중요!! 함수가 호출한 주체가 this가 된다. apply, call
console.log(this);
},
move : function(dir){
switch(dir){
case 1: // 북쪽
this.y -= 10; // 브라우저 기준으로 시작을 생각해보면 된다.
break;
case 2: // 동쪽
this.x += 10;
break;
case 3: // 남쪽
this.y += 10;
break;
case 4: // 서쪽
this.x -= 10;
break;
}
},
}
// var boy1 = new Boy();
// boy1.draw(ctx);
// var boy2 = new Boy();
// boy2.draw(ctx);
// boy2.move(2);
// boy2.draw(ctx);
3. ES6 JS : [23.01.03]
1) ES5 vs ES6
-
ES5
- 모듈 시스템이 없다.(스코프 개념, import, export)
- 객체 지향을 지원하지 않는다.(class 개념)
- 비동기 처리를 위한 도구가 없다.(프로미스)
- 인터페이스를 통해 심볼릭 필요
2) 변수 : let, const
-
let : 중괄호가 생기면 stack에 쌓이고 중괄호가 끝나면 변수도 사라진다.
-
const : final과 같은 역할이다. 상수처럼 값이 고정되어 있다.
-
일반적인 변수는 let이다.
3) 디스트럭처링 (객체 뽀개기)
// ES6 공부 시작
let kor = 20;
let eng = 30;
// 기존 방식
let obj = {
kor: kor,
eng: eng,
total: function(){
return this.kor + this.eng;
}
};
function print(exam){
let kor = exam.kor; // 코드를 연산할 때, 더 간단하게 만들어준다!!!(집중화)
let eng = exam.eng;
//let total = exam.total.bind(exam); // 문제가 많다. 원래는 exam.total이여야 한다. 하지만, let 변수(let obj는 window에서 호출)에 의해서 exam이 아니라 window가 호출했다.
// 따라서, exam이 호출했다고 인식하도록 bind를 시켜주어야 한다.
// 호출되는 시점에 객체를 통해 호출을 한다.
// 그래서 bind하는 것이고 let을 이용하면, 지역변수가 된다.
// ** let으로 선언했기 때문에 print함수가 끝나면 let변수는 모두 사라진다.
let total = exam.total(); // 굳이 bind를 이용하지 않고 이런식으로 exam.total()을 변수 total에 담아도 된다.
console.log("kor:"+ kor);
console.log("eng: "+ eng);
console.log("total: "+ total);
}
print(obj)
// 새로운 방식!!([객체 뽀개기])
let obj1 = {kor,eng,total(){ // 키와 값이 같다면 하나만 써도 된다. 함수를 이렇게 바로 쓸 수도 있다.
return this.kor + this.eng;
}};
function print1({kor,eng}){ // 디스트턱처링!! : 객체 뽀개기. 객체를 쪼개서 3개로 나눠서 받는다.
// 원래 데이터만 뽀개야한다. 함수는 뽀개는 것이 아니다!!******
console.log("kor:"+ kor);
console.log("eng: "+ eng);
//console.log("total: "+ total());
}
print1(obj1);
// [디스트럭쳐]의 또 다른 기능
// 중괄호 : 오른쪽 객체 생성, 왼쪽은 변수 생성
// 따라서, 중괄호로 변수를 선언했으면, 더이상 선언할 수 없다.
let obj2 = {kor,eng,total(){ // 키와 값이 같다면 하나만 써도 된다. 함수를 이렇게 바로 쓸 수도 있다.
return this.kor + this.eng;
}};
function print2(exam){
let {kor, eng, aa=9} = exam; // 변수 선언만 한 것이다. 앞의 변수를 간단하게 만들어주는 것과 같다.
// 또한, 이런 식으로 aa가 선언되면서 초기화 할 수 있다.
console.log(aa); // 9가 출력
console.log("kor: "+ kor);
console.log("eng: "+ eng);
// console.log("total: "+ total());
}
print2(obj2);
// *** ES6 정리(23.01.03) : 클래스를 정의하고 기존 프로토 타입의 속성을 함수로 재구성
// 변수 const, let 등장!!
// 데이터를 뽀개는 방식으로 지역 변수 선언 방법이 새로 생김.
3. ES6 JS 정리 : [23.01.04]
-
이제는 ES6에서는 이진법 사용 가능(8진수 , 2진수 등등)
-
지역변수의 사용 가능(let, const) : 블록안에서만 라이프 사이클
-
var는 함수 안에서 어디서든 사용할 수 있었다.
1) let, const 사용
// -----------------------------------------
console.log(x);
var x = 3; // undefined
// console.log(y); // let으로 선언하면 그 변수에는 초기화되지 않는 쓰레기값이 들어간다.
// let y = 3; // let은 선언과 상관없이 준비가 되어있기 때문이다.
2) 심볼의 개념 : 숫자는 의미가 없기 때문에 심볼로 의미를 부여 해준다.
// const로 사용하며, 대문자를 이용하여 변수명을 작성한다.
const N = 7;
3) Template String(문자를 출력할 때, 덧붙이는 과정) : 백틱(`) 기호 사용
let year = 2023;
let month = 1;
let day = 4;
let regdate = year+"-"+month+"-"+day;
console.log(regdate);
let template = `${year}-${month}-${day}`; // 백틱에 의해서 감싸야지 템플릿 이용 가능(어금 기호?)
console.log(template);
// ** ES6 위주로 공부하기(ES5는 핵심적인 것만) // 익숙해지는 것이 중요하다.
4) 위의 템플릿을 써야하는 이유? : 싱글 코테이션 사용에 의해서
let product = '<section class="p-elect"></section>' // ''이 ""와 섞여 있어서 복잡하다.
// JS 코드에서 내려쓰기 하고 싶으면, ``(백틱)을 이용해야 한다. (+를 사용하면, 코드가 복잡해진다.)
// 나중에 html 코드에 꽂아 넣을 때, 이용한다.*********
let className = 'p-elect';
let title = '스마트폰';
let product1 = `<section class="`+className+`"> // 불편한 방식
<h1>?</h1>
</section>`;
console.log(product1);
let product2 = `<section class="${className}"> // 편한 방식()
<h1>${title}</h1>
</section>`;
console.log(product2);
let product3 = String.raw`<section class="${className}">\n\n\n // \n\n\n 그대로 출력할 수 있다.
<h1>${title}</h1> // String.raw 이용! //
</section>`;
console.log(product3);
// ************ 암기하기!!
5) 향상된 JSON 객체 표현식 #1
- function 키워드를 생략 가능
- Computed Property? : ES6에서는 변수명을 왼쪽에 넣어버리는 경우도 발생하고 연산도 가능하다.
- 진도를 빨리나가는 이유? : 게임에 적용해보려고
- 실습!!
let attName = "kor";
// *** 유형 1
let exam = {
[attName]: 10,
eng: 20,
math: 30
};
console.log(`kor:${exam.kor}`);
6) Object Destructuring
-
데이터 구조는 중첩을 낮춰서 사용할 것 (.을 최대한 적게 사용해야 한다.)
-
추가로 공부할 것 : 객체 속성에 그 객체 내부의 속성을 덧붙여서 출력하는 것(유튭에 있는 내용)
let {kor, eng:english, math=0} = exam; // 뽀개는 것도 원하는 것만 뽑을 수도 있다.(굳이 3개 전부 뽑을 필요가 없다.)
// 뽀개는 것을 원래 속성과 다르면 별칭으로도 사용 가능!(eng:english)
console.log(kor, english); // 기존의 eng에서 english로 속성을 바꾸면 undefined로 나오는데
// *** 유형 2
// JS에서는 객체가 중첩인 경우도 엄청 많다.
let exam1 = {
[attName]:10,
eng:20,
math:30,
student:{
name:'newlec',
phone:'010-2222-3333'
}
};
console.log(`kor:${exam1.kor}`);
let {kor, eng:english, math=0, student:{name, phone}} = exam1; // 위에서 객체의 중복 속성 표시 가능!!(이것도 중괄호가 중복됨)
// let {kor, eng:english, math=0, student} = exam1; // 이런식으로 중복 속성 쪼개기 가능 *****
// let {name, phone} = student; // 중복 속성을 2줄에 나눠서 쪼개기 가능
console.log(kor)
console.log(english)
console.log(phone)
//////// ** 정리 :
- ES6 함수 정의 방법 : Arrow Function
- 자기 this가 없으니까 밖의 this를 사용한다. (자기만의 영역이 없다. 지역화가 없다.)
- 자기만의 this가 없어서 bind를 사용하지 않는다.
window.setTimeout(()=>{
this.run();
},1000);
-
game study
- app.js
<script> window.addEventListener("load", function(){ var gameCanvas = new GameCanvas(); // 다른 파일을 가져오는 방식이 자바스크립트에서는 없다. // 그래서 사용할 모듈을 html에게 요청한다. // 에러 : GameCanvas 객체를 생성시, 참조는 소문자에 넣어준다. gameCanvas.run(); // run()은 무한 loop해야 한다. UI 스레드는 별도의 흐름을 갖게 해야 한다. // vscode에선 F12로 함수 선언과 이동 가능 }); </script>
- game-canvas.js
class GameCanvas { // 클래스로 변경 constructor(){ this.dom = document.querySelector(".game-canvas"); // 여기서 this는 GameCanvas의 new된 결과이다. // dom이 canvas이다. canvas가 this의 멤버로 들어온 것이다. // 원래 java에서는 canvas가 canvas를 갖는 것이 이상하다. // 그래서, JS는 틀로 쓸 수 있는 것이 아니다.(프레임워크가 아니였다.) // 앞으로는 this의 dom의 기능을 이용한다. /** @type {CanvasRenderingContext2D} */ this.ctx = this.dom.getContext('2d'); // var ctx가 아니라 this.ctx이다.(Boy의 ctx이므로) this.boy = new Boy(100, 100); this.gameover = false; // setTime을 위한 상태값!!****, 게임 오버는 아니고 애니메이션만 // 멈추게 해야 한다. 즉, 멈추면 상태값이 전부 날아가는 것이 // 아니라 정지인 상태이다.(배경과 적들은 움직임) this.pause = false; // 그래서, 게임이 끝나는 것이 아니라 일시정지가 되어야 한다. // **** 다른 애가 불러내서 위임되는 함수라고 부르며 "call back function"이라고 부른다. // 사용자가 클릭해달라고 위임했기 때문이다. call back function은 지금 실행하는 것이 아니라 나중에 실행된다.(위임해야지 나중에 전화할 수 있기 때문이다.(나중에 실행)) // 전화하는 애가 주체가 된다. // dom이 다시 호출해준다? //this.dom.onclick = this.clickHandler; // ()처럼 함수를 호출한 결과 값 넣지 말기 this.dom.onclick = this.clickHandler.bind(this); // bind(this)의 this는 이벤트 핸들러를 위한 객체를 불러온다. 원래 this는 gamecanvas이다. } // 사용자가 클릭해달라고 요청하는 이벤트 핸들러이다. // 이렇게 사용하면 위의 코드와 비교해서 dom은 이전의 생성자에서 넘겨받아서 this가 dom이 아니다. //console.log(this) run(){ if(this.pause) return; // 60프레임으로 화면을 다시 그리는 코드 this.update(); this.draw(); // // 게임 만들 때, 심박동을 위해서 setTimeOut, setInterval, AnimationFrame 심박동을 우리가 맞출 수 없다.(조정이 불가능) // // setTimeOut - 1회성, setInterval - 반복성, AnimationFrame - 프레임 1초에 60회로 고정 // window.setTimeout(function(){ // console.log("time out"); // },1000); // // this를 알게 하기 위해서 호출하는 주체를 봐야 한다. // window.setTimeout(function(){ // this.run().bind(this); // 여기서 함수를 호출하는 것이 window라고 앞에 적혀 있어서 // },1000); // 그래서 우리는 여기 있는 run을 실행하기 위해서 bind를 사용한다!! // // ************ ES6 함수 정의 방법 : Arrow Function ************ // // 자기 this가 없으니까 밖의 this를 사용한다. (자기만의 영역이 없다. 지역화가 없다.) // // 자기만의 this가 없어서 bind를 사용하지 않는다.**** // window.setTimeout(()=>{ // this.run(); // },1000); window.setTimeout(()=>{ // 알람 맞추는 것과 같다.(60 프레임에 맞추어서) this.run(); },17); // 여기까지 하면, 무한히 반복한다. 멈추고 싶으면? 이 안에서 멈추는 도구가 필요하다. 그래서 상태변수가 필요하다. } // canvas는 UI이고 UI에 의해서 꼭두각시처럼 컨트롤된다. update(){ // update는 단위 계수마다 잘게 쪼개서 움직여야 한다. // 시간에 국한에서 잘게 짜르려면 frame을 나누어야 한다. // 이동하는 속도가 일정하려면, this.boy.update(); // 이동하고 } draw(){ this.boy.draw(this.ctx); // 다시 그리고 } pause(){ } // ------------------------------- event handler***** -------------------------------- // 이벤트가 발생했다!! 사용자 입력에 의해서 이벤트가 발생 // 사건의 구체적인 정보를 알아야내 한다.(어떤 위치에 마우스를 클릭했는지, 어떤 키보드를 눌렀는지) clickHandler(e){ // 클릭하면, 타이머가 멈춘다!! //this.pause = true; // 같은 함수 내부라서 함수 호출 시, // 함수를 사용하는 것이 아니라 값을 입력해준다. // this.boy.move(2); // 마우스로 move하는 것은 옳지 않고 방향키로 나중에 구현할 것 // 마우스는 사용자 입력이다. this.boy.moveTo(e.x,e.y); // 이동할 때, 대각선 길이를 잘게 쪼개는데 가로 세로는 그 값이 얼마였든지간에 비율로 대각선 길이로 나눈다. // 나중에 배우자! 이벤트 함수 : screen x, event x, osell x // 화면 지우기 this.boy.draw(this.ctx); // 함수 호출자는 dom이다. 하지만, 우리는 this가 GameCanvas였으면 좋겠다. 서로 역할이 엇갈림. // 그래서 우리는 보따리를 쌓아 줘야한다.(위에서 bind로 묶어줌.) ******* console.log(this); // 위의 dom이 앞에서 위임받아서 dom이 아니라 이전 생성자이다. } }
- boy.js
// 원래 웹에서는 파일명을 가장 앞에는 소문자로 구분하고 대쉬(-)로 구분한다 // 중요!!!!**** // 캡슐화 vs 캡슐(데이터를 구조화한 것) // 캡슐화란 - 책임을 부여하는 것, 역할을 나누는 것, 기능을 부여하는 것 // 객체지향은 그림을 그릴 수 밖에 없고, 그 그림은 설계가 된다. class Boy { // ******* constructor(x,y){ this.x = x || 200; // 6. 오버로드 생성자로 사용자가 넘겨줄 값이 있을 때, 가능하게 하려고 이렇게도 가능하다. this.y = y || 100; // 사용자 입력이 없을 때는, x와 y는 || 뒤의 기본값이 들어간다. this.vx = 0; // 단위 위치(잘게 조개진 값) this.vy = 0; this.dx = 0; // 목적지 위치 this.dy = 0; this.ix = 1; this.iy = 2; this.sw = 106; this.sh = 148.25; this.sx = this.sw * this.ix; this.sy = this.sh * this.iy; } draw(ctx){ var img = new Image(); img.src = './image/boy.png'; img.onload = function(){ // 이렇게 만들면 에러가 나는 이유? ctx.drawImage(img, this.sx, this.sy, this.sw, this.sh, this.x, this.y, 106, 148.25); // sx는 source(원본의 x크기), dx(display의 x크기) }.bind(this); // 여기서는 Boy에서 img로 이벤트가 위임 되었다!!(중요) // 개념적인 부분 : 위임? 다시 이벤트 위임 공부, Function.prototype.call()은 모든 함수에서 call이 있어야 한다. // 중요!! 함수가 호출한 주체가 this가 된다. apply, call console.log(this); } update(){ this.x += this.vx; // 여기서 목적직 위치까지 단위 위치로 더하면서 업데이트 시킴 this.y += this.vy; } moveTo(dx,dy){ let w = dx - this.x; // 목적지 위치 - 현재 위치(업데이트되고나서) = 남은 위치 거리 계산 let h = dy - this.y; let d = Math.sqrt(w*w+h*h); // 대각선 길이 공식 this.vx = w / d; // 여기 this도 주의해서 적기, 그 남은 위치 거리를 다시 잘게 쪼개진 비율로 나누어서 이동한다. this.vy = h / d; } move(dir){ switch(dir){ case 1: // 북쪽 this.y -= 1; // 브라우저 기준으로 시작을 생각해보면 된다. break; case 2: // 동쪽 this.x += 1; break; case 3: // 남쪽 this.y += 1; break; case 4: // 서쪽 this.x -= 1; break; } } }