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
Related
I am trying to make a game. The object of the game is to move the square across the screen without hitting a raindrop falling from the roof. How do i make it so that if one of the raindrops enters the square, the square returns to the beginning of the canvas or x =0. Here is the code:
var canvas = document.getElementById('game');
var ctx = canvas.getContext('2d');
var WIDTH = 1000;
var HEIGHT = 700;
var x = 0;
var y = HEIGHT-20;
var xPos = [0];
var yPos = [0];
var speed = [1];
var rainDrops = 50;
var rectWidth = 20;
var rectHeight = 20;
for (var i = 0; i < rainDrops; i++) {
xPos.push(Math.random()* WIDTH);
yPos.push(0);
speed.push(Math.random() * 5);
}
function rainFall () {
window.requestAnimationFrame(rainFall);
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i <rainDrops; i++) {
//Rain
ctx.beginPath();
ctx.arc(xPos[i], yPos[i], 3, 0, 2 * Math.PI);
ctx.fillStyle = 'blue';
ctx.fill();
//Rain movement
yPos[i]+=speed[i];
//Box
ctx.fillStyle = 'red';
ctx.fillRect(x, y, rectWidth, rectWidth);
if (yPos[i] > HEIGHT) {
yPos[i]= 0;
yPos[i]+=speed[0];
}
//This is the part where I need the Help!!!!!!!!!
if (){
x = 0;
}
}
};
//Move Object
function move (e) {
if (e.keyCode === 37) {
ctx.clearRect (0, 0, WIDTH, HEIGHT);
x -=10;
}
if (e.keyCode === 39) {
ctx.clearRect (0, 0, WIDTH, HEIGHT);
x+=10;
}
canvas.width=canvas.width
}
//Lockl Screen
window.addEventListener("keydown", function(e) {
// Lock arrow keys
if( [37,39,].indexOf(e.keyCode) > -1) {
e.preventDefault();
}
}, false);
rainFall();
document.onkeydown = move;
window.addEventListener("load", doFirst, false);
Conditional statements
I am never too sure how to answer these types of questions. As you are a beginner I don't want to overwhelm you with code and techniques, but at the same time I don't want to give an answer that perpetuates some bad techniques.
The short answer
So first the simple answer from your code where you wrote
//This is the part where I need the Help!!!!!!!!!
// the test checks the center of the drop
// if drop is greater than > top of player (y) and (&&)
// drop x greater than > left side of player x and (&&) drop is less than <
// right side of player (x + rectWidth) then drop has hit player
if (yPos[i] > y && xPos[i] > x && xPos[i] < x + rectWidth ){
x = 0; // move player back
}
BTW you are drawing the player rectangle for each rain drop. You should move that draw function outside the loop.
The long answer
Hopefully I have not made it too confusing and have added plenty of comments about why I did this and that.
To help keep everything organised I separate out the various elements into their own objects. There is the player, rain, and keyboard handler. This is all coordinated via the mainLoop the is called once a frame (by requestAnimationFrame) and calls the various functions to do all that is needed.
The player object holds all the data to do with the player, and functions to draw and update the player (update moves the player)
The rain object holds all the rain in an array called rain.drops it has functions to draw and update the rain. It also has some functions to randomize a drop, and add new drops.
To test if the rain has hit the player I do it in the rain.update function (where I move the rain) I don`t know what you wanted to happen when the rain hits the player so I just reset the rain drop and added 1 to the hit counter.
I first check if the bottom of the rain drop drop.y + drop.radius is greater than the top of the player if(drop.y + drop.radius >= player.y){
This makes it so we dont waste time checking rain that is above the player.
The I test for the rain in the x direction. The easiest is the test the negative (if the rain is not hitting the player) as the logic is a little simplier.
If the right side of the drop is to the left of the left side of the player, or (use || for or) the left side of the drop is to the right of the right side of the player than the drop can not be hitting the player. As we want the reverse condition we wrap it in a brackets a put a not in front if(! ( ... ) )
The test is a little long so I break it into 2 lines for readability.
// drop is a single rain drop player is the player
if (drop.y + drop.radius >= player.y) {
if ( ! (drop.x + drop.radius < player.x ||
drop.x - drop.radius > player.x + player.width) ) {
// code for player hit by rain in here
}
}
The rain.update function also checks if the rain has hit the bottom of the canvas and resets it if so.
Demo
I copied your code from in the question and modified it.
addEventListener("load",function(){ // you had onload at the bottom after all the code that gets the canvas
// etc. Which kind of makes the on load event pointless. Though not needed
// in this example I have put it in how you can use it for a web page.
// Using onload event lets you put the javascript anywhere in the HTML document
// if you dont use onload you must then only put the javascript after
// the page elements you need eg Canvas.
var canvas = document.getElementById('gameCanvas');
var ctx = canvas.getContext('2d');
ctx.font = "20px arial";
var frameCount = 0; // counts the frames
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var currentMaxDrops = 5; // rain will increase over time
const numberFramesPerRainIncrease = 60 * 5; // long name says it all. 60 frames is one second.
const maxRainDrops = 150; // max number of rain drops
// set up keyboard handler
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
requestAnimationFrame(mainLoop); // request the first frame of the animation (get it all going)
//==========================================================================================
// Setup the keyboard input stuff
const keys = { // list of keyboard keys to listen to by name.
ArrowLeft : false,
ArrowRight : false,
}
function keyEvent(event){ // the key event argument event.code hold the named key.
if(keys[event.code] !== undefined){ // is this a key we want
event.preventDefault(); // prevent default browser action
keys[event.code] = event.type === "keydown"; // true if keydown false if not
}
}
//==========================================================================================
const player = { // object for everything to do with the player
x : 0, // position
y : HEIGHT - 20,
width : 20, // size
height : 20,
speed : 4, // speed per frame
color : "red",
showHit : 0, // when this is > 0 then draw the player blue to indicate a hit.
// This counter is counted down each frame so setting its value
// determins how long to flash the blue
hitCount : 0, // a count of the number of drops that hit the player.
status(){ // uses hit count to get a status string
if(player.hitCount === 0){
return "Dry as a bone.";
}
if(player.hitCount < 5){
return "A little damp.";
}
if(player.hitCount < 15){
return "Starting to get wet.";
}
return "Soaked to the core";
},
draw(){ // draw the player
if(player.showHit > 0){
player.showHit -= 1; // count down show hit
ctx.fillStyle = "blue";
}else{
ctx.fillStyle = player.color;
}
ctx.fillRect(player.x,player.y,player.width,player.height);
},
update(){ // this updates anything to do with the player
// Not sure how you wanted movement. You had it so that you move only when key down events
// so I have done the same
if(keys.ArrowLeft){
player.x -= player.speed; // move to the left
keys.ArrowLeft = false; // turn off the key. If you remove this line then will move left while
// the key is down and stop when the key is up.
if(player.x < 0){ // is the player on or past left side of canvas
player.x = 0; // move player back to zero.
}
}
if(keys.ArrowRight){
player.x += player.speed; // move to the right
keys.ArrowRight = false; // turn off the key. If you remove this line then will move right while
// the key is down and stop when the key is up.
if(player.x + player.width >= WIDTH){ // is the player on or past right side of canvas
player.x = WIDTH - player.width; // move player back to inside the canvas.
}
}
}
}
//==========================================================================================
const rain = { // object to hold everything about rain
numberRainDrops : 50,
drops : [], // an array of rain drops.
randomizeDrop(drop){ // sets a drop to random position etc.
drop.x = Math.random() * WIDTH; // random pos on canvas
drop.y = -10; // move of screen a little so we dont see it just appear
drop.radius = Math.random() *3 + 1; // give the drops a little random size
drop.speed = Math.random() * 4 + 1; // and some speed Dont want 0 speed so add 1
return drop;
},
createDrop(){ // function to create a rain drop and add it to the array of drops
if(rain.drops.length < currentMaxDrops){ // only add if count is below max
rain.drops.push(rain.randomizeDrop({})); // create and push a drop. {} creates an empty object that the function
// randomizeDrop will fill with the starting pos of the drop.
rain.numberRainDrops = rain.drops.length;
}
},
draw(){ // draw all the rain
ctx.beginPath(); // start a new path
ctx.fillStyle = 'blue'; // set the colour
for(var i = 0; i < rain.drops.length; i ++){
var drop = rain.drops[i]; // get the indexed drop
ctx.arc(drop.x, drop.y, drop.radius, 0, 2 * Math.PI);
ctx.closePath(); // stops the drops rendered as one shape
}
ctx.fill(); // now draw all the drops.
},
update(){
for(var i = 0; i < rain.drops.length; i ++){
var drop = rain.drops[i]; // get the indexed drop
drop.y += drop.speed; // move down a bit.
if(drop.y + drop.radius >= player.y){ // is this drop at or below player height
// checks if the drop is to the left or right of the player
// as we want to know if the player is hit we use ! (not)
// Thus the next if statement is if rain is not to the left or to the right then
// it must be on the player.
if(!(drop.x + drop.radius < player.x || // is the rigth side of the drop left of the players left side
drop.x - drop.radius > player.x + player.width)){
// rain has hit the player.
player.hitCount += 1;
player.showHit += 5;
rain.randomizeDrop(drop); // reset this drop.
}
}
if(drop.y > HEIGHT + drop.radius){ // is it off the screen ?
rain.randomizeDrop(drop); // restart the drop
}
}
}
}
function mainLoop () { // main animation loop
requestAnimationFrame(mainLoop); // request next frame (don`t need to specify window as it is the default object)
ctx.clearRect(0, 0, WIDTH, HEIGHT);
frameCount += 1; // count the frames
// when the remainder of frame count and long name var is 0 increase rain drop count
if(frameCount % numberFramesPerRainIncrease === 0){
if(currentMaxDrops < maxRainDrops){
currentMaxDrops += 1;
}
}
rain.createDrop(); // a new drop (if possible) per frame
rain.update(); // move the rain and checks if the player is hit
player.update(); // moves the player if keys are down and check if play hits the side of the canvas
player.draw(); // draw player
rain.draw(); // draw rain after player so its on top.
ctx.fillStyle = "black";
ctx.fillText("Hit " + player.hitCount + " times.",5,20);
ctx.setTransform(0.75,0,0,0.75,5,34); // makes status font 3/4 size and set position to 5 34 so I dont have to work out where to draw the smaller font
ctx.fillText(player.status(),0,0); // the transform set the position so text is just drawn at 0,0
ctx.setTransform(1,0,0,1,0,0); // reset the transform to the default so all other rendering is not effected
};
});
canvas {
border : 2px black solid;
}
<canvas id="gameCanvas" width=512 height=200></canvas>
Hope this was helpfull.
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;
}
}
I have problems animating part of the character 'W' that is converted to svg. This character is styled out a bit, it has like small flag at the left side (the part that I want to animate).
Right now when the animation is going, that flag is stretched vertically at the top of page. It should stay at the same position where it was, also the top and bottom line of the flag should be in parallel( like in image sample below).
Code sample:
var pathData = "M253.477,175...";
var path = new paper.Path(pathData);
var flags = {
collection:[]
}
var Flag = function(){
var model = {
startIndex:0, // start point in path.segments array
middleIndex:0,// middle point in path.segments array
endIndex:0, // end point in path.segments array
height:20, // the wave animation height
segments:[] // only flag segments
}
return model;
};
var initializeFlag = function(){
var segments = path.segments;
//...
for(var i = flag.startIndex; i <= flag.endIndex; i++ ){
flag.segments.push(segments[i]);
}
flags.collection.push(flag); //adds to flags collection
};
var doWaveAnimation = function(segment, counter, height, top, e){
var sinus = Math.sin(e.time * 3 + counter);
segment.point.y = sinus * height + top;
};
var animateFlags = function(e){
var collection = flags.collection;
for(var i = 0; i < collection.length; i++){
var flag = collection[i];
for(var s = flag.startIndex, n = flag.endIndex -1;
s < flag.middleIndex && n > flag.middleIndex -2;
s++, n--){
//top line
doWaveAnimation(flag.segments[n], n, flag.height, 180, e);
//bottom line
doWaveAnimation(flag.segments[s], s, flag.height, 200, e);
}
}
};
//...
Full code sample -> flag animation
To get greater understanding what kind of "wave" animation I want, here is also one example(at the bottom of page) -> http://paperjs.org/
EDIT
Looks like the main reason why this animation is not working properly is that both lines are not positioned horizontally but diagonally..
There's a few things you can do to make this easier:
Make the 'flag' segments linear instead curved
Create your flags so that they are N segments long and are at the end of a path. Then you can refer to them by segment index instead of matching coordinates
Store each moving segment's initial coordinates in a property
Move each segment by a ratio of it's distance from the letter form over the entire length of the flag.
Here's an example sketch
Try moving the X-coordinate of each segment with a different phase to create a more complex motion.
Well obviously a sine wave is centred on zero. So you are going to have to keep a record of all your flag Y coordinates when you loop through finding your start and end indices. Then add those Ys back in when you are doing your animation.
I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.
Hi everyone I started to write a little game with ball and bricks and have some problem with collision detection. Here is my code http://jsbin.com/ibufux/9 . I know that detection works though array, but I can't figure how I can apply it to my code.
Here is what i have tried:
bricksCollision: function() {
for (var i = 0; i < $bricks.length; i++) {
if ($ball.t == $bricks[i].offset().top) {
$bricks[i].splice(i, 1);
}
}
Every bricks in game are generated by for loop and then goes to $bricks array. Every brick after generating receive top and left position and have position absolute. I have tried to check if $ball.t (it's properties of my ball object which detects ball top position) reach the bricks and than remove bricks.
Thanks for any help. I'm only start to learn JS that's why my code is
knotty.
First of all, let'stalk about some code errors
$ball.t should be probably $ball.top
you do not need to have $ as a prefix, for your code, it's simply a variable and you are calling $ball instead of ball witch results in assumption errors!
for those assumption errors here is what you are doing wrong:
$ball is a dom element, not a jQuery element
the same as $bricks
$ball is an array
with those concluded from some console.log() let's try to fix the code:
the $ball should be called, as there's only one, by it's array element, as $ball[0] and because you have variables pointing to DOM elements and not jQuery elements, you need to wrap it in Jquery as:
if ( $($ball[0]).top === $($bricks[i]).offset().top ) { ...
a good idea not to get confused is only use $ in jQuery elements, prefixing it in a variable, does not make them a jQuery Element.
And everytime you see that you have an error such as "element x has no method y" always assume that you're calling a method from a DOM element, and not a jQuery element.
Now, that #balexandre has nicely explained some points about your code, lets examine how we can compute the collision.
Imagine 2 Ranges overlapping each other (Range a partly overlaps Range b)
[100 .|.. 300]
[200 ..|. 400]
The part overlapping is from | to | -> 200 to 300, so the size of the overlap is 100
If you look at the numbers, you notice, that the overlap could be seen like,
Take the smaller number of the right side -> 300
Take the greate number of the left side -> 200
Subtract them from each other -> 300 - 200 = 100.
Lets take a look at 2 other situations. (Range b completely in Range a)
[50 ... 150]
[75...125]
So the values we have are: Math.min (150,125) //125 for the end value and Math.max (50,75) // 75 for the start value, resulting in a value of 125 - 75 = 50 for the overlap
Let's take a look the last example (Range a not in Range b)
[50 ... 150]
[200 ... 300]
Using the above formula, yields the result Math.min (150 , 300 ) - Math.max (50,200) // -50 which absolutes' value is the gap between the 2 Ranges, 50
Now we can add a last condition, as you want to compute the collision, only values > 0 are of interest for us. Given this we can put it into one condition.
Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)
Which will yield true if the elements' overlap and false if they don't.
Applying this to your code, we could compute the collision the following way
bricksCollision: function () {
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
if (ball.t <= (offset.top + 20) && (Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0])) > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
return true;
}
}
}
With this we could return true to the move function, when the Ball collides with a Brick.
But hey, we want it to Bounce off in the right direction, so we will face another problem.
So rather then returning a Boolean value whether the Brick collides or not, we could return a new direction in which the Ball will should move.
To be able to easily change only the x or the y part of the direction, we should use something like a vector.
To do so, we could use 2 Bits of an Integer, where the bit b0 stays for the x direction and the bit b1 for the y direction. Such that.
Dec Bin Direction
0 -> 00 -> Down Left
^ -> Left
^ -> Down
1 -> 01 -> Down Right
^ -> Right
^ -> Down
2 -> 10 -> Up Left
^ -> Left
^ -> Up
3 -> 11 -> Up Right
^ -> Right
^ -> Up
But to be able to change only a part of the direction, we need to pass the old direction to the collision function, and use bitwise & and | respectively to turn them off or on
Also we have to compute from which side the ball collides.
Fortunatly we have overlap calculation from before, which already uses all values we need, to compute the direction of collision.
If it comes frome the
right
Brick ["Right"] - Ball["Left"] has to be the same value as the overlap.
left
Ball ["Right"] - Brick["Left"] has to be the same value as the overlap.
If none of them are true, it has to either come from the
bottom
if Ball["Top"] is more than ( Brick["Top"] plus half of the Brick["height"] )
or else from the top.
To reduce the range where the condition, for the collision from the side, evaluates to true we can add another condition that the overlap has to be less than e.g ... && overlap < 2
So if it collides with the edge it doesn't always bounce of to the side.
So enough the talking, in code this could look like something like this.
bricksCollision: function (direction) {
var newDirection = direction
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var overlap = Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0]);
if (ball.t <= ((offset.top - field.t) + 20) && overlap > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
if (ballBounds[1] - brickBounds[0] == overlap && overlap < 2) { //ball comes from the left side
newDirection &= ~(1); //Turn the right bit off -> set x direction to left
} else if (brickBounds[1] - ballBounds[0] == overlap && overlap < 2) { //ball comes from the right side
newDirection |= 1; // Turn the right bit on -> set x direction to right;
} else {
if (ball.t > (offset.top + (20 / 2))) //Ball comes from downwards
newDirection &= ~(2) // Turn the left bit off -> set y direction to down;
else //Ball comes from upwards
newDirection |= 2; // Turn the left bit on -> set y direction to up;
}
//console.log("Coming from: %s Going to: %s", field.directionsLkp[direction], field.directionsLkp[newDirection], direction)
return newDirection;
}
}
return direction;
}
To get that to work, we should also change the moveXX functions, to use the new direction, returned.
But if we are going to get the new direction from the collision function anyway, we could move the complete collision detection to the function, to simplify our move functions.
But before that, we should have a look at the move functions and, add a lookup object to field which holds the numbers for the direction, to maintain readability.
var field = {
directions: {
uR : 3, // 11
dR : 1, // 01
dL : 0, // 00
uL : 2 // 10
},
directionsLkp: [
"dL","dR","uL","uR"
],
...
}
Now the move functions could then look like this,
ballCondact: function () {
var moves = [moveDl,moveDr,moveUl,moveUr]
var timeout = 5;
function moveUr() {
var timer = setInterval(function () {
$ball.css({
top: (ball.t--) + "px",
left: (ball.l++) + "px"
})
var newDirection = game.bricksCollision(field.directions.uR) //get the new direction from the collision function
if (newDirection !== field.directions.uR) {
clearInterval(timer);
moves[newDirection](); //move in the new direction
}
}, timeout);
}
...
}
Like this, the move function simply changes the direction if the collision function returns a direction which differs from the current one.
Now we can start moving the wall collisions to the collision function, to do this we could add another check at the beginning.
bricksCollision: function (direction) {
...
if (ball.t <= field.t)
newDirection &= ~(2); //Ball is at top, move down
else if (ball.l <= 0) //Ball is at the left, move right
newDirection |= 1;
else if (ball.t >= field.b - ball.height) //Ball is at the bottom, move up
newDirection |= 2;
else if (ball.l > field.width - ball.width) //Ball is at the right, move left
newDirection &= ~(1);
if (direction !== newDirection)
return newDirection
...
}
Note, i left out the collision check for the platform, as the idea should be clear =)
Here is a Fiddle