Пятница, время расслабляющих статей… Я решил написать эту статью благодаря хабровчанину TheShock и благодаря человеку, написавшему в своё время LOOT-RPG.
Как собственно я делал игру, можно прочитать под катом.
Открыв курс по JS на codeacademy, я дошел до простых структур данных и мне наскучило. А что если не выполнить скучные задания, а сделать свою собственную игру с блекджеком и девицами, примерно как в первом примере? Сказано — сделано…
Надо было определиться с жанром. Больше всего на свете я люблю стратегии, чуть меньше — RPG. Значит выбираем стратегии. Что у нас будет? Будет город и его менеджмент. Что-то в духе Townsmen/Travian.
Для начала нам нужен город. Рисуем что-нибудь в духе пустого пейзажа города Heroes или пустого пейзажа неотсроенного города Travian.
Объявляем переменные и инициируем их:
var gold          = 20;
var pop           = 2;
var buildLevelD   = 0;
var buildLevelH   = 0;
var buildLevelTreasury = 0;
var treasuryGuard = 0;
updateResources(); 

Вуаля, в нашем городе начальное население, какая-никакая казна, а также нулевой уровень трех доступных построек и ноль городовых. Поскольку наша игра будет не пошаговой, а в режиме реального времени, то зададим таймер, по которому у нас будет прирост ресурсов:
setInterval(resourcesTimer, 30000);
function resourcesTimer() {
	gold     = gold*1+pop*1;
	if (pop < 2*Math.pow((buildLevelH*1+3),2)) {
		pop      = pop*1+1;
	}
	if (treasuryGuard > 0) {
		if (gold - treasuryGuard * treasuryGuardPricePayroll >= 0) {
			gold = gold - treasuryGuard * treasuryGuardPricePayroll;
		} else {
			treasuryGuard = 0;
			}
	}
	updateResources();
}

Внимательные читатели обратят внимание на updateResources() и вот его код:
	function updateResources() {
		document.getElementById("gold").innerHTML = gold;
		document.getElementById("pop").innerHTML  = pop;
		document.getElementById("treasuryGuard").innerHTML = treasuryGuard;
	}

Давайте уже что-нибудь построим по нажатию кнопки
<button id="treasury" onclick="Build('Treasury')" class="interface" style="top:250px; left:20px">Build treasury: 20 gold</button>

function Build(Structure) {
		if (Structure==="Wall" || Structure ==="Tower") {
			if (gold >= costWall && buildLevelD == 0) { 
				buildLevelD = buildLevelD*1 + 1;
				gold = gold - costWall;
				redraw   = 1;
				whatDraw = "Wall";
				updateResources();
				return true;
			}
			if (gold >= costTower && buildLevelD == 1) { 
				buildLevelD = buildLevelD*1 + 1;
				gold = gold - costTower;
				redraw   = 1;
				whatDraw = "Tower";
				updateResources();
				return true;
			}
		}
		if (Structure==="Home") {
			if (gold >= Math.pow(costHome,(buildLevelH*1 + 1))) {
				gold = gold - Math.pow(costHome,(buildLevelH*1 + 1));
				redraw   = 1;
				whatDraw = "Home";
				buildLevelH = buildLevelH*1 + 1;
				updateResources();
				return true;
			}
		}
		if (Structure==="Treasury") {
			if (gold >= Math.pow(costTreasury,(buildLevelTreasury*1 + 1))) {
				gold = gold - Math.pow(costTreasury,(buildLevelTreasury*1 + 1));
				redraw   = 1;
				whatDraw = "Treasury";
				buildLevelTreasury = buildLevelTreasury*1 + 1;
				updateResources();
				return true;
			}
		}

}
Отрисовка сделана на Canvas. Это, пожалуй, одна из немногих вещей, которая не пахнет нафталином во всем этом коде. Однако код может многим показаться странным, хоть и неоднократно приведен в разных местах в качестве примера:
function composite() {
			imagesLoaded = imagesLoaded*1 + 1;
			if (imagesLoaded > 1) {
				// composite now
				//ctx.globalAlpha = 1.00;
				ctx.drawImage(img1, 0, 0);   //draw background
				ctx.drawImage(img2, 0, 0);   //draw sawmill
				ctx.drawImage(img3, 10, 40); //draw gold icon
				ctx.drawImage(img4, 10, 80); //draw pop  icon
				ctx.drawImage(img8, 0, 0); //draw wishing well
				if (buildLevelD > 0) {
					// draw back piece of wall
					ctx.drawImage(img6_1, 0, 0);
				}
				if (buildLevelH > 0 && buildLevelH < 10) {
					// draw houses
					ctx.drawImage(img5, 0, 0);
				}
				if (buildLevelH > 10) {
					// draw more houses
					ctx.drawImage(img5_1, 0, 0);
				}
				if (buildLevelD > 1) {
					// draw castle
					ctx.drawImage(img7, 0, 0);
				}
				if (buildLevelD > 0) {
					// draw front piece of wall
					ctx.drawImage(img6_2, 0, 0);
				}
				if (buildLevelTreasury > 0) {
					// draw treasury
					ctx.drawImage(img9, 0, 0);
				}
			}
		}
		
		function loadImage(src, onload) {
			var img = new Image();
			img.onload = onload;
			img.src = src;
			return img;
		}

