Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Прошу совета по коду игры (https://javascript.ru/forum/project/70476-proshu-soveta-po-kodu-igry.html)

malinovsky 07.09.2017 21:32

Прошу совета по коду игры
 
Здравствуйте, вот сделал простенькую стрелялку и теперь прошу ваших советов по коду, а именно, как правильно задавать скорость движения объектов.
Ссылка на скрипт: https://github.com/himbotop/first_ga...ster/script.js

Сама игра: https://himbotop.github.io/first_game_js/

рони 07.09.2017 23:32

malinovsky,
убрал стиль из canvas, математику(позиционирован е элементов) проверьте снова , на экране 10 кораблей с разной скоростью, смотреть shipsGeneration

<!DOCTYPE html>
<html>
<head>
  <title>1Game</title>
  <meta charset="utf-8">
</head>
<body>
<h2>Управление игрой:</h2>
<p>Перемещение влево/вправо стрелки влево/вправо на клавиатуре, клавиша "пробел" - выстрел.</p>
<canvas id="canvas" width=600 height=400></canvas>
<script>
  var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var keys = {
  'Left' : 37,
  'Right' : 39,
  'Space' : 32
};

var keyDown = {};

var setKey = function (keyCode) {
  keyDown[keyCode] = true;
};

var clearKey = function (keyCode) {
  keyDown[keyCode] = false;
};

var isKeyDown = function (keyName) {
  return keyDown[keys[keyName]] == true;
};


window.onkeydown = function (e) {
  setKey(e.keyCode);
};

window.onkeyup = function (e) {
  clearKey(e.keyCode);
};


var drawRect = function (x, y, sizeX, sizeY, color) {
  ctx.beginPath();
  ctx.rect(x, y, sizeX, sizeY);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.closePath();
};

var drawPlayer = function (x) {
  ctx.beginPath();
  ctx.rect(x, 395, 50, 5);
  ctx.rect(x+10, 390, 30, 5);
  ctx.rect(x+23, 385, 5, 5);
  ctx.fillStyle = "rgb(173, 105, 82)";
  ctx.fill();
  ctx.closePath();
};

var Destruction = function () {
  for(var i = 0; i < shots.length; i++) {
    for(var j = 0; j < ships.length; j++) {
      if(~~shots[i].y == ~~ships[j].y+5 && shots[i].x+5 >= ships[j].x && shots[i].x <= ships[j].x+10) {
        ships.splice(j, 1);
        shots.splice(i, 1);
        break;
      }
    }
  }
};
;
var shipsGeneration = function (time) {
  if( countShips > 30 && ships.length < 10) {
    ships.push({
      t : (3 + Math.random()*13|0)*1000,//время падения от 3сек до 15сек
      ts : time,//время старта
      x : Math.random() * 590|0,
      y : 0
    });
    countShips = 0;
  }
  countShips++;

  for(var i = 0; i < ships.length; i++) {
    var s = ships[i], p = (time-s.ts)/s.t;
    if(p < 1)s.y = p*400|0;
    else {s.y= 0; s.ts = time ; s.t= (3 + Math.random()*13|0)*1000;
    s.x = Math.random() * 590|0;
    };

    drawRect(s.x, s.y, 10, 10, "rgb(22, 105, 67)");

  }
};

var shotsGeneration = function (time) {
  if(isKeyDown('Space')) {
    var length = shots.length;
    if(length > 0) {
      if( (shots[length-1].y + 7) < 130) {
        shots.push({
          x : xPlayer+23,
          y : 130
        });
      }
    } else {
      shots.push({
        x : xPlayer+23,
        y : 130
      });
    }

  }

  for(var i = 0; i < shots.length; i++) {

    drawRect(shots[i].x, shots[i].y, 5, 5, "rgb(195, 55, 67)");
    shots[i].y -= 2;
  }
};

var Player = function () {
  if(isKeyDown('Left')) {
    xPlayer-=4;
  }
  if(isKeyDown('Right')) {
    xPlayer+=4;
  }
  drawPlayer(xPlayer);
};

var ships = [];
var shots = [];
var countShips = 0;
var xPlayer = 120;

var main = function (time) {

  ctx.clearRect(0, 0, 600, 400);
  drawRect(0, 0, 600, 400, "rgb(36, 177, 219)");
  Destruction();

  shipsGeneration(time);

  shotsGeneration();

  Player();

  requestAnimationFrame(main);
};

main();

</script>
</body>
</html>

malinovsky 08.09.2017 20:01

Спасибо Рони, теперь понял куда копать, только вот не совсем понял условие (p < 1), почему 1. Я еще немного изменил код.
<!DOCTYPE html>
<html>
<head>
  <title>1Game</title>
  <meta charset="utf-8">
</head>
<body>
<h2>Управление игрой:</h2>
<p>Перемещение влево/вправо стрелки влево/вправо на клавиатуре, клавиша "пробел" - выстрел.</p>
<canvas id="canvas" width=600 height=400></canvas>
<script>
  var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

canvas.width = 600;
canvas.height = 400;



var keys = {
  'Left' : 37,
  'Right' : 39,
  'Space' : 32
};

var keyDown = {};

var setKey = function (keyCode) {
  keyDown[keyCode] = true;
};

var clearKey = function (keyCode) {
  keyDown[keyCode] = false;
};

var isKeyDown = function (keyName) {
  return keyDown[keys[keyName]] == true;
};


window.onkeydown = function (e) {
  setKey(e.keyCode);
};

window.onkeyup = function (e) {
  clearKey(e.keyCode);
};


var drawRect = function (x, y, sizeX, sizeY, color) {
  ctx.beginPath();
  ctx.rect(x, y, sizeX, sizeY);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.closePath();
};

var drawPlayer = function (x) {
  ctx.beginPath();
  ctx.rect(x, 395, 50, 5);
  ctx.rect(x+10, 390, 30, 5);
  ctx.rect(x+23, 385, 5, 5);
  ctx.fillStyle = "rgb(173, 105, 82)";
  ctx.fill();
  ctx.closePath();
};

var Destruction = function () {
  for(var i = 0; i < shots.length; i++) {
    for(var j = 0; j < ships.length; j++) {
      if(~~shots[i].y == ~~ships[j].y+5 && shots[i].x+5 >= ships[j].x && shots[i].x <= ships[j].x+10) {
        ships.splice(j, 1);
        shots.splice(i, 1);
        break;
      }
    }
  }
};
;
var shipsGeneration = function (time) {
  if( countShips > densityShips && ships.length < numberShips) {
    ships.push({
      t : speedShips*1000,//время падения от 3сек до 15сек
      ts : time,//время старта
      x : Math.random() * 590|0,
      y : 0
    });
    countShips = 0;
  }
  countShips++;

  for(var i = 0; i < ships.length; i++) {
    var s = ships[i], p = (time-s.ts)/s.t;
    if(p < 1)s.y = p*400|0;
    else {s.y= 0; s.ts = time ; s.t= speedShips*1000;
    s.x = Math.random() * 590|0;
    };

    drawRect(s.x, s.y, 10, 10, "rgb(22, 105, 67)");

  }
};

var shotsGeneration = function (frameTime) {
  if(isKeyDown('Space')) {
    var length = shots.length;
    if(length > 0) {
      if( (shots[length-1].y + 7) < 380) {
        shots.push({
          x : xPlayer+23,
          y : 380
        });
      }
    } else {
      shots.push({
        x : xPlayer+23,
        y : 380
      });
    }

  }

  for(var i = 0; i < shots.length; i++) {

    drawRect(shots[i].x, shots[i].y, 5, 5, "rgb(195, 55, 67)");
    shots[i].y -= (frameTime / 1000) * speedShots;
  }
};

var Player = function (frameTime) {
  if(isKeyDown('Left')) {
    xPlayer -= (frameTime / 1000) * speedPlayer;
  }
  if(isKeyDown('Right')) {
    xPlayer += (frameTime / 1000) * speedPlayer;
  }
  drawPlayer(xPlayer);
};

var ships = [];
var shots = [];
var densityShips = 0;
var xPlayer = 120;
var lastTime = 0;
var countShips = 0;

var speedPlayer = 200; // скорость передвижения игрока
var densityShips = 30; // плотность кораблей
var numberShips = 20; // количество кораблей
var speedShips = 15;
var speedShots = 150; // скорость пуль

var main = function (time) {
	//console.log(time);
  ctx.clearRect(0, 0, 600, 400);
  drawRect(0, 0, 600, 400, "rgb(36, 177, 219)");

  var startTime = time;
  var frameTime = time - lastTime;

  Destruction();

  shipsGeneration(time);

  shotsGeneration(frameTime);

  Player(frameTime);

  lastTime = startTime;

  requestAnimationFrame(main);
};

main();


</script>
</body>
</html>

рони 08.09.2017 22:09

malinovsky,
время падения 20 сек --- прошло 10сек 10/20 = .5 или 50% пути должно быть пройдено, весь путь 400px, 400 * .5 = 200px
прошло 20 сек -- 20/20 = 1 или 100% , 400 * 1 = 400px.
всё что больше 1 выходит за 400px , поэтому обнуляем данные.

malinovsky 08.09.2017 23:23

Теперь дошло, даже на бумажке посчитал (15605.45 - 605.45) / 15000 = 1, плохо у меня с математикой. А как мое изменение по установке скорости для игрока и пуль?
Дальше буду работать над столкновениями.

рони 08.09.2017 23:32

malinovsky,
https://learn.javascript.ru/js-animation

malinovsky 09.09.2017 07:13

Спасибо Рони, буду разбираться. А пока просто исправил условие столкновения и добавил удаление пуль улетевших за пределы канваса.
https://github.com/himbotop/first_ga...ster/script.js

Играть: https://himbotop.github.io/first_game_js/

Rise 10.09.2017 22:51

malinovsky,
надо на классах делать

malinovsky 11.09.2017 20:49

Rise,
ок, спасибо понял.

Rise 11.09.2017 21:17

malinovsky,
каких интерфейсов?

malinovsky 12.09.2017 11:15

Rise,
Цитата:

надо на классах делать
как здесь: https://www.youtube.com/watch?v=ju09womACpQ ?

Rise 13.09.2017 19:25

malinovsky,
да, но возможно там не совсем очевидны преимущества ООП подхода в общем и классов в частности, давай я тебе покажу:
<canvas width="600" height="400"></canvas>

<script>

class Game {
	constructor(canvas) {
		canvas.onmouseenter = () => this.start();
		canvas.onmouseleave = () => this.pause();
		this.ctx2d = canvas.getContext('2d');
		this.ctx2d.fillText('START', 0, 10);
		this.world = new Set();
		this._last = 0;
		this._step = (now) => {
			this._loop = requestAnimationFrame(this._step);
			this.delta = Math.min(now - this._last, 100) / 1000;
			this._last = now;
			this.update();
			this.render();
		};
	}
	start() {
		if (!this._loop) this._loop = requestAnimationFrame(this._step);
	}
	pause() {
		if (this._loop) this._loop = cancelAnimationFrame(this._loop);
	}
	collide(entity1, type) {
		for (let entity2 of this.world) {
			if (entity1 != entity2  && 
				entity2.type & type && 
				entity1.x < entity2.x + entity2.w &&
				entity1.x + entity1.w > entity2.x &&
				entity1.y < entity2.y + entity2.h &&
				entity1.h + entity1.y > entity2.y) return true;
		}
		return false;
	}
	update() {
		for (let entity of this.world) if (entity.update) entity.update(this);
	}
	render() {
		this.ctx2d.clearRect(0, 0, 600, 400);
		for (let entity of this.world) if (entity.render) entity.render(this);
		if (!this.world.size) this.pause();
	}
}

class StaticBase {
	constructor(statics) {
		Object.assign(this, { x: 0, y: 0, w: 10, h: 10, c: 'black' }, statics);
	}
	render(game) {
		game.ctx2d.fillStyle = this.c;
		game.ctx2d.fillRect(this.x, this.y, this.w, this.h);
	}
}
class DynamicBase extends StaticBase {
	constructor(statics, dynamics) {
		super(statics);
		Object.assign(this, { vx: 0, vy: 0 }, dynamics);
	}
	update(game) {
		this.x += this.vx * game.delta;
		this.y += this.vy * game.delta;
	}
}
class SinusoidBase extends DynamicBase {
	constructor(statics, sinusoid) {
		super(statics, null);
		Object.assign(this, { A: 0, B: 0, C: 0, D: 0, E: 0, F: 0, G: 0, H: 0, t: 0 }, sinusoid);
	}
	update(game) {
		this.t += game.delta;
		this.vx = this.A + this.B * Math.sin(this.C * this.t + this.D);
		this.vy = this.E + this.F * Math.sin(this.G * this.t + this.H);
		super.update(game);
	}
}

const PLAYER = 1, SHIP = 2, SHOT = 4, LINE = 8;

class Line extends StaticBase {
	constructor(statics, extra) {
		super(statics);
		Object.assign(this, { type: LINE }, extra);
	}
}
class Shot extends DynamicBase {
	constructor(statics, dynamics, extra) {
		super(statics, dynamics);
		Object.assign(this, { type: SHOT }, extra);
	}
	update(game) {
		super.update(game);
		if (game.collide(this, PLAYER | SHOT | LINE)) game.world.delete(this);
	}
}
class Ship extends SinusoidBase {
	constructor(statics, sinusoid, extra) {
		super(statics, sinusoid);
		Object.assign(this, { type: SHIP }, extra);
	}
	update(game) {
		super.update(game);
		if (game.collide(this, PLAYER | SHOT | LINE)) game.world.delete(this);
	}
}

const theGame = new Game(document.querySelector('canvas'));

theGame.world
.add(new Line({ x:   0, y:   5, w:   5, h: 375 }))
.add(new Line({ x:   0, y:   0, w: 600, h:   5 }))
.add(new Line({ x: 595, y:   5, w:   5, h: 375 }))
.add(new Line({ x:   0, y: 380, w: 600, h:  20 }))
.add(new Shot({ x: 100, y: 370, w:   5, h:   5 }, { vy: -50 }))
.add(new Shot({ x: 400, y: 370, w:   5, h:   5 }, { vy: -20 }))
.add(new Shot({ x: 400, y:   5, w:   5, h:   5 }, { vy:  40 }))
.add(new Ship({ x: 100, y:   5, c: 'gray'  }, { E:   20 }))
.add(new Ship({ x:   5, y: 100, c: 'brown' }, { A:   30 }))
.add(new Ship({ x: 410, y:  10, c: 'orange'}, { B: -200, C: 1, E: 20, F: 100, G: 2, H: 1.5 }))
.add(new Ship({ x: 580, y:  60, c: 'red'   }, { B: -100, C: 1, E: 10, F: 100, G: 1, H: 1.5 }))
.add(new Ship({ x: 200, y:   5, c: 'green' }, { B:  100, C: 4, E: 30 }))
.add(new Ship({ x: 280, y:   5, c: 'blue'  }, { B:  300, C: 2, E: 20 }))

</script>

Как видишь теперь разрабатывать удобней и понятней, и не составит труда придумывать новые проекты (классы) сущностей на основе базовых или более сложных и заселять ими игровой мир. И чем сложнее разработка тем эти преимущества острее чувствуются, потому что человеку по природе естественно размышлять объектами.

рони 13.09.2017 20:04

Rise,
спасибо за науку!!! :thanks:

malinovsky 14.09.2017 21:00

Rise,
Спасибо! Буду изучать)

