TIL-6주차 코드 - Day4~5

 

1. JS (배열 뽀개기 ,Collection : Set, Map) [23.01.05]

1) Array Destructring(배열 뽀개기)


   
    let kors =[1,2,3];   
    let[kor1,kor2,kor3] = kors;     //신기하네. let에 바로 붙여쓰네
    console.log(kor1);


    // 에러발생 : 이미 지역 변수가 있어서** 
    let kors2 = [2,3,4];
    // let[kor1,kor2,kor3] = kors2;   // 이렇게 쓰면 에러가 발생한다. 선언을 2개 해버려서..
    [kor1,kor2,kor3] = kors2;         // 따라서, 선언해주지 않는다.
    console.log(kor1);


    let std1 = {name:"dragon", phone:'010'};
    // let {name, phone} = std1;   // 에러발생!
    // {name, phone} = std1;       // 에러발생! 괄호를 묶어줘야 한다.(parenths 에러)

    ({name, phone} = std1);   // 에러를 해결하기 위해서 전체에 괄호를 묶어준다.
                              // JS를 ";"안 쓰고 줄바꾸기 위해서 완전한 문장이여야 한다. 이 문장은 불안정하다! 

    let a, b;                 // 배열을 이런식으로 넣어 줄수도 있다.
    [a, b] = kors2;           // 뽀개기된 kor2를 다시 배열([a, b])에 넣어 줄수 있다.      
    console.log(a,b);               

    a = 20;
    b = 30;
    console.log(a);
    [a,b] = [b,a];                  // 자리바꾸기.. 이게 되네...
    console.log(a);

    let korss = [10, 20, 30];
    let [kor4, kor5] = korss;
    console.log(kor4);
    console.log(kor5);

    let[,,kor6] =korss;             // 앞에는 뭐가 들어감?
    console.log(kor6);

    let numbers = [1,2,3,4,5,6,7,8,9,10];
    let [n1,n2,rest] = numbers;
    console.log(rest);              // 배열의 나머지를 요소를 출력하려면 나머지 연산자가 필요하다!! 


    let numberss = [1,2,3,4,5,6,7,8,9,10];
    let [n11,n22,...rrest] = numbers;
    console.log(rrest);                     // 나머지 연산자(...)를 사용하기, 순서도 마지막에 배치해야 한다.




