I've made some pacman-like shapes that are animated on an html5 canvas and currently only move back and forth in 1 dimension. The program currently has a feature for directional buttons that change the path of the shapes, but I need the shapes to simply follow the cursor coordinates and not just travel in one direction. Currently, I'm getting the coordinates of the mouse using this:
function readMouseMove(e) {
var result_x = document.getElement('x_result');
var result_y = document.getElement('y_result');
result_x.innerHTML = e.clientX;
result_y.innerHTML = e.clientY;
}
document.onmousemove = readMouseMove;
var xR = document.getElementById("x_result");
var yR = document.getElementById("y_result");
var x = xR.innerHTML
var y = yR.innerHTML
As I'm having to make elements and extract the innerHTML of the cursor coordinates, I don't think this is the most efficient way to do this. I have a function object for each pacman shape that has attributes of its x and y positions (which shift based on the direction input), and the size and speed so I'm looking for a way to continuously set the x and y attributes for each object inside of my animationLoop. I don't really want to use jquery as is done here, as I'm using canvas transformations so what would be the best way to go about doing this? I have the code for the animation loop below for reference, thanks:
function animationLoop() {
canvas.width = canvas.width;
renderGrid(20, "red");
for (var i = 0; i < WokkaWokkas.length; i++) {
//WWM is the name of each pacman object
var WWM = WokkaWokkas[i];
renderContent(WWM);
setAngles(WWM);
//used for the direction input
switch (WWM.direction) {
case up:
WWM.posY -= WWM.speed;
if (WWM.posY <= 0) WWM.direction = down;
break;
case down:
WWM.posY += WWM.speed;
if (WWM.posY > 600) WWM.direction = up;
break;
case left:
WWM.posX -= WWM.speed;
if (WWM.posX < 0) WWM.direction = right;
break;
case right:
WWM.posX += WWM.speed;
if (WWM.posX > 600) WWM.direction = left;
break;
}
}
Related
Can an HTML canvas element be internally cropped to fit its content?
For example, if I have a 500x500 pixel canvas with only a 10x10 pixel square at a random location inside it, is there a function which will crop the entire canvas to 10x10 by scanning for visible pixels and cropping?
Edit: this was marked as a duplicate of Javascript Method to detect area of a PNG that is not transparent but it's not. That question details how to find the bounds of non-transparent content in the canvas, but not how to crop it. The first word of my question is "cropping" so that's what I'd like to focus on.
A better trim function.
Though the given answer works it contains a potencial dangerous flaw, creates a new canvas rather than crop the existing canvas and (the linked region search) is somewhat inefficient.
Creating a second canvas can be problematic if you have other references to the canvas, which is common as there are usually two references to the canvas eg canvas and ctx.canvas. Closure could make it difficult to remove the reference and if the closure is over an event you may never get to remove the reference.
The flaw is when canvas contains no pixels. Setting the canvas to zero size is allowed (canvas.width = 0; canvas.height = 0; will not throw an error), but some functions can not accept zero as an argument and will throw an error (eg ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height); is common practice but will throw an error if the canvas has no size). As this is not directly associated with the resize this potencial crash can be overlooked and make its way into production code.
The linked search checks all pixels for each search, the inclusion of a simple break when an edge is found would improve the search, there is still an on average quicker search. Searching in both directions at the same time, top and bottom then left and right will reduce the number of iterations. And rather than calculate the address of each pixel for each pixel test you can improve the performance by stepping through the index. eg data[idx++] is much quicker than data[x + y * w]
A more robust solution.
The following function will crop the transparent edges from a canvas in place using a two pass search, taking in account the results of the first pass to reduce the search area of the second.
It will not crop the canvas if there are no pixels, but will return false so that action can be taken. It will return true if the canvas contains pixels.
There is no need to change any references to the canvas as it is cropped in place.
// ctx is the 2d context of the canvas to be trimmed
// This function will return false if the canvas contains no or no non transparent pixels.
// Returns true if the canvas contains non transparent pixels
function trimCanvas(ctx) { // removes transparent edges
var x, y, w, h, top, left, right, bottom, data, idx1, idx2, found, imgData;
w = ctx.canvas.width;
h = ctx.canvas.height;
if (!w && !h) { return false }
imgData = ctx.getImageData(0, 0, w, h);
data = new Uint32Array(imgData.data.buffer);
idx1 = 0;
idx2 = w * h - 1;
found = false;
// search from top and bottom to find first rows containing a non transparent pixel.
for (y = 0; y < h && !found; y += 1) {
for (x = 0; x < w; x += 1) {
if (data[idx1++] && !top) {
top = y + 1;
if (bottom) { // top and bottom found then stop the search
found = true;
break;
}
}
if (data[idx2--] && !bottom) {
bottom = h - y - 1;
if (top) { // top and bottom found then stop the search
found = true;
break;
}
}
}
if (y > h - y && !top && !bottom) { return false } // image is completely blank so do nothing
}
top -= 1; // correct top
found = false;
// search from left and right to find first column containing a non transparent pixel.
for (x = 0; x < w && !found; x += 1) {
idx1 = top * w + x;
idx2 = top * w + (w - x - 1);
for (y = top; y <= bottom; y += 1) {
if (data[idx1] && !left) {
left = x + 1;
if (right) { // if left and right found then stop the search
found = true;
break;
}
}
if (data[idx2] && !right) {
right = w - x - 1;
if (left) { // if left and right found then stop the search
found = true;
break;
}
}
idx1 += w;
idx2 += w;
}
}
left -= 1; // correct left
if(w === right - left + 1 && h === bottom - top + 1) { return true } // no need to crop if no change in size
w = right - left + 1;
h = bottom - top + 1;
ctx.canvas.width = w;
ctx.canvas.height = h;
ctx.putImageData(imgData, -left, -top);
return true;
}
Can an HTML canvas element be internally cropped to fit its content?
Yes, using this method (or a similar one) will give you the needed coordinates. The background don't have to be transparent, but uniform (modify code to fit background instead) for any practical use.
When the coordinates are obtained simply use drawImage() to render out that region:
Example (since no code is provided in question, adopt as needed):
// obtain region here (from linked method)
var region = {
x: x1,
y: y1,
width: x2-x1,
height: y2-y1
};
var croppedCanvas = document.createElement("canvas");
croppedCanvas.width = region.width;
croppedCanvas.height = region.height;
var cCtx = croppedCanvas.getContext("2d");
cCtx.drawImage(sourceCanvas, region.x, region.y, region.width, region.height,
0, 0, region.width, region.height);
Now croppedCanvas contains only the cropped part of the original canvas.
I want to style the second elment in this array by adding a CSS Property
here is a global variable to define the array
<canvas id="canvas"></canvas>
<script>
var paddles = [2], // Array containing two paddles
function update() {
// Update scores
updateScore();
// Move the paddles on mouse move
// Here we will add another condition to move the upper paddle in Y-Axis
if(mouse.x && mouse.y) {
for(var i = 1; i < paddles.length; i++) {
p = paddles[i];
// the botoom paddle
if (i ==1){
p.x = mouse.x - p.w/2;
}else{
// the top paddle
//paddles[2].x = mouse.x - p.w/2;
debugger
paddles[2].style.backgroundColor="red";
}
}
}
and here what the style I want
paddles[2].style.backgroundColor="red";
when I use the debugger I face this problem
TypeError: paddles[2].style is undefined
UPDATE:
Since it looks like you are creating some kind of "pong" or "breakout" game, I decided to take my first stab at HTML canvas and do it myself for fun. Here is a simple version that shows how to:
draw two paddles on a canvas (original author)
keep track of the "boxes" for the paddles in an array (original author)
use a loop via setInterval to redraw the canvas as it gets updated (original author)
use the keyboard to move shapes around on HTML canvas (my code)
See the fiddle for working demo and full code: http://jsfiddle.net/z4ckpcLc/1/
I will not post the full code because I didn't write most of it.. I used the example from this site for the code for drawing the boxes and for keeping track of them in an array: http://simonsarris.com/project/canvasdemo/demo1.html
The function I added to this example is the arrowKeyMove() handler, wired up to the onkeydown event of document.body via this line: document.body.onkeydown = arrowKeyMove;
function arrowKeyMove(e) {
var direction = 0; // -1 means left, 1 means right, 0 means no change
var moveFactor = 10; // higher factor = more movement when arrow keys pressed
var arrowKeyUsed = false; // to indicate which 'paddle' we are moving
switch (e.which) {
case 37:
// left arrow (upper paddle)
direction = -1;
arrowKeyUsed = true;
break;
case 39:
// right arrow (upper paddle)
direction = 1;
arrowKeyUsed = true;
break;
case 65:
// "a" key for left strafe (lower paddle)
direction = -1;
break;
case 68:
// "d" key for right strafe (lower paddle)
direction = 1;
break;
}
var boxIndex = 1; // box index defaults to lower paddle
if (arrowKeyUsed) { // if using arrow keys, we are moving upper paddle
boxIndex = 0;
}
var maxX = 240; // constrain movement to within 10px of box borders (240 == canvas width minus paddle width minus padding)
var minX = 20;
var box = boxes[boxIndex]; // grab the box; we will update position and redraw canvas
if((direction < 0 && box.x >= minX) || (direction > 0 && box.x <= maxX))
{
// move the box in the desired direction multiplied by moveFactor
box.x = box.x + (direction * moveFactor);
invalidate(); // invalidate canvas since graphic elements changed
}
}
ORIGINAL ANSWER:
Array items use zero-based indexing.
If you only have two paddles like you said, you must use index 1, not 2. And if you want to access the first paddle, use 0, not 1. You probably want your for loop to use var i=0 instead, and basically change places you are checking 1 to 0.
For example:
paddles[0].style.backgroundColor="red"; // paddle 1
paddles[1].style.backgroundColor="red"; // paddle 2
Also, var array = [2] does not create a two-array element. It creates a one-array element with an integer value of 2
For DOM elements you may want something like this:
<div id='paddle1'></div>
<div id='paddle2'></div>
<script type='text/javascript'>
var paddles = [];
paddles[0] = document.getElementById('paddle1');
paddles[1] = document.getElementById('paddle2');
paddles[0].style.backgroundColor="red"; // paddle 1 is red
paddles[1].style.backgroundColor="orange"; // paddle 2 is orange
</script>
I'm not sure, but maybe you can use something like this:
paddles[2].css('background-color','red');
edit: now I see you don't use jQuery, so my solution wouldn't work
I want to add the vertical lines when I draw rectangle. The no of lines is dependent on the user and can be read from the text box.
I know the logic but somehow I am not able to get the answer.
I am calculating the width of the rectangle and then diving the width on the basis of no of vertical lines.
Click the checkbox near rectangle and draw using mouse down events
Please let me know where I am going wrong.
function PlotPitch()
{
var iPatches = document.getElementById('txtPatchCount').value;
var iTop = mySel.y;
var iBottom = mySel.y + mySel.h;
var iLeft = mySel.x;
var iX = iLeft;
canvas = document.getElementById('canvas2');
context = canvas.getContext('2d');
for (var iPatch=1; iPatch<iPatches; ++iPatch) {
iX = iLeft + iPatch*mySel.w/iPatches;
context.moveTo(iX, iTop);
context.lineTo(iX, iBottom);
}
context.lineWidth=0.25;
context.stroke();
}
http://jsfiddle.net/K5wcs/4/
If I am adding this the code is breaking and I am not able to draw anything.
What you should do if you have 'strange' behaviour is to separate concerns, so in this case that could be by creating a function that you test separately, which draws the lines, then once it's tested ok, plug it in code by just calling the function. You should find quickly.
So begin by testing this :
function drawLines(Context, mySel, iPatches) {
var iTop = mySel.y;
var iBottom = mySel.y + mySel.h;
var iLeft = mySel.x;
var iX = iLeft;
var colWidth = mySel.w/iPatches ;
for (var iPatch=1; iPatch<iPatches; ++iPatch) {
iX += colWidth;
Context.moveTo(iX, iTop);
Context.lineTo(iX, iBottom);
}
Context.lineWidth=0.25;
Context.stroke();
}
Good luck.
I’m fairly new to web development and I’ve only ever used jQuery to write my scripts. Today however, I’d like to improve my skills and build a little game that could be used on a smartphone as a web app in vanilla JS.
The game’s pretty straightforward:
You hold your phone in portrait mode and control a character that stays at the bottom of the screen and has to dodge objects that are falling on him. The character can only move left or right and thus always stays on the same x-axis. In order to control him, your finger has to stay on the screen. Once you take it off, you lose. Also, the move isn’t triggered by tapping the screen, but by moving your finger left or right.
For now, I’ve only been experimenting to get the hang of touchevents and was able to make the character move when swiping:
document.addEventListener('touchmove',function(e){
e.preventDefault(); //disable scroll
var board = document.getElementById(‘board);
var character = document.getElementById(‘character’);
if (e.targetTouches.length === 1) {
var touch = e.targetTouches[0];
board.classList.add(‘moving’);
character.style.left = touch.pageX + 'px';
}
}, false);
(The ‘moving’ class is used to move the background-position of the board and animate the character’s sprite in plain CSS.)
Separately, I made a little script that puts objects with random classes in a container with a set interval. These objects are then animated in css and fall from the top to the bottom of the screen.
Now, here comes the tricky part: the collision detection.
As I said, I’m new to development and vanilla JS, so I searched a bit to figure out how to detect when two objects collide, and it seems that most tutorials do this using canvases. The thing is, I’ve never used them and they scare me quite a bit. What’s more, I think it would render what I’ve done so far useless.
I’m okay with trying the canvas way, but before I do, I’d like to know if there’s any other way to detect if two moving objects collide?
Also, if there turns out to be no real way to do this without canvas, I plan on using this tutorial to learn how to build the app. However, this game wasn’t built for touchscreen devices, and the spaceship’s position changes on certain keystrokes (left & right) :
function update() {
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
My question is: how should I do to update the position using touchmove instead of keystrokes?
Thank you all in advance.
1) the idea : 'if you stop touching, you loose', is just a bad idea, drop it.
2) most convenient way to control is to handle any touch event (touch start/move/end/cancel), and to have the character align on the x coordinate of this event.
3) the intersection test is just a basic boundig box intersection check.
I made a very basic demo here, that uses touch, but also mouse to ease testing :
http://jsbin.com/depo/1/edit?js,output
a lot of optimisations are possible here, but you will see that touches adjust the ship's position, and that collisions are detected, so it will hopefully lead you to your own solution
Edit : i added default to 0 for left, top, in case they were not set.
boilerplate code :
var collisionDisplay = document.getElementById('collisionDisplay');
// hero ship
var ship = document.getElementById('ship');
ship.onload = launchWhenReady ;
// bad ship
var shipBad = document.getElementById('shipBad');
shipBad.onload = launchWhenReady ;
// image loader
imagesCount = 2 ;
function launchWhenReady() {
imagesCount --;
if (imagesCount) return;
setInterval(animate, 20);
}
var shipBadY = 0;
touch events :
// listen any touch event
document.addEventListener('touchstart', handleTouchEvent, true);
document.addEventListener('touchmove', handleTouchEvent, true);
document.addEventListener('touchend', handleTouchEvent, true);
document.addEventListener('touchcancel', handleTouchEvent, true);
// will adjust ship's x to latest touch
function handleTouchEvent(e) {
if (e.touches.length === 0 ) return;
e.preventDefault();
e.stopPropagation();
var touch = e.touches[0];
ship.style.left = (touch.pageX - ship.width / 2) + 'px';
}
animation :
// animation loop
function animate() {
// move ship
shipBadY += 1;
shipBad.style.top = Math.ceil(shipBadY) + 'px';
// test collision
var isColliding = testCollide(shipBad);
collisionDisplay.style.display = isColliding ? 'block' : 'none';
}
collision :
// collision test when the enemy and the ship are images
function testCollide(enemi) {
var shipPosX = parseInt(ship.style.left) || 0 ;
var shipPosY = parseInt(ship.style.top) || 0 ;
var shipWidth = ship.width ;
var shipHeight = ship.height;
var badX = parseInt(enemi.style.left) || 0 ;
var badY = parseInt(enemi.style.top) || 0 ;
var badWidth = enemi.width;
var badHeight = enemi.height;
return bBoxIntersect(shipPosX, shipPosY, shipWidth, shipHeight,
badX, badY, badWidth, badHeight);
}
EDIT : in case you're not using images :
// collision test when the enemy and the ship are ** NOT ** images
function testCollide(o) {
var characterPosX = parseInt(character.style.left);
var characterPosY = parseInt(character.style.top);
var characterWidth = parseInt(character.style.width);
var characterHeight = parseInt(character.style.height);
var obstacleX = parseInt(o.style.left) || 0 ;
var obstacleY = parseInt(o.style.top) || 0 ;
var obstacleWidth = parseInt(o.style.width);
var obstacleHeight = parseInt(o.style.height);
return boundingBoxIntersect(characterPosX, characterPosY, characterWidth, characterHeight, obstacleX, obstacleY, obstacleWidth, obstacleHeight);
}
function bBoxIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
return !(x1 + w1 < x2 || x1 > x2 + w2 || y1 + h1 < y2 || y1 > y2 + w2);
}
mouse events :
// -----------------------------------------------------
// Handle mouse event for easy testing on Browser
document.addEventListener('mousemove', handleMouseEvent);
function handleMouseEvent(e) {
ship.style.left = (e.pageX - ship.width / 2) + 'px';
}
I've got an animation on my canvas where I have some images (drawn using drawImage()). For the sake of simplicity, let's say there's only two images. These images are following a circular path in faux-3d space such that sometimes one image overlaps the other and other times the second image overlaps the first. These images are also scaled as they move "closer" or "further" from the viewer.
Each of these images is an object with the following (simplified) code:
function slide_object() {
this.x = 0.0; // x = position along path (in radians)
this.xpos = 0.0; // x position on canvas
this.ypos = 0.0; // y position on canvas
this.scale = 0.0;
this.name = ""; // a string to be displayed when slide is moused over
this.imgx = 0.0; // width of original image
this.imgy = 0.0; // height of original image
this.init = function (abspath, startx, name) { // startx = path offset (in radians)
this.name = name;
this.x = (startx % (Math.PI * 2));
var slide_image = new Image();
slide_image.src = abspath;
this.img = slide_image;
calcObjPositions(0, this); // calculate's the image's position, then draws it
};
this.getDims = function () { // called by calcObjPositions when animation is started
this.imgx = this.img.width / 2;
this.imgy = this.img.height / 2;
}
}
Each of these objects is stored in an array called slides.
In order to overlap the images appropriately, the drawObjs function first sorts the slides array in order of slides[i].scale from smallest to largest, then draws the images starting with slides[0].
On $(document).ready() I run an init function that, among other things, adds an event listener to the canvas:
canvas = document.getElementById(canvas_id);
canvas.addEventListener('mousemove', mouse_handler, false);
The purpose of this handler is to determine where the mouse is and whether the mouse is over one of the slides (which will modify a <div> on the page via jQuery).
Here's my problem -- I'm trying to figure out how to determine which slide the mouse is over at any given time. Essentially, I need code to fill in the following logic (most likely in the mouse_handler() function):
// if (mouse is over a slide) {
// slideName = get .name of moused-over slide;
// } else {
// slideName = "";
// }
// $("#slideName").html(slideName);
Any thoughts?
Looks like I just needed some sleep before I could figure this one out.
I've got everything I need to determine the size of the image and it's placement on the canvas.
I added the following function to my slide_object:
this.getBounds = function () {
var bounds = new Array();
bounds[0] = Math.ceil(this.xpos); // xmin
bounds[1] = bounds[0] + Math.ceil(this.imgx * this.scale); // xmax
bounds[2] = Math.ceil(this.ypos); // ymin
bounds[3] = bounds[2] + Math.ceil(this.imgy * this.scale); // ymax
return bounds;
};
Then in my mouse_handler function, I get the index of the currently moused-over slide from a function I call isWithinBounds() which takes a mouse x and mouse y position:
function isWithinBounds(mx,my) {
var index = -1;
for ( var i in slides ) {
bounds = slides[i].getBounds();
if ((mx >= bounds[0] && mx <= bounds[1]) && (my >= bounds[2] && my <= bounds[3])) {
index = i;
}
}
return index;
}
Since slides is sorted in order of scale, smallest to largest, each iteration will either replace or preserve the value of index. If there's more than one image occupying a space, the top-most slide's index gets returned.
The only problem now is to figure out how to make the code more efficient. Chrome runs the animation without any lag. Firefox has some and I haven't even thought about implementing excanvas.js yet for IE users. For browsers lacking canvas support, the canvas object is simply hidden with display:none.