Rise 16.09.2017 00:06

Стоит еще отметить, что это PLAYER | SHOT | LINE - битовая маска, до 32 констант можно создать и комбинировать их в разные маски, что весьма удобно:
const TYPE_NAME01 = 1 << 0, // ...0000000001 (2)   1 (10)
      TYPE_NAME02 = 1 << 1, // ...0000000010 (2)   2 (10)
      TYPE_NAME03 = 1 << 2, // ...0000000100 (2)   4 (10)
      TYPE_NAME04 = 1 << 3, // ...0000001000 (2)   8 (10)
      TYPE_NAME05 = 1 << 4, // ...0000010000 (2)  16 (10)
      TYPE_NAME06 = 1 << 5, // ...0000100000 (2)  32 (10)
      TYPE_NAME07 = 1 << 6, // ...0001000000 (2)  64 (10)
      TYPE_NAME08 = 1 << 7, // ...0010000000 (2) 128 (10)
      TYPE_NAME09 = 1 << 8, // ...0100000000 (2) 256 (10)
      TYPE_NAME10 = 1 << 9, // ...1000000000 (2) 512 (10)
   // ...           ...        ...
   // TYPE_NAME32 = 1 << 31,// ...10000000000000000000000000000000 (2) 2147483648 (10)
      TYPE_GROUP1 = TYPE_NAME01 | TYPE_NAME02 | TYPE_NAME03 | TYPE_NAME04 | TYPE_NAME05, // ...0000011111 (2)  31 (10)
      TYPE_GROUP2 = TYPE_NAME06 | TYPE_NAME07 | TYPE_NAME08 | TYPE_NAME09 | TYPE_NAME10, // ...1111100000 (2) 992 (10)
      TYPE_ALL    = TYPE_GROUP1 | TYPE_GROUP2 ; // ...1111111111 (2) 1023 (10)