2) ES6 JS의 Set 콜렉션


  • Iterator :
    • 일괄적으로 뽑아낼 때, Iterator로 뽑아낸다
    • of를 사용할 수 있으면 이터레이터이다.(반복적으로 값을 뽑아낼 때)


  • Set 특징 :
    • key가 따로 구현하지 않는다, 단독적으로 특정한 값을 꺼낼 수 없다,
    • key가 충돌하지않아서 중복되는 값이 존재하지 않는다.
    • set.add()로 Set 콜렉션에 값을 더 한다.



    let set = new Set();
    set.add(5);             // add로 Set에 데이터를 대입해준다.
    set.add("5");           // set은 데이터를 배열에 담는다.***
    set.add(2);
    set.add(5);

    // ** size 이용, 크기를 출력하면, 중복제거된 상태로 값이 출력
    console.log(set.size);                          // 3이 출력

    console.log(`size : ${set.size}`);

    // ** for of 이용
    for(let n of set) {  // set의 값을 하나씩 뽑아낸다.
        console.log(n);
        set.has(5)
    }

    // ** has 이용
    set.has(5)          // 5를 갖고 있는지 boolean형으로 반환 



  • forEach 구문!! vs for of와 비교하기(vs for in)


    
    // ** 
    set.forEach((v)=>{
        console.log(v);       // **** forEach로 각각의 값을 꺼내서 뽑아 쓸 때, 사용했다.
    })                                              

    set.forEach((v)=>{
        console.log(`value: ${v}`);   // forEach로 각각의 값을 직접 꺼내서 사용했다.
    })                                // for of는 Set에서 값을 뽑아낸다.
                                      // forEach는 예전 방식이라서 이런식으로도 사용한다.
                                      // 요즘에는 값을 뽑아내기 위해서 for of를 이용한다.
    // for in 문 사용!
    for(let k in set)
        console.log(k);              // 그냥 key 값은 안 나온다.(Set이라서 key가 없다.)
    
        console.log("----------------------------------------------------"); 
    
    set.forEach((v,k) => {
        console.log(`key:${k}, value:${v}`);    // key와 value 모두 같은 값으로 출력(forEach)
    });

    // ***WeekSet : 중복이 제거되었을때 확인하기 위해 사용한다.(Set에 담지 않는다.)



  • Map : key, value 이용하기(이제 Object를 이용하지 않는다**)
    • 이터레이터 사용가능
    • Object는 키 값으로 문자열만 가질 수 있지만, Map은 키로 모든 데이터 타입을 가질 수 있다. Map은 순서를 보장한다.


  • Map 함수 모음 **
    • keys : key들만 확인
    • values : value들만 확인
    • entries : key, value 모두 확인
    • size : 크기 확인
    • @@ 태그 : 심볼이라서 인터페이스가 제공되지 않아서 약속에 해당하는 것
      • ex) @@species, @@toStringTag



    let map = new Map();
    map.set("id", 1);
    map.set("title","map이란?");

    console.log("\nforeach---------------------");
    map.forEach(function(v,k){
        console.log(`key:${k}, value:${v}`);
    });

    console.log("/foreach---------------------");

    let notice = new Map();             // 속성을 묶어서 사용할 때, 사용하며 변수에 담을 수 있다.
    notice.set("id",1);                 // 묶어서 한 번만 사용할 때, 임시형 데이터 집합을 사용할 때, Map을 사용한다.
    notice.set("title","map is ...");
    notice.set("writer", "newlec");

    console.log(notice.get("title"));   // 데이터 1개만 가져올 때, get으로 받아온다. 

    notice.forEach((v,k)=>{
        console.log(`key:${k}, value:${v}`);
    });

    for(let key of notice.keys())
        console.log(`key:${key}`);    // Set으로 꺼내진다.

    for(let v of notice.values())
        console.log(`value:${v}`);

    // for(let entry of notice.entries()){
    //     console.log()                           // 이것도 틀렸다.
    //     console.log(`key: ${entry.key}`);       // 이건 올바르지 않는 방식이다.
    //  }
    for(let [k,v] of notice.entries())            // notice.entries()가 iterator이다. notice도 iterator를 갖고 있다.*****
                                                  // entries는 iterator라서 map 타입으로 key-set을 하느냐? set 계열의 collection하느냐 이다.
        console.log(`key: ${k}, value: ${v}`);    // key: ~ , value : ~ 이런 형식으로 출력된다.

    // forEach를 안 쓰고 for of를 사용하는 이유? ES5에서 예전에 사용하던 방식이며 화살표 함수와 같이 함수를 사용해야하므로 복잡하다.  
    


  • iterator 추가 개념**
    /// **** 
    for(let [,v] of notice.entries())   // notice.entries()가 iterator이다. notice도 iterator를 갖고 있다.*****
                                        // entries는 iterator라서 map 타입으로 key-set을 하느냐? set 계열의 collection하느냐 이다.
        console.log(`value: ${v}`);     // value만 출력!!!!

    for(let [k,] of notice.entries())   // notice.entries()가 iterator이다. notice도 iterator를 갖고 있다.*****
                                        // entries는 iterator라서 map 타입으로 key-set을 하느냐? set 계열의 collection하느냐 이다.
        console.log(`key: ${k}`);       // key만 출력!!!!

    
        
    for(let n of notice)            // notice.entries()가 iterator이다. notice도 iterator를 갖고 있다.*****
                                    // entries는 iterator라서 map 타입으로 key-set을 하느냐? set 계열의 collection하느냐 이다.
        console.log(`n: ${n[1]}`);  // map의 1번째 데이터를 가져와서 출력한다.
    


  • Object를 Map으로 바꿀 수 있는가?(map 함수 이용하기)

    let exam3 = {
        kor:10,
        eng:20,
        math:30
    };

    // *** 에러 발생
    // for(let v of exam3)     // iterable 에러 발생
    //     console.log(v);     // 원래는 entry()에서 가져와야하는데 아니라서 에러가 발생
    //                         // 따라서, key와 value를 갖고 있는 데이터 타입을 만들어서 거기서 가져와야 한다.

    // 그래서, 나중에 Object의 static 메서드를 살펴보기****(mdn static methord 검색하기)
    for(let [k,v] of Object.entries(exam3))
        console.log(`key: ${k}, value: ${v}`);

  • forEach 개념(map 함수와 비교하기 != Map 컬렉션)



    let list = [
        {id:1, title:"jsp is...", writerId:"newlec"},
        {id:2, title:"servlet is...", writerId:"newlec"},
        {id:3, title:"javascript is...", writerId:"newlec"},
        {id:4, title:"spring is...", writerId:"newlec"}
    ]

    list.forEach((n)=>{});  // forEach의 원래 개념

    // *** map 함수 개념 (Map 콜렉션이 아니다!****) : 매핑시켜주는 것!!
    // map 함수는 변환할 때, 좋은 도구이다.
    // list라는 변수의 배열 데이터 타입을 n이라는 Object 타입으로 변경해준다.
    // 화살표 함수에 의해 list라는 배열 타입의 변수를 map 함수에 의해 n이라는 Object 타입으로 변경하고
    // n에 담아서 return으로 n의 속성을 반환한다. 
    let ar = list.map((n)=> {return `<span>${n.title}</span>`});
        console.log(ar);




