Онлайн игра на Node.js очень сильно виснет
Как можно оптимизировать код игры так, чтобы лаги были минимальные? Сервер запускал на более мощных машинах (Linux, Windows), но особой разницы не видно. В изменении направления присутствует задержка примерно 10 секунд, на мощной машине до 8 секунд. Задержка присутствует именно при изменении угла движения игрока, то есть кадры в секунду нормальные.
P.S. Клиент здесь абсолютно ни при чем, лагает именно сервер.
Код:
var http = require("http");
var app = require("express")();
var websocketServer = require("websocket").server;
var httpServer = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>;-;</h1>');
res.end();
});
var WebSocket = require("websocket").client, PORT = 0, players = {}, masterHost = "localhost", objectsCount = 0, oldname = {}, playersCount = 0, updateZ = null, foodCount = 0, mouseMid = {}, setMov = {}, deletetimer = {}, systimer = {}, gameMouse = {}, sendTimer = {}, animTimer = {}, hydroloss = {}, reviveHP = {}, objects = {}, defsystemmsg = "cyan", stars = {};
var topx;
var imp = 1;
srconfig = {
maxConnections: 100,
leftBorder: 0,
rightBorder: 7000,
upBorder: 0,
downBorder: 7000,
movspeed: 1.05,
startPlayerMass: 0,
uName: "no name",
foodMCount: 800
};
function gameServer() {
this.moduleManager = new ModuleManager(this);
this.players = players;
this.config = srconfig;
this.objects = objects;
this.defsystemmsg = defsystemmsg;
players = this.players;
objects = this.objects;
defsystemmsg = this.defsystemmsg;
}
stars["type"] = {
desc: "Some description"
}
var wsServer = new websocketServer({
"httpServer": httpServer
})
function angle(cx, cy, ex, ey) {
var dy = ey - cy, dx = ex - cx;
var theta = Math.atan2(dy, dx);
theta *= 180 / Math.PI;
return theta;
}
function sq(x) { return x*x; }
function updateGame() {
for (var name in objects) {
objectC = objects[name];
if (objectC.canmove == 1) {
if (objectC.mY == 0 && objectC.mX == 0) {
objectC.mY = Math.floor(Math.random() * 11) -5;
objectC.mX = Math.floor(Math.random() * 11) -5;
}
if (objectC.speed == 0) {
objectC.speed = Math.random();
}
needSpeedObj = objectC.speed || srconfig.movspeed / 3;
var objAngle = Math.atan2(objectC.mY, objectC.mX);
var objDist = Math.sqrt(sq(objectC.mX) + sq(objectC.mY));
objectC.anglez = angle(0, 0, objectC.mX, objectC.mY);
speed = Math.min(needSpeedObj, 0.035 * Math.pow(objDist, impImitz));
if (speed < 0.06 && speed > -0.06) { speed = 0 }
objectC.vx = speed * Math.cos(objAngle);
objectC.vy = speed * Math.sin(objAngle);
objectC.x += objectC.vx;
objectC.y += objectC.vy;
}
if (objectC.x <= srconfig.leftBorder || objectC.x-objectC.size <= srconfig.leftBorder) {
objectC.x = srconfig.rightBorder - objectC.size - 25;
} else if (objectC.x >= srconfig.rightBorder || objectC.x+objectC.size >= srconfig.rightBorder - 25) {
objectC.x = srconfig.leftBorder + objectC.size + 25;
}
if (objectC.y <= srconfig.upBorder || objectC.y-objectC.size <= srconfig.upBorder) {
objectC.y = srconfig.downBorder - objectC.size - 25;
} else if (objectC.y >= srconfig.downBorder || objectC.y+objectC.size >= srconfig.downBorder - 25) {
objectC.y = srconfig.upBorder + objectC.size + 25;
}
if (objectC.angle >= 360) {
objectC.angle = 0;
}
objectC.angle += objectC.speedZ;
objectC.size = objectC.mass / 2;
}
for (var name in players) {
var checkingPlayer = players[name];
var checkingPlayerMouse = gameMouse[name];
if (checkingPlayer.spectator == 1) {
checkingPlayer.x += checkingPlayer.specspeedx;
checkingPlayer.y += checkingPlayer.specspeedy;
checkingPlayer.name = "";
if (checkingPlayer.x <= srconfig.leftBorder) {
checkingPlayer.specspeedx = 3;
} else if (checkingPlayer.x >= srconfig.rightBorder) {
checkingPlayer.specspeedx = -3;
}
if (checkingPlayer.y <= srconfig.upBorder) {
checkingPlayer.specspeedy = 3;
} else if (checkingPlayer.y >= srconfig.downBorder) {
checkingPlayer.specspeedy = -3;
}
}
if (checkingPlayer.spectator == 0) {
if (checkingPlayer.hp <= 0) {
checkingPlayer.spectator = 1;
checkingPlayer.hp = 100;
checkingPlayer.hydrogen = 100;
checkingPlayer.mass = checkingPlayer.mass / 2;
setSysmsg("You're dead! Watch your health!", checkingPlayer.clientid);
} else {
if (checkingPlayer.abilitycooldown > 0) {
checkingPlayer.abilitycooldown -= 1;
}
checkingPlayer.mX = checkingPlayerMouse.x;
checkingPlayer.mY = checkingPlayerMouse.y;
needSpeedPlr = checkingPlayer.speed || srconfig.movspeed;
var mouseAngle = Math.atan2(checkingPlayer.mY, checkingPlayer.mX);
var mouseDist = Math.sqrt(sq(checkingPlayer.mX) + sq(checkingPlayer.mY));
checkingPlayer.angle = angle(0, 0, checkingPlayer.mX, checkingPlayer.mY);
speed = Math.min(needSpeedPlr, 0.035 * Math.pow(mouseDist, impImitz));
if (speed < 0.06 && speed > -0.06) { speed = 0 }
checkingPlayer.vx = speed * Math.cos(mouseAngle);
checkingPlayer.vy = speed * Math.sin(mouseAngle);
checkingPlayer.x += checkingPlayer.vx;
checkingPlayer.y += checkingPlayer.vy;
if (checkingPlayer.x <= srconfig.leftBorder || checkingPlayer.x-checkingPlayer.size <= srconfig.leftBorder) {
checkingPlayer.x = srconfig.rightBorder - checkingPlayer.size - 25;
} else if (checkingPlayer.x >= srconfig.rightBorder || checkingPlayer.x+checkingPlayer.size >= srconfig.rightBorder - 25) {
checkingPlayer.x = srconfig.leftBorder + checkingPlayer.size + 25;
}
if (checkingPlayer.y <= srconfig.upBorder || checkingPlayer.y-checkingPlayer.size <= srconfig.upBorder) {
checkingPlayer.y = srconfig.downBorder - checkingPlayer.size - 25;
} else if (checkingPlayer.y >= srconfig.downBorder || checkingPlayer.y+checkingPlayer.size >= srconfig.downBorder - 25) {
checkingPlayer.y = srconfig.upBorder + checkingPlayer.size + 25;
}
for (var objid in objects) {
objectZ = objects[objid];
if (checkingPlayer.y+(checkingPlayer.size-5) + 2 * Math.PI > objectZ.y && checkingPlayer.y-(checkingPlayer.size-5) - 2 * Math.PI < objectZ.y && checkingPlayer.x+(checkingPlayer.size-5) + 2 * Math.PI > objectZ.x && checkingPlayer.x-(checkingPlayer.size-5) - 2 * Math.PI < objectZ.x) {
if (objectZ.type == "asteroid") {
foodCount--;
checkingPlayer.mass += Number(objectZ.mass / 2);
checkingPlayer.hydrogen += Number(objectZ.mass / 2);
delete objects[objid];
}
}
}
if (checkingPlayer.mass > checkingPlayer.xpToUpgrade) {
checkingPlayer.size = checkingPlayer.xpToUpgrade / 5;
} else {
checkingPlayer.size = checkingPlayer.mass / 5;
}
if (checkingPlayer.size < 25) {
checkingPlayer.size = 15;
}
checkingPlayer.camzoom = 1;
}
}
}
}
function spawnObject(type, count, mass, movz, x, y, owner, time, alpha, speedz, speed, mx, my) {
count = count == null ? 1 : count;
for (var i = 0; i < count; i++) {
var randxx = Math.floor(Math.random() * srconfig.rightBorder - 50) + 50;
var randyy = Math.floor(Math.random() * srconfig.downBorder - 50) + 50;
var randz = Math.floor(Math.random() * 3 - 1) + 1;
var moveornot = Math.random() < 0.7;
if (moveornot && type == "asteroid" && movz == undefined) {
var movz = 1;
} else {
var movz = 0;
}
colorrandx = '#' + (Math.random() * Math.pow(256, 3) | 0).toString(16).padStart(6, '0');
if (objects[objectsCount + ''] == null) {
if (typeof mass != "number") {
return
}
objects[objectsCount + ''] = {
x: x || randxx,
y: y || randyy,
mass: Number(mass),
angle: 0,
anglez: 0,
mX: mx || 0,
mY: my || 0,
vx: 0,
canmove: movz || 0,
vy: 0,
speed: speed || 0,
size: Math.floor(mass / 2),
type: type,
speedZ: speedz || Number(randz),
alpha: alpha || 1,
id: objectsCount + '',
owner: owner || null,
time: time || 0
};
objectsCount++;
if (type == "asteroid") {
foodCount++;
}
if (isNaN(mass)) {
console.log("[i] Object " + objectsCount + " spawned with " + mass + " xp!");
objectsCount--;
delete objects[objectsCount + ''];
}
} else {
console.log("[i] Object ID already exist!");
}
}
}
function deleteMessage(playerID) {
if (players[playerID]) {
players[playerID].message = "";
}
}
function deleteSysMessage(playerID) {
if (players[playerID]) {
players[playerID].sysmsgvis -= 0.01;
if (players[playerID].sysmsgvis <= 0) {
players[playerID].sysmsgvis = 0;
players[playerID].sysmsg = "";
clearInterval(systimer[playerID]);
}
}
}
function delSys(id) {
systimer[id] = setInterval(deleteSysMessage, 25, id)
}
function setSysmsg(msg, id) {
if (!players[id]) {
console.log("No player")
return
}
if (systimer[id]) {
clearInterval(systimer[id]);
}
players[id].sysmsgvis = 1;
players[id].sysmsg = msg;
console.log("[SYS] New sys msg to '" + players[id].name + "' is '" + msg + "'.")
setTimeout(delSys, 6000, id);
}
gameServer.prototype.setMessage = function(id, msg, color) {
if (!color) {
color = "white";
}
if (!players[id]) {
console.log(players)
return
}
if (deletetimer[id]) {
clearTimeout(deletetimer[id]);
}
players[id].message = msg;
players[id].messageColor = color;
console.log("[CHAT] New message by '" + players[id].name + "' is '" + msg + "'. Color '" + color + "'.")
deletetimer[id] = setTimeout(deleteMessage, 7000, id);
}
gameServer.prototype.start = function(zip, zport) {
var myIP = zip;
var myPort = zport;
PORT = myPort;
app.get("/", (req,res)=> res.sendFile(__dirname + "/index.html"))
app.listen(PORT+1, ()=>console.log("[i] Index started on port " + (PORT+1) + "!"))
httpServer.listen(PORT, () => console.log("[i] Starting on port " + PORT + "!"))
wsServer.on("request", request => {
var connection = request.accept(null, request.origin);
var clientID = guid();
if (playersCount + 1 > srconfig.maxConnections) {
var data = {
"method": "disconnect",
"reason": "Server is full."
}
connection.send(JSON.stringify(data))
connection.close();
console.log("[i] Rejected connect from " + connection.remoteAddress + ". Reason: Server is full.")
return;
}
connection.on("close", e => {
console.log("[i] Disconnected - '" + connection.remoteAddress + "'. Nick - " + players[clientID].name + ".");
playersCount -= 1;
clearInterval(sendTimer[clientID]);
clearInterval(animTimer[clientID]);
clearInterval(hydroloss[clientID]);
clearInterval(reviveHP[clientID]);
delete players[clientID];
delete setMov[clientID];
delete mouseMid[clientID];
delete sendTimer[clientID];
delete animTimer[clientID];
delete hydroloss[clientID];
delete reviveHP[clientID];
})
connection.on("message", message => {
var response = JSON.parse(message.utf8Data);
if (response.method === "nick"){
if (players[response.clientID]) {
if (response.nick == "") {
players[clientID].name = srconfig.uName;
} else {
players[clientID].name = response.nick;
}
console.log("[i] Set nick \"" + response.nick + "\" for " + clientID)
}
} else if (response.method === "movement"){
if (players[response.clientID]) {
gameData[response.clientID] = {
"method": "gameData",
"players": players,
"downBorder": srconfig.downBorder,
"rightBorder": srconfig.rightBorder,
"objects": objects,
"playersCount": playersCount,
"top": topx
}
connection.send(JSON.stringify(gameData))
mouseMid[response.clientID].x = response.cWidth / 2;
mouseMid[response.clientID].y = response.cHeight / 2;
gameMouse[response.clientID].x = (response.curx - (mouseMid[response.clientID].x - 2 * players[response.clientID].camzoom)) / players[response.clientID].camzoom;
gameMouse[response.clientID].y = (response.cury - (mouseMid[response.clientID].y - 2 * players[response.clientID].camzoom)) / players[response.clientID].camzoom;
}
} else if (response.method === "enter") {
if (players[response.clientID]) {
players[response.clientID].spectator = 0;
var randxx = Math.floor(Math.random() * srconfig.rightBorder - 50) + 50;
var randyy = Math.floor(Math.random() * srconfig.downBorder - 50) + 50;
players[response.clientID].x = randxx;
players[response.clientID].y = randyy;
setSysmsg(stars[players[response.clientID].type].desc, response.clientID)
}
} else if (response.method === "clicked") {
if (!players[response.clientID]) {
return
}
} else if (response.method === "chat") {
if (players[response.clientID]) {
//chat
}
} else {
console.log("[i] Unknown packet received: \n-------------\n" + response.method + "\n-------------")
}
})
console.log("[i] Connected - '" + connection.remoteAddress + "', ID - " + clientID);
var payLoad = {
"method": "connect",
"clientID": clientID
}
mouseMid[clientID] = {
x: 0,
y: 0
}
gameMouse[clientID] = {
x: 0,
y: 0
}
var gameData = {
"method": "gameData",
"players": players,
"downBorder": srconfig.downBorder,
"rightBorder": srconfig.rightBorder,
"objects": objects,
"playersCount": playersCount,
"top": topx
}
connection.send(JSON.stringify(payLoad))
var specChance = Math.floor(Math.random() * 1 - 0) + 0;
var specx = specChance == 0 ? -3 : 3;
var specy = specChance == 0 ? -3 : 3;
colorrandx = '#' + (Math.random() * Math.pow(256, 3) | 0).toString(16).padStart(6, '0');
playersCount++;
players[clientID] = {
x: Math.floor(Math.random() * srconfig.rightBorder - 50) + 50,
y: Math.floor(Math.random() * srconfig.rightBorder - 50) + 50,
mass: srconfig.startPlayerMass,
size: 1,
color: colorrandx,
type: "type",
abilitycooldown: 400,
numtype: 1,
name: srconfig.uName,
clientid: clientID,
id: playersCount + '',
xpToUpgrade: 30,
angle: 0,
sysmsg: "",
trace: 0,
camzoom: 1,
hp: 40,
maxhp: 40,
speed: 0,
mX: 0,
animsize: 5,
animspeed: 1,
sysmsgvis: 0.5,
mY: 0,
hydrogen: 100,
maxhydrogen: 100,
vx: 0,
vy: 0,
message: "",
camview: 750,
spectator: 1,
specspeedx: specx,
specspeedy: specy,
messageColor: "white"
};
function changeAnim() {
if (!players[clientID] || players[clientID].spectator == 1) {
return
}
if (players[clientID].animsize > 10) {
players[clientID].animspeed = -0.5;
} else if (players[clientID].animsize < 1) {
players[clientID].animspeed = 0.5;
}
players[clientID].animsize += players[clientID].animspeed;
}
function lossHydro() {
if (!players[clientID] || players[clientID].spectator == 1) {
return
}
if (players[clientID].hydrogen < 0) {
players[clientID].hp -= 5;
return
}
players[clientID].hydrogen -= 2.5;
}
function HPRevive() {
if (!players[clientID] || players[clientID].spectator == 1) {
return
}
if (players[clientID].hp > players[clientID].maxhp) {
return
}
if (players[clientID].hydrogen > 0) {
players[clientID].hp += 5;
return
}
}
function drawz() {
if (!players[clientID].spectator) {
return
}
connection.send(JSON.stringify(gameData))
}
animTimer[clientID] = setInterval(changeAnim, 100);
sendTimer[clientID] = setInterval(drawz, 1e3 / 60);
hydroloss[clientID] = setInterval(lossHydro, 3000);
reviveHP[clientID] = setInterval(HPRevive, 1500);
})
for (var i = 0; i < srconfig.foodMCount; i++) {
var randXP = Number(Math.random() * (5 - 2) + 2);
spawnObject('asteroid', 1, Number(randXP));
}
updateTimer = setInterval(updateGame, 13);
spawnFood = setInterval(function () {
if (foodCount < srconfig.foodMCount) {
spawnFoodCount = srconfig.foodMCount - foodCount;
spawnObject('asteroid', spawnFoodCount, srconfig.asteroidMass);
}
}, 10000)
}