fivem-svelte-boilerplate Svelte Themes

Fivem Svelte Boilerplate

🎮 FiveM Svelte Boilerplate

โครงสร้างพื้นฐานสำหรับพัฒนา FiveM Resource ด้วย Svelte Framework

📋 สารบัญ


🚀 Getting Started

ความต้องการของระบบ

  • Node.js (เวอร์ชัน 18 ขึ้นไป)
  • FiveM Server
  • Git

วิธีการติดตั้ง

  1. Clone และเข้าไปในโฟลเดอร์

    git clone https://github.com/Netizen-alt/fivem-svelte-boilerplate.git
    cd fivem-svelte-boilerplate
    
  2. ติดตั้ง Dependencies

    cd interface
    npm install
    
  3. Build โปรเจค

    npm run build
    
  4. วางไฟล์ใน Server

  • คัดลอกโฟลเดอร์ fivem-svelte-boilerplate ไปยัง resources/[local]/
  • เพิ่ม start fivem-svelte-boilerplate ใน server.cfg

✨ Features

Feature รายละเอียด
🎨 Svelte 5 Framework ที่ทันสมัยและรวดเร็ว
Vite Build tool ที่รวดเร็วเหนือใคร
📘 TypeScript Type Safety สำหรับโค้ดที่ปลอดภัย
Hot Reload อัปเดตโค้ดแบบ Real-time
🎮 FiveM Ready พร้อมใช้งานกับ NUI Callbacks

📁 โครงสร้างโปรเจค

fivem-svelte-boilerplate/
├── 📄 fxmanifest.lua          # ไฟล์ Manifest ของ FiveM
├── 📁 client/                 # โค้ด Client-side
│   ├── 📄 app.lua            # Logic หลักของ Client
│   └── 📄 nui.lua            # Utilities สำหรับ NUI
├── 📁 interface/             # Frontend Application
│   ├── 📄 package.json       # Dependencies ของ Node.js
│   ├── 📄 vite.config.ts     # การตั้งค่า Vite
│   ├── 📄 svelte.config.js   # การตั้งค่า Svelte
│   ├── 📁 src/               # Source Code หลัก
│   │   ├── 📄 App.svelte     # Component หลัก
│   │   ├── 📄 main.ts        # Entry Point
│   │   └── 📁 lib/           # Components ที่นำกลับมาใช้ได้
└── 📁 server/                # Server-side (ว่างเปล่า)

📄 รายละเอียดไฟล์สำคัญ

fxmanifest.lua - การกำหนดค่า FiveM

fx_version "cerulean"
description "Basic FiveM resource boilerplate using Svelte"
author "Netizen-alt"
version '0.1beta'
games {"gta5","rdr3"}

ui_page 'interface/build/index.html'    # หน้า NUI หลัก
client_script "client/**/*"             # Client Scripts
server_script "server/**/*"             # Server Scripts

files {
    'interface/build/index.html',
    'interface/build/**/*',              # ไฟล์ Build จาก Svelte
}

client/app.lua - Client Logic

  • toggleNuiFrame() - เปิด/ปิด NUI Interface
  • show-nui Command - คำสั่งแสดง UI
  • hideFrame Callback - ปิด UI จากหน้าเว็บ
  • getClientData Callback - รับส่งข้อมูลระหว่าง Client-UI

client/nui.lua - NUI Utilities

  • SendMessageNui() - ส่งข้อความไปยัง UI
  • debugPrint() - Debug function ที่เปิด/ปิดได้

🎨 Svelte Utils

การสร้าง Component ใหม่

สร้างไฟล์ในโฟลเดอร์ interface/src/lib/:

<!-- PlayerInfo.svelte -->
<script lang="ts">
  interface PlayerProps {
    name: string;
    level: number;
    money?: number;
  }
  
  let { name, level, money = 0 }: PlayerProps = $props();
  
  // รูนรับ State ใหม่ของ Svelte 5
  let isOnline = $state(true);
  let health = $state(100);
  
  // Derived State
  let statusColor = $derived(health > 50 ? 'green' : 'red');
  
  // Effect
  $effect(() => {
    console.log(`${name} health: ${health}`);
  });
  
  function updateHealth() {
    health = Math.max(0, health - 10);
  }
