So, I'm trying to get into HTML5 Canvas with a small project animating an existing header consisting of red laser-like lines bursting from a single point. I'm able to draw these lines as I want them, and animate them as a group at a nice speed, but the next hurdle is above my head, I think: I'd like each line to move independently!
I've got an object defining the starting point for my lines such as:
var myLines = {
a: 1500,
b: 700,
c: 400,
d: 310,
e: 140,
f: 60
}
And I then draw with a loop kinda like:
for (var i in myLines)
{
var cur = myLines[i];
context.beginPath();
context.moveTo(0, canvas.height);
context.lineTo(canvas.width, myLine.y-cur);
context.stroke();
}
Using an awkward method of incrementation:
var step = 1;
if (myLine.y+step > canvas.height) {set = false;}
if (myLine.y-step < 0) {set = true;}
if (set) {
myLine.y = myLine.y+step;
} else {
myLine.y = myLine.y-step;
}
I'm aware the above code is poorly written, I'm out of my league here, I work during the day as a chef in a small kitchen, but I'd like some advice, please!
Here's a fiddle to see it all in action.
Thanks in advance!
If I were you I would change the script so that each line was a separate object, rather than just an offset. Something like:
var line = { x: 0, y: 0, speed: 1 };
That way each line can have its own speed, and inside your loop, you can do something like the following to add on the speed and flip it when you reach the edge of the screen:
line.x += line.speed;
if(/* edge of screen */) {
line.speed = -line.speed;
}
Related
Followed the post The exactly same idea puts a rectangle along the centerline of it though cannot put an image in the right place, how do I fix it?, I tried to move the bolt along the line.
here is the code
let opposite_side = Math.abs(190 - 290);
let adjacent_side = Math.abs(290 - 90);
let adjacent_tartget = 123
let opposite_tartget = opposite_side / adjacent_side
opposite_tartget *= adjacent_tartget
this.tweens.add({
targets: bolt,
x: 290 - adjacent_tartget,
y: 190 + opposite_tartget,
rotation: angle,
ease: 'Linear',
duration: 1500,
onComplete: function(tween, targets) {
targets[0].setVisible(false);
}
});
and then I got what I want, a bolt moving along the line
to calculate the target x and y for tweens animation, I used SOHCAHTOA
I'd just like to know if Phaser 3 provides a handy tool to do the job.
An easy option is to make the image/sprite to physics-object and just move it, along the angle / direction.
Here the needed changes:
add physics to the game config:
...
physics: {default: 'arcade' },
...
calculate the direction as a Vector
let direction = new Phaser.Math.Vector2( 290 - 90, 190 - 290);
add physics to the image (and any object it should interact with)
this.physics.add.existing(bolt);
set the velocity of the physics body of the image
bolt.body.setVelocity(direction.x, direction.y );
Info: for this example you would not need to create a Vector2, but I like to use them, since they have some very convenient methods. like normalize, scale, length, and so on. So you don't have to do the math, just know the right function.
Here a working example:
Update: Bolt now hits target (second circle) and bolt is destroyed on impact.
var game = new Phaser.Game({
width: 600,
height: 180,
physics: {
default: 'arcade'
},
scene: {
preload: preload,
create: create
}
});
function preload() {
this.load.path = 'https://raw.githubusercontent.com/liyi93319/phaser3_rpg/main/part1/assets/';
this.load.atlas('bolt', 'bolt_atlas.png', 'bolt_atlas.json');
}
function create() {
let player = this.add.circle(90, 170, 10, 0xf00000);
let target = this.add.circle(490, 10, 10, 0xf00000);
let direction = new Phaser.Math.Vector2( target.x - player.x, target.y - player.y);
let angle = Phaser.Math.Angle.Between(player.x, player.y, target.x, target.y)
let reference = this.add.rectangle(player.x, player.y, 600, 5, 0x00f000).setOrigin(0, .5);
reference.rotation = angle
let bolt = this.add.sprite(player.x, player.y, 'bolt', 'bolt_strike_0002').setOrigin(0, .5);
bolt.rotation = angle;
this.physics.add.existing(bolt);
this.physics.add.existing(target);
bolt.body.setVelocity(direction.x, direction.y );
this.physics.add.collider(bolt, target, removeBolt );
}
function removeBolt(bolt, target){
bolt.destroy();
target.destroy();
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Extra Informtion: for more detailed examples on phaser physics, I recommend looking at these official examples: https://phaser.io/examples/v3/category/physics/arcade they cover most of the common needed function/use cases, and explain them very well. (Better then I ever could)
I know I am now supposed to just ask you "why is my code so slow" and give you a big amount of code, but I have this problem for a week now, and it really frustrates me.
function drawFront() {
ctx.beginPath();
var tempPosition = scene.box.func.calcPos(scene.box.names[i], 0, 0, 0);
ctx.moveTo(tempPosition.x, tempPosition.y);
var tempPosition = scene.box.func.calcPos(scene.box.names[i], 1, 0, 0);
ctx.lineTo(tempPosition.x, tempPosition.y);
var tempPosition = scene.box.func.calcPos(scene.box.names[i], 1, 1, 0);
ctx.lineTo(tempPosition.x, tempPosition.y);
var tempPosition = scene.box.func.calcPos(scene.box.names[i], 0, 1, 0);
ctx.lineTo(tempPosition.x, tempPosition.y);
ctx.closePath();
if (scene.box[scene.box.names[i]].material.useFaces == true){ //this is true
ctx.fillStyle = scene.box[scene.box.names[i]].material.faces[0].c;
ctx.fill();
}
if (scene.box[scene.box.names[i]].material.grid == true) // this is true
ctx.stroke();
}
This code is supposed to draw a shape with 4 corners, all of these corner positions are calculated using the scene.box.func.calcPos() function. All of this works perfectly fine, but the drawing gets slower and slower and slower as times moves by.
My CPU temperature gradually goes up from 40°C to 80°C and above.
What could be the cause of this?
EDIT:
here is a jsfiddle of it: https://jsfiddle.net/fswxtxyz/
here is the github page to the project: https://github.com/Quoteme/Fluchtpunkt3D
Thank you very very much for your help
var context = document.getElementById("canvas").getContext("2d");
for(var i = 0; i< savedMove.length; i++){
doSetTimeout(i);
}
function doSetTimeout(i) {
setInterval(function() { animate(savedMove[i][0], savedMove[i][1]); }, 100);
}
function animate(xPos, yPos) {
context.fillStyle = "red";
context.fillRect(xPos, yPos, 5, 5);
}
I have every x and y position move inside of 2D array (savedMove) and I want to draw with array information with delay. But Canvas does not draw this. I keep debugging but I cannot figure out the problem.
You're setting savedMove.length timers to tick parallelly every 100 milliseconds. I'm pretty sure this is not what you want, though it's hard to guess what it is. First I would change setInterval to setTimeout and make them fire at different times, 100 ms away from each other:
function doSetTimeout(i) {
setTimeout(function() { animate(savedMove[i][0], savedMove[i][1]); }, 100 * i);
}
Note that this is not the best way to do it, but certainly better than the original code.
Then you can debug it, 'cause you might draw out of the visible canvas:
console.log("canvas size:", document.getElementById("canvas").width, document.getElementById("canvas").height);
function animate(xPos, yPos) {
context.fillStyle = "red";
context.fillRect(xPos, yPos, 5, 5);
console.log("animate:", xPos, yPos);
}
I want to create a mobile web page where a shape appears on the screen, the user can only traces over the outline of the shape with his/her finger and then a new shape will appear. This library has a few good examples of what I am looking to do, just with more shapes. I have already found a couple of good examples for drawing on the canvas on a touch device here and here. The thing I don't know is how to constrain the line so you are only drawing on the path with a single continuous line. Is there something built in that will let me specify the only path you can draw, or do I have to write that logic by hand?
We can split the issue into two parts :
1) knowing if the user is on the path.
2) knowing if the user went on all path parts.
For 1), we can use the isPointInPath context2D method to know if the mouse/touch point (x,y) is on the curve. The constraint here is that you must build a closed surface, meaning a surface drawn by a fill(), not one built with a stroke(). So in case you are stroking thick lines, you have to do some little math to build the corresponding figures out of moveTo+lineTo+fill.
For 2) : build a list of 'check-points' for your shape. You might have, for instance 8 control points for a circle. Then decide of a distance at which the user will 'activate' the check point. Now the algorithm is, in pseudo-code:
mouseDown => check()
mouseMove => if mouse is down, check()
checkPointList = [ [ 10, 40, false ] , [ centerX, centerY, isChecked], ... ] ;
checked = 0;
function check() {
clear screen
draw the path
if (mouse down and mouse point on path) {
for ( checkPoint in CheckPointList) {
if (checkPoint near enough of mouse) {
checkPoint[3]=true;
checked++;
}
}
if (checked == checkPointList.length) ==>>> User DID draw all the shape.
} else
clear the flags of the checkPointList;
checked=0;
}
I did a moooost simple demo here, which quites work.
The control points are shown in red when disactivated, green when activated :
http://jsbin.com/wekaxiguwiyo/1/edit?js,output
// boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
function draw() {
ctx.clearRect(0,0,300,300);
drawShape();
drawCP();
}
// Shape
function drawShape() {
ctx.beginPath();
ctx.moveTo(30,5);
ctx.lineTo(80,5);
ctx.lineTo(80, 300);
ctx.lineTo(30,300);
ctx.closePath();
ctx.lineWidth= 16;
ctx.fillStyle='#000';
ctx.fill();
}
// Control points
var points = [ [50, 50, false], [50,120, false], [50, 190, false],[50,260, false ] ];
var pointsCount = 0;
function drawCP() {
for (var i=0; i<points.length; i++) {
var p = points[i];
ctx.fillStyle=p[2]?'#0F0':'#F00';
ctx.fillRect(p[0]-1, p[1]-1, 2, 2);
}
}
function resetCP() {
for (var i=0; i<points.length; i++) {
points[i][2]=false;
}
pointsCount=0;
}
function testCP(x,y) {
var d=30;
d=sq(d);
for (var i=0; i<points.length; i++) {
if (sq(points[i][0]-x)+sq(points[i][1]-y)<d) {
if (!points[i][2]) pointsCount++;
points[i][2]=true
};
}
}
function sq(x) { return x*x; }
//
draw();
// most simple event handling
addEventListener('mousemove', mouseMove);
var r = cv.getBoundingClientRect();
function mouseMove(e) {
var x = e.pageX-r.left;
var y = e.pageY-r.top;
draw();
ctx.fillStyle='#000';
if (ctx.isPointInPath(x,y)) {
ctx.fillStyle='#F00';
testCP(x,y);
} else {
resetCP();
}
ctx.fillRect(x-3,y-3,6,6);
var pathDrawn = (pointsCount == points.length);
if (pathDrawn) ctx.fillText('Shape drawn!!', 150, 150);
}
I'm trying to create an array of shapes that overlap. But I'm having difficulty preventing those shapes stacking on top of one-another.
I guess I want them to mesh together, if that makes sense?
Here's the code:
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x,y);
for (j=0;j<rectQTY;j++){ // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
// Degrees to rotate for next position
overlap_context.rotate((Math.PI/180)*360/rectQTY);
}
And here's my jsFiddle:
http://jsfiddle.net/Q8yjP/
And here's what I'm trying to achieve:
Any help or guidance would be greatly appreciated!
You cannot specify this behavior but you can implement an algorithmic-ish approach that uses composite modes.
As shown in this demo the result will be like this:
Define line width and the rectangles you want to draw (you can fill this array with the loop you already got to calculate the positions/angles - for simplicity I just use hard-coded ones here):
var lw = 4,
rects = [
[20, 15, 200, 75],
[150, 20, 75, 200],
[20, 150, 200, 75],
[15, 20, 75, 200]
], ...
I'll explain the line width below.
/// set line-width to half the size
ctx.lineWidth = lw * 0.5;
In the loop you add one criteria for the first draw which is also where you change composite mode. We also clear the canvas with the last rectangle:
/// loop through the array with rectangles
for(;r = rects[i]; i++) {
ctx.beginPath();
ctx.rect(r[0], r[1], r[2], r[3]);
ctx.fill();
ctx.stroke();
/// if first we do a clear with last rectangle and
/// then change composite mode and line width
if (i === 0) {
r = rects[rects.length - 1];
ctx.clearRect(r[0] - lw * 0.5, r[1] - lw * 0.5, r[2] + lw, r[3] + lw);
ctx.lineWidth = lw;
ctx.globalCompositeOperation = 'destination-over';
}
}
This will draw the rectangles and you have the flexibility to change the sizes without needing to recalculate clipping.
The line-width is set separately as stroke strokes the line from the middle. Therefor, since we later use destination-over mode it means half of the line won't be visible as we first fill which becomes part of destination so that the stroke will only be able to fill outside the stroked area (you could reverse the order of stroke and fill but will always run into an adjustment for the first rectangle).
We also need it to calculate the clipping which must include (half) the line on the outside.
This is also why we initially set it to half as the whole line will be drawn the first time - otherwise the first rectangle will have double as thick borders.
The only way to do it to cut your rectangles and compute which sub rectangle goes over which one. But I think you will have to draw your borders and inner rectangles separately because separating rectangles will add additional borders.
Hope it helped
Sadly, the feature you want of setting z-indexes on part of an element using canvas is not available currently. If you just need it for the four rectangle object you could do something like this which hides part of the rectangle to fake the effect you want, however this is hard coded to only 4 rectangles.
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x, y);
for (j = 0; j < rectQTY; j++) { // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
if (j === 3) {
overlap_context.beginPath();
overlap_context.rect(24, -86, 72, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20, -89.5);
overlap_context.lineTo(100, -89.5);
overlap_context.stroke();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20.5, -93.1);
overlap_context.lineTo(20.5, 23);
overlap_context.stroke();
overlap_context.closePath();
}
// Degrees to rotate for next position
overlap_context.rotate((Math.PI / 180) * 360 / rectQTY);
}
Demo here
If you have to make it dynamic, you could cut the shapes like Dark Duck suggested or you could try to create a function that detects when an object is overlapped and redraw it one time per rectangle (hard to do and not sure if it'd work). Perhaps you could come up with some equation for positioning the elements in relation to how I have them hard coded now to always work depending on the rotation angle, this would be your best bet IMO, but I don't know how to make that happen exactly
Overall you can't really do what you're looking for at this point in time
Using pure JavaScript ...
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="mycanvas" width="400px" height="400px"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//cheat - use a hidden canvas
var hidden = document.createElement('canvas');
hidden.width = 400;
hidden.height = 400;
var hiddenCtx = hidden.getContext('2d');
hiddenCtx.strokeStyle = 'blue';
hiddenCtx.fillStyle = 'yellow';
hiddenCtx.lineWidth = 5;
//translate origin to centre of hidden canvas, and draw 3/4 of the image
hiddenCtx.translate(200,200);
for(var i=0; i<3; i++){
hiddenCtx.fillRect(-170, -150, 300, 120);
hiddenCtx.strokeRect(-170, -150, 300, 120);
hiddenCtx.rotate(90*(Math.PI/180));
}
//reset the hidden canvas to original status
hiddenCtx.rotate(90*(Math.PI/180));
hiddenCtx.translate(-200,-200);
//translate to middle of visible canvas
ctx.translate(200, 200);
//repeat trick, this time copying from hidden to visible canvas
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
ctx.rotate(180*(Math.PI/180));
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
};
</script>
</body>
</html>
Demo on jsFiddle