二维地图阵列上的简单 Javascript 碰撞检测

Simple Javascript Collision Detection on a 2D map array

你好 :) 我为一个学校项目制作了一个小游戏。这是一款基本的 Javascript 2D Canvas 游戏,带有来自数组的 Tile Map。

我的问题是,我无法使碰撞正常工作。在我的示例中,我想 "walk" 和我的 "Hero" 在带有 1 的瓷砖上。0 是 "Hero" 不行走的瓷砖。

我有一个针对 Canvas 边界的简单碰撞检测,所以我的英雄不能走出 Canvas。但是我不知道如何让它为其余的工作。

HTML

  <script type="text/javascript">
    var Spielfeld, Spieler, Zuletzt, Collision;         


    function initialisieren( Anzeige ) {    
        Spielfeld = Anzeige ;                                               
        Spieler = new Spieler( Spielfeld ) ;                                
        Spieler.Name = 'Ich' ;                                              
        Zuletzt = 0 ;
        document.getElementsByTagName('body')[0].onkeydown = steuern ;      
        document.getElementsByTagName('body')[0].onkeyup = steuern ;        
        window.requestAnimationFrame( aktualisieren ) ; 

    }

    function steuern( Ereignis ) {                                          
        switch( Ereignis.keyCode ) {                                        
            case 87: Spieler.setOben( Ereignis.type == 'keydown'); break;   
            case 83: Spieler.setUnten( Ereignis.type == 'keydown'); break;  
            case 65: Spieler.setLinks( Ereignis.type == 'keydown'); break;  
            case 68: Spieler.setRechts( Ereignis.type == 'keydown'); break;
            case 38: Spieler.setOben( Ereignis.type == 'keydown'); break;
            case 40: Spieler.setUnten( Ereignis.type == 'keydown'); break;
            case 37: Spieler.setLinks( Ereignis.type == 'keydown'); break;
            case 39: Spieler.setRechts( Ereignis.type == 'keydown'); break;
        }
    }
    function aktualisieren() {                                              
        var Jetzt = new Date();                                             
        var Dauer = Jetzt.getTime() - Zuletzt ;                             
        Zuletzt = Jetzt.getTime() ;                                         
        loeschen( Spielfeld ) ;                                             
        Spieler.aktualisieren( Dauer ) ;                                                                        
        window.requestAnimationFrame( aktualisieren ) ;                     
    }

    function loeschen( Anzeige ) {
        Stift = Anzeige.getContext('2d') ;                                  
        Stift.clearRect( 0,0 , Anzeige.width, Anzeige.height ) ;}</script>
 </head>





  <body onload="initialisieren( document.getElementById('Spielfeld') ) ;">      
<canvas id="Laufweg" width="1280" height="768" style="position: absolute; z-index: 3">Funktioniert nicht!</canvas>
<canvas id="Hintergrund" width="1280" height="768" style="position: absolute; z-index: 1">Funktioniert nicht!</canvas>
<canvas id="Spielfeld" width="1280" height="768" style="position: absolute; z-index: 2">Funktioniert nicht!</canvas>
    <script type="text/javascript" src="./javascript/Charakter.js"></script>    
 </body>    

