The following is an example code I wrote just to show I handle certain things on my game:
https://jsfiddle.net/qk7ayx7n/25/
<canvas id = "canvas"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
JS:
var canvas = document.getElementById("canvas");
var ctx=canvas.getContext("2d");
canvas.width = 750; //keeping ratio
canvas.height = 587; //keeping ratio
$('#canvas').css("height", window.innerHeight);
$('#canvas').css("width", window.innerHeight * 1.277); //keeping the ratio
//and also resizing according to the window(not to overflow)
var board = new Image();
board.src = "https://s21.postimg.org/ko999yaaf/circ.png";
var circle = new Image();
circle.src = "https://s21.postimg.org/4zigxdh7r/circ.png";
ctx.drawImage(board, 0, 0);
var x = 10, y = 10;
ctx.drawImage(circle, x, y);
startMoving();
function startMoving(){
if(y > 310) return;
y+=3;
ctx.clearRect(0,0,750,587);
ctx.drawImage(board, 0, 0);
ctx.drawImage(circle, x, y);
setTimeout(function(){startMoving()}, 30);
}
A little explanation: This is a simple board game. first the canvas is set to the board dimensions themselves in order to get the coordinates X Y correctly(this is not useful here but in my actual game yes).
then it is resized according to the window of the player, with regards to the actual ratio of the original board image. keeping the ratio is important for the quality of the image.
Now the movement is done with a simple timer in a function, once it gets to a certain X and Y the movement is stopped.
I have trouble getting the movement of the circle to move without breaks/lags in some browsers and devices (like on an cordova app), though it works fine usually. I know that the lags are caused by the way I handle things, but why?
also, I have trouble keeping the speed of the movement constant - +3 doesn't seem to move the same in every browser.
In most cases, you should use requestAnimationFrame for JavaScript-based animations to avoid choppiness. With this technique, the position is a function of time not how many execution frames take place. This way, fast computers will have more animation frames than slow computers, but you'll still perceive the same animation velocity. For example:
var x = 10, y = 10;
var startPos = 10;
var destPos = 310;
var startTime = Date.now();
var velocity = 0.1; // pixels per millisecond
var distance = destPos - startPos;
var duration = Math.abs(distance) / velocity;
requestAnimationFrame(startMoving);
function startMoving(now) {
var elapsedTime = Math.min(now - startTime, duration);
y = startPos + (elapsedTime * velocity);
ctx.clearRect(0,0,750,587);
ctx.drawImage(board, 0, 0);
ctx.drawImage(circle, x, y);
if (elapsedTime < duration)
requestAnimationFrame(startMoving);
}
Related
I'm writing a simple game in which a user can move around a sprite. By clicking the stage, the sprite moves towards that location. The problem I'm facing is that I want to set a speed for this sprite. I do not know the values the user is going to click. I can't think of a way in which the sprite's speed is always the same.
The thing with PIXI.js is that you can set the x and y movement speed of the sprite. I want the result of those movement speeds to always be the same, for example 5. So if the sprite moves down, the y-speed would be 5. When the sprite is moving diagonally, the diagonal speed should be 5. I currently use this script, but the solution I came up with does not completely work, as the speed differs for each time I click.
Does anyone have any idea how to solve this problem?
var Container = PIXI.Container,
autoDetectRenderer = PIXI.autoDetectRenderer,
loader = PIXI.loader,
resources = PIXI.loader.resources,
Sprite = PIXI.Sprite;
var stage = new PIXI.Container(),
renderer = PIXI.autoDetectRenderer(1000, 1000);
document.body.appendChild(renderer.view);
PIXI.loader
.add("rocket.png")
.load(setup);
var rocket, state;
function setup() {
//Create the `tileset` sprite from the texture
var texture = PIXI.utils.TextureCache["animal.png"];
//Create a rectangle object that defines the position and
//size of the sub-image you want to extract from the texture
var rectangle = new PIXI.Rectangle(192, 128, 32, 32);
//Tell the texture to use that rectangular section
texture.frame = rectangle;
//Create the sprite from the texture
rocket = new Sprite(texture);
rocket.anchor.x = 0.5;
rocket.anchor.y = 0.5;
rocket.x = 50;
rocket.y = 50;
rocket.vx = 0;
rocket.vy = 0;
//Add the rocket to the stage
stage.addChild(rocket);
document.addEventListener("click", function(){
x = event.clientX - rocket.x;
y = event.clientY - rocket.y;
rocket.vmax = 5;
var total = Math.abs(x) + Math.abs(y);
var tx = x/total;
var ty = y/total;
rocket.vx = tx*rocket.vmax;
rocket.vy = ty*rocket.vmax;
});
state = play;
gameLoop();
}
function gameLoop() {
//Loop this function at 60 frames per second
requestAnimationFrame(gameLoop);
state();
//Render the stage to see the animation
renderer.render(stage);
}
function play(){
rocket.x += rocket.vx;
rocket.y += rocket.vy;
}
How about this? This would normalize x and y.
var total = Math.Sqrt(x * x + y * y);
and it looks x and y are missing 'var'.
var x = event.clientX - rocket.x;
var y = event.clientY - rocket.y;
I have made a program to make squares that produce smaller squares on the top and left that are smaller then the they where made form but running into problems controlling the variant of their sizes.
The live code can be found
jsfiddle link
main function to make the squares:
function createCubes(maxX, maxY, minX, minY,lastColor)
{
if (maxX - minX < 50 || maxY - minY < 50 )
{
return;
}
//var decayRate = .5;
var x = getNumber(minX+50, maxX-50);
var y = getNumber(minY+50, maxY-50);
var width = maxX - x;
var height = maxY - y;
var color;
do
{
color = getNumber(0, colors.length);
}
while(color == lastColor);
var tempCube = new Cube(color, x, y, width, height);
cubes.push(tempCube);
createCubes(maxX, y, x, minY,color);
createCubes(x, maxY,minX, y,color);
}
I tried increasing the min and deceasing the max values put into the getNumber function but it resulted in the squares going out of bounds.
Yes I know I called them cubes in the program.
if you need any explaining comment I will try to get to it as fast as possible.
Thanks for the Help!
Update:
I found that when subtracting the max value and setting the base chase to what I subtracted helps keep them nicer but you don't get as many.
Update:
added color and an attempt to control the squares. They still decay at an uncontrollable rate
Use requestAnimationFrame as a timing loop. rAF automatically sends a timestamp argument that you can use to control the drawing rate (decay rate) of your rectangles.
Here is annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var rectSize=100;
var rectResizing=0.75;
var rectX=0;
var nextTime=0;
var decayDelay=500;
var decayRate=0.95;
var loopCount=0;
var labelY=150;
requestAnimationFrame(decayLoop);
function decayLoop(time){
// wait for elapsed time
if(time<nextTime){requestAnimationFrame(decayLoop);return;}
// reset for nextTime
nextTime=time+decayDelay;
// update the decay
decayDelay*=decayRate;
// draw the decayed rect
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(rectX,20,rectSize,rectSize);
rectX+=rectSize;
rectSize*=rectResizing;
// display current decayDelay & rectSize
//ctx.clearRect(0,0,cw,40);
ctx.fillStyle='black';
ctx.fillText('Loop count: '+(loopCount++)+', RectSize: '+parseInt(rectSize)+', DecayDelay: '+decayDelay,10,labelY);
labelY+=12;
// request another loop
if(rectSize>=1){
requestAnimationFrame(decayLoop);
}else{
alert('End: Rect size has decayed below 1px');
}
}
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="canvas" width=512 height=512></canvas>
I am new with canvas and I've been Googling for a couple of hours, but I am stuck.
What I would like to do is to render a video on a canvas element, divide it and animate the pieces. I am halfway there (see: http://jsbin.com/riduxadazi/edit?html,css,js,console,output ) but I have a couple of questions:
Am I doing things right, or is this extremly inefficient?
I would like to use the video fullscreen. Whatever I try, the canvas grid + video don't seem to match size.
I would like to animate the pieces of the video, but I have no clue how I should address them. Can I get some sort of array and animate the pieces one by one?
My JS looks like this. I tried to add comments to the most important parts. At least what I think were the most important parts ;)
var video = document.getElementById('video'); // Get the video
var ctx = canvas.getContext('2d'),
columns = 6,
rows = 4,
w, h, tileWidth, tileHeight;
// Start video and add it to canvas
video.addEventListener('play', function() {
var $this = this; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
ctx.drawImage($this, 0, 0,window.innerWidth,window.innerHeight);
calcSize(); // Divide video
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
}, 0);
function calcSize() {
video.width = w = window.innerWidth;
video.height = h = window.innerHeight;
tileWidth = w / columns;
tileHeight = h / rows;
ctx.strokeStyle = '#000';
render();
}
function render() {
for(var x = 0; x < columns; x++) {
ctx.moveTo(x * tileWidth, 0);
ctx.lineTo(x * tileWidth, h);
}
for(var y = 0; y < rows; y++) {
ctx.moveTo(0, y * tileHeight);
ctx.lineTo(w, y * tileHeight);
}
ctx.stroke();
}
You would perhaps consider:
Using requestAnimationFrame to update the loop. This allows for perfect synchronization with the monitor update rate as well as being more efficient than setTimeout/setInterval You could throttle it so you only update per 1/30 frame to match video rate by using a simple boolean flag that alternates.
The video element does not need to be inserted into DOM. Also, the actual video bitmap size is read through the properties videoWidth and videoHeight, though, in the provided code you should use canvas' properties width and height as this determine the destination size. To draw proportional you can for example use this answer.
Using drawImage() using the clipping parameters would be the more efficient way to draw video onto canvas if you want to split the content.
You could split your video using a mathematical approach (see this answer) or using objects which allows you to define source regions and have individual properties on it such as position, rotation, scale and so forth. In case you would have to consider destination position to adopt to the current size of canvas.
I am trying to design a traveling sine wave in JavaScript, but the design appears quite slow. The main bottleneck is the clearRect() for canvas clearing.
How can I solve this?
Also I am drawing the pixel by ctx.fillRect(x, y,1,1), but when I clear using clearRect(x, y,1,1), it leaves some footprints. Instead I have to do clearRect(x, y,5,5) to get proper clearing. What can be the work around?
/******************************/
var x = 0;
var sineval = [];
var offset = 0;
var animFlag;
function init() {
for(var i=0; i<=1000; ++i){
sineval[i] = Math.sin(i*Math.PI/180);
}
// Call the sineWave() function repeatedly every 1 microseconds
animFlag = setInterval(sineWave, 1);
//sineWave();
}
function sineWave()
{ //console.log('Drawing Sine');
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
}
for(x=0 ; x<1000 ;++x){
// Find the sine of the angle
//var i = x % 361;
var y = sineval[x+offset];
// If the sine value is positive, map it above y = 100 and change the colour to blue
if(y >= 0)
{
y = 100 - (y-0) * 70;
ctx.fillStyle = "green";
}
// If the sine value is negative, map it below y = 100 and change the colour to red
if( y < 0 )
{
y = 100 + (0-y) * 70;
ctx.fillStyle = "green";
}
// We will use the fillRect method to draw the actual wave. The length and breath of the
if(x == 0) ctx.clearRect(0,y-1,5,5);
else ctx.clearRect(x,y,5,5);
ctx.fillRect(x, y,1,1 /*Math.sin(x * Math.PI/180) * 5, Math.sin(x * Math.PI/180 * 5)*/);
}
offset = (offset > 360) ? 0 : ++offset ;
}
You need to refactor the code a bit:
Move all global variables such as canvas and context outside of the loop function
Inside the loop, clear full canvas at beginning, redraw sine
Use requestAnimationFrame instead of setInterval
Replace fillRect() with rect() and do a single fill() outside the inner for-loop
Using a timeout value of 1 ms will potentially result in blocking the browser, or at least slow it down noticeably. Considering that a monitor update only happens every 16.7ms this will of course be wasted cycles. If you want to reduce/increase the speed of the sine you can reduce/increase the incremental step instead.
In essence:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var sineval = [];
var offset = 0;
init();
function init() {
for (var i = 0; i <= 1000; ++i) {
sineval.push(Math.sin(i * Math.PI / 180));
}
// Call the sineWave() function
sineWave();
}
function sineWave() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.fillStyle = "green";
// draw positive part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y >= 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "red";
// draw negative part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y < 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
offset = (offset > 360) ? 0 : ++offset;
requestAnimationFrame(sineWave);
}
<canvas id="canvas" width=800 height=500></canvas>
And of course, if you load the script in <head> you need to wrap it in a window.onload block so canvas element is available. Or simply place the script at the bottom of the page if you haven't already.
A few speedups and odd ends:
In init, set up the sine wave pixel values one time.
Use typed arrays for these since sticking with integers is faster than using floats if possible.
We will manipulate the pixel data directly instead of using fill and clear. To start this, in init we call ctx.getImageData one time. We also just one time max the alpha value of all the pixels since the default 0 value is transparent and we want full opacity at 255.
Use setInterval like before. We want to update the pixels at a steady rate.
Use 'adj' as knob to adjust how fast the sine wave moves on the screen. The actual value (a decimal) will depend on the drawing frame rate. We use Date.now() calls to keep track of milliseconds consumed across frames. So the adjustment on the millisecond is mod 360 to set the 'offset' variable. Thus offset value is not inc by 1 every frame but instead is decided based on the consumption of time. The adj value could later be connected to gui if want.
At end of work (in sineWave function), we call requestAnimationFrame simply to do the ctx.putImageData to the canvas,screen in sync to avoid tearing. Notice 'paintit' function is fast and simple. Notice also that we still require setInterval to keep steady pace.
In between setting the offset and calling requestAnimationFrame, we do two loops. The first efficiently blackens out the exact pixels we drew from the prior frame (sets to 0). The second loop draws the new sine wave. Top half of wave is green (set the G in pixel rgba to 255). Bottom half is red (set the R pixel rgba to 255).
Use the .data array to paint a pixel, and index it to the pixel using 4x + 4y*canvas.width. Add 1 more if want the green value instead of the red one. No need to touch the blue value (byte offset 2) nor the already set alpha (byte offset 3).
The >>>0 used in some places turns the affected value into an unsigned integer if it wasn't already. It can also be used instead of Math.ceil. .data is typed Array already I think.
This answer is rather late but it addresses some issues brought up in comments or otherwise not yet addressed. The question showed up during googling.
Code hasn't been profiled. It's possible some of the speedups didn't speed anything up; however, the cpu consumption of firefox was pretty light by the end of the adjustments. It's set to run at 40 fps. Make 'delay' smaller to speed it up and tax cpu more.
var sineval;
var offset = 0;
var animFlag;
var canvas;
var ctx;
var obj;
var milli;
var delay=25;
var adj=1/delay; // .04 or so for 25 delay
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
obj=ctx.getImageData(0,0,canvas.width,canvas.height);
for (let i=0; i<obj.data.length; i+=4) {
obj.data[i+3]=255; //set all alpha to full one time only needed.
}
sineval=new Uint8Array(1400); //set up byte based table of final pixel sine values.. 1400 degrees total
for (let i=0; i<=1400; ++i) { //1400
sineval[i] = (100-70*Math.sin(i*Math.PI/180))>>>0;
}
animFlag = setInterval(sineWave, delay); //do processing once every 25 milli
milli=Date.now()>>>0; //start time in milli
}
function sineWave() {
let m=((Date.now()-milli)*adj)>>>0;
let oldoff = offset;
offset=(m % 360)>>>0; //offset,frequency tuned with adj param.
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+oldoff];
obj.data [0+x*4+y*4*canvas.width]=0; //black the reds
obj.data [1+x*4+y*4*canvas.width]=0; //black the greens
}
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+offset];
if (y<100) {
obj.data [1+x*4+y*4*canvas.width]=255; //rGba //green for top half
} else {
obj.data [0+x*4+y*4*canvas.width]=255; //Rgba //red for bottom half
}
}
requestAnimationFrame(paintit); //at end of processing try to paint next frame boundary
}
function paintit() {
ctx.putImageData(obj,0,0);
}
init();
<canvas id="canvas" height=300 width=1000></canvas>
I'd like to throw a ball (with an image) into a 2d scene and check it for a collision when it reached some distance. But I can't make it "fly" correctly. It seems like this has been asked like a million times, but with the more I find, the more confused I get..
Now I followed this answer but it seems, like the ball behaves very different than I expect. In fact, its moving to the top left of my canvas and becoming too little way too fast - ofcouse I could adjust this by setting vz to 0.01 or similar, but then I dont't see a ball at all...
This is my object (simplyfied) / Link to full source who is interested. Important parts are update() and render()
var ball = function(x,y) {
this.x = x;
this.y = y;
this.z = 0;
this.r = 0;
this.src = 'img/ball.png';
this.gravity = -0.097;
this.scaleX = 1;
this.scaleY = 1;
this.vx = 0;
this.vy = 3.0;
this.vz = 5.0;
this.isLoaded = false;
// update is called inside window.requestAnimationFrame game loop
this.update = function() {
if(this.isLoaded) {
// ball should fly 'into' the scene
this.x += this.vx;
this.y += this.vy;
this.z += this.vz;
// do more stuff like removing it when hit the ground or check for collision
//this.r += ?
this.vz += this.gravity;
}
};
// render is called inside window.requestAnimationFrame game loop after this.update()
this.render = function() {
if(this.isLoaded) {
var x = this.x / this.z;
var y = this.y / this.z;
this.scaleX = this.scaleX / this.z;
this.scaleY = this.scaleY / this.z;
var width = this.img.width * this.scaleX;
var height = this.img.height * this.scaleY;
canvasContext.drawImage(this.img, x, y, width, height);
}
};
// load image
var self = this;
this.img = new Image();
this.img.onLoad = function() {
self.isLoaded = true;
// update offset to spawn the ball in the middle of the click
self.x = this.width/2;
self.y = this.height/2;
// set radius for collision detection because the ball is round
self.r = this.x;
};
this.img.src = this.src;
}
I'm also wondering, which parametes for velocity should be apropriate when rendering the canvas with ~ 60fps using requestAnimationFrame, to have a "natural" flying animation
I'd appreciate it very much, if anyone could point me to the right direction (also with pseudocode explaining the logic ofcourse).
Thanks
I think the best way is to simulate the situation first within metric system.
speed = 30; // 30 meters per second or 108 km/hour -- quite fast ...
angle = 30 * pi/180; // 30 degree angle, moved to radians.
speed_x = speed * cos(angle);
speed_y = speed * sin(angle); // now you have initial direction vector
x_coord = 0;
y_coord = 0; // assuming quadrant 1 of traditional cartesian coordinate system
time_step = 1.0/60.0; // every frame...
// at most 100 meters and while not below ground
while (y_coord > 0 && x_coord < 100) {
x_coord += speed_x * time_step;
y_coord += speed_y * time_step;
speed_y -= 9.81 * time_step; // in one second the speed has changed 9.81m/s
// Final stage: ball shape, mass and viscosity of air causes a counter force
// that is proportional to the speed of the object. This is a funny part:
// just multiply each speed component separately by a factor (< 1.0)
// (You can calculate the actual factor by noticing that there is a limit for speed
// speed == (speed - 9.81 * time_step)*0.99, called _terminal velocity_
// if you know or guesstimate that, you don't need to remember _rho_,
// projected Area or any other terms for the counter force.
speed_x *= 0.99; speed_y *=0.99;
}
Now you'll have a time / position series, which start at 0,0 (you can calculate this with Excel or OpenOffice Calc)
speed_x speed_y position_x position_y time
25,9807687475 14,9999885096 0 0 0
25,72096106 14,6881236245 0,4286826843 0,2448020604 1 / 60
25,4637514494 14,3793773883 0,8530785418 0,4844583502 2 / 60
25,2091139349 14,0737186144 1,2732304407 0,7190203271
...
5,9296028059 -9,0687933774 33,0844238036 0,0565651137 147 / 60
5,8703067779 -9,1399704437 33,1822622499 -0,0957677271 148 / 60
From that sheet one can first estimate the distance of ball hitting ground and time.
They are 33,08 meters and 2.45 seconds (or 148 frames). By continuing the simulation in excel, one also notices that the terminal velocity will be ~58 km/h, which is not much.
Deciding that terminal velocity of 60 m/s or 216 km/h is suitable, a correct decay factor would be 0,9972824054451614.
Now the only remaining task is to decide how long (in meters) the screen will be and multiply the pos_x, pos_y with correct scaling factor. If screen of 1024 pixels would be 32 meters, then each pixel would correspond to 3.125 centimeters. Depending on the application, one may wish to "improve" the reality and make the ball much larger.
EDIT: Another thing is how to project this on 3D. I suggest you make the path generated by the former algorithm (or excel) as a visible object (consisting of line segments), which you will able to rotate & translate.
The origin of the bad behaviour you're seeing is the projection that you use, centered on (0,0), and more generally too simple to look nice.
You need a more complete projection with center, scale, ...
i use that one for adding a little 3d :
projectOnScreen : function(wx,wy,wz) {
var screenX = ... real X size of your canvas here ... ;
var screenY = ... real Y size of your canvas here ... ;
var scale = ... the scale you use between world / screen coordinates ...;
var ZOffset=3000; // the bigger, the less z has effet
var k =ZOffset; // coeficient to have projected point = point for z=0
var zScale =2.0; // the bigger, the more a change in Z will have effect
var worldCenterX=screenX/(2*scale);
var worldCenterY=screenY/(2*scale);
var sizeAt = ig.system.scale*k/(ZOffset+zScale*wz);
return {
x: screenX/2 + sizeAt * (wx-worldCenterX) ,
y: screenY/2 + sizeAt * (wy-worldCenterY) ,
sizeAt : sizeAt
}
}
Obviously you can optimize depending on your game. For instance if resolution and scale don't change you can compute some parameters once, out of that function.
sizeAt is the zoom factor (canvas.scale) you will have to apply to your images.
Edit : for your update/render code, as pointed out in the post of Aki Suihkonen, you need to use a 'dt', the time in between two updates. so if you change later the frame per second (fps) OR if you have a temporary slowdown in the game, you can change the dt and everything still behaves the same.
Equation becomes x+=vx*dt / ... / vx+=gravity*dt;
you should have the speed, and gravity computed relative to screen height, to have same behaviour whatever the screen size.
i would also use a negative z to start with. to have a bigger ball first.
Also i would separate concerns :
- handle loading of the image separatly. Your game should start after all necessary assets are loaded. Some free and tiny frameworks can do a lot for you. just one example : crafty.js, but there are a lot of good ones.
- adjustment relative to the click position and the image size should be done in the render, and x,y are just the mouse coordinates.
var currWidth = this.width *scaleAt, currHeight= this.height*scaleAt;
canvasContext.drawImage(this.img, x-currWidth/2, y-currHeight/2, currWidth, currHeight);
Or you can have the canvas to do the scale. bonus is that you can easily rotate this way :
ctx.save();
ctx.translate(x,y);
ctx.scale(scaleAt, scaleAt); // or scaleAt * worldToScreenScale if you have
// a scaling factor
// ctx.rotate(someAngle); // if you want...
ctx.drawImage(this.img, x-this.width/2, x-this.height/2);
ctx.restore();