Как сделать объект “твёрдым” | Баги с картой уровня

284
23 марта 2021, 05:30

Когда-то давно я нашёл, дескать, "самую сложную игру" ( http://caniplay.ru/flash/4565 ), и вот, спустя несколько лет, решил сделать что-то подобное, только не шибко хардкорное. Дело в том, что столкновения-то я обнаруживать научился, но вот как сделать объект твёрдым при столкновении - это уже посложнее. Я вообще не понимаю что в коде не так, вроде всё логично, но js так не считает.

Ещё с картой уровня проблемы: верхняя часть съезжает. Логикой это дело не возьмёшь. Помогите, пожалуйста.

P.s. только чёткий и ясный ответ, не псевдокод или тупо объяснение с хабра, а именно рабочий пример.

<!DOCTYPE html> 
<html> 
<head> 
	<title>.</title> 
	<meta charset="UTF-8"> 
</head> 
<body style="margin: 0"> 
	<canvas id="can" style="display: block"></canvas> 
	<script type="text/javascript"> 
		function getId(o){return document.getElementById(o)}; 
		class Block { 
			constructor(position, size, color, type) { 
				this.position = position, 
				this.size = size, 
				this.sys = {color: color} 
			} 
		}; 
 
		let can = getId("can"), ctx = can.getContext('2d'), game = { 
			size: 35 
		}, 
		player = { 
			position: { 
				x: innerWidth / 2 - 25, 
				y: innerHeight - 25 - 300 
			}, 
			size: { 
				width: 50, 
				height: 50 
			}, 
			sys: { 
				color: "#AA00FF", 
				velocity: { 
					x: 0, 
					y: 0, 
					friction: .9, 
					plus: 1 
				}, 
				keys: { 
					w: false, 
					a: false, 
					s: false, 
					d: false 
				}, 
				scroll: { 
					x: 0, 
					y: 0 
				} 
			} 
		}, blocks = [], createBlock = (x, y, w, h, color) => blocks.push( new Block({x: x, y: y}, {width: w, height: h}, color)), 
		collides = (pl1, pl2) => (pl1.position.x + pl1.size.width < pl2.position.x || 
			pl2.position.x + pl2.size.width < pl1.position.x || 
			pl1.position.y + pl1.size.height < pl2.position.y || 
			pl2.position.y + pl2.size.height < pl1.position.y) ? false : true, 
		map = ` 
		bbbbbbbbbbbbbbbbbbbbbbbbbbbb-b 
		b----------------------------b 
		b----------------------------b 
		b----------------------------b 
		b----------------------------b 
		b----------------------------b 
		b-------------P--------------b 
		b-------------b--------------b 
		b----------------------------b 
		b----------------------------b 
		bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 
		`.trim().split('\n'); 
 
		// Тут какая-то фигня: блоки вверху "слетают" почему-то. 
		for(let y = 0; y < map.length; y++) { 
			for(let x = 0; x < map[y].length; x++) { 
				let sym = map[y][x], 
				w = game.size, 
				h = game.size; 
				if(sym == "P") { 
					player.position.x = x * w; 
					player.position.y = y * h; 
					player.size.width = w; 
					player.size.height = h 
				} else if(sym == "b") { 
					createBlock(x * w, y * h, w, h, "#902") 
				} 
			} 
		}; 
 
		onresize = () => { 
			can.width = innerWidth; 
			can.height = innerHeight 
		}; 
		onresize(); 
 
		document.onkeydown = e => { 
			let kc = e.keyCode; 
			if(kc == 'W'.charCodeAt()) player.sys.keys.w = true; 
			if(kc == 'A'.charCodeAt()) player.sys.keys.a = true; 
			if(kc == 'S'.charCodeAt()) player.sys.keys.s = true; 
			if(kc == 'D'.charCodeAt()) player.sys.keys.d = true; 
			if(kc == 'Z'.charCodeAt()) blocks.pop() 
		}; 
		document.onkeyup = e => { 
			let kc = e.keyCode; 
			if(kc == 'W'.charCodeAt()) player.sys.keys.w = false; 
			if(kc == 'A'.charCodeAt()) player.sys.keys.a = false; 
			if(kc == 'S'.charCodeAt()) player.sys.keys.s = false; 
			if(kc == 'D'.charCodeAt()) player.sys.keys.d = false 
		}; 
		 
		(loop = () => { 
			ctx.fillStyle = "rgba(0, 0, 0, .24903)"; 
			ctx.fillRect(-3e38, -3e38, 3e38 * 2, 3e38 * 2); 
 
			// Скроллинг 
			player.sys.scroll.x = -player.position.x + innerWidth / 2 - player.size.width / 2; 
			player.sys.scroll.y = -player.position.y + innerHeight / 2 - player.size.height / 2; 
			ctx.save(); 
			ctx.translate(player.sys.scroll.x, player.sys.scroll.y); 
 
			if(player.sys.keys.w) player.sys.velocity.y -= player.sys.velocity.plus; 
			if(player.sys.keys.s) player.sys.velocity.y += player.sys.velocity.plus; 
			if(player.sys.keys.a) player.sys.velocity.x -= player.sys.velocity.plus; 
			if(player.sys.keys.d) player.sys.velocity.x += player.sys.velocity.plus; 
 
			player.sys.velocity.y *= player.sys.velocity.friction; 
			player.sys.velocity.x *= player.sys.velocity.friction; 
			player.position.y += player.sys.velocity.y; 
			player.position.x += player.sys.velocity.x; 
 
			blocks.forEach(block => { 
				// ВОТ ТУТ ВООБЩЕ НИЧЕГО НЕ ПОНЯТНО 
				if(collides(block, player)) { 
					if(player.position.y + player.size.height + player.sys.velocity.y > block.position.y) { 
						player.position.y = block.position.y - block.size.height 
					} else if(player.position.y - player.sys.velocity.y < block.position.y + block.size.height) { 
						player.position.y = block.position.y + block.size.height 
					} 
				}; 
 
				ctx.fillStyle = block.sys.color; 
				ctx.fillRect(block.position.x, block.position.y, block.size.width, block.size.height) 
			}); 
 
			ctx.fillStyle = player.sys.color; 
			ctx.fillRect(player.position.x, player.position.y, player.size.width, player.size.height); 
			ctx.restore(); 
 
			requestAnimationFrame(loop) 
		})(); 
	</script> 
</body> 
</html>

Answer 1

Вот, я думаю как-то так, даже кое где тверже чем хотелось бы =).