Charakter.js

 function Spieler( Spielfeld ) {
var Held = document.createElement('img');                       
Held.src = '../Arbeitsdateien/items/item_berliner.png';                         
var Anzeige = Spielfeld ;                                                                       
var Breite = 32 ;                                                                       
var Hoehe = 32 ;                                                
var PosX = 32 ;                                                 
var PosY = 192 +32 ;                                                    
var Schritt = 400 ;                                                                                         

var hero = Held;


var Oben, Unten, Links, Rechts ;                                                                        
    Oben = Unten = Links = Rechts = false ;                                 

this.setOben = function( Schalter ) { Oben = Schalter == true ; }                                           
this.setUnten = function( Schalter ) { Unten = Schalter == true ; }                                         
this.setLinks = function( Schalter ) { Links = Schalter == true ; }                                         
this.setRechts = function( Schalter ) { Rechts = Schalter == true ; }                                       
this.aktualisieren = function( Dauer ) {                                                                    
    bewegen( Dauer ) ;                                                                                      
    anzeigen() ;                                                                        
}

//--------------------------------------------------------------------------------------------------------------------------
//Kollision für die Laufwege -----------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------

var fliese = {
    fliesenGroesse: 32
};

var mapKollision = [                                                                                                                                                                            //004
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1,0,1,1,1,1,0,0,0,1,1,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,0,0,0,1,1,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0],
    [1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0],
    [0,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0],
    [0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
    [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
];
var viewport = document.getElementById('Laufweg');
var ctx = viewport.getContext('2d');

 function renderMap() {
var i, j;

ctx.clearRect( 0, 0, 1280, 768 );
ctx.fillStyle = "rgba(255,0,0,0.5)";

for( i = 0; i < mapKollision.length; i++ ) {
    for( j = 0; j < mapKollision[ i ].length; j++ ) {
        if( mapKollision[ i ][ j ] !== 0 ) {
            ctx.fillRect(
                j * fliese.fliesenGroesse, i * fliese.fliesenGroesse,
                fliese.fliesenGroesse, fliese.fliesenGroesse
            );
        }
    }
}
 }
 renderMap();

 function bewegen( Dauer ) {
var Etappe = Dauer / 1000 ;
if( Links ) PosX -= Schritt * Etappe ;
if( Rechts ) PosX += Schritt * Etappe ;
if( Oben ) PosY -= Schritt * Etappe ;
if( Unten ) PosY += Schritt * Etappe ;


if( PosX  < 0 ) PosX = 0 ;
if( PosX  > Anzeige.width -32  ) PosX = 1248 ;
if( PosY  < 0 ) PosY = 0 ;
if( PosY  > Anzeige.height -32 ) PosY = 736 ;
 }

function anzeigen() {
    Stift = Anzeige.getContext('2d') ;  
    Stift.drawImage( Held, PosX,PosY) ;
}
 }  

通过这段代码,我完成了边界碰撞工作:

if( PosX  < 0 ) PosX = 0 ;
if( PosX  > Anzeige.width -32  ) PosX = 1248 ;
if( PosY  < 0 ) PosY = 0 ;
if( PosY  > Anzeige.height -32 ) PosY = 736 ;

也许你可以帮我解决我的问题,让我的 0 块瓷砖发生碰撞。 这是一个 img 它的外观(红色是行走的路径(1)白色是不可行走的瓷砖(0)) (https://imgur.com/a/YLu6HU2)

希望您能理解我的问题所在。 我真的是 Javascript 的新手,但在此之前,它对我来说工作正常:)

我尝试对您正在研究的 概念 进行超级简化,希望它能帮助您更好地理解如何修复代码。它还介绍了一些您可能不熟悉的概念,但它们将极大地帮助您以 "game dev" 模式思考,我相信!

所以,如果我没有直接修复您的代码,我深表歉意 – 我不得不说,变量名称的 "native language" 没有帮助 :) – 但我仍然希望它能有所帮助。

这里是HTML文件:

<canvas width="7" height="6"></canvas>

如您所见,我创建了一个超小型 canvas。这可能对你没有用,但实际测试东西和 "retrogaming" 风格是惊人的。

这里是CSS:

canvas {
  border: 1px solid silver;
  width: 350px;
  height: 300px;
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
}

基本上 canvas 的 1 个像素占用 50 css 像素。我们有巨大的像素:)

现在是有趣的部分。 JS:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const w = canvas.width; // 7
const h = canvas.height; // 6

到目前为止没有什么奇怪的,我只是得到了我们稍后需要使用的参考。

const imageData = ctx.getImageData(0, 0, w, h);
const buff = new Uint32Array(imageData.data.buffer);

