Simple Javascript Collision Detection on a 2D map array

问题: Hello :) I make a small Game for a school project. It`s a basic Javascript 2D Canvas game with a Tile Map from an Array. My problem is, i cant get the collision working....

问题:

Hello :) I make a small Game for a school project. It`s a basic Javascript 2D Canvas game with a Tile Map from an Array.

My problem is, i cant get the collision working. In my Example i want to "walk" with my "Hero" on the tiles with the 1. 0 are the Tiles where the "Hero" don`t walk.

I have a simple Collision detection for the Canvas border, so my Hero can`t walk outside of the Canvas. But i dont know how to make it working for the rest .

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) ;
}
 }  

With this code part i got my border collision work:

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

Maybe you can help me with my Problem to get the collision work for my 0 tiles. Here an img how it look (Red is the path to walk on (1) white are the non walkable tiles(0)) (https://imgur.com/a/YLu6HU2)

I hope you can understand what my Problem is. I´m realy new to Javascript but until this, it is working ok for me :)


回答1:

I tried to do a version super-semplified of the concept you're working on, and I hope it will help you to understand better how to fix your code. It also introduced some concept that you might not be familiar with, but they will greatly help you to thinking in "game dev" mode, I believe!

So, my apologies if I didn't fix directly your code – I have to say, it the "native language" for the variable's name didn't help :) – but I still hope it will helps.

Here the HTML file:

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

As you can see, I create a extra-super-tiny canvas. This is not probably useful to you but it's amazing to actually testing things and for "retrogaming" style.

Here the CSS:

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

Basically 1 pixel of the canvas takes 50 css pixel. We have HUGE pixels :)

And now the interesting part. The JS:

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

So far nothing strange, I just got the reference to what we need to use later.

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

I don't want to use Canvas 2D APIs, I want ti "blit" directly the pixel. So I get the whole canvas as 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.

Note: this code assume we're in little endian (you can safely assume that generally speaking nowadays, for this kind of thing).

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
];

Here map, 7x6.

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

And here we define our palette: so our "game" currently has only three colors, the first one is black (maximum opacity, 0xff, and 0 for R, G and B), the second is white (0xff for every component), the third is red (remember the endianness, 0xff for Alpha and Red channel, 0 for Green and Blue).

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]]
    }
  }
}

This should be more or less familiar: we have loops to iterate the map array. Based on the value of the map (0 or 1) we choose the color from the palette, so the wall are black where the corridor is white.

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

The hero is used to store the current coords where to draw it.

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

The logic is pretty much the same used for the map, but in this case we use the third color from the palette.

// listen to keyboard

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

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

This is used to "store" the key pressed. It's not optimized, but for this purpose was enough. It uses an object as event listener instead of functions (see: 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;
  }
}

And here is where the collision is detected! The update function updates the hero coords based on the keys pressed, the walls, and the canvas' edge. So, first of all, set x and y based on the keys pressed (if any) but within the boundaries of the canvas. Then, if the "new proposed coords" returns 1 in the map, it mean the character can move there, and so the hero object is updated as well. Otherwise it won't be updated.

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

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

  requestAnimationFrame(loop);
}


loop();

That's just the game loop, simple enough I believe. We need of course to "flush" everything we wrote to the buff in the actual canvas.

This is not optimized, and there are plenty of things should be done (e.g. you should update the coords considering the time, now is too fast), but I hope it would help you.

If not, please, let me know how how to modify it to have a simplified version of your issue in order to help you better.

Here there is also a running example of the code described: https://codepen.io/zer0/pen/JxxYgQ

  • 发表于 2019-02-19 21:54
  • 阅读 ( 87 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除