2) Game project

  • this.dom.onclick = this.clickHandler.bind(this)의 부분 공부하기
// game-canvs.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() 처리하면서 .bind(this)의 this는 이벤트 핸들러를 위한 객체를 불러온다. 원래 this는 gamecanvas이다.
    }                                                            
                                                                  // **** 내장 객체의 함수와 새로만들어 지는 객체의 함수를 비교할 것.
                                                                  // clickHandler는 사용자 입력에 의한 이벤트 핸들러이기 때문에
                                                                  //  window에서 가져와야 해서 .bind(this)를 뒤에 붙여준다.
    

            // 이렇게 사용하면 위의 코드와 비교해서 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 프레임에 맞추어서 = 1000/60 = 17)
            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이 아니라 이전 생성자이다.
        }
            
}



2. Game 관련 Canvas [23.01.06]

1) 스레드 개념

  • UI를 담당하는 부분 : 메인 스레드

  • 게임을 동작하는 부분 : 게임 스레드

  • 이 2개의 흐름이 소통하는 방법? 상태 변수를 사용하여 구분해야 한다.

  • 게임 스레드가 계속 돌고 있으면서 캐릭터의 함수에서 값을 계속 확인하면서 상호 작용 후 진행된다.

  • 스레드 순서를 중요하게 정해서 나열해야지 객체의 순서를 잘 보일 수 있다.(비행기, 메인, 배경 등등**)


2) 보정 기술 개념

  • 먼거리를 이동할 때의 위치를 잡아주는 보정 기술이 필요하다.(우주 공학에서는 이런 것이 정밀하게 필요하다.)


  • 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/background.js"></script>    <!-- 새로운 페이지 등록 시, url 연결 시켜주기! -->
    <script src="./item/boy.js"></script>
    <script src="./panel/game-canvas.js"></script>  
    <script src="./app.js"></script>
    
    <script src="ex1-es6-var.js"></script>

    <!-- 문서가 다 읽혀지고 읽어지는 방법 : html, js에서 해결 가능(document.addEventListener 이용) -->
    
</head>
<body>
      <!-- 주의사항 : css를 키우면 배율이 커진다. 그래서 속성을 키워야 한다. 즉, canvas를 키워야 한다.
           보통의 경우 속성을 키우고 css로 줄이는 경우는 이미지가 깨지는것을 방지해준다.  -->

      <input tabindex="2">
      <img>
      <div tabindex="1">            <!-- tabindex를 넣어야지 tab으로 이동할 수 있다. -->
            test                    <!-- 마우스가 기본이라서 마우스가 우선 순위라서 사용자가 마우스를 클릭하고나서 키의 입력을 받을 수 있다. -->
      </div>                        <!-- 그래서 키보드 입력하기 위해서 tabindex를 설정한다!! -->
      <input tabindex="0">
      <!-- style="display: none;"은 style 안보이지만 메모리에 올라가 있다 즉, dom에 올라가 있따. -->
      <img src="./image/boy.png" id ="boy" style="display: none;">
      <img src="./image/map.png" id ="bg" style="display: none;">
      <canvas tabindex="0" class="game-canvas" width="500" height="1000"></canvas>


</body>
</html>



  • app.js
window.addEventListener("load", function(){

    var gameCanvas = new GameCanvas();  // 다른 파일을 가져오는 방식이 자바스크립트에서는 없다.
                                        // 그래서 사용할 모듈을 html에게 요청한다.


                                        // 에러 : GameCanvas 객체를 생성시, 참조는 소문자에 넣어준다. 
    gameCanvas.run(); 
        
    // run()은 무한 loop해야 한다. UI 스레드는 별도의 흐름을 갖게 해야 한다.      
    // vscode에선 F12로 함수 선언과 이동 가능

});



  • game-canvas.js
// game-canvs.js는 사용자 입력과 출력을 담당한다.(중요!!) *****

class GameCanvas {        // 클래스로 변경  

