14 คะแนน โดย cybrshin 2026-01-04 | 9 ความคิดเห็น | แชร์ทาง WhatsApp

เดิมทีผมใช้ claude code กับ cursor แต่หลังจากลาออกเมื่อปีที่แล้วก็เปลี่ยนมาใช้ antigravity ตั้งแต่เดือนพฤศจิกายน และทำโปรเจกต์ toy web service เล็ก ๆ วันละอย่างมาตลอด แล้วจู่ ๆ เมื่อ 3 วันก่อน (1/2) ก็อยากลองสร้างเกม Tower Defense ขึ้นมาดูครับ
เดิมทีผมเป็นนักพัฒนาเว็บ frontend อยู่แล้ว เลยขี้เกียจห่อเป็นแอป ก็เลยตั้งใจให้ทำงานเฉพาะบนเดสก์ท็อปเบราว์เซอร์เท่านั้น อีกทั้งงานที่อิงกับ canvas และการทำไฟล์ภาพก็น่าจะใช้เวลานานเกินไป เลยเริ่มจากไอเดียว่าใช้ emoji ให้คุ้มที่สุดดีกว่า

ตอนแรกกะว่าจะทำให้เสร็จภายในวันเดียว แต่พอทำไปทำมาก็สนุก เลยแก้โน่นเพิ่มนี่ไปเรื่อย ๆ สุดท้ายใช้เวลาไปถึง 3 วันเลยครับ ฮือ

เทคโนโลยีสแต็กพื้นฐานมีดังนี้

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

หลังจากปรับแก้อยู่หลายอย่าง สุดท้ายก็ได้ตัดสินใจเรื่อง game logic ดังนี้

1. พลังโจมตีของทาวเวอร์

ทาวเวอร์จะมีระยะโจมตี (Range) เพิ่มขึ้นทีละ 10 ทุกครั้งที่เลเวลเพิ่มผ่านการ Merge แต่ประสิทธิภาพพลังโจมตีจะถูกปรับเป็น 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): ศัตรู tier ปัจจุบัน (70%) และศัตรู tier ก่อนหน้า (30%) จะออกมาปะปนกัน
  • เวฟบอส (Every 6th): บอสจะเป็นศัตรูที่แข็งแกร่งที่สุดของยุคนั้นจำนวน 1 ตัว และมีลูกน้องคอยคุ้มกันอยู่รอบ ๆ
    • จำนวนศัตรูในเวฟบอส: Wave / 6 (เวฟ 6 มี 1 ตัว, เวฟ 12 มี 2 ตัว...)

4. รางวัลทอง (Active)

จะได้รับทองเมื่อกำจัดศัตรูหรือเคลียร์เวฟสำเร็จ

ps. ไม่ได้คำนึงถึงการใช้งานบนมือถือมากนักครับ แม้จะเพิ่ม touch event ไว้แล้ว แต่บน Fold หรือแท็บเล็ตใช้งานได้ดี ขณะที่หน้าจอเล็กยังไม่ได้ใส่ใจเท่าไรนัก~

ps. ถ้ามีจุดที่ควรปรับปรุงหรือพบบั๊ก แจ้งผ่านคอมเมนต์ได้เลยครับ ผมจะทยอยนำไปแก้ตอนมีเวลา

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

9 ความคิดเห็น

 
roxie 2026-02-27

เอาจริงดิ... 700 เวฟเนี่ยนะ...............???

 
cybrshin 2026-02-28

ทำได้ครับ 555

 
wancomplete 2026-01-07

ช่วงนี้ผมก็ลองทำเกมอยู่เหมือนกันเลยได้ลองใช้ https://verse8.io ดูด้วย แต่ที่นั่นช่วยสร้างทั้งภาพและแอนิเมชันสไปรต์ 2D ให้ได้โดยตรง ทำให้ไม่ต้องเสียเวลากับงานทำแอสเซ็ตและโฟกัสกับตรรกะของเกมได้อย่างเดียวเลยครับ แถมยังปล่อยขึ้นแพลตฟอร์มได้ทันทีอีกด้วย เลยไม่ต้องกังวลเรื่องโฮสติ้งแยกต่างหาก สะดวกมากจริง ๆ

 
huiya 2026-01-06

สนุกดีนะครับ รู้สึกสดใหม่มากที่มีการใช้อีโมจิอย่างเต็มที่
ผมไม่ได้เล่นเกม tower defense บ่อยนัก เลยไม่แน่ใจว่านี่เป็นความเห็นที่ถูกต้องไหม แต่รู้สึกแปลกที่ไม่ได้แยกเวลาเวฟกับเวลาเตรียมตัวออกจากกัน ทำให้แม้ตอนที่เวฟกำลังมาก็ยังสามารถเปลี่ยนตำแหน่งของทาวเวอร์ได้
ถ้ามองแบบสุดโต่ง ก็สามารถย้ายตำแหน่งไปเรื่อย ๆ เพื่อให้โจมตีได้ทุกจังหวะ เลยรู้สึกว่าระยะโจมตีกลายเป็นสิ่งที่ไม่มีความหมาย

 
cybrshin 2026-01-06

ขอบคุณที่เล่นกันอย่างสนุกนะครับ
ตอนที่ผมทำเองก็รู้สึกเหมือนกันว่าการย้ายตำแหน่งมันดูแปลก ๆ แต่ผมคิดว่านั่นก็อาจเป็นทั้งข้อดีของเกมนี้ และเป็นองค์ประกอบที่ทำให้เกมมีความไดนามิกและควบคุมได้ เลยปล่อยไว้แบบนั้นครับ
จริง ๆ แล้วถ้าจะย้ายให้เร็ว ถ้าไม่ merge ทาวเวอร์ก็ลำบากมากเลยครับ
ดูเหมือนว่าบาลานซ์ของเกม เช่น จะเพิ่มบทลงโทษสำหรับการย้ายหรือไม่ ก็ยังเป็นจุดที่ต้องคิดต่อไปเรื่อย ๆ ครับ

 
huiya 2026-01-07

อ๊ะ คุณพิจารณาไว้หมดแล้วนี่เอง ผมคิดสั้นไปเองครับ

 
cybrshin 2026-01-07

ไม่ใช่ครับ/ค่ะ ขอบคุณสำหรับความเห็นครับ/ค่ะ

 
waylake 2026-01-04

สวัสดีครับ ผมลองเล่นแล้วสนุกดี เลยขอส่งฟีดแบ็กด้านความปลอดภัยให้ครับ

  • ตอนนี้ HMAC secret (คีย์สำหรับลงลายเซ็น) ถูกเปิดเผยอยู่ในฝั่งฟรอนต์เอนด์
    • สามารถแก้ไข 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);  
})();  
  
 
cybrshin 2026-01-04

อ๊ะ ขอบคุณนะ~