The Problem
I have been creating a game, I have got to a stage where I want to see what it looks like with a mockup background I have created.
The Question
Where about in my code should I place this code as the place it currently is doesnt show the background.
I want this background on the canvas, the dimensions are correct.
The Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
background_img.src = "background.png";
background_img.onload = function(){
context.drawImage(background_img, 0, 0);
}
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 20) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 20) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#410b11";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
ctx.drawImage(background_img, 0, backgroundY);
}
return {
init: init
};
}
As already pointed out in a comment, here more precisely:
First of all, the background picture must be rendered first in every animation frame.
However, the picture didn't show up at all. This is due to the fact that variable was used (backgroundY), which is never declared somewhere.
This should actually printed to the console as an error "backgroundY" is not defined.
Whenever an the property src of an image object is set to a value, it takes some time until it's loaded. So in many cases, it's necessary to indicate the moment, when it's finished loading by the onload callback.
In this case, however, it's not necessary. The tick / animation loop function will just draw nothing (an empty image object) until it's loaded. After it's loaded it will continue to draw the loaded image every frame.
If the background is really important, meaning, the app should only start, when it's there, of course, one can only start the whole game / animation from within the img.onload handler.
You must draw:
the background first
the player later
level/score info last
Background < Player < UI < You Looking
The drawing order is from back to top (painters algorithm)
Also note that for performance reasons if you background never changes you could draw it in another 'static' canvas under the game canvas.
Otherwise the background will be drawn above/over the player and hide it.
Related
I'm trying to get this code to work, I'm trying to make a game where you click on disappearing rectangles, and have made some good progress, but my issue is that when the rectangle disappears the 'hitbox' for it is still there, so to speak.
I'm still new with canvases, but I think it has something to do with the clearRect method on context, but I'm not sure. Here's my code:
<script>
const CANVAS_HEIGHT = 800;
const CANVAS_WIDTH = 1200;
var elem = document.getElementById('myCanvas'),
elemLeft = elem.offsetLeft,
elemTop = elem.offsetTop,
context = elem.getContext('2d'),
elements = [];
// Game vars
var score = 0;
var interval = 1000;
var wid = 150;
var hei = 100;
// Add event listener for `click` events.
elem.addEventListener('click', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.log(`Clicked at (${x}, ${y})`);
// Collision detection between clicked offset and element.
elements.forEach(function(element) {
if (y > element.top && y < element.top + element.height
&& x > element.left && x < element.left + element.width) {
score += 1;
wid -= 5/wid;
hei -= 5/hei;
console.log(`Score: ${score}`);
}
});
}, false);
// Add element.
function addElements(){
var wp = Math.floor(Math.random() * CANVAS_WIDTH);
var hp = Math.floor(Math.random() * CANVAS_HEIGHT);
// Get rekt
elements.push({
colour: '#05EFFF',
width: wid,
height: hei,
top: hp,
left: wp
});
// Render
elements.forEach(function(element) {
context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
context.fillStyle = element.colour;
context.fillRect(element.left, element.top, element.width, element.height);
});
}
(function myLoop (i) {
setTimeout(function () {
addElements(); // your code here
if (--i) myLoop(i); // decrement i and call myLoop again if i > 0
}, interval)
})(100);
</script>
You never remove any elements what so ever.
Try adding a console.log(elements.length); inside myLoop and watch as the counter goes up and up :)
One quickfix is just to reassign elements to an empty array in the top of addElements
elements = [];
I am making a bug smasher game, and want Boolean value(isOnCircle) to return 'true' when a user clicks the bug. However, it always returns 'false'. Although when I tested it as a circle instead of a bug image (with 'context.arc'), it returned 'true'.
*I also inserted an image into the canvas background.
bug.r=32 is the bug image's correct radius size.
var Bug = (function () {
function Bug() {
}
return Bug;
}());
var jumpInterval;
var interval = 4000;
var bug = new Bug();
var canvas = document.getElementById('canvas');
canvas.width =727
canvas.height = 483;
var context = canvas.getContext('2d');
var background = new Image();
background.src = "images/lawn.jpg";
background.onload = function () {
context.drawImage(background, 0, 0);
}
function handleClick(evt) {
console.log(evt.x + ',' + evt.y);
var x = evt.x - 50;
var y = evt.y - 100;
// (x - center_x)^2 + (y - center_y)^2 < radius^2
var isOnCircle = Math.pow(x - bug.x, 2) + Math.pow(y - bug.y, 2) < Math.pow(bug.r, 2);
console.log(isOnCircle);
if (interval > 500) {
interval -= 300;
} else {
interval = 500;
}
console.log(interval);
}
function jump() {
bug.x = ((Math.random() * 10324897) % 500) + 1;
bug.y = ((Math.random() * 10324897) % 500) + 1;
bug.r = 32;
context.clearRect(0, 0, canvas.width, canvas.height);
var background = new Image();
background.src = "images/lawn.jpg";
background.onload = function () {
context.drawImage(background, 0, 0);
}
console.log(bug.x);
console.log(bug.y);
context.beginPath();
//context.arc(bug.x, bug.y, bug.r, 0, 2 * Math.PI);
var bugImage = new Image();
bugImage.src = "images/bug.PNG";
bugImage.onload = function () {
context.drawImage(bugImage, bug.x, bug.y);
}
context.stroke();
clearInterval(jumpInterval);
jumpInterval = setInterval(jump, interval);
}
jumpInterval = setInterval(jump, interval);
canvas.addEventListener("click", handleClick);
Goal
The stripes in the background remain fixed while the cones rotate about the center.
Current State
live demo:
https://codepen.io/WallyNally/pen/yamGYB
/*
The loop function is around line 79.
Uncomment it to start the animation.
*/
var c = document.getElementById('canv');
var ctx = c.getContext('2d');
var W = c.width = window.innerWidth;
var H = c.height = window.innerHeight;
var Line = function() {
this.ctx = ctx;
this.startX = 0;
this.startY = 0;
this.endX = 0;
this.endY = 0;
this.direction = 0;
this.color = 'blue';
this.draw = function() {
this.ctx.beginPath();
this.ctx.lineWidth = .1;
this.ctx.strokeStlye = this.color;
this.ctx.moveTo(this.startX, this.startY);
this.ctx.lineTo(this.endX, this.endY);
this.ctx.closePath();
this.ctx.stroke();
}
this.update = function() {
//for fun
if (this.direction == 1) {
this.ctx.translate(W/2, H/2);
this.ctx.rotate(-Math.PI/(180));
}
}//this.update()
}//Line();
objects=[];
function initLines() {
for (var i =0; i < 200; i++) {
var line = new Line();
line.direction = (i % 2);
if (line.direction == 0) {
line.startX = 0;
line.startY = -H + i * H/100;
line.endX = W + line.startX;
line.endY = H + line.startY;
}
if (line.direction == 1) {
line.startX = 0;
line.startY = H - i * H/100;
line.endX = W - line.startX;
line.endY = H - line.startY;
}
objects.push(line);
line.draw();
}
}
initLines();
function render(c) {
c.clearRect(0, 0, W, H);
for (var i = 0; i < objects.length; i++)
{
objects[i].update();
objects[i].draw();
}
}
function loop() {
render(ctx);
window.requestAnimationFrame(loop);
}
//loop();
What I have tried
The translate(W/2, H/2) should place the context at the center of the page, then this.ctx.rotate(-Math.PI/(180)) should rotate it one degree at a time. This is the part that is not working.
Using save()and restore() is the proper way to keep some parts of an animation static while others move. I placed the save and restore in different parts of the code to no avail. There are one of two types of result : Either a new entirely static image is produced, or some erratic animation happens (in the same vein of where it is now).
Here is the changed pen: http://codepen.io/samcarlinone/pen/LRwqNg
You needed a couple of changes:
var c = document.getElementById('canv');
var ctx = c.getContext('2d');
var W = c.width = window.innerWidth;
var H = c.height = window.innerHeight;
var angle = 0;
var Line = function() {
this.ctx = ctx;
this.startX = 0;
this.startY = 0;
this.endX = 0;
this.endY = 0;
this.direction = 0;
this.color = 'blue';
this.draw = function() {
this.ctx.beginPath();
this.ctx.lineWidth = .1;
this.ctx.strokeStlye = this.color;
this.ctx.moveTo(this.startX, this.startY);
this.ctx.lineTo(this.endX, this.endY);
this.ctx.closePath();
this.ctx.stroke();
}
this.update = function() {
//for fun
if (this.direction == 1) {
this.ctx.translate(W/2, H/2);
this.ctx.rotate(angle);
this.ctx.translate(-W/2, -H/2);
}
}//this.update()
}//Line();
objects=[];
function initLines() {
for (var i =0; i < 200; i++) {
var line = new Line();
line.direction = (i % 2);
if (line.direction == 0) {
line.startX = 0;
line.startY = -H + i * H/100;
line.endX = W + line.startX;
line.endY = H + line.startY;
}
if (line.direction == 1) {
line.startX = 0;
line.startY = H - i * H/100;
line.endX = W - line.startX;
line.endY = H - line.startY;
}
objects.push(line);
line.draw();
}
}
initLines();
function render(c) {
c.clearRect(0, 0, W, H);
for (var i = 0; i < objects.length; i++)
{
ctx.save();
objects[i].update();
objects[i].draw();
ctx.restore();
}
}
function loop() {
render(ctx);
window.requestAnimationFrame(loop);
angle += Math.PI/360;
}
loop();
First I added a variable to keep track of rotation and increment it in the loop
Second I save and restore for each individual line, alternatively if all lines were going to perform the same transformation you could move that code before and after the drawing loop
Third to get the desired affect I translate so the center point is in the middle of the screen, then I rotate so that the lines are rotated, then I translate back because all the lines have coordinates on the interval [0, H]. Instead of translating back before drawing another option would be to use coordinates on the interval [-(H/2), (H/2)] etc.
The Problem
I am creating a game that involves dodging projectiles. The player is in control of an image of a ship and I dont want the ship to move exactly together as this looks very unrealistic.
The Question
Is there a way to control how fast the image moves, how can i slow the movemnt of the image down?
The Code
var game = create_game();
game.init();
//music
var snd = new Audio("Menu.mp3");
snd.loop = true;
snd.play();
document.getElementById('mute').addEventListener('click', function (evt) {
if ( snd.muted ) {
snd.muted = false
evt.target.innerHTML = 'mute'
}
else {
snd.muted = true
evt.target.innerHTML = 'unmute'
}
})
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#ff6600";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 74) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 177) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#ff6600";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
}
return {
init: init
};
}
https://jsfiddle.net/a6nmy804/4/ (Broken)
Throttle the player's movement using a "timeout" countdown
Create a global var playerFreezeCountdown=0.
In mousemove change player.x only if playerFreezeCountdown<=0.
If playerFreezeCountdown>0 you don't change player.x.
If playerFreezeCountdown<=0 you both change player.x and also set playerFreezeCountdown to a desired "tick timeout" value: playerFreezeCountdown=5. This timeout will cause prevent the player from moving their ship until 5 ticks have passed.
In tick, always decrement playerFreezeCountdown--. This will indirectly allow a change to player.x after when playerFreezeCountdown is decremented to zero or below zero.
JSFiddle
After each circle object is created, it should increase in size while the mouse is down, and stop when mouse is up. clearInterval doesn't seem to reach the internal variable "growLoop" even though it's supposed to be global by declaring it first(which was the advice on many other posts about this same issue). In the console it shows growLoop undefined, but it's defined in line 95, right?
Also, the time interval seems to decrease with every new circle created, and they grow faster. How could the value of setInterval change?
//set up canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var circles = [];
//create circle
function create(location) {
circles.push({
x: location.x,
y: location.y,
radius: 10,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
});
}
//figure out mouse position
var rect = document.getElementById("canvas").getBoundingClientRect();
// Get canvas offset on page
var offset = {
x: rect.left,
y: rect.top
};
function isOnCanvas(a) {
if ((a.x >= 0 && a.x <= rect.width) && (a.y >= 0 && a.y <= rect.height)) {
return true;
}
return false;
}
function isOnCircle(a) {
var i = 0,
l = circles.length,
x, y, d, c;
for (; i < l; ++i) {
c = circles[i];
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
// draw all circles
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI);
ctx.fillStyle = p.color;
ctx.fill();
}
}
//make last drawn circle 1px bigger
function grow(){
var a = circles[circles.length-1];
a.radius += 1;
}
//find percentage of canvas filled in
var totalSpace = canvas.width * canvas.height;
var totalFilled = function () {
total = 0;
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
total += Math.PI * Math.pow(p.radius, 2);
}
return total;
console.log(total);
}
function findPercentage() {
return (totalFilled() / totalSpace) * 100;
}
function updateInfo() {
percentage = findPercentage();
document.getElementById("percentage").innerHTML = "You've filled in " + percentage.toFixed(1) + "%";
}
//do all the stuff
var animate = function(){
draw();
grow();
updateInfo();}
var growLoop = 0;
window.onmousedown = function (e) {
// get event location on page offset by canvas location
var location = {
x: e.pageX - offset.x,
y: e.pageY - offset.y
};
if (isOnCanvas(location) && !isOnCircle(location)) {
create(location);
var growLoop = setInterval(animate, 100);
}
};
window.onmouseup = function (e) {
clearInterval(growLoop);
}
window.onmouseout = function (e) {
clearInterval(growLoop);
}
var growLoop = setInterval(animate, 100);
By adding var here you are declaring an internal variable also named growLoop and not assigning to the global. Remove var.
growLoop = setInterval(animate, 100);
http://jsfiddle.net/SeAGU/85/