Clickable enemies - javascript

Hi im totally new to javascript and need help.im making an HtML javascript game. I just wanted to ask how do i get my enemies to be clickable?? i have managed to successfully create my enemies for my game but currently they come down from the top of the game screen and exit at the bottom. If the player touches any of the enemies then it goes to game over but i want the player to be able to click on the enemies then proceed to game over instead. Ive been trying for a couple of weeks now and im lost.
Also my player currently is being controlled by the mouse, as in the mouse is the player.
Do i need to change my collison test?? im just not sure how to make the player be able to click on the enemies. do i need to register a 'click' function like onmouseclick etc?
im using:
window.addEventListener("mousedown",onDown,false);
window.addEventListener("mousemove",onMove,false);
window.addEventListener("mouseup",onUp,false);
thanks any help would be great!! just need a sbit of help to go in the right direction.
Thanks in advance :)
this is the function for when the player is moving the mouse (player). it works as my player is controlled by the mouse movememt:
function onMove(e) {
if (!e) var e = window.event;
//get mouse position
var posx = 0;
var posy = 0;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
var totalOffsetX = 0;
var totalOffsetY = 0;
var currentElement = canvas;
do{
totalOffsetX += currentElement.offsetLeft;
totalOffsetY += currentElement.offsetTop;
}
while(currentElement = currentElement.offsetParent)
mouseX = posx - totalOffsetX;
mouseY = posy - totalOffsetY;
}
}
and for mouse up:
function onUp(e) {
mouseDown = false;
}
for enemies, i have done:
enemies = new Array();
createEnemies();
and the function with the animations for the enemy objects (food and fruit items in the game) :
function createEnemies() {
var enemy
if(level>2 && Math.random()<0.2) {
var breakfastItems = Math.floor(Math.random() * breakfastsheets.length);
var tmpAnimation = new Animation(breakfastsheets[breakfastItems],4,2)
enemy = new Skull(tmpAnimation,Math.random()*(canvas.width-tmpAnimation.width),-tmpAnimation.height);
} else if(level>3 && Math.random()<0.2) {
var randomVegetable = Math.floor(Math.random() * vegetablesheets.length);
var tmpAnimation = new Animation(vegetablesheets[randomVegetable],4,2)
enemy = new Skull(tmpAnimation,Math.random()*(canvas.width-tmpAnimation.width),-tmpAnimation.height);
}else {
var randomFruit = Math.floor(Math.random() * enemysheets.length);
var tmpAnimation = new Animation(enemysheets[randomFruit],4,2)
enemy = new Skull(tmpAnimation,Math.random()*(canvas.width-tmpAnimation.width),-tmpAnimation.height);
}
enemy.setExplosionSound(explosionSoundPool);
enemies.push(enemy);
}
forgot to say that the 'Skull' thats in the enemies is this one: forget the missiles though im not using them.
function Skull (image, x,y, width, height) {
//call constructor of parent object
DisplayObject.call(this,'skull', image, x,y, width, height);
//initialise objects
this.img.play();
this.img.setLoop(true);
this.img.setRange(0,4);
//private variables
var dying = false;
var alive = true;
var speed = 5;
var explosionSound;
//public methods
this.update = function(game_area, missiles) { //game area is a Rect2d, missiles is an array of display objects.
this.y+=speed;
this.img.next();
if(!dying && missiles) {
for(var i = 0; i<missiles.length; i++) {
if(Collision.test(missiles[i],this)) {
missiles[i].kill();
dying = true;
this.img.setRange(4,8);
this.img.setLoop(false);
this.img.setFrame(0);
//play explosion sound.
if(explosionSound) explosionSound.play(0.5);
}
}
}
if(Collision.isOutside(this,game_area) || (dying && !this.img.isPlaying())) {
alive = false;
}
}
//set a sound to be played when the enemy is hit.
this.setExplosionSound = function (soundPool) {
explosionSound = soundPool;
}
this.isDying = function () {
return dying;
}
this.isDead = function () {
return !alive;
}
}
Skull.prototype = new DisplayObject();

Assuming the enemies are square objects that move around the screen,
What you can do is create a class for enemies that contain their current position with:
function newEnemy(){
this.topLeftx = 'some random value'
this.topLefty = 'some random value'
this.bottomRightx = 'some random value'
this.bottomRighty = 'some random value'
this.isSelected = false;
...
}
Then have a method that is called when the user clicks, and goes through the list of enemies one by one. For each enemy, call a 'hit test' function that will check if the user's (x,y) coordinates -on the mouse- are inside the square of the enemy.
If any of the shapes are selected, then set them to true, and on the next draw cycle, have selected enemies drawn differently or not drawn at all i.e. destroyed?
If the enemies are circular then you will need an x,y coordinate with a radius for each one. Then, simply check to see if a line drawn between the center of the circle and the mouse coordinates are less than the radius of the circle itself. Use the Pythagorean theorem to find the lengths.