Сохранения храним в локальном хранилище. Честно, это удобнее, чем работать с куками, но и менее универсально. Хотя с момента, когда я переписал большую часть графика на Canvas, это стало не принципиальным: старые браузеры одинаково не поддерживают и Canvas и localStorage.
function saveGame() {
		localStorage.setItem('gold', gold);
		localStorage.setItem('pop', pop);
		localStorage.setItem('buildLevelD', buildLevelD);
		localStorage.setItem('buildLevelH', buildLevelH);
		localStorage.setItem('treasuryGuard', treasuryGuard);
		localStorage.setItem('buildLevelTreasury', buildLevelTreasury);
	}
	function loadGame() {
		gold = localStorage.getItem('gold');
		pop  = localStorage.getItem('pop');
		buildLevelD   = localStorage.getItem('buildLevelD');
		buildLevelH   = localStorage.getItem('buildLevelH');
		treasuryGuard = localStorage.getItem('treasuryGuard');
		buildLevelTreasury = localStorage.getItem('buildLevelTreasury');
		composite();
		updateResources();
	}

Этой статьей я хотел показать, что если вашей давней мечтой является создание пусть простенькой, но своей игры (и не «3 в ряд»), то всё в ваших руках. Я выбрал JavaScript для этой задачи, потому что браузер есть почти везде, а значит почти везде я смогу играть в свою игру, и мне для этого не потребуется подключение к интернету. Браузер стал универсальной средой исполнения, а не просто программой для отображения контента. Благодаря небольшим переделкам, игру можно стилизовать под те или иные требования, добавить тот или иной функционал, расширить как угодно. Главное — это ваша фантазия, а значит всё в ваших руках. Когда я создавал свой проект, я старался как можно дольше не использовать зависимости, и на текущий момент моя игра пока не использует никаких внешних зависимостей.

P.S.: размещая проект на гитхабе и устанавливая метки (labels), я натолкнулся на проект, где автор кроме того как программирует, еще и обладает художественным вкусом, однако общая идея похожа.

P.P.S.: любые замечания приветствуются)
Поделиться с друзьями
-->

Комментарии (3)


  1. sinneren
    04.08.2017 12:48

    Ну, пошла жара:
    Почему строения константой? Почему ресурсы начальные не в одном объекте? Где вообще какой-то ООП? Где база архитектуры игры, почему сразу о ресурсах? И самое главное — где игра-то? Ссылка на чужую есть, а от своей какие-то огрызки кода и всё.


    1. Areso
      04.08.2017 12:57

      Отвечаю по порядку. Это не константы (уж точно нет). Начальные и не начальные (сэйв, лоад) ресурсы не в одном объекте, потому что я хотел это сделать (подглядев из примера LOOT-RPG) и забыл, занимаясь другими задачами (когда нужно больше зданий, больше случайных событий, немного было не до переписывания того, что уже работает). Так уж получилось, что я не фанат ООП, и пишу в процедурном стиле, где это возможно. Это простая игра, здесь не было острой необходимости в ООП. Архитектуру игры я пытался некоторое время поддерживать в yEd, но потом схема безнадежно отстала от реальности.
      Вот игра, которую я нашел по меткам. Вот Моя игра, но я не хотел её показывать по причине очень жуткой самодельной графики (к сожалению, я не художник и не смог найти никого, кто бы вызвался помочь с графикой на добровольных началах, а собрать из PD0 ресурсов нормальный набор мне не удалось).


      1. GrimMaple
        04.08.2017 13:24

        Я тоже не особый фанат ООП, но избегать ОО в явно объектной задаче как минимум странно. В играх любой сложности объектная ориентированность (?) сильно упрощает жизнь.