Skip to content

Klasa lopte

Ovaj vodič objašnjava upotrebu JavaScript klasa u canvas animacijama i nije namenjen početnicima. Preporučujem da najpre prođete kroz naš odeljak o Lighting Engine-u i isprobate vodič za ciklus boja.

Uz to, ovde neću koristiti strogu sintaksu klasa, jer je to samo “sintaksički šećer” u JavaScript-u i osnovi koncepti ostaju isti. Ako vas zanima složeniji klas, preporučujem posetu ovoj stranici za sveobuhvatan uvod. Inače ostajemo pri osnovama.

Pre svega, klasa je jednostavno regularna JavaScript funkcija. Funkcioniše na isti način, ali dodajemo mogućnost vezivanja vrednosti za određenu instancu funkcije. U ovom primeru kreirao sam loptu koja se ponaša prema osnovnoj fizici. Prati svoju poziciju i brzinu kao i “gravitaciju” koja deluje na nju.

<head>
<title>Simple Ball Bounce</title>
<meta description="Ball bounce"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Canvas variables
var width = 320;
var height = 200;
// X-component of ball position
let ballX = 160;
// Y-component of ball position
let ballY = 100;
// Change in ballY over time. Positive values move down, negative move up
let velocityY = 0;
// Change in velocityY over time.
let accelerationY = 1;
// Effects array
let effects = [];
function update() {
// 1. Overwrite the previous frame
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Make sure there is one instance of the Ball class in the effects array
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterate through the effects array and call each element's draw function
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Declare the Ball class
function Ball(){
// 5. Set instance variables
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Define draw function
this.draw = function(){
// 7. Change the y-velocity by the y-acceleration
this.vy += this.ay;
// 8. If an impact with the ground has been detected, set the velocity upwards and set the impact boolean to false. This check happens before the next function so that we draw the impact frame instead of skipping it.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detect a future impact. If the ball position plus its radius plus its velocity would put it under the floor, we run a few operations -
if (this.by + this.radius + this.vy > 200){
// Set the velocity to 0 so the ball stays where we put it this frame.
this.vy = 0;
// Set impact to true so the next run-through draws the up-bounce
this.impact = true;
// Next frame draws the up-bounce. This frame detected a future collision. If we want to see the impact itself we have to force it. While the ball's outer radius is below the floor, we move the ball up.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Change the y-component by the y-velocity.
this.by += this.vy;
// Draw shape.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Simple Ball Bounce</title>
<meta description="Ball bounce"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Canvas variables
var width = 320;
var height = 200;
// X-component of ball position
let ballX = 160;
// Y-component of ball position
let ballY = 100;
// Change in ballY over time. Positive values move down, negative move up
let velocityY = 0;
// Change in velocityY over time.
let accelerationY = 1;
// Effects array
let effects = [];
function update() {
// 1. Overwrite the previous frame
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Make sure there is one instance of the Ball class in the effects array
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterate through the effects array and call each element's draw function
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Declare the Ball class
function Ball(){
// 5. Set instance variables
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Define draw function
this.draw = function(){
// 7. Change the y-velocity by the y-acceleration
this.vy += this.ay;
// 8. If an impact with the ground has been detected, set the velocity upwards and set the impact boolean to false. This check happens before the next function so that we draw the impact frame instead of skipping it.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detect a future impact. If the ball position plus its radius plus its velocity would put it under the floor, we run a few operations -
if (this.by + this.radius + this.vy > 200){
// Set the velocity to 0 so the ball stays where we put it this frame.
this.vy = 0;
// Set impact to true so the next run-through draws the up-bounce
this.impact = true;
// Next frame draws the up-bounce. This frame detected a future collision. If we want to see the impact itself we have to force it. While the ball's outer radius is below the floor, we move the ball up.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Change the y-component by the y-velocity.
this.by += this.vy;
// Draw shape.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
// 1. Overwrite the previous frame
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Make sure there is one instance of the Ball class in the effects array
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterate through the effects array and call each element's draw function
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Declare the Ball class
function Ball(){
// 5. Set instance variables
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Define draw function
this.draw = function(){
// 7. Change the y-velocity by the y-acceleration
this.vy += this.ay;
// 8. If an impact with the ground has been detected, set the velocity upwards and set the impact boolean to false. This check happens before the next function so that we draw the impact frame instead of skipping it.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detect a future impact. If the ball position plus its radius plus its velocity would put it under the floor, we run a few operations -
if (this.by + this.radius + this.vy > 200){
// Set the velocity to 0 so the ball stays where we put it this frame.
this.vy = 0;
// Set impact to true so the next run-through draws the up-bounce
this.impact = true;
// Next frame draws the up-bounce. This frame detected a future collision. If we want to see the impact itself we have to force it. While the ball's outer radius is below the floor, we move the ball up.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Change the y-component by the y-velocity.
this.by += this.vy;
// Draw shape.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

Kada savladamo početnu složenost, lakše je dodati naprednija ponašanja. U sledećem primeru dodao sam sudare sa zidovima i uveo neke slučajne varijacije u x-pravcu da bi lopta ostala u pokretu.

<head>
<title>Full Bounce</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
// 1. Added velocityX
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Give x a random initial velocity
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Add x and y-components to the impact boolean for horizontal contact
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Each frame, add a small amount of random velocity in the x-direction to keep momentum going
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. If a horizontal impact is detected, reverse the x-velocity and subtract a small amount of force to prevent an out-of-control ball. Mirroring or adding to the force here can have interesting consequences.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detect a horizontal impact left or right and adjust like we did the y-component.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Change the x- and y-components by their velocity.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Full Bounce</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
// 1. Added velocityX
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Give x a random initial velocity
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Add x and y-compnents to the impact boolean for horizontal contact
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Each frame, add a small amount of random velocity in the x-direction to keep momentum going
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. If a horizontal impact is detected, reverse the x-velocity and subtract a small amount of force to prevent an out-of-control ball. Mirroring or adding to the force here can have interesting consequences.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detect a horizontal impact left or right and adjust like we did the y-component.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Change the x- and y-components by their velocity.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Give x a random initial velocity
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Add x and y-compnents to the impact boolean for horizontal contact
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Each frame, add a small amount of random velocity in the x-direction to keep momentum going
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. If a horizontal impact is detected, reverse the x-velocity and subtract a small amount of force to prevent an out-of-control ball. Mirroring or adding to the force here can have interesting consequences.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detect a horizontal impact left or right and adjust like we did the y-component.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Change the x- and y-components by their velocity.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

Moram biti iskren: ovaj primer je Složen sa velikim S i verovatno zahteva više truda nego što je potrebno za većinu osnovnih efekata. Da ne bih previše komentarisao kod, ovde ćemo proći kroz neke nezgodne koncepte.

Prvo: kako lopta zna da će se sudariti sa drugom? Svaki frejm svaka instanca klase lopte mora proveravati poziciju svake druge instance lopte. Poređenjem njihovih pozicija možemo utvrditi da li se dve lopte nalaze jedna unutar poluprečnika druge, a zatim obrnuti njihove x i y brzine prema tome.

Ponekad lopta može dobiti dovoljno brzine da prođe kroz drugu loptu i gotovo zauzme isti prostor. Matematika za prilagođavanje pozicija obe lopte bila bi previše složena, pa jednostavno uklanjam instancu lopte koju trenutno proveravamo. Pošto funkcija update osigurava minimalni broj lopti, biće kreirana nova instanca lopte i simulacija se nastavlja.

Konačno: kada treba izračunati promenu brzine i pozicije za svaku loptu? Da li je važno da li se lopta crta pre ili posle ažuriranja promenljivih? Problem u programiranju je što postoji više odgovora na oba pitanja. Da bih se nosio s tim, uveo sam nešto drugačiji metod praćenja sudara. Na vama je da izaberete preferirani, ali obe metode mogu i rade.

<head>
<title>Multiple Balls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Increase the number of balls present in the effects array
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Add a unique ID to each ball so we can compare them to each other and not themselves. We have also removed "impactX" and "impactY" in favor of a better solution for more balls.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Add a "ceiling" to the canvas in case a ball collision causes the height to exceed 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterate through each ball in the effects array to check for collisions
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// If the ball we are checking against is this ball, skip this cycle
if (this.id == ele.id){
continue;
}
// If the balls are clipping too much, remove this ball from the canvas
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// If the balls will collide, reverse their velocities and subtract some force to account for elasticity
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) && Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Because complicated calcualtions for the velocities happen before this point, finalize the x- and y-coordinates by making sure they are bounded within the canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Multiple Balls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Increase the number of balls present in the effects array
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Add a unique ID to each ball so we can compare them to each other and not themselves. We have also removed "impactX" and "impactY" in favor of a better solution for more balls.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Add a "ceiling" to the canvas in case a ball collision causes the height to exceed 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterate through each ball in the effects array to check for collisions
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// If the ball we are checking against is this ball, skip this cycle
if (this.id == ele.id){
continue;
}
// If the balls are clipping too much, remove this ball from the canvas
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// If the balls will collide, reverse their velocities and subtract some force to account for elasticity
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) && Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Because complicated calcualtions for the velocities happen before this point, finalize the x- and y-coordinates by making sure they are bounded within the canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Increase the number of balls present in the effects array
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Add a unique ID to each ball so we can compare them to each other and not themselves. We have also removed "impactX" and "impactY" in favor of a better solution for more balls.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Add a "ceiling" to the canvas in case a ball collision causes the height to exceed 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterate through each ball in the effects array to check for collisions
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// If the ball we are checking against is this ball, skip this cycle
if (this.id == ele.id){
continue;
}
// If the balls are clipping too much, remove this ball from the canvas
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// If the balls will collide, reverse their velocities and subtract some force to account for elasticity
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) &&
Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Because complicated calculations for the velocities happen before this point, finalize the x- and y-coordinates by making sure they are bounded within the canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

Na kraju dodajemo neke osnovne korisničke kontrole da bi stvar bila zanimljivija. Promene nijanse sam već detaljno razmatrao, pa ćemo se radi jednostavnosti koda fokusirati na podešavanje broja i poluprečnika lopti. Ovo zahteva samo nekoliko izmena postojećih funkcija.

<head>
<title>User Controls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
<!-- Add two numerical sliders: radius and amount --->
<meta property="radius" label="Size" type="number" min="5" max="100" default="20">
<meta property="amount" label="Amount" type="number" min="1" max="50" default="2">
</head>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// Set the minimum effects to be the user-controlled amount. If the length of effects is greater than amount, pop an element off the end.
if (effects.length < amount){
effects.push(new Ball());
} else if (effects.length > amount){
effects.pop();
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
this.draw = function(){
// Finally, set the radius to be the user-controlled radius inside of the draw function each frame.
this.radius = radius;
this.vy += this.ay;
this.vx += Math.sin(Date.now());
// . . . rest of function . . .
}

Nadam se da ste uživali u našem vodiču za animacije sa JavaScript klasama! Čak i na ovom nivou složenosti, ovaj efekat je samo vrh ledenog brega onoga što se može postići sa HTML canvas-om. Pored toga, ja ga još ne bih smatrao gotovim. Ako težimo savršenstvu, primetićete da se sudari lopti detektuju jedan frejm pre stvarnog udarca, što znači da često uopšte ne dođu do sudara. Sudari su takođe pojednostavljeni u pogledu prenosa energije, jer rezultujuća brzina uzima u obzir samo trenutnu brzinu lopte, a ne energiju druge lopte. Konačno, višestruko proveravanje pozicije svake lopte po petlji neefikasno je u pogledu vremenske složenosti i moglo bi se optimizovati sa osnovnom memoizacijom.

Na kraju uvek budite kritični prema svojim efektima. Obično se mogu poboljšati na jedan ili drugi način, a razlika između osrednjeg i odličnog efekta često dolazi samo od gledanja u njega i dozvoljavanja sebi da bar jednom ne bude zadovoljni njim.

Za poslednji vodič korak-po-korak, pogledajte naš odeljak Callbacks, gde prolazim kroz proces preuzimanja podataka merača za aktiviranje i deaktiviranje efekata.