demo(TYPE_ALL, 'all');
demo(TYPE_GROUP1, 'group 1');
demo(TYPE_GROUP2, 'group 2');
demo(TYPE_ALL ^ TYPE_NAME03 ^ TYPE_NAME08, '(^) all types except 3 and 8');             // ...1101111011 (2) 891 (10)
demo(TYPE_ALL & ~TYPE_NAME03 & ~TYPE_NAME08, '(& ~) all types except 3 and 8');         // 
demo(TYPE_NAME03 | TYPE_GROUP2 ^ TYPE_NAME08, 'only type 3 and group 2 except type 8'); // ...1101100100 (2) 868 (10)

function demo(type, note) {
    const mask = [null, null, null, null, null, null, null, null, null, null];
    if (type & TYPE_NAME01) mask[9] = '01';
    if (type & TYPE_NAME02) mask[8] = '02';
    if (type & TYPE_NAME03) mask[7] = '03';
    if (type & TYPE_NAME04) mask[6] = '04';
    if (type & TYPE_NAME05) mask[5] = '05';
    if (type & TYPE_NAME06) mask[4] = '06';
    if (type & TYPE_NAME07) mask[3] = '07';
    if (type & TYPE_NAME08) mask[2] = '08';
    if (type & TYPE_NAME09) mask[1] = '09';
    if (type & TYPE_NAME10) mask[0] = '10';
    console.log(mask, note);
}

