Related
I'm trying to construct a Voronoi diagram using p5.js. So far I have managed to create an image representation of it by coloring pixels that belong to the same region, using the algorithm from this video. Here how it looks like:
Here's the code (a bit messy and terribly inefficient)
const h = 512
const w = 512
const scl = 512;
const rez = h / scl;
const tiles = []
let points = []
function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min)
}
function setup() {
createCanvas(w, h);
background(220);
for (let i = 0; i < 12; i++) {
const p = randomIntFromInterval(0, h * h)
points.push(p)
}
for (let y = 0; y < scl; y++) {
for (let x = 0; x < scl; x++) {
tiles.push(0);
const xx = x * rez;
const yy = y * rez;
noFill();
stroke(0);
rect(xx, yy, rez, rez);
const indx = `${x + y * scl}`
if (+indx === points[0] || +indx === points[1] || +indx === points[2]) {
stroke(225, 0, 0)
fill(255, 0, 0)
}
text(indx, xx + rez / 2 - 5, yy + rez / 2 + 5)
}
}
compute(0, scl, scl)
for (const p of points) {
const x = p % scl;
const y = (p - x) / scl;
fill(0)
circle(x * rez, y * rez, 5)
}
}
function compute(x, len, grandLen) {
const corners = getCorners(x, len, grandLen)
const lookup = []
for (const corner of corners) {
const ds = []
for (const point of points) {
ds.push(distance(corner, point, scl));
}
lookup.push(ds)
}
const is = []
for (let i = 0; i < lookup.length; i++) {
const min = Math.min(...lookup[i])
const iMin = lookup[i].indexOf(min);
is.push(iMin);
}
if (is.every((val, i, arr) => val === arr[0])) {
const colorR = map(points[is[0]], 0, h*h, 0, 255)
const colorG = 255 - map(points[is[0]], 0, h*h, 0, 255)
paintRegion(corners[0], len, rez, color(colorR, colorG, 200))
} else {
const rects = divide(corners[0], len)
rects.forEach(r => {
compute(r, len / 2, grandLen)
})
}
}
function paintRegion(a, len, size, color) {
let ax;
let ay;
[ax, ay] = toCoords(a);
fill(color)
noStroke(0);
rect(ax * size, ay * size, size * len, size * len)
}
function toCoords(index) {
const x = index % scl;
const y = Math.floor((index - x) / scl);
return [x, y]
}
function distance(a, b, len) {
let ax;
let ay;
let bx;
let by;
[ax, ay] = toCoords(a);
[bx, by] = toCoords(b);
const p1 = Math.pow(bx - ax, 2);
const p2 = Math.pow(by - ay, 2);
return sqrt(p1 + p2);
}
// l1 is square, l2 is canvas
function getCorners(a, l1, l2) {
const corners = []
corners.push(a);
corners.push(a + l1 - 1);
corners.push(a + (l1 - 1) * l2)
corners.push(a + (l1 - 1) * l2 + (l1 - 1));
return corners
}
function divide(a, len) {
let ax;
let ay;
[ax, ay] = toCoords(a);
const d = len / 2;
const p1 = ax + ay * scl;
const p2 = ax + d + ay * scl;
const p3 = ax + (ay + d) * scl;
const p4 = ax + d + (ay + d) * scl;
return [p1, p2, p3, p4];
}
function draw() {
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.5.0/lib/p5.js"></script>
My problem is, such representation isn't very useful for me. I need get coordinates of regions' edges (including edges of a canvas). I've searched for ways to find edges of a shape on a 2d plane, but I feel like this is a very backwards approach. Is there a way to calculate a Voronoi diagram edges? If not, what's the simplest way to find edges by going over the pixels array?
I know there is quite a few resources on how to do this using numpy or in matlab, but I actually need a javaScript solution, if possible.
UPD: As I was researching the topic further and looking into related question brought up by Cristian in the comments, I came up to a conclusion that Future's algorithms is the best option for getting regions' edges. Fortunately, Wikipedia has links to its implementations. I tried this great implementation by Raymond Hill and it worked out great.
I am trying to make a color pallet in which the user can click on the gradient and it will show you the RGB and HSL values so far I have printed out all the Hue values and now what I want to do is make the appropriate display of Saturation and Luminesance/lightness values as can be seen here https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Colors/Color_picker_tool
I have tried to use a double nested loop as a generator such as:
function * hslGen(hue){
for(let s = 0; s < 100; s++){
for(let l = 100; l > 0; l--){
yield `hsl(${hue}, ${s}%, ${l}%)`;}
}
}
But the result looks the following https://codepen.io/superpauly/pen/XWMQboe?editors=1010 which confivuration should I have the loops to display a collor pallet like shown in the example above?
So I have got the generator and the rest of the code is as follow:
const canvasContext = canvRef.current.getContext("2d");
for (let hue = 0; hue < 360; hue++) {
// Generates Hue Spectrum
canvasContext.fillStyle = "hsl(" + hue + ", 100%, 50%)";
canvasContext.fillRect(5 * hue, 0, 5, 75);
}
canvRef.current.addEventListener("click", (e) => {
data = canvasContext.getImageData(e.layerX, e.layerY, 1, 1).data;
color.rgb = `rgb( ${data[0]}, ${data[1]}, ${data[2]})`;
color.h = rgbToHue(data);
color.hsl = `hsl( ${color.h}, 100%, 50%)`;
let pixel = 0;
for (let m of hslGen(color.h)){
canvasContext.fillStyle = m; //HSL Generator string
canvasContext.fillRect(pixel+=1, 75, 1440, 500); // Here is where I try to make the gradient bu it fails.
}
Thank you.
You need track x and y coordinates for each pixel:
import React, {
useEffect,
useRef,
useState
} from "https://cdn.skypack.dev/react#17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom#17.0.1";
console.clear();
function * hslGen(hue){
for(let l = 100; l >= 0; l--)
{
for(let s = 0; s <= 100; s++)
{
yield `hsl(${hue}, ${s}%, ${l}%)`;
}
}
}
const RGBToHex = (r, g, b) => {
r = r.toString(16);
g = g.toString(16);
b = b.toString(16);
if (r.length == 1) r = "0" + r;
if (g.length == 1) g = "0" + g;
if (b.length == 1) b = "0" + b;
return `#${r+g+b}`;
};
function rgbToHue(getImageData) {
let rgbHue = {
red: getImageData[0] / 255,
green: getImageData[1] / 255,
blue: getImageData[2] / 255
};
let maxValueKey = Object.keys(rgbHue).reduce((a, b) =>
rgbHue[a] > rgbHue[b] ? a : b
);
let maxValue = Math.max(rgbHue["red"], rgbHue["green"], rgbHue["blue"]);
let minValue = Math.min(rgbHue["red"], rgbHue["green"], rgbHue["blue"]);
let hue = 0.0;
switch (maxValueKey) {
case "red":
hue = (rgbHue["green"] - rgbHue["blue"]) / (maxValue - minValue);
break;
case "green":
hue = 2.0 + (rgbHue["blue"] - rgbHue["red"]) / (maxValue - minValue);
break;
case "blue":
hue = 4.0 + (rgbHue["red"] - rgbHue["green"]) / (maxValue - minValue);
break;
}
hue = hue * 60.0;
if (hue < 0.0) {
hue = hue + 360.0;
}
console.log("Hue in Deg: " + hue);
return hue;
}
const ColorPicker = () => {
const canvRef = useRef();
const color = { rgb: "", hsl: "",
h:0};
let [col, setCol] = useState({});
let data = 0;
useEffect(() => {
const canvasContext = canvRef.current.getContext("2d");
for (let hue = 0; hue < 360; hue++) {
canvasContext.fillStyle = "hsl(" + hue + ", 100%, 50%)";
canvasContext.fillRect(5 * hue, 0, 5, 75);
}
canvRef.current.addEventListener("click", (e) => {
data = canvasContext.getImageData(e.layerX, e.layerY, 1, 1).data;
color.rgb = `rgb( ${data[0]}, ${data[1]}, ${data[2]})`;
color.h = rgbToHue(data);
color.hsl = `hsl( ${color.h}, 100%, 50%)`;
setCol({
rgb: color.rgb,
hsl: color.hsl
});
debugger;
let x = 0,
y = 0,
pix = 3; //pixel size
console.log("Start");
for (let m of hslGen(color.h)){
canvasContext.fillStyle = m;
canvasContext.fillRect(100 + x, 175 + y, pix, pix); //Need to made this a 2d saturation, light graph
console.log(m, x, y);
x += pix;
x = x % (pix * 101);
if (!x)
y += pix;
}
console.log("End");
});
console.log(color);
}, []);
return (
<>
<canvas
ref={canvRef}
height={window.innerHeight}
width={window.innerWidth}
></canvas>
<span>HSL: {col.hsl}</span>
<span>RGB: {col.rgb}</span>
</>
);
};
ReactDOM.render(, document.getElementById("colorCanvas"));
https://codepen.io/vanowm/pen/JjWgJOO
If you're set on using the generator you can do something like so:
const scale = 4; // likely don't want the user to have to click on a single pixel so need some kind of scale factor
const hueInput = document.getElementById('hue');
const canvas = document.getElementById('color-picker');
canvas.width = 100 * scale;
canvas.height = 100 * scale;
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
const context = canvas.getContext('2d');
function * hslGen(hue){
for(let s = 0; s < 100; s++){
for(let l = 100; l > 0; l--){
yield `hsl(${hue}, ${s}%, ${l}%)`;}
}
}
function updateColorPicker() {
const hue = hueInput.value;
// need to keep track of both x and y as it's two dimensional
let y = 0, x = 0;
for (const m of hslGen(hue)) {
context.fillStyle = m; //HSL Generator string
context.fillRect(x * scale, y * scale, scale, scale);
// iterator is doing saturation then lightness so we populate y first then x
y++;
if (y >= 100) {
y = 0;
x++;
}
}
}
hueInput.addEventListener('change', updateColorPicker);
updateColorPicker();
<label> HUE <input type="number" id="hue" value="5"></label>
<br>
<canvas id="color-picker"></canvas>
Because the expected output is 2 dimensional you need both an x and y component. Because of this, the generator in it's current form doesn't seem to make much sense.
A better approach would be to either remove the generator altogether, or make it fit the problem better. Here's an example without the generator :
const scale = 4; // likely don't want the user to have to click on a single pixel so need some kind of scale factor
const hueInput = document.getElementById('hue');
const canvas = document.getElementById('color-picker');
canvas.width = 100 * scale;
canvas.height = 100 * scale;
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
const context = canvas.getContext('2d');
function updateColorPicker() {
const hue = hueInput.value;
// need to keep track of both x and y as it's two dimensional
for(let x = 0; x < 100; x++) {
for(let y = 0; y < 100; y++) {
var m = `hsl(${hue}, ${x}%, ${100-y}%)`;
context.fillStyle = m; //HSL Generator string
context.fillRect(x * scale, y * scale, scale, scale);
}
}
}
hueInput.addEventListener('change', updateColorPicker);
updateColorPicker();
<label> HUE <input type="number" id="hue" value="5"></label>
<br>
<canvas id="color-picker"></canvas>
I've been working on a simulation of a magnetic pendulum (Magnetic Pendulum for reference). Now, I have a question concerning how you calculate the forces/acceleration:
In examples you find online of the magnetic pendulum (such as the one provided above), and other physics force and acceleration calculations, the forces are calculated at a starting position, the position updated according to time-step dt and then new forces at time t+dt calculated to get a new position and so on. Basic numerical integration, makes sense.
However, I noticed, that if I calculate the forces this way, things don't go as one would expect. For example: when the pendulum is attracted by a magnet it just stops at the particluar magnet even tough you would expect it to "overswing" a bit. Therefore, I introduced three variables for each force which is all previous forces added together, and now it somehow seems to work correctly.
I'm wondering if
the way of calculating the forces I used makes sense in a physics simulation and
if not, then what am I doing wrong? Because it seems to work for other people.
Here's my code:
p5.disableFriendlyErrors = true;
let magnete = [];
let particles = [];
var maganzahl = 3; //number of magnets
var magradius = 100; //radius of magnets from mid-point
var G = 0.002; //coefficient of force towards middle (gravity)
var R = 0.2; //friction coefficient
var h = 50; //height of bob over magnets
var M = 150000; //strength of magnets
var m = 1; //mass (might not work)
var dt = 0.25; //timestep
var d = 2; //pixel density
var counter2 = 0;
var Reset;
var end = false;
//--------------------------------------------
function setup() {
createCanvas(600, 600);
pixelDensity(d);
background(60);
Reset = createButton('Reset');
Reset.position(width + 19, 49);
Reset.mousePressed(resetcanvas);
//construction of magnets
for (var i = 0; i < maganzahl; i++) {
magnete.push(new Magnet((width / 2) + magradius * cos(TWO_PI * (i / maganzahl)), (height / 2) + magradius * sin(TWO_PI * (i / maganzahl)), i));
}
}
//construction of a new "starting position" by mouse-click
function mousePressed() {
if (mouseX < width && mouseY < height) {
particles.push(new Particle(mouseX, mouseY));
}
}
function draw() {
for (var i = 0; i < particles.length; i++) {
if (particles[i].counter < 1000) {
//5 updates per frame(to speed it up)
for (var k = 0; k < 5; k++) {
for (var j = 0; j < magnete.length; j++) {
particles[i].attracted(magnete[j]);
}
particles[i].update();
particles[i].show2();
}
} else if (particles[i].counter < 1001) {
particles[i].counter += 1;
var nearest = [];
for (var j = 0; j < magnete.length; j++) {
nearest.push(particles[i].near(magnete[j]));
}
if (nearest.indexOf(min(nearest)) == 0) {
var c = color("green");
}
if (nearest.indexOf(min(nearest)) == 1) {
var c = color("purple");
}
if (nearest.indexOf(min(nearest)) == 2) {
var c = color("orange");
}
if (nearest.indexOf(min(nearest)) == 3) {
var c = color("blue");
}
if (nearest.indexOf(min(nearest)) == 4) {
var c = color("red");
}
if (nearest.indexOf(min(nearest)) == 5) {
var c = color("yellow");
}
//show particle trace according to nearest magnet
particles[i].show(c);
}
}
//displaying magnets
for (var i = 0; i < magnete.length; i++) {
magnete[i].show();
}
//displaying mid-point
stroke(255);
circle(width / 2, height / 2, 3);
}
function resetcanvas() {
background(60);
}
function Particle(x, y) {
this.orgpos = createVector(x, y);
this.pos = createVector(x, y);
this.prev = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.accpre = createVector();
this.accprepre = createVector();
this.velprediction = this.vel;
this.magnetf = createVector();
this.gravity = createVector();
this.friction = createVector();
this.shape = new Array();
this.counter = 0;
//calculating new positions
this.update = function() {
//predictor for velocity -> Beeman's algorithm
this.velprediction.add(this.accpre.mult(3 / 2 * dt).add(this.accprepre.mult(-1 / 2 * dt)));
var momgrav = createVector(width / 2 - this.pos.x, height / 2 - this.pos.y);
var momfric = createVector(this.velprediction.x, this.velprediction.y);
momgrav.mult(G * m); //force due to gravity
momfric.mult(-R); //force due to friction
this.gravity.add(momgrav);
this.friction.add(momfric);
//a = F/m
this.acc = createVector((this.magnetf.x + this.gravity.x + this.friction.x) / m, (this.magnetf.y + this.gravity.y + this.friction.y) / m);
//-=Beeman's Algorithm=-
this.vel.add(this.acc.mult(dt * 1 / 3).add(this.accpre.mult(dt * 5 / 6)).add(this.accprepre.mult(-1 / 6 * dt)));
this.pos.add(this.vel.mult(dt).add(this.accpre.mult(dt * dt * 2 / 3)).add(this.accprepre.mult(-1 / 6 * dt * dt)));
this.accprepre = createVector(this.accpre.x, this.accpre.y);
this.accpre = createVector(this.acc.x, this.acc.y);
this.velprediction = createVector(this.vel.x, this.vel.y);
this.counter += 1;
this.shape.push(new p5.Vector(this.pos.x, this.pos.y));
}
//calculating force due to magnets -> attracted called earlier than update in sketch.js
this.attracted = function(target) {
var magn = createVector(target.pos.x - this.pos.x, target.pos.y - this.pos.y);
var dist = sqrt(sq(h) + sq(magn.x) + sq(magn.y)); //distance bob - magnet
strength = M / (Math.pow(dist, 3));
magn.mult(strength);
this.magnetf.add(magn);
}
//calculating distance to target
this.near = function(target) {
var dist = sqrt(sq(h) + sq(this.pos.x - target.pos.x) + sq(this.pos.y - target.pos.y));
return (dist);
}
//trace
this.show = function(col) {
beginShape();
stroke(col);
for (var i = 0; i < this.shape.length - 1; i++) {
line(this.shape[i].x, this.shape[i].y, this.shape[i + 1].x, this.shape[i + 1].y);
strokeWeight(2);
noFill();
}
endShape();
}
//dots
this.show2 = function() {
strokeWeight(1)
point(this.pos.x, this.pos.y);
}
}
function Magnet(x, y, n) {
this.pos = createVector(x, y);
this.n = n;
this.show = function() {
noStroke();
//color for each magnet
if (n == 0) {
fill("green");
}
if (n == 1) {
fill("purple");
}
if (n == 2) {
fill("orange");
}
if (n == 3) {
fill("blue");
}
if (n == 4) {
fill("red");
}
if (n == 5) {
fill("yellow");
}
strokeWeight(4);
circle(this.pos.x, this.pos.y, 10);
}
}
Any help would be greatly appreciated!
I found the issue! So apparently in p5.js you have to be careful with your vector calculations. If you for example do something like:
[some variable] = [Vector].add([anotherVector].mult(2));
both [Vector] and [anotherVector] change their value. Makes sense now that I think about it...
The fact that it still seemed to work somewhat "realistically" and that I managed to generate some beautiful pictures using it (Such as this one 1 and this one 2) is still quite mysterious to me but I guess sometimes numbers just do their magic ;)
Run it:
Code Preview
If you want to change some variables/edit:
p5.js Web Editor
I'm trying to create a hyperdrive effect, like from Star Wars, where the stars have a motion trail. I've gotten as far as creating the motion trail on a single circle, it still looks like the trail is going down in the y direction and not forwards or positive in the z direction.
Also, how could I do this with (many) randomly placed circles as if they were stars?
My code is on jsfiddle (https://jsfiddle.net/5m7x5zxu/) and below:
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
var xPos = 180;
var yPos = 100;
var motionTrailLength = 16;
var positions = [];
function storeLastPosition(xPos, yPos) {
// push an item
positions.push({
x: xPos,
y: yPos
});
//get rid of first item
if (positions.length > motionTrailLength) {
positions.pop();
}
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i = positions.length-1; i > 0; i--) {
var ratio = (i - 1) / positions.length;
drawCircle(positions[i].x, positions[i].y, ratio);
}
drawCircle(xPos, yPos, "source");
var k=2;
storeLastPosition(xPos, yPos);
// update position
if (yPos > 125) {
positions.pop();
}
else{
yPos += k*1.1;
}
requestAnimationFrame(update);
}
update();
function drawCircle(x, y, r) {
if (r == "source") {
r = 1;
} else {
r*=1.1;
}
context.beginPath();
context.arc(x, y, 3, 0, 2 * Math.PI, true);
context.fillStyle = "rgba(255, 255, 255, " + parseFloat(1-r) + ")";
context.fill();
}
Canvas feedback and particles.
This type of FX can be done many ways.
You could just use a particle systems and draw stars (as lines) moving away from a central point, as the speed increase you increase the line length. When at low speed the line becomes a circle if you set ctx.lineWidth > 1 and ctx.lineCap = "round"
To add to the FX you can use render feedback as I think you have done by rendering the canvas over its self. If you render it slightly larger you get a zoom FX. If you use ctx.globalCompositeOperation = "lighter" you can increase the stars intensity as you speed up to make up for the overall loss of brightness as stars move faster.
Example
I got carried away so you will have to sift through the code to find what you need.
The particle system uses the Point object and a special array called bubbleArray to stop GC hits from janking the animation.
You can use just an ordinary array if you want. The particles are independent of the bubble array. When they have moved outside the screen they are move to a pool and used again when a new particle is needed. The update function moves them and the draw Function draws them I guess LOL
The function loop is the main loop and adds and draws particles (I have set the particle count to 400 but should handle many more)
The hyper drive is operated via the mouse button. Press for on, let go for off. (It will distort the text if it's being displayed)
The canvas feedback is set via that hyperSpeed variable, the math is a little complex. The sCurce function just limits the value to 0,1 in this case to stop alpha from going over or under 1,0. The hyperZero is just the sCurve return for 1 which is the hyper drives slowest speed.
I have pushed the feedback very close to the limit. In the first few lines of the loop function you can set the top speed if(mouse.button){ if(hyperSpeed < 1.75){ Over this value 1.75 and you will start to get bad FX, at about 2 the whole screen will just go white (I think that was where)
Just play with it and if you have questions ask in the comments.
const ctx = canvas.getContext("2d");
// very simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// High performance array pool using buubleArray to separate pool objects and active object.
// This is designed to eliminate GC hits involved with particle systems and
// objects that have short lifetimes but used often.
// Warning this code is not well tested.
const bubbleArray = () => {
const items = [];
var count = 0;
return {
clear(){ // warning this dereferences all locally held references and can incur Big GC hit. Use it wisely.
this.items.length = 0;
count = 0;
},
update() {
var head, tail;
head = tail = 0;
while(head < count){
if(items[head].update() === false) {head += 1 }
else{
if(tail < head){
const temp = items[head];
items[head] = items[tail];
items[tail] = temp;
}
head += 1;
tail += 1;
}
}
return count = tail;
},
createCallFunction(name, earlyExit = false){
name = name.split(" ")[0];
const keys = Object.keys(this);
if(Object.keys(this).indexOf(name) > -1){ throw new Error(`Can not create function name '${name}' as it already exists.`) }
if(!/\W/g.test(name)){
let func;
if(earlyExit){
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ if (items[i++].${name}() === true) { break } }`;
}else{
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ items[i++].${name}() }`;
}
!this.items && (this.items = items);
this[name] = new Function(func);
}else{ throw new Error(`Function name '${name}' contains illegal characters. Use alpha numeric characters.`) }
},
callEach(name){var i = 0; while(i < count){ if (items[i++][name]() === true) { break } } },
each(cb) { var i = 0; while(i < count){ if (cb(items[i], i++) === true) { break } } },
next() { if (count < items.length) { return items[count ++] } },
add(item) {
if(count === items.length){
items.push(item);
count ++;
}else{
items.push(items[count]);
items[count++] = item;
}
return item;
},
getCount() { return count },
}
}
// Helpers rand float, randI random Int
// doFor iterator
// sCurve curve input -Infinity to Infinity out -1 to 1
// randHSLA creates random colour
// CImage, CImageCtx create image and image with context attached
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const sCurve = (v,p) => (2 / (1 + Math.pow(p,-v))) -1;
const randHSLA = (h, h1, s = 100, s1 = 100, l = 50, l1 = 50, a = 1, a1 = 1) => { return `hsla(${randI(h,h1) % 360},${randI(s,s1)}%,${randI(l,l1)}%,${rand(a,a1)})` }
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
// create image to hold text
var textImage = CImageCtx(1024, 1024);
var c = textImage.ctx;
c.fillStyle = "#FF0";
c.font = "64px arial black";
c.textAlign = "center";
c.textBaseline = "middle";
const text = "HYPER,SPEED FX,VII,,Battle of Jank,,Hold the mouse,button to increase,speed.".split(",");
text.forEach((line,i) => { c.fillText(line,512,i * 68 + 68) });
const maxLines = text.length * 68 + 68;
function starWarIntro(image,x1,y1,x2,y2,pos){
var iw = image.width;
var ih = image.height;
var hh = (x2 - x1) / (y2 - y1); // Slope of left edge
var w2 = iw / 2; // half width
var z1 = w2 - x1; // Distance (z) to first line
var z2 = (z1 / (w2 - x2)) * z1 - z1; // distance (z) between first and last line
var sk,t3,t3a,z3a,lines, z3, dd = 0, a = 0, as = 2 / (y2 - y1);
for (var y = y1; y < y2 && dd < maxLines; y++) { // for each line
t3 = ((y - y1) * hh) + x1; // get scan line top left edge
t3a = (((y+1) - y1) * hh) + x1; // get scan line bottom left edge
z3 = (z1 / (w2 - t3)) * z1; // get Z distance to top of this line
z3a = (z1 / (w2 - t3a)) * z1; // get Z distance to bottom of this line
dd = ((z3 - z1) / z2) * ih; // get y bitmap coord
a += as;
ctx.globalAlpha = a < 1 ? a : 1;
dd += pos; // kludge for this answer to make text move
// does not move text correctly
lines = ((z3a - z1) / z2) * ih-dd; // get number of lines to copy
ctx.drawImage(image, 0, dd , iw, lines, t3, y, w - t3 * 2, 1.5);
}
}
// canvas settings
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// diagonal distance used to set point alpha (see point update)
var diag = Math.sqrt(w * w + h * h);
// If window size is changed this is called to resize the canvas
// It is not called via the resize event as that can fire to often and
// debounce makes it feel sluggish so is called from main loop.
function resizeCanvas(){
points.clear();
canvas.width = innerWidth;
canvas.height = innerHeight;
w = canvas.width;
h = canvas.height;
cw = w / 2; // center
ch = h / 2;
diag = Math.sqrt(w * w + h * h);
}
// create array of points
const points = bubbleArray();
// create optimised draw function itterator
points.createCallFunction("draw",false);
// spawns a new star
function spawnPoint(pos){
var p = points.next();
p = points.add(new Point())
if (p === undefined) { p = points.add(new Point()) }
p.reset(pos);
}
// point object represents a single star
function Point(pos){ // this function is duplicated as reset
if(pos){
this.x = pos.x;
this.y = pos.y;
this.dead = false;
}else{
this.x = 0;
this.y = 0;
this.dead = true;
}
this.alpha = 0;
var x = this.x - cw;
var y = this.y - ch;
this.dir = Math.atan2(y,x);
this.distStart = Math.sqrt(x * x + y * y);
this.speed = rand(0.01,1);
this.col = randHSLA(220,280,100,100,50,100);
this.dx = Math.cos(this.dir) * this.speed;
this.dy = Math.sin(this.dir) * this.speed;
}
Point.prototype = {
reset : Point, // resets the point
update(){ // moves point and returns false when outside
this.speed *= hyperSpeed; // increase speed the more it has moved
this.x += Math.cos(this.dir) * this.speed;
this.y += Math.sin(this.dir) * this.speed;
var x = this.x - cw;
var y = this.y - ch;
this.alpha = (Math.sqrt(x * x + y * y) - this.distStart) / (diag * 0.5 - this.distStart);
if(this.alpha > 1 || this.x < 0 || this.y < 0 || this.x > w || this.h > h){
this.dead = true;
}
return !this.dead;
},
draw(){ // draws the point
ctx.strokeStyle = this.col;
ctx.globalAlpha = 0.25 + this.alpha *0.75;
ctx.beginPath();
ctx.lineTo(this.x - this.dx * this.speed, this.y - this.dy * this.speed);
ctx.lineTo(this.x, this.y);
ctx.stroke();
}
}
const maxStarCount = 400;
const p = {x : 0, y : 0};
var hyperSpeed = 1.001;
const alphaZero = sCurve(1,2);
var startTime;
function loop(time){
if(startTime === undefined){
startTime = time;
}
if(w !== innerWidth || h !== innerHeight){
resizeCanvas();
}
// if mouse down then go to hyper speed
if(mouse.button){
if(hyperSpeed < 1.75){
hyperSpeed += 0.01;
}
}else{
if(hyperSpeed > 1.01){
hyperSpeed -= 0.01;
}else if(hyperSpeed > 1.001){
hyperSpeed -= 0.001;
}
}
var hs = sCurve(hyperSpeed,2);
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,0,0); // reset transform
//==============================================================
// UPDATE the line below could be the problem. Remove it and try
// what is under that
//==============================================================
//ctx.fillStyle = `rgba(0,0,0,${1-(hs-alphaZero)*2})`;
// next two lines are the replacement
ctx.fillStyle = "Black";
ctx.globalAlpha = 1-(hs-alphaZero) * 2;
//==============================================================
ctx.fillRect(0,0,w,h);
// the amount to expand canvas feedback
var sx = (hyperSpeed-1) * cw * 0.1;
var sy = (hyperSpeed-1) * ch * 0.1;
// increase alpha as speed increases
ctx.globalAlpha = (hs-alphaZero)*2;
ctx.globalCompositeOperation = "lighter";
// draws feedback twice
ctx.drawImage(canvas,-sx, -sy, w + sx*2 , h + sy*2)
ctx.drawImage(canvas,-sx/2, -sy/2, w + sx , h + sy)
ctx.globalCompositeOperation = "source-over";
// add stars if count < maxStarCount
if(points.getCount() < maxStarCount){
var cent = (hyperSpeed - 1) *0.5; // pulls stars to center as speed increases
doFor(10,()=>{
p.x = rand(cw * cent ,w - cw * cent); // random screen position
p.y = rand(ch * cent,h - ch * cent);
spawnPoint(p)
})
}
// as speed increases make lines thicker
ctx.lineWidth = 2 + hs*2;
ctx.lineCap = "round";
points.update(); // update points
points.draw(); // draw points
ctx.globalAlpha = 1;
// scroll the perspective star wars text FX
var scrollTime = (time - startTime) / 5 - 2312;
if(scrollTime < 1024){
starWarIntro(textImage,cw - h * 0.5, h * 0.2, cw - h * 3, h , scrollTime );
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Here's another simple example, based mainly on the same idea as Blindman67, concetric lines moving away from center at different velocities (the farther from center, the faster it moves..) also no recycling pool here.
"use strict"
var c = document.createElement("canvas");
document.body.append(c);
var ctx = c.getContext("2d");
var w = window.innerWidth;
var h = window.innerHeight;
var ox = w / 2;
var oy = h / 2;
c.width = w; c.height = h;
const stars = 120;
const speed = 0.5;
const trailLength = 90;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#fff"
ctx.fillRect(ox, oy, 1, 1);
init();
function init() {
var X = [];
var Y = [];
for(var i = 0; i < stars; i++) {
var x = Math.random() * w;
var y = Math.random() * h;
X.push( translateX(x) );
Y.push( translateY(y) );
}
drawTrails(X, Y)
}
function translateX(x) {
return x - ox;
}
function translateY(y) {
return oy - y;
}
function getDistance(x, y) {
return Math.sqrt(x * x + y * y);
}
function getLineEquation(x, y) {
return function(n) {
return y / x * n;
}
}
function drawTrails(X, Y) {
var count = 1;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
function anim() {
for(var i = 0; i < X.length; i++) {
var x = X[i];
var y = Y[i];
drawNextPoint(x, y, count);
}
count+= speed;
if(count < trailLength) {
window.requestAnimationFrame(anim);
}
else {
init();
}
}
anim();
}
function drawNextPoint(x, y, step) {
ctx.fillStyle = "#fff";
var f = getLineEquation(x, y);
var coef = Math.abs(x) / 100;
var dist = getDistance( x, y);
var sp = speed * dist / 100;
for(var i = 0; i < sp; i++) {
var newX = x + Math.sign(x) * (step + i) * coef;
var newY = translateY( f(newX) );
ctx.fillRect(newX + ox, newY, 1, 1);
}
}
body {
overflow: hidden;
}
canvas {
position: absolute;
left: 0;
top: 0;
}
I am new to game development and I have build a car game where the automatically moves and when it hits a monster.Now I want to make the car move towards the monster.So I looked into the path finding algorithms and for now I thought to implement A-Star path finding algorithm in my game.So the function for finding path is like below:
function findPath(world, pathStart, pathEnd)
{
// shortcuts for speed
var abs = Math.abs;
var max = Math.max;
var pow = Math.pow;
var sqrt = Math.sqrt;
// the world data are integers:
// anything higher than this number is considered blocked
// this is handy is you use numbered sprites, more than one
// of which is walkable road, grass, mud, etc
var maxWalkableTileNum = 0;
// keep track of the world dimensions
// Note that this A-star implementation expects the world array to be square:
// it must have equal height and width. If your game world is rectangular,
// just fill the array with dummy values to pad the empty space.
var worldWidth = world[0].length;
var worldHeight = world.length;
var worldSize = worldWidth * worldHeight;
// which heuristic should we use?
// default: no diagonals (Manhattan)
var distanceFunction = ManhattanDistance;
var findNeighbours = function(){}; // empty
/*
// alternate heuristics, depending on your game:
// diagonals allowed but no sqeezing through cracks:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighbours;
// diagonals and squeezing through cracks allowed:
var distanceFunction = DiagonalDistance;
var findNeighbours = DiagonalNeighboursFree;
// euclidean but no squeezing through cracks:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighbours;
// euclidean and squeezing through cracks allowed:
var distanceFunction = EuclideanDistance;
var findNeighbours = DiagonalNeighboursFree;
*/
// distanceFunction functions
// these return how far away a point is to another
function ManhattanDistance(Point, Goal)
{ // linear movement - no diagonals - just cardinal directions (NSEW)
return abs(Point.x - Goal.x) + abs(Point.y - Goal.y);
}
function DiagonalDistance(Point, Goal)
{ // diagonal movement - assumes diag dist is 1, same as cardinals
return max(abs(Point.x - Goal.x), abs(Point.y - Goal.y));
}
function EuclideanDistance(Point, Goal)
{ // diagonals are considered a little farther than cardinal directions
// diagonal movement using Euclide (AC = sqrt(AB^2 + BC^2))
// where AB = x2 - x1 and BC = y2 - y1 and AC will be [x3, y3]
return sqrt(pow(Point.x - Goal.x, 2) + pow(Point.y - Goal.y, 2));
}
// Neighbours functions, used by findNeighbours function
// to locate adjacent available cells that aren't blocked
// Returns every available North, South, East or West
// cell that is empty. No diagonals,
// unless distanceFunction function is not Manhattan
function Neighbours(x, y)
{
var N = y - 1,
S = y + 1,
E = x + 1,
W = x - 1,
myN = N > -1 && canWalkHere(x, N),
myS = S < worldHeight && canWalkHere(x, S),
myE = E < worldWidth && canWalkHere(E, y),
myW = W > -1 && canWalkHere(W, y),
result = [];
if(myN)
result.push({x:x, y:N});
if(myE)
result.push({x:E, y:y});
if(myS)
result.push({x:x, y:S});
if(myW)
result.push({x:W, y:y});
findNeighbours(myN, myS, myE, myW, N, S, E, W, result);
return result;
}
// returns every available North East, South East,
// South West or North West cell - no squeezing through
// "cracks" between two diagonals
function DiagonalNeighbours(myN, myS, myE, myW, N, S, E, W, result)
{
if(myN)
{
if(myE && canWalkHere(E, N))
result.push({x:E, y:N});
if(myW && canWalkHere(W, N))
result.push({x:W, y:N});
}
if(myS)
{
if(myE && canWalkHere(E, S))
result.push({x:E, y:S});
if(myW && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns every available North East, South East,
// South West or North West cell including the times that
// you would be squeezing through a "crack"
function DiagonalNeighboursFree(myN, myS, myE, myW, N, S, E, W, result)
{
myN = N > -1;
myS = S < worldHeight;
myE = E < worldWidth;
myW = W > -1;
if(myE)
{
if(myN && canWalkHere(E, N))
result.push({x:E, y:N});
if(myS && canWalkHere(E, S))
result.push({x:E, y:S});
}
if(myW)
{
if(myN && canWalkHere(W, N))
result.push({x:W, y:N});
if(myS && canWalkHere(W, S))
result.push({x:W, y:S});
}
}
// returns boolean value (world cell is available and open)
function canWalkHere(x, y)
{
return ((world[x] != null) &&
(world[x][y] != null) &&
(world[x][y] <= maxWalkableTileNum));
};
// Node function, returns a new object with Node properties
// Used in the calculatePath function to store route costs, etc.
function Node(Parent, Point)
{
var newNode = {
// pointer to another Node object
Parent:Parent,
// array index of this Node in the world linear array
value:Point.x + (Point.y * worldWidth),
// the location coordinates of this Node
x:Point.x,
y:Point.y,
// the heuristic estimated cost
// of an entire path using this node
f:0,
// the distanceFunction cost to get
// from the starting point to this node
g:0
};
return newNode;
}
// Path function, executes AStar algorithm operations
function calculatePath()
{
// create Nodes from the Start and End x,y coordinates
var mypathStart = Node(null, {x:pathStart[0], y:pathStart[1]});
var mypathEnd = Node(null, {x:pathEnd[0], y:pathEnd[1]});
// create an array that will contain all world cells
var AStar = new Array(worldSize);
// list of currently open Nodes
var Open = [mypathStart];
// list of closed Nodes
var Closed = [];
// list of the final output array
var result = [];
// reference to a Node (that is nearby)
var myNeighbours;
// reference to a Node (that we are considering now)
var myNode;
// reference to a Node (that starts a path in question)
var myPath;
// temp integer variables used in the calculations
var length, max, min, i, j;
// iterate through the open list until none are left
while(length = Open.length)
{
max = worldSize;
min = -1;
for(i = 0; i < length; i++)
{
if(Open[i].f < max)
{
max = Open[i].f;
min = i;
}
}
// grab the next node and remove it from Open array
myNode = Open.splice(min, 1)[0];
// is it the destination node?
if(myNode.value === mypathEnd.value)
{
myPath = Closed[Closed.push(myNode) - 1];
do
{
result.push([myPath.x, myPath.y]);
}
while (myPath = myPath.Parent);
// clear the working arrays
AStar = Closed = Open = [];
// we want to return start to finish
result.reverse();
}
else // not the destination
{
// find which nearby nodes are walkable
myNeighbours = Neighbours(myNode.x, myNode.y);
// test each one that hasn't been tried already
for(i = 0, j = myNeighbours.length; i < j; i++)
{
myPath = Node(myNode, myNeighbours[i]);
if (!AStar[myPath.value])
{
// estimated cost of this particular route so far
myPath.g = myNode.g + distanceFunction(myNeighbours[i], myNode);
// estimated cost of entire guessed route to the destination
myPath.f = myPath.g + distanceFunction(myNeighbours[i], mypathEnd);
// remember this new path for testing above
Open.push(myPath);
// mark this node in the world graph as visited
AStar[myPath.value] = true;
}
}
// remember this route as having no more untested options
Closed.push(myNode);
}
} // keep iterating until the Open list is empty
return result;
}
// actually calculate the a-star path!
// this returns an array of coordinates
// that is empty if no path is possible
return calculatePath();
} // end of findPath() function
and then call the function by
currentPath = findPath(world,pathStart,pathEnd);
But not working.My working pen
Any help is appreciated.
Here is a simple path finding script to start from.
Once you have a path calculated, it should be trivial to move the car along it.
This script has two stages:
World generation
Where the map is scanned for hindrances and monsters
Path generation
Where a monster is found and a path is being calculated.
//HTML elements
var canvas = document.body.appendChild(document.createElement("canvas"));
canvas.height = 500;
canvas.width = canvas.height;
var ctx = canvas.getContext("2d");
//Logic elements
var tileSize = 16;
var monster = {
x: Math.floor(Math.random() * Math.ceil(canvas.width / tileSize) / 2) * 2,
y: Math.floor(Math.random() * Math.ceil(canvas.height / tileSize) / 2) * 2
};
var player = {
x: 9,
y: 9
};
var aStar = {
path: [],
opened: [],
closed: [],
done: false
};
//Simple distance formular
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Tested Tiles
ctx.fillStyle = "cyan";
for (var pi = 0; pi < aStar.closed.length; pi++) {
var p = aStar.closed[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Path
ctx.fillStyle = "blue";
for (var pi = 0; pi < aStar.path.length; pi++) {
var p = aStar.path[pi];
ctx.fillRect(p.x * tileSize, p.y * tileSize, tileSize, tileSize);
}
//Monster
ctx.fillStyle = "red";
ctx.fillRect(monster.x * tileSize, monster.y * tileSize, tileSize, tileSize);
//Player
ctx.fillStyle = "green";
ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize);
//Tiles
for (var x = 0; x < Math.ceil(canvas.width / tileSize); x++) {
for (var y = 0; y < Math.ceil(canvas.height / tileSize); y++) {
ctx.strokeRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
function main() {
//If no steps, open "player"
if (aStar.opened.length == 0) {
aStar.opened.push({
x: player.x,
y: player.y,
step: 0
});
}
//Check for monster
if ((aStar.opened.some(function(c) {
return c.x === monster.x && c.y === monster.y;
})) == true) {
//if monster found
if (aStar.path.length < 1) {
//If no steps in path, add monster as first
aStar.path.push(aStar.opened.find(function(c) {
return c.x === monster.x && c.y === monster.y;
}));
} else if ((aStar.path.length > 0 ? aStar.path[aStar.path.length - 1].step == 0 : false) === false) {
//If last step of path isn't player, compute a step to path
var lastTile = aStar.path[aStar.path.length - 1];
var bestTile = {
x: lastTile.x,
y: lastTile.y,
step: lastTile.step
};
//Loop through tiles adjacent to the last path tile and pick the "best"
for (var x = lastTile.x - 1; x < lastTile.x + 2; x++) {
for (var y = lastTile.y - 1; y < lastTile.y + 2; y++) {
var suspect = aStar.closed.find(function(c) {
return c.x === x && c.y === y;
});
if (suspect !== void 0) {
if (suspect.step + distance(suspect, player) < bestTile.step + distance(bestTile, player)) {
bestTile = suspect;
}
}
}
}
//Add best tile to path
aStar.path.push(bestTile);
}
} else {
//If monster isn't found, continue world mapping
//"newOpen" will hold the next "opened" list
var newOpen = [];
//For each opened, check neighbours
for (var oi = 0; oi < aStar.opened.length; oi++) {
var o = aStar.opened[oi];
for (var x = o.x - 1; x < o.x + 2; x++) {
for (var y = o.y - 1; y < o.y + 2; y++) {
if (x === o.x && y === o.y ||
aStar.closed.some(function(c) {
return c.x === x && c.y === y;
}) ||
aStar.opened.some(function(c) {
return c.x === x && c.y === y;
}) ||
newOpen.some(function(c) {
return c.x === x && c.y === y;
})) {
continue;
}
//If neighbours isn't in any list, add it to the newOpen list
newOpen.push({
x: x,
y: y,
step: o.step + 1
});
}
}
}
//Close the previously opened list
aStar.closed = aStar.closed.concat(aStar.opened);
//Add new opened list
aStar.opened = newOpen;
}
//Draw progress
draw();
requestAnimationFrame(main);
}
//Start process
requestAnimationFrame(main);
EDIT 1 - No pathfinding
I am not even sure you need pathfinding for this.
In the example below the cars are simply pushed towards a target relative to their angle to it:
var __extends = (this && this.__extends) || (function() {
var extendStatics = Object.setPrototypeOf ||
({
__proto__: []
}
instanceof Array && function(d, b) {
d.__proto__ = b;
}) ||
function(d, b) {
for (var p in b)
if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function(d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Game;
(function(Game) {
var GameImage = (function() {
function GameImage(name, src) {
this.name = name;
this.src = src;
this.node = document.createElement("img");
GameImage._pending++;
this.node.onload = GameImage._loading;
this.node.src = this.src;
GameImage.all.push(this);
}
GameImage.loaded = function() {
return this._loaded === this._pending;
};
GameImage._loading = function() {
this._loaded++;
};
GameImage.getImage = function(id) {
return this.all.find(function(img) {
return img.name === id;
});
};
return GameImage;
}());
GameImage.all = [];
GameImage._loaded = 0;
GameImage._pending = 0;
new GameImage("background", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_2048/v1492045665/road_dwsmux.png");
new GameImage("hero", "http://res.cloudinary.com/dfhppjli0/image/upload/c_scale,w_32/v1491958999/car_p1k2hw.png");
new GameImage("monster", "http://res.cloudinary.com/dfhppjli0/image/upload/v1491958478/monster_rsm0po.png");
new GameImage("hero_other", "http://res.cloudinary.com/dfhppjli0/image/upload/v1492579967/car_03_ilt08o.png");
function distance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function degreeToRadian(degrees) {
return degrees * (Math.PI / 180);
}
function radianToDegree(radians) {
return radians * (180 / Math.PI);
}
function angleBetweenTwoPoints(p1, p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
}
var Actor = (function() {
function Actor() {
this.angle = 0;
}
Actor.prototype.main = function() {};
Actor.prototype.render = function(ctx) {
if (this.angle != 0) {
var rads = degreeToRadian(this.angle - 90);
ctx.translate(this.position.x + 0.5 * this.image.node.naturalWidth, this.position.y + 0.5 * this.image.node.naturalHeight);
ctx.rotate(rads);
ctx.drawImage(this.image.node, 0, 0);
ctx.rotate(-rads);
ctx.translate(-(this.position.x + 0.5 * this.image.node.naturalWidth), -(this.position.y + 0.5 * this.image.node.naturalHeight));
} else {
ctx.drawImage(this.image.node, this.position.x, this.position.y);
}
};
return Actor;
}());
var Monster = (function(_super) {
__extends(Monster, _super);
function Monster(position) {
var _this = _super.call(this) || this;
_this.position = position;
_this.image = GameImage.getImage("monster");
Monster.all.push(_this);
return _this;
}
return Monster;
}(Actor));
Monster.all = [];
var Car = (function(_super) {
__extends(Car, _super);
function Car(position, target) {
if (target === void 0) {
target = null;
}
var _this = _super.call(this) || this;
_this.position = position;
_this.target = target;
_this.hitCount = 0;
_this.image = GameImage.getImage("hero");
_this.speed = 10;
Car.all.push(_this);
return _this;
}
Car.prototype.main = function() {
var angle = angleBetweenTwoPoints(this.target.position, this.position);
var cos = Math.cos(degreeToRadian(angle)) * -1;
var sin = Math.sin(degreeToRadian(angle));
this.angle = angle;
this.position.x += cos * this.speed;
this.position.y -= sin * this.speed;
if (distance(this.position, this.target.position) < 10) {
this.target.position.x = Math.random() * mainCanvas.width;
this.target.position.y = Math.random() * mainCanvas.height;
this.hitCount++;
console.log("Hit!");
}
};
return Car;
}(Actor));
Car.all = [];
var background = GameImage.getImage("background");
var mainCanvas = document.body.appendChild(document.createElement("canvas"));
mainCanvas.width = background.node.naturalWidth;
mainCanvas.height = background.node.naturalHeight;
var ctx = mainCanvas.getContext("2d");
var monster1 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
var monster2 = new Monster({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
});
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster1);
new Car({
x: Math.random() * mainCanvas.width,
y: Math.random() * mainCanvas.height
}, monster2);
function main() {
ctx.drawImage(background.node, 0, 0);
for (var ci = 0; ci < Car.all.length; ci++) {
var c = Car.all[ci];
c.main();
c.render(ctx);
}
for (var mi = 0; mi < Monster.all.length; mi++) {
var m = Monster.all[mi];
m.main();
m.render(ctx);
}
requestAnimationFrame(main);
}
requestAnimationFrame(main);
})(Game || (Game = {}));
As long as there are not obstacles, this works fine.