2

So im trying to learn about canvas and canvas games and im currently (more or less) following the W3Schools tutorial on canvas games.

At some point in the tutorial i had the idea to make 2 players, which should both be controled on the same keyboard (NOT online multiplayer).

So i followed the logic given by the tutorial and found the key codes for both WASD and the arrows.

I understand 95% of my code, which means i did not just copy everything without understanding it. (I will comeback to this soon)

The problem with my code is that when i add another player to the system i can move them freely when i only control one player at a time, when i try to move both players at the same time, they cannot be moved freely and i can only press a total of 4 buttons at a time.

Tryout the snippet and play around with the cubes with WASD and the arrows to see what im talking about.

As i said there is a part i do not understand 100% which might be the place for this error? I marked it out anyway on the code snippet.

So all in all my question is: Why can i not move both players freely at the same time?

The whole code is as following and i marked the part i do not understand:

For best experience, use the full screen function

{
    
    function startGame() {
        myGameArea.start();
        myStick = new component(100, 100, 200, 200, "red");
        myStick2 = new component(100, 100, 600, 200, "green");
    }
     
    var myGameArea = {
        canvas : document.createElement("canvas"),

        start : function() {
            var bodyID = document.getElementById("body");

            this.canvas.width = bodyID.offsetWidth;
            this.canvas.height = (bodyID.offsetHeight);
            this.context = this.canvas.getContext("2d");
            document.body.insertBefore(this.canvas, document.body.childNodes[2]);
            this.interval = setInterval(updateGameArea, (1000 / 60));
            
            //The part i do not understand
            window.addEventListener('keydown', function (e) {
                myGameArea.keys = (myGameArea.keys || []);
                myGameArea.keys[e.keyCode] = true;
            });
            window.addEventListener('keyup', function (e) {
                myGameArea.keys[e.keyCode] = false; 
            });
            //End
        },
            
        clear : function(){
            this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        }
    };
    
    
    function component(width, height, x, y, color, mLeft, mRight, mUpLeft, mUpRigth){
        this.width = width;
        this.height = height;
        this.x = x;
        this.y = y;
        this.speedX = 0;
        this.speedY = 0;
        
        this.update = function(){
            ctx = myGameArea.context;
            ctx.fillStyle = color;
            ctx.fillRect(this.x, this.y, this.width, this.height);
        };
        
        this.newPos = function(){
            this.x += this.speedX;
            this.y += this.speedY;
        };
        
        this.player1 = function(){
            this.speedX = 0;
            this.speedY = 0; 
            if (myGameArea.keys && myGameArea.keys[65]) {this.speedX = -2; } //  Left
            if (myGameArea.keys && myGameArea.keys[68]) {this.speedX = 2; }  //  Right
            if (myGameArea.keys && myGameArea.keys[87]) {this.speedY = -2; } //  Up
            if (myGameArea.keys && myGameArea.keys[83]) {this.speedY = 2; }  //  Down
        };
        
        this.player2 = function(){
            this.speedX = 0;
            this.speedY = 0; 
            if (myGameArea.keys && myGameArea.keys[37]) {this.speedX = -2; } //  Left
            if (myGameArea.keys && myGameArea.keys[39]) {this.speedX = 2; }  //  Right
            if (myGameArea.keys && myGameArea.keys[38]) {this.speedY = -2; } //  Up
            if (myGameArea.keys && myGameArea.keys[40]) {this.speedY = 2; }  //  Down
        };
        
        
    }
    
    function updateGameArea(){
        myGameArea.clear();
        myStick.player1();
        myStick.newPos();
        
        myStick2.player2();
        myStick2.newPos();
        
        
        myStick.update();
        myStick2.update();
    }
    
    
    
}
.nm{
    margin: 0;
    padding: 0;
}

canvas{
    display: block;
    background-color: lightgray;
}
<html>
    <head>
        <meta charset="UTF-8">
        <title>Canvas stick game!</title>
        <link rel="stylesheet" href="css/standard.css">
    </head>
    <body id="body" onload="startGame()" class="nm" style="height: 100vh">
        
    </body>
</html>