рони 16.09.2017 00:28

Rise,
:write:

j0hnik 16.09.2017 01:02

Цитата:

Сообщение от рони (Сообщение 464620)
Rise,
:write:

Для меня пока что это темный лес :(

рони 16.09.2017 01:40

j0hnik,
маска

malinovsky 18.09.2017 00:45

Rise,
рони,
Большое Вам спасибо, что помогаете новичкам. :thanks:
Начал разбирать примеры с ООП и осознал - нужно учить ООП.:-?
Вот например, почему это работает:
class Game 
{
	constructor()
	{
		this.loop = (time) => {
			console.log(time);
			requestAnimationFrame(this.loop);
		};

		this.loop();

	}
}
var game = new Game();

а это нет:
class Game 
{
	constructor()
	{
		this.loop = function(time) {
			console.log(time);
			requestAnimationFrame(this.loop);
		};

		this.loop();

	}
}
var game = new Game();


Почему в стрелочных функциях this ведет себя не так как в обычных?
Совсем запутался.:blink:

malinovsky 18.09.2017 00:56

Взялся, блин, делать игры, математику не понимаю, ООП не знаю. В GameDev меня ждет большое будущее...:-E

рони 18.09.2017 01:12

malinovsky,
"use strict"
class Game
{
  constructor()
  {
    let self = this;
    this.loop = function(time) {
      console.log(time);
      requestAnimationFrame(self.loop);
    };

    this.loop();

  }
}
var game = new Game();


"use strict"
class Game
{
  constructor()
  {

    this.loop = function(time) {
      console.log(time);
      requestAnimationFrame(this.loop);
    }.bind(this);

    this.loop();

  }
}
var game = new Game();

Rise 19.09.2017 16:01

Цитата:

Сообщение от malinovsky
Почему в стрелочных функциях this ведет себя не так как в обычных?

очевидно это одно из их отличий

MallSerg 19.09.2017 19:41

Цитата:

Сообщение от Rise (Сообщение 464867)
очевидно это одно из их отличий

Тут даже дело не в this а в том что на каждый вызов функции создается контекст (окружение) для исполнения этой функции хотя в этом редко бывает реальная необходимость. Это настоящий удар ниже пояса для производительности. Вот в новом стандарте и ввели способ это обойти вместо создания нового контекста используется уже существующий в котором описывается стрелочная функция а поведение this это следствие отсутствия нового контекста исполнения. И как еще одно следствие в некоторых случая стрелочные функции могут быть в разы быстрее.

Rise 19.09.2017 20:59

MallSerg,
чем докажешь что нет своего this которому присвоен this из замыкания?

MallSerg 19.09.2017 22:56

Цитата:

Сообщение от Rise (Сообщение 464899)
MallSerg,
чем докажешь что нет своего this которому присвоен this из замыкания?

Я не понял вопроса =(.
this это ключевое слово в JavaScript которое на этапе лексического разбора заменяется на ссылку на вполне конкретный объект. Это поведение подробно описано в спецификации.

Функции в JS это объекты первого типа т.е. их можно использовать как обычные переменные (сохранять передавать как параметры и.т.д.) функции в JS могут оперировать не только с параметрами и локальной областью видимости но и с переменными во внешней области видимости. Что заставляет интерпретатор при создании каждой функции в JS создавать [[skope]] ссылка на который есть в каждой функции.
Если бы механизма сохранения внешнего окружения не было то у сохраненной или же переданной функции не было бы доступа к внешним переменным.
Именно создание уникального окружения (LexicalEnvironment) для каждой функции и делает возможным механизм замыкания.

this заменяется на ссылку на обычный object в котором нет ни явных ни скрытых ссылок на контекст в котором исполняется функция.

Т.е. если совсем грубо и на пальцах написал ключевое слово function вне глобальной области видимости получи оверхед на создание окружения для функции.

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

Rise 20.09.2017 00:33

Цитата:

Сообщение от MallSerg
на каждый вызов функции создается контекст (окружение) для исполнения этой функции

Цитата:

Сообщение от MallSerg
Вот в новом стандарте и ввели способ это обойти вместо создания нового контекста используется уже существующий

Цитата:

Сообщение от MallSerg
создание уникального окружения (LexicalEnvironment) для каждой функции и делает возможным механизм замыкания

Цитата:

Сообщение от MallSerg
function вне глобальной области видимости получи оверхед на создание окружения для функции

бла бла бла, о каком окружении/контексте ты говоришь? о замыкании? но стрелочные функции способны делать замыкание также как и обычные функции, а по твоей логике нет.
let a = (() => {
    let b = 7;
    return () => { alert(b) }
})();
a();

И в замыкании функции остаются только те переменные из окружения на которые есть ссылки внутри функции, а не все существующие в окружении ее объявления как ты думаешь...

malinovsky 10.10.2017 21:08

Rise,
посмотри пожалуйста:
class Game
{
	constructor(canvas) {
		this.canvas = canvas;
		this.ctx = canvas.getContext('2d');
		this.world = new Set();
		this._last = 0;
		this.count = 0;
		this.objPlayer = new Player(this.canvas.width/2-30, this.canvas.height-30, 30, 20, "rgb(173, 105, 82)", 200);
		this.world.add(this.objPlayer);
		this.world.add(new Line(0, 0, 600, 5, "rgb(36, 177, 219)"));
		this.world.add(new Line(0, 395, 600, 5, "rgb(36, 177, 219)"));
		console.log(this.objPlayer);
		this.lastObjShot = false;
		this._step = (now) => {
			this._loop = requestAnimationFrame(this._step);
			this.delta = Math.min(now - this._last, 100) / 1000;
			this._last = now;
			this.shipsGenerator();
			this.shotsGenerator();
			this.update();
			this.render();
		};
		this._step();
	}

	update() {
		for (let entity of this.world) if (entity.update) entity.update(this);
	}

	render() {
		this.ctx.fillStyle = "rgb(36, 177, 219)";
		this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
		for (let entity of this.world) if (entity.render) entity.render(this);
	}

	collide(entity1, type) {
		for (let entity2 of this.world) {
			if (entity1 != entity2  && 
				entity2.type & type && 
				entity1.positionX < entity2.positionX + entity2.sizeX &&
				entity1.positionX + entity1.sizeX > entity2.positionX &&
				entity1.positionY < entity2.positionY + entity2.sizeY &&
				entity1.sizeY + entity1.positionY > entity2.positionY) return true;
		}
		return false;
	}

	stop()
	{
		if (this._loop) this._loop = cancelAnimationFrame(this._loop);
	}

	shipsGenerator() {
		if(this.count > 10) {
			this.world.add(new Ship(Math.random() * 590|0, 5, 10, 10, "rgb(22, 105, 67)", 100));
		    this.count = 0;
		}
		
		this.count++;
	}

	shotsGenerator() {
		if(keyEvent.space) {
		    if(this.lastObjShot) {
				if(this.lastObjShot.positionY+7 < 375) {
					this.lastObjShot = new Shot(this.objPlayer.positionX+11, 375, 7, 7, "rgb(173, 105, 82)", 100);
		    		this.world.add(this.lastObjShot);
				}
		    } else {
		    	this.lastObjShot = new Shot(this.objPlayer.positionX+11, 375, 7, 7, "rgb(173, 105, 82)", 100);
		    	this.world.add(this.lastObjShot);
		    }
	  	}
	}
}

class Rect
{
	constructor(positionX, positionY, sizeX, sizeY, color, vel) {
		this.positionX = positionX;
		this.positionY = positionY;
		this.sizeX = sizeX;
		this.sizeY = sizeY;
		this.color = color;
		this.vel = vel || false;
	}

	render(game) {
		game.ctx.fillStyle = this.color;
		game.ctx.fillRect(this.positionX, this.positionY, this.sizeX, this.sizeY);
	}
}

const PLAYER = 1, SHIP = 2, SHOT = 4, LINE = 8;

class Player extends Rect
{
	constructor(positionX, positionY, sizeX, sizeY, color, vel) {
		super(positionX, positionY, sizeX, sizeY, color, vel);
		Object.assign(this, { type: PLAYER });
	}

	update(game) {

		if(keyEvent.left) {
			this.positionX -= this.vel * game.delta;
		}
		if(keyEvent.right) {
			this.positionX += this.vel * game.delta;
		}
		if (game.collide(this, PLAYER | SHIP | LINE )) game.stop();
	}
}

class Ship extends Rect
{
	constructor(positionX, positionY, sizeX, sizeY, color, vel) {
		super(positionX, positionY, sizeX, sizeY, color, vel);
		Object.assign(this, { type: SHIP });
	}

	update(game) {
		this.positionY += this.vel * game.delta;
		if (game.collide(this, PLAYER | SHOT | LINE )) game.world.delete(this);
	}
}

class Shot extends Rect
{
	constructor(positionX, positionY, sizeX, sizeY, color, vel) {
		super(positionX, positionY, sizeX, sizeY, color, vel);
		Object.assign(this, { type: SHOT });
	}

	update(game) {
		this.positionY -= this.vel * game.delta;
		if (game.collide(this, SHIP | SHOT | LINE )) game.world.delete(this);
	}
}

class Line extends Rect
{
	constructor(positionX, positionY, sizeX, sizeY, color, vel) {
		super(positionX, positionY, sizeX, sizeY, color, vel);
		Object.assign(this, { type: LINE });
	}
}

const keyEvent = {
	left : false,
	right : false,
	space : false
};

window.onkeydown = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = true; break;
  	case 39 : keyEvent.right = true; break;
  	case 32 : keyEvent.space = true; break;
  }
};