Баги с картой уровня - распечатайте содержимое строки, образующей уровень и увидите там \t

Добавил там Вам всякого рисования для отладки:

class Block { 
  constructor(position, size, color, type) { 
    this.position = position, 
      this.size = size, 
      this.sys = { 
        color: color 
      } 
  } 
}; 
let debug = false; 
let ctx = can.getContext('2d'), 
  game = {size: 35}, 
  player = { 
    position: {x: innerWidth / 2 - 25,y: innerHeight - 25 - 300}, 
    size: {width: 50, height: 50}, 
    sys: { 
      color: "#AA00FF", 
      velocity: {x: 0, y: 0,friction: .9,plus: .99}, 
      keys: {w: false,a: false,s: false, d: false}, 
      scroll: {x: 0, y: 0} 
    } 
  }, 
  blocks = [], 
  createBlock = (x, y, w, h, color) =>  
    blocks.push(new Block({x: x, y: y}, {width: w, height: h}, color)), 
  collides = (pl1, pl2) => !( 
    pl1.position.x + pl1.size.width < pl2.position.x || 
    pl2.position.x + pl2.size.width < pl1.position.x || 
    pl1.position.y + pl1.size.height < pl2.position.y || 
    pl2.position.y + pl2.size.height < pl1.position.y), 
  map = ` 
		bbbbbbbbbbbbbbbbbbbbbbbbb----b 
		b----------------------------b 
		b-------------------------bbbb 
		b----------------------------b 
		b----b-----------------------b 
		b----------------------------b 
		b-------------P--------------b 
		b-------------b--------------b 
		b-----------------------b----b 
		b----------------------------b 
		bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 
		`.trim().split('\t').join('').split('\n'); 
 
for (let y = 0; y < map.length; y++) { 
  for (let x = 0; x < map[y].length; x++) { 
    let sym = map[y][x], w = game.size,  h = game.size; 
    if (sym == "P") { 
      player.position.x = x * w; 
      player.position.y = y * h; 
      player.size.width = w; 
      player.size.height = h 
    } else if (sym == "b") { 
      createBlock(x * w, y * h, w, h, "#902") 
    } 
  } 
}; 
 
onresize = () => {can.width = innerWidth;can.height = innerHeight}; 
onresize(); 
 
document.onkeydown = e => { 
  let kc = e.keyCode; 
  if (kc == 'W'.charCodeAt()) player.sys.keys.w = true; 
  if (kc == 'A'.charCodeAt()) player.sys.keys.a = true; 
  if (kc == 'S'.charCodeAt()) player.sys.keys.s = true; 
  if (kc == 'D'.charCodeAt()) player.sys.keys.d = true; 
  if (kc == 'Z'.charCodeAt()) blocks.pop() 
}; 
 