    constructor(){                                          // **** 중요한 개념 : 
        this.dom = document.querySelector(".game-canvas");  // 여기서 this는 GameCanvas의 new된 결과이다.
                                                            // dom이 canvas이다. canvas가 this의 멤버로 들어온 것이다.
                                                            // 원래 java에서는 canvas가 canvas를 갖는 것이 이상하다. 
                                                            // 그래서, JS는 틀로 쓸 수 있는 것이 아니다.(프레임워크가 아니였다.)

                                                            // 앞으로는 this의 dom 기능을 이용한다.

        this.dom.focus();     // focus를 잡아주면 canvas에서 키보드 입력을 할 수 있다. **** 즉, 우선순위를 마우스에서 키보드로 넘겨준다.
                              // 키보드로 무브하는 방법???? 
    
        /** @type {CanvasRenderingContext2D} */ 
        
        this.ctx = this.dom.getContext('2d');   // var ctx가 아니라 this.ctx이다.(Boy의 ctx이므로)         
        
        this.boy = new Boy(100, 100);

        this.bg = new Background();

        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() 처리하면서 .bind(this)의 this는 이벤트 핸들러를 위한 객체를 불러온다. 원래 this는 gamecanvas이다.
        // this.dom.onclick = this.clickHandler;                       // **** .bind() 처리하면서 .bind(this)의 this는 이벤트 핸들러를 위한 객체를 불러온다. 원래 this는 gamecanvas이다.

        this.dom.onkeydown = this.keyDownHandler.bind(this);
        

    }                                                            
                                                                  // **** 내장 객체의 함수와 새로만들어 지는 객체의 함수를 비교할 것.
                                                                  // clickHandler는 사용자 입력에 의한 이벤트 핸들러이기 때문에
                                                                  //  window에서 가져와야 해서 .bind(this)를 뒤에 붙여준다.
    

            // 이렇게 사용하면 위의 코드와 비교해서 dom은 이전의 생성자에서 넘겨받아서 this가 dom이 아니다.
            //console.log(this)


    run(){
        //console.log(this);
        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);

        // window.setTimeout(this.run.bind(this), 10000);

        // window.setTimeout(this.run.bind(this), 1000);

        // // this를 알게 하기 위해서 호출하는 주체를 봐야 한다.
        // window.setTimeout(function(){
        //     this.run();      // 여기서 함수를 호출하는 것이 window라고 앞에 적혀 있어서    
        // },1000).bind(this);                        // 그래서 우리는 여기 있는 run을 실행하기 위해서 bind를 사용한다!!

        // // ************ ES6 함수 정의 방법 : Arrow Function ************
        // // 자기 this가 없으니까 밖의 this를 사용한다. (자기만의 영역이 없다. 지역화가 없다.)
        // // 자기만의 this가 없어서 bind를 사용하지 않는다.****  
        // window.setTimeout(()=>{
        //     this.run();    
        // },1000);

        window.setTimeout(()=>{                 // 알람 맞추는 것과 같다.(60 프레임에 맞추어서 = 1000/60 = 17)
            this.run();    
        },17);

