The ball seems to bounce off one side of the paddle, but when it comes from the side it glitches through the paddle. I just can't find a way behind it and it really bothers me. I am using some logic gates to define where the ball's direction is need to be invereted
function startGame() {
GameArea.start();
Ball1 = new CircleComp('white' , window.innerWidth - 200 , window.innerHeight - 20);
Ball1.ySpeed = 13.5;
Ball1.xSpeed = 6;
Paddle1 = new PaddleComp( 87, 83, 0, window.innerHeight / 2.5, 10, 70);
Paddle2 = new PaddleComp( 38, 40, window.innerWidth - 10, window.innerHeight / 2.5, 10 , 70);
}
var GameArea = {
canvas : canvas = document.querySelector("canvas"),
start : function (){
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.ctx = this.canvas.getContext('2d');
this.interval = setInterval(updateGameArea, 20);
window.addEventListener('keydown', function (e) {
GameArea.keys = (GameArea.keys || []);
GameArea.keys[e.keyCode] = true;
})
window.addEventListener('keyup', function (e) {
GameArea.keys[e.keyCode] = false;
})
},
clear : function() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
function CircleComp(color, x , y){
this.x = x;
this.y = y;
this.width = 8;
this.height = 8;
var context1 = GameArea.ctx;
this.update = function(){
context1.beginPath();
context1.fillStyle = color;
context1.fillRect(this.x, this.y, this.width, this.height);
context1.fill();
context1.stroke();
this.updatePosition();
}
this.updatePosition = function(){
this.y += this.ySpeed;
this.x += this.xSpeed;
if(this.x + this.width > GameArea.canvas.width){
this.xSpeed = -this.xSpeed;
}
if(this.y + this.height > GameArea.canvas.height){
this.ySpeed = -this.ySpeed;;
}
if(this.x - this.width < 0){
this.xSpeed = -this.xSpeed;
}
if(this.y - this.height < 0){
this.ySpeed = -this.ySpeed;
}
if(this.y + this.height > Paddle2.y && this.y - this.width < (Paddle2.y + 130) && this.x + this.width > Paddle2.x ){
this.xSpeed = -this.xSpeed;
}
if(this.y + this.height > Paddle1.y && this.y - this.width < (Paddle1.y + 70) && this.x - this.height < Paddle1.x + 10){
this.xSpeed = -this.xSpeed;
}
}
}
function PaddleComp(Upkey, Downkey, x, y, width, height){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.ySpeed = 0;
var context2 = GameArea.ctx;
this.update = function(){
context2.fillStyle = 'white';
context2.fillRect(x,this.y,this.width,this.height);
this.updatePosition();
}
this.updatePosition = function() {
this.ySpeed = 0;
if (GameArea.keys && GameArea.keys[Upkey]) {
this.ySpeed = -15; //console.log('Up');
}
if (GameArea.keys && GameArea.keys[Downkey]) {
this.ySpeed = 15; //console.log('Down');
}
if ((GameArea.keys && GameArea.keys[Downkey]) && this.y + 130 > window.innerHeight){
this.ySpeed = this.ySpeed -15 ;
}
if ((GameArea.keys && GameArea.keys[Upkey]) && this.y < 0 ){
this.ySpeed = this.ySpeed +15 ;
}
this.y += this.ySpeed;
}
}
function updateGameArea(){
GameArea.clear();
Paddle1.update();
Paddle2.update();
Ball1.update();
}
<html>
<head>
<meta charset='urf-8'>
<style>
canvas{
border: 0px solid black;
background-color: black;
}
body{
margin: 0;
overflow: hidden;
}
</style>
</head>
<body onload='startGame()'>
<canvas></canvas>
<script src='Pong.js'></script>
</body>
</html>
Could not see directly what the problem was with your code so i just rewrote the code with the ball, bat (paddle) test function in the ball Object and called from the players object. ball.checkPad(player); tests if the ball has hit the players bat. To help picture what is happening I have slowed it all down and made the bats real phat. When the ball hits the bat it will turn yellow and the bat red for a second or so.
There is plenty of comments in the parts you asked about,
Hope it helps
Demo copied from OP question.
const setting = {
speed : 2, // of ball
left : 0,
width : 400,
height : 200,
padWidth : 50,
padHeight : 80,
padSpeed : 4, // double balls
hitPauseCount : 30, // nuber of frames to hold when there is a collisiotn so you
// can check all is good
}
const keys = {
ArrowUp : false,
ArrowDown : false,
ArrowLeft : false,
ArrowRight : false,
keyEvent(e) { // dont use keyCode it has depreciated
if (keys[e.code] !== undefined) {
keys[e.code] = e.type === "keydown";
e.preventDefault();
}
}
}
var ctx;
var ball1, paddle1, paddle2;
var gameArea = {
start() {
canvas.width = setting.width;
canvas.height = setting.height;
ctx = canvas.getContext('2d');
requestAnimationFrame(updateGameArea);
},
clear() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
}
gameArea.start();
ball = new CircleComp('white', window.innerWidth - 200, window.innerHeight - 20);
ball.ySpeed = setting.speed;
ball.xSpeed = setting.speed;
paddle1 = new PaddleComp("ArrowUp", "ArrowDown", setting.left, setting.height / 2, setting.padWidth, setting.padHeight);
paddle2 = new PaddleComp("ArrowLeft", "ArrowRight", setting.width - setting.padWidth, setting.height / 2, setting.padWidth, setting.padHeight);
window.addEventListener('keydown', keys.keyEvent);
window.addEventListener('keyup', keys.keyEvent);
function CircleComp(color, x, y) {
this.x = x;
this.y = y;
this.width = 8;
this.height = 8;
this.xSpeed = setting.speed;
var hit = 0;
var restartCount;
var serveDirection;
this.reset = function(){
this.x = ctx.canvas.width /2;
this.y = ctx.canvas.height / 2;
this.xSpeed = -this.xSpeed
this.ySpeed = setting.speed * Math.sign(Math.random() - 0.5);
restartCount = 60;
}
this.draw = function () {
if(hit > 0){
hit -= 1;
ctx.fillStyle = "yellow";
}else{
ctx.fillStyle = color;
}
ctx.fillRect(this.x, this.y, this.width, this.height);
}
// next funtion is called by the player objects
this.checkPad = function (player) {
if (player.x > canvas.width / 2) { // is player on left or right
if (this.xSpeed > 0) { // player on right only check if ball moving rigth
if (this.x + this.width > player.x) { // ball is in paddles zone
//if not bottom of ball above top of bat or top of ball bellow bottom of bat
if (!(this.y + this.height <= player.y || this.y >= player.y + player.height)) {
// ball and bat in contact
// is ball moving down and the balls top edge above the player
// then ball has hit the top side of the bat
if(this.ySpeed > 0 && this.y <= player.y){
this.y = player.y - this.width;
this.ySpeed = -setting.speed;
}else if(this.ySpeed < 0 && this.y + this.height >= player.y + player.height){ // do bottom check
this.y = player.y + player.height;
this.ySpeed = setting.speed;
}else{ // ball hit front of bat
this.x = player.x - this.width;
this.xSpeed = - setting.speed;
}
player.hit = setting.hitPauseCount; // counters to show FX when a hit happens
hit = setting.hitPauseCount;
}
}
}
} else { // player must be left
if (this.xSpeed < 0) { // ball must move left
if (this.x < player.x + player.width) { // ball is in paddles zone
if (!(this.y + this.height <= player.y || this.y >= player.y + player.height)) {
// ball and bat in contact
// ball and bat in contact
// is ball moving down and the balls top edge above the player
// then ball has hit the top side of the bat
if(this.ySpeed > 0 && this.y <= player.y){
this.y = player.y - this.width;
this.ySpeed = -setting.speed;
}else if(this.ySpeed < 0 && this.y + this.height >= player.y + player.height){ // do bottom check
this.y = player.y + player.height;
this.ySpeed = setting.speed;
}else{ // ball hit front of bat
this.x = player.x + player.width;
this.xSpeed = setting.speed;
}
player.hit = setting.hitPauseCount; // counters to show FX when a hit happens
hit = setting.hitPauseCount;
}
}
}
}
}
this.update = function () {
if(restartCount > 0){ // wait for restart pause
restartCount -= 1;
}else{
if(hit > 0){ // do nothing if paused
return;
}
this.y += this.ySpeed;
this.x += this.xSpeed;
if (this.x + this.width >= canvas.width) {
this.reset(); // point
} else if (this.x < 0) {
this.reset(); // point
}
if (this.y + this.height >= canvas.height) {
this.y = canvas.height - this.height;
this.ySpeed = -setting.speed;
} else if (this.y < 0) {
this.y = 0;
this.ySpeed = setting.speed;
}
}
}
this.reset();
}
function PaddleComp(upKey, downKey, x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.hit = 0;
this.draw = function () {
if(this.hit > 0){
this.hit -= 1;
ctx.fillStyle = "red";
}else{
ctx.fillStyle = '#9CF';
}
ctx.fillRect(this.x, this.y, this.width, this.height);
}
this.update = function () {
if (keys[upKey]) {
this.y -= setting.padSpeed;
};
if (keys[downKey]) {
this.y += setting.padSpeed;
};
if (this.y < 0) {
this.y = 0;
}
if (this.y + this.height >= canvas.height) {
this.y = canvas.height - this.height;
}
ball.checkPad(this);
}
}
function updateGameArea() {
gameArea.clear();
paddle1.update();
paddle2.update();
ball.update();
paddle1.draw();
paddle2.draw();
ball.draw();
requestAnimationFrame(updateGameArea);
}
<canvas id=canvas style='background:#69C;border:2px blue solid'></canvas>
Related
I am trying to make a game in which a player has an equippable weapon. As of now, I have set this weapon to be an image of a bow, and I want to have said weapon to move around the player while facing the mouse. This is similar to buildroyale.io, where the player rotates along with his weapon to face the mouse.
As of now, with the help of #Justin, I've got the bow rotating (somewhat) on the screen. It only shows up when the left click is down, as desired, but does not rotate as expected. Here is a clip showcasing how it moves: clip
Here is the code I use:
class EventHandler {
equip_weapon() {
if (holding) {
player.weapon = player.bag.slot_1;
canv.globalAlpha = 1;
player.weapon.equip();
player.weapon.update();
}
}
}
class Weapon {
image_path;
constructor(image_path) {
this.x = player.x + 30;
this.y = player.y + 30;
this.width = 120;
this.height = 120;
this.angle = 0;
this.distance = 50;
this.image = image_path;
}
equip() {
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
canv.save();
canv.translate(this.x, this.y);
canv.rotate(this.angle);
canv.drawImage(this.image, this.distance, -this.height/2, this.width, this.height);
canv.restore();
}
update() {
this.x = player.x + player.width / 2;
this.y = player.y + player.height / 2;
}
}
bow = new Weapon(bow_image);
player.bag.slot_1 = bow;
aim_bounds = document.documentElement.getBoundingClientRect();
class Player {
constructor() {
this.name = null;
this.speed = 5;
this.skin = player_sheet;
this.can_move = true;
this.is_moving = false;
this.width = 68;
this.height = 68;
this.scale = 1;
this.x = 566;
this.y = 316;
this.direction = facing.down;
this.frame = 0;
this.shadow_offset = 25;
this.frame_rate = 10;
this.health = 100;
this.clip_amount = 10;
this.weapon = null;
// Player inventory
this.bag = {
slot_1 : null,
slot_2 : null,
slot_3 : null,
slot_4 : null,
slot_5 : null,
offhand : null,
armor : null
}
this.is_menu_open = false;
console.log("Player constructed!");
}
update() {
// Animation updates
if (game.tick % this.frame_rate == 0 && this.is_moving && !this.is_menu_open) {
this.frame += 68;
if (this.frame >= 272) { this.frame = 0; }
} else if (!this.is_moving) { this.frame = 0; }
// Movement updates
if (this.can_move) {
if (controller.up) { this.direction = facing.up; this.y -= this.speed; this.is_moving = true; }
else if (controller.down) { this.direction = facing.down; this.y += this.speed; this.is_moving = true; }
else if (controller.left) { this.direction = facing.left; this.x -= this.speed; this.is_moving = true; }
else if (controller.right) { this.direction = facing.right; this.x += this.speed; this.is_moving = true; }
if (!controller.up && !controller.down && !controller.left && !controller.right) { this.is_moving = false; }
}
// Checks
if (this.is_menu_open) { this.can_move = false; } else { this.can_move = true; }
document.getElementById("health_bar").value = this.health;
if (this.is_menu_open) { menu.style.display = "block"; } else { menu.style.display = "none"; }
}
animate() {
// Player shadow
canv.drawImage(player_shadow, this.x, this.y + this.shadow_offset);
// Player
canv.globalAlpha = 1;
canv.drawImage(player_sheet, (sprite.x + this.frame), (sprite.y * this.direction),
sprite.width, sprite.height,
this.x, this.y,
this.width, this.height);
}
}
Here is a download to my current code in case you need more to debug: game.zip
The way I handle this is to make sure my image is first oriented correctly. In my case my bow image would look like this (ignore the quality). Just see that it faces right.
In the draw function I use translate() to position the image and I use the x and y inside the drawImage(img, x, y, w, h) to draw the image centered along the top edge of the canvas. The x (set to 50 in this example) position is essentially the radius of the image rotation and the y is just to center my bow's arrow on the y axis of the canvas.
Using Math.atan2() I can rotate the image
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
In this snippet I have two lines that are commented out. If you uncomment them you will better see what is happening.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
let mouse = {x: 10, y: 10}
let canvasBounds = canvas.getBoundingClientRect();
canvas.addEventListener('mousemove', e => {
mouse.x = e.x - canvasBounds.x
mouse.y = e.y - canvasBounds.y
})
let bow = new Image();
bow.src = "https://lh3.googleusercontent.com/g5Sr3HmGZgWx07sRQMvgvtxZ-ErhWNT0_asFdhLIlw-EQMTuUq3BV3YY8d5rrIrZBiJ-Uo2l836Qlmr8dmaCi-dcCCqN6veS6xnE8jSrmdtRtZKnmF5FQ5aTxuVBgB28n6ICoxSlpA=w2400";
class Weapon {
constructor() {
this.x = 200;
this.y = 200;
this.w = 60;
this.h = 60;
this.angle = 0
}
draw() {
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
//ctx.fillStyle = 'lightgrey';
//ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(bow, 50, -this.h/2, this.w, this.h)
ctx.restore();
}
}
let bowArrow = new Weapon();
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
bowArrow.draw();
requestAnimationFrame(animate)
}
animate();
<canvas id="canvas"></canvas>
UPDATE:
Here is what I changed in your files and it works well on my end.
engine.controller.js change mousemove and add listener for resize. I'm also pretty sure there's a way you can get rid of the getMouse function since the listener below gets mouse coordinates for you.
canvas.addEventListener("mousemove", function (event) {
mouse_position = event
mouse.x = event.x - canvas_bounds.x;
mouse.y = event.y - canvas_bounds.y;
})
window.addEventListener('resize', () => {
canvas_bounds = canvas.getBoundingClientRect();
})
engine.eventhandler.js
class Weapon {
image_path;
constructor(image_path) {
this.x = player.x + player.width/2;
this.y = player.y + player.height/2;
this.w = 60;
this.h = 60;
this.angle = 0;
this.image = image_path;
}
equip() {
this.x = player.x + player.width/2;
this.y = player.y + player.height/2;
this.angle = Math.atan2(mouse.y - this.y, mouse.x - this.x)
canv.save();
canv.translate(this.x, this.y);
canv.rotate(this.angle);
canv.drawImage(this.image, 50, -this.h/2, this.w, this.h);
canv.restore();
}
}
engine.main.js
function setup() {
game = new Game;
player = new Player;
controller = new Controller;
event_handler = new EventHandler;
canvas.width = 1200;
canvas.height = 700;
canvas_bounds = canvas.getBoundingClientRect();
// REMOVE LATER
bow = new Weapon(bow_image);
player.bag.slot_1 = bow;
document.getElementById("bag").style.display = "block";
}
engine.setup.js
// Weapon stuff
var weapon_equiped = false;
var canvas = document.getElementById("canvas");
let mouse = {
x : 10,
y : 10
}
let canvas_bound;
var mouse_position, holding;
var rect, mouse_x, mouse_y;
...and
//you have a space between canvas. height in both fillRect
canv.fillRect(0, 0, canvas.width, canvas. height);
I think that's all I changed. Pretty sure you can use your mouse_x or the mouse.x but you probably don't need both in your code.
so I made this way to detect and react to squares moving and touching other squares. It uses Pythagorean theorem to make a third parameter so you don't get two true if statements when squares touch. I recently have been attempting to use this method on a square to a rectangle, and cannot seem to get it working. I have drawn lines to help visualize what the code is doing. Anybody have any suggestions on how to get this collision working properly?
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, w, h, vx, vy, c, j) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.canvasCollision() //must be after other updates
}
}
let player1 = new Player(75, canvas.height/2 + 75, 75, 75, 0, 0, '#8DAA9D', false);
function controlPlayer1(obj) {
//this order matters. If update is before jump then obj won't jump when on top of other block.
if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller1.left1) { obj.vx -= 0.5 };
if (controller1.right1) { obj.vx += 0.5 };
obj.update();
}
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.down1 = false;
let controller1 = (e) => {
if (e.code === 'KeyD') { this.right1 = e.type === 'keydown' }
if (e.code === 'KeyA') { this.left1 = e.type === 'keydown' }
if (e.code === 'KeyW') { this.up1 = e.type === 'keydown' }
if (e.code === 'KeyS') { this.down1 = e.type === 'keydown' }
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
}
}
let controller1 = new Controller();
//PLATFORM
class Platform {
constructor(x, y, w, h, yv, c) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.yv = yv;
this.color = c;
}
draw(){
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
update(){
this.draw();
this.y += this.yv;
this.yv *= 0.9;// friction
}
}
let platform1 = new Platform(canvas.width/2, canvas.height - 150, 100, 30, 0, '#202020');
let platform2 = new Platform (canvas.width/4, canvas.height/2, 75, 75, 0, '#202020');
//COLLISION DETECTION
function platformDetection(obj, obj2){
//center point of each side of obj1
let objLeft = {x: obj.x, y: obj.y + obj.h/2};
let objTop = {x: obj.x + obj.w/2, y: obj.y};
let objRight = {x: obj.x + obj.w, y: obj.y + obj.h/2};
let objBottom = {x: obj.x + obj.w/2, y: obj.y + obj.h};
//center point of each side a obj2
let obj2Left = {x: obj2.x, y: obj2.y + obj2.h/2};
let obj2Top = {x: obj2.x + obj2.w/2, y: obj2.y};
let obj2Right = {x: obj2.x + obj2.w, y: obj2.y + obj2.h/2};
let obj2Bottom = {x: obj2.x + obj2.w/2, y: obj2.y + obj2.h};
//distance between obj1 and obj2 opposing sides
let rightDistX = objRight.x - obj2Left.x;
let rightDistY = objRight.y - obj2Left.y;
let leftDistX = objLeft.x - obj2Right.x;
let leftDistY = objLeft.y - obj2Right.y;
let topDistX = objTop.x - obj2Bottom.x;
let topDistY = objTop.y - obj2Bottom.y;
let bottomDistX = objBottom.x - obj2Top.x;
let bottomDistY = objBottom.y - obj2Top.y;
//pythagorean theorem for distance. dRight is from the right side of obj1 to the left of obj2. the rest follow suit.
let dRight = Math.sqrt(rightDistX*rightDistX + rightDistY*rightDistY);
let dLeft = Math.sqrt(leftDistX*leftDistX + leftDistY*leftDistY);
let dTop = Math.sqrt(topDistX*topDistX + topDistY*topDistY);
let dBottom = Math.sqrt(bottomDistX*bottomDistX + bottomDistY*bottomDistY);
//Math.min return the smallest value thus variable minimum will be which ever sides are closest together
let minimum = Math.min(dRight, dLeft, dBottom, dTop);
let val = 0;
//compare minimum to pythagorean theorem and set val based on which ever side is closest
if (dTop == minimum) {
val = 1;
//the context stuff can be deleted. It's just here for visual. The if statements can be one line each.
context.lineWidth = 2;
context.strokeStyle = 'blue';
context.beginPath();
context.moveTo(objTop.x, objTop.y);
context.lineTo(obj2Bottom.x, obj2Bottom.y);
context.stroke();
}
else if (dRight == minimum) {
val = 2;
context.strokeStyle = 'orange';
context.beginPath();
context.moveTo(objRight.x, objRight.y);
context.lineTo(obj2Left.x, obj2Left.y);
context.stroke();
}
else if (dBottom == minimum) {
val = 3;
context.strokeStyle = 'green';
context.beginPath();
context.moveTo(objBottom.x, objBottom.y);
context.lineTo(obj2Top.x, obj2Top.y);
context.stroke();
}
else if (dLeft == minimum) {
val = 4;
context.strokeStyle = 'pink';
context.beginPath();
context.moveTo(objLeft.x, objLeft.y);
context.lineTo(obj2Right.x, obj2Right.y);
context.stroke();
}
//pass the objects and val
platformAction(obj, obj2, val);
}
//ACTION
function platformAction(obj, obj2, val){
//player1 top to player2 bottom
if (obj.y <= obj2.y + obj2.h && obj2.y + obj2.h >= obj.y && val == 1) {
obj2.y = obj.y - obj2.h;
obj.y = obj2.y + obj2.h;
obj2.vy = 0;
obj2.jumping = false;
obj.jumping = true;
}
//player1 right to player2 left
if (obj.x + obj.w >= obj2.x && obj2.x <= obj.x + obj.w && val == 2) {
obj2.x = obj.x + obj.w;
obj.x = obj2.x - obj.w - 1;
obj2.vx = 0;
}
//player1 bottom to player2 top
if (obj.y + obj.h >= obj2.y && obj2.y <= obj.y + obj.h && val == 3) {
obj.y = obj2.y - obj.h;
obj2.y = obj.y + obj.h;
obj.vy = 0;
obj.jumping = false;
obj2.jumping = true;
}
//player1 left to player2 right
if (obj.x <= obj2.x + obj2.w && obj2.x + obj2.w >= obj.x && val == 4) {
obj2.x = obj.x - obj2.w;
obj.x = obj2.x + obj2.w + 1;
obj.vx = 0;
obj2.vx = 0;
}
}
function initObj(obj){
obj.update();
}
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
//PLATFORM
initObj(platform1);
initObj(platform2);
//PLATFORM DETECTION
platformDetection(player1, platform1);
platformDetection(player1, platform2);
//PLAYER
controlPlayer1(player1);
requestAnimationFrame(loop)
}
loop();
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi">
<title>Tone.io</title>
<style>
body {
height:100vh;
width:100vh;
margin: 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src = "Js/Tone.js" type = "text/javascript"></script>
</body>
</html>
#MPdoor2 "I made...". Anywho, when I gave you that code I did specifically say I had hacked it from a method I created to be used on tilemaps. The method works flawless for that purpose although there is more to the code and yes it is built for squares since that's what tiles map mainly are.
I have been playing with alternate methods of doing CD. Here's a shorter method that (so far) seems to be working well. This method still determines the distance between each side but in a different way. Once the broadphase determines a collision has occurred it calls the narrow phase and whichever side has the shortest distance is the side being penetrated. i.e. when you collide with another block from the player right to the object left we know that even the Y axis penetrates (top and bottom corners of player). This calculates the distance between all three and since the distance between X would be 0 it is the shortest and the CD for moving the player in the Y direction does not get called.
Try the snippet below and see if this works for you.
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
canvas.width = 700;
canvas.height = 500;
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, vx, vy, c, j) {
//each player must have separate values for control purposes i.e. velocity/jump/attack etc.
this.x = x;
this.y = y;
this.w = 50;
this.h = 50;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
this.center = {x: this.x + this.w/2, y: this.y + this.h/2};
this.topLeft = {x: this.x, y: this.y};
this.topRight = {x: this.x + this.w, y: this.y};
this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
this.bottomLeft = {x: this.x, y: this.y + this.h};
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.center = {x: this.x + this.w/2, y: this.y + this.h/2};
this.topLeft = {x: this.x, y: this.y};
this.topRight = {x: this.x + this.w, y: this.y};
this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
this.bottomLeft = {x: this.x, y: this.y + this.h};
this.canvasCollision() //must be after other updates
}
}
let player = new Player(0, 0, 0, 0, 'red', false);
function controlPlayer1(obj) {
if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller1.left1) { obj.vx -= 0.5 };
if (controller1.right1) { obj.vx += 0.5 };
obj.update();
}
class Platform {
constructor(x,y,w,h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.center = {x: this.x + this.w/2, y: this.y + this.h/2};
this.topLeft = {x: this.x, y: this.y};
this.topRight = {x: this.x + this.w, y: this.y};
this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
this.bottomLeft = {x: this.x, y: this.y + this.h};
}
draw() {
context.fillStyle = 'blue';
context.fillRect(this.x, this.y, this.w, this.h);
}
}
let platforms = [];
function createPlatforms() {
for (let i=0; i<4; i++) {
platforms.push(new Platform(200 * i, 450 - (i * 75), 100, 25));
}
}
createPlatforms();
let platform1 = platforms.push(new Platform(300, 400, 50, 100));
let platform2 = platforms.push(new Platform(400, 400, 50, 50));
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.left2 = false;
this.up2 = false;
this.right2 = false;
let controller1 = (e) => {
if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
if (e.code === 'ArrowLeft') { this.left1 = e.type === 'keydown' }
if (e.code === 'ArrowUp') { this.up1 = e.type === 'keydown' }
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
}
}
let controller1 = new Controller();
function collisionDetection(obj) {
if (player.x + player.w < obj.x ||
player.x > obj.x + obj.w ||
player.y + player.h < obj.y ||
player.y > obj.y + obj.h) {
return
}
narrowPhase(obj);
}
function narrowPhase(obj) {
let playerTop_ObjBottom = Math.abs(player.y - (obj.y + obj.h));
let playerRight_ObjLeft = Math.abs((player.x + player.w) - obj.x);
let playerLeft_ObjRight = Math.abs(player.x - (obj.x + obj.w));
let playerBottom_ObjTop = Math.abs((player.y + player.h) - obj.y);
if ((player.y <= obj.y + obj.h && player.y + player.h > obj.y + obj.h) && (playerTop_ObjBottom < playerRight_ObjLeft && playerTop_ObjBottom < playerLeft_ObjRight)) {
player.y = obj.y + obj.h;
player.vy = 0;
}
if ((player.y + player.h >= obj.y && player.y < obj.y) && (playerBottom_ObjTop < playerRight_ObjLeft && playerBottom_ObjTop < playerLeft_ObjRight)) {
player.y = obj.y - player.h;
player.jumping = false;
player.vy = 0;
}
if ((player.x + player.w >= obj.x && player.x < obj.x) && (playerRight_ObjLeft < playerTop_ObjBottom && playerRight_ObjLeft < playerBottom_ObjTop)) {
player.x = obj.x - player.w;
player.vx = 0;
}
if ((player.x <= obj.x + obj.w && player.x + player.w > obj.x + obj.w) && (playerLeft_ObjRight < playerTop_ObjBottom && playerLeft_ObjRight < playerBottom_ObjTop)) {
player.x = obj.x + obj.w;
player.vx = 0;
}
}
function animate() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
controlPlayer1(player);
for (let i=0;i<platforms.length;i++) {
platforms[i].draw();
collisionDetection(platforms[i])
};
requestAnimationFrame(animate)
}
animate();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
<title>CD</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="squareRectCollision2.js"></script>
</body>
</html>
To detect collisions between rectangles, use the position of their sides. For instance, they "collide vertically" if:
object1's bottom side is below object2's bottom, and object1's top is above object2's bottom
or object1's top side is above object2's top, and object1's bottom is below object2's top
Consider they do collide if they collide both "vertically" and "horizontally".
Now once you've determined that, you need to detect from which edge. I'm using the closest one here, evaluating the distance for all that "collide vertically" or "horizontally".
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
canvas.width = Math.min(window.innerWidth - 3, 800);
canvas.height = Math.min(window.innerHeight - 3, 200);
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, w, h, vx, vy, c, j) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {
this.y = canvas.height - this.h;
this.vy = 0;
this.jumping = false
};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.canvasCollision() //must be after other updates
}
}
let player1 = new Player(75, canvas.height / 2 + 75, 75, 75, 0, 0, '#8DAA9D', false);
function controlPlayer1(obj) {
//this order matters. If update is before jump then obj won't jump when on top of other block.
if (controller1.up1 && !obj.jumping) {
obj.vy -= 25;
obj.jumping = true
};
if (controller1.left1) {
obj.vx -= 0.5
};
if (controller1.right1) {
obj.vx += 0.5
};
obj.update();
}
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.down1 = false;
let controller1 = (e) => {
if (e.code === 'KeyD') {
this.right1 = e.type === 'keydown'
}
if (e.code === 'KeyA') {
this.left1 = e.type === 'keydown'
}
if (e.code === 'KeyW') {
this.up1 = e.type === 'keydown'
}
if (e.code === 'KeyS') {
this.down1 = e.type === 'keydown'
}
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
}
}
let controller1 = new Controller();
//PLATFORM
class Platform {
constructor(x, y, w, h, yv, c) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.yv = yv;
this.color = c;
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
update() {
this.draw();
this.y += this.yv;
this.yv *= 0.9; // friction
}
}
let platform1 = new Platform(canvas.width / 2, canvas.height - 150, 100, 30, 0, '#202020');
let platform2 = new Platform(canvas.width / 4, canvas.height / 2, 75, 75, 0, '#202020');
//COLLISION DETECTION
function platformDetection(obj, obj2) {
let ol1 = obj.x;
let ot1 = obj.y;
let or1 = ol1 + obj.w;
let ob1 = ot1 + obj.h;
let ol2 = obj2.x;
let ot2 = obj2.y;
let or2 = ol2 + obj2.w;
let ob2 = ot2 + obj2.h;
let [collideTop, collideBottom, collideRight, collideLeft] = [-1, -1, -1, -1];
if (ob1 >= ob2) {
if (ot1 <= ob2) {
collideTop = ob2 - ot1;
}
}
if (ol1 <= ol2) {
if (or1 >= ol2) {
collideRight = or1 - ol2;
}
}
if (ot1 <= ot2) {
if (ob1 >= ot2) {
collideBottom = ob1 - ot2;
}
}
if (or1 >= or2) {
if (ol1 <= or2) {
collideLeft = or2 - ol1;
}
}
let min = minOfVals(collideTop, collideRight, collideBottom, collideLeft);
let val = 0;
if (min >= 0) {
if (min == collideTop && (collideLeft >= 0 || collideRight >= 0)) {
val = 1;
} else if (min == collideRight && (collideTop >= 0 || collideBottom >= 0)) {
val = 2;
} else if (min == collideBottom && (collideLeft >= 0 || collideRight >= 0)) {
val = 3;
} else if (min == collideLeft && (collideTop >= 0 || collideBottom >= 0)) {
val = 4;
}
}
if (val) console.log(val, min, collideTop, collideRight, collideBottom, collideLeft);
//pass the objects and val
platformAction(obj, obj2, val);
}
function minOfVals(...vals) {
let min = -1;
function isBelowMin(v) {
return v >= 0 && (min < 0 || v < min);
}
for (v of vals) {
if (isBelowMin(v)) min = v;
}
return min;
}
//ACTION
function platformAction(obj, obj2, val) {
//player1 top to player2 bottom
if (val == 1) {
console.log("colliding from top");
obj2.y = obj.y - obj2.h;
//obj.y = obj2.y + obj2.h; //useless
obj2.vy = 0;
obj2.jumping = false;
obj.jumping = true;
}
//player1 right to player2 left
if (val == 2) {
console.log("colliding from right");
obj2.x = obj.x + obj.w;
obj.x = obj2.x - obj.w - 1;
obj2.vx = 0;
}
//player1 bottom to player2 top
if (val == 3) {
console.log("colliding from bottom");
obj.y = obj2.y - obj.h;
//obj2.y = obj.y + obj.h; //useless
obj.vy = 0;
obj.jumping = false;
obj2.jumping = true;
}
//player1 left to player2 right
if (val == 4) {
console.log("colliding from left");
obj2.x = obj.x - obj2.w;
obj.x = obj2.x + obj2.w + 1;
obj.vx = 0;
obj2.vx = 0;
}
}
function initObj(obj) {
obj.update();
}
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
//PLATFORM
initObj(platform1);
initObj(platform2);
//PLATFORM DETECTION
platformDetection(player1, platform1);
platformDetection(player1, platform2);
//PLAYER
controlPlayer1(player1);
requestAnimationFrame(loop)
}
loop();
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi">
<title>Tone.io</title>
<style>
html {
margin: 0;
padding: 0;
}
body {
height: 100vh;
width: 100vh;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="Js/Tone.js" type="text/javascript"></script>
</body>
</html>
There's still a different behavior when your controlled player comes from the left or from the right: push speed is not the same. But that's not due to collisions, so I'm letting you figure out this one (maybe it's on purpose too).
Yo, thx Justin for a solution. I took that solution and also added it to a previous question I asked, which was how to do the collision detection and action for two squares that have movement (with arrows and wasd). This is how I added it:
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let gravity = 1.5;
let friction = 0.9;
//CHARACTER:
class Player {
constructor(x, y, w, h, vx, vy, c, j) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = vx;
this.vy = vy;
this.color = c;
this.jumping = j;
this.center = {x: this.x + this.w/2, y: this.y + this.h/2};
this.topLeft = {x: this.x, y: this.y};
this.topRight = {x: this.x + this.w, y: this.y};
this.bottomRight = {x: this.x + this.w, y: this.y + this.h};
this.bottomLeft = {x: this.x, y: this.y + this.h};
}
draw() {
context.fillStyle = this.color;
context.fillRect(this.x, this.y, this.w, this.h);
}
canvasCollision() {
if (this.x <= 0) this.x = 0;
if (this.y <= 0) this.y = 0;
if (this.x + this.w >= canvas.width) this.x = canvas.width - this.w;
if (this.y + this.h >= canvas.height) {this.y = canvas.height - this.h; this.vy = 0; this.jumping = false};
}
update() {
this.draw();
this.vy += gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.canvasCollision() //must be after other updates
}
}
let player1 = new Player(canvas.width/2, canvas.height/2 + 75, 75, 75, 0, 0, 'darkgrey', false);
let player2 = new Player(75, canvas.height/2 + 75, 75, 75, 0, 0, '#8DAA9D', false);
function controlPlayer1(obj) {
//this order matters. If update is before jump then obj won't jump when on top of other block.
if (controller1.up1 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller1.left1) { obj.vx -= 0.5 };
if (controller1.right1) { obj.vx += 0.5 };
obj.update();
}
function controlPlayer2(obj) {
if (controller2.up2 && !obj.jumping) { obj.vy -= 25; obj.jumping = true };
if (controller2.right2) { obj.vx += 0.5 };
if (controller2.left2) { obj.vx -= 0.5 };
obj.update();
}
//MOVEMENT:
class Controller {
constructor() {
this.left1 = false;
this.up1 = false;
this.right1 = false;
this.left2 = false;
this.up2 = false;
this.right2 = false;
this.down = false;
let controller1 = (e) => {
if (e.code === 'ArrowRight') { this.right1 = e.type === 'keydown' }
if (e.code === 'ArrowLeft') { this.left1 = e.type === 'keydown' }
if (e.code === 'ArrowUp') { this.up1 = e.type === 'keydown' }
if (e.code === 'ArrowDown') { this.down = e.type === 'keydown' }
}
let controller2 = (e) => {
if (e.code === 'KeyD') { this.right2 = e.type === 'keydown' }
if (e.code === 'KeyA') { this.left2 = e.type === 'keydown' }
if (e.code === 'KeyW') { this.up2 = e.type === 'keydown' }
if (e.code === 'KeyS') { this.down = e.type === 'keydown' }
}
window.addEventListener('keydown', controller1);
window.addEventListener('keyup', controller1);
window.addEventListener('keydown', controller2);
window.addEventListener('keyup', controller2);
}
}
let controller1 = new Controller();
let controller2 = new Controller();
//COLLISION DETECTION
function collisionDetection(obj, obj2){
if (obj.x + obj.w <= obj2.x ||
obj.x >= obj2.x + obj2.w ||
obj.y + obj.h < obj2.y ||
obj.y > obj2.y + obj2.h)
return
collisionAction(obj, obj2);
}
//ACTION
function collisionAction(obj, obj2){
let playerTop_ObjBottom = Math.abs(obj.y - (obj2.y + obj2.h));
let playerRight_ObjLeft = Math.abs((obj.x + obj.w) - obj2.x);
let playerLeft_ObjRight = Math.abs(obj.x - (obj2.x + obj2.w));
let playerBottom_ObjTop = Math.abs((obj.y + obj.h) - obj2.y);
//virtical obj 1 top
if ((obj.y <= obj2.y + obj2.h && obj.y + obj.h > obj2.y + obj2.h) && (playerTop_ObjBottom < playerRight_ObjLeft && playerTop_ObjBottom < playerLeft_ObjRight)) {
obj2.y = obj.y - obj2.h;
obj2.jumping = false;
obj2.vy = 0;}
//virtical obj 1 bottom
if ((obj.y + obj.h >= obj2.y && obj.y < obj2.y) && (playerBottom_ObjTop < playerRight_ObjLeft && playerBottom_ObjTop < playerLeft_ObjRight)) {
obj.y = obj2.y - obj.h;
obj.jumping = false;
obj.vy = 0;}
//horizontal obj 1 right
if ((obj.x + obj.w >= obj2.x && obj.x < obj2.x) && (playerRight_ObjLeft < playerTop_ObjBottom && playerRight_ObjLeft < playerBottom_ObjTop)) {
obj2.x = obj.x + obj.w;
obj.x = obj2.x - obj.w;
obj.vx = 0;
obj2.vx = 0;}
//horizontal obj1 left
if ((obj.x <= obj2.x + obj2.w && obj.x + obj.w > obj2.x + obj2.w) && (playerLeft_ObjRight < playerTop_ObjBottom && playerLeft_ObjRight < playerBottom_ObjTop)) {
obj2.x = obj.x - obj2.w;
obj.x = obj2.x + obj2.w;
obj.vx = 0;
obj2.vx = 0;}
}
function loop() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = 'grey';
context.fillRect(0, 0, canvas.width, canvas.height);
//PLAYER
controlPlayer1(player1);
controlPlayer2(player2);
collisionDetection(player1, player2)
requestAnimationFrame(loop)
}
loop();
Im making a simple pong game and when you move the red box up with 'w' the ball still bounces on nothing back to the left. I feel like maybe the bounding box is set up incorrectly to the left side. I cant figure out what I did wrong?
var canvas;
var context;
var timer;
var interval = 1000 / 60;
var player1;
var player2;
var ball;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
//player = new Player();
player1 = new GameObject();
player2 = new GameObject();
ball = new GameObject();
timer = setInterval(animate, interval);
function animate() {
context.clearRect(0, 0, canvas.width, canvas.height);
ball.move();
//Move the Player to the right
if (w) {
console.log("");
player1.y += -8;
}
if (s) {
console.log("");
player1.y += 8;
}
if (d) {
console.log("");
player2.y += -8;
}
if (u) {
console.log("");
player2.y += 8;
}
if (player1.y > canvas.height - player1.height / 2) {
player1.y = canvas.height - player1.height / 2;
}
if (player1.y < player1.height / 2) {
player1.y = player1.height / 2;
}
if (player2.y > canvas.height - player2.height / 2) {
player2.y = canvas.height - player2.height / 2;
}
if (player2.y < player2.height / 2) {
player2.y = player2.height / 2;
}
//bounce
if (ball.x > canvas.width - ball.width / 2) {
ball.vx = -ball.vx;
//ball.x = 300;
color = "#ff0000";
}
if (ball.x < 0 + ball.width / 2) {
//this flips it if its a negative sign.
ball.vx = -ball.vx;
//ball.x = 300;
color = "#FF4500";
}
//-------------------------------------------------------
//Define Booleans for each key
var w = false;
var s = false;
var u = false;
var d = false;
//Add Event Listeners
document.addEventListener("keydown", press);
document.addEventListener("keyup", release);
document.addEventListener("keydown", presss);
document.addEventListener("keyup", releases);
//Event Functions
function press(e) {
//---This logs key codes into the browser's console.
console.log("Pressed" + e.keyCode);
if (e.keyCode == 87) {
w = true;
}
if (e.keyCode == 83) {
s = true;
}
}
function release(e) {
//---This logs key codes into the browser's console.
//console.log("Released" + e.keyCode);
if (e.keyCode == 87) {
w = false;
}
if (e.keyCode == 83) {
s = false;
}
}
function presss(e) {
//---This logs key codes into the browser's console.
//console.log("Pressed" + e.keyCode);
if (e.keyCode == 40) {
u = true;
}
if (e.keyCode == 38) {
d = true;
}
}
function releases(e) {
//---This logs key codes into the browser's console.
//console.log("Released" + e.keyCode);
if (e.keyCode == 40) {
u = false;
}
if (e.keyCode == 38) {
d = false;
}
}
// JavaScript Document
function GameObject() {
//player's location
this.x = canvas.width / 4;
this.y = canvas.height / 2;
//player's dimensions
this.width = 50;
this.height = 250;
//barriers
//ballss velocity or speed on each axis
this.vx = 8;
this.vy = 0;
//player's color
var red = "#ff0000";
this.blue = "#0000FF";
this.color = "#ff0000";
this.other = "#0000FF";
//This draws the player to the screen
this.drawRect = function() {
context.save();
context.fillStyle = this.color;
context.translate(this.x, this.y);
context.fillRect((-this.width / 2), (-this.height / 2), this.width, this.height);
context.restore();
}
this.second_drawRect = function() {
context.save();
context.fillStyle = this.blue;
context.translate(canvas.width / 1.5, this.y);
context.fillRect((-this.width / 2), (-this.height / 2), this.width, this.height);
context.restore();
}
this.drawCircle = function() {
context.save();
context.fillStyle = this.color;
context.translate(this.x, this.y);
context.beginPath();
context.arc(0, 0, 50, 0, 360 * Math.PI / 180, true)
context.fill();
context.stroke();
//context.fillRect((-this.width/2), (-this.height/2), this.width, this.height);
context.restore();
}
//This changes the player's position
this.move = function() {
this.x += this.vx;
this.y += this.vy;
}
this.left = function() {
return this.x - this.width / 1;
}
this.right = function() {
return this.x + this.width / 2;
}
this.top = function() {
return this.y - this.height / 4;
}
this.bottom = function() {
return this.y + this.height / 4;
}
//////////////////////////////////////////////////////////////
this.leftt = function() {
return this.x - this.width / 2;
}
this.rightt = function() {
return this.x + this.width / 2;
}
this.topp = function() {
return this.y - this.height / 2;
}
this.bottomm = function() {
return this.y + this.height / 2;
}
this.hitTestObject = function(obj) {
if (this.left() < obj.right() &&
this.right() > obj.left() &&
this.top() < obj.bottom() &&
this.bottom() > obj.top()) {
return true
}
return false;
}
}
<canvas id="canvas" width = "1024" height ="800" >
Your browser is outdated and does not support HTML5. Please update to the latest version.
</canvas>
I recently made a JS Pong game. It works well, but the ball rarely gets stuck at the bottom or top. It looks like it is halfway through the wall and constantly bouncing. Video of the issue happening. You can try the game here. I do not know why this issue is happening because the logic seems right and works 90% of the time correctly. Here are the main two functions of my program:
function moveAll() {
if (showingWinScreen) {
return;
}
computerMovement();
ballX += ballSpeedX;
ballY += ballSpeedY;
if (ballY <= 10) {
ballSpeedY = -ballSpeedY;
} else if (ballY >= HEIGHT - 10) {
ballSpeedY = -ballSpeedY;
}
if (ballX >= WIDTH - 10) {
if ((ballY > paddleY) && (ballY < paddleY + 100)) {
ballSpeedX = -ballSpeedX;
var deltaY = ballY - paddleY - 50;
ballSpeedY = deltaY / 5;
} else {
player1Score++;
ballReset();
}
} else if (ballX <= 10) {
if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
ballSpeedX = -ballSpeedX;
deltaY = ballY - mouseY;
ballSpeedY = deltaY / 6;
} else {
player2Score++;
ballReset();
}
}
}
function drawAll() {
if (showingWinScreen) {
colorRect(0, 0, WIDTH, HEIGHT, "black");
canvas.fillStyle = "yellow";
canvas.fillText("Click to continue!", 300, 300);
if (player1Score == WINNING_SCORE) {
canvas.fillText("You won!", 360, 500);
} else if (player2Score == WINNING_SCORE) {
canvas.fillText("The computer beat you!", 280, 500);
}
return;
}
colorRect(0, 0, WIDTH, HEIGHT, "black");
drawNet();
makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
colorRect(790, paddleY, 10, 100, "cyan");
colorRect(0, mouseY - 50, 10, 100, "yellow");
canvas.fillStyle = "white";
canvas.fillText(player1Score + " " + player2Score, 360, 100);
}
Thank you for your help!
I think there's only one case in which this could happen: when, in a colliding frame, you decrease the speed.
When the speed remains the same, no matter what, your ball will always bounce back to the previous' frames position:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 5, 5, "red"),
Ball(100, 50, 0, 5, 10, "blue"),
Ball(150, 50, 0, 5, 15, "green"),
Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -1;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
But when the speed changes, things get stuck:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 10, 5, "red"),
Ball(100, 50, 0, 10, 10, "blue"),
Ball(150, 50, 0, 10, 15, "green"),
Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -0.5;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
In your case, I'm thinking this can only happen when there's a paddle collision AND a wall collision simultaneously.
A quick-to-implement solution would be to check if the new position is valid before translating the ball position. If you don't want the precise location, you can place the ball at the point of collision. Note that this will produce a slightly off frame.
E.g.:
var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
ballY = 10;
ballSpeedY = -ballSpeedY;
}
// Bottom wall
else if(newY >= HEIGHT-10){
ballY = HEIGHT - 10;
ballSpeedY = -ballSpeedY;
}
// No collision
else {
ballY = newY;
}
Update: a more detailed description of what can happen
Let's say your ball collides with the top border of your canvas and with your paddle in the same frame.
First, you move the ball to the colliding position: ballY += ballSpeedY; Say your ballY is 4, and your ballSpeedY is -5, you'll position the ball to -1, inside the wall.
If this were to be the only collision, you should be okay. You flip the speed (ballSpeedY = -ballSpeedY), so in the next frame, your ball should be back at -1 + 5 = 4, so ballY will be 4 again, and your ball will move towards 4 + 5 = 9 in the next frame.
Now a problem arises, when in the -1 positioned frame, you collide with the paddle as well! When the paddle hits the ball, you modify the ballspeed: ballSpeedY = deltaY / 5;. If this turns out to be < 1, your ball won't be able to exit the wall in the next frame. Instead of -1 + 5 = 4, your ball will, for example, move to: -1 + 0.5 = -0.5.
Now, your ball won't be able to get back in to play, since the next frame will, again, calculate a collision and flip the speed. This results in the bouncy, trembling effect you see when the ball gets stuck.
A naive but pretty decent solution, is to only update the position of the ball to a valid position. I.e.: never to a colliding coordinate.
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
context.fillStyle = "#FF00FF";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function () {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function () {
update();
render();
animate(step);
};
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
}
Paddle.prototype.render = function () {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > 400) {
this.x = 400 - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
this.paddle.render();
};
Computer.prototype.update = function (ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > 400) {
this.paddle.x = 400 - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
this.paddle.render();
};
Player.prototype.update = function () {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function () {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > 400) {
this.x = 395;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > 600) {
this.x_speed = 0;
this.y_speed = 3;
this.x = 200;
this.y = 300;
}
if (top_y > 300) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete keysDown[event.keyCode];
});
http://jsfiddle.net/kHJr6/2/
I'm trying to make a brick game with a ball and a player (platform). If the ball hits the player, it should go off in the other direction like ping pong. However, it's not detecting the collision.
Here's the code
html:
<canvas id="canvas" width= "400" height= "400"></canvas>
css:
#canvas{border:1px solid black}
Js:
var width = 400
var height = 400
var drawRect = function (x, y) {
ctx.fillRect(x, y, 30, 5)
};
// The Ball constructor
var Player = function () {
this.x = 395
this.y = 395
this.xSpeed = 5;
this.ySpeed = 0;
};
// Update the ball's position based on its speed
Player.prototype.move = function () {
this.x += this.xSpeed;
this.y += this.ySpeed;
if (this.x < 0) {
this.x = width;
} else if (this.x > width) {
this.x = 0;
} else if (this.y < 0) {
this.y = height;
} else if (this.y > height) {
this.y = 0;
}
};
// Draw the ball at its current position
Player.prototype.draw = function () {
drawRect(this.x, this.y);
};
// Set the ball's direction based on a string
Player.prototype.setDirection = function (direction) {
if (direction === "left") {
this.xSpeed = -5;
this.ySpeed = 0;
} else if (direction === "right") {
this.xSpeed = 5;
this.ySpeed = 0;
} else if (direction === "stop") {
this.xSpeed = 0;
this.ySpeed = 0;
}
};
// Create the ball object
var player = new Player();
// An object to convert keycodes into action names
var keyActions = {
32: "stop",
37: "left",
38: "up",
39: "right",
40: "down"
};
// The keydown handler that will be called for every keypress
$("html").keydown(function (event) {
var direction = keyActions[event.keyCode];
player.setDirection(direction);
});
var Ball = function () {
this.x = 100;
this.y = 100;
this.xSpeed = -2
this.ySpeed = 3;
};
var circle = function (x, y, radius, fillCircle) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false)
if (fillCircle) {
ctx.fill();
} else {
ctx.stroke();
}
}
Ball.prototype.move = function () {
this.x += this.xSpeed
this.y += this.ySpeed
};
Ball.prototype.draw = function () {
circle(this.x, this.y, 3, true);
};
Ball.prototype.checkCollision = function () {
if (this.x < 0 || this.x > 400) {
this.xSpeed = -this.xSpeed
}
if (this.y < 0) {
this.ySpeed = -this.ySpeed
}
};
Ball.prototype.checkCollisionPlayer = function () {
if (this.x === Player.x || this.y === player.y) {
this.ySpeed = -this.ySpeed
this.xSpeed = -this.xSpeed
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
setInterval(function () {
ctx.clearRect(0, 0, 400, 400);
player.draw();
player.move();
ball.draw();
ball.move();
ball.checkCollision();
ball.checkCollisionPlayer();
}, 40);
var ball = new Ball();
Thanks for your support. :)
I've added a simple box collision and update the classes to have the box dimensions of the ball and player. Remember that the ball box has to adjust for radius offset.
update
The kind of collision detection needed for the boxes you have to know side or corner that was hit. I updated the example as a starting point for this but it's needs to have the corners added and I also don't know the optimal detection. The blocks are setup for testing. I hope this helps.
var width = 400
var height = 400
function Brick(x, y, w, h, color) {
this.x = x;
this.y = y;
this.color = color;
this.w = w;
this.h = h;
this.hits = 0;
}
Brick.prototype.draw = function() {
ctx.save();
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
ctx.fillStyle = "white";
ctx.fillText(this.hits, this.x + 2, this.y + 10);
ctx.restore();
};
var bricks = [
new Brick(80, 120, 15, 15, 'red'),
new Brick(220, 90, 15, 15, 'blue'),
new Brick(340, 100, 50, 20, 'green')
];
// The Ball constructor
var Player = function() {
this.x = 395
this.y = 395
this.xSpeed = 5;
this.ySpeed = 0;
this.w = 30;
this.h = 5;
};
// Update the ball's position based on its speed
Player.prototype.move = function() {
this.x += this.xSpeed;
this.y += this.ySpeed;
if (this.x < 0) {
this.x = width;
} else if (this.x > width) {
this.x = 0;
} else if (this.y < 0) {
this.y = height;
} else if (this.y > height) {
this.y = 0;
}
};
// Draw the ball at its current position
Player.prototype.draw = function() {
ctx.fillRect(this.x, this.y, this.w, this.h);
};
// Set the ball's direction based on a string
Player.prototype.setDirection = function(direction) {
if (direction === "left") {
this.xSpeed = -5;
this.ySpeed = 0;
} else if (direction === "right") {
this.xSpeed = 5;
this.ySpeed = 0;
} else if (direction === "stop") {
this.xSpeed = 0;
this.ySpeed = 0;
}
};
// Create the ball object
var player = new Player();
// An object to convert keycodes into action names
var keyActions = {
32: "stop",
37: "left",
38: "up",
39: "right",
40: "down"
};
// The keydown handler that will be called for every keypress
$("html").keydown(function(event) {
var direction = keyActions[event.keyCode];
player.setDirection(direction);
});
var Ball = function() {
this.x = 100;
this.y = 100;
this.xSpeed = -2
this.ySpeed = 3;
this.radius = 3;
};
var circle = function(x, y, radius, fillCircle) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false)
if (fillCircle) {
ctx.fill();
} else {
ctx.stroke();
}
}
Ball.prototype.move = function() {
this.x += this.xSpeed
this.y += this.ySpeed
if ((this.y + this.radius) > ctx.canvas.height) {
// floor to ceiling
this.y = 0;
}
};
Ball.prototype.draw = function() {
circle(this.x, this.y, this.radius, true);
};
Ball.prototype.getBox = function() {
return {
x: this.x - this.radius,
y: this.y - this.radius,
w: this.radius * 2,
h: this.radius * 2
};
};
Ball.prototype.checkCollision = function() {
if (this.x < 0 || this.x > 400) {
this.xSpeed = -this.xSpeed
}
if (this.y < 0) {
this.ySpeed = -this.ySpeed
} else {
var boxA = this.getBox();
switch (boxCollide(boxA, player)) {
case 1:
case 3:
this.ySpeed = -this.ySpeed;
break;
case 2:
case 4:
this.xSpeed = -this.xSpeed;
break;
}
}
};
// does box a collide with box b
// box = {x:num,y:num,w:num,h:num}
function boxCollide(a, b) {
var ax2 = a.x + a.w;
var ay2 = a.y + a.h;
var bx2 = b.x + b.w;
var by2 = b.y + b.h;
// simple hit true, false
//if (ax2 < b.x || a.x > bx2 || ay2 < b.y || a.y > by2) return false;
// return true
var xInRange = (a.x >= b.x && a.x <= bx2 || ax2 >= b.x && ax2 <= bx2);
var yInRange = (a.y >= b.y && a.y <= by2 || ay2 >= b.y && ay2 <= by2);
// Clockwise hit from top 1,2,3,4 or -1
if (ay2 > b.y && a.y < by2 && xInRange) return 1; // A hit the top of B
if (a.x < bx2 && ax2 > b.x && yInRange) return 2; // A hit the right of B
if (a.y < by2 && ay2 > b.y && xInRange) return 3; // A hit the bottom of B
if (ax2 > b.x && a.x < bx2 && yInRange) return 4; // A hit the right of B
return -1; // nohit
}
Ball.prototype.checkCollisionPlayer = function() {
if (this.x === Player.x || this.y === player.y) {
this.ySpeed = -this.ySpeed
this.xSpeed = -this.xSpeed
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
setInterval(function() {
ctx.clearRect(0, 0, 400, 400);
player.draw();
player.move();
ball.draw();
ball.move();
ball.checkCollision();
ball.checkCollisionPlayer();
var boxA = ball.getBox();
for (var i = 0; i < bricks.length; i++) {
switch (boxCollide(boxA, bricks[i])) {
case 1:
ball.y = bricks[i].y - ball.radius - 1;
ball.ySpeed = -ball.ySpeed;
bricks[i].hits++;
break;
case 3:
ball.y = bricks[i].y + ball.radius + bricks[i].h + 1;
ball.ySpeed = -ball.ySpeed;
bricks[i].hits++;
break;
case 2:
ball.x = bricks[i].x + ball.radius + bricks[i].w + 1;
ball.xSpeed = -ball.xSpeed;
bricks[i].hits++;
break;
case 4:
ball.x = bricks[i].x - ball.radius - 1;
ball.xSpeed = -ball.xSpeed;
bricks[i].hits++;
break;
}
bricks[i].draw();
}
},
40);
var ball = new Ball();
#canvas {
border: 1px solid black
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>