window.onkeyup = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = false; break;
  	case 39 : keyEvent.right = false; break;
  	case 32 : keyEvent.space = false; break;
  }
};

const game = new Game(document.getElementById('canvas'));

Rise 11.10.2017 05:36

malinovsky,
class Player extends Rect {
	constructor(options) {
		Object.assign(this, { rate: 0.4, delay: 0 }, options);
	}
	update(game) {
		this.delay -= game.delta;
		if (keyEvent.space && this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Shot(this.x, this.y, 7, 7, 'red', 100));
		}
	}
}
class Ship extends Rect {
	constructor(options) {
		Object.assign(this, { rate: 0.8, delay: 0 }, options);
	}
	update(game) {
		this.delay -= game.delta;
		if (this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Shot(this.x, this.y, 7, 7, 'green', -100));
		}
	}
}
class Attack {
	constructor(options) {
		Object.assign(this, { size: 10, rate: 0.5, delay: 0 }, options);
	}
	update(game) {
		this.delay -= game.delta;
		if (this.delay < 0) {
			this.delay = Math.random() * this.rate;
			game.world.add(new Ship(Math.random() * 590, 5, 10, 10, 'green', 100));
			if (!--this.size) game.world.delete(this);
		}
	}
}

malinovsky 11.10.2017 22:26