</script>

<div class="player-card" style="border-color: {statusColor}">
  <h3>{name}</h3>
  <p>Level: {level}</p>
  <p>Money: ${money}</p>
  <p>Health: {health}%</p>
  
  <button onclick={updateHealth}>
    Take Damage
  </button>
</div>

<style>
  .player-card {
    border: 2px solid;
    padding: 1rem;
    border-radius: 8px;
    background: rgba(0,0,0,0.8);
    color: white;
  }
</style>

การจัดการ Global State

สร้าง interface/src/lib/stores.ts:

import { writable } from 'svelte/store';

interface GameState {
  playerName: string;
  playerCoords: { x: number; y: number; z: number };
  isUIVisible: boolean;
}

export const gameState = writable<GameState>({
  playerName: '',
  playerCoords: { x: 0, y: 0, z: 0 },
  isUIVisible: false
});

// Helper functions
export function updatePlayerPosition(coords: {x: number, y: number, z: number}) {
  gameState.update(state => ({
    ...state,
    playerCoords: coords
  }));
}

export function showUI() {
  gameState.update(state => ({ ...state, isUIVisible: true }));
}

export function hideUI() {
  gameState.update(state => ({ ...state, isUIVisible: false }));
}

🎮 Usage

เปิด/ปิด UI Interface

จาก Client (Lua):

-- เปิด UI
function OpenMyUI()
    SetNuiFocus(true, true)
    SendNUIMessage({
        action = 'showUI',
        data = {
            playerName = GetPlayerName(PlayerId()),
            serverId = GetPlayerServerId(PlayerId())
        }
    })
end

-- ปิด UI  
RegisterNUICallback('closeUI', function(data, cb)
    SetNuiFocus(false, false)
    cb('ok')
end)

-- คำสั่งเปิด UI
RegisterCommand('myui', function()
    OpenMyUI()
end, false)

จาก Svelte UI:

// ปิด UI และคืน Focus ให้เกม
function closeInterface() {
  fetch('https://fivem-svelte-boilerplate/hideFrame', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({})
  });
}