        // 여기까지 하면, 무한히 반복한다. 멈추고 싶으면? 이 안에서 멈추는 도구가 필요하다. 그래서 상태변수가 필요하다.

    }

        // canvas는 UI이고 UI에 의해서 꼭두각시처럼 컨트롤된다.

    update(){   // update는 단위 계수마다 잘게 쪼개서 움직여야 한다.
                // 시간에 국한에서 잘게 짜르려면 frame을 나누어야 한다. 
                // 이동하는 속도가 일정하려면, 

        this.boy.update();              // 이동하고 
        //this.bg.update();              // 이동하고 
    }
    
    draw(){
        this.bg.draw(this.ctx);         // 순서 중요!!
        this.boy.draw(this.ctx);        
        //this.bg.draw(this.ctx);
    }

    pause(){
        
    }
    
    // ------------------------------- event handler***** --------------------------------
    // 이벤트가 발생했다!! 사용자 입력에 의해서 이벤트가 발생 // 사건의 구체적인 정보를 알아야내 한다.(어떤 위치에 마우스를 클릭했는지, 어떤 키보드를 눌렀는지)

    clickHandler(e){         // 클릭하면, 타이머가 멈춘다!!
        //this.pause = true; // 같은 함수 내부라서 함수 호출 시, 
                             // 함수를 사용하는 것이 아니라 값을 입력해준다.

        // this.boy.move(2);    // 마우스로 move하는 것은 옳지 않고 방향키로 나중에 구현할 것
                                // 마우스는 사용자 입력이다.
        //console.log(this);
        // ** 상태 변수를 이용                        
        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이 아니라 이전 생성자이다.
        }


    // ***** canvas나 div 태그는 키 입력을 안 받게 했다.
    keyDownHandler(e){
        this.boy.move(e.key); 
        console.log(e.key);
    }
            
}




  • 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.img = document.querySelector("#boy");          // html에서 이미지를 이렇게 가져온다.(이젠, 생성자에서 그리기 위해서!) *************

        this.ix = 1;                                        // ix, iy는 스프라이트 사진에서 부분을 꺼낼수 있게 배열의 인덱스 값이다!
        this.iy = 2;
    
        this.walkDelay = 20;            // 프레임 낮춰주기!!

        this.sw = 106;
        this.sh = 148.25;
        this.sx = this.sw * this.ix;
        this.sy = this.sh * this.iy;    // 메인 스레드는 절차를 담당하는 기본 흐름이다. setTimeout인 보조 흐름이며 추가적으로 작동을 만든 것이 게임 스레드이다.

    }                                   // 이것의 차이를 알기!!****
                                        // update와 draw는 메인 스레드(게임 스레드)이다.(사용자 입력이 없더라도, 내가 호출하는 것이 아니라 초당 60번씩 계속 돌고 있다.)
                                        // 나머지는 UI 스레드이다.(사용자 입력이 있을 때만, 한 번만 움직임.)
    draw(ctx){

        this.sx = this.sw * this.ix;
        this.sy = this.sh * this.iy;
        ctx.drawImage(this.img, this.sx, this.sy, this.sw, this.sh,     // **** 에러발생 : img는 생성자에서 가져오기 때문에 this.img 이다!!
            this.x-this.sw/2, this.y-this.sh+15, this.sw, this.sh);     // 발이 기준이 되게 값을 조정해주자!! 
        
        // var img = new Image();	
        // img.src = './image/boy.png';    // 이미지도 생성자에서 생성하자!!*****

        // img.onload = function(){    // 이렇게 만들면 에러가 나는 이유?

        //                             // sx는 source(원본의 x크기), dx(display의 x크기)
          
        // }.bind(this);               // 여기서는 Boy에서 img로 이벤트가 위임 되었다!!(중요)
                                    
                                    // 개념적인 부분 : 위임? 다시 이벤트 위임 공부, Function.prototype.call()은 모든 함수에서 call이 있어야 한다.
        
                                    // 중요!! 함수가 호출한 주체가 this가 된다. apply, call
        //console.log(this);
    }

    update(){       // ** update는 상태값만 변경해줘야 한다.

        // ======================================
    
        

        // setInterval(draw, 10);

        //this.draw().drawImage(this.img, this.sx*this.ix, this.sy*this.iy);

        // 조건 설정!! dx가 중심점, dy가 중심점이라고 가정하에 조건문 설정!
        // 정확히는 dx가 기준점이 되고 x가 움직이는 변수이다.
        //  x축 따로, y축 따로 구성해서 합쳐서 구현!!

        if(this.dx - 1 < this.x  && this.x < this.dx + 1 ||  this.dy - 1 < this.y && this.y < this.dy + 1){           
            this.vx = 0;
            this.vy = 0;

            this.ix = 1;                         // 멈출 때만 상태값을 바꿔줘도 되는가?***
            
        }
        
        if(this.vx == 0 && this.vy == 0 ) {      // 멈출 때 조건 설정 : vx 설정 (이것도 되긴 된다.)
            this.ix = 1;                         // 멈췄는지 안멈췄는지는 vx의 유무가 중요하다!!
            return;
        }

        // ======================================

        this.x += this.vx;                  // 여기서 목적직 위치까지 단위 위치(벡터)로 더하면서 업데이트 시킴
        this.y += this.vy;

        this.walkDelay--;

        //this.ix=this.ix==2?0:2;   // 3항 연산 중요!!!
        if(this.walkDelay == 0){
            if(this.ix == 0){
                this.ix = 2;
                
            }
            else if(this.ix == 1){
                this.ix = 0;
                
            }
            else if(this.ix == 2){
                this.ix = 0;
                
            }
            this.walkDelay = 20;
        }

    }

    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;
        
        this.dx = dx;
        this.dy = dy;
    }

    move(dir){

        switch(dir){
            case 1: // 북쪽
                this.y -= 50;       // 브라우저 기준으로 시작을 생각해보면 된다.
            break;

            case 2: // 동쪽
                this.x += 50;
            break;
            
            case 3: // 남쪽
                this.y += 50;
            break;
            
            case 4: // 서쪽
                this.x -= 50;
            break;
            
        }
    }
}



-background.js

class Background {
    
    constructor(){
        this.x=0;
        this.y=0;
        this.img = document.querySelector("#bg");

    }

    draw(ctx){
        ctx.drawImage(this.img, this.x, this.y);
    }

    update(){

    }
}