diff --git a/README.md b/README.md index e27c080..fe581be 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# particle-game +# Particle Game diff --git a/index.html b/index.html new file mode 100644 index 0000000..97fbe8c --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + Document + + + + + +
+

Particles

+
+ + diff --git a/script/main.js b/script/main.js new file mode 100644 index 0000000..3687332 --- /dev/null +++ b/script/main.js @@ -0,0 +1,86 @@ +window.onload = function () { + var Particle = /** @class */ (function () { + function Particle(x, y, color, radius) { + this.vx = 0; + this.vy = 0; + this.x = x; + this.y = y; + this.color = color; + this.radius = radius; + } + Particle.prototype.draw = function () { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); + ctx.fillStyle = this.color; + ctx.fill(); + }; + Particle.DEFAULT_RADIUS = 2.5; + return Particle; + }()); + var canvas = document.querySelector("canvas"); + var ctx = canvas.getContext("2d"); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + window.addEventListener("resize", function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }); + var particles = []; + function randomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + function makeGroup(number, color, options) { + var _a; + var group = []; + for (var i = 0; i < number; i++) { + var particle = new Particle(randomInt(0, canvas.width), randomInt(0, canvas.height), color, (_a = options.radius) !== null && _a !== void 0 ? _a : Particle.DEFAULT_RADIUS); + group.push(particle); + particles.push(particle); + } + return group; + } + function interaction(group1, group2, options) { + var _a, _b, _c; + if (options === void 0) { options = {}; } + options.g = (_a = options.g) !== null && _a !== void 0 ? _a : 0.1; + options.maxDistance = (_b = options.maxDistance) !== null && _b !== void 0 ? _b : 100; + options.minDistance = (_c = options.minDistance) !== null && _c !== void 0 ? _c : 0; + for (var i = 0; i < group1.length; i++) { + var fx = 0; + var fy = 0; + for (var j = 0; j < group2.length; j++) { + var a = group1[i]; + var b = group2[j]; + var dx = a.x - b.x; + var dy = a.y - b.y; + var d = Math.sqrt(dx * dx + dy * dy); + if (d > 0 && d < options.maxDistance && d > options.minDistance) { + var F = options.g / d; + fx += F * dx; + fy += F * dy; + } + a.vx = (a.vx + fx) * 0.5; + a.vy = (a.vy + fy) * 0.5; + a.x += a.vx * 0.005; + a.y += a.vy * 0.005; + } + } + } + var blue = makeGroup(5000, "#0000ff", { radius: 1 }); + var red = makeGroup(1000, "#ff0000", { radius: 2.5 }); + var green = makeGroup(50, "#00ff00", { radius: 4 }); + function animate() { + interaction(red, red, { g: -0.05, maxDistance: 250 }); + interaction(green, red, { g: -0.1, maxDistance: 500 }); + interaction(red, green, { g: -0.05, maxDistance: 250 }); + interaction(blue, red, { g: -0.4, maxDistance: 250 }); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#000000"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + particles.forEach(function (particle) { + particle.draw(); + }); + requestAnimationFrame(animate); + } + animate(); +}; diff --git a/script/main.ts b/script/main.ts new file mode 100644 index 0000000..1c7756a --- /dev/null +++ b/script/main.ts @@ -0,0 +1,130 @@ +window.onload = function () { + class Particle { + public x: number; + public y: number; + public radius: number; + public color: string; + public vx: number = 0; + public vy: number = 0; + + public static DEFAULT_RADIUS = 2.5; + + public constructor(x: number, y: number, color: string, radius: number) { + this.x = x; + this.y = y; + this.color = color; + this.radius = radius; + } + + public draw() { + ctx.beginPath(); + ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); + ctx.fillStyle = this.color; + ctx.fill(); + } + } + + const canvas = document.querySelector("canvas") as HTMLCanvasElement; + const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + window.addEventListener("resize", () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + }); + + const particles: Particle[] = []; + + function randomInt(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + + function makeGroup( + number: number, + color: string, + options: { radius?: number } + ) { + const group: Particle[] = []; + + for (let i = 0; i < number; i++) { + const particle = new Particle( + randomInt(0, canvas.width), + randomInt(0, canvas.height), + color, + options.radius ?? Particle.DEFAULT_RADIUS + ); + + group.push(particle); + particles.push(particle); + } + + return group; + } + + type Options = { + minDistance?: number; + maxDistance?: number; + g?: number; + }; + + function interaction( + group1: Particle[], + group2: Particle[], + options: Options = {} + ) { + options.g = options.g ?? 0.1; + options.maxDistance = options.maxDistance ?? 100; + options.minDistance = options.minDistance ?? 0; + + for (let i = 0; i < group1.length; i++) { + let fx = 0; + let fy = 0; + + for (let j = 0; j < group2.length; j++) { + let a = group1[i]; + let b = group2[j]; + + const dx = a.x - b.x; + const dy = a.y - b.y; + const d = Math.sqrt(dx * dx + dy * dy); + + if (d > 0 && d < options.maxDistance && d > options.minDistance) { + const F = options.g / d; + fx += F * dx; + fy += F * dy; + } + + a.vx = (a.vx + fx) * 0.5; + a.vy = (a.vy + fy) * 0.5; + a.x += a.vx * 0.005; + a.y += a.vy * 0.005; + } + } + } + + const blue = makeGroup(5000, "#0000ff", { radius: 1 }); + const red = makeGroup(1000, "#ff0000", { radius: 2.5 }); + const green = makeGroup(50, "#00ff00", { radius: 4 }); + + function animate() { + interaction(red, red, { g: -0.05, maxDistance: 250 }); + interaction(green, red, { g: -0.1, maxDistance: 500 }); + interaction(red, green, { g: -0.05, maxDistance: 250 }); + interaction(blue, red, { g: -0.4, maxDistance: 250 }); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + ctx.fillStyle = "#000000"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + particles.forEach((particle) => { + particle.draw(); + }); + + requestAnimationFrame(animate); + } + + animate(); +}; diff --git a/style/main.css b/style/main.css new file mode 100644 index 0000000..2798f58 --- /dev/null +++ b/style/main.css @@ -0,0 +1,29 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Roboto", sans-serif; + overflow: hidden; +} + +.gui { + position: absolute; + top: 10px; + left: 10px; + z-index: 100; + background: rgba(255, 255, 255, 0.0666666667); + color: #ffffff; + padding: 15px; + border-radius: 5px; +} +.gui .label { + text-transform: uppercase; + font-size: 12px; + font-weight: 700; + color: rgba(255, 255, 255, 0.5333333333); +} + +/*# sourceMappingURL=main.css.map */ diff --git a/style/main.css.map b/style/main.css.map new file mode 100644 index 0000000..ed8bc90 --- /dev/null +++ b/style/main.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA","file":"main.css"} \ No newline at end of file diff --git a/style/main.scss b/style/main.scss new file mode 100644 index 0000000..8e16800 --- /dev/null +++ b/style/main.scss @@ -0,0 +1,28 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Roboto", sans-serif; + overflow: hidden; +} + +.gui { + position: absolute; + top: 10px; + left: 10px; + z-index: 100; + background: #ffffff11; + color: #ffffff; + padding: 15px; + border-radius: 5px; + + .label { + text-transform: uppercase; + font-size: 12px; + font-weight: 700; + color: #ffffff88; + } +}