Pull to refresh

Игровой сервер за один день на Node.js + Socket.io

Reading time 3 min
Views 45K
В конце рабочего дня в пятницу, обдумывая текущую задачу, в воспаленном мозгу неожиданно возникла мысль — а не попробовать ли мне написать свой игровой сервер?

В мыслях проскакивали картины одна краше другой, изобретались механики будущей игры, спецэффекты. В голове игра была практически готова, осталось всего ничего, подготовить концепт, начать работу над дизайн документом, найти художников, сценаристов, звукооператоров, программистов… Горестно вздохнув, очередной раз отправил идею на свалку.

Но по-видимому, заноза прочно засела в голове, поэтому проснувшись в субботу я принялся творить.

Во-первых мне нужно было выбрать технологии, с помощью которых реализовать задуманное. Сказав решительное нет опыту, который твердил что нужно использовать хорошо знакомые мне технологии, такие например, как java.nio для сервера и cocos2d для клиента был выбран следующий стек технологий: javascript + node.js + socket.io + html canvas.

Учитывая, что все эти технологии для меня в диковинку, пессимист во мне подумал, ну ладно, поиграйся немного, все равно быстро ничего не получится.

Для начала нужно было установить node.js, на маке это решилось довольно просто, используя ports:
sudo port install nodejs
sudo port install npm
npm install socket.io

На этом приготовления были закончены.

Бодро начав работу, быстро увяз на некоторое время в синтаксисе javascript, но в итоге набросал таки костяк приложения. И что самое удивительное, оно работало!

Вот код:

SERVER
var io = require('socket.io').listen(80);

io.sockets.on('connection', function (socket) {
  socket.on('message', function () { });
  socket.on('disconnect', function () { });
});

CLIENT
<script>
  var socket = io.connect('http://localhost/');
  socket.on('connect', function () {
    socket.send('hi');

    socket.on('message', function (msg) {
      // my msg
    });
  });
</script>


Который я беззастенчиво взял отсюда socket.io/#how-to-use

Впрочем, код быстро видоизменился:

CLIENT
var URL = 'http://localhost:9090';

var Network = function(url) {
	this.url = url;
	this.pingTime = 0;
	this.actorId = 0;
	this.sendingPing = false;
}

Network.prototype.ping = function() {
	if(this.sendingPing) {
		return;
	}
	this.sendingPing = true;
	this.startDate = new Date();
	this.socket.send("hi");
}

Network.prototype.start = function(game) {
	var socket = io.connect(this.url);

	this.socket = socket;

	socket.on('connect', function () {

		socket.on('message', function () {
			var now = new Date();
			network.pingTime = (now.getTime()-network.startDate.getTime());
			network.sendingPing = false;
		});

		socket.on('actorId', function (id) {
			network.actorId = id;
		});

		socket.on('gameInfo', function (msg) {
			game.networkUpdate(msg);
		});

		network.ping();
		game.active = true;
	});

	socket.on('disconnect', function () {
		network.start(game);
	});
}

Network.prototype.update = function() {
	this.ping();
}

Network.prototype.sendInput = function(pos) {
	this.socket.emit('input', pos);
}

var network = new Network(URL);

network.start(game);


SERVER
var io = require('socket.io').listen(9090);
var clients = [];

io.sockets.on('connection', function (socket) {
	var client = new Client(socket);
	clients.push(client);
	game.active = clients.length != 0;

	socket.on('message', function (msg) { 
		client.onMessage(msg);
	});

	socket.on('input', function (pos) { 
		client.onInput(pos);
	});

	socket.on('disconnect', function () {		
		var index = clients.indexOf(client);
		if(index != -1) {
			clients.splice(index);
		}
		game.active = clients.length != 0;
		client.onDisconnect();
	});
});

console.log("Server started..");

setInterval(function() {
	if(!game.active) {
		return;
	}
	game.update(TICK_TIME);
	var gameInfo = game.gameInfo();
	for(var i=0;i<clients.length;i++) {
		var client = clients[i];
		client.update(gameInfo);
	}
}, TICK_TIME);


В деме предусмотрено управление одним из актеров (зелененьким), при нажатии в определенное место канвы актер телепортируется туда и меняет свое направление и скорость на рандомные.

Игровой цикл реализовал в классе Game, поведение в классе Actor. Вся логика просчитывается на сервере и передает координаты клиенту, который рисует кружочки (наших актеров) в нужных координатах. Построение достоверной физической модели не было целью данного эксперимента, поэтому я просто реализовал «отскок» шариков от стенок. В общем получилось все довольно просто, залив код на сервер с удивлением понял, что все работает и неожиданно плавно.

Поначалу хотел сделать общие базовые классы Game и Actor для сервера и клиента, но решил не морочить голову себе и людям и написал код дважды для клиента и сервера.

Как итог — познакомился с незнакомыми мне до сего момента технологиями, ощутил в полной мере, что все таки технологии не стоят на месте и что платформа node.js в частности заслуживает внимания чтобы присмотреться к ней повнимательнее.

Стресс тестирования не проводил, поэтому ничего не могу сказать по производительности. По субъективным ощущениям должно быть «не очень», но пока не подтвердить не могу.

Для желающих попробовать, вот ссылка (в качестве сервера используется не самая быстрая виртуалка, так что может быть всякое).
Если у кого-то есть желание ознакомится с исходным кодом, то пожалуйте сюда.
Tags:
Hubs:
+12
Comments 5
Comments Comments 5

Articles