// การส่งข้อมูลไปยัง Client
function sendToClient(action: string, data: any) {
  fetch(`https://fivem-svelte-boilerplate/${action}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  })
  .then(response => response.json())
  .then(result => {
    console.log('Response:', result);
  });
}

การรับส่งข้อมูล Client ↔ UI

Svelte Component สำหรับรับ Messages:

<script lang="ts">
  import { onMount } from 'svelte';
  import { gameState } from './lib/stores.js';
  
  let playerData = $state({
    name: '',
    coords: { x: 0, y: 0, z: 0 },
    health: 100
  });
  
  onMount(() => {
    // ฟังข้อความจาก Client
    const handleMessage = (event: MessageEvent) => {
      const { action, data } = event.data;
      
      switch (action) {
        case 'showUI':
          gameState.update(state => ({ ...state, isUIVisible: true }));
          break;
          
        case 'updatePlayer':
          playerData = { ...playerData, ...data };
          break;
          
        case 'updateCoords':
          playerData.coords = data;
          break;
      }
    };
    
    window.addEventListener('message', handleMessage);
    
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  });
</script>

{#if $gameState.isUIVisible}
  <div class="ui-container">
    <h2>Welcome, {playerData.name}</h2>
    <p>Position: {playerData.coords.x}, {playerData.coords.y}, {playerData.coords.z}</p>
    <p>Health: {playerData.health}%</p>
    
    <button onclick={() => sendToClient('heal', { amount: 25 })}>
      Heal Player
    </button>
    
    <button onclick={closeInterface}>
      Close UI
    </button>
  </div>
{/if}

Event System สำหรับการสื่อสาร

Client Event Handler:

-- ส่งข้อมูลไปยัง UI เป็นระยะ
Citizen.CreateThread(function()
    while true do
        if nuiOpen then
            local coords = GetEntityCoords(PlayerPedId())
            SendNUIMessage({
                action = 'updateCoords',
                data = { x = coords.x, y = coords.y, z = coords.z }
            })
        end
        Citizen.Wait(1000) -- อัปเดตทุก 1 วินาที
    end
end)

-- รับ Action จาก UI
RegisterNUICallback('heal', function(data, cb)
    local ped = PlayerPedId()
    local currentHealth = GetEntityHealth(ped)
    local newHealth = math.min(200, currentHealth + data.amount)
    
    SetEntityHealth(ped, newHealth)
    
    -- ส่งผลลัพธ์กลับไปยัง UI
    cb({ 
        success = true, 
        newHealth = GetEntityHealth(ped) 
    })
end)

🛠️ Misc Utils

Debug Mode Configuration

เปิดใช้งาน Debug Mode:

# ใน server.cfg
set fivem-svelte-boilerplate-debugMode 1

จากนั้นใช้งานใน Lua:

debugPrint('This will only show when debug mode is on!')
debugPrint('Player data:', json.encode(playerData))

TypeScript Definitions

สร้าง interface/src/types/fivem.ts:

// ประเภทข้อมูลสำหรับ FiveM
export interface PlayerData {
  id: number;
  name: string;
  coords: Vector3;
  health: number;
  money: number;
  job?: string;
}

export interface Vector3 {
  x: number;
  y: number;
  z: number;
}

export interface NUIMessage<T = any> {
  action: string;
  data: T;
}

export interface NUICallback {
  (data: any): void;
}

// Global declarations สำหรับ Window
declare global {
  interface Window {
    invokeNative?: (hash: string, ...args: any[]) => any;
  }
}

Custom Hooks สำหรับ NUI

สร้าง interface/src/lib/hooks.ts:

import { onMount, onDestroy } from 'svelte';

// Hook สำหรับฟัง NUI Messages
export function useNUIListener<T>(
  action: string, 
  handler: (data: T) => void
) {
  onMount(() => {
    const listener = (event: MessageEvent) => {
      if (event.data.action === action) {
        handler(event.data.data);
      }
    };
    
    window.addEventListener('message', listener);
    
    return () => {
      window.removeEventListener('message', listener);
    };
  });
}

// Hook สำหรับส่ง NUI Callbacks
export function useNUICallback() {
  return {
    send: async <T>(action: string, data?: any): Promise<T> => {
      try {
        const response = await fetch(`https://fivem-svelte-boilerplate/${action}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data || {})
        });
        return await response.json();
      } catch (error) {
        console.error('NUI Callback Error:', error);
        throw error;
      }
    }
  };
}

// Hook สำหรับจัดการ Visibility
export function useUIVisibility() {
  let visible = $state(false);
  
  useNUIListener('setVisible', (isVisible: boolean) => {
    visible = isVisible;
    document.body.style.display = isVisible ? 'block' : 'none';
  });
  
  return {
    get visible() { return visible; },
    show: () => { visible = true; },
    hide: () => { visible = false; }
  };
}

Utility Functions

สร้าง interface/src/lib/utils.ts:

// การจัดรูปแบบเงิน
export function formatMoney(amount: number): string {
  return new Intl.NumberFormat('th-TH', {
    style: 'currency',
    currency: 'THB'
  }).format(amount);
}

