Mr. Greenwoodz
Mr. Greenwoodz

Reputation: 255

HTML5 canvas draw a rectangle with gelatine effect

I'm creating a platformer game in javascript in which players are rectangles and they can jump on and off platforms. This is pretty straightforward but now I'm trying to add a 'gelatine effect' to the players so that when they land on a platform they move a bit(like a gelatine). I've been searching for quite some time now but I can't seem to find any good examples on how I can change the shape of a rectangle.

All I've come up with is by using @keyframes in css and i've tried implementing it, which works if I use it on html elements. This is because with the DOM elements I can access the CSSStyleDeclaration with .style but if I create a new player object I can't(as seen in my code below).

I'm not sure how I can convert the css @keyframes to javascript or if what i'm doing isn't possible.. or if there perhaps are other (better) ways to achieve my goal?

var keystate = [];
var players = [];

var jelly = document.getElementById('jelly');
console.log(jelly.style); // shows the CSSStyleDeclarations

document.body.addEventListener("keydown", function(e) {
  keystate[e.keyCode] = true;
});

document.body.addEventListener("keyup", function(e) {
  keystate[e.keyCode] = false;
});

function gelatine(e) {
  if (e.style.webkitAnimationName !== 'gelatine') {
    e.style.webkitAnimationName = 'gelatine';
    e.style.webkitAnimationDuration = '0.5s';
    e.style.display = 'inline-block';

    setTimeout(function() {
      e.style.webkitAnimationName = '';
    }, 1000);
  }
}

var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 300;

function Player(x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  this.jumping = false;
  this.velocityY = 0;
  this.gravity = 0.3;
  this.speed = 5;
  this.timer = 0;
  this.delay = 120;
}

Player.prototype.render = function render() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.fillStyle = 'blue';
  context.fillRect(this.x, this.y, this.width, this.height);
  context.fillStyle = 'black';
  context.font = '20pt sans-serif';
  context.fillText("I'm not jelly:(", this.x - 160, this.y + 30);
};

Player.prototype.update = function update() {
  // arrow up key to jump with the player
  if (keystate[38]) {
    if (!this.jumping) {
      this.jumping = true;
      this.velocityY = -this.speed * 2;
    }
  }

  this.velocityY += this.gravity;
  this.y += this.velocityY;

  if (this.y >= canvas.height - this.height) {
    this.y = canvas.height - this.height;
    this.jumping = false;
  }

  if (this.timer === 0) {
    gelatine(jelly);
    this.timer = this.delay;
  }
  if (this.timer > 0 && this.timer <= this.delay) {
    this.timer--;
  }
};

players.push(new Player((canvas.width / 2) - 25, (canvas.height / 2), 50, 50));

console.log(players[0].style); // no CSSStyleDeclarations :(
function render() {
  for (var i = 0; i < players.length; i++) {
    players[i].render();
  }
}

function update() {
  for (var i = 0; i < players.length; i++) {
    players[i].update();
  }
}

function tick() {
  update();
  render();

  requestAnimationFrame(tick);
}
tick();
<html>

<head>
  <style>
    #jelly {
      position: absolute;
      margin-left: auto;
      margin-right: auto;
      top: 80px;
      bottom: 0;
      left: 0;
      right: 0;
      display: inline-block;
      width: 100px;
      height: 100px;
      background: blue;
    }
    p {
      font-size: 20px;
      color: white;
    }
    canvas {
      border: 1px solid #000;
      position: absolute;
      margin: auto;
      top: 20px;
      bottom: 0;
      left: 0;
      right: 0;
    }
    @keyframes gelatine {
      25% {
        -webkit-transform: scale(0.9, 1.1);
        transform: scale(0.9, 1.1);
      }
      50% {
        -webkit-transform: scale(1.1, 0.9);
        transform: scale(1.1, 0.9);
      }
      75% {
        -webkit-transform: scale(0.95, 1.05);
        transform: scale(0.95, 1.05);
      }
    }
  </style>
  <title>Jelly rectangle</title>