Related

How would I make a collider that stops the game when it collides with the "character"?

I'm still pretty new to this, so I don't know how to create a collider. My end goal is to have a game like the chrome dinosaur game. Same principles, and all. My question is, though, how do I even make a collider. I will be using a .gif for the "dinosaur". I'd like to make it where if this collider were to touch another collider, the game stops and a "game over" is shown. I have tried to create a collider, but they just keep showing up underneath the screen where the game is shown. Ant tips, tricks, or advice? Thanks
Code is as follows:
let img; //background
var bgImg; //also the background
var x1 = 0;
var x2;
var scrollSpeed = 4; //how fast background is
let music; //for music
let catBus; //catbus
//collider variables
let tinyToto;
let tiniestToto;
let hin;
let totoWithBag;
let noFace;
let happySoot;
var mode; //determines whether the game has started
let gravity = 0.2; //jumping forces
let velocity = 0.1;
let upForce = 7;
let startY = 730; //where cat bus jumps from
let startX = 70;
let totoX = 900;
let totoY = 70;
let tinToX = 900;
let tinToY = 70;
var font1; //custom fonts
var font2;
p5.disableFriendlyErrors = true; //avoids errors
function preload() {
bgImg = loadImage("backgwound.png"); //importing background
music = loadSound("catbus theme song.mp3"); //importing music
font1 = loadFont("Big Font.TTF");
font2 = loadFont("Smaller Font.ttf");
//tinyToto.setCollider("rectangle",0,25,75,75)
}
function setup() {
createCanvas(1000, 1000); //canvas size
img = loadImage("backgwound.png"); //background in
x2 = width;
music.loop(); //loops the music
catBus = {
//coordinates for catbus
x: startX,
y: startY,
};
/*
tinyToto = {
x: totoX,
y: totoY,
}
tinTo = {
x : tinToX,
y: tinToY,
}
*/
catGif = createImg("catgif.gif"); //creates catbus
catGif.position(catBus.x, catBus.y); //creates position
catGif.size(270, 100); //creates how big
/*
tinyToto = createImg("TinyToto.gif")
tinyToto.position(tinyToto.x, tinyToto.y)
tinyToto.size(270,100)
tiniestTo = createImg("tiniest Toto.gif")
tiniestTo.position(tinToX.x, tinToY.y)
tiniestTo.size(270,100)
*/
mode = 0; //game start
textSize(50); //text size
}
function draw() {
let time = frameCount; //start background loop
image(img, 0 - time, 0);
image(bgImg, x1, 2, width, height);
image(bgImg, x2, 2, width, height);
x1 -= scrollSpeed;
x2 -= scrollSpeed;
if (x1 <= -width) {
x1 = width;
}
if (x2 <= -width) {
x2 = width;
} //end background loop
fill(128 + sin(frameCount * 0.05) * 128); //text colour
if (mode == 0) {
textSize(20);
textFont(font1);
text("press SPACE to start the game!", 240, 500); //what text to type
}
fill("white");
if (mode == 0) {
textSize(35);
textFont(font2);
text("CATBUS BIZZARE ADVENTURE", 90, 450); //what text to type
}
catBus.y = catBus.y + velocity; //code for jumping
velocity = velocity + gravity;
if (catBus.y > startY) {
velocity = 0;
catBus.y = startY;
}
catGif.position(catBus.x, catBus.y);
//setCollider("tinyToto")
}
function keyPressed() {
if (keyCode === 32 && velocity == 0) {
//spacebar code
mode = 1;
velocity += -upForce;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
well, this is how I would generally do that kind of thingy:
function draw(){
for(let i in objects) // objects would be cactuses or birds
if(objects[i].x > player.x &&
objects[i].x < player.x + player.width &&
objects[i].y > player.y &&
objects[i].y < player.y + player.height){
noLoop()
// maybe do something else here
} // you could also use: for(let object of objects)
}
or if you want to do class stuff:
let player = new Player()
class Entity {
hasCollided_pointRect(_x, _y, _width, _height){
if(this.x > _x &&
this.x < _x + _width &&
this.y > _y &&
this.y < _y + _height){
return true
}
}
}
class Cactus extends Entity {
update(){
if(hasCollided_pointRect(player.x, player.y, player.width, player.height))
lossEvent()
}
}
class Player {
// ...
}
function lossEvent(){
noLoop()
}
this is a pretty classy way to do it and for a small game you really don't need all of this
also MDN has a nice article on rect with rect & point with rect collisions,
point with point collision is just (x == x && y == y)
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
this is one of my recent loss "functions":
if(flag.health <= 0){
noLoop()
newSplashText("You lost!\nPress F5 to restart!", "center", "center", 1)
}
The way I handled game states in my Processing games was by making seperate classes for them. Then my main sketch's draw function looked something like
fun draw()
{
currentState.draw();
}
Each gamestate then acted as their own sketches (for example a menu screen, playing, game over, etc), and had a reference to the main sketch which created the states. They would then alter the main's currentState to, i.e., a new GameOverState() etc. where needed.
For now, don't worry about doing that too much if all you want a really simple gameoverscreen with an image and some text.
I would suggest a structure like this instead. Use this pseudocode in your main draw function:
fun draw()
{
if (gameOver)
{
// show game over screen
img(gameOver);
text("game over!");
// skip rest of the function
return;
}
// normal game code goes here
foo();
bar();
// update game over after this frame's game code completes
gameOver = checkGameOver();
}
Now you need a way of checking for a collision to determine the result of checkGameOver()
For the collision handling, check out Jeffrey Thompson's book/website on collision handling. It's an amazing resource, I highly recommend you check it out.
From the website I just linked, here's an excerpt from the website talking about handling collisions between 2d rectangles.
And here's a modified version of the collision handling function listed there (I updated the variable names to be a little more intuitive)
boolean rectRect(float rect1X, float rect1Y, float rect1Width, float rect1Height, float rect2X, float rect2Y, float rect2Width, float r2h)
{
// are the sides of one rectangle touching the other?
if (rect1X + rect1Width >= rect2X && // r1 right edge past r2 left
rect1X <= rect2X + rect2Width && // r1 left edge past r2 right
rect1Y + rect1Height >= rect2Y && // r1 top edge past r2 bottom
rect1Y <= rect2Y + r2h)
{ // r1 bottom edge past r2 top
return true;
}
return false;
You can use that function in your checkGameOver() function which would return a bool depending on whether your collision criteria are met.
For your game, you would loop over every obstacle in your game and check whether the dino and the obstacle overlap.
Pseudocode:
boolean checkGameOver()
{
foreach (Obstacle obstacle in obstacles)
{
if (rectRect(dino, obstacle))
{
return true;
}
}
return false;
}

How to add a logic statement to an object entering a moving area

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.

how to detect and move/drag the free flow drawn lines in html canvas?

My implementation is this:
Detect if mousedown and mousemove and if true then draw and saved the points in an array.
In my mousemove I will convert the points that will be drawn in
I converted the curPath to (Date,value) then to (X and Y-axis) so that they will be saved in the implementation in my canvas.
My problem is that how will I detect points[] ? so that I can highlight it and drag as well.
UPDATE.
This is bigger than I expected. I will continue to improve the quality of the answer as I go. See the bottom of the answer for status.
Picking.
The simplest way is to check how far the mouse is from each point in the line, and highlight the line that has the closest point. The problem is that when you have many lines and lots of points it slows down and become unusable.
Another ways is to store some extra info on each line to help you vet lines that are not going to be picked. In the example I create a bounding box for each line and check if the mouse is inside or near that box. If so then I search the line some more checking each line segment and keeping the line that is closest to the mouse.
Some of the function to look at.
Helpers.prototype.resetExtent();
Helpers.prototype.extent();
Helpers.prototype.copyOfExtent();
Used to find the bounding box. (extent)
function refreshLine(line);
Called after a line is drawn, it takes a set of points drawn and adds the bounding box (extent), plus other stuff for the demo.
function findLineAt(x,y){
This function takes the x,y position of the mouse (or what ever) and returns the closest line within 20 pixels. It first checks the bounding box, if that passes it calls
Helpers.prototype.getDistToPath = function (line,x,y) {
This gets the line as just a set of points and checks the distance to the center of each line. It also checks if the check needs more details and calls the function.
Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {
This function will return the shortest distance from a point to a line. x,y is the point x1,y1,x2,y2 is the line. It does not check the line segment but the line which is infinitely long. Helpers.lineSegPos property will hold the normalised position of the closest point on the line. If you need it.
So back to findLineAt(x,y), after all those calls it will return the line if found or undefined if not.
Highlight and dragging.
Highlighting is up to you. I just cycle the hue of the line closest very quickly. You may wish to put a bounding box around it. Easy to do as all you do is redraw the closest line each update.
How its works
The main loop.
update()
Handles the main loop, is called 60 times a second and has to parts, the Draw section is for drawing and pick for picking. See if(drawMode==="Pick"). The mouse is only read in the update, the mouse is set independently by the mouse listener. At the end of every loop I save the mouse button state mouse.lastButton so that I can check when the mouse moves down and up.
In the pick part if the mouse is not down I call the findLineAt function. If I find a line (line !== undefined) I highlight the line by changing its colour and drawing it.
Because every update I have the current mouseButton state and what it was last update, I will know when the mouse button first moves down because mouse.button is true and mouse.lastButton is false. If there is a line near the mouse, I record the mouse position in dragOffX and dragOffY and set a flag dragging to true. I also draw the canvas onto another canvas to keep as background. At this point I also check which mouse button is down. If right button I copy the line and set it as the line to be dragged, or if the middle button I search all the lines to find its index in the lineArray and delete it, then just redraw.
Next update (1/60th second later) and dragging flag is true mouse.button is true and lastLine (the line that was closest) is not undefined I know I am dragging a line. I clear the canvas, draw the saved copy of the canvas (it's faster to draw that then redraw all the lines again especially if you have 100's of lines with 1000's of points), and then redraw the line I am dragging.
To workout where to draw the dragged line I get the distance the mouse is from dragOffX and dragOffY and set the transpose part of setTransform(1, 0 , 0, 1, mouse.x - dragOffX, mouse.y - dragOffY) to that distance. That has the effect of moving the line by the drag amount. I keep doing this until the mouse button is up.
Drop
Next update and mouse.button is up.
If the mouse button is up and the dragging flag is true then I must drop the line. At this point a get the mouse distance from dragOffX dragOffY and add it to each point in the line. Effectively moving the line. I also update the bounding box. I then clear the screen and redraw all the line, that removes the old position of the line from the canvas and put it at it's new home.
Done.
The code grew a little long. If this answer gets some support then I will clean it up some more. If not well then it does not matter..
It covers the basics of your question, detecting and moving points via mouse action. Highlighting and moving lines made of sets of points. My point are arrays of objects each with an x and y. Each line is stored in the lineArray a line has style, extent, id properties, and an array called line with all the points.
There is one mouse handler that takes the required mouse events. Move, mouse down and up, and mouse out. Mouse out stops the mouse locking up by turning the mouse buttons off. I also stop the context menu while the mouse is over the canvas.
I use requestAnimationFrame to call update to keep it all running smoothly.
I hope this helps you. I will improve it if it is what you are after. If not you will have to give a litte more info. Please do ask if you have problems.
Updates.
Added bounding box and improved the Helpers.prototype.getDistToPath(line,x,y) which I forgot to fix last night. Now its quicker and does not miss lines parallel to x and y axis. Moved screen redraw to accommodate the bounding box and add more comments.
Please do not hesitate to ask if you have question to this problem.
function log(){}; // duck up the logs
customCursors ={
encoding:"url('data:image/png;base64,",
drag_small : {
center : " 25 25,",
image : "iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAACQElEQVRoQ+2azW7DIAyAYZdJW6vlVmmnvcLe/yH2CjtN6i1Tu0m9rIMsJIYChmCvCWkuqZSA/fkPQyoF83VWl5RSqJtQd8kpjnVyB4QdiA0GghhvcHuIBcYH8h9A5DAxEG4gUhgN8rzbiY/9Hs1zjpAjg0nxiEtIDUQCMwWEI+SKYfJBzorDFkvloSvAXKZTs92K9nAoXlTJYFwV9YofunyNAEWHQALjU9qETijpA2OK9CkaHLJ8NYumBrzBoMss/sK6wkyHDLRJyp6EKsxyZUc9Y5R62mzE5/GYvB+hhNFVMVV+EMZVKGeVpoYxwYHp4IUp3VhxwehwjwFdwIQUwawC84oTJgZkwaQogRfIvzcA/DCkb1m63Eu9sE4CFqQBxgty+hLi/mHocnMOVyzFf96EuHv1AkKopmlE27YW5wiuDHD6Vvo8Ds/daOlggh7pYMbBqdaEnon9zpmve9ejDwSS0f3IRBgYGqOwF2W0dysEKWCskO4dkz1vbADMF9PaQ6OF8qBECT1ndZ6pJ2eMa6upZlGg/mFunF91ncGAFtcBxIDmApPVm4WA5gCD6bCO/Qz0EFzMFrvTnLoip3TfKUbJlb+uA41c60S7cPUQS+Ip8syYm2eg9dzjoMFK/edy19KxTqI0j4o9Y5LdVXqxXwFy+zYXfHbfZ9IPKWb85QyrXlh1oqxuxTmDdduJ22sSPUgmgUBV/A8gx0OUoWX1jVhMT3leVW8WKgpcHmFtZ3whxw2iZZIWAF9IOod/rPJ+AQ3iOFgpekFcAAAAAElFTkSuQmCC')"
},
}
function setCursor (name){
if(name === undefined){
canvas.style.cursor = "default";
}
if(customCursors[name] !== undefined){
var cur = customCursors[name];
canvas.style.cursor = customCursors.encoding + cur.image + cur.center + " pointer";
}else{
canvas.style.cursor = name;
}
}
// get canvas button and creat context2D
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var but = document.getElementById("redrawAllID");
but.addEventListener("click",function(){
if(drawMode === "Pick"){
drawMode = "Draw";
but.value = "Draw Mode";
}else{
drawMode = "Pick";
but.value = "Pick Mode";
lastLine = undefined;
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}
})
// Groover Bitmaps API dependency replacement
// Extracted from Groover.Bitmaps
var createImage= function(w,h){ // create a image of requier size
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d"); // tack the context onto the image
return image;
}
var backGroundImage = createImage(canvas.width,canvas.height);
if(!mouse){
// get all the mouse events
canvas.addEventListener('mousemove',mouseMoveEvent);
canvas.addEventListener('mousedown',mouseMoveEvent);
canvas.addEventListener('mouseup' ,mouseMoveEvent);
canvas.addEventListener('mouseout' ,mouseMoveEvent);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
// helper for random colour
var mouse = { // mouse data
x:0,
y:0,
button:false,
lastButton:false, // need this to see when the mouse goes down
which:[false,false,false],
};
}
function mouseMoveEvent(event){// handle all canvas mouse events as they come in
// get new mouse positions
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.x === undefined){ // if firefox
mouse.x = event.clientX;
mouse.y = event.clientY;
}
if(event.type === "mouseout"){
mouse.button = false;
mouse.which[0] = false;
mouse.which[1] = false;
mouse.which[2] = false;
}
if(event.type === "mousedown"){ // now see if there is extra info
mouse.button = true;
mouse.which[event.which-1] = true;
}
if(event.type === "mouseup"){ // now see if there is extra info
mouse.button = false;
mouse.which[event.which-1] = false;
}
event.preventDefault();
}
// because forEach is too slow
if (Array.prototype.each === undefined) {
Object.defineProperty(Array.prototype, 'each', {
writable : false,
enumerable : false,
configurable : false,
value : function (func) {
var i,
returned;
var len = this.length;
for (i = 0; i < len; i++) {
returned = func(this[i], i);
if (returned !== undefined) {
this[i] = returned;
}
}
}
});
}
// helper functions
function Helpers(){
}
Helpers.prototype.randomColour = function(){
return "hsl("+Math.floor(Math.random()*360)+",100%,50%)";
}
Helpers.prototype.occilatingColour = function(){
var t = (new Date()).valueOf()
return "hsl("+(Math.floor(t/2)%360)+",100%,50%)";
}
// used for building up the extent of a cloud of points
Helpers.prototype.resetExtent = function(){
if(this.extentObj === undefined){ // check if the extentObj is there
this.extentObj = {}; // if not create it
}
this.extentObj.minX = Infinity;
this.extentObj.minY = Infinity;
this.extentObj.maxX = -Infinity;
this.extentObj.maxY = -Infinity;
}
Helpers.prototype.extent = function( p) { // add a point to the extent
this.extentObj.minX = Math.min(this.extentObj.minX, p.x);
this.extentObj.minY = Math.min(this.extentObj.minY, p.y);
this.extentObj.maxX = Math.max(this.extentObj.maxX, p.x);
this.extentObj.maxY = Math.max(this.extentObj.maxY, p.y);
}
Helpers.prototype.copyOfExtent = function () { // get a copy of the extent object
return {
minX : this.extentObj.minX,
minY : this.extentObj.minY,
maxX : this.extentObj.maxX,
maxY : this.extentObj.maxY,
centerX : (this.extentObj.maxX-this.extentObj.minX)/2,
centerY : (this.extentObj.maxY-this.extentObj.minY)/2,
width:this.extentObj.maxX-this.extentObj.minX,
height:this.extentObj.maxY-this.extentObj.minY,
};
}
Helpers.prototype.getID = function(){ // return a unique ID for this session
if(this.id === undefined){
this.id = 0;
}
this.id += 1;
return this.id;
}
// function to get distance of point to a line
Helpers.prototype.distPointToLine = function (x, y, x1, y1, x2, y2) {
var px = x2 - x1;
var py = y2 - y1;
var u = this.lineSegPos = Math.max(0, Math.min(1, ((x - x1) * px + (y - y1) * py) / (this.distSqr1 = (px * px + py * py))));
return Math.sqrt(Math.pow((x1 + u * px) - x, 2) + Math.pow((y1 + u * py) - y, 2));
}
// function to get the distance of a point to a set of point describing a line
Helpers.prototype.getDistToPath = function (line,x,y) {
var i,len, lineLen,dist;
len = line.length;
x1 = line[0].x;
y1 = line[0].y;
var minDist = Infinity;
for(i = 1; i < len-1; i++){
var near = false;
x2 = line[i].x;
y2 = line[i].y;
lineLen = Math.hypot(x1-x2,y1-y2);
dist = Math.hypot((x1+x2)/2-x,(y1+y2)/2-y);
minDist = Math.min(minDist,dist);
if(dist < lineLen ){
minDist = Math.min(minDist,helpers.distPointToLine(x,y,x1,y1,x2,y2));
}
if(minDist < minDistToPass){
return minDist;
}
x1 = x2;
y1 = y2;
}
return minDist;
}
var helpers = new Helpers();
// Stuff for paths and drawing
var lineArray = []; // list of paths
var lastLine; // last line drawn
var points; // current recording path
var drawing = false; // flag is mouse down and drawing
var dragging = false;
var dragOffX;
var dragOffY;
var drawMode = "Draw";
var minDistToPass = 2; // If a line is closer than this then stop search we found the winning line
// functions to redraw all recorded lines
function redrawAll(){ // style to draw in
ctx.clearRect(0,0,canvas.width,canvas.height);
lineArray.each(function(p){ // draw each one point at atime
redraw(p,p.style);
})
}
// lineDesc is a line and its description
// style is a the style to draw the line in.
// withBox if true draw bounding box [optional]
function redraw(lineDesc,style,withBox){ // draws a single line with style
var line = lineDesc.line;
var len = line.length;
var i;
ctx.beginPath(); //
ctx.strokeStyle = style.colour; // set style and colour
ctx.lineWidth = lineDesc.style.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.moveTo(line[0].x,line[0].y); // move to the first pont
for(i = 1; i < line.length; i++){ // lineto all the rest
ctx.lineTo(line[i].x,line[i].y);
};
ctx.stroke(); // stroke
if(withBox){
var w = Math.ceil(lineDesc.style.width/2); // need the lines width to expand the bounding box
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.strokeRect( // draw the box around the line
lineDesc.extent.minX-w,
lineDesc.extent.minY-w,
lineDesc.extent.width+w*2,
lineDesc.extent.height+w*2
)
}
// done
}
// Finds the closest line and returns it. If no line can be found it returns undefined.
function findLineAt(x,y){
var minDist = 20; // Set the cutoff limit. Lines further than this are ignored
var minLine;
var w;
lineArray.each(function(line){ // do ech line one at a time
w = line.style.width;
if(x >= line.extent.minX-w && x <= line.extent.maxX+w && // is the point inside the bounding
y >= line.extent.minY-w && y <= line.extent.maxY+w){ // boc
var dist = helpers.getDistToPath(line.line,x,y); // if so then do a detailed distance check
if(dist < minDist){ // is the distance to the line less than any other distance found
minDist = dist; // if so remember the line
minLine = line;
}
}
dist = Math.hypot(line.extent.centerX-x,line.extent.centerY-y); // check the distance to the
if(dist<minDist){ // center of the bounding boc
minDist = dist; // use this one if bound box center if close
minLine = line;
}
});
return minLine;
}
function refreshLine(line){ // updates the line to get extend and add stuff if needed
// a good place to smooth the line if need
if(!line.isLine){
var newLine = {}; // new object
newLine.isLine = true; // flag to indicate that the line has been updated
newLine.line = line; // attach the line
newLine.id = helpers.getID(); // get a unique Id for the line
newLine.style = { // give it a style
colour:helpers.randomColour(),
width:Math.random()*4+10,
};
}else{
var newLine = line;
}
var extent = helpers.extent.bind(helpers)
helpers.resetExtent();
line.each(extent);
newLine.extent = helpers.copyOfExtent();
return newLine;
}
function update(){ // one animframe
if(drawMode === "Draw"){
if(!mouse.lastButton && mouse.button ){ // if the mouse but just down;
points = []; // create an new array
drawing = true; // flag drawinf
lineArray.push(points); // save the point array onto the pointsArray
points.push({x:mouse.x,y:mouse.y}); // add the first point
setCursor("none");
}else
if(drawing && mouse.button){ // while the mouse is down keep drawing
points.push({x:mouse.x,y:mouse.y}); // save new point
var p1 = points[points.length-2]; // get last line seg and draw it
var p2 = points[points.length-1];
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}else{
if(drawing){ // if drawing and mouse up
points.push({x:mouse.x,y:mouse.y}); // add the last point
lineArray.push(points = refreshLine(lineArray.pop()))
// redraw the newly draw line
redraw(points,points.style);
drawing = false; // flag that drawing is off.
}else{
setCursor("crosshair");
}
}
}else
if(drawMode = "Pick"){
if(!dragging && !mouse.button){ // is the mouse near a line and not dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear background
ctx.drawImage(backGroundImage,0,0); // draw copy of existing lines
var line = findLineAt(mouse.x,mouse.y); // find the line
if(line !== undefined){ // is a line is near
setCursor("drag_small"); // highlight it
lastLine = line; // remember it
// draw it hightlighted with bounding box.
redraw(lastLine,{colour:helpers.occilatingColour(),width:lastLine.width},true);
}else{
setCursor(); // not near a line so turn of cursoe
}
}else // next check if the mouse has jsut been click to start a drag.
if(lastLine !== undefined && !mouse.lastButton && mouse.button){
if(mouse.which[2]){ // Check which button. Right? then copy
var newLine = [];
lastLine.line.each(function(p){newLine.push({x:p.x,y:p.y})});
newLine = refreshLine(newLine)
newLine.style = lastLine.style;
lastLine = newLine;
lineArray.push(newLine)
}else
if(mouse.which[1]){ // Check which button. Middle? then delete
var index;
lineArray.each(function(line,i){
if(line.id === lastLine.id){
index = i;
}
})
if(index !== undefined){
lineArray.splice(index,1);
}
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
lastLine = undefined;
if(lineArray.length === 0){
drawMode = "Draw";
but.value = "Draw Mode";
}
}
if(lastLine !== undefined){
dragging = true;
dragOffX = mouse.x;
dragOffY = mouse.y;
// backGroundImage.ctx.clearRect(0,0,canvas.width,canvas.height);
// backGroundImage.ctx.drawImage(canvas,0,0);
}
}else{
if(dragging && !mouse.button){ // Drop is dragging true and not mouse down
dragging = false;
var ox = mouse.x-dragOffX; // Find the drag offset
var oy = mouse.y-dragOffY;
helpers.resetExtent(); // get ready for new bounding box.
lastLine.line.each(function(p){ // move each point of the line
p.x += ox;
p.y += oy;
helpers.extent(p); // and test the bounding box
return p;
})
lastLine.extent = helpers.copyOfExtent(); // get the new boundong box
ctx.clearRect(0,0,canvas.width,canvas.height);
redrawAll();
backGroundImage.ctx.clearRect(0,0,backGroundImage.width,backGroundImage.height);
backGroundImage.ctx.drawImage(canvas,0,0);
}else
if(dragging){ // if dragging
ctx.clearRect(0,0,canvas.width,canvas.height); // clear
ctx.drawImage(backGroundImage,0,0); // draw existing lines
var ox = mouse.x-dragOffX; // get the drag offset
var oy = mouse.y-dragOffY;
ctx.setTransform(1,0,0,1,ox,oy); // translate by drag offset
redraw(lastLine,lastLine.style); //draw the dragged line
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
}
}
mouse.lastButton = mouse.button; // set the last button state
window.requestAnimationFrame(update); // request a new frame
}
window.requestAnimationFrame(update)
.canC {
width:256px;
height:256px;
border:black 2px solid;
}
.info{
font-size:x-small;
}
<input type="button" id="redrawAllID" value="Click to Pick"></input>
<div class="info">Mouse down to draw.In pick mode mouse hover over line.<br> Left Button drag,middle delete, right copy.</div>
<canvas class="canC" id="canV" width=256 height=256></canvas>

Stop Movement on Collision

I'm creating a little game in Javascript and I have created a collision detection function that detects when my 2 images collide. I have a player image and enemy image and I move the player around with the arrow keys. When I collide with the enemy image I want the player to not be able to cross over the image but still be able to move, like colliding with a wall or something. I don't really know how to go about this so I cant supply and example code but I can give you my collide function and player objects;
//PLAYER OBJECTS
var playerImg = new Image();
playerImg.src = "../Images/player.png";
var playerReady = false;
playerImg.onload = function(){
playerReady = true;
};
var player = {
x: 300,
y: 150,
speed: 200
};
//COLLIDE FUNCTION
function CollisionCheck(Img1, Img2, Obj1, Obj2, width){
var colliding = false;
if(Obj1.x < Obj2.x + width && Obj1.x + width > Obj2.x && Obj1.y < Obj2.y + width && Obj1.y + width > Obj2.y){
colliding = true;
}else{
colliding = false;
}
return colliding;
}
Maybe I could detect which side the collision is on and stop the player from moving towards the image whilst colliding?
I call the function with:
if(CollisionCheck(player, enemy, 32)){
}
function checkTouching(img1, img2)
{
// checks if the two images are touching at any coordinate
// given two objects with x, y, width, and height
var touching =
img1.x + img1.width >= img2.x
&& img1.x <= img1.x + img1.width
&& img1.height + img1.y >= img2.y
&& img2.y <= img2.y + img2.y;
if (touching)
{
// images are touching
}
else
{
// images aren't touching
}
}

Dragging an image on canvas flashing

I'm trying to drag an image around on my canvas but in doing so I have an issue where once the image is in negative coordinates I get a condition where its
mouseX - negativeImageCoords // 200 - minus 210 = 410
making my image jump around like a popcorn kitten on the canvas, not the desired effect.
Here is my code and I'm hoping it's something stupid and I can put this down to being tired..
function (e) {
var
// The mouse x and y positions
mx = e.offsetX,
my = e.offsetY,
// The last known position
lx = cache.lx, // These are from a JSON object
ly = cache.ly;
// The temporary image
var img = $('#loadedImage');
// Get the image context
var canvas_context = this.mask();
cache.lx = (mx - lx);
cache.ly = (my - ly);
console.log(mx, lx);
console.log(my, ly);
// Redraw
canvas_context.drawImage(img.get(0), cache.lx, cache.ly, img.width(), img.height());
}
here is the mask function (included in case it is the perpetrator..
function () {
var mask_name = 'circle';
var context = ctx.context();
var mask;
var isSameMask = false;
var composition = 'lighter';
// Add a check to see if it's the same mask
if (cache.mask && cache.mask.src) {
if (cache.mask.src !== 'images/masks/' + mask_name + '.png') {
isSameMask = false;
}
}
// If we don't have a cached mask, load it and cache it
if (!cache.mask && !isSameMask) {
// Create a new mask
mask = new Image;
// Draw when its loaded
mask.onload = function () {
//ctx.clear();
context.drawImage(this, 0, 0);
context.globalCompositeOperation = composition;
};
mask.src = 'images/masks/' + mask_name + '.png';
// Set the cache as this new mask
cache.mask = mask;
imageEvents.size(0);
} else {
ctx.clear();
// It's cached, so just redraw it
context.drawImage(cache.mask, 0, 0);
context.globalCompositeOperation = composition;
}
return context;
}
Why is the image jumping around?
It has to be noted that I have thrown this together for an appjs project, any help/advice from you all is greatly appreciated.
Right, managed to get this working. The fix was updating the cached image positions on mousedown and just adding the cached positions to the mouse positions. Here is the code:
function drag (e) { // :void
var
// The mouse x and y positions
mx = e.offsetX,
my = e.offsetY,
// The last known position
lx = mx+cache.lx,
ly = my+cache.ly;
// The temporary image
var img = $('#loadedImage');
// Get the image context
var canvas_context = this.mask();
cache.ix = lx;
cache.iy = ly;
// Redraw
canvas_context.drawImage(img.get(0), lx, ly, img.width(), img.height());
textEvents.draw();
}
And my down events
cache.ix = 0;
cache.iy = 0;
// Listen for a mousedown or touchstart event
canvas.on('mousedown touchstart', function (e) {
cache.lx = cache.ix - e.offsetX;
cache.ly = cache.iy - e.offsetY;
// Add a move listener
canvas.on('mousemove touchmove', function (e) {
that.drag.call(that, e);
});
});
It's hard to provide an answer without seeing the code in action but could it be those conditions you specify, e.g.:
if (lx < 0) {
cache.lx = (mx + lx);
} else {
cache.lx = (mx - lx);
}
Surely you don't want to change the sums if lx is less or more than 0. Just let the maths do its job. Mathematically:
mx + -1 is the same as mx - 1
mx + +1 is the same as mx + 1
mx - -1 is the same as mx + 1 [double negative]
That would be why '200 - minus 210 = 410'; that's actually correct.
EDIT
The variable lx is the cached (therefore old) position; mx is the new position.
Therefore lx - mx will return the difference between the cached and the new position, which I think (if I understand you correctly) is what you want to move your image by a certain amount. Same for ly - my.
When it comes to caching the new mouse positions, surely you just want to cache the current ones, e.g.
cache.lx = mx; // current position cached for future reference
Caching the difference or a summation will just add to the confusion (again, if I've understood what you're trying to do).

Categories