Scaling a shape in Phaser 2 without having it move - javascript

So, I'm trying to create a (very) simple progress bar in Phaser, (initially it's empty) and getting it to resize/scale to a specific size.
I'm not a fan of erasing and redrawing, since I assume this is more resource-heavy / not as efficient as simply tweening.
Whenever I scale its width, it insists on moving to the right - it is also updating its X coordinate as well.
I do know that we should change/set the anchor. I tried setting it to 0.5 (center), to 0 (left, top) and to 1.0 (bottom, right) as I saw in this question (and in the Phaser forums), but no matter the value that I set, the progress bar always moves (and always to the same place) as well.
Here is my code:
//Fill
var graphs = game.add.graphics(0, 0);
graphs.lineStyle(2, 0xFF0000, 1);
graphs.beginFill(0xFF0000, 1);
progressBar = graphs.drawRect(100, 50, 1, 20);
progressBar.anchor.setTo(0.5, 0.5);
//Outline
graphs = game.add.graphics(0, 0);
graphs.lineStyle(2, 0xFFFFFF, 1);
progressBarOutline = graphs.drawRect(100, 50, 100, 20);
progressBarOutline.anchor.setTo(0.5, 0.5);
//Resize it whenever the user presses the game area
game.input.onDown.add( resizeProgressBar, this );
And here is the code that I call to resize the (fill) bar (currently done through a tween):
function resizeProgressBar()
{
game.add.tween(loadingProgressBar.scale).to( { x: 2.5},
1000, Phaser.Easing.Quadratic.InOut, true);
}
Here is a fiddle that shows the issue (click on the canvas to update the bar).
This can't be that complicated. Am I forgetting something? Or am I supposed to calculate the new x position myself?
I'm using Phaser CE 2.11

I'm not sure why the height of the progressBar is growing, nor why it's moving, as I haven't done much with the raw graphics drawing capabilities in Phaser. However, there's a few things I do know which might help.
The difference between the questions you've linked to and your progress bar is that you're using raw graphics, instead of sprites.
If you switch to using a sprite-based progress bar you could either scale or set the width, depending upon what your progress graphic looks like.
Another option would be to do something similar to this tutorial for progress bars in Phaser 3. The relevant bits are defining the progress bar and then creating it:
var progressBar = this.add.graphics();
/// ...
this.load.on('progress', function (value) {
progressBar.clear();
progressBar.fillStyle(0xffffff, 1);
progressBar.fillRect(250, 280, 300 * value, 30);
});
You can change the last five lines as needed for your particular circumstances.
While this does clear and then redraw the rectangle, I personally haven't seen much of a performance hit doing this. At least with older versions of Phaser 2, using Sprites instead of Graphics seems to be the way to go. Of course, it also depends upon how often and how many of these progress bars you'll be displaying.

Related

Draw out of canvas JavaScript

I have been developing a program which includes some sort of genetic algorithm. For my program, let's say there is a population of 200 units, and each unit can be in 5 different states. Inititlly, they all start at state 0, and they can randomly jump to states 1 to 4, and influence other units to jump as well. This way, the more units are on state 2, the more units will jump to state 2 and so on. I have these units moving randomly inside my canvas, bouncing off the walls when they hit them.
The one thing I want to do now is visualize the evolution on a chart, and for that I would like to have the canvas with the units jumping states on one side and the chart next to it, dynamically representing the percentage of units in state 0, 1, 2... simultaneously. I will presumably have no problem in coding the chart, however I cannot find a way of displaying it outside the canvas or without altering it.
Just in case, I am programming in Atom and have mostly used p5 libraries.
Any ideas??
You have 2 options:
Make a second canvas (Like enhzflep said), but this might be complicated for you, becuase you will not have access to P5.js drawing tools on that second canvas, look at this:
(On your first canvas)
fill(255,0,0)
rect(50,50,50,50);
To make and draw to a second canvas:
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
//deal with positioning, scaling, and other stuff (comment if you need help with that)
...
const c = canvas.getContext('2d');
c.fillStyle = "rgb(255,0,0)";
c.fillRect(50,50,50,50);
(See, lots of effort)
Or, you can just use your first canvas, and partition a section off that is dedicated to the graph
createCanvas(600 + graphWidth, 600);
//Wherever your bouncing off walls code is
//for the right side of the screen
if(this.x > width - graphWidth){
bounce();
}
//that leaves you a graphWidth by 600 rectangle for you to draw you graph
The second option is much easier to read and will save you some headaches (I would use that).

How to tween a line color in easeljs canvas

I have a series of dots with connected lines that I am animating in an easel.js canvas. The dots move around, and the lines stay connected to them as they move. As the dots move, I'm animating their color, so I want the lines to animate color as well.
I tried calling a color tween on the line, but it requires that I cache the line first.
For a circle, that's easy - I get the radius and, since its registration is in the center, its x and y coordinates and width and height are easy to calculate (for a circle with r=100 at 50,50, its cache would be cache(0,0,100,100). But for a line, I'm not sure how to reference the right coordinates for the cache statement, especially since the line start position, end position, and length are always changing.
Anyone have a way to do this?
I'm using greensock's timelinemax / tweenlite with the easeljs plugin to handle all the animations, if that's helpful.
If TweenLite handles color tweens, then you should just be able to update the "style" of your line any time:
var shape = new createjs.Shape();
var colorCommand = shape.graphics.beginStroke("#000000").command;
shape.graphics.moveTo(0,0).lineTo(100,100); // Draw the line
// Any time
colorCommand.style = "#ff0000";
// So in a tween:
TweenLite.to(colorCommand, 20, {style:"#00ffff"});
If you are using EaselJS, you can also use TweenJS, which has a ColorPlugin. Using similar code:
createjs.Tween.get(colorCommand).to({style:"#00fffff"}, 20000);
Here is a fiddle I made tweening the color of a line with TweenJS https://jsfiddle.net/lannymcnie/5zxpb944/
Cheers.

