Related
I am trying to add a colour gradient to the canvas text in this code but nothing I've tried works.
I am new to this so am having difficulties finding the solution.
Is this effect even possible? I have found the code to change the text colour using
ctx.fillStyle = gradient;
but it doesn't seem to be working. I'm not even sure where to insert it.
Does anyone know how to achieve this?
window.onresize = function() {
resize();
return true;
};
var pixels = []
var canv = $('canv')
var ctx = canv.getContext('2d')
var wordCanv = $('wordCanv')
var wordCtx = wordCanv.getContext('2d')
var mx = -1
var my = -1
var words = ''
var txt = []
var cw = 0
var ch = 0
var resolution = 1
var n = 0
var timerRunning = false
var resHalfFloor = 0
var resHalfCeil = 0
var width = 600
var height = 600
function canv_mousemove(evt) {
mx = evt.clientX - canv.offsetLeft
my = evt.clientY - canv.offsetTop
}
function Pixel(homeX, homeY) {
this.homeX = homeX
this.homeY = homeY
this.x = Math.random() * cw
this.y = Math.random() * ch
this.xVelocity = Math.random() * 10 - 5
this.yVelocity = Math.random() * 10 - 5
}
Pixel.prototype.move = function() {
var homeDX = this.homeX - this.x
var homeDY = this.homeY - this.y
var homeDistance = Math.sqrt(Math.pow(homeDX, 2) + Math.pow(homeDY, 2))
var homeForce = homeDistance * 0.01
var homeAngle = Math.atan2(homeDY, homeDX)
var cursorForce = 0
var cursorAngle = 0
if (mx >= 0) {
var cursorDX = this.x - mx
var cursorDY = this.y - my
var cursorDistanceSquared = Math.pow(cursorDX, 2) + Math.pow(cursorDY, 2)
cursorForce = Math.min(10000 / cursorDistanceSquared, 10000)
cursorAngle = Math.atan2(cursorDY, cursorDX)
} else {
cursorForce = 0
cursorAngle = 0
}
this.xVelocity +=
homeForce * Math.cos(homeAngle) + cursorForce * Math.cos(cursorAngle)
this.yVelocity +=
homeForce * Math.sin(homeAngle) + cursorForce * Math.sin(cursorAngle)
this.xVelocity *= 0.92
this.yVelocity *= 0.92
this.x += this.xVelocity
this.y += this.yVelocity
}
function $(id) {
return document.getElementById(id)
}
function timer() {
if (!timerRunning) {
timerRunning = true
setTimeout(timer, 33)
for (var i = 0; i < pixels.length; i++) {
pixels[i].move()
}
drawPixels()
n++
if (
n % 10 == 0 &&
(cw != width || ch != height)
)
body_resize()
timerRunning = false
} else {
setTimeout(timer, 5)
}
}
function drawPixels() {
var imageData = ctx.createImageData(cw, ch)
var actualData = imageData.data
var index
var goodX
var goodY
var realX
var realY
for (var i = 0; i < pixels.length; i++) {
goodX = Math.floor(pixels[i].x)
goodY = Math.floor(pixels[i].y)
for (
realX = goodX - resHalfFloor; realX <= goodX + resHalfCeil && realX >= 0 && realX < cw; realX++
) {
for (
realY = goodY - resHalfFloor; realY <= goodY + resHalfCeil && realY >= 0 && realY < ch; realY++
) {
index = (realY * imageData.width + realX) * 4
actualData[index + 3] = 255
}
}
}
imageData.data = actualData
ctx.putImageData(imageData, 0, 0)
}
function readWords() {
var randomWords = ['Text goes here', 'Text 2 goe ']
words = item = randomWords[Math.floor(Math.random() * randomWords.length)]
txt = words.split('\n')
}
function init() {
readWords()
var fontSize = 100
var wordWidth = 0
do {
wordWidth = 0
fontSize -= 5
wordCtx.font = fontSize + 'px sans-serif'
for (var i = 0; i < txt.length; i++) {
var w = wordCtx.measureText(txt[i]).width
if (w > wordWidth) wordWidth = w
}
} while (wordWidth > cw - 50 || fontSize * txt.length > ch - 50)
wordCtx.clearRect(0, 0, cw, ch)
wordCtx.textAlign = 'center'
wordCtx.textBaseline = 'middle'
for (var i = 0; i < txt.length; i++) {
wordCtx.fillText(
txt[i],
cw / 2,
ch / 2 - fontSize * (txt.length / 2 - (i + 0.5))
)
}
var index = 0
var imageData = wordCtx.getImageData(0, 0, cw, ch)
for (var x = 0; x < imageData.width; x += resolution) {
for (var y = 0; y < imageData.height; y += resolution) {
i = (y * imageData.width + x) * 4
if (imageData.data[i + 3] > 128) {
if (index >= pixels.length) {
pixels[index] = new Pixel(x, y)
} else {
pixels[index].homeX = x
pixels[index].homeY = y
}
index++
}
}
}
pixels.splice(index, pixels.length - index)
}
function body_resize() {
cw = width
ch = height
canv.width = cw
canv.height = ch
wordCanv.width = cw
wordCanv.height = ch
init()
}
resHalfFloor = Math.floor(resolution / 2)
resHalfCeil = Math.ceil(resolution / 2)
body_resize()
timer()
setInterval(init, 3000)
<canvas id="wordCanv" width="100%" height="600px" style="border:10px solid rgb(255,255,255);display:none;">
</canvas>
<canvas id="canv" onmousemove="canv_mousemove(event);" onmouseout="mx=-1;my=-1;" width="100%" height="600px">
</canvas>
It is not clear what kind of gradient effect you want to achieve...
So I guess that any will get you moving, see code below
I just added this:
actualData[index + 1] = realX % 255
in your function drawPixels
Play with those values until you get the desired combination
I'm assuming you are familiar with ImageData if not read more here:
https://developer.mozilla.org/en-US/docs/Web/API/ImageData
https://developer.mozilla.org/en-US/docs/Web/API/ImageData/data
var pixels = []
var canv = $('canv')
var ctx = canv.getContext('2d')
var wordCanv = $('wordCanv')
var wordCtx = wordCanv.getContext('2d')
var mx = -1
var my = -1
var words = ''
var txt = []
var cw = 0
var ch = 0
var resolution = 1
var n = 0
var timerRunning = false
var resHalfFloor = 0
var resHalfCeil = 0
var width = 560
var height = 200
function canv_mousemove(evt) {
mx = evt.clientX - canv.offsetLeft
my = evt.clientY - canv.offsetTop
}
function Pixel(homeX, homeY) {
this.homeX = homeX
this.homeY = homeY
this.x = Math.random() * cw
this.y = Math.random() * ch
this.xVelocity = Math.random() * 10 - 5
this.yVelocity = Math.random() * 10 - 5
}
Pixel.prototype.move = function() {
var homeDX = this.homeX - this.x
var homeDY = this.homeY - this.y
var homeDistance = Math.sqrt(Math.pow(homeDX, 2) + Math.pow(homeDY, 2))
var homeForce = homeDistance * 0.01
var homeAngle = Math.atan2(homeDY, homeDX)
var cursorForce = 0
var cursorAngle = 0
if (mx >= 0) {
var cursorDX = this.x - mx
var cursorDY = this.y - my
var cursorDistanceSquared = Math.pow(cursorDX, 2) + Math.pow(cursorDY, 2)
cursorForce = Math.min(10000 / cursorDistanceSquared, 10000)
cursorAngle = Math.atan2(cursorDY, cursorDX)
} else {
cursorForce = 0
cursorAngle = 0
}
this.xVelocity +=
homeForce * Math.cos(homeAngle) + cursorForce * Math.cos(cursorAngle)
this.yVelocity +=
homeForce * Math.sin(homeAngle) + cursorForce * Math.sin(cursorAngle)
this.xVelocity *= 0.92
this.yVelocity *= 0.92
this.x += this.xVelocity
this.y += this.yVelocity
}
function $(id) {
return document.getElementById(id)
}
function timer() {
if (!timerRunning) {
timerRunning = true
setTimeout(timer, 33)
for (var i = 0; i < pixels.length; i++) {
pixels[i].move()
}
drawPixels()
n++
if (
n % 10 == 0 &&
(cw != width || ch != height)
)
body_resize()
timerRunning = false
} else {
setTimeout(timer, 5)
}
}
function drawPixels() {
var imageData = ctx.createImageData(cw, ch)
var actualData = imageData.data
var index
var goodX
var goodY
var realX
var realY
for (var i = 0; i < pixels.length; i++) {
goodX = Math.floor(pixels[i].x)
goodY = Math.floor(pixels[i].y)
for (
realX = goodX - resHalfFloor; realX <= goodX + resHalfCeil && realX >= 0 && realX < cw; realX++
) {
for (
realY = goodY - resHalfFloor; realY <= goodY + resHalfCeil && realY >= 0 && realY < ch; realY++
) {
index = (realY * imageData.width + realX) * 4
actualData[index + 1] = realX % 255
actualData[index + 3] = 255
}
}
}
imageData.data = actualData
ctx.putImageData(imageData, 0, 0)
}
function readWords() {
var randomWords = ['Text 4 foo ', 'Text 2 goe ']
words = item = randomWords[Math.floor(Math.random() * randomWords.length)]
txt = words.split('\n')
}
function init() {
readWords()
var fontSize = 80
var wordWidth = 0
wordCtx.font = fontSize + 'px sans-serif'
do {
wordWidth = 0
fontSize -= 10
for (var i = 0; i < txt.length; i++) {
var w = wordCtx.measureText(txt[i]).width
if (w > wordWidth) wordWidth = w
}
} while (wordWidth > cw - 50 || fontSize * txt.length > ch - 50)
wordCtx.clearRect(0, 0, cw, ch)
wordCtx.textAlign = 'center'
wordCtx.textBaseline = 'middle'
for (var i = 0; i < txt.length; i++) {
wordCtx.fillText(
txt[i],
cw / 2,
ch / 2 - fontSize * (txt.length / 2 - (i + 0.5))
)
}
var index = 0
var imageData = wordCtx.getImageData(0, 0, cw, ch)
for (var x = 0; x < imageData.width; x += resolution) {
for (var y = 0; y < imageData.height; y += resolution) {
i = (y * imageData.width + x) * 4
if (imageData.data[i + 3] > 128) {
if (index >= pixels.length) {
pixels[index] = new Pixel(x, y)
} else {
pixels[index].homeX = x
pixels[index].homeY = y
}
index++
}
}
}
pixels.splice(index, pixels.length - index)
}
function body_resize() {
cw = width
ch = height
canv.width = cw
canv.height = ch
wordCanv.width = cw
wordCanv.height = ch
init()
}
resHalfFloor = Math.floor(resolution / 2)
resHalfCeil = Math.ceil(resolution / 2)
body_resize()
timer()
setInterval(init, 3000)
<canvas id="wordCanv" width="100%" height="200px" style="display:none;">
</canvas>
<canvas id="canv" width="100%" height="200px">
</canvas>
I'm working on a simple DHTML application, nothing strange: I have around 500 balls colliding with each other with different speeds and by clicking a button they stack based on their velocities creating a Maxwell-Boltzmann distribution(but that's another talk).
Well, for switching from the first to the second case I'm changing every single x and y coordinate for every single ball, to move them and pile them in that way.
Now, my question is: is it possible to have a sort of an animation in which balls from the first chaotic case, instead of jumping into the chart-configuration in one frame(as soon as the button gets clicked), gradually stack on top of each other in a much fancier and "graphical" animation? For example with transitions or transformations, but I couldn't manage to find a way to do that... I'm quite new to programming.
By the way, here's the full code:
FULL CODE:
class Ball {
constructor(x, y, dx, dy, radius, color){
this.radius = radius;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
// mass is that of a sphere as opposed to circle
// it *does* make a difference in how realistic it looks
this.mass = this.radius * this.radius * this.radius;
this.color = color;
};
draw() {
ctx.beginPath();
ctx.arc(Math.round(this.x), Math.round(this.y), this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
// ctx.strokeStyle = this.color;
ctx.stroke();
ctx.closePath();
};
speed() {
// magnitude of velocity vector
return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
};
angle() {
// velocity's angle with the x axis
return Math.atan2(this.dy, this.dx);
};
onGround() {
return (this.y + this.radius >= canvas.height)
};
};
//FUNCTIONS
//will remove
function randomColor() {
let red = Math.floor(Math.random() * 3) * 127;
let green = Math.floor(Math.random() * 3) * 127;
let blue = Math.floor(Math.random() * 3) * 127;
// dim down the small balls
if (!bigBalls){
red *= 0.65
green *= 0.65
blue *= 0.65
}
let rc = "rgb(" + red + ", " + green + ", " + blue + ")";
return rc;
}
function randomX() {
let x = Math.floor(Math.random() * canvas.width);
if (x < 30) {
x = 30;
} else if (x + 30 > canvas.width) {
x = canvas.width - 30;
}
return x;
}
function randomY() {
let y = Math.floor(Math.random() * canvas.height);
if (y < 30) {
y = 30;
} else if (y + 30 > canvas.height) {
y = canvas.height - 30;
}
return y;
}
//will remove
function randomRadius() {
if (bigBalls) {
let r = Math.ceil(Math.random() * 10 + 20);
return r;
} else {
let r = Math.ceil(Math.random() * 2 + 2);
//let r = 5;
return r;
}
}
//will remove
function randomDx() {
let r = Math.floor(Math.random() * 10 - 4);
return r;
}
//will remove
function randomDy() {
let r = Math.floor(Math.random() * 10 - 3);
return r;
}
function distanceNextFrame(a, b) {
return Math.sqrt((a.x + a.dx - b.x - b.dx)**2 + (a.y + a.dy - b.y - b.dy)**2) - a.radius - b.radius;
}
function distance(a, b) {
return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2);
}
let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
let objArray = [];
let probArray = [];
let paused = false;
let bumped = false;
let leftHeld = false;
let upHeld = false;
let rightHeld = false;
let downHeld = false;
let arrowControlSpeed = .25;
let gravityOn = false;
let clearCanv = true;
let bigBalls = false;
let lastTime = (new Date()).getTime();
let currentTime = 0;
let dt = 0;
let numStartingSmallBalls = 500;
let numStartingBigBalls = 0;
document.addEventListener("keydown", keyDownHandler);
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function keyDownHandler(event) {
if (event.keyCode == 80) { // p
paused = !paused;
} else if (event.keyCode == 82) { // r
objArray = [];
} else if (event.keyCode == 75) { // k
clearCanv = !clearCanv;
} else if (event.keyCode == 88) { // x
bigBalls = !bigBalls;
}
}
function canvasBackground() {
canvas.style.backgroundColor = "rgb(215, 235, 240)";
}
function wallCollision(ball) {
if (ball.x - ball.radius + ball.dx < 0 ||
ball.x + ball.radius + ball.dx > canvas.width) {
ball.dx *= -1;
}
if (ball.y - ball.radius + ball.dy < 0 ||
ball.y + ball.radius + ball.dy > canvas.height) {
ball.dy *= -1;
}
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius;
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
}
if (ball.x + ball.radius > canvas.width) {
ball.x = canvas.width - ball.radius;
}
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
}
}
function ballCollision() {
for (let i=0; i<objArray.length-1; i++) {
for (let j=i+1; j<objArray.length; j++) {
let ob1 = objArray[i]
let ob2 = objArray[j]
let dist = distance(ob1, ob2)
if (dist < ob1.radius + ob2.radius) {
let theta1 = ob1.angle();
let theta2 = ob2.angle();
let phi = Math.atan2(ob2.y - ob1.y, ob2.x - ob1.x);
let m1 = ob1.mass;
let m2 = ob2.mass;
let v1 = ob1.speed();
let v2 = ob2.speed();
let dx1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.cos(phi) + v1*Math.sin(theta1-phi) * Math.cos(phi+Math.PI/2);
let dy1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.sin(phi) + v1*Math.sin(theta1-phi) * Math.sin(phi+Math.PI/2);
let dx2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.cos(phi) + v2*Math.sin(theta2-phi) * Math.cos(phi+Math.PI/2);
let dy2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.sin(phi) + v2*Math.sin(theta2-phi) * Math.sin(phi+Math.PI/2);
ob1.dx = dx1F;
ob1.dy = dy1F;
ob2.dx = dx2F;
ob2.dy = dy2F;
/* if(ob1.speed() * 160 < 400)
ob1.color = 'lightblue';
else if(ob1.speed() * 160 > 800)
ob1.color = 'red';
else
ob1.color = 'orange';
if(ob2.speed() * 160 < 400)
ob2.color = 'lightblue';
else if(ob2.speed() * 160 > 800)
ob2.color = 'red';
else
ob2.color = 'orange';*/
staticCollision(ob1, ob2);
}
}
wallCollision(objArray[i]);
}
if (objArray.length > 0)
wallCollision(objArray[objArray.length - 1])
}
function staticCollision(ob1, ob2, emergency = false)
{
let overlap = ob1.radius + ob2.radius - distance(ob1, ob2);
let smallerObject = ob1.radius < ob2.radius ? ob1 : ob2;
let biggerObject = ob1.radius > ob2.radius ? ob1 : ob2;
// When things go normally, this line does not execute.
// "Emergency" is when staticCollision has run, but the collision
// still hasn't been resolved. Which implies that one of the objects
// is likely being jammed against a corner, so we must now move the OTHER one instead.
// in other words: this line basically swaps the "little guy" role, because
// the actual little guy can't be moved away due to being blocked by the wall.
if (emergency) [smallerObject, biggerObject] = [biggerObject, smallerObject]
let theta = Math.atan2((biggerObject.y - smallerObject.y), (biggerObject.x - smallerObject.x));
smallerObject.x -= overlap * Math.cos(theta);
smallerObject.y -= overlap * Math.sin(theta);
if (distance(ob1, ob2) < ob1.radius + ob2.radius) {
// we don't want to be stuck in an infinite emergency.
// so if we have already run one emergency round; just ignore the problem.
if (!emergency) staticCollision(ob1, ob2, true)
}
}
function moveObjects() {
for (let i=0; i<objArray.length; i++) {
let ob = objArray[i];
ob.x += ob.dx * 1;
ob.y += ob.dy * 1;
}
}
function drawObjects() {
for (let obj in objArray) {
objArray[obj].draw();
}
}
let begin = true;
let temperature;
document.getElementById("temp").oninput = function()
{
temperature = parseInt(document.getElementById("temp").value);
generateBalls(temperature);
}
function drawChart()
{
let index = 0
let cx = 10 , cy;
for(let i = 0; i < 59; i++) {
cy = canvas.height - 6;
if(probArray[i] != 0) {
n = 0;
while(n < probArray[i]) {
objArray[index + n].x = cx;
objArray[index + n].y = cy;
cy -= 12;
n++;
}
index += n;
}
cx += 20;
}
chart = !chart;
}
function draw() {
/*currentTime = (new Date()).getTime();
dt = (currentTime - lastTime) / 1000; // delta time in seconds
// dirty and lazy solution
// instead of scaling down every velocity vector
// we decrease the speed of time
dt *= 20;*/
if(begin) {
generateBalls(300);
begin = false;
}
//work in progress
if(chart) {
drawChart();
}
if (clearCanv) clearCanvas();
canvasBackground();
if (!paused) {
moveObjects();
ballCollision();
}
drawObjects();
lastTime = currentTime;
window.requestAnimationFrame(draw);
}
//work in progress
function setColor(vel)
{
let red = 255, green = 255, blue = 255;
let rc;
green /= (vel * 0.001);
blue /= (vel * 0.01);
return rc = "rgb(" + red + ", " + green + ", " + blue + ")";
}
let N = 550;
let m = 2.66e-26;
let T = 5;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls;
let angolox;
let vel;
let color;
function generateBalls(T)
{
paused = false;
v = 50;
objArray = [];
for(let i = 0; i < 59; i++)
{ //each 50m/s, with dv = 50, until 2000m/s
//molecules number between v and v+50
probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);
v += 50;
}
v = 50;
let l;
for(let i = 0; i < 59; i++)
{
let n = 0;
balls = 0;
while(n < probArray[i])
{
angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v) / 160;
if(vel * 160 < 400)
color = 'lightblue';
else if(vel * 160 > 800)
color = 'red';
else
color = 'orange';
l = objArray.length;
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 5, color);
balls++;
n++;
}
v += 50;
}
}
let chart = false
function drawChart_bool() {
chart = !chart;
paused = !paused;
}
draw();
body {
background-color: khaki;
text-align: center;
font-family: Ubuntu Mono;
}
#title {
color: black;
font-size: 200%;
font-style: normal;
margin: 1px;
border: 1px;
}
#balls {
margin-top: 5px;
}
#myCanvas {
margin-top: -20px;
}
section.footer {
color: black;
font-family: Ubuntu Mono;
font-style: normal;
font-size: small;
}
#disclaimer {
font-size: 74%;
color: gray;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2d collision</title>
<link rel="stylesheet" type="text/css" href="gas.css">
</head>
<body style="text-align: center">
<canvas onload="generateBalls()" id="myCanvas" width="1225%" height="500" style="border:1px solid black; margin-top: 10px;"></canvas>
<p>
<input type="range" min="50" max="2050" value="300" step="100" id="temp">
<input type="button" onclick="drawChart_bool()">
<strong>[K]</strong> to toggle clearCanvas(); <br>
<strong>[P]</strong>: pause/unpause || <strong>[R]</strong>: [RESET]
</p>
<div id="disclaimer" align="center" style="word-break: break-word; width: 350px; display:inline-block;">
<p>
Make sure to press a few buttons and play around.<br>Made with pure javascript.
</p>
</div>
</section>
</body>
<script src="gas.js"></script>
</html>
FUNCTION I USE TO DRAW CHART(objArray is an ascending ordered one, based on speeds)
function drawChart()
{
let index = 0
let cx = 10 , cy;
for(let i = 0; i < 59; i++) {
cy = canvas.height - 6;
if(probArray[i] != 0) {
n = 0;
while(n < probArray[i]) {
objArray[index + n].x = cx;
objArray[index + n].y = cy;
cy -= 12;
n++;
}
index += n;
}
cx += 20;
}
chart = !chart;
}
FUNCTION I USE TO GENERATE BALLS:
function generateBalls(T)
{
paused = false;
v = 50;
objArray = [];
for(let i = 0; i < 59; i++)
{ //each 50m/s, with dv = 50, until 2000m/s
//molecules number between v and v+50
probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);
v += 50;
}
v = 50;
let l;
for(let i = 0; i < 59; i++)
{
let n = 0;
balls = 0;
while(n < probArray[i])
{
angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v) / 160;
if(vel * 160 < 400)
color = 'lightblue';
else if(vel * 160 > 800)
color = 'red';
else
color = 'orange';
l = objArray.length;
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 5, color);
balls++;
n++;
}
v += 50;
}
}
Heartfelt thanks in advance for any answer,
Greg.🙏
My raycaster's sprites keep blinking, and I think it has to do with ZBuffer.
Using this for reference: https://lodev.org/cgtutor/index.html
I have done plenty of research and can't find any JS raycasters that answer my problem. By sprites, I mean billboarded images, and when you move they blink like an atari game that is being pushed to its limits.
Here is the project on JSFiddle: https://jsfiddle.net/Vakore/bsvx1m26/
Here is where I am using ZBuffer:
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX =
floor((256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth) / spriteWidth) / 256;
// the conditions in the if are:
// 1) it's in front of camera plane so you don't see things behind you
// 2) it's on the screen (left)
// 3) it's on the screen (right)
// 4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
can.drawImage(
document.getElementById(sprite[i][2]),
texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY
);
}
}
ZBuffer was declared and assigned an array value earlier in the code.
var keys = {
"a": false,
"s": false,
"d": false,
"A": false,
"S": false,
"D": false,
"W": false,
"Z": false,
"X": false,
"C": false,
"ENTER": false
};
document.addEventListener("keydown", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = true;
if (e.keyCode === 37)
keys["a"] = true;
if (e.keyCode === 39)
keys["d"] = true;
if (e.keyCode === 65)
keys["A"] = true;
if (e.keyCode === 68)
keys["D"] = true;
if (e.keyCode === 83)
keys["S"] = true;
if (e.keyCode === 87)
keys["W"] = true;
if (e.keyCode === 40)
keys["S"] = true;
if (e.keyCode === 38)
keys["W"] = true;
if (e.keyCode === 67)
keys["C"] = true;
if (e.keyCode === 88)
keys["X"] = true;
if (e.keyCode === 90)
keys["Z"] = true;
if (e.keyCode === 13)
keys["ENTER"] = true;
})
document.addEventListener("keyup", function(e) {
e.preventDefault();
if (e.keyCode === 16)
keys["SHIFT"] = false;
if (e.keyCode === 37)
keys["a"] = false;
if (e.keyCode === 39)
keys["d"] = false;
if (e.keyCode === 65)
keys["A"] = false;
if (e.keyCode === 68)
keys["D"] = false;
if (e.keyCode === 83)
keys["S"] = false;
if (e.keyCode === 87)
keys["W"] = false;
if (e.keyCode === 40)
keys["S"] = false;
if (e.keyCode === 38)
keys["W"] = false;
if (e.keyCode === 67)
keys["C"] = false;
if (e.keyCode === 88)
keys["X"] = false;
if (e.keyCode === 90)
keys["Z"] = false;
})
/*
Basic functions(ease of access)
*/
var canvas = document.getElementById("canva");
var can = canvas.getContext("2d");
canvas.requestPointerLock = canvas.requestPointerLock || canvas.mozRequestPointerLock;
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock;
canvas.onclick = function() {
canvas.requestPointerLock();
clicked = true;
};
var mouseX = 0;
var mouseMove = function(e) {
mouseX += e.movementX;
};
var rect = function(x, y, w, h) {
can.fillRect(x, y, w, h);
};
var arc = function(x, y, r, start, stop) {
can.beginPath();
can.arc(x, y, r, start, stop);
can.fill();
};
var circle = function(x, y, r) {
arc(x, y, r, 0, 360);
};
var fill = function(r, g, b, a) {
if (a === undefined) {
a = 1;
}
can.fillStyle = "rgb(" + r + "," + g + "," + b + "," + a + ")";
};
var font = function(siz) {
can.font = siz;
};
var textAlign = function(ali) {
can.textAlign = ali;
};
var text = function(txt, x, y, w, h) {
can.fillText(txt, x, y, w, h);
};
var quad = function(x1, y1, x2, y2, x3, y3, x4, y4) {
can.beginPath();
can.moveTo(x1, y1);
can.lineTo(x2, y2);
can.lineTo(x3, y3);
can.lineTo(x4, y4);
can.closePath();
can.fill();
};
var random = function(min, max) {
return round(Math.random() * (max - min)) + min;
};
var translate = function(x, y) {
can.save();
can.translate(x, y);
};
var scale = function(w, h) {
can.scale(w, h);
}
var trestore = function() {
can.restore();
};
var floor = function(num) {
return Math.floor(num);
};
var sqrt = function(num) {
return Math.sqrt(num);
};
var abs = function(num) {
return Math.abs(num);
};
var color = function(r, g, b, a) {
return [r, g, b, a];
};
var dist = function() {
return true;
};
var cos = function(num) {
return Math.cos(num);
}
var sin = function(num) {
return Math.sin(num);
}
var dist = function(x1, y1, x2, y2) {
dx = x1 - x2;
dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
};
//Begin the raycaster
var scaler = 3; //This reduces the resolution so that it can display large rooms without lagging
var w = 640 / scaler;
var h = 480 / scaler;
var texWidth = 64;
var texHeight = 64;
var rotSpeed = 0.1;
var moveSpeed = 0.1;
var worldMap = [
"1111111111111111111111111111111111111111",
"1001000001234567800000000000000000000001",
"1001000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1001000000000000000000000000000000000001",
"1001000000000000000000000000222200000001",
"1001000000000000000000000000200200000001",
"1111000000000000000000000000200200000001",
"1000000000000000000000000033300333000001",
"8008000000000000000000000000000000000001",
"8000800000000000000000000033300333000001",
"8000080000000000000000000000300300000001",
"8000008000000000000000000000333300000001",
"8888888000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1666006660000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1000000060000000000000000000000000000001",
"1666666660000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000040444444444444400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000040000000000000400000000000000001",
"1000000044444444444444400000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000007775777000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000005000005000000000000000001",
"1000000000000007000007000000000000000001",
"1000000000000000000007000000000000000001",
"1111111111111117775777111111111111111111",
];
try {
var ZBuffer = [];
var sprite = [
[5, 5, "barrel", 0], //Y and X are reversed
[6.5, 2.5, "pillar", 1],
[6.5, 1.5, "pillar", 1.5],
[8, 6, "greenlight", 2],
];
var posX = 2,
posY = 2,
dirX = -1,
dirY = 0,
planeX = 0,
planeY = 0.66; //The 2d raycaster version of the camera plane
} catch (e) {
alert(e);
}
var castRays = function() {
for (var x = 0; x < w; x++) {
var cameraX = 2 * x / (w) - 1; //x-coordinate in camera space
var rayDirX = dirX + planeX * cameraX;
var rayDirY = dirY + planeY * cameraX;
//which box of the map we're in
var mapX = floor(posX);
var mapY = floor(posY);
//length of ray from current position to next x or y-side
var sideDistX;
var sideDistY;
//length of ray from one x or y-side to next x or y-side
var deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
var deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
var perpWallDist = 0;
//what direction to step in x or y-direction (either +1 or -1)
var stepX = 0;
var stepY = 0;
var hit = 0; //was there a wall hit?
var side = 0; //was a NS or a EW wall hit?
//calculate step and initial sideDist
if (rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
while (hit == 0) {
//jump to next map square, OR in x-direction, OR in y-direction
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
//Check if ray has hit a wall
if (worldMap[mapX][mapY] == 0) {}
if (worldMap[mapX][mapY] != 0) {
hit = 1;
}
} //end of while loop
//Calculate distance of perpendicular ray (Euclidean distance will give fisheye effect!)
if (side == 0) {
perpWallDist = (mapX - posX + (1 - stepX) / 2) / rayDirX;
} else {
perpWallDist = (mapY - posY + (1 - stepY) / 2) / rayDirY;
}
//Calculate height of line to draw on screen
var lineHeight = abs(floor(h / perpWallDist));
//calculate lowest and highest pixel to fill in current stripe
var drawStart = -lineHeight / 2 + h / 2;
//if(drawStart < 0) {drawStart = 0;}
var drawEnd = lineHeight / 2 + h / 2;
//if(drawEnd >= h) {drawEnd = h - 1;}
//texturing calculations
//var texNum = worldMap[mapX][mapY] - 1; //1 subtracted from it so that texture 0 can be used!
//calculate value of wallX
var wallX = 0; //where exactly the wall was hit
if (side == 0) {
wallX = posY + perpWallDist * rayDirY;
} else {
wallX = posX + perpWallDist * rayDirX;
}
wallX -= floor((wallX));
//x coordinate on the texture
var texX = floor(wallX * (texWidth));
if (side == 0 && rayDirX > 0) {
texX = texWidth - texX - 1;
}
if (side == 1 && rayDirY < 0) {
texX = texWidth - texX - 1;
}
//Untextured variant
/*var currentColor = color(255, 255, 255);
if (worldMap[mapX][mapY] == "1") {currentColor = color(150, 150, 150);}
if (worldMap[mapX][mapY] == "2") {currentColor = color(255, 255, 255);}
if (worldMap[mapX][mapY] == "3") {currentColor = color(255,0,0);}
if (worldMap[mapX][mapY] == "4") {currentColor = color(0,0,255);}
if (worldMap[mapX][mapY] == "5") {currentColor = color(0,255,0);}
if (worldMap[mapX][mapY] == "6") {currentColor = color(255,0,255);}
if (worldMap[mapX][mapY]=="7") {currentColor = color(0,255,255);}
if (side === 1) {currentColor = [currentColor[0] - 40, currentColor[1] - 40, currentColor[2] - 40];}
//if (dist(mapX, mapY, px, pz) > 10) {continue;}
fill(currentColor[0],currentColor[1],currentColor[2]);
rect(x, drawStart, 1, drawEnd - drawStart);*/
var currentImg = "bluestone";
if (worldMap[mapX][mapY] == "1") {
currentImg = "bluestone";
}
if (worldMap[mapX][mapY] == "2") {
currentImg = "greystone";
}
if (worldMap[mapX][mapY] == "3") {
currentImg = "wood";
}
if (worldMap[mapX][mapY] == "4") {
currentImg = "colorstone";
}
if (worldMap[mapX][mapY] == "5") {
currentImg = "eagle";
}
if (worldMap[mapX][mapY] == "6") {
currentImg = "mossy";
}
if (worldMap[mapX][mapY] == "7") {
currentImg = "redbrick";
}
if (worldMap[mapX][mapY] == "8") {
currentImg = "purplestone";
}
//if (worldMap[mapX][mapY] =="a") {currentImg = "barrel";}
//if (dist(mapX, mapY, posX, posY) > 20) {continue;}
can.drawImage(document.getElementById(currentImg), texX + ((worldMap[mapX][mapY] - 1) * 64), 0, 1, texHeight, x, drawStart, 1, drawEnd - drawStart);
//fill(255, 0, 0);rect(x, drawStart, 1, drawEnd - drawStart);
if (side === 1) {
fill(0, 0, 0, 0.3);
rect(x, drawStart, 1, drawEnd - drawStart);
}
//Set the ZBuffer
ZBuffer[x] = perpWallDist;
//FLOOR CASTING(not using till figure out how to do without lag)
var floorXWall = 0,
floorYWall = 0;
if (side == 0 && rayDirX > 0) {
floorXWall = mapX;
floorYWall = mapY + wallX;
} else if (side == 0 && rayDirX < 0) {
floorXWall = mapX + 1.0;
floorYWall = mapY + wallX;
} else if (side == 1 && rayDirY > 0) {
floorXWall = mapX + wallX;
floorYWall = mapY;
} else {
floorXWall = mapX + wallX;
floorYWall = mapY + 1.0;
}
var distWall, distPlayer, currentDist;
distWall = perpWallDist;
distPlayer = 0.0;
for (var y = drawEnd + 1; y < h; y++) {
//if (dist(mapX, mapY, posX, posY) > 5) {continue;}
currentDist = h / (2.0 * y - h); //you could make a small lookup table for this instead
var weight = (currentDist - distPlayer) / (distWall - distPlayer);
var currentFloorX = weight * floorXWall + (1.0 - weight) * posX;
var currentFloorY = weight * floorYWall + (1.0 - weight) * posY;
var floorTexX, floorTexY;
floorTexX = floor(currentFloorX * texWidth) % texWidth;
floorTexY = floor(currentFloorY * texHeight) % texHeight;
//OPTIMIZE FOR LESS LAG!!!!!!!!!! (search
//FLOOR
//can.drawImage(document.getElementById("greystone"), floorTexX, floorTexY, texWidth, texHeight, x, y, texWidth, texHeight);
//CEILING
//can.drawImage(document.getElementById("wood"), floorTexX, floorTexY, texWidth, 1, x, h - y, texWidth, 1);
} //End of the 'y' loop
} //end the loop
//SPRITE CASTING
//Sort sprites
sprite.sort(function(a, b) {
return b[3] - a[3];
}); //Sort the depth of each sprite
//Draw sprites
for (var i = 0; i < sprite.length; i++) {
var spriteX = sprite[i][0] - posX;
var spriteY = sprite[i][1] - posY;
sprite[i][3] = abs((posX - sprite[i][0]) - (posY - sprite[i][1]));
var invDet = 1.0 / (planeX * dirY - dirX * planeY);
var transformX = invDet * (dirY * spriteX - dirX * spriteY);
var transformY = invDet * (-planeY * spriteX + planeX * spriteY); //this is actually the depth inside the screen, that what Z is in 3D(Thanks lodev.org for explanation!)
var spriteScreenX = floor((w / 2) * (1 + transformX / transformY));
//calculate height of the sprite on screen
var spriteHeight = abs(floor(h / (transformY))); //using "transformY" instead of the real distance prevents fisheye
//calculate lowest and highest pixel to fill in current stripe
var drawStartY = -spriteHeight / 2 + h / 2;
//if (drawStartY < 0) {drawStartY = 0;} Don't need THIS
var drawEndY = spriteHeight / 2 + h / 2;
//if (drawEndY >= h) {drawEndY = h - 1;} Or THIS
//calculate width of the sprite
var spriteWidth = abs(floor(h / (transformY)));
var drawStartX = -spriteWidth / 2 + spriteScreenX;
//if (drawStartX < 0) {drawStartX = 0;} Nor this
var drawEndX = spriteWidth / 2 + spriteScreenX;
//if (drawEndX >= w) {drawEndX = w - 1;} Nor dis
//loop through every vertical stripe of the sprite on screen
for (var stripe = drawStartX; stripe < drawEndX; stripe++) {
var texX = floor(256 * (stripe - (-spriteWidth / 2 + spriteScreenX)) * texWidth / spriteWidth) / 256;
//the conditions in the if are:
//1) it's in front of camera plane so you don't see things behind you
//2) it's on the screen (left)
//3) it's on the screen (right)
//4) ZBuffer, with perpendicular distance
var ruffer = 0;
if (sprite[i][2] == "barrel") {
ruffer = 576 - 64;
}
if (sprite[i][2] == "pillar") {
ruffer = 576;
}
if (sprite[i][2] == "greenlight") {
ruffer = 576 + 64;
}
if (transformY > 0 && stripe > 0 && stripe < w && transformY < ZBuffer[stripe]) {
can.drawImage(document.getElementById(sprite[i][2]), texX + ruffer, 0, 1, texHeight, stripe, drawStartY, 1, drawEndY - drawStartY);
}
}
} //End of 'i' loop
//Bottom of dat
if (keys["W"]) {
if (worldMap[floor(posX + dirX * moveSpeed)][floor(posY)] == false) {
posX += dirX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + dirY * moveSpeed)] == false) {
posY += dirY * moveSpeed
};
}
//move backwards if no wall behind you
if (keys["S"]) {
if (worldMap[floor(posX - dirX * moveSpeed)][floor(posY)] == false) {
posX -= dirX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - dirY * moveSpeed)] == false) {
posY -= dirY * moveSpeed;
}
}
if (keys["D"]) {
if (worldMap[floor(posX + planeX * moveSpeed)][floor(posY)] == false) {
posX += planeX * moveSpeed
};
if (worldMap[floor(posX)][floor(posY + planeY * moveSpeed)] == false) {
posY += planeY * moveSpeed
};
}
if (keys["A"]) {
if (worldMap[floor(posX - planeX * moveSpeed)][floor(posY)] == false) {
posX -= planeX * moveSpeed;
}
if (worldMap[floor(posX)][floor(posY - planeY * moveSpeed)] == false) {
posY -= planeY * moveSpeed;
}
}
//rotate to the right
mouseX = -mouseX / 75;
var oldDirX = dirX;
dirX = dirX * cos(mouseX) - dirY * sin(mouseX);
dirY = oldDirX * sin(mouseX) + dirY * cos(mouseX);
var oldPlaneX = planeX;
planeX = planeX * cos(mouseX) - planeY * sin(mouseX);
planeY = oldPlaneX * sin(mouseX) + planeY * cos(mouseX);
mouseX = 0;
if (keys["d"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed);
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed);
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed);
}
//rotate to the left
if (keys["a"]) {
//both camera direction and camera plane must be rotated
var oldDirX = dirX;
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed);
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed);
var oldPlaneX = planeX;
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed);
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed);
}
};
var run = function() {
try {
if (document.pointerLockElement === canvas || document.mozPointerLockElement === canvas) {
document.addEventListener("mousemove", mouseMove, false);
} else {
document.removeEventListener("mousemove", mouseMove, false);
} //Mouse stuff
translate(0, 0);
scale(scaler, scaler);
fill(40, 40, 40);
rect(0, 0, w, h / 2);
fill(80, 80, 80);
rect(0, h / 2, w, h / 2);
if (keys["C"]) {
rotSpeed = 0.04;
} else {
rotSpeed = 0.1;
}
/*if (keys["W"]) {fill(255, 0, 0);}
if (keys["A"]) {fill(0, 255, 0);}
if (keys["S"]) {fill(0, 0, 255);}
if (keys["D"]) {fill(255, 0, 255);}
if (keys["X"]) {fill(255, 255, 0);}
if (keys["Z"]) {fill(0, 255, 255);}*/
castRays();
trestore();
requestAnimationFrame(run);
} catch (e) {
alert(e);
}
};
run();
<html>
<title>Jailbreak</title>
<div class="center">
<canvas width="640" height="480" id="canva"></canvas>
<br>
<img id="barrel" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="pillar" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greenlight" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="bluestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="greystone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="wood" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="colorstone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="eagle" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="mossy" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="redbrick" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
<img id="purplestone" src="https://lodev.org/cgtutor/images/wolftexturesobj.gif">
</div>
</html>
UPDATE: I tried changing
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]//Pay attention to this line
) {
to:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY > ZBuffer[stripe]//Now look
) {
Which basically reverses the check to see if it is behind a wall. Surprisingly, the blinking continues.
The answer was pretty simple. It was because stripe is not an even value, and if you were to have an array like this: var thisArray = [1, 2, 3, 4]; and you were to try and grab var money = thisArray[1.5]; the value undefined would be returned.
So I changed this:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[stripe]
) {
To this:
if (
transformY > 0 &&
stripe > 0 &&
stripe < w &&
transformY < ZBuffer[Math.round(stripe)]
) {
Math.floor also works, since it also returns an integer value.
I am working on a college project that requires me to build a 2D game in javascript. A problem that I'm having at the moment is that it cannot read the 'addEventListener'. This error has caused my game to not work completely.
document.getElementById('restart').addEventListener('click', startGame);
Here is the full code that I have used. The error is down the very bottom.
(function()
{
//Define variables
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var player, score, stop, ticker;
var ground = [], water = [], enemies = [], environment = [];
//Platform variables
var platformHeight, platformLength, gapLength;
var platformWidth = 32;
var platformBase = canvas.height - platformWidth;
var platformSpacer = 64;
//Randomly generates a number
function random(low, high)
{
return Math.floor(Math.random() * (high - low + 1) + low);
}
//Bounds a number
function bound(num, low, high)
{
return Math.max(Math.min(num, high), low);
}
//Loads all of the assets
var assetLoader = (function()
{
this.imgs = {
'bg' : 'Images/bg.png',
'sky' : 'Images/sky.png',
'backdrop' : 'Images/backdrop.png',
'backdrop2' : 'Images/backdrop_ground.png',
'grass' : 'Images/grass.png',
'avatar_normal' : 'Images/normal_walk.png',
'water' : 'imgs/water.png',
'grass1' : 'imgs/grassMid1.png',
'grass2' : 'imgs/grassMid2.png',
'bridge' : 'imgs/bridge.png',
'plant' : 'imgs/plant.png',
'bush1' : 'imgs/bush1.png',
'bush2' : 'imgs/bush2.png',
'cliff' : 'imgs/grassCliffRight.png',
'spikes' : 'imgs/spikes.png',
'box' : 'imgs/boxCoin.png',
'slime' : 'imgs/slime.png'
};
var assetsLoaded = 0; //How many assets have been loaded
var numImgs = Object.keys(this.imgs).length; //Total number of image assets
this.totalAssest = numImgs; //Total number of assets
function assetLoaded(dic, name)
{
if(this[dic][name].status !== 'loading')
{
return;
}
this[dic][name].status = 'loaded';
assetsLoaded++;
if(assetsLoaded === this.totalAssest && typeof this.finished === 'function')
{
this.finished();
}
}
this.downloadAll = function()
{
var _this = this;
var src;
for (var img in this.imgs)
{
if (this.imgs.hasOwnProperty(img))
{
src = this.imgs[img];
(function(_this, img)
{
_this.imgs[img] = new Image();
_this.imgs[img].status = 'loading';
_this.imgs[img].name = img;
_this.imgs[img].onload = function() {assetLoaded.call(_this, 'imgs', img)};
_this.imgs[img].src = src;
})(_this, img);
}
}
}
return{
imgs: this.imgs,
totalAssest: this.totalAssest,
downloadAll: this.downloadAll
};
})();
assetLoader.finished = function()
{
startGame();
}
function SpriteSheet(path, frameWidth, frameHeight)
{
this.image = new Image();
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
var self = this;
this.image.onload = function()
{
self.framesPerRow = Math.floor(self.image.width / self.frameWidth);
};
this.image.src = path;
}
function Animation(spritesheet, frameSpeed, startFrame, endFrame)
{
var animationSequence = [];
var currentFrame = 0;
var counter = 0;
for (var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++)
{
animationSequence.push(frameNumber);
}
this.update = function()
{
if (counter == (frameSpeed - 1))
{
currentFrame = (currentFrame + 1) % animationSequence.length;
}
counter = (counter + 1) % frameSpeed;
};
this.draw = function(x, y)
{
var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
var col = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);
ctx.drawImage
(
spritesheet.image,
col * spritesheet.frameWidth, row * spritesheet.frameHeight,
spritesheet.frameWidth, spritesheet.frameHeight,
x, y,
spritesheet.frameWidth, spritesheet.frameHeight);
};
}
var background = (function()
{
var sky = {};
var backdrop = {};
var backdrop2 = {};
this.draw = function()
{
ctx.drawImage(assetLoader.imgs.bg, 0, 0);
sky.x -= sky.speed;
backdrop.x -= backdrop.speed;
backdrop2.x -= backdrop2.speed;
ctx.drawImage(assetLoader.imgs.sky, sky.x, sky.y);
ctx.drawImage(assetLoader.imgs.sky, sky.x + canvas.width, sky.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x + canvas.width, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x, backdrop2.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x + canvas.width, backdrop2.y);
if (sky.x + assetLoader.imgs.sky.width <= 0)
{
sky.x = 0;
}
if (backdrop.x + assetLoader.imgs.backdrop.width <= 0)
{
backdrop.x = 0;
}
if (backdrop2.x + assetLoader.imgs.backdrop2.width <= 0)
{
backdrop2.x = 0;
}
};
this.reset = function()
{
sky.x = 0;
sky.y = 0;
sky.speed = 0.2;
backdrop.x = 0;
backdrop.y = 0;
backdrop.speed = 0.4;
backdrop2.x = 0;
backdrop2.y = 0;
backdrop2.speed = 0.6;
}
return{
draw: this.draw,
reset: this.reset
};
})();
//A vector for 2D space
function Vector(x, y, dx, dy)
{
// position
this.x = x || 0;
this.y = y || 0;
// direction
this.dx = dx || 0;
this.dy = dy || 0;
}
//Advances the vector's position
Vector.prototype.advance = function()
{
this.x += this.dx;
this.y += this.dy;
};
//Gets the minimum distance between two vectors
Vector.prototype.minDist = function(vec)
{
var minDist = Infinity;
var max = Math.max(Math.abs(this.dx), Math.abs(this.dy),Math.abs(vec.dx), Math.abs(vec.dy));
var slice = 1 / max;
var x, y, distSquared;
// get the middle of each vector
var vec1 = {}, vec2 = {};
vec1.x = this.x + this.width/2;
vec1.y = this.y + this.height/2;
vec2.x = vec.x + vec.width/2;
vec2.y = vec.y + vec.height/2;
for(var percent = 0; percent < 1; percent += slice)
{
x = (vec1.x + this.dx * percent) - (vec2.x + vec.dx * percent);
y = (vec1.y + this.dy * percent) - (vec2.y + vec.dy * percent);
distSquared = x * x + y * y;
minDist = Math.min(minDist, distSquared);
}
return Math.sqrt(minDist);
};
//The player object
var player = (function(player)
{
//Player properties
player.width = 60;
player.height = 96;
player.speed = 6;
//Jumping
player.gravity = 1;
player.dy = 0;
player.jumpDy = -10;
player.isFalling = false;
player.isJumping = false;
//Spritesheets
player.sheet = new SpriteSheet('Images/normal_walk.png', player.width, player.height);
player.walkAnim = new Animation(player.sheet, 4, 0, 15);
player.jumpAnim = new Animation(player.sheet, 4, 15, 15);
player.fallAnim = new Animation(player.sheet, 4, 11, 11);
player.anim = player.walkAnim;
Vector.call(player, 0, 0, 0, player.dy);
var jumpCounter = 0;
player.update = function()
{
//Jump if not currently jumping or falling
if(KEY_STATUS.space && player.dy === 0 && !player.isJumping)
{
player.isJumping = true;
player.dy = player.jumpDy;
jumpCounter = 12;
}
//Jump higher if the spacebar is continually pressed
if(KEY_STATUS.space && jumpCounter)
{
player.dy = player.jumpDy;
}
jumpCounter = Math.max(jumpCounter - 1, 0);
this.advance();
//Gravity
if(player.isFalling || player.isJumping)
{
player.dy += player.gravity;
}
//Falling Animation
if(player.dy > 0)
{
player.anim = player.fallAnim;
}
// change animation is jumping
else if(player.dy < 0)
{
player.anim = player.jumpAnim;
}
else
{
player.anim = player.walkAnim;
}
player.anim.update();
};
//Draw the player's current position
player.draw = function()
{
player.anim.draw(player.x, player.y);
};
//Resets the player's position
player.reset = function()
{
player.x = 64;
player.y = 250;
};
return player;
})(Object.create(Vector.prototype));
//Sprites
function Sprite(x, y, type)
{
this.x = x;
this.y = y;
this.width = platformWidth;
this.height = platformWidth;
this.type = type;
Vector.call(this, x, y, 0, 0);
//Updating the sprites
this.update = function()
{
this.dx = -player.speed;
this.advancer();
}
//Drawing the sprites
this.draw = function()
{
ctx.save();
ctx.translate(0.5, 0.5);
ctx.drawImage(assetLoader.imgs[this.type], this.x, this.y);
ctx.restore();
}
}
Sprite.prototype = Object.create(Vector.prototype);
//Platforms
function getType()
{
var type;
switch(platformHeight)
{
case 0:
case 1:
type = Math.random() > 0.5 ? 'grass1' : 'grass2';
break;
case 2:
type = 'grass';
break;
case 3:
type = 'bridge';
break;
case 4:
type = 'box';
break;
}
if (platformLength === 1 && platformHeight < 3 && rand(0, 3) === 0)
{
type = 'cliff';
}
return type;
}
//Update and draw all ground positions
function updateGround()
{
//Animate ground
player.isFalling = true;
for(var i = 0; i < ground.length; i++)
{
ground[i].update();
ground[i].draw();
//Stop the player going through the platforms when landing
var angle;
if(player.minDist(ground[i]) <= player.height/2 + platformWidth/2 && (angle = Math.atan2(player.y - ground[i].y, player.x - ground[i].x) * 180/Math.PI) > -130 &&angle < -50)
{
player.isJumping = false;
player.isFalling = false;
player.y = ground[i].y - player.height + 5;
player.dy = 0;
}
}
//Remove the ground that has gone off screen
if(ground[0] && ground[0].x < -platformWidth)
{
ground.splice(0, 1);
}
}
//Update and draw all water positions
function updateWater()
{
//Animate water
for(var i = 0; i < water.length; i++)
{
water[i].update();
water[i].draw();
}
//Remove water that has gone off screen
if (water[0] && water[0].x < -platformWidth)
{
var w = water.splice(0, 1)[0];
w.x = water[water.length-1].x + platformWidth;
water.push(w);
}
}
//Update and draw all environment positions
function updateEnvironment()
{
//Animate environment
for(var i = 0; i < environment.length; i++)
{
environment[i].update();
environment[i].draw();
}
//R emove environment that have gone off screen
if(environment[0] && environment[0].x < -platformWidth)
{
environment.splice(0, 1);
}
}
//Update and draw all enemies position. Also check for collision against the player.
function updateEnemies()
{
//Animate enemies
for(var i = 0; i < enemies.length; i++)
{
enemies[i].update();
enemies[i].draw();
//Player ran into enemy
if(player.minDist(enemies[i]) <= player.width - platformWidth/2)
{
gameOver();
}
}
//Remove enemies that have gone off screen
if(enemies[0] && enemies[0].x < -platformWidth)
{
enemies.splice(0, 1);
}
}
//Update and draw the players position
function updatePlayer()
{
player.update();
player.draw();
//Game over
if(player.y + player.height >= canvas.height)
{
gameOver();
}
}
//Spawn new sprites off screen
function spawnSprites()
{
//Increase score
score++;
//First create a gap
if(gapLength > 0)
{
gapLength--;
}
//Then create the ground
else if(platformLength > 0)
{
var type = getType();
ground.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * platformSpacer,
type
));
platformLength--;
//Add random environment sprites
spawnEnvironmentSprites();
//Add random enemies
spawnEnemySprites();
}
//Start over
else
{
//Increase gap length every speed increase of 4
gapLength = rand(player.speed - 2, player.speed);
// only allow a ground to increase by 1
platformHeight = bound(rand(0, platformHeight + rand(0, 2)), 0, 4);
platformLength = rand(Math.floor(player.speed/2), player.speed * 4);
}
}
//Spawn new environment sprites off screen
function spawnEnvironmentSprites()
{
if(score > 40 && rand(0, 20) === 0 && platformHeight < 3)
{
if (Math.random() > 0.5)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'plant'));
}
else if(platformLength > 2)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'bush1'));
environment.push(new Sprite(canvas.width + platformWidth % player.speed + platformWidth, platformBase - platformHeight * platformSpacer - platformWidth, 'bush2'));
}
}
}
//Spawn new enemy sprites off screen
function spawnEnemySprites()
{
if(score > 100 && Math.random() > 0.96 && enemies.length < 3 && platformLength > 5 && (enemies.length ? canvas.width - enemies[enemies.length-1].x >= platformWidth * 3 || canvas.width - enemies[enemies.length-1].x < platformWidth : true))
{
enemies.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, Math.random() > 0.5 ? 'spikes' : 'slime'));
}
}
//Game Loop
function animate()
{
if(!stop)
{
requestAnimFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
background.draw();
//Update entities
updateWater();
updateEnvironment();
updatePlayer();
updateGround();
updateEnemies();
//Draw the score
ctx.fillText('Score: ' + score + 'm', canvas.width - 140, 30);
//Spawn a new Sprite
if(ticker % Math.floor(platformWidth / player.speed) === 0)
{
spawnSprites();
}
//Increase player's speed only when player is jumping
if(ticker > (Math.floor(platformWidth / player.speed) * player.speed * 20) && player.dy !== 0)
{
player.speed = bound(++player.speed, 0, 15);
player.walkAnim.frameSpeed = Math.floor(platformWidth / player.speed) - 1;
//Reset ticker
ticker = 0;
//Spawn a platform to fill in gap created by increasing player speed
if(gapLength === 0)
{
var type = getType();
ground.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer, type));
platformLength--;
}
}
ticker++;
}
}
//Spacebar events
var KEY_CODES = {
32: 'space'
};
var KEY_STATUS = {};
for(var code in KEY_CODES)
{
if(KEY_CODES.hasOwnProperty(code))
{
KEY_STATUS[KEY_CODES[code]] = false;
}
}
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = true;
}
};
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = false;
}
};
//Request Animation Polyfill
var requestAnimFrame = (function()
{
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element)
{
window.setTimeout(callback, 1000 / 60);
};
})();
//Start the game and resets all variables and entities, spawn ground and water.
function startGame()
{
document.getElementById('game-over').style.display = 'none';
ground = [];
water = [];
environment = [];
enemies = [];
player.reset();
ticker = 0;
stop = false;
score = 0;
platformHeight = 2;
platformLength = 15;
gapLength = 0;
ctx.font = '16px arial, sans-serif';
for (var i = 0; i < 30; i++)
{
ground.push(new Sprite(i * (platformWidth-3), platformBase - platformHeight * platformSpacer, 'grass'));
}
for (i = 0; i < canvas.width / 32 + 2; i++)
{
water.push(new Sprite(i * platformWidth, platformBase, 'water'));
}
background.reset();
animate();
}
//End the game and restart
function gameOver()
{
stop = true;
document.getElementById('game-over').style.display = 'block';
}
document.getElementById('restart').addEventListener('click', startGame);
assetLoader.downloadAll();
})();
Fiddle: http://jsfiddle.net/7zomjc7z/
JavaScript:
function init() {
var ctx;
var turn = [];
var xV = [-1, 0, 1, 0];
var yV = [0, -1, 0, 1];
var queue = [];
var elements = 1;
var map = [];
var MR = Math.random;
var X = 5 + (MR() * (100 - 10)) | 0;
var Y = 5 + (MR() * (30 - 10)) | 0;
var direction = MR() * 3 | 0;
var interval = 0;
var score = 0;
var inc_score = 50;
var sum = 0,
easy = 0;
var i, dir;
var win = window;
var doc = document;
var canvas = doc.createElement('canvas');
var setInt = win.setInterval;
var clInt = win.clearInterval;
for (i = 0; i < 100; i++) {
map[i] = [];
}
canvas.setAttribute('width', 100 * 10);
canvas.setAttribute('height', 30 * 10);
ctx = canvas.getContext('2d');
var div = document.getElementById("pnlGame");
div.appendChild(canvas);
//doc.body.appendChild(canvas);
function placeFood() {
var x, y;
do {
x = MR() * 100 | 0;
y = MR() * 30 | 0;
} while (map[x][y]);
map[x][y] = 1;
ctx.strokeRect(x * 10 + 1, y * 10 + 1, 10 - 2, 10 - 2);
}
placeFood();
function clock() {
if (easy) {
X = (X + 100) % 100;
Y = (Y + 30) % 30;
}
--inc_score;
if (turn.length) {
dir = turn.pop();
if ((dir % 2) !== (direction % 2)) {
direction = dir;
}
}
if (
(easy || (0 <= X && 0 <= Y && X < 100 && Y < 30))
&& 2 !== map[X][Y]) {
if (1 === map[X][Y]) {
score += Math.max(5, inc_score);
inc_score = 50;
placeFood();
elements++;
}
ctx.fillRect(X * 10, Y * 10, 10 - 1, 10 - 1);
map[X][Y] = 2;
queue.unshift([X, Y]);
X += xV[direction];
Y += yV[direction];
if (elements < queue.length) {
dir = queue.pop()
map[dir[0]][dir[1]] = 0;
ctx.clearRect(dir[0] * 10, dir[1] * 10, 10, 10);
}
} else if (!turn.length) {
if (confirm("You lost! Play again? Your Score is " + score)) {
ctx.clearRect(0, 0, 450, 300);
queue = [];
elements = 1;
map = [];
X = 5 + (MR() * (100 - 10)) | 0;
Y = 5 + (MR() * (30 - 10)) | 0;
direction = MR() * 3 | 0;
score = 0;
inc_score = 50;
for (i = 0; i < 100; i++) {
map[i] = [];
}
placeFood();
} else {
clInt(interval);
//window.location = "YourTasks.aspx";
}
}
}
interval = setInt(clock, 120);
doc.onkeydown = function (e) {
var code = e.keyCode - 37;
/*
* 0: left
* 1: up
* 2: right
* 3: down
**/
if (0 <= code && code < 4 && code !== turn[0]) {
turn.unshift(code);
} else if (-5 == code) {
if (interval) {
clInt(interval);
interval = 0;
} else {
interval = setInt(clock, 120);
}
} else { // O.o
dir = sum + code;
if (dir == 44 || dir == 94 || dir == 126 || dir == 171) {
sum += code
} else if (dir === 218) easy = 1;
}
}
}
When there is no more turn left and the javascript alert is shown, the snake square get stuck and the canvas doesn't clear.
How can I resolve it?
You've set the width of your canvas to 1000 (canvas.setAttribute('width', 100 * 10);)
where you are clearing it you are only clearing a 450 wide box
ctx.clearRect(0, 0, 450, 300);
You need to change this to
ctx.clearRect(0, 0, 1000, 300);