<script src="js/canvas.js"></script>
user1509104
  • 132
  • 1
  • 7
  • **I** could move both players simultaneously. What's the problem you're facing? – J. Allan Aug 06 '16 at 18:26
  • Yes you can move both players simultaneously but not freely, heres an example press the UP and Right arrow at the same time, and then try to press W or D. One player will move while the other stand still. :) (Remember to go fullscreen) – user1509104 Aug 06 '16 at 18:31
  • Sorry, dude. I can't reproduce your problem. What browser are you using? – J. Allan Aug 06 '16 at 18:34
  • 2
    Maybe it's because [keyboards are evil.](https://www.sjbaker.org/wiki/index.php?title=Keyboards_Are_Evil) – J. Allan Aug 06 '16 at 18:39
  • I'm using Google Chrome – user1509104 Aug 06 '16 at 18:39
  • I just tried in both Chrome and Firefox and i can make it happen everytime?? I expect you did, but did you remember to hold down the arrow keys and not letting go? When i do this (Holding down and not letting go) i cannot use W nor D same goes the other way around, if i press Down and Left i cannot use A nor D – user1509104 Aug 06 '16 at 18:48
  • Tried with Chrom**ium**, still can't get it to work. – J. Allan Aug 06 '16 at 18:48
  • I just read some of the "keyboards are evil", while i could not reproduce his example, i could reproduce my problem in a text editor, if i press and hold UP and RIGHT i cannot write the letters W and D but any other letter i can :/ – user1509104 Aug 06 '16 at 18:52
  • I'm guessing that is the problem, then. – J. Allan Aug 06 '16 at 18:54

2 Answers2

2

Not sure what the bug was, looked ok as it was but as there were several issues. Saddly W3Schools is not what I would consider a uptodate, but I can offer no alternative.

I made changes to improve the whole thing but you will still lose keys, I am not sure why as the standard says nothing about loss of keys (It says all keys MUST be reported)

When hitting 4 or more keys at the same time (within 1/60th second) none of them are reported. This is highly unlikely when two people are playing, but when one person is testing both direction pads it happens often. I am unaware of a solution.

The key event keyCode has depreciated and you should be using event.key which is a string but event.key is only partially supported.

{
// key maps
const KEY_UP = 38;
const KEY_DOWN = 40;
const KEY_LEFT = 37;
const KEY_RIGHT = 39;
const KEY_W = 87;
const KEY_S = 83;
const KEY_A = 65;
const KEY_D = 68;
const DIR_KEY_MAP2 = [KEY_W, KEY_S, KEY_A, KEY_D];
const DIR_KEY_MAP1 = [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT];
const BLOCK_DEFAULT_FOR = [KEY_W, KEY_S, KEY_A, KEY_D, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT];
const keyEvents = function(event){ // keyboard event listener sets the key array to true if keydown false if not
    if(!event.repeat){ // ignore repeating key events
        myGameArea.keys[event.keyCode] = event.type === "keydown";
    }
    // block default action for keys in array BLOCK_DEFAULT_FOR
    if(BLOCK_DEFAULT_FOR.indexOf(event.keyCode) > -1){
        event.preventDefault();
    }
}


function startGame() {
    myGameArea.start();
    myStick = new Component(100, 100, 200, 200, "red", DIR_KEY_MAP2);
    myStick2 = new Component(100, 100, 600, 200, "green", DIR_KEY_MAP1);
}
 
var myGameArea = {
    canvas : document.createElement("canvas"),
    keys : [],  // create the key array
    start : function() {            
        var bodyID = document.getElementById("body");
        this.canvas.width = bodyID.offsetWidth;
        this.canvas.height = (bodyID.offsetHeight);
        this.context = this.canvas.getContext("2d");
        document.body.insertBefore(this.canvas, document.body.childNodes[2]);
        
        requestAnimationFrame(updateGameArea); // request the first animation frame don't use setInterval
        window.addEventListener('resize', function () { // for stackoverflow 
            myGameArea.canvas.width = bodyID.offsetWidth;
            myGameArea.canvas.height = bodyID.offsetHeight;
        });            
        window.addEventListener('keydown', keyEvents); // this is called once for every key down
        window.addEventListener('keyup', keyEvents); // this is called once for every key up.
    },
    clear : function () {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }
};


function Component(width, height, x, y, color, keyMap){ // key map is the keys used to control
                                               
    this.width = width;
    this.height = height;
    this.x = x;
    this.y = y;
    this.speedX = 0;
    this.speedY = 0;
    // get reference to context
    var ctx = myGameArea.context;
    // clear the keys 
    var i = 3;
    while(i >= 0){ myGameArea.keys[keyMap[i--]] = false; }
    
    this.update = function(){
        this.userInput();
        this.x += this.speedX;
        this.y += this.speedY;
    }
    this.display = function(){
        ctx.fillStyle = color;
        ctx.fillRect(this.x, this.y, this.width, this.height);
    };
    
    
    this.userInput = function(){  // keyMap is accessed via closure
        this.speedY = this.speedX = 0;
        if (myGameArea.keys[keyMap[2]]) {this.speedX = -2; } //  Left
        if (myGameArea.keys[keyMap[3]]) {this.speedX = 2; }  //  Right
        if (myGameArea.keys[keyMap[0]]) {this.speedY = -2; } //  Up
        if (myGameArea.keys[keyMap[1]]) {this.speedY = 2; }  //  Down
    };
    

    
    
}

function updateGameArea(){
    myGameArea.clear();
    myStick.update();
    myStick2.update();
    myStick.display();
    myStick2.display();
    requestAnimationFrame(updateGameArea); // request the next animation frame in 1/60th second
}



}
.nm{
    margin: 0;
    padding: 0;
}

canvas{
    display: block;
    background-color: lightgray;
}
<html>
    <head>
        <meta charset="UTF-8">
        <title>Canvas stick game!</title>
        <link rel="stylesheet" href="css/standard.css">
    </head>
    <body id="body" onload="startGame()" class="nm" style="height: 100vh">
        
    </body>
</html>

<script src="js/canvas.js"></script>
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • +1 For the updated code and comments explaining. Even though this did not fix the problem, since the problem has been located to a keyboard bug, which does not allow me to press both UP + RIGHT at the same time typing W + D – user1509104 Aug 06 '16 at 19:10
  • Why should i use "requestAnimationFrame" and not a loop? Since i tested your version and my version, the cubes on your canvas moves faster which must mean the function "updateGameArea" runs more often than 1/60 of a secund? – user1509104 Aug 06 '16 at 19:16
  • Are you sure your version was running at 1/60th of a second. The only way to have requestAnimationFrame run over 60fps is to use Browser specific flags to block sync display refresh. For more on requestAnimationFrame http://stackoverflow.com/a/38709924/3877726 – Blindman67 Aug 06 '16 at 19:26
  • @Blindman67: +1 for requestAnimationFrame as well as other 'modernizing.' – J. Allan Aug 06 '16 at 19:30
  • @Blindman67 Yes i quite sure since i made the timeout "1000 / 60" which should give me an experiance of 60 FPS. Maybe the reason for this change is that i have a 120 Hz monitor? – user1509104 Aug 06 '16 at 19:50
  • Yeah, i changed my monitor refresh rate down to 60 which made it move at the same speed as my own. I tried counting in my head, at 60 Hz both took around 4.5 sec and in 120 Hz my own took around 4.5 sec and yours around 2.5 which is almost double the speed. (Remember i was counting on my own) (The time was the time it took to move from the left side to the right side) – user1509104 Aug 06 '16 at 19:53
  • Odd i am running 120Hz and getting 60fps from requestAnimationFrame (RAF). You can dial a frame rate by using the callback's time argument `updateGameArea(timeSinceLoad) {` (timeSinceLoad is the time in milliseconds since page load and has accuracy to 1/1000000th second 0.001) to drop any frames over speed. – Blindman67 Aug 06 '16 at 20:06
  • @user1509104 this answer http://stackoverflow.com/a/38032486/3877726 includes details on how to fix frame rate using requestAnimationFrame. Using setInterval has many issues, you can use it if you are aware of the problems that can result. – Blindman67 Aug 06 '16 at 20:12
2

I know this is redundant (anyone who reads the comments below your question will figure this out), but just so it's here as an answer, your problem is a keyboard bug.

Because of the way (some) non-digital keyboards work, certain keyboard combinations do not work properly. (ie. Certain keys will cancel each other out.)

(This is just another reason to supply customizable game controls, customizable program shortcuts, etc. Another reason is the fact that people with DVORAK keyboards are likely to find your QWERTY optimized game controls to be cumbersome.)


Other:

PS: I cannot reproduce your problem, and if you tried it on another computer, it is likely that you could not reproduce it either.

PPS: For more information check out the following article: Keyboards are evil.

J. Allan
  • 1,418
  • 1
  • 12
  • 23