Working on a project and cannot seem to get my animation right. I will not be showing the code because it simply doesn't work but it would be cool if someone were to give me a few pointers on how to animate a cloud of smoke moving upwards while slowly fading and increasing in size.
This effect should technically repeat once the y value reaches 0 i.o.w. the cloud reaches the top of the canvas.
What I need to know is how do I animate this, and which methods do I use. This is a kind of a self learning assignment.
Thanks in advance.
Here is a Plunker example of sprites growing in size and fading in transparency.
It is done using Pixi.js which actually renders in webgl with a canvas fallback. It should be possible to take the algorithm and apply it to raw canvas (although it would take some work).
var insertAfter = function(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
var range = function(aCount) {
return new Array(aCount)
}
function main() {
var el_main = document.getElementById("animation_main");
var el_div = document.createElement('div');
el_div.setAttribute('id', 'main_stage');
insertAfter(el_div, el_main);
renderer = PIXI.autoDetectRenderer(300, 300, {
transparent: true,
antialias: true
});
el_div.appendChild(renderer.view);
window.stage = new PIXI.Container();
window.stage.x = 0;
window.stage.y = 0;
renderer.render(window.stage);
var s = [];
for (x of range(400)) {
tCircle = new PIXI.Graphics();
tCircle.beginFill(0x000000, 1);
tCircle.s = (Math.random() * 2) + 1;
tCircle.drawCircle(0, 0, 5 - tCircle.s);
tCircle.x = Math.random() * 300
tCircle.y = (Math.random() * 50) + 20
tCircle.endFill();
s.push(tCircle);
window.stage.addChild(tCircle)
}
window.t = 0
animate = function(t) {
d = t - window.t
window.t = t
//Animation Start
for (n in s){
s[n].x += ((s[n].s / 25) * d)
s[n].alpha = 1 - s[n].x / 300
s[n].scale.x = 1 - s[n].alpha
s[n].scale.y = 1 - s[n].alpha
if (s[n].x > 300) {
s[n].x = 0
s[n].y = (Math.random() * 50) + 20
}
}
renderer.render(window.stage)
//Animation End
requestAnimationFrame(animate);
}
requestAnimationFrame(animate)
}
document.addEventListener("DOMContentLoaded", function(e){
main();
});
At the moment all of the tweening is linear ... it might look more realistic with a logarithmic or exponential tween ... but for simplicity i just left it as linear.
Jakob Jenkov has done a really nice on-line book about canvas here:
http://tutorials.jenkov.com/html5-canvas/index.html
Since yours is a learning experience, I would just point you towards:
The basic workflow of html5 Canvas: Anything drawn on the canvas cannot be altered, so all canvas animation requires repeatedly doing these things in an animation loop: (1) clearing the canvas, (2) calculating a new position for your objects, and (3) redrawing the objects in their new positions.
Animations: requestAnimationFrame as a timing loop: http://blog.teamtreehouse.com/efficient-animations-with-requestanimationframe
Transformations: Canvas gives you the ability to scale, rotate and move the origin of its drawing surface.
Styling: Canvas provides all the essential styling tools for drawing--including globalAlpha which sets opacity.
Related
I am trying to implement a game where there are moving objects(Bitmaps) and I need to detect collision. I used the following code to make objects move(transform) and check hitTest with mouse hover.
However, the alpha is not changed with the correct mouse position, instead, it detects the left upper corner of the canvas.
rock = new createjs.Bitmap(loader.getResult("rock"));
rock.setTransform(800, 270, 0.5, 0.5);
stage.addChild(rock);
// .....
createjs.Ticker.timingMode = createjs.Ticker.RAF;
createjs.Ticker.addEventListener("tick", tick);
// .....
function tick(event) {
rock.alpha = 0.7;
if (rock.hitTest(stage.mouseX, stage.mouseY)) {//if hit, change alpha
rock.alpha = 1;
}
var deltaS = event.delta / 1000;
rock.x = (rock.x - deltaS * groundSpeed);//to gradually shift the rock
if (rock.x + rock.image.width * rock.scaleX <= 0) {//to re-position the rock
rock.x = w + Math.random() * 1000;
}
stage.update(event);
}
I found the answer when reading the answer to this question.
Rather than directly getting stage.mouseX or stage.mouseY, the correct position can be obtained using globalToLocal as on this.
var p = rock.globalToLocal(stage.mouseX, stage.mouseY);
if (rock.hitTest(p.x, p.y)) {
rock.alpha = 1;
}
I'm trying to make a game with top-down perspective where the player sprite is fixed in the center of screen and the background is moving (scrolling) in the direction opposite to the player's direction, so it results in effect of the player's movement.
I started with these examples: Invaders (for background scrolling) and Asteroids Movement (for sprite movement). The former uses TileSprite and its tilePosition property to make vertical scrolling of the background. This works good for linear scrolling in fixed direction (vertical or horizontal). But in my case I need implement scrolling (i.e. movement) in any direction. Furthermore I need such physics features as acceleration and drag, like in the Asteroids example. So I would like to apply Arcade Physics to TileSprite. But it seems that Arcade Physics doesn't work with TileSprite. Or, to be more precise, it doesn't work as I expected.
I've tried to enable Arcade Physics for TileSprite as it used with Sprites. Here is the code:
function preload() {
game.load.baseURL = 'http://examples.phaser.io/assets/';
game.load.crossOrigin = 'anonymous';
game.load.image('ship', 'games/invaders/player.png');
game.load.image('starfield', 'games/invaders/starfield.png');
}
var player;
var cursors;
var starfield;
var playerAngle = 0; // angle of the player's movement, in degrees
var playerSpeed = 5; // the player's speed, px per frame
function create() {
game.physics.startSystem(Phaser.Physics.ARCADE);
// The scrolling starfield background
starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');
game.physics.enable(starfield, Phaser.Physics.ARCADE);
starfield.body.velocity.set(200);
// The player sprite
player = game.add.sprite(400, 300, 'ship');
player.anchor.setTo(0.5, 0.5);
// Gameplay controls
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
// Scroll the background
var delta = 0;
if (cursors.left.isDown)
delta = -1;
else if (cursors.right.isDown)
delta = 1;
if (delta)
player.angle = playerAngle = (playerAngle + delta + 360) % 360;
var a = (playerAngle + 90) / 360 * 2 * Math.PI; // angle of the background movement/scrolling, in radians
// move the background by playerSpeed along the a angle
game.physics.arcade.velocityFromRotation(a, 200, starfield.body.velocity);
}
Try at Phaser sandbox
Unfortunately this code doesn't work as I expected.
As far as I understood and as written in the docs, Arcade Physics is only intended for work with Sprite, not TileSprite nor any other game object. If so, this means that I have to manually reproduce all the Physics stuff such as acceleration and drag myself with respect to TileSprite. I've written simple TileSprite movement with constant velocity (no acceleration, drag and other stuff). Here is the code:
function preload() {
game.load.baseURL = 'http://examples.phaser.io/assets/';
game.load.crossOrigin = 'anonymous';
game.load.image('ship', 'games/invaders/player.png');
game.load.image('starfield', 'games/invaders/starfield.png');
}
var player;
var cursors;
var starfield;
var playerAngle = 0; // angle of the player's movement, in degrees
var playerSpeed = 5; // the player's speed, px per frame
function create() {
game.physics.startSystem(Phaser.Physics.ARCADE);
// The scrolling starfield background
starfield = game.add.tileSprite(0, 0, 800, 600, 'starfield');
// The player sprite
player = game.add.sprite(400, 300, 'ship');
player.anchor.setTo(0.5, 0.5);
// Gameplay controls
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
// Scroll the background
var delta = 0;
if (cursors.left.isDown)
delta = -1;
else if (cursors.right.isDown)
delta = 1;
if (delta)
player.angle = playerAngle = (playerAngle + delta + 360) % 360;
var a = (playerAngle + 90) / 360 * 2 * Math.PI; // angle of the background movement/scrolling, in radians
// move the background by playerSpeed along the a angle
starfield.tilePosition.x += playerSpeed * Math.cos(a);
starfield.tilePosition.y += playerSpeed * Math.sin(a);
}
Try at Phaser sandbox
Sure it works, however even such simple thing as linear motion require some math knowledge. Other things will require much more knowledge of math and physics. I would not like to implement such things manually.
So is there any way to use Phaser Physics in conjunction with tilePosition?
My suggestion is to rethink the whole thing. Main idea is to not make the player sprite fixed, but to make it "dynamic" with physics enabled. To have your desired effect, just make the camera follow the player sprite (with no deadzone). That way, the player remains in the center, and manage the tilesprite classically.
For some reason my rendering stutters sometimes. As you can see here(http://sirius-btx.com/test/).
Use your arrow keys to move.
The bottom canvas is prerendered only once, the prerender will then be drawn on the main upper canvas every frame.
Here is my code thats gets called every frame:
var tick = (function() {
var lastTimestamp = 0;
return function(timestamp) {
dt = (timestamp - lastTimestamp) / 1000;
lastTimestamp = timestamp;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, self.resolution.width, self.resolution.height);
var cameraPosition = camera.position;
if(keys[37]) {
cameraPosition.x -= 10 * dt;
}
if(keys[38]) {
cameraPosition.y -= 10 * dt;
}
if(keys[39]) {
cameraPosition.x += 10 * dt;
}
if(keys[40]) {
cameraPosition.y += 10 * dt;
}
camera.position = cameraPosition;
var tileSet = self.resource.get("tiles");
ctx.save();
// mapCamera starts at the same position as camera.
// So (mapCamera.position - camera.position) is the offset we have to move.
ctx.translate((mapCamera.position.x - camera.position.x) * 16, (mapCamera.position.y - camera.position.y) * 16);
// mapCanvas is the prerendered canvas.
ctx.drawImage(mapCanvas, -16, -16, self.resolution.width + 32, self.resolution.height + 32);
ctx.restore();
requestAnimationFrame(tick);
};
})();
I've been trying to figure out why its not 100% smooth, but I can't find a solution.
Someone has an idea why it is happening?
3 advices here :
• Call early requestAnimationFrame : with my modest testing , it showed that things were a bit smoother when rAF is the first function called. Most probably because it needs behind-the-scene work to get to work. Try it !
• When using drawImage, coordinates will be rounded for you : you might want to do the rounding yourself, since it is 'floor' that is used, and that 'ceil' that gives smoother results.
• Most importantly, you are using the DOM (divs or like) to show the mouse position / fps. Don't do this. use fillText on your canvas (or on some other visible canvas), because you can't be sure you cause a reflow/repaint when changing those values.
I try to write an isometric tile game engine and have problem with speed of this code:
$(function() {
var canvas = document.getElementById('GameCanvas');
var context = document.getElementById('GameCanvas').getContext('2d');
var imgObj = new Image();
imgObj.src = 'img/sand_surface.png';
var Game = {
tileScaleX: 64,
tileScaleY: 32,
FPSLimit: 50, // max allowed framerate
realFPS: 0, // real framerate
init: function() {
this.cycle(); // main animation loop
},
cycle: function() {
this.debug(); // print framerate
startTime = new Date; // start fps time
this.clear(); // celar canvas
this.draw(); // draw frame
endTime = new Date; // end fps time
setTimeout(function() {
endTimeWithSleep = new Date; // end fps time with sleep
this.realFPS = 1000 / (endTimeWithSleep - startTime);
this.cycle(); // repeat animation loop
}.bind(this), (1000 / this.FPSLimit) - (endTime - startTime));
},
debug: function() {
$('.DebugScreen').html('<b>FPS:</b> ' + Math.round(this.realFPS*1)/1);
},
clear: function() {
canvas.width = canvas.width; // clear canvas
},
draw: function() {
Location.drawSurface(); // draw tiles
},
}
var Location = {
width: 60,
height: 120,
drawSurface: function() {
for (y = 0; y < this.height; y++) {
for (x = 0; x < this.width; x++) {
if ((y % 2) == 0) {
rowLeftPadding = 0;
} else {
rowLeftPadding = Game.tileScaleX / 2;
}
context.drawImage(imgObj, (x * Game.tileScaleX + rowLeftPadding), y * (Game.tileScaleY / 2), Game.tileScaleX, Game.tileScaleY);
}
}
},
}
Game.init(); // start game
});
If I set Location.width and Location.height to low numbers, then it run fast (50 fps) but in this example (Location.width = 60, Location.height = 120) framerate is 10 fps and I need 50 fps, do you have any sugestions how to speed up this script?
1) Seems to me that you are drawing every tile, even if they are not in view. Use "clipping". You need to calculate whether the tile is in view before calling context.drawImage().
2) If your scenery is static, precalculate it (as much as possible). However, creating a huge image is not a good idea either, you would rather precalculate some big chunks (i.e. 512x512).
3) In some browsers, it is said you can get better frame rates if instead of using 'setTimeout()' you use requestAnimationFrame (I also found this article quite interesting).
4) Resizing/scaling may impact performance (especially in older browser or hardware). If your tiles are already 32x64, you can use drawImage() with only 3 parameters, avoiding resizing (not applicable if you do need to scale to achieve zoom effects or similar).
In addition to #jjmontes excellent answer, you should also use multiple canvas elements in your game and only update the portions of the canvas that have changed. Right now you are clearing and redrawing everything each time.
After messing with your code on my side Im getting between 45-50 with the code you posted. One suggestion is to not use jQuery, and also don't modify the html of an element to display the fps. I also modified your demo to max out at 100 frames, and its getting about 70 fps.
You can Also try cacheing the resized image and use that instead, you should see an increase in performance. In the below demo I cache the resized image on a temp canvas and use it instead.
Live Demo (I didnt feel like implementing an onload for the image, so if its a white screen just hit run again)
I'm making a small platform game with the canvas element and I'm having trouble working out the best way to use sprites.
Canvas doesn't support animated GIFs, so I can't do that, so instead I made a film strip for my animated sprites to give the illusion that a character is moving. Like this: http://i.imgur.com/wDX5R.png
Here's the relevant code:
function player() {
this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frameCount = 12;
this.runningSprite = new Image();
this.runningSprite.src = "/game/images/runningSprite.png";
this.runningSprite.frameWidth = 34;
this.update = function() {
}
var frameCount = 1;
this.draw = function() {
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
if(frameCount < this.idleSprite.frameCount - 1) { frameCount++; } else { frameCount = 0; }
}
}
var player = new player();
As you can see, I'm defining the idle sprite and also its frame width and frame count. Then in my draw method, I'm using those properties to tell the drawImage method what frame to draw. Everything works fine, but I'm unhappy with the frameCount variable defined before the draw method. It seems... hacky and ugly. Would there be a way that people know of to achieve the same effect without keeping track of the frames outside the draw method? Or even a better alternative to drawing animated sprites to canvas would be good.
Thanks.
You could select the frame depending on some fraction of the current time, e.g.
this.draw = function() {
var fc = this.idleSprite.frameCount;
var currentFrame = 0 | (((new Date()).getTime()) * (fc/1000)) % fc;
c.drawImage(this.idleSprite, this.idleSprite.frameWidth * currentFrame, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, 0, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
}
That will give you animation with a period of one second (the 1000 is a millisecond value). Depending on your frame rate and animation this might look jerky, but it doesn't rely on persistent counters.