โครงสร้างพื้นฐานสำหรับพัฒนา FiveM Resource ด้วย Svelte Framework
Clone และเข้าไปในโฟลเดอร์
git clone https://github.com/Netizen-alt/fivem-svelte-boilerplate.git
cd fivem-svelte-boilerplate
ติดตั้ง Dependencies
cd interface
npm install
Build โปรเจค
npm run build
วางไฟล์ใน Server
fivem-svelte-boilerplate
ไปยัง resources/[local]/
start fivem-svelte-boilerplate
ใน server.cfg
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
- การกำหนดค่า FiveMfx_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 LogictoggleNuiFrame()
- เปิด/ปิด NUI Interfaceshow-nui
Command - คำสั่งแสดง UIhideFrame
Callback - ปิด UI จากหน้าเว็บgetClientData
Callback - รับส่งข้อมูลระหว่าง Client-UIclient/nui.lua
- NUI UtilitiesSendMessageNui()
- ส่งข้อความไปยัง UIdebugPrint()
- Debug function ที่เปิด/ปิดได้สร้างไฟล์ในโฟลเดอร์ 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>
สร้าง 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 }));
}
-- เปิด 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)
// ปิด 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);
});
}
<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}
-- ส่งข้อมูลไปยัง 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)
เปิดใช้งาน 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))
สร้าง 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;
}
}
สร้าง 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; }
};
}
สร้าง 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);
}
เริ่ม Vite Dev Server:
cd interface
npm run dev
แก้ไข 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
Script | คำอธิบาย |
---|---|
npm run dev |
เริ่ม development server |
npm run build |
Build สำหรับ production |
npm run preview |
ดูตัวอย่าง build |
npm run check |
ตรวจสอบ TypeScript |
-- ใช้ debugPrint แทน print เสมอ
debugPrint('UI opened for player:', GetPlayerName(PlayerId()))
debugPrint('Player coords:', json.encode(GetEntityCoords(PlayerPedId())))
F12
เพื่อเปิด DevTools<script>
let debugData = $state({ count: 0, name: 'test' });
// ใช้ $inspect สำหรับ debug state
$inspect(debugData);
</script>
<!-- ✅ ถูกต้อง -->
<script lang="ts">
interface Props {
title: string;
data?: PlayerData;
}
let { title, data }: Props = $props();
</script>
<!-- ❌ ไม่ควรทำ -->
<script>
export let title; // ไม่มี type safety
</script>
// ✅ ถูกต้อง
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;
}
}
-- 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)
-- ใช้ oxmysql
local oxmysql = exports.oxmysql
function GetPlayerData(playerId)
local result = oxmysql:query_async('SELECT * FROM players WHERE id = ?', {playerId})
return result[1]
end
พัฒนาโดย Netizen-alt - ยินดีรับ contributions!
git checkout -b feature/new-feature
)git commit -m 'Add new feature'
)git push origin feature/new-feature
)🚀 Happy Coding with Svelte + FiveM!