</head>

<body>
  <div id="jelly">
    <p>&nbsp&nbsp i'm jelly :)</p>
  </div>
  <canvas id='canvas'></canvas>

</body>

</html>

Upvotes: 0

Views: 1272

Answers (1)

Blindman67
Blindman67

Reputation: 54069

Jelly.

To create a jelly effect for using in a game we can take advantage of a step based effect that is open ended (ie the effect length is dependent on the environment and has no fixed time)

Jelly and most liquids have a property that they are incompressible. This means that no matter the force applied to them the volume does not change. For a 2D game this mean that for jelly the area does not change. This means we can squash the height and knowing the area calculate the width.

Thus when a object drops and hits the ground we can apply a squashing force to the object in the height direction. Simulating a dampened spring we can produce a very realistic jelly effect.

Defining the jelly

I am being a little silly as it is the season so the variables wobbla and bouncy define the dampened spring with wobbla being the stiffness of the spring from 0 to 1, with 0.1 being very soft to 1 being very stiff. Bouncy being the damping of the spring from 0 to 1, with 0 having 100% damping and 1 having no damping.

I have also added react which is a simple multiplier to the force applied to the spring. As this is a game there is a need to exaggerate effects. react multiplies the force applied to the spring. Values under 1 reduce the effect, values over 1 increase the effect.

hr and hd (yes bad names) are the real height (displayed) and the height delta ( the change in height per frame). These two variable control the spring effect.

There is a flag to indicate that the jelly is on the ground and the sticky flag if true keeps the jelly stuck to the ground.

There are also three functions to control the jelly

function createJellyBox(image,x, y, wobbla, bouncy, react){
    return {
        img:image,
        x : x,                 // position
        y : y,
        dx : 0,                // movement deltas
        dy : 0, 
        w : image.width,       // width 
        h : image.height,      // height
        area : image.width * image.height,  // area 
        hr : image.height,     // squashed height chaser
        hd : 0,                // squashed height delta
        wobbla : wobbla,       // higher values make it wobble more
        bouncy : bouncy,       // higher values make it react to change in speed
        react : react,         // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction
        onGround : false,      // true if on the ground
        sticky : true,         // true to stick to the ground. false to let it bounce
        squashed : 1,          // the amount of squashing or stretching along the height
        force : 0,             // the force applied to the jelly when it hits the ground
        draw : drawJelly,      // draw function
        update : updateJelly,  // update function
        reset : resetJelly,    // reset function
    }
}

The functions

There are three functions to control the jelly, reset, update and draw. Sorry the answer is over the 30000 character limit so find the functions in the demo below.

Draw

Draw simply draws the jelly. It uses the squashed value to calculate the height and from that calculates the width, then simply draws the image with the set width and height. The image is drawn at its center to keep calculations simpler.

Reset

Simply resets the jelly at a position defined by x and y you can modify it to remove any wobbles by setting jelly.hr = jelly.h and jelly.hd = 0

Update

This is where the hard work is. It updates the object position by adding gravity to the delta Y. It checks if the jelly has hit the ground, if it has it applies the force to the jelly.hr spring by adding to the jelly.hd (height delta) the force equal to the speed of the inpact times jelly.react

I found that the force applied should be over time so there is a little cludge (marked with comments) to apply a squashing force over time. That can be removed but it just reduces the smoothness of the jelly wobbly effect.

Last thing is to recalculate the height and move the jelly so that it does not cross into the ground.

Clear as mud cake I am sure. So feel free anyone to ask questions if there is a need to clarify.

DEMO

What would any good SO answer be without a demo. So here is a jelly simulation. Click on the canvas to drop the jelly. The three sliders on the top left control the values Wobbla, Bouncy, and React. Play witht the values to change the jelly effect. The check box turn on and off sticky, but you need a long screen to get the impact you need to bounce up. Remove the cludge code to see sticky real purpose.

As I needed a UI there is a lot of extra code that does not apply to the answer. The Answer code is clearly marked so to be easy to find. The main loop is at the bottom. I have not yet tested it on FF but it should work. If not let me know and I will fix it.

var STOP = false; // stops the app
var jellyApp = function(){

    /** Compiled by GROOVER Quick run 9:43pm DEC-22-2015 **/   
    /** fullScreenCanvas.js begin **/
    var canvas = (function(){
        var canvas = document.getElementById("canv");
        if(canvas !== null){
            document.body.removeChild(canvas);
        }
        // creates a blank image with 2d context
        canvas = document.createElement("canvas"); 
        canvas.id = "canv";    
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight; 
        canvas.style.position = "absolute";
        canvas.style.top = "0px";
        canvas.style.left = "0px";
        canvas.style.zIndex = 1000;
        canvas.ctx = canvas.getContext("2d"); 
        document.body.appendChild(canvas);
        return canvas;
    })();
    var ctx = canvas.ctx;
    /** fullScreenCanvas.js end **/
    /** MouseFull.js begin **/
    var canvasMouseCallBack = undefined;  // if needed
    var mouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
            interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
            over : false,  // mouse is over the element
            bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
            getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
            startMouse:undefined,
        };
        function mouseMove(e) {
            var t = e.type, m = mouse;
            m.x = e.offsetX; m.y = e.offsetY;
            if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
            m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
            if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
            } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
            } else if (t === "mouseover") { m.over = true;
            } else if (t === "mousewheel") { m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
            if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); }
            e.preventDefault();
        }
        function startMouse(element){
            if(element === undefined){
                element = document;
            }
            "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach(
            function(n){element.addEventListener(n, mouseMove);});
        }
        mouse.mouseStart = startMouse;
        return mouse;
    })();
    if(typeof canvas === "undefined"){
        mouse.mouseStart(canvas);
    }else{
        mouse.mouseStart();
    }
    /** MouseFull.js end **/





    // unit size for rendering scale
    var ep = canvas.height / 72;
    // font constants
    const FONT = "px Arial Black";
    const FONT_SIZE = Math.ceil(3 * ep);

    const GRAVITY = ep * (1/5);
    const GROUND_AT = canvas.height - canvas.height * (1/20);


    // Answer code.
    //-----------------------------------------------------------------------        // draw the jelly
    function drawJelly(ctx){
        var w,h;
        w = this.w,
        h = this.h;
        h *= this.squashed;
        // the width is ajusted so that the area of the rectagle remains constant
        w = this.area / h; // to keep the area constant
        ctx.drawImage(this.img,this.x - w / 2, this.y - h / 2, w, h);
    }
    function resetJelly(x,y){  // reset the jelly position 
        this.x = x;
        this.y = y;
        this.onGround = false;
        this.dx = 0;
        this.dy = 0;
    }
    // do the jelly math
    function updateJelly(){
        var h;                          // temp height
        var hitG = false;               // flag that the ground has just been hit
        h = this.h * this.squashed;     // get the height from squashed
        if(!this.onGround){             // if not on the ground add grav
            this.dy += GRAVITY; 
        }else{                          // if on the ground move it so it touches correctly
            this.dy = 0;                
            this.y = GROUND_AT - h / 2;
        }
        // update the position
        this.x += this.dx;
        this.y += this.dy;

        // check if it has hit the ground
        if(this.y + h / 2 >= GROUND_AT && this.dy >= 0){
            this.hd += this.dy * this.react;  // add the hit speed to the height delta
                                              // multiply with react to inhance or reduce effect
            this.force += this.dy * this.react;
            hitG = true;
            this.onGround = true;             // flag that jelly is on the ground
        }
        if(this.force > 0){
            this.hd += this.force;
            this.force *= this.wobbla;
        }
        this.hd += (this.h - this.hr) * this.wobbla;  // add wobbla to delta height
        this.hd *= this.bouncy;                       // reduce bounce
        this.hr += this.hd;                           // set the real height
        this.squashed = this.h / this.hr;             // calculate the new squashed amount
        h = this.h * this.squashed;                   // recalculate hieght to make sure
                                                      // the jelly does not overlap the ground
        // do the finnal position ajustment to avoid overlapping the ground                                              
        if(this.y + h / 2 >= GROUND_AT || hitG || (this.sticky && this.onGround)){
            this.y = GROUND_AT - h / 2;
        }
    }

    // create a jelly box
    function createJellyBox(image,x, y, wobbla, bouncy, react){
        return {
            img:image,
            x : x,                 // position
            y : y,
            dx : 0,                // movement deltas
            dy : 0, 
            w : image.width,       // width 
            h : image.height,      // height
            area : image.width * image.height,  // area 
            hr : image.height,     // squashed height chaser
            hd : 0,                // squashed height delta
            wobbla : wobbla,       // higher values make it wobble more
            bouncy : bouncy,       // higher values make it react to change in speed
            react : react,         // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction
            onGround : false,      // true if on the ground
            sticky : true,         // true to stick to the groun. false to let it bounce
            squashed : 1,          // the amount of squashing or streaching along the height
            force : 0,
            draw : drawJelly,      // draw function
            update : updateJelly,  // update function
            reset : resetJelly,    // reset function
        }
    }

    // --------------------------------------------------------------------------------------------
    // END OF ANSWER CODE.
    // FIND the usage at the bottom inside the main loop function.
    // --------------------------------------------------------------------------------------------




    // The following code is just helpers and UI stuff and are not related to the answer.

    var createImage = function (w, h ){
        var image = document.createElement("canvas");
        image.width = w;
        image.height = h;
        image.ctx = image.getContext("2d");
        return image;
    }

    var drawSky = function (img, col1, col2){
        var c, w, h;
        c = img.ctx;
        w = img.width;
        h = img.height;
        var g = c.createRadialGradient(w * (1 / 2), h * (3 / 2), h * (1 / 2) +w * (1 / 4), w * (1 / 2), h * (3 / 2), h * (3 / 2) + w * ( 1 / 4));
        g.addColorStop(0,col1);
        g.addColorStop(1,col2);
        c.fillStyle = g;
        c.fillRect(0, 0, w, h);
        return img;
    }
    var drawGround = function (img, col1, col2,lineColour, lineWidth) {
        var c, w, h, lw;
        c = img.ctx;
        w = img.width;
        h = img.height;
        lw = lineWidth;
        var g = c.createLinearGradient(0, 0, 0, h + lineWidth);
        g.addColorStop(0, col1);
        g.addColorStop(1, col2);
        c.fillStyle = g;
        c.lineWidth = lw;
        c.strokeStyle = lineColour;
        c.strokeRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4);
        c.fillRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4);
        return img;
    }
    /** CanvasUI.js begin **/
    var drawRoundedBox = function (img, colour, rounding, lineColour, lineWidth) {
        var c, x, y, w, h, r, p
        p = Math.PI/2; // 90 deg
        c = img.ctx;
        w = img.width;
        h = img.height;
        lw = lineWidth;
        r = rounding ;
        c.lineWidth = lineWidth;
        c.fillStyle = colour;
        c.strokeStyle = lineColour;
        c.beginPath();
        c.arc(w - r - lw / 2, h - r - lw / 2, r, 0, p);
        c.lineTo(r + lw / 2, h - lw / 2);
        c.arc(r + lw / 2, h - r - lw / 2, r, p, p * 2);
        c.lineTo(lw / 2, h - r - lw / 2);
        c.arc(r + lw / 2, r + lw / 2, r, p * 2, p * 3);
        c.lineTo(w-r - lw / 2,   lw / 2);
        c.arc(w - r - lw / 2, r + lw / 2, r, p * 3, p * 4);
        c.closePath();
        c.stroke();
        c.fill();
        return img;
    }
    var drawTick = function (img , col, lineColour, lineWidth){
        var c, w, h, lw, m, l;
        m = function (x, y) {c.moveTo(lw / 2 + w * x, lw / 2 + h * y);};
        l = function (x, y) {c.lineTo(lw / 2 + w * x, lw / 2 + h * y);};
        lw = lineWidth;
        c = img.ctx;
        w = img.width - lw;
        h = img.height - lw;
        c.fillStyle = col;
        c.strokeStyle = lineColour;
        c.lineWidth = lw;
        c.beginPath();
        m(1, 0);
        l(5 / 8, 1);
        l(0, 3 / 4);
        l(1 / 4, 2 / 4);
        l(2 / 4, 3 / 4);
        l(1, 0);
        c.stroke();
        c.fill();
        return img;
    }
    var setFont = function(ctx,font,align){
        ctx.font = font;
        ctx.textAlign = align;
    }
    var measureText = function(ctx,text){
        return ctx.measureText(text).width;
    }
    var drawText = function(ctx,text,x,y,col,col1){
        var of;
        of = Math.floor(FONT_SIZE/10);
        ctx.fillStyle = col1;
        ctx.fillText(text,x+of,y+of);
        ctx.fillStyle = col;
        ctx.fillText(text,x,y);
    }
    var drawSlider = function(ctx){
        var x,y;
        x = this.owner.x;
        y = this.owner.y;
        ctx.drawImage(this.image, this.x + x, this.y + y);
        ctx.drawImage(this.nob, this.nx + x, this.ny + y);
    }
    var updateSlider = function(mouse){
        var mx, my;
        mx = mouse.x - this.owner.x;
        my = mouse.y - this.owner.y;
        this.cursor = "";
        
        if(this.owner.dragging === -1 || this.owner.dragging === this.id ){
            if(mx >= this.x && mx < this.x + this.w &&
                my >= this.y && my <= this.y + this.h){
                this.mouseOver = true;       
                this.cursor = "pointer"
            }else{
                this.mouseOver = false;
            }
            if(mx >= this.nx && mx < this.nx + this.nw &&
                my >= this.ny && my <= this.ny + this.nh){
                this.mouseOverNob = true;       
                this.cursor = "ew-resize"
            }else{
                this.mouseOverNob = false;
            }
            if((mouse.buttonRaw&1) === 1 && (this.mouseOver||this.mouseOverNob) && !this.dragging){
                this.owner.dragging = this.id;
                this.dragging = true;
                this.cursor = "ew-resize"
            }else
            if(this.dragging){
                this.cursor = "ew-resize"
                if((mouse.buttonRaw & 1)=== 0){
                    this.dragging = false;
                    this.owner.dragging = -1;
                    this.cursor = "pointer";
                }
                var p = mx- (this.x+this.nw/2);
                p /= (this.w-this.nw);
                p *= this.range;
                p += this.min;
                this.value = Math.min(this.max, Math.max(this.min, p));
            }
            if(this.mouseOver || this.mouseOverNob || this.dragging){
                this.owner.toolTip = this.toolTip.replace("##",this.value.toFixed(this.decimals));
            }

        }

        this.nx = (this.value - this.min) / this.range * (this.w - this.nw) + this.x;
    }
    var createSlider = function(image,nobImage, x, y, value, min, max, toolTip)   {
        var decimals = 0;
        if(toolTip.indexOf("#.") > -1){
            if(toolTip.indexOf(".DDD") > -1){
                decimals = 3;    
                toolTip = toolTip.replace("#.DDD","##");
            }else
            if(toolTip.indexOf(".DD") > -1){
                decimals = 2;    
                toolTip = toolTip.replace("#.DD","##");
            }else
            if(toolTip.indexOf(".D") > -1){
                decimals = 1;    
                toolTip = toolTip.replace("#.D","##");
            }else{
                toolTip = toolTip.replace("#.","##");
            }
        }
        return {
            id : undefined,
            image : image,
            nob : nobImage,
            min : min,
            max : max,
            x : x,
            y : y + (nobImage.height - image.height)/2,
            ny : y  ,
            nx : ((value - min) / (max - min)) * (image.width - nobImage.width) + x,
            range : max - min,
            w : image.width,
            h : image.height,
            nw : nobImage.width,
            nh : nobImage.height,
            value : value,
            maxH : Math.max( image.height, nobImage.height),
            mouseOver : false,
            mouseOverNob : false,
            toolTip:toolTip,
            decimals:decimals,
            dragging : false,
            update : updateSlider,
            draw : drawSlider,
            position : function (x, y){
                this.x += x;
                this.y += y;
                this.nx += x;
                this.ny += y;
            },
        }
    }
    var drawTickCont = function(ctx){
        var x,y, ofx, ofy;
        x = this.owner.x;
        y = this.owner.y;
        ctx.drawImage(this.image, this.x + x, this.y + y);
        ofy = this.h / 2 - this.textImage.height / 2;
        ofx = this.w / 2;
        ctx.drawImage(this.textImage, this.x + x + this.w + ofx, this.y + y + ofy);    
        if(this.value){
            x -= this.tickImage.width * ( 1/ 4);
            y -= this.tickImage.height * ( 2/  5);
            ctx.drawImage(this.tickImage, this.x + x, this.y + y);
        }
    }
    var updateTick = function(mouse){
        var mx, my;
        mx = mouse.x - this.owner.x;
        my = mouse.y - this.owner.y;
        this.cursor = "";
        if(this.owner.dragging === -1 || this.owner.dragging === this.id ){
            if(mx >= this.x && mx < this.x + this.w &&
                my >= this.y && my <= this.y + this.h){
                this.mouseOver = true;       
                this.cursor = "pointer"
            }else{
                this.mouseOver = false;
            }
            if((mouse.buttonRaw&1) === 1 && this.mouseOver && !this.dragging){
                this.owner.dragging = this.id;
                this.dragging = true;
            }else
            if(this.dragging){
                if((mouse.buttonRaw & 1)=== 0){
                    if(this.mouseOver){
                        this.value = ! this.value;
                    }
                    this.dragging = false;
                    this.owner.dragging = -1;
                    this.cursor = "pointer";
                }
            }
            if(this.mouseOver || this.dragging){
                this.owner.toolTip = this.toolTip;
            }        
        }
    }
    var createTick= function(image,tickImage,textImage, x, y, value, toolTip)   {
        return {
            id : undefined,
            image : image,
            tickImage : tickImage,
            textImage : textImage,
            x : x,
            y : y,
            w : image.width,
            h : image.height,
            value : value,
            maxH : Math.max( image.height, tickImage.height),
            mouseOver : false,
            mouseOverNob : false,
            toolTip:toolTip,
            dragging : false,
            update : updateTick,
            draw : drawTickCont,
            position : function (x, y){
                this.x += x;
                this.y += y;
            },
        }
    }
    function UI(ctx, mouse, x, y){
        this.dragging = -1;
        var ids = 0;
        var controls = [];
        var length = 0;
        this.x = x;
        this.y = y;
        var posX = 0;
        var posY = 0;
        this.addControl = function (control, name) {
            control.id = ids ++;
            control.owner = this;
            control.position(posX, posY);
            posY += control.maxH + ep;
            length = controls.push(control)
            this[name] = control;
        }
        this.update = function(){
            var i, cursor, c;
            cursor = "";
            this.toolTip = "";
            for(i = 0; i < length; i ++){
                c = controls[i];
                c.update(mouse);
                if(c.cursor !== ""){
                    cursor = c.cursor;
                }
                c.draw(ctx);
            }
            if(cursor === ""){
                ctx.canvas.style.cursor = "default";
            }else{
                ctx.canvas.style.cursor = cursor;
            }
            if(this.toolTip !== ""){
                if(mouse.y - FONT_SIZE * (5 / 3) < 0){
                    drawText(ctx, this.toolTip, mouse.x, mouse.y + FONT_SIZE * (4 / 3), "#FD4", "#000");
                }else{
                    drawText(ctx, this.toolTip, mouse.x, mouse.y - FONT_SIZE * (2 / 3), "#FD4", "#000");
                }
            }
        }
    }
    /** CanvasUI.js end **/

    //-----------------------------
    // create UI
    //-----------------------------

    setFont(ctx, FONT_SIZE + FONT, "left");
    // images for UI
    var tickBox = drawRoundedBox(createImage(4 * ep, 4 * ep), "#666", (3 / 2) * ep, "#000", (1 / 2) * ep);
    var tickBoxTick = drawTick(createImage(6 * ep, 6 * ep), "#0D0", "#000", (1 / 2) * ep);
    var w = measureText(ctx, "Sticky");
    var tickBoxText = createImage(w, FONT_SIZE);
    setFont(tickBoxText.ctx, FONT_SIZE + FONT, "left");
    drawText(tickBoxText.ctx, "Sticky", 0, FONT_SIZE * (3 / 4), "white", "black");

    var sliderBar = drawRoundedBox(createImage(20 * ep, 2 * ep), "#666", ep * 0.9, "#000", (1 / 2) * ep);
    var sliderNob = drawRoundedBox(createImage(3 * ep, 4 * ep), "#AAA", ep, "#000", (1 / 2) * ep);
    // UI control
    var controls = new UI(ctx, mouse, 10, 10);
    controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.3, 0, 1, "Wobbla #.DD"), "wobbla");
    controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.8, 0, 1, "Bouncy #.DD"), "bouncy");
    controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 1.5, 0, 2, "React #.DD"), "react");
    controls.addControl(createTick(tickBox, tickBoxTick, tickBoxText, 0, 0, true, "Activate / Deactivate sticky option."), "sticky");
    //-----------------------------
    // create playfield 
    //-----------------------------
    var skyImage = drawSky(createImage(32, 32), "#9EF", "#48D");
    var groundImage = drawGround(createImage(100, canvas.height - GROUND_AT), "#5F5", "#5A5", "#181", 4);

    //-----------------------------
    // create jelly 
    //-----------------------------
    var jellyImage = drawRoundedBox(createImage(ep * 20, ep * 20), "#FA4", ep * 2, "black", ep * (3 / 5));
    var jelly = createJellyBox(
            jellyImage,
            canvas.width / 2,
            100,
            0.1,
            0.8,
            1.2
    );

    // some more settings
    ctx.imageSmoothingEnabled = true;
    setFont(ctx, FONT_SIZE + FONT, "left");

    //-----------------------------
    // Main animtion loop
    //-----------------------------
    function updateAnim(){
        // draw the background and ground
        ctx.drawImage(skyImage, 0, 0, canvas.width, canvas.height)
        ctx.drawImage(groundImage, 0, GROUND_AT, canvas.width, canvas.height - GROUND_AT)
        // update and draw jelly
        jelly.update();
        jelly.draw(ctx);
        // update and draw controls
        controls.update();
        // update jelly setting from controls.
        jelly.wobbla = controls.wobbla.value;
        jelly.bouncy = controls.bouncy.value;
        jelly.react = controls.react.value;
        jelly.sticky = controls.sticky.value;

        // if the mouse is not busy then use left mouse click and drag to position jellu
        if(controls.dragging < 0 && mouse.buttonRaw === 1){
            controls.dragging = -2;
            jelly.reset(mouse.x,mouse.y);
        }else
        if(controls.dragging === -2){   // release mouse
            controls.dragging = -1;
        }
        if(!STOP){
            requestAnimationFrame(updateAnim);
        }else{
          STOP = false;
        }
    }
    updateAnim();
};
function resizeEvent(){
    var waitForStopped = function(){
        if(!STOP){  // wait for stop to return to false
            jellyApp();
            return;
        }
        setTimeout(waitForStopped,200);
    }
    STOP = true;
    setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
jellyApp();

Upvotes: 3

Related Questions