document.onkeyup = e => { 
  let kc = e.keyCode; 
  if (kc == 'W'.charCodeAt()) player.sys.keys.w = false; 
  if (kc == 'A'.charCodeAt()) player.sys.keys.a = false; 
  if (kc == 'S'.charCodeAt()) player.sys.keys.s = false; 
  if (kc == 'D'.charCodeAt()) player.sys.keys.d = false 
}; 
 
(loop = () => { 
 
  requestAnimationFrame(loop); 
 
  ctx.fillStyle = "rgba(0, 0, 0, .24903)"; 
  ctx.fillRect(-3e38, -3e38, 3e38 * 2, 3e38 * 2); 
 
  // Скроллинг 
  player.sys.scroll.x = -player.position.x + innerWidth / 2 - player.size.width / 2; 
  player.sys.scroll.y = -player.position.y + innerHeight / 2 - player.size.height / 2; 
  ctx.save(); 
  ctx.translate(player.sys.scroll.x, player.sys.scroll.y); 
 
  if (player.sys.keys.w) player.sys.velocity.y -= player.sys.velocity.plus; 
  if (player.sys.keys.s) player.sys.velocity.y += player.sys.velocity.plus; 
  if (player.sys.keys.a) player.sys.velocity.x -= player.sys.velocity.plus; 
  if (player.sys.keys.d) player.sys.velocity.x += player.sys.velocity.plus; 
 
  player.sys.velocity.y *= player.sys.velocity.friction; 
  player.sys.velocity.x *= player.sys.velocity.friction; 
 
  let top = null, bottom = null, left = null, right = null; 
  blocks.forEach(block => { 
    let a, collision = collides(block, player); 
    if (collision) { 
      // определяем с какой стороны стена и запоминаем ограничения 
      let dy = player.position.y - block.position.y; 
      let dx = player.position.x - block.position.x; 
      a = Math.atan2(dy, dx)/Math.PI*180; 
      if (a> 45 && a< 135) top = block.position.y+block.size.height; 
      if (a<-45 && a>-135) bottom = block.position.y-player.size.height; 
      if (a< 45 && a> -45) left = block.position.x+block.size.width; 
      if (a>135 || a<-135) right = block.position.x-player.size.width; 
    }; 
 
    ctx.fillStyle = debug && collision ? 'wheat' : block.sys.color; 
    ctx.fillRect(block.position.x, block.position.y, block.size.width, block.size.height) 
    if (debug && collision){ 
      let cx = block.position.x+block.size.width/2; 
      let cy = block.position.y+block.size.height/2; 
      ctx.beginPath();  
      ctx.moveTo(cx,cy); 
      ctx.lineTo(cx+Math.cos(a/180*Math.PI)*15,cy+Math.sin(a/180*Math.PI)*15); 
      ctx.stroke(); 
    } 
  }); 
  let f = 0.66; // имитация трения об препятствия 
  let fritcion = (bottom?f:1)*(top?f:1)*(left?f:1)*(right?f:1); 
  player.position.y += player.sys.velocity.y * fritcion; 
  player.position.x += player.sys.velocity.x * fritcion; 
   
  if (bottom != null) 
    player.position.y = Math.min(player.position.y, bottom); 
  if (top != null) 
    player.position.y = Math.max(player.position.y, top); 
  if (left != null) 
    player.position.x = Math.max(player.position.x, left); 
  if (right != null) 
    player.position.x = Math.min(player.position.x, right); 
     
  ctx.fillStyle = player.sys.color; 
  ctx.fillRect(player.position.x, player.position.y, player.size.width, player.size.height); 
   
  if (!debug) 
    return ctx.restore(); 
   
  ctx.fillStyle = 'green'; 
  if (player.sys.velocity.y < 0 && top) 
   ctx.fillRect(player.position.x, player.position.y, player.size.width, 4); 
  if (player.sys.velocity.y > 0 && bottom) 
   ctx.fillRect(player.position.x, player.position.y+player.size.height-4, player.size.width, 4); 
  if (player.sys.velocity.x < 0 && left) 
   ctx.fillRect(player.position.x, player.position.y, 4, player.size.height); 
  if (player.sys.velocity.x > 0 && right) 
   ctx.fillRect(player.position.x+player.size.width-4, player.position.y, 4, player.size.height);  
   
  ctx.restore(); 
   
})();
body, canvas {margin: 0; display: block}
<canvas id="can"></canvas>

READ ALSO
Как поступить с центровкой элементов?

Как поступить с центровкой элементов?

В общем ситуация как обычно

101
Как исправить ошибку в JS? [закрыт]

Как исправить ошибку в JS? [закрыт]

Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском

108