Rise,

this.delay -= game.delta; // NaN - а почему?


пока сделал так:

class Game
{
	constructor(canvas) {
		this.canvas = canvas;
		this.ctx = canvas.getContext('2d');
		this.world = new Set();
		this._last = 0;
		this.count = 0;
		this.world.add(new Player(this.canvas.width/2-30, this.canvas.height-30, 30, 20, "rgb(173, 105, 82)", 200));
		this.world.add(new Attack(50));
		this.world.add(new Line(0, 0, 600, 5, "rgb(36, 177, 219)"));
		this.world.add(new Line(0, 395, 600, 5, "rgb(36, 177, 219)"));
		this.lastObjShot = false;
		this._step = (now) => {
			this._loop = requestAnimationFrame(this._step);
			this.delta = Math.min(now - this._last, 100) / 1000;
			this._last = now;
			this.update();
			this.render();
		};
		this._step();
	}

	update() {
		for (let entity of this.world) if (entity.update) entity.update(this);
	}

	render() {
		this.ctx.fillStyle = "rgb(36, 177, 219)";
		this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
		for (let entity of this.world) if (entity.render) entity.render(this);
	}

	collide(entity1, type) {
		for (let entity2 of this.world) {
			if (entity1 != entity2  && 
				entity2.type & type && 
				entity1.x < entity2.x + entity2.w &&
				entity1.x + entity1.w > entity2.x &&
				entity1.y < entity2.y + entity2.h &&
				entity1.h + entity1.y > entity2.y) return true;
		}
		return false;
	}

	stop()
	{
		if (this._loop) this._loop = cancelAnimationFrame(this._loop);
	}
}

class Rect
{
	constructor() {
	}

	render(game) {
		game.ctx.fillStyle = this.color;
		game.ctx.fillRect(this.x, this.y, this.w, this.h);
	}
}

const PLAYER = 1, SHIP = 2, SHOT = 4, LINE = 8;

class Player extends Rect
{
	constructor(px, py, pw, ph, c, v) {
		super();
		Object.assign(this, { type: PLAYER, rate: 0.4, delay: 2, x : px, y : py, w : pw, h : ph, color : c, vel : v });
	}

	update(game) {

		if(keyEvent.left) {
			this.x -= this.vel * game.delta;
		}
		if(keyEvent.right) {
			this.x += this.vel * game.delta;
		}
		this.delay -= 0.05;
		if (keyEvent.space && this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Shot(this.x+11, this.y-7, 7, 7, 'red', 100));
		}
		if (game.collide(this, PLAYER | SHIP | LINE )) game.stop();
	}
}

class Ship extends Rect
{
	constructor(px, py, pw, ph, c, v) {
		super();
		Object.assign(this, { type: SHIP, x : px, y : py, w : pw, h : ph, color : c, vel : v });
	}

	update(game) {
		this.y += this.vel * game.delta;
		if (game.collide(this, PLAYER | SHOT | LINE )) game.world.delete(this);
	}
}

class Shot extends Rect
{
	constructor(px, py, pw, ph, c, v) {
		super();
		Object.assign(this, { type: SHOT, x : px, y : py, w : pw, h : ph, color : c, vel : v });
	}

	update(game) {
		this.y -= this.vel * game.delta;
		if (game.collide(this, SHIP | SHOT | LINE )) game.world.delete(this);
	}
}

class Line extends Rect
{
	constructor(px, py, pw, ph, c, v) {
		super();
		Object.assign(this, { type: LINE, x : px, y : py, w : pw, h : ph, color : c, vel : v  });
	}
}

class Attack {
	constructor(s) {
		Object.assign(this, { size: s, rate: 0.5, delay: 0 });
	}
	update(game) {
		this.delay -= 0.05;
		if (this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Ship(Math.random() * 590, 5, 10, 10, 'green', 100));
			if (!--this.size) game.world.delete(this);
		}
	}
}

