with this code I managed to make a video. I am not a very expert on the subject, but I would think that my problem occurs because I do not know how many frames I should use. for example I want my video to last 15 seconds (I don't know how to set it, I don't know how many frames I should indicate)
var queue = d3.queue(1);
d3.range(240).forEach(function (frame) {
queue.defer(drawFrame, frame / 240);
});
queue.awaitAll(function (err, frames) {
recorder.start();
drawFrame();
function drawFrame() {
if (frames.length) {
context.drawImage(frames.shift(), 0, 0, width, height);
requestAnimationFrame(drawFrame);
} else {
recorder.stop();
}
}
});
how can I improve my code to get a smooth video?
const progressbar = document.getElementById('progressbar');
d3.select("#visualization").append('svg');
const vis = d3.select("svg").attr("width", 800).attr("height", 150).attr("xmlns", "http://www.w3.org/2000/svg").style("border", "1px solid red").style("fill", "white").style("background", "white");
const rectangle = vis.append("rect")
const circle = vis.append("circle");
let second = 0;
let interval;
const rectValues = {
"delay": 0000,
"duration": 5000,
"begin": {
"x": 0,
"y": 0,
"height": 70,
"width": 100,
"opacity": 1,
"fill": "red"
},
"destiny": {
"x": 250,
"y": 1,
"height": 100,
"width": 120,
"opacity": 0.8,
"fill": "green"
}
};
const circleValues = {
"delay": 4000,
"duration": 3000,
"begin": {
"cx": 250,
"r": 20,
"fill": "blue"
},
"destiny": {
"cx": 0,
"fill": "orange"
}
}
function startAnimation() {
//rectangle properties
startShapeAnimation(rectangle, rectValues);
//circle properties
startShapeAnimation(circle, circleValues);
}
function startShapeAnimation(shape, shapeValues) {
shape.attrs(shapeValues.begin)
.transition()
.duration(0)
.attrs(shapeValues.begin)
.transition()
.delay(shapeValues.delay)
.duration(shapeValues.duration)
.ease(d3.easeLinear)
.attrs(shapeValues.destiny);
}
startAnimation(0);
/***** CREATION OF VIDEO *******/
var canvas = document.createElement("canvas"),
width = 800,
height = 150,
context = canvas.getContext("2d");
var data = [],
stream = canvas.captureStream(),
recorder = new MediaRecorder(stream, {
mimeType: "video/webm"
});
recorder.ondataavailable = function(event) {
if (event.data && event.data.size) {
data.push(event.data);
}
};
recorder.onstop = () => {
var url = URL.createObjectURL(new Blob(data, {
type: "video/webm"
}));
//d3.selectAll("canvas, svg").remove();
d3.select("body")
.append("video")
.attr("src", url)
.attr("controls", true)
.attr("autoplay", true);
};
var queue = d3.queue(1);
d3.range(240).forEach(function(frame) {
queue.defer(drawFrame, frame / 240);
});
queue.awaitAll(function(err, frames) {
recorder.start();
drawFrame();
function drawFrame() {
if (frames.length) {
context.drawImage(frames.shift(), 0, 0, width, height);
requestAnimationFrame(drawFrame);
} else {
recorder.stop();
}
}
});
function drawFrame(t, cb) {
var img = new Image(),
serialized = new XMLSerializer().serializeToString(vis.node()),
url = URL.createObjectURL(new Blob([serialized], {
type: "image/svg+xml"
}));
img.onload = function() {
cb(null, img);
};
img.src = url;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="visualization"></div>
<div id="contenedor_canvas"></div>
Instead of limiting yourself to 240 frames, try to get as many as possible using requestAnimationFrame. Using this answer as inspiration, I was able to generate a much smoother video.
const progressbar = document.getElementById('progressbar');
d3.select("#visualization").append('svg');
const vis = d3.select("svg").attr("width", 800).attr("height", 150).attr("xmlns", "http://www.w3.org/2000/svg").style("border", "1px solid red").style("fill", "white").style("background", "white");
const rectangle = vis.append("rect")
const circle = vis.append("circle");
let second = 0;
let interval;
const rectValues = {
"delay": 0000,
"duration": 5000,
"begin": {
"x": 0,
"y": 0,
"height": 70,
"width": 100,
"opacity": 1,
"fill": "red"
},
"destiny": {
"x": 250,
"y": 1,
"height": 100,
"width": 120,
"opacity": 0.8,
"fill": "green"
}
};
const circleValues = {
"delay": 4000,
"duration": 3000,
"begin": {
"cx": 250,
"r": 20,
"fill": "blue"
},
"destiny": {
"cx": 0,
"fill": "orange"
}
}
function startAnimation() {
//rectangle properties
startShapeAnimation(rectangle, rectValues);
//circle properties
startShapeAnimation(circle, circleValues);
}
function startShapeAnimation(shape, shapeValues) {
shape.attrs(shapeValues.begin)
.transition()
.duration(0)
.attrs(shapeValues.begin)
.transition()
.delay(shapeValues.delay)
.duration(shapeValues.duration)
.ease(d3.easeLinear)
.attrs(shapeValues.destiny);
}
startAnimation(0);
/***** CREATION OF VIDEO *******/
var canvas = document.createElement("canvas"),
width = 800,
height = 150,
context = canvas.getContext("2d");
var data = [],
stream = canvas.captureStream(),
recorder = new MediaRecorder(stream, {
mimeType: "video/webm"
});
recorder.ondataavailable = function(event) {
if (event.data && event.data.size) {
data.push(event.data);
}
};
recorder.onstop = () => {
var url = URL.createObjectURL(new Blob(data, {
type: "video/webm"
}));
//d3.selectAll("canvas, svg").remove();
d3.select("body")
.append("video")
.attr("src", url)
.attr("controls", true)
.attr("autoplay", true);
};
var lengthInSeconds = 15;
var lengthInMS = lengthInSeconds * 1000;
var startTime = Date.now();
startRecording();
drawFrame();
function startRecording() {
recorder.start();
drawFrame();
}
function drawFrame() {
var elapsedTime = Date.now() - startTime;
if (elapsedTime > lengthInMS) {
stopRecording();
return;
}
console.log(((elapsedTime / lengthInMS) * 100).toFixed(2) + '%');
requestAnimationFrame(drawFrame);
var img = new Image(),
serialized = new XMLSerializer().serializeToString(vis.node()),
url = URL.createObjectURL(new Blob([serialized], {
type: "image/svg+xml"
}));
img.onload = function() {
context.drawImage(img, 0, 0, width, height);
};
img.src = url;
}
function stopRecording() {
recorder.stop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="visualization"></div>
<div id="contenedor_canvas"></div>
Related
I really like the CodePen linked here, and would like to incorporate it into a personal project I'm working on, however I don't know JS and want to change the fly-in effect. I just want the entire transition to fade into view as soon as the webpage loads. Is there any way anyone could help me do this?
console.clear();
const { gsap, imagesLoaded } = window;
const buttons = {
prev: document.querySelector(".btn--left"),
next: document.querySelector(".btn--right"),
};
const cardsContainerEl = document.querySelector(".cards__wrapper");
const appBgContainerEl = document.querySelector(".app__bg");
const cardInfosContainerEl = document.querySelector(".info__wrapper");
buttons.next.addEventListener("click", () => swapCards("right"));
buttons.prev.addEventListener("click", () => swapCards("left"));
function swapCards(direction) {
const currentCardEl = cardsContainerEl.querySelector(".current--card");
const previousCardEl = cardsContainerEl.querySelector(".previous--card");
const nextCardEl = cardsContainerEl.querySelector(".next--card");
const currentBgImageEl = appBgContainerEl.querySelector(".current--image");
const previousBgImageEl = appBgContainerEl.querySelector(".previous--image");
const nextBgImageEl = appBgContainerEl.querySelector(".next--image");
changeInfo(direction);
swapCardsClass();
removeCardEvents(currentCardEl);
function swapCardsClass() {
currentCardEl.classList.remove("current--card");
previousCardEl.classList.remove("previous--card");
nextCardEl.classList.remove("next--card");
currentBgImageEl.classList.remove("current--image");
previousBgImageEl.classList.remove("previous--image");
nextBgImageEl.classList.remove("next--image");
currentCardEl.style.zIndex = "50";
currentBgImageEl.style.zIndex = "-2";
if (direction === "right") {
previousCardEl.style.zIndex = "20";
nextCardEl.style.zIndex = "30";
nextBgImageEl.style.zIndex = "-1";
currentCardEl.classList.add("previous--card");
previousCardEl.classList.add("next--card");
nextCardEl.classList.add("current--card");
currentBgImageEl.classList.add("previous--image");
previousBgImageEl.classList.add("next--image");
nextBgImageEl.classList.add("current--image");
} else if (direction === "left") {
previousCardEl.style.zIndex = "30";
nextCardEl.style.zIndex = "20";
previousBgImageEl.style.zIndex = "-1";
currentCardEl.classList.add("next--card");
previousCardEl.classList.add("current--card");
nextCardEl.classList.add("previous--card");
currentBgImageEl.classList.add("next--image");
previousBgImageEl.classList.add("current--image");
nextBgImageEl.classList.add("previous--image");
}
}
}
function changeInfo(direction) {
let currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
let previousInfoEl = cardInfosContainerEl.querySelector(".previous--info");
let nextInfoEl = cardInfosContainerEl.querySelector(".next--info");
gsap.timeline()
.to([buttons.prev, buttons.next], {
duration: 0.2,
opacity: 0.5,
pointerEvents: "none",
})
.to(
currentInfoEl.querySelectorAll(".text"),
{
duration: 0.4,
stagger: 0.1,
translateY: "-120px",
opacity: 0,
},
"-="
)
.call(() => {
swapInfosClass(direction);
})
.call(() => initCardEvents())
.fromTo(
direction === "right"
? nextInfoEl.querySelectorAll(".text")
: previousInfoEl.querySelectorAll(".text"),
{
opacity: 0,
translateY: "40px",
},
{
duration: 0.4,
stagger: 0.1,
translateY: "0px",
opacity: 1,
}
)
.to([buttons.prev, buttons.next], {
duration: 0.2,
opacity: 1,
pointerEvents: "all",
});
function swapInfosClass() {
currentInfoEl.classList.remove("current--info");
previousInfoEl.classList.remove("previous--info");
nextInfoEl.classList.remove("next--info");
if (direction === "right") {
currentInfoEl.classList.add("previous--info");
nextInfoEl.classList.add("current--info");
previousInfoEl.classList.add("next--info");
} else if (direction === "left") {
currentInfoEl.classList.add("next--info");
nextInfoEl.classList.add("previous--info");
previousInfoEl.classList.add("current--info");
}
}
}
function updateCard(e) {
const card = e.currentTarget;
const box = card.getBoundingClientRect();
const centerPosition = {
x: box.left + box.width / 2,
y: box.top + box.height / 2,
};
let angle = Math.atan2(e.pageX - centerPosition.x, 0) * (35 / Math.PI);
gsap.set(card, {
"--current-card-rotation-offset": `${angle}deg`,
});
const currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
gsap.set(currentInfoEl, {
rotateY: `${angle}deg`,
});
}
function resetCardTransforms(e) {
const card = e.currentTarget;
const currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
gsap.set(card, {
"--current-card-rotation-offset": 0,
});
gsap.set(currentInfoEl, {
rotateY: 0,
});
}
function initCardEvents() {
const currentCardEl = cardsContainerEl.querySelector(".current--card");
currentCardEl.addEventListener("pointermove", updateCard);
currentCardEl.addEventListener("pointerout", (e) => {
resetCardTransforms(e);
});
}
initCardEvents();
function removeCardEvents(card) {
card.removeEventListener("pointermove", updateCard);
}
function init() {
let tl = gsap.timeline();
tl.to(cardsContainerEl.children, {
delay: 0.15,
duration: 0.5,
stagger: {
ease: "power4.inOut",
from: "right",
amount: 0.1,
},
"--card-translateY-offset": "0%",
})
.to(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), {
delay: 0.5,
duration: 0.4,
stagger: 0.1,
opacity: 1,
translateY: 0,
})
.to(
[buttons.prev, buttons.next],
{
duration: 0.4,
opacity: 1,
pointerEvents: "all",
},
"-=0.4"
);
}
const waitForImages = () => {
const images = [...document.querySelectorAll("img")];
const totalImages = images.length;
let loadedImages = 0;
const loaderEl = document.querySelector(".loader span");
gsap.set(cardsContainerEl.children, {
"--card-translateY-offset": "100vh",
});
gsap.set(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), {
translateY: "40px",
opacity: 0,
});
gsap.set([buttons.prev, buttons.next], {
pointerEvents: "none",
opacity: "0",
});
images.forEach((image) => {
imagesLoaded(image, (instance) => {
if (instance.isComplete) {
loadedImages++;
let loadProgress = loadedImages / totalImages;
gsap.to(loaderEl, {
duration: 1,
scaleX: loadProgress,
backgroundColor: `hsl(${loadProgress * 120}, 100%, 50%`,
});
if (totalImages == loadedImages) {
gsap.timeline()
.to(".loading__wrapper", {
duration: 0.8,
opacity: 0,
pointerEvents: "none",
})
.call(() => init());
}
}
});
});
};
waitForImages();
Here's the code I'm working with. I have tried altering the --card-transition-duration variable to less than 800ms, but this doesn't seem to have the effect that I want since it is used so often throughout the entire CodePen.
I am trying to make a shooting game in matter.js but can't find a way to shoot bullets from the player's exact location and how to count the collision between player and bullet but not with the walls.
I want to fire a bullet from player1 and then on pressing D again it should fire another bullet from the player1's last position.
My Codepen of this game
let p1= Matter.Bodies.polygon(200, 200, 3, 40, {
chamfer: {
radius: [15,10,15]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#F9ED69'
},
mass:1
});
let p2 = Matter.Bodies.polygon(1100, 200, 3, 40, {
chamfer: {
radius: [15,10,15]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#11999E'
},
mass:1
});
let bullet1 = Matter.Bodies.polygon(400, 300, 3, 7, {
chamfer: {
radius: [4,2,4]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#F9ED69'
},
mass:0
});
const keyHandlers = {
KeyS: () => {
Matter.Body.applyForce(p1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.0, y: 0.001})
},
KeyW: () => {
Matter.Body.applyForce(p1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.0, y: -0.002})
},
KeyD:()=>{
Matter.Body.applyForce(bullet1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.001, y: 0.0})
},
};
const keysDown = new Set();
document.addEventListener("keydown", event => {
keysDown.add(event.code);
});
document.addEventListener("keyup", event => {
keysDown.delete(event.code);
});
Matter.Events.on(engine, "beforeUpdate", event => {
[...keysDown].forEach(k => {
keyHandlers[k]?.();
});
});
// on collision of a bullet with wall and other bodies remove the bullet from the world after some delay and add the score
let score1 = 0;
let score2 = 0;
let health
Matter.Events.on(engine, "collisionStart", event => {
for (let i = 0; i < event.pairs.length; i++) {
const pair = event.pairs[i];
if (pair.bodyA === bullet1 || pair.bodyB === bullet1) {
Matter.World.remove(engine.world, bullet1);
alert('1');
}
if (pair.bodyA === bullet2 || pair.bodyB === bullet2) {
Matter.World.remove(engine.world, bullet2);
alert('2');
}
}
score1++;
alert(`SCore1 is ${score1}`); // these alerts are just to confirm the collision
});
You're on the right track, but if you hardcode bullet1 and bullet2 you're stuck with just those two bullets. Even with a fixed number of bullets and re-using the bodies (good for performance but maybe premature optimization), I'd probably use an array to store these bullets, which is almost always the correct move after you catch yourself doing thing1, thing2...
Here's a proof of concept. I'm creating and destroying bullets here to keep the coding easier, but it'd be more performant to keep a pool of objects and re-use/re-position them.
I'm also using sets to keep track of the types of the bodies, but you might want to use labels. Most of the code here could go in many different directions, specific to your use case.
const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const map = {width: 300, height: 300};
const render = Matter.Render.create({
element: document.body,
engine,
options: {...map, wireframes: false},
});
const player = {
score: 0,
body: Matter.Bodies.polygon(
map.width / 2, map.height / 2, 3, 15, {
frictionAir: 0.06,
density: 0.9,
render: {fillStyle: "red"},
},
),
lastShot: Date.now(),
cooldown: 150,
fireForce: 0.1,
rotationAngVel: 0.03,
rotationAmt: 0.03,
rotateLeft() {
Matter.Body.rotate(this.body, -this.rotationAmt);
Matter.Body.setAngularVelocity(
this.body, -this.rotationAngVel
);
},
rotateRight() {
Matter.Body.rotate(this.body, this.rotationAmt);
Matter.Body.setAngularVelocity(
this.body, this.rotationAngVel
);
},
fire() {
if (Date.now() - this.lastShot < this.cooldown) {
return;
}
// move the bullet away from the player a bit
const {x: bx, y: by} = this.body.position;
const x = bx + (Math.cos(this.body.angle) * 10);
const y = by + (Math.sin(this.body.angle) * 10);
const bullet = Matter.Bodies.circle(
x, y, 4, {
frictionAir: 0.006,
density: 0.1,
render: {fillStyle: "yellow"},
},
);
bullets.add(bullet);
Matter.Composite.add(engine.world, bullet);
Matter.Body.applyForce(
bullet, this.body.position, {
x: Math.cos(this.body.angle) * this.fireForce,
y: Math.sin(this.body.angle) * this.fireForce,
},
);
this.lastShot = Date.now();
},
};
const bullets = new Set();
const makeEnemy = () => Matter.Bodies.polygon(
(Math.random() * (map.width - 40)) + 20,
(Math.random() * (map.height - 40)) + 20,
5, 6, {
render: {
fillStyle: "transparent",
strokeStyle: "white",
lineWidth: 1,
},
},
);
const enemies = new Set([...Array(100)].map(makeEnemy));
const walls = new Set([
Matter.Bodies.rectangle(
0, map.height / 2, 20, map.height, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width / 2, 0, map.width, 20, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width, map.height / 2, 20, map.height, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width / 2, map.height, map.width, 20, {isStatic: true}
),
]);
Matter.Composite.add(engine.world, [
player.body, ...walls, ...enemies
]);
const keyHandlers = {
ArrowLeft: () => player.rotateLeft(),
ArrowRight: () => player.rotateRight(),
Space: () => player.fire(),
};
const validKeys = new Set(Object.keys(keyHandlers));
const keysDown = new Set();
document.addEventListener("keydown", e => {
if (validKeys.has(e.code)) {
e.preventDefault();
keysDown.add(e.code);
}
});
document.addEventListener("keyup", e => {
if (validKeys.has(e.code)) {
e.preventDefault();
keysDown.delete(e.code);
}
});
Matter.Events.on(engine, "beforeUpdate", event => {
[...keysDown].forEach(k => {
keyHandlers[k]?.();
});
if (enemies.size < 100 && Math.random() > 0.95) {
const enemy = makeEnemy();
enemies.add(enemy);
Matter.Composite.add(engine.world, enemy);
}
});
Matter.Events.on(engine, "collisionStart", event => {
for (const {bodyA, bodyB} of event.pairs) {
const [a, b] = [bodyA, bodyB].sort((a, b) =>
bullets.has(a) ? -1 : 1
);
if (bullets.has(a) && walls.has(b)) {
Matter.Composite.remove(engine.world, a);
bullets.delete(a);
}
else if (bullets.has(a) && enemies.has(b)) {
Matter.Composite.remove(engine.world, a);
Matter.Composite.remove(engine.world, b);
bullets.delete(a);
enemies.delete(b);
document.querySelector("span").textContent = ++player.score;
}
}
});
Matter.Render.run(render);
Matter.Runner.run(Matter.Runner.create(), engine);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div>press left/right arrow keys to rotate and space to shoot</div>
<div>score: <span>0</span></div>
I'm a newb, when it comes to developing games, especially with a new language and framework. I'm having trouble at making the camera stick to the player. Not sure where should I put the this.camera.startFollow(); statement so it would work. The game is an asteroids remake, however, the map is larger than 800x600. The game is already set up with socket.io. Here's the code:
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 3200,
height: 2400,
physics: {
default: 'arcade',
arcade: {
debug: false,
gravity: {
x: 0,
y: 0
}
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
function preload() {
this.load.image('ship', 'assets/spaceShips_001.png');
this.load.image('otherPlayer', 'assets/enemyBlack5.png');
this.load.image('star', 'assets/star_gold.png');
this.load.image('backgroundStar1', 'assets/star2.png');
this.load.image('backgroundStar2', 'assets/star3.png');
this.load.image('space', 'assets/deep-space.jpg');
//this.load.image('background', 'assets/background.png');
}
function create() {
var self = this;
this.add.tileSprite(0, 0, 6400, 4800, 'space');
this.physics.world.setBounds(0, 0, 3200, 3200);
//this.cameras.main.setBounds(0, 0, 3200, 3200);
this.socket = io();
this.otherPlayers = this.physics.add.group();
this.socket.on('currentPlayers', function (players) {
Object.keys(players).forEach(function (id) {
if (players[id].playerId === self.socket.id) {
addPlayer(self, players[id]);
//this.camera.startFollow(self.ship); - does not work
} else {
addOtherPlayers(self, players[id]);
}
});
});
this.socket.on('newPlayer', function (playerInfo) {
addOtherPlayers(self, playerInfo);
});
this.socket.on('disconnect', function (playerId) {
self.otherPlayers.getChildren().forEach(function (otherPlayer) {
if (playerId === otherPlayer.playerId) {
otherPlayer.destroy();
}
});
});
this.socket.on('playerMoved', function (playerInfo) {
self.otherPlayers.getChildren().forEach(function (otherPlayer) {
if (playerInfo.playerId === otherPlayer.playerId) {
otherPlayer.setRotation(playerInfo.rotation);
otherPlayer.setPosition(playerInfo.x, playerInfo.y);
}
});
});
this.cursors = this.input.keyboard.createCursorKeys();
this.blueScoreText = this.add.text(16, 16, '', { fontSize: '32px', fill: '#0000FF' });
this.redScoreText = this.add.text(584, 16, '', { fontSize: '32px', fill: '#FF0000' });
this.socket.on('scoreUpdate', function (scores) {
self.blueScoreText.setText('Blue: ' + scores.blue);
self.redScoreText.setText('Red: ' + scores.red);
});
this.socket.on('starLocation', function (starLocation) {
if (self.star) self.star.destroy();
self.star = self.physics.add.image(starLocation.x, starLocation.y, 'star');
self.physics.add.overlap(self.ship, self.star, function () {
this.socket.emit('starCollected');
}, null, self);
});
}
function addPlayer(self, playerInfo) {
self.ship = self.physics.add.image(playerInfo.x, playerInfo.y, 'ship').setOrigin(0.5, 0.5).setDisplaySize(53, 40).setCollideWorldBounds(true);
if (playerInfo.team === 'blue') {
self.ship.setTint(0x0000ff);
} else {
self.ship.setTint(0xff0000);
}
self.ship.setDrag(100);
self.ship.setAngularDrag(100);
self.ship.setMaxVelocity(200);
}
function addOtherPlayers(self, playerInfo) {
const otherPlayer = self.add.sprite(playerInfo.x, playerInfo.y, 'otherPlayer').setOrigin(0.5, 0.5).setDisplaySize(53, 40);
if (playerInfo.team === 'blue') {
otherPlayer.setTint(0x0000ff);
} else {
otherPlayer.setTint(0xff0000);
}
otherPlayer.playerId = playerInfo.playerId;
self.otherPlayers.add(otherPlayer);
}
function update() {
if (this.ship) {
if (this.cursors.left.isDown) {
this.ship.setAngularVelocity(-150);
} else if (this.cursors.right.isDown) {
this.ship.setAngularVelocity(150);
} else {
this.ship.setAngularVelocity(0);
}
if (this.cursors.up.isDown) {
this.physics.velocityFromRotation(this.ship.rotation + 1.5, 100, this.ship.body.acceleration);
} else {
this.ship.setAcceleration(0);
}
this.physics.world.wrap(this.ship, 5);
// emit player movement
var x = this.ship.x;
var y = this.ship.y;
var r = this.ship.rotation;
if (this.ship.oldPosition && (x !== this.ship.oldPosition.x || y !== this.ship.oldPosition.y || r !== this.ship.oldPosition.rotation)) {
this.socket.emit('playerMovement', { x: this.ship.x, y: this.ship.y, rotation: this.ship.rotation });
}
// save old position data
this.ship.oldPosition = {
x: this.ship.x,
y: this.ship.y,
rotation: this.ship.rotation
};
}
}
Solved by adding the following code in the create() function:
window.scene = this;
then changed my commented line
//this.cameras.main.setBounds(0, 0, 3200, 3200);
to
scene.cameras.main.setBounds(0, 0, 3200, 3200);
and lastly added
scene.cameras.main.startFollow(self.ship);
in my addPlayer() function.
I am trying to programmatically set the width and heights of the chained bodies in matter.js. Unfortunately, I am only getting 0 as values and I am unsure why. My guess is that the images are not being loaded fast enough to provide those values. How can I load those dimensions before the images are loaded?
Pseudo-code
Several bodies from Array
Get the width and height of each image in the Array
Use this value to set the Bodies dimensions
Code
var playA = Composites.stack(
percentX(25) - assetSize / 2,
percentY(25),
1,
6,
5,
5,
function (x, y) {
iA++;
var imgWidth;
var imgHeight;
var img = new Image();
img.src = String(design[iA]);
var imgWidth = 0;
var imgHeight = 0;
img.onload = function a() {
imgWidth = img.naturalWidth;
imgHeight = img.naturalHeight;
console.log(String(design[iA]), imgWidth, imgHeight);
};
console.log(String(design[iA]), imgHeight, imgWidth); // I can't access the values here.
return Bodies.rectangle(x, y, imgWidth, imgHeight, {
// collisionFilter: { group: group },
friction: 1,
render: {
sprite: {
texture: design[iA],
xScale: (assetSize / 100) * 0.46,
yScale: (assetSize / 100) * 0.46
}
}
});
}
);
Composites.chain(playA, 0.3, 0, -0.5, 0, {
stiffness: 1,
length: 10,
render: { type: "line", visible: false }
});
If you know the dimensions and can populate an array beforehand, the solution is potentially straightforward since Matter.js loads images given a URL string, with the caveat that the engine doesn't wait for the loads before running.
Here's a minimal example of iterating over width/height pairs in an array and passing these properties into the rectangle calls which I'll use as a stepping stone to the example that matches your use case.
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
options: {
width: 450,
height: 250,
wireframes: false, // required for images
}
});
Matter.Render.run(render);
const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);
const imgSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
const stack = Matter.Composites.stack(
// xx, yy, columns, rows, columnGap, rowGap, cb
150, 50, 4, 1, 0, 0,
(x, y, i) => {
const [w, h] = imgSizes[i];
return Matter.Bodies.rectangle(x, y, w, h, {
render: {
sprite: {
texture: `http://placekitten.com/${w}/${h}`
}
}
});
}
);
Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
stiffness: 0.75,
length: 10,
render: {type: "line", visible: true}
});
Matter.Composite.add(engine.world, [
stack,
Matter.Bodies.rectangle(225, 0, 450, 25, {
isStatic: true
}),
Matter.Bodies.rectangle(450, 150, 25, 300, {
isStatic: true
}),
Matter.Bodies.rectangle(0, 150, 25, 300, {
isStatic: true
}),
Matter.Bodies.rectangle(225, 250, 450, 25, {
isStatic: true
})
]);
const mouse = Matter.Mouse.create(render.canvas);
const mouseConstraint = Matter.MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: {visible: true}
}
});
Matter.Composite.add(engine.world, mouseConstraint);
render.mouse = mouse;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
Now, if you need to load images using onload and use their dimensions, you'll need to use promises or put all code dependent on these images into the sequence of onload callback(s) as described in the canonical How do I return the response from an asynchronous call?.
The failing pattern is:
const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);
let data = null;
getSomethingAsync(result => {
data = result;
console.log("this runs last");
});
console.log(data); // guaranteed to be null, not "something"
// more logic that is supposed to depend on data
The fix is:
const getSomethingAsync = cb => setTimeout(() => cb("something"), 0);
getSomethingAsync(data => {
console.log(data);
// logic that depends on the data from `getSomethingAsync`
});
console.log("this will run first");
// logic that doesn't depend on data from `getSomethingAsync`
Since you're juggling multiple onloads, you can promisify the onloads to make them easier to work with. I have a couple examples of doing this here and here agnostic of matter.js.
Here's an example of using promises to load images applied to your general problem. Again, I'll use my own code so that it's runnable and reproducible, but the pattern should be easy to extrapolate to your project.
The idea is to first load the images using a series of promises which are resolved when onload handlers fire, then use Promise.all to chain a then which runs the MJS initializer callback only when all images are loaded. The widths and heights are then accessible to your matter.js code within the callback.
As a side benefit, this ensures images are loaded by the time MJS runs.
const initializeMJS = images => {
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
options: {
width: 450,
height: 250,
wireframes: false, // required for images
}
});
Matter.Render.run(render);
const runner = Matter.Runner.create();
Matter.Runner.run(runner, engine);
const stack = Matter.Composites.stack(
// xx, yy, columns, rows, columnGap, rowGap, cb
150, 50, 4, 1, 0, 0,
(x, y, i) => {
const {width: w, height: h} = images[i];
return Matter.Bodies.rectangle(x, y, w, h, {
render: {
sprite: {
texture: images[i].src
}
}
});
}
);
Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
stiffness: 0.75,
length: 10,
render: {type: "line", visible: true}
});
Matter.Composite.add(engine.world, [
stack,
Matter.Bodies.rectangle(225, 0, 450, 25, {
isStatic: true
}),
Matter.Bodies.rectangle(450, 150, 25, 300, {
isStatic: true
}),
Matter.Bodies.rectangle(0, 150, 25, 300, {
isStatic: true
}),
Matter.Bodies.rectangle(225, 250, 450, 25, {
isStatic: true
})
]);
const mouse = Matter.Mouse.create(render.canvas);
const mouseConstraint = Matter.MouseConstraint.create(engine, {
mouse: mouse,
constraint: {
stiffness: 0.2,
render: {visible: true}
}
});
Matter.Composite.add(engine.world, mouseConstraint);
render.mouse = mouse;
};
const imageSizes = [[56, 48], [45, 50], [35, 50], [60, 63]];
const imageURLs = imageSizes.map(([w, h]) =>
`http://placekitten.com/${w}/${h}`
);
Promise.all(imageURLs.map(e =>
new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = e;
})
))
.then(initializeMJS)
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
I have created a small game using chart.js and canvas. However I have only one canvas to create the whole game so I wanted to draw some images too on the canvas.
Due to this the games works superb on windows as well as android phone but I'm facing performance issue on the iPad. Can anyone assist me in achieving my task keeping only one canvas
I have recreated the scenario in the below pen
https://codepen.io/sushantipte48/pen/voLxyW
var _ctx = document.getElementById('myChart').getContext("2d")
var canvas = document.createElement('canvas');
canvas.width = 300;
canvas.height =200;
var bg = new Image();
bg.src = 'https://img.freepik.com/free-vector/vibrant-pink-watercolor-painting-background_53876-58930.jpg?size=626&ext=jpg';
var ctx = canvas.getContext('2d')
var graphImage;
var mainData = [],
points = [],
x = 0,
y = 0,
config,
currentMax = 20,
slope = 1;
config = {
"data": {
"datasets": [{
"type": "line",
"data": mainData,
"backgroundColor": "#ff0000",
"borderColor": "#ff0000",
"fill": false,
"lineTension": 1,
"borderWidth": 1,
"pointRadius": 0
}]
},
"options": {
"legend": {
"display": false
},
"animation": {
"duration": 0
},
"responsive": false,
"maintainAspectRatio": false,
"scales": {
"xAxes": [{
"display": true,
"type": "linear",
"ticks": {
"beginAtZero": true,
"min": 0,
"max": 100
}
}],
"yAxes": [{
"display": true,
"stacked": true,
"ticks": {
"padding": 10,
"beginAtZero": true,
"min": 0,
"max": 100
}
}]
}
}
};
var myChart = new Chart(ctx, config);
setInterval(addingData, 1000/24);
function addingData() {
mainData.push({x:x,y:y});
x += 0.1;
y = slope * x;
if (x % 10 === 0) {
points.push(y);
}
config.data.datasets[0].data = mainData;
myChart.update();
var img = new Image();
img.src = canvas.toDataURL('image/png',1.0)
img.onload = function () {
graphImage = img;
drawCanvas();
};
}
function drawCanvas(){
_ctx.drawImage(bg,0,0,600,400)
_ctx.drawImage(graphImage,0,0,300,200);
}