// การคำนวณระยะทาง
export function calculateDistance(pos1: Vector3, pos2: Vector3): number {
  const dx = pos1.x - pos2.x;
  const dy = pos1.y - pos2.y;
  const dz = pos1.z - pos2.z;
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

// การ Format เวลา
export function formatTime(seconds: number): string {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = seconds % 60;
  
  return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

// สุ่มสี HEX
export function randomColor(): string {
  return '#' + Math.floor(Math.random()*16777215).toString(16);
}

⚙️ Development Workflow

การพัฒนาในโหมด Development

  1. เริ่ม Vite Dev Server:

    cd interface
    npm run dev
    
  2. แก้ไข UI Page ใน fxmanifest.lua: ```lua

-- สำหรับ Development (Hot Reload) ui_page 'http://localhost:5173'

-- สำหรับ Production -- ui_page 'interface/build/index.html'


3. **Hot Reload จะทำงานอัตโนมัติ** เมื่อมีการแก้ไขไฟล์

### Build สำหรับ Production

```bash
# Build โปรเจค
cd interface
npm run build

# ตรวจสอบ Type
npm run check

Scripts ที่มีให้ใช้งาน

Script คำอธิบาย
npm run dev เริ่ม development server
npm run build Build สำหรับ production
npm run preview ดูตัวอย่าง build
npm run check ตรวจสอบ TypeScript

Debugging Tips

1. FiveM Console Debugging:

-- ใช้ debugPrint แทน print เสมอ
debugPrint('UI opened for player:', GetPlayerName(PlayerId()))
debugPrint('Player coords:', json.encode(GetEntityCoords(PlayerPedId())))

2. Browser DevTools:

  • เปิด UI แล้วกด F12 เพื่อเปิด DevTools
  • ตรวจสอบ Console สำหรับ JavaScript errors
  • ใช้ Network tab ดู NUI callbacks

3. Svelte Inspector:

<script>
  let debugData = $state({ count: 0, name: 'test' });
  
  // ใช้ $inspect สำหรับ debug state
  $inspect(debugData);
</script>

📝 Additional Notes

⚠️ ข้อควรระวัง

  1. การจัดการ Memory: ปิด event listeners เมื่อ component ถูก destroy
  2. CORS Policy: FiveM มี security restrictions สำหรับ external requests
  3. Performance: หลีกเลี่ยง excessive re-rendering
  4. File Paths: ใช้ relative paths ใน components

🎯 Best Practices

Component Architecture:

<!-- ✅ ถูกต้อง -->
<script lang="ts">
  interface Props {
    title: string;
    data?: PlayerData;
  }
  
  let { title, data }: Props = $props();
</script>

<!-- ❌ ไม่ควรทำ -->
<script>
  export let title; // ไม่มี type safety
</script>

Error Handling:

// ✅ ถูกต้อง
async function callServer(action: string, data: any) {
  try {
    const response = await fetch(`https://resource-name/${action}`, {
      method: 'POST',
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Server call failed:', error);
    // แสดง error message ให้ user
    return null;
  }
}

🔧 การขยาย Features

เพิ่ม Server-side Logic:

-- server/main.lua
RegisterServerEvent('myResource:getData')
AddEventHandler('myResource:getData', function(playerId)
    local player = GetPlayerName(source)
    
    -- ดึงข้อมูลจาก Database
    local playerData = {
        name = player,
        money = 10000,
        level = 5
    }
    
    TriggerClientEvent('myResource:receiveData', source, playerData)
end)

Database Integration:

-- ใช้ oxmysql
local oxmysql = exports.oxmysql

function GetPlayerData(playerId)
    local result = oxmysql:query_async('SELECT * FROM players WHERE id = ?', {playerId})
    return result[1]
end

📚 Resources & Documentation


🤝 การมีส่วนร่วม

พัฒนาโดย Netizen-alt - ยินดีรับ contributions!

  1. Fork repository
  2. สร้าง feature branch (git checkout -b feature/new-feature)
  3. Commit การเปลี่ยนแปลง (git commit -m 'Add new feature')
  4. Push ไปยัง branch (git push origin feature/new-feature)
  5. เปิด Pull Request

📞 ติดต่อ & สนับสนุน


🚀 Happy Coding with Svelte + FiveM!

Top categories

Loading Svelte Themes