我不想使用 Canvas 2D API,我想直接 "blit" 像素。 所以我得到整个 canvas 作为 ImageData, and see its data buffer as an array of Unsigned 32 bit Integer. In this way, every element of this array represent one single pixel. A pixel is made by four byte, one byte for each channel of the color: Red, Green, Blue, and Alpha (opacity). It's more or less the same when you specify a RGB color by CSS (e.g. 0xffeedd), the only difference is that the order of the bytes are inverted (so you don't hage RGBA but ABGR) due the endianess.

注意:此代码假设我们采用小端(您可以安全地假设现在一般来说,对于这种事情)。

const map = [
  0, 0, 0, 0, 0, 0, 0,
  1, 1, 0, 0, 0, 0, 0,
  0, 1, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 0, 0,
  0, 0, 0, 1, 0, 0, 0,
  0, 0, 0, 1, 1, 1, 1
];

这里是地图,7x6.

const palette = [0xff000000, 0xffffffff, 0xff0000ff];

这里我们定义了我们的palette:所以我们的"game"目前只有三种颜色,第一种是黑色(最大不透明度,0xff,R、G、B为0),第二个是白色(每个组件都是 0xff),第三个是红色(记住字节顺序,Alpha 和红色通道为 0xff,绿色和蓝色通道为 0)。

function drawMap() {
  for (let k = 0; k < h; k++) {
    for (let j = 0; j < w; j++) {
      let i = j + k * w;
      buff[i] = palette[map[i]]
    }
  }
}

这应该或多或少是熟悉的:我们有循环来迭代 map 数组。根据地图的值(01),我们从调色板中选择颜色,所以墙壁是黑色的,而走廊是白色的。

const hero = {x: 0, y: 1};

hero用于存储当前绘制位置的坐标。

function drawHero() {
  buff[hero.x + hero.y * w] = palette[2];
}

地图使用的逻辑几乎相同,但在本例中我们使用调色板中的第三种颜色。

// listen to keyboard

const Keys = {
  pressed: {},
  handleEvent({type, code}) {
    this.pressed[code] = type === "keydown"
  }
}

document.addEventListener("keydown", Keys);
document.addEventListener("keyup", Keys);

这用于"store"按下的键。它没有优化,但为此目的就足够了。它使用对象作为事件侦听器而不是函数(参见:https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent

function update() {
  let x = hero.x, y = hero.y;

  if (Keys.pressed["ArrowLeft"]) {
    x = Math.max(0, hero.x - 1);
  }
  if (Keys.pressed["ArrowRight"]) {
    x = Math.min(w - 1, hero.x + 1);
  }
  if (Keys.pressed["ArrowUp"]) {
    y = Math.max(0, hero.y - 1);
  }
  if (Keys.pressed["ArrowDown"]) {
    y = Math.min(h - 1, hero.y + 1);
  }

  if (map[x + y * w]) {
    hero.x = x;
    hero.y = y;
  }
}

这里是检测到碰撞的地方! update 函数根据按下的键、墙壁和 canvas' 边缘更新英雄坐标。 因此,首先,根据按下的键(如果有)但在 canvas 的边界内设置 xy。 然后,如果 map 中的 "new proposed coords" returns 1,则表示角色可以移动到那里,因此 hero 对象也被更新。 否则不会更新。

function loop() {
  update();
  drawMap();
  drawHero();

  // flush
  ctx.putImageData(imageData, 0, 0);

  requestAnimationFrame(loop);
}


loop();

这只是游戏循环,我相信这很简单。我们当然需要 "flush" 我们在实际 canvas.

中写入 buff 的所有内容

这不是优化,还有很多事情要做(比如你应该考虑时间更新坐标,现在太快了),但我希望它能帮助你。

如果没有,请让我知道如何修改它以获得简化版本的问题,以便更好地帮助您。

这里还有一个 运行 描述的代码示例: https://codepen.io/zer0/pen/JxxYgQ