const keyEvent = {
	left : false,
	right : false,
	space : false
};

window.onkeydown = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = true; break;
  	case 39 : keyEvent.right = true; break;
  	case 32 : keyEvent.space = true; break;
  }
};

window.onkeyup = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = false; break;
  	case 39 : keyEvent.right = false; break;
  	case 32 : keyEvent.space = false; break;
  }
};

const game = new Game(document.getElementById('canvas'));


и у меня там кажется бардак в конструкторах :-?

Rise 12.10.2017 19:05

malinovsky,
this._step(0);

malinovsky 18.10.2017 23:35

Rise,
спасибо)

malinovsky 18.10.2017 23:42

Rise,
Я тут еще заметил, что объекты при столкновении удаляются не все, например, если сталкивается пуля с противником и пуля удаляется первой, то противник может остаться и продолжить движение, поэтому я немного изменил код. Теперь вроде бы работает правильно. Жду твоего мнения.
class Game
{
	constructor(canvas) {
		this.canvas = canvas;
		this.ctx = canvas.getContext('2d');
		this.world = new Set();
		this._last = 0;
		this.count = 0;
		this.world.add(new Player(this.canvas.width/2-30, this.canvas.height-30, 30, 20, "rgb(173, 105, 82)", 200));
		this.world.add(new Attack(50));
		this.lastObjShot = false;
		this._step = (now) => {
			this._loop = requestAnimationFrame(this._step);
			this.delta = Math.min(now - this._last, 100) / 1000;
			this._last = now;
			this.update();
			this.render();
		};
		this._step(0);
	}

	update() {
		for (let entity of this.world) if (entity.update) entity.update(this);
	}

	render() {
		this.ctx.fillStyle = "rgb(36, 177, 219)";
		this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
		for (let entity of this.world) if (entity.render) entity.render(this);
	}

	collide(entity1, type) {
		for (let entity2 of this.world) {
			if (entity1 != entity2  &&
				entity1.x <= entity2.x + entity2.w &&
				entity1.x + entity1.w >= entity2.x &&
				entity1.y <= entity2.y + entity2.h &&
				entity1.h + entity1.y >= entity2.y) 
				{
					if(entity1.type == (SHOT || SHIP) && entity2.type == (SHIP || SHOT))
					{
						this.world.delete(entity1);
						this.world.delete(entity2);
					}
					if(entity1.type == PLAYER && entity2.type == SHIP)
					{
						this.stop();
					}
				}	
		}
	}

	stop()
	{
		if (this._loop) this._loop = cancelAnimationFrame(this._loop);
	}
}

class Rect
{
	constructor() {
	}

	render(game) {
		game.ctx.fillStyle = this.color;
		game.ctx.fillRect(this.x, this.y, this.w, this.h);
	}
}

const PLAYER = 1, SHIP = 2, SHOT = 4, LINE = 8;

class Player extends Rect
{
	constructor(px, py, pw, ph, c, v, game) {
		super();
		Object.assign(this, { type: PLAYER, rate: 0.1, delay: 0, x : px, y : py, w : pw, h : ph, color : c, vel : v });
	}

	update(game) {

		if(keyEvent.left) {
			this.x -= this.vel * game.delta;
		}
		if(keyEvent.right) {
			this.x += this.vel * game.delta;
		}
		this.delay -= game.delta;
		if (keyEvent.space && this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Shot(this.x+11, this.y-8, 7, 7, 'red', 200));
		}
		game.collide(this);
	}
}

class Ship extends Rect
{
	constructor(px, py, pw, ph, c, v, game) {
		super();
		Object.assign(this, { type: SHIP, x : px, y : py, w : pw, h : ph, color : c, vel : v });
		game.ship = this;
	}

	update(game) {
		this.y += this.vel * game.delta;
		game.collide(this);
	}
}

class Shot extends Rect
{
	constructor(px, py, pw, ph, c, v) {
		super();
		Object.assign(this, { type: SHOT, x : px, y : py, w : pw, h : ph, color : c, vel : v });
	}

	update(game) {
		this.y -= this.vel * game.delta;
		game.collide(this);
		if(this.y < 0) game.world.delete(this);
	}
}

class Attack {
	constructor(s) {
		Object.assign(this, { size: s, rate: 0.5, delay: 0 });
	}
	update(game) {
		this.delay -= game.delta;
		if (this.delay < 0) {
			this.delay = this.rate;
			game.world.add(new Ship(Math.random() * 590, 5, 10, 10, 'green', 100, game));
			if (!--this.size) game.world.delete(this);
		}
	}
}

const keyEvent = {
	left : false,
	right : false,
	space : false
};

window.onkeydown = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = true; break;
  	case 39 : keyEvent.right = true; break;
  	case 32 : keyEvent.space = true; break;
  }
};

window.onkeyup = function(e) {
  switch(e.keyCode) {
  	case 37 : keyEvent.left = false; break;
  	case 39 : keyEvent.right = false; break;
  	case 32 : keyEvent.space = false; break;
  }
};

const game = new Game(document.getElementById('canvas'));

Rise 21.10.2017 15:57

malinovsky,
class Game {
	constructor(canvas, ...entities) {
		// ...
		this.world = new Set(entities);
		this.cache = new Set();
		// ...
	}
	// ...
	update() {
		for (let entity of this.world) if (entity.update) entity.update(this);
		for (let entity of this.cache) {
			this.world.delete(entity);
			this.cache.delete(entity);
		}
	}
	// ...
}

