Created
June 29, 2026 16:14
-
-
Save EncodeTheCode/a5816adca54377c4bff404ad0783cc34 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class NetClient { | |
| constructor(serverUrl) { | |
| this.ws = null; | |
| this.serverUrl = serverUrl; | |
| this.connected = false; | |
| this.authenticated = false; | |
| this.seq = 0; | |
| } | |
| connect() { | |
| this.ws = new WebSocket(this.serverUrl); | |
| this.ws.onopen = () => { | |
| this.connected = true; | |
| console.log("Connected to server"); | |
| }; | |
| this.ws.onmessage = (e) => { | |
| this.onMessage(JSON.parse(e.data)); | |
| }; | |
| this.ws.onclose = () => { | |
| this.connected = false; | |
| console.log("Disconnected"); | |
| }; | |
| } | |
| send(data) { | |
| if (!this.connected) return; | |
| this.ws.send(JSON.stringify(data)); | |
| } | |
| onMessage(msg) { | |
| if (msg.type === "auth_ok") { | |
| this.authenticated = true; | |
| console.log("Authenticated:", msg.user_id); | |
| } | |
| } | |
| } | |
| // ======================================================= | |
| // 🧠 INPUT SYSTEM (KEYBOARD STATE) | |
| // ======================================================= | |
| class InputController { | |
| constructor() { | |
| this.keys = {}; | |
| window.addEventListener("keydown", (e) => this.keys[e.key] = true); | |
| window.addEventListener("keyup", (e) => this.keys[e.key] = false); | |
| } | |
| getMovementVector() { | |
| let dx = 0, dy = 0, dz = 0; | |
| if (this.keys["w"]) dz -= 1; | |
| if (this.keys["s"]) dz += 1; | |
| if (this.keys["a"]) dx -= 1; | |
| if (this.keys["d"]) dx += 1; | |
| return { dx, dy, dz }; | |
| } | |
| isRunning() { | |
| return !!this.keys["Shift"]; | |
| } | |
| isWalking() { | |
| return !!this.keys[" "]; | |
| } | |
| } | |
| // ======================================================= | |
| // 🧍 PLAYER (LOCAL GAME ENTITY) | |
| // ======================================================= | |
| class Player { | |
| constructor() { | |
| this.user_id = null; | |
| this.x = 0; | |
| this.y = 0; | |
| this.z = 0; | |
| this.px = 0; | |
| this.py = 0; | |
| this.pz = 0; | |
| this.health = 100; | |
| this.armor = 0; | |
| this.weapon_id = 0; | |
| this.primary_ammo = 30; | |
| this.secondary_ammo = 90; | |
| this.walking = false; | |
| this.running = false; | |
| this.ping = 0; | |
| } | |
| // store previous position for interpolation | |
| savePrevious() { | |
| this.px = this.x; | |
| this.py = this.y; | |
| this.pz = this.z; | |
| } | |
| applyServerState(s) { | |
| this.px = this.x; | |
| this.py = this.y; | |
| this.pz = this.z; | |
| this.x = s.x; | |
| this.y = s.y; | |
| this.z = s.z; | |
| this.health = s.health; | |
| this.armor = s.armor; | |
| this.weapon_id = s.weapon_id; | |
| this.primary_ammo = s.primary_ammo; | |
| this.secondary_ammo = s.secondary_ammo; | |
| this.walking = s.walking; | |
| this.running = s.running; | |
| this.ping = s.ping; | |
| this.user_id = s.user_id; | |
| } | |
| } | |
| // ======================================================= | |
| // 🌍 WORLD STATE (ALL PLAYERS) | |
| // ======================================================= | |
| class World { | |
| constructor() { | |
| this.players = {}; | |
| } | |
| update(serverPlayers) { | |
| this.players = serverPlayers; | |
| } | |
| getPlayer(id) { | |
| return this.players[id]; | |
| } | |
| } | |
| // ======================================================= | |
| // 🎮 MAIN GAME CLIENT (FULL SYSTEM) | |
| // ======================================================= | |
| class GameClient { | |
| constructor(serverUrl) { | |
| this.net = new NetClient(serverUrl); | |
| this.input = new InputController(); | |
| this.player = new Player(); | |
| this.world = new World(); | |
| this.tickRate = 20; | |
| this.pingRate = 1000; | |
| } | |
| // ========================= | |
| // START GAME | |
| // ========================= | |
| start() { | |
| this.net.connect(); | |
| this.setupNetworking(); | |
| this.startLoops(); | |
| this.gameLoop(); | |
| } | |
| // ========================= | |
| // AUTH | |
| // ========================= | |
| sendAuth() { | |
| this.net.send({ | |
| type: "auth", | |
| user_id: "player_123", | |
| token: "secure_token" | |
| }); | |
| } | |
| // ========================= | |
| // NETWORK HANDLING | |
| // ========================= | |
| setupNetworking() { | |
| this.net.onMessage = (msg) => { | |
| if (msg.type === "auth_ok") { | |
| this.net.authenticated = true; | |
| this.player.user_id = msg.user_id; | |
| console.log("Logged in as:", msg.user_id); | |
| } | |
| if (msg.type === "world") { | |
| this.world.update(msg.players); | |
| // find self inside world | |
| for (const id in msg.players) { | |
| const p = msg.players[id]; | |
| if (p.user_id === this.player.user_id) { | |
| this.player.applyServerState(p); | |
| } | |
| } | |
| } | |
| }; | |
| } | |
| // ========================= | |
| // SEND MOVEMENT PACKET | |
| // ========================= | |
| sendMovement() { | |
| const m = this.input.getMovementVector(); | |
| this.net.send({ | |
| type: "update", | |
| seq: this.net.seq++, | |
| dx: m.dx, | |
| dy: m.dy, | |
| dz: m.dz, | |
| walking: this.input.isWalking(), | |
| running: this.input.isRunning() | |
| }); | |
| } | |
| // ========================= | |
| // SEND COMBAT STATE | |
| // ========================= | |
| sendCombat() { | |
| this.net.send({ | |
| type: "combat", | |
| weapon_id: this.player.weapon_id, | |
| primary_ammo: this.player.primary_ammo, | |
| secondary_ammo: this.player.secondary_ammo | |
| }); | |
| } | |
| // ========================= | |
| // SEND PING | |
| // ========================= | |
| sendPing() { | |
| this.net.send({ | |
| type: "ping", | |
| t: Date.now() | |
| }); | |
| } | |
| // ========================= | |
| // INTERPOLATION (SMOOTH MOVEMENT) | |
| // ========================= | |
| lerp(a, b, t) { | |
| return a + (b - a) * t; | |
| } | |
| getSmoothPosition(p) { | |
| return { | |
| x: this.lerp(p.px, p.x, 0.15), | |
| y: this.lerp(p.py, p.y, 0.15), | |
| z: this.lerp(p.pz, p.z, 0.15) | |
| }; | |
| } | |
| // ========================= | |
| // MAIN GAME LOOP | |
| // ========================= | |
| gameLoop() { | |
| const loop = () => { | |
| // update movement every frame | |
| if (this.net.connected && this.net.authenticated) { | |
| this.sendMovement(); | |
| } | |
| requestAnimationFrame(loop); | |
| }; | |
| loop(); | |
| } | |
| // ========================= | |
| // TIMED NETWORK LOOPS | |
| // ========================= | |
| startLoops() { | |
| // ping | |
| setInterval(() => { | |
| this.sendPing(); | |
| }, this.pingRate); | |
| // combat sync | |
| setInterval(() => { | |
| this.sendCombat(); | |
| }, 200); | |
| } | |
| } | |
| // ======================================================= | |
| // 🚀 BOOT GAME CLIENT | |
| // ======================================================= | |
| const game = new GameClient("ws://localhost:4445"); | |
| game.start(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment