Search code examples
javascripthtmlimagecanvaskeypress

How to move image on canvas using arrow keys in javascript


I've tried a few different ways that I have seen on here, but I can't quite get my image to move. Whenever I try adapting code for arrow key presses, it just seems to make my canvas shrink and my player model (spaceperson) disappear.

here is the "drawing board" I keep returning to, and what I have so far.

// Get the canvas and context
var canvas = document.getElementById("space"); 
var ctx = canvas.getContext("2d");
canvas.width = 1920;
canvas.height = 700;


// Create the image object
var spaceperson = new Image();

// Add onload event handler
spaceperson.onload = function () {
   // Done loading, now we can use the image
   ctx.drawImage(spaceperson, 280, 300);
};


// artwork by Harrison Marley (using make8bitart.com)
spaceperson.src = "http://i.imgur.com/Eh9Dpq2.png";`

I am quite new to javascript, and I am just trying to work out how I can move the specperson image using arrow keys. I was trying to make a class for space person to access their x,y values, but I can't seem to draw the image without using .onload


Solution

  • here a more complete example:

    //just a utility
    function image(url, callback){
        var img = new Image();
        if(typeof callback === "function"){
            img.onload = function(){
                //just to ensure that the callback is executed async
                setTimeout(function(){ callback(img, url) }, 0)
            }
        }
        img.src = url;
        return img;
    }
    
    //a utility to keep a value constrained between a min and a max
    function clamp(v, min, max){
        return v > min? v < max? v: max: min;
    }
    
    //returns a function that can be called with a keyCode or one of the known aliases 
    //and returns true||false wether the button is down
    var isKeyDown = (function(aliases){
        for(var i=256, keyDown=Array(i); i--; )keyDown[i]=false;
        var handler = function(e){ 
            keyDown[e.keyCode] = e.type === "keydown";
            e.preventDefault();  //scrolling; if you have to suppress it
        };
    
        addEventListener("keydown", handler, false);
        addEventListener("keyup", handler, false);
    
        return function(key){
            return(true === keyDown[ key in aliases? aliases[ key ]: key ])
        }
    })({
        //some aliases, to be extended
        up: 38,
        down: 40,
        left: 37,
        right: 39
    });
    
    
    
    // Get the canvas and context
    var canvas = document.getElementById("space"); 
    canvas.width = 1920;
    canvas.height = 700;
    var ctx = canvas.getContext("2d");
    
    //the acutal image is just a little-part of what defines your figue
    var spaceperson = {
        image: image("//i.imgur.com/Eh9Dpq2.png", function(img){ 
            spaceperson.width = img.naturalWidth;
            spaceperson.height = img.naturalHeight;
    
            //start the rendering by calling update
            update();
        }),
    
        //position
        x: 60, y: 310,
        width: 0, height: 0,
    
        speed: 200  // 200px/s
    };
    
    var lastCall = 0;  //to calculate the (real) time between two update-calls
    //the render-fucntion
    function update(){
        //taking account for (sometimes changing) framerates
        var now = Date.now(), time = lastCall|0 && (now-lastCall)/1000;
        lastCall = now;
        requestAnimationFrame(update);
    
        var sp = spaceperson,
            speed = sp.speed;
    
        //checking the pressed buttons and calculates the direction
        //two opposite buttons cancel out each other, like left and right
        var dx = (isKeyDown('right') - isKeyDown('left')) * time, 
            dy = (isKeyDown('down') - isKeyDown('up')) * time;
    
        //fix the speed for diagonals
        if(dx && dy) speed *= 0.7071067811865475;   // * 1 / Math.sqrt(2)
    
        if(dx) { //there is some movement on the x-axes
            sp.x = clamp(
                //calculate the new x-Position
                //currentPos + direction * speed
                sp.x + dx * sp.speed, 
    
                //restraining the result to the bounds of the map
                0, canvas.width - sp.width
            );
        }
    
        //same for y
        if(dy) sp.y = clamp(sp.y + dy * sp.speed, 0, canvas.height - sp.height);
    
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(sp.image, sp.x, sp.y);
    }
    

    Edit:

    A quick question (I hope); if I was to later add other objects, would I check for collisions in update()?

    This is still just a very basic example. The main purpose of the update()-function should be to work as the main event-loop. To trigger all Events that have to happen each frame in the order they have to happen.

    var lastCall = 0;
    function update(){
        //I always want a next frame
        requestAnimationFrame(update);
    
        //handle timing
        var now = Date.now(), 
            //time since the last call in seconds
            //cause usually it's easier for us to think in 
            //tems like 50px/s than 0.05px/ms or 0.8333px/frame
            time = lastCall|0 && (now-lastCall) / 1000;
    
        lastCall = now;
    
        movePlayer(time);
        moveEnemies(time);
        moveBullets(time);
    
        collisionDetection();
    
        render();
    }
    
    function render(){
        ctx.clear(0, 0, canvas.width, canvas.height);
        drawBackground(ctx);
        for(var i=0; i<enemies.length; ++i)
            enemies[i].render(ctx);
        player.render(ctx);
    }
    

    Not saying that you have to implement all these functions now, but to give you an idea of a possible structure. Don't be scared to break big tasks (functions) up into subtasks.
    And it might make sense to give each enemy a move()-function so you can implement different movement-patterns per enemy, or you say that the pattern is (and will be) all the same for each enemy, parameterized at the best, then you can handle that in a loop.
    Same thing for rendering, as I'm showing in the last part of code.