function preload() {
this.load.image('background', 'images/table_en.png.webp');
this.load.image('wheel', 'images/UpperWheel.png.webp');
this.load.image('ball', 'images/ball.png.webp');
roulette.circle.array.forEach(i => {
this.load.image(`${i.index}`, i.src);
});
roulette.lever.array.forEach(i => {
this.load.image(`${i.index}`, i.src)
});
}
function update() {
if (roulette.circle.previousCircle) {
roulette.circle.previousCircle.destroy()
}
if (roulette.circle.previousLever) {
roulette.circle.previousLever.destroy();
}
if (roulette.circle.currentStep === 100 && run) {
ball.run = true;
}
let circle = this.add.image(108, 110, `circle${roulette.circle.currentStep}`).setOrigin(0, 0).setScale(0.7);
let lever = this.add.image(200, 150, `lever${roulette.lever.currentStep}`).setOrigin(0, 0).setScale(0.7);
roulette.circle.method();
roulette.lever.method();
roulette.circle.previousCircle = circle;
roulette.circle.previousLever = lever;
}
I am writing roulette, where 359 pictures fall on a wheel (considering all its conditions). I uploaded all these pictures in the preload function, and in the update function I simply create the downloaded picture and delete the previous one. All this affects productivity (because the speed of changing images can be different). How to solve this problem?
Perhaps if i reduce the speed of the change of pictures, the problem would be solved, but I do not know how to do it
The update() method is called once per frame. The default target frames per second (fps) in Phaser games is 60 - API Reference.
Many factors in your game can influence the actual fps that you'll see. If you leave it at default, your update() method is being called approximately 60x per second. If you want the user to actually be able to see each image, this is probably not desired behavior.
You could lower the target fps number in your game config like this:
{
type: Phaser.AUTO,
...,
fps: {
target: 30 // 30x per second
}
}
... but that can be imperfect. It is a global change to your game's update speed and there may be other things you want to happen 60x per second (like checking for input, etc.).
To change how often your images change without modifying your game's fps, consider creating a separate method to handle the destroy/create of images and reference that method inside update().
function update() {
if (!changingImage) {
changingImage = true;
changeImage();
}
}
function changeImage() {
if (roulette.circle.previousCircle) {
roulette.circle.previousCircle.destroy()
}
if (roulette.circle.previousLever) {
roulette.circle.previousLever.destroy();
}
if (roulette.circle.currentStep === 100 && run) {
ball.run = true;
}
let circle = this.add.image(108, 110, `circle${roulette.circle.currentStep}`).setOrigin(0, 0).setScale(0.7);
let lever = this.add.image(200, 150, `lever${roulette.lever.currentStep}`).setOrigin(0, 0).setScale(0.7);
roulette.circle.method();
roulette.lever.method();
roulette.circle.previousCircle = circle;
roulette.circle.previousLever = lever;
changeImage = true;
}
If it's still happening too quickly, consider wrapping the changeImage boolean in a timeout to further delay how often your images get updated.
setTimeout(() => {
changingImage = true;
}, 1000);
Related
I have written some code to display images very quickly in succession, however, there are often gaps in between when images are loaded which is extremely jarring. I would be curious as to how I could get rid of these large gaps. I have about 1000 images so I am attempting to make this code as non-tedious as possible. My current thought would be to somehow load them to memory first using p5.js. Anything helps!
let faces = [];
let i = 0
function preload(){
window.onload = setInterval(() => {
i++
if(i > 999){
i = 0;
}
faces[i] = loadImage(`aberdeen_results/${i}.jpg`)
}, 400);
}
function setup(){
createCanvas(innerWidth,innerHeight);
}
function draw(){
background(256)
image(faces[i], 450, 150,);
}
From my experience if this image fills entire screen you can use
background(faces[i]);
and it gives way less lag, maybe it would help.
There were some flaws with your code, since it was giving errors on my side.
You need not to use window.onload, and setInterval inside preload().
P5 documentation says "If a preload function is defined, setup() will wait until any load calls within have finished." Since you were continuously loading image using loadImage() inside setInterval, the setup() will never be called.
You can use a setInterval outside of preload() to update the i.
(I have used only 10 images for demo purpose)
Fixed Code:
let faces = [];
let i = 0
function preload() {
for (let k = 0; k < 10; k++) {
faces[k] = loadImage(`https://picsum.photos/450/150?random=${k}`)
}
}
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
}
setInterval(() => {
i++;
if (i > 9) {
i = 0;
}
}, 400)
function draw() {
background(256)
image(faces[i], 0, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js" integrity="sha512-N4kV7GkNv7QR7RX9YF/olywyIgIwNvfEe2nZtfyj73HdjCUkAfOBDbcuJ/cTaN04JKRnw1YG1wnUyNKMsNgg3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Also, it is not advisable to load all 1000 images, because it will take huge amount of RAM, and may crash the browser. I didn't had any problems with gaps in between images (maybe bcoz of only 10 images, but with 1000 images, the most probable reason for gap would be RAM).
var random = randomNumber(1, 7);
var Block = createSprite(200, 0);
var Blocks = createGroup();
Blocks.add(Block);
Blocks.setAnimationEach(random.toString());
createEdgeSprites();
function draw() {
background("white");
Move("down", 4, 1);
Move2("right", 4, 0, "left");
Move2("left", -4, 0, "right");
if (Blocks.isTouching(bottomEdge)) {
var Block = createSprite(200, 0);
Blocks.add(Block);
Blocks.setAnimationEach(randomNumber(1, 7).toString());
}
if (Blocks.isTouching(edges)) {
Blocks.collide(edges);
}
drawSprites();
}
function Move(Key, Velocity, Velocity2) {
if (keyDown(Key)) {
Block.velocityY = Velocity;
} else {
Block.velocityY = Velocity2;
}
}
function Move2(Key, Velocity, Velocity2, Key2) {
if (keyDown(Key)) {
Block.velocityX = Velocity;
} else if ((!keyDown(Key2))) {
Block.velocityX = Velocity2;
}
}
My issue is with
if (Blocks.isTouching(bottomEdge)) {
var Block = createSprite(200, 0);
Blocks.add(Block);
Blocks.setAnimationEach(randomNumber(1, 7).toString());
}
That code is basically for when a piece touches the ground, a new piece is made with a random Animation, that is the shape. I know I have to fix other stuff, but I know how to fix it. The thing I don't know how to do is set an animation once? Is there a code for that, because it constantly randomly changes the animation because the function draw() runs multiple times. But if I put it outside of that function, it only runs in the beginning of the program. Should I like push my sprite back up a little afterwards? I can't use const and I don't even know how to use it. Each time a sprite touches the ground, I want a new sprite to made and that animation is only set once. My biggest issue is that, I know there are other issues in that segment, but I can fix it myself
This question already has an answer here:
HTML5 Canvas performance very poor using rect()
(1 answer)
Closed 2 years ago.
I start the code, watch in dev window, get no errors. The image moves very quickly at first but, after a few seconds, it comes to a craw.
I checked on here but I can't figure it out. I'm a rookie so that could be the problem.
I've tried breaking it out into basic functional steps rather than any class, put "===" and "==" back and forth (cause I do not get the real difference between them), and changed from a "setInterval" to a "setTimeout" just in case I was calling the interval too soon.
I am very much a noob to Javascript and this is my first real work with canvas.
The HTML code simply adds the script with nothing else. The window load at the end of the script runs "startgame".
Thanks for anything you can help me with.
var winX=0;
var winY=0;
var scaleX=0;
var scaleY=0;
var bkcolor="#777777";
var ctx;
var objs=[];
var wallimg = new Image();
wallimg.src = 'wall.png';
var willy=new Image();
willy.src='willy.gif';
var player;
var gameActive=0;
var keyboard=[];
function startGame()
{
var i;
setWindow();
theBoard.start();
gameActive=1;
someting=new Obj(0,10,600,20,"PATTERN",wallimg);
someting.setimage(wallimg);
Obj.Wall(40,100,100,16,wallimg);
Obj.Wall(0,420,620,16,wallimg);
Obj.Wall(0,0,16,440,wallimg);Obj.Wall(584,0,16,440,wallimg);
player=new Obj(24,400,16,16,"PLAYER",willy);
player.setimage(willy);
player.gravity=1;
}
function setWindow()
{
winX = window.innerWidth|| document.documentElement.clientWidth|| document.body.clientWidth;
winY = window.innerHeight|| document.documentElement.clientHeight|| document.body.clientHeight;
winX=winX-4;
winY=winY-4;
scaleX=640/winX;
scaleY=480/winY;
if (gameActive==1) {
theBoard.canvas.width = 600/scaleX;
theBoard.canvas.height = 440/scaleY;
theBoard.canvas.style.left=""+20/scaleX+"px";
theBoard.canvas.style.top=""+20/scaleY+"px";
}
}
function setBackdrop(img)
{
var str="<img src='"+img+"' onclick='showCoords(event);' style='";
str=str+"width:"+winX+"px;height:"+winY+"px;'>";
document.getElementById('page').innerHTML=str;
document.getElementById('page').innerHTML=str;
currimage=img;
}
var theBoard = {
canvas : theCanvas=document.createElement("canvas"),
start : function() {
this.canvas.width = 600/scaleX;
this.canvas.height = 440/scaleY;
this.canvas.style.left=""+20/scaleX+"px";
this.canvas.style.top=""+20/scaleY+"px";
this.canvas.style.position="absolute";
this.canvas.tabIndex=1;
this.context = this.canvas.getContext("2d");
ctx=this.context;
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.canvas.style.backgroundColor=bkcolor;
setTimeout(updateGameArea, 40);
window.addEventListener('keydown', function (e) {
e.preventDefault();
keyboard=(keyboard||[]);
keyboard[e.keyCode]=(e.type=="keydown");
})
window.addEventListener('keyup', function (e) {
keyboard[e.keyCode]=(e.type=="keydown");
})
},
stop : function() {
},
restart:function() { this.interval = setTimeout(updateGameArea, 40);},
clear : function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function updateGameArea()
{
var i;
theBoard.clear();
if (keyboard && keyboard[37])
{
player.speed-=2; if (player.speed<-8) player.speed=-8;
}
else if (player.speed<0)
{
player.speed+=1;
}
if (keyboard && keyboard[39])
{
player.speed+=2; if (player.speed>8) player.speed=8;
}
else if (player.speed>0)
{
player.speed-=1;
}
if (player.gravity<1) player.gravity++;
if (keyboard && keyboard[38] && player.gravity>-1 && player.canjump==1){
player.gravity=-16;
player.dir=-6;
player.canjump=0;
}
if (player.gravity<4) {player.gravity=player.gravity+player.dir; player.dir+=4;if (player.dir>16) player.dir=16;}
if (player.gravity!=0)
{
player.y+=player.gravity;
if (checkWalls(player)==true)
{ player.y-=player.gravity;
if (player.gravity>0) player.canjump=1;
}
}
if (player.speed!=0)
{
player.x+=player.speed;
if (checkWalls(player)===true)
player.x-=player.speed;
}
for (i=0;i<objs.length;i++)
objs[i].draw();
setTimeout(updateGameArea, 10);
}
function checkWalls(obj)
{
var i;
for (i=0;i<objs.length;i++)
{
if (objs[i].type=="WALL")
if (obj.collision(objs[i])) {return true;}
}
return false;
}
class Obj {
constructor (x,y,w,h,t,img="") {
this.width=w;
this.height=h;
this.x=x;
this.y=y;
this.type=t;
this.imagemap=img;
this.speed=0;
this.gravity=0;
this.dir=0;
this.canjump=1;
this.pattern=0;
objs[objs.length]=this;
}
static Wall(x,y,w,h,img) {
var id=new Obj(x,y,w,h,"WALL",img);
return id;
}
draw()
{
if ((this.x/scaleX)<0 || (this.x/scaleX)>theBoard.canvas.width ||
(this.y/scaleY)<0 || (this.y/scaleY)>theBoard.canvas.height)
return;
switch (this.type){
case 'PATTERN':
case 'WALL':
{
if (this.pattern===0)
{ this.pattern=ctx.createPattern(this.imagemap,"repeat");}
ctx.rect(this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
ctx.fillStyle=this.pattern;
ctx.fill();
break;
}
case 'PLAYER':
ctx.drawImage(this.imagemap,0,0,this.width,this.height,this.x/scaleX,this.y/scaleY,this.width/scaleX,this.height/scaleY);
break;
}
}
setimage(img)
{
this.imagemap=img;
}
collision(wth) {
if (((this.x+this.width)>wth.x) && (this.x<(wth.x+wth.width))
&& ((this.y+this.height)>wth.y) && (this.y<(wth.y+wth.height)))
{return true;}
else return false;
}
}
window.onload=startGame();
As pointed out by #Kaiido, solution to your problem is here: HTML5 Canvas performance very poor using rect().
In short, just put your main loop code between beginPath and closePath without changing your theBoard.clear() method.
function updateGameArea()
{
var i;
theBoard.clear();
theBoard.context.beginPath();
...
theBoard.context.closePath();
requestAnimationFrame(updateGameArea);
}
Answer I originally wrote:
Resetting the dimensions to clear the canvas works better in your case, but it would induce performance issues.
clear : function() {
this.context.canvas.width = 600 / scaleX;
this.context.canvas.height = 440 / scaleY;
}
Also, use requestAnimationFrame as it eliminates any flicker that can happen when using setTimeout.
requestAnimationFrame(updateGameArea);
The following is a guess. I think you're running out of cycles and your frames are piling up on top of each other. At a glance, I don't see anything in your code that would cause a memory leak. Unless you look at the console memory graph and find out that you do, because you're adding listeners over and over or something like that. But simply clearing a canvas does not slow things down. It's basically the same as setting a bunch of values in an array.
However: Running heavy canvas operations within a setTimeout() can have a big toll on your CPU, if the CPU can't finish one operation before the next one enters the queue. Remember that timeouts are asynchronous. If your CPU throttles down and if the refresh rate you are specifying (40 milliseconds) is too short, then you will be left with a whole stack of redraws and clears that are waiting to go right after the last one, without giving the CPU any time to breathe.
Most Canvas animation packages have ways of dealing with this, by not just setting a timeout but waiting to make sure the last redraw is finished before triggering the next one in the call stack, and dropping a frame if necessary. At a bare minimum, you want to set a global variable like _redrawing=true before you do your redraw, and then set it to false when the redraw is finished, and ignore any call to setTimeout while it's still true. That will let you count how many frames you might be dropping. If you see this number going up over time, your CPU may be throttling as well. But do also check the memory graph and see if anything else is leaking.
Edit as correctly noted by #N3R4ZZuRR0 using requestAnimationFrame() will also avoid the timer problem. But you then need to measure the time between animation frames to figure out where things should actually be at that point in time. My suggestion of dropping frames here and there is primitive and most packages use requestAnimationFrame(), but it would help you identify whether your problem is with some other part of your code or with your frames building up in the timer.
I'm new to JavaScript and I'm having trouble figuring out how to resize multiple elements with one function for my rhythm game. This is for my CSP class and theres no use of jQuery sadly. I'm also limited to the commands that the program (AppLab) I'm using has provided. My goal right now is to make an "animation" of a circle growing to its desired size to indicate that it should be clicked. I need these elements to appear while another one is in the process growing and so on.
I'm aware that my code probably sucks so if there is also a way to simplify or improve it I would love to know.
This is my current program code and the hitIndicator function is the one I'm having the most trouble with:
var circleSizeW = 0;
var circleSizeL = 0;
var score = 0;
hitCircle("hitcircle", 300, 6, 206);
hitCircle("image2", 300, 6, 682);
function circleEffects(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
playSound("47 (1).mp3", false);
hideElement(circleid);
});
}, whentohit);
}
function hitIndicator(circleid, growthRate) {
var xPos = getXPosition(circleid);
var yPos = getYPosition(circleid);
var t = setInterval(function() {
circleSizeW = circleSizeW + growthRate;
circleSizeL = circleSizeL + growthRate;
xPos = xPos - (growthRate/2);
yPos = yPos - (growthRate/2);
showElement(circleid);
setSize(circleid, circleSizeW, circleSizeL);
setPosition(circleid, xPos, yPos);
if (circleSizeW >= 60) {
clearInterval(t);
circleSizeW = 0;
circleSizeL = 0;
}
}, 50);
}
function scoreSystem(circleid, whentohit) {
setTimeout(function() {
onEvent(circleid, "click", function() {
score = score + 100;
setText("scoreTrack", score);
});
}, whentohit);
}
function hitCircle(circleid, whentohit, growthRate, appearancetime) {
setTimeout(function() {
console.log("hitcircle");
circleEffects(circleid, whentohit);
hitIndicator(circleid, growthRate);
scoreSystem(circleid, whentohit);
}, appearancetime);
My code is nowhere near completion either so there are still many things that needs to be done.
I'm not sure how to have multiple circles running that function at similar times because when I try to fix the errors/change the values of the functions' parameters they sometimes loop twice, infinitely loop, or receive the changed values of the previous circle while the previous circle is still growing.
I am also fairly new with Javascript, but I have a fairly good idea of what should be done here.
I would write an object constructor for instantiating circles.
Scroll down this page to see how to make object constructors.
Then for your circle object add a hitindicator method.
This page covers methods.
Then set up a function that will retrieve and loop through every instantiated circle object and run .hitindicator on each circle. Best way to do this, might be to add every instantiated circle to a circle array, then just loop through the array?
Then have an update() function that calls the function in step 3, and have update() called every "frame" with setInterval.
The pages linked should give you enough information to figure it out from here.
I'm trying to adapt the Html JS library Char.js to QML QtQuick 2.4.
The library have a function to animate the scene. Everything works great if I don't animate it (eg animate with only 1 step of animation).
When the animation is used, the canvas freeze until the animation is finished, and then the scene is redraw. Here is the animationLoop function:
animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
var currentStep = 0,
easingFunction = easingEffects[easingString] || easingEffects.linear;
var animationFrame = function(){
currentStep++;
var stepDecimal = currentStep/totalSteps;
var easeDecimal = easingFunction(stepDecimal);
callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
onProgress.call(chartInstance,easeDecimal,stepDecimal);
if (currentStep < totalSteps){
chartInstance.animationFrame = chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
} else{
onComplete.apply(chartInstance);
}
};
chartInstance.chart.canvas.requestAnimationFrame(animationFrame);
},
Each time the onAnimationProgress callback is called, I call the Canvas requestPaint function to redraw the scene. Unfortunately, the onPaint function will not be called until every step of the animation have finished.
The Canvas object is accessible with : chartInstance.chart.canvas. I tried to call directely chartInstance.chart.canvas.requestPaint() at each iteration too, which do not work. I still have to wait until the animation finished to see the scene redraw correctly.
onAnimationProgress: function(easeDecimal,stepDecimal) {
skipNextChartUpdate = true;
requestPaint();
}
onPaint: {
if (!chartInstance) {
initializeChart()
} else if(!skipNextChartUpdate) {
chartInstance.scale.update({width:width,height:height});
chartInstance.resize(chartInstance.update);
} else {
skipNextChartUpdate = false;
}
}
This is more or less as I would expect. requestPaint is not an immediate operation, it's a request to repaint at a later point. So if you call requestPaint multiple times in a single JS function, you will only receive one onPaint event per (vsync) frame.
So if you want to drive an animation, you should be driving them inside onPaint, by calling requestPaint (to ask for another onPaint event in the future) if there is still more animation to show for instance.