class Ship extends Rect {
	// ...
	update(game) {
		// ...
		if (game.collide(this, PLAYER | SHOT | LINE )) game.cache.add(this);
	}
}

const game = new Game(canvas, new Player(), new Attack());

malinovsky 24.10.2017 23:59

Rise,
тоже самое, вот игра: https://himbotop.github.io/MFG3/
и сам код
class Start
{
    constructor(canvas, urlArr) {
        this.canvas = canvas;
        this.urlCache = {};
        urlArr.forEach((url) => {
                this.load(url);
            });
    }

    load(url) {
        let img = new Image();
        img.onload = () => {
            this.urlCache[url] = img;          
            if(this.isReady()) {
                new Game(this, 
                        new Player(this.canvas.width/2-60, this.canvas.height-100, 102, 83, this.get('img/ship.png'), 200),
                        new Attack(50));
            }
        };
        this.urlCache[url] = false;
        img.src = url;
    }

    get(url) {
        return this.urlCache[url];
    }

    isReady() {
        let ready = true;
        for(let k in this.urlCache) {
            if(this.urlCache.hasOwnProperty(k) &&
               !this.urlCache[k]) {
                ready = false;
            }
        }
        return ready;
    }
}

class Game
{
    constructor(resources, ...entities) {
        this.ctx = resources.canvas.getContext('2d');
        this.resources = resources; 
        this.terrainPattern = this.ctx.createPattern(this.resources.get('img/starfield.png'), 'repeat');
        this.world = new Set(entities);
        this.cache = new Set();
        this._last = 0;
        this.lastObjShot = false;
        this._step = (now) => {
            this._loop = requestAnimationFrame(this._step);
            this.delta = Math.min(now - this._last, 100) / 1000;
            this._last = now;
            this.update();
            this.render();
        };
        this._step(0);
    }

    update() {
        for (let entity of this.world) if (entity.update) entity.update(this);
        for (let entity of this.cache) {
            this.world.delete(entity);
            this.cache.delete(entity);
        }
    }

    render() {
        this.ctx.fillStyle = this.terrainPattern;
        this.ctx.fillRect(0, 0, this.resources.canvas.width, this.resources.canvas.height);
        for (let entity of this.world) if (entity.render) entity.render(this);
    }

    collide(entity1, type) {
        for (let entity2 of this.world) {
            if (entity1 != entity2  && 
                entity2.type & type && 
                entity1.x < entity2.x + entity2.w &&
                entity1.x + entity1.w > entity2.x &&
                entity1.y < entity2.y + entity2.h &&
                entity1.h + entity1.y > entity2.y) return true;
        }
        return false;
    }

    stop() {
        if (this._loop) this._loop = cancelAnimationFrame(this._loop);
    }
}

class Draw
{
    constructor(options) {
        const property = {
            x : null,
            y : null,
            w : null,
            h : null,
            image : null,
            vel : null
        };
        let length = 0;
        for(let prop in property) {
            property[prop] = options[length];
            length++
        }
       Object.assign(this, property);
    }

    render(game) {
        game.ctx.drawImage(this.image, this.x, this.y, this.w, this.h)
    }
}

const PLAYER = 1, SHIP = 2, SHOT = 4, LINE = 8;

class Player extends Draw
{
    constructor(...options) {
        super(options);
        Object.assign(this, { type: PLAYER, rate: 0.2, delay: 0 });
    }

    update(game) {

        if(keyEvent.left) {
            this.x -= this.vel * game.delta;
        }
        if(keyEvent.right) {
            this.x += this.vel * game.delta;
        }
        this.delay -= game.delta;
        if (keyEvent.space && this.delay < 0) {
            this.delay = this.rate;
            game.world.add(new Shot(this.x+45, this.y-30, 10, 38, game.resources.get('img/bullet.png'), 300));
        }
        if (game.collide(this, PLAYER | SHIP | LINE)) game.stop();
    }
}

class Ship extends Draw
{
    constructor(...options) {
        super(options);
        Object.assign(this, { type: SHIP });
    }

    update(game) {
        this.y += this.vel * game.delta;
        if (game.collide(this, PLAYER | SHOT | LINE )) game.cache.add(this);
    }
}

class Shot extends Draw
{
    constructor(...options) {
        super(options);
        Object.assign(this, { type: SHOT });
    }

    update(game) {
        this.y -= this.vel * game.delta;
        if (game.collide(this, SHIP | SHOT | LINE )) game.world.delete(this);
        if(this.y < 0) game.world.delete(this);
    }
}

class Attack {
    constructor(s) {
        Object.assign(this, { size: s, rate: 0.5, delay: 0 });
    }
    update(game) {
        this.delay -= game.delta;
        if (this.delay < 0) {
            this.delay = this.rate;
            game.world.add(new Ship(Math.random() * 590, 5, 66, 74, game.resources.get('img/enemy.png'), 100));
            if (!--this.size) game.world.delete(this);
        }
    }
}

const keyEvent = {
    left : false,
    right : false,
    space : false
};

window.onkeydown = function(e) {
  switch(e.keyCode) {
    case 37 : keyEvent.left = true; break;
    case 39 : keyEvent.right = true; break;
    case 32 : keyEvent.space = true; break;
  }
};

window.onkeyup = function(e) {
  switch(e.keyCode) {
    case 37 : keyEvent.left = false; break;
    case 39 : keyEvent.right = false; break;
    case 32 : keyEvent.space = false; break;
  }
};

const canvas = document.getElementById('canvas');

const start = new Start(canvas,
                        ["img/bullet.png",
                        "img/enemy.png",
                        "img/ship.png",
                        "img/starfield.png"]);


Часовой пояс GMT +3, время: 13:29.