Parallax effect with zoom and rotating

I am currently experimenting with parallax effect that i am planning to implement to my HTML5-canvas game engine.
The effect itself is fairly easy to achieve, but when you add zooming and rotating, things get a little more complicated, at least for me. My goal is to achieve something like this:Youtube video.
As you can see, you can zoom in and out "to the center", and also rotate around it and get the parallax effect.
In my engine i want to have multiple canvases that are going to be my parallax layers, and i am going to translate them.
I came up with something like this:
var parallax = {
target: {
x: Mouse.x,
y: Mouse.y
},
offset: {
x: -ctx.width / 2,
y: -ctx.height / 2
},
factor: {
x: 1,
y: 1
}
}
var angle = 0;
var zoomX = 1;
var zoomY = 1;
var loop = function(){
ctx.canvas.width = ctx.canvas.width; //Clear the canvas.
ctx.translate(parallax.target.x * parallax.factor.x, parallax.target.y * parallax.factor.y);
ctx.rotate(angle);
ctx.scale(zoomX, zoomY);
ctx.translate((-parallax.target.x - parallax.offset.x) * parallax.factor.x, (-parallax.target.y - parallax.offset.y) * parallax.factor.y);
Draw(); //Function that draws all the objects on the screen.
}
This is a very small and simplified part of my script, but i hope that's enough to get what i am doing. The object "parallax" contains the target position, the offset(the distance from the target), and the factor that is determining how fast the canvas is moving away relatively to the target. ctx is the canvas that is moving in the opposite direction of the target.(In this example i am using only one layer.) I am using the mouse as the "target", but i could also use the player, or some other object with x and y property. The target is also the point around which i rotate and scale the canvas.
This method works completely fine as long as the factor is equal to 1. If it is something else, the whole thing suddenly stops working correctly, and when i try to zoom, it zooms to the top-left corner, not the target. I also noticed that if i zoom out too much, the canvas is not moving in the opposite way of the target, but in the same direction.
So my question is: What is the correct way of implementing parallax with zooming and rotating?
P.S. It is important to me that i am using canvases as the layers.
To prepare for the next animation frame, you must undo any previous transforms in the reverse order they were executed:
context.translate(x,y);
context.scale(sx,sy);
context.rotate(r);
// draw stuff
context.rotate(-r);
context.scale(-sx,-sy);
context.translate(-x,-y);
Alternatively, you can use context.save / context.restore to undo the previous transforms.
Adjust your parallax values for the current frame,
Save the un-transformed context state using context.save(),
Do your transforms (translate, scale, rotate, etc),
Draw you objects as if they were in non-transformed space with [0,0] at your translate point,
Restore your context to it's untransformed state using context.restore()/
Either way will correctly give you a default-oriented canvas to use for your next animation frame.
The exact parallax effects you apply are up to your own design, but using these methods will make the canvas return to a normal default state for you to design with.

What is the most efficient way to reset the size of a shape after scaling in PaperJS

I am attempting to create a very simple beacon-like animation in Paper JS. The idea is that a circle starts off very small and totally opaque and then gets larger and more transparent until it disappears and the animation restarts.
I'm using scaling to make the image larger but resetting it to it's original size is becoming problematic and at the moment I have resorted to cloning a second circle to reset it rather than just working with a single shape, there has to be a simpler way of doing this.
I've create a jsFiddle to demonstrate my rough code so far, any help would be appreciated.
http://jsfiddle.net/colethecoder/Y3S9n/1
Paperjs does not store the original Path, nor does it remember any operations that have been applied to reach the current state, so it can be difficult to reset to a previous state. The easiest approach is to use the this.scale that your current code is calculating and when you want to reset do this.circle.scale(1/this.scale); Here is a jsfiddle that way.
FYI, here is the code path for scaling:
Item.scale()
Item.transform()
Item.apply()
Path._apply()
Segment._transformCoordinates()
So the end result is that _transformCoordinates() is called on each of the four segments in the circle, and it simply moves the point coordinates...nothing is remembered to "undo" the scaling later.
Alternatively to remembering the scale yourself, you can use the Path.fitBounds() function to shrink the circles to an arbitrary size...for instance you could save the bounding rectangle right after creating the Circle, and then fitBounds back to that size.
Set item.applyMatrix = false if you don't want to persist transformations alongside item.
For example, the following code linearly (i.e. "additively") animates item.scaling:
var item = new Path.Rectangle({
point: [75, 75],
size: [5, 5],
strokeColor: 'black',
applyMatrix: false
});
function onFrame(event) {
item.scaling += 0.1;
}
The way i approached this issue was attaching a new object called originalBounds to the paper.js shapes immediately after their instantiation. If i needed to play with its size, coming back its original one became fairly trivial.

Pixel perfect 2D mouse picking with Canvas

I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;

Categories