[아이스 브레이킹]

화면 기록 2024-10-23 오후 12.02.40.mov

https://bolt.new/ 에서 AI가 만든 스페이스 슈팅 게임

1. 배경 만들기

2. 비행기 만들기

image.png

// [ useLoadImage.tsx ] : 별도의 이미지 load hook 생성
export const useLoadImage = () => {
    // 이미지 로드 확인용
    const [isImgLoaded, setIsImgLoaded] = useState<boolean[]>([false, false]);
    const isCompleted = isImgLoaded.some(item => !item); // 모든 이미지 불러오기 완료

		// 이미지 load 완료할 경우 상태 값 변경 
    const handleImageLoad = (idx: number) => {
        if(isImgLoaded[idx]) return;

        setIsImgLoaded([
            ...isImgLoaded.slice(0, idx),
            true,
            ...isImgLoaded.slice(idx + 1, isImgLoaded.length)
        ]);
    }

		// 이미지 불러오기
    const bgImg = new Image();
    bgImg.src = '/images/space.png';
    bgImg.onload = () => handleImageLoad(0);

    const fighterImg = new Image();
    fighterImg.src = '/images/fighter.png';
    fighterImg.onload = () => handleImageLoad(1);

    return {
		    // 이미지 load 상태값 
        isCompleted,
        // 이미지 객체 
        bgImg, fighterImg,
    }
}
// [ FighterVO.tsx ] : 비행기 객체 생성
interface IFighterVO {
    x: number;
    y: number;
    speed: number;
}

export default class FighterVO {
    x: number;
    y: number;
    speed: number;

    constructor(vo: IFighterVO) {
        this.x = vo.x;
        this.y = vo.y;
        this.speed = vo.speed;
    }
}
// [ Shooter.tsx ]
// 전체 캔버스 영역 사이즈
const ctxW = 600;
const ctxH = 400;
    
// 비행기 객체 좌표 가지고 있는 객체 생성
const fighter = new FighterVO({
    x: 50,
    y: ctxH / 2,
    speed: 5
});

const Shooter = () => {
    const { isCompleted, bgImg, fighterImg } = useLoadImage();
    
		// 배경 이미지 그리기
		const animate = (ctx: CanvasRenderingContext2D) => {
		    if(isCompleted) return;
		
		    ctx.drawImage(bgImg, 0,0);
		    ctx.drawImage(fighterImg, fighter.x, fighter.y);
		};
		
		const ref = useCanvas(ctxW, ctxH, animate);
		
		return (
		      <canvas ref={ref}/>
		);
}

3. 비행기 움직이기

화면 기록 2024-10-20 오전 1.49.08.mov

// [ FighterVO.tsx ] 

// 키보드 key에 따른 비행기 좌표 수정
update = (code: string) =>{
      // 키보드 키에 따른 비행기 좌표 변경
      switch (code) {
          case  'w':
              this.y -= this.speed;
              break;
          case  's':
              this.y += this.speed;
              break;
          case  'a':
              this.x -= this.speed;
              break;
          case  'd':
              this.x += this.speed;
              break;
      }

      // 비행기가 화면 밖으로 안나가도록 조건 처리
      if(this.x <= 0) {
          this.x = 0;
      }
      if(this.x >= this.ctxW - 60) {
          this.x = this.ctxW - 60;
      }
      if(this.y <= 0) {
          this.y = 0;
      }
      if(this.y >= this.ctxH - 30) {
          this.y = this.ctxH - 30;
      }
  }
// useKeyBoadEvents.ts
export const useKeydownEvent = (onEvent: (code: string) => void) => {
    useEffect(() => {
        window.addEventListener('keydown', (e: KeyboardEvent) => {
            onEvent(e.key);
        })

        return () => {
            window.removeEventListener('keydown', (e: KeyboardEvent) => {
                onEvent(e.key);
            })
        }
    }, []);
}
// [ Shooter.tsx ]
// 비행기 객체
const fighter = new FighterVO({
    x: 50,
    y: 400 / 2,
    speed: 5,
    //화면 벗어남 방지 위한 캔버스 사이즈 값 추가
    ctxW, 
    ctxH 
});

// 프레임 시간 조절 함 
let lastUpdateTime = 0;
let acDelta = 0;
let msPerFrame = 1000;

const animate = (ctx: CanvasRenderingContext2D) => {
    if(isCompleted) return;

    let delta = Date.now() - lastUpdateTime;
    if(acDelta > msPerFrame) {
        acDelta = 0;

        ctx.drawImage(bgImg, 0,0);
        ctx.drawImage(fighterImg, fighter.x, fighter.y);

        lastUpdateTime = Date.now(); // 예제 소스와 다른 부분!!
    } else {
        acDelta = delta;
    }
};

//키도브 이벤트 등록
useKeydownEvent(fighter.update);

4. 비행기 총알 발사하기

화면 기록 2024-10-20 오전 2.32.09.mov

// [ LaserVO.ts ] > 미사일 객체 
interface  ILaserVO {
    x: number;
    y: number;
}

export default class LaserVO {
    x: number;
    y: number;

    constructor(vo: ILaserVO) {
        this.x = vo.x;
        this.y = vo.y;
    }

		//
    moveX() {
        this.x += 20;
    }
}
// [ useLoadImage.tsx ]

// 미사일 이미지 추가
const laserImg = new Image();
laserImg.src = '/images/laser.png';
laserImg.onload = () => handleImageLoad(2);
// [ Shooter.tsx ]

// 레이저 관련
let lasers: LaserVO[] = [];
const laserTotal = 10;

// 미사일 그림 그리는 함수
const drawLaser = (ctx: CanvasRenderingContext2D) => {
    if(lasers.length) {
        lasers.forEach((item, idx) => {
            ctx.drawImage(laserImg, item.x, item.y);
            
						//미사일 이동 처리
            item.moveX();

						// 미사일이 캔버스 영역 벗어날 경우 삭제 처리
            if(item.x > ctxW) {
                lasers.splice(idx, 1);
            }
        })
    }
  }
  
 const animate = (ctx: CanvasRenderingContext2D) => {
    if(isCompleted) return;

    let delta = Date.now() - lastUpdateTime;
    if(acDelta > msPerFrame) {
        acDelta = 0;

        ctx.drawImage(bgImg, 0,0);
        ctx.drawImage(fighterImg, fighter.x, fighter.y);

        drawLaser(ctx); // 미사일 함수 추가

        lastUpdateTime = Date.now();
    } else {
        acDelta = delta;
    }
};

useKeydownEvent((key) => {
	  // 비행기 객체 > update에서 현재 비행기 좌표 값 반환하도록 수정 
	  // FighterVO > update() >  return {x: this.x, y: this.y}; 소스 추가
    const {x, y} = fighter.update(key);

    // 스페이스바 키보드 이벤트시 미사일 객체 추가
    if(key == ' ' && lasers.length <= laserTotal){
        lasers.push(new LaserVO({
            x: x + 50,
            y: y + 10
        }));
    }
});

5. 우주 배경 화면 움직이기