I know in traditional HTML5 canvas, we can use drawImage method (the longest one with 9 properties) and change frameX and frameY to make sprite sheet animation. But I am new to matter.js. I've checked matter.js document but still don't have any idea about how to animate my sprite. Here is my object:
const ball = Bodies.circle(340, 340, 10, {
density: 0.0005,
frictionAir: 0.06,
restitution: 0,
friction: 0,
render: {
sprite: {
texture: "images/blueMonster.png",
yScale: 0.2,
xScale: 0.2,
isStatic: true,
},
},
inertia: Infinity,
label: "ball",
});
World.add(world, ball);
If I need to provide more info to solve this problem, please let me know. Thank you very much for your time!
There may be a fundamental misconception here. Matter.js is a physics engine that can plug into any rendering front-end. You don't have to use the built-in MJS rendering engine which is primarily there for prototyping. You can use your existing HTML5 code or something like Phaser which has robust support for sprite sheets.
Here's a simple proof-of-concept using vanilla JS to render a sprite animation with MJS as the physics engine. The approach is to call Matter.Engine.update(engine); to run the engine each frame and use coin.position to draw the sprite. More complex animations might use vertices and angle as shown here and here in addition to the sprite sheet, but this is use-case dependent.
(async () => {
const image = await new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = "https://art.pixilart.com/c7f297523ce57fc.png";
});
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 250;
const engine = Matter.Engine.create();
const coin = Matter.Bodies.circle(100, 0, 100, {
density: 0.0005,
frictionAir: 0.06,
restitution: 0,
friction: 0,
});
const ground = Matter.Bodies.rectangle(
0, 350, 1500, 170, {isStatic: true}
);
const mouseConstraint = Matter.MouseConstraint.create(
engine, {element: canvas}
);
Matter.Composite.add(
engine.world, [coin, ground, mouseConstraint]
);
const w = 200;
const h = 170;
let frameNumber = 0;
(function rerender() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const offset = (~~frameNumber * w) % image.width;
const {x, y} = coin.position;
ctx.drawImage(
image, // image
offset, // sx
40, // sy
w, // sWidth
h, // sHeight
x - w / 2, // dx
y - h / 2, // dy
w, // dWidth
h // dHeight
);
frameNumber += 0.1;
Matter.Engine.update(engine);
requestAnimationFrame(rerender);
})();
})();
canvas {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<canvas></canvas>
Another approach is to plop in a gif or video--easily done since you're just making a webpage in the DOM that happens to have physics attached. See how to round an image in matter.js for an example.
Related
In a html canvas, I am trying to generate a drop shadow on an image with transparent pieces in it. This image is generated by code and then drawn to the canvas using: ctx.putImageData(dst, 0, 0)
The problem is that the following code is not generating any shadow:
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 15;
ctx.shadowColor = 'rgba(0,0,0,1)';
ctx.putImageData(dst, 0, 0);
Any help would be appreciated
ctx.putImageData() will replace the pixels in your context with the ones contained in the ImageData that you puts.
There is no context's property like shadowBlur, nor filter, nor globalCompositeOperation, nor even matrix tranforms that will affect it. Even transparent pixels in your ImageData will be transparent in the context.
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'salmon';
ctx.fillRect(0,0,300,150);
ctx.translate(120, 50);
ctx.rotate(Math.PI/3);
ctx.translate(-25, -25);
ctx.filter = 'blur(5px)';
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = '#0000FF';
ctx.fillRect(0,0,50,50);
setTimeout(() => {
// at this time, all previous filters, transform, gCO are still active
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
ctx.putImageData(bluerect, 0, 0); // same as our previous fillRect();
// a transparent ImageData (smaller)
const transrect = ctx.createImageData(25, 25);
ctx.putImageData(transrect, 170, 50); // push a bit farther;
}, 1500);
body {
background: lightblue;
}
<canvas id="canvas"></canvas>
So, how to deal with an ImageData and still be able to apply the context's properties on it?
Go through a second off-screen canvas, on which you will put your ImageData, and that you will then draw on your main canvas. drawImage accepts an HTMLCanvasElement as source, and it is affected by context properties like shadowBlur:
const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
// create a new canvas, the size of our ImageData
const offscreen = document.createElement('canvas');
offscreen.width = bluerect.width;
offscreen.height = bluerect.height;
// put our ImageData on it
offscreen.getContext('2d')
.putImageData(bluerect, 0, 0);
// draw it on main canvas
ctx.drawImage(offscreen, 50, 50);
<canvas id="canvas"></canvas>
Now, new browsers have also the ability to do it without the use of a second browser, by generating an ImageBitmap from the ImageData, but this operation is asynchronous, so you may still prefer the old way.
const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
// create an ImageBitmap from our ImageData
createImageBitmap(bluerect)
.then(bitmap => { // later
ctx.drawImage(bitmap, 50, 50);
});
<canvas id="canvas"></canvas>
I'm creating a 2D html5 game using canvas. I'm currently making the map who is very large (25000px x 25000px). I'm now trying to add textures to map shapes
Here is the code :
let snowPattern;
let sandPattern;
let junglePattern;
const jungleTexture = new Image();
jungleTexture.src = "./assets/game/map/jungleTexture.png";
jungleTexture.onload = function() {
junglePattern = ctx.createPattern(jungleTexture, "repeat");
};
const snowTexture = new Image();
snowTexture.src = "./assets/game/map/snowTexture.png";
snowTexture.onload = function() {
snowPattern = ctx.createPattern(snowTexture, "repeat");
};
const sandTexture = new Image();
sandTexture.src = "./assets/game/map/sandTexture.png";
sandTexture.onload = function() {
sandPattern = ctx.createPattern(sandTexture, "repeat");
};
//Function to draw map shapes
function animate() {
mapX = document.documentElement.clientWidth / 2 - camera.x * zoom;
mapY = document.documentElement.clientHeight / 2 - camera.y * zoom;
ctx.setTransform(1, 0, 0, 1, mapX, mapY);
//Arctic
if (document.getElementById("3").checked === true) {
ctx.fillStyle = snowPattern;
}
else {
ctx.fillStyle = "#EFF4F6";
}
ctx.fillRect(0, 0, 12500 * zoom, 12500 * zoom);
//Desert
if (document.getElementById("3").checked === true) {
ctx.fillStyle = sandPattern;
}
else {
ctx.fillStyle = "#E5C068";
}
ctx.fillRect(12499 * zoom, 0 * zoom, 12500 * zoom, 12500 * zoom);
//Jungle
if (document.getElementById("3").checked === true) {
ctx.fillStyle = junglePattern;
}
else {
ctx.fillStyle = "#0F7F2A";
}
ctx.fillRect(0, 12500 * zoom, 25000 * zoom, 12500 * zoom);
window.requestAnimationFrame(animate);
}
animate();
So when I only put colors on the background of the shapes, it's working perfectly (constent 144fps), but with patterns, my fps decrease to 20.
Does anyone have an idea about how can I improve the performances ?
You are trying to draw a massive rectangle and it creates an overhead which is expected. It depends on the browser but canvas has some limits. When you reach those limits, you will suffer performance or crash. You can see the limits here Also drawing a huge rectangle will always end with poor performance.
My suggestion would be: draw a smaller rectangle (probably a bit bigger than your game screen) and move it till end of the rectangle and just before pattern ends, move it back again.
Finally, the problem wasnt coming from the size, even with a 50px x 50px pixel rect, the performance was terrible. You need to use ctx.beginPath(), ctx.rect, ctx.closePath() and ctx.fill() to get normal performances.
I am trying to create a html application which is based on a canvas. The canvas elements should have a huge grid, lets say the size of the grid is 1500 x 700. This results in 1,050,000 cells (that's slightly over a million).
So the first question is essentially how do we render this efficiently. We did ofcourse try a very naive implementation of generating rectangles in a loop.
But the page gets stuck when you try and loop over a million times.
The next question would be that I need to load data from a server to render this grid. I was thinking of having a character represent a color in every single cell of the 1500 x 700 grid. If we were to limit the colors to about 20 (using a letter from the alphabet), the file size seems to be limited to around 1 MB, which is not a problem.
But again after loading this file, the question is how do we write it onto the canvas without causing performance issues.
Also this is sort of a lite version of https://pixelcanvas.io/ that we are trying to accomplish. Which admittedly can seemingly handle a million pixels (or cells) on screen.
How would one go about implementing this efficiently.
Use an ImageData to act as your grid, then can put it to a non-visible canvas and draw that non-visible canvas on your visible one, scaled as you wish.
// viewport size (canvas)
const vw = 500;
const vh = 500;
// image size (ImageData)
const iw = 2500;
const ih = 720;
// to handle the camera we use a DOMMatrix object
// which offers a few handful methods
const camera = new DOMMatrix();
const [ z_input, x_input, y_input ] = document.querySelectorAll( "[type='range']" );
[ z_input, x_input, y_input ].forEach( (elem) => {
elem.oninput = updateCamera;
} );
function updateCamera() {
const z = +z_input.value;
const x = +x_input.value;
const y = +y_input.value;
camera.a = camera.d = z;
camera.e = vw / 2 - (x * z);
camera.f = vh / 2 - (y * z);
draw();
}
const colorinput = document.querySelector( "input[type='color']" );
let color = colorinput.value = "#FF0000";
colorinput.oninput = (evt) => {
color = colorinput.value;
draw();
};
// the visible canvas
const canvas = document.querySelector( "canvas" );
canvas.width = vw;
canvas.height = vh;
const ctx = canvas.getContext( "2d" );
// we hold our pixel's data directly in an ImageData
const img = new ImageData( iw, ih );
// use a 32 bit view to access each pixel directly as a single value
const pixels = new Uint32Array( img.data.buffer );
// an other canvas, kept off-screen
const scaler = document.createElement( "canvas" );
// the size of the ImageData
scaler.width = iw;
scaler.height = ih;
const scalerctx = scaler.getContext( "2d" );
// fill with white, for demo
for(let i=0; i<pixels.length; i++) {
pixels[ i ] = 0xFFFFFFFF;
}
const mouse = { x: 0, y: 0, down: false };
canvas.onmousemove = (evt) => {
const canvasBBox = canvas.getBoundingClientRect();
// relative to the canvas viewport
const x = evt.clientX - canvasBBox.left;
const y = evt.clientY - canvasBBox.top;
// transform it by the current camera
const point = camera.inverse().transformPoint( { x, y } );
mouse.x = Math.round( point.x );
mouse.y = Math.round( point.y );
if( mouse.down ) {
addPixel( mouse.x, mouse.y, color );
}
draw();
};
canvas.onmousedown = (evt) => { mouse.down = true; };
document.onmouseup = (evt) => { mouse.down = false; };
function draw() {
// first draw the ImageData on the scaler canvas
scalerctx.putImageData( img, 0, 0 );
// reset the transform to default
ctx.setTransform( 1, 0, 0, 1, 0, 0 );
ctx.clearRect( 0, 0, vw, vh );
// set the transform to the camera
ctx.setTransform( camera );
// pixel art so no antialising
ctx.imageSmoothingEnabled = false;
// draw the image data, scaled on the visible canvas
ctx.drawImage( scaler, 0, 0 );
// draw the (temp) cursor
ctx.fillStyle = color;
ctx.fillRect( mouse.x, mouse.y, 1, 1 );
}
function addPixel( x, y, color ) {
const index = y * img.width + x;
if( index > 0 && index < pixels.length ) {
pixels[ index ] = parseColor( color );
}
}
function parseColor( str ) {
return Number( "0xFF" + str.slice(1).match(/.{2}/g).reverse().join("") );
}
// initial call
updateCamera();
canvas {
border: 1px solid;
background: ivory;
cursor: none;
}
z:<input type=range min=0.1 max=20 step=0.1 id=z-range><br>
x:<input type=range min=0 max=2500 step=0.1 id=x-range><br>
y:<input type=range min=0 max=720 step=0.1 id=y-range><br>
<input type=color><br>
<canvas width=500 height=500></canvas>
If you need bigger area, use multiple ImageData objects and the same "trick".
I can't work out why my sprites are not working in matter.js. I am creating a car/football game and have disabled wireframes on the renderer but the sprites are still not applying correctly.
My first issue is that when I apply a sprite texture to a composite body (the car), the sprite does not render at all.
My second issue is that when I apply a sprite texture to the body of the car, the sprite does not rotate with the body (the sprite does not rotate at all).
My third issue is that when I apply a sprite texture to the ball (not a composite body), both the sprite and the body representing the ball become invisible. They ball is still visible to the engine but neither the body or the sprite can be seen on the canvas.
function game ()
{
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// module aliases
var Engine = Matter.Engine,
Render = Matter.Render,
World = Matter.World,
Bodies = Matter.Bodies,
Body = Matter.Body,
Composite = Matter.Composite,
Composites = Matter.Composites,
Vertices = Matter.Vertices;
// create an engine
var engine = Engine.create();
engine.world.gravity.y = 0.6;
// create a renderer
var render = Render.create
({
//element: canvas,
element: document.body,
canvas: canvas,
engine: engine,
options:
{
width: window.innerWidth - 30,
height: window.innerHeight - 30,
wireframes: false
}
});
var offset = 1;
var wallSize = 20;
var ground = Bodies.rectangle(400, 510, 1810, 60,
{
isStatic: true,
friction: 0,
restitution: 0
});
var ball = Bodies.circle(window.innerWidth/2, window.innerHeight/2, 40,
{
mass: 5,// Used to be 0.5
restitution: 0.95,
friction: 0,
frictionAir: 0.01,
});
ball.render.sprite.texture = "soccarball.png";
const carBody = Matter.Bodies.fromVertices(100, 100, [{x:200, y:200},{x:260, y:210},{x:260, y:220},{x: 200, y: 220}]);
carBody.render.sprite.texture = "car_sprites.jpg";
carBody.render.sprite.xScale = 0.06;
carBody.render.sprite.yScale = 0.06;
const frontWheel = Matter.Bodies.circle(100 -20, 115, 8);
const rearWheel = Matter.Bodies.circle(100 +20, 115, 8);
const car = Body.create
({
parts: [carBody, frontWheel, rearWheel],
inertia: 100000,
friction: 0,
mass: 100,
restitution: -1,
});
var floor = Bodies.rectangle(window.innerWidth/2, window.innerHeight + offset, window.innerWidth + 2 * offset, wallSize,
{
isStatic: true, friction: 0
});
World.add(engine.world, [ground, car, ball, floor]);
// MAIN lOOP
function cycle()
{
requestAnimationFrame(cycle);
}
cycle();
// run the engine
Engine.run(engine);
//Engine.update(engine);
// run the renderer
Render.run(render);
}
window.onload = game();
///////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
function checkButtons()
{
if(keys["68"]) // KEY_D
{
if(car.speed < 10)
{
//Body.applyForce( car, {x: car.position.x, y: car.position.y}, {x: 0.5, y: 0})
let force = (0.001 * car.mass);
Body.applyForce(car,car.position,{x:force,y:0});
//console.log("Car Speed: " + car.speed);
}
}
if(keys["87"]) // KEY_W
{
if(onGround())
{
carAvailableJumps--;
let verticalForce = (-0.013 * car.mass);
Body.applyForce(car,car.position,{x:0,y:verticalForce});
}
if(carAvailableJumps > 0)
{
if(!onGround() && keys["68"]) // KEY_D
{
carAvailableJumps--;
let rotationalForce = (0.0009 * car.mass);
Body.applyForce(car,{x: (car.position.x - carWidth/2), y: (car.position.y)},{x:0,y:-rotationalForce});
}
if(!onGround() && keys["65"]) // KEY_A
{
carAvailableJumps--;
let rotationalForce = (0.0009 * car.mass);
Body.applyForce(car,{x: (car.position.x - carWidth/2), y: (car.position.y)},{x:0,y:rotationalForce});
}
}
}
if(keys["83"]) // KEY_S
{
}
if(keys["65"]) // KEY_A
{
if(car.speed < 10)
{
//Body.applyForce( car, {x: car.position.x, y: car.position.y}, {x: 0.5, y: 0})
let force = (-0.001 * car.mass);
Body.applyForce(car,car.position,{x:force,y:0});
//console.log("Car Speed: " + car.speed);
}
}
}
*/
<!DOCTYPE HTML>
<html>
<meta charset="UTF-8"/>
<head>
<title>This is the title</title>
</head>
<body>
<div id="div">
<canvas id="myCanvas"</canvas>
<script src="matter.js" type="text/javascript"></script>
<script src="internethelp.js" type="text/javascript"></script>
</div>
</body>
</html>
You are supposed to draw the car and other sprites yourself on the canvas. Matter only calculates the coordinates in the physics world for you. You have to apply those coordinates to the objects that you draw in your canvas.
This is an example where the matter coordinates are read, and then used to position a DIV element in the DOM
let physicsBox = Matter.Bodies.rectangle(x, y, w, h);
Matter.World.add(world, [physicsBox]);
let div = document.createElement("box");
document.body.appendChild(div);
// draw
let pos = physicsBox.position;
let angle = physicsBox.angle;
let degrees = angle * (180 / Math.PI);
div.style.transform = "translate(" + (pos.x - 10) + "px, " + (pos.y - 10) + "px) rotate(" + degrees + "deg)";
I am creating a game using the HTML5 Canvas element, and as one of the visual effects I would like to create a glow (like a light) effect. Previously for glow effects I found solutions involving creating shadows of shapes, but these require a solid shape or object to cast the shadow. What I am looking for is a way to create something like an ambient light glow with a source location but no object at the position.
Something I have thought of was to define a centerpoint x and y and create hundreds of concentric circles, each 1px larger than the last and each with a very low opacity, so that together they create a solid center and a transparent edge. However, this is very computationally heavy and does not seem elegant at all, as the resulting glow looks awkward.
While this is all that I am asking of and I would be more than happy to stop here, bonus points if your solution is A) computationally light, B) modifiable to create a focused direction of light, or even better, C) if there was a way to create an "inverted" light system in which the entire screen is darkened by a mask and the shade is lifted where there is light.
I have done several searches, but none have turned up any particularly illuminating results.
So I'm not quite sure what you want, but I hope the following snippet will help.
Instead of creating a lot of concentric circles, create one radialGradient.
Then you can combine this radial gradient with some blending, and even filters to modify the effect as you wish.
var img = new Image();
img.onload = init;
img.src = "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/car.svg";
var ctx = c.getContext('2d');
var gradCtx = c.cloneNode().getContext('2d');
var w, h;
var ratio;
function init() {
w = c.width = gradCtx.canvas.width = img.width;
h = c.height = gradCtx.canvas.height = img.height;
draw(w / 2, h / 2)
updateGradient();
c.onmousemove = throttle(handleMouseMove);
}
function updateGradient() {
var grad = gradCtx.createRadialGradient(w / 2, h / 2, w / 8, w / 2, h / 2, 0);
grad.addColorStop(0, 'transparent');
grad.addColorStop(1, 'white');
gradCtx.fillStyle = grad;
gradCtx.filter = "blur(5px)";
gradCtx.fillRect(0, 0, w, h);
}
function handleMouseMove(evt) {
var rect = c.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
draw(x, y);
}
function draw(x, y) {
ctx.clearRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(gradCtx.canvas, x - w / 2, y - h / 2);
ctx.globalCompositeOperation = 'lighten';
ctx.fillRect(0, 0, w, h);
}
function throttle(callback) {
var active = false; // a simple flag
var evt; // to keep track of the last event
var handler = function() { // fired only when screen has refreshed
active = false; // release our flag
callback(evt);
}
return function handleEvent(e) { // the actual event handler
evt = e; // save our event at each call
if (!active) { // only if we weren't already doing it
active = true; // raise the flag
requestAnimationFrame(handler); // wait for next screen refresh
};
}
}
<canvas id="c"></canvas>