Aller au contenu

Classe Ball

Ce tutoriel explique comment utiliser les classes JavaScript dans les animations canvas et n’est pas destiné aux débutants. Je recommande de parcourir d’abord notre section Moteur d’éclairage et d’essayer le tutoriel Cycle de Couleurs.

Cela dit, je n’utiliserai pas ici la syntaxe stricte des classes car c’est simplement du “sucre syntaxique” JavaScript, et les concepts sous-jacents restent les mêmes. Si vous souhaitez en savoir plus sur les animations plus complexes, je vous suggère de consulter cette page pour une introduction approfondie. Sinon, restons avec les bases.

Pour commencer, une classe est simplement une fonction JavaScript ordinaire. Elle fonctionne de la même manière, mais nous ajoutons la capacité de lier des valeurs à une instance spécifique de la fonction. Dans cet exemple, j’ai créé une balle qui se comporte selon la physique de base. Elle suit sa position et sa vélocité, ainsi que la “gravité” qui agit sur elle.

<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();
}
}

Une fois que nous avons surmonté la complexité initiale, il est plus facile d’ajouter des comportements plus avancés. Dans l’exemple suivant, j’ai intégré des collisions avec les murs et introduit quelques variations aléatoires dans la direction x pour maintenir la balle en mouvement.

<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();
}
}

Je vais être honnête avec vous, cet exemple est Compliqué avec un grand C, et il nécessite probablement plus d’efforts que ce dont vous auriez besoin pour la plupart des effets de base. Pour m’éviter d’ajouter trop de commentaires dans le code, nous allons passer en revue quelques concepts délicats ici.

Tout d’abord, comment une balle sait-elle qu’elle va entrer en collision avec une autre ? À chaque image, chaque instance de la classe balle doit vérifier la position de chaque autre instance de balle. En comparant leurs positions, nous pouvons déterminer si deux balles sont dans le rayon l’une de l’autre, puis inverser leurs vitesses x et y en conséquence.

Parfois, une balle peut prendre suffisamment de vitesse pour passer à travers une autre balle, les faisant occuper presque le même espace. Les calculs nécessaires pour ajuster la position des deux balles seraient trop complexes, alors je supprime simplement l’instance de balle que nous vérifions actuellement. Puisque la fonction de mise à jour assure un nombre minimum de balles, une nouvelle instance de balle sera créée et la simulation continuera.

Enfin, quand devons-nous calculer le changement de vitesse et de position pour chaque balle ? Est-ce que l’ordre d’exécution (dessiner avant ou après la mise à jour des variables) a de l’importance ? Le problème en programmation est qu’il y a plusieurs réponses aux deux questions. Pour gérer cela, j’ai introduit une méthode légèrement différente pour suivre les collisions. C’est à vous de choisir celle que vous préférez, mais les deux méthodes peuvent et fonctionnent.

<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();
}
}

Enfin, nous allons ajouter quelques contrôles utilisateur de base pour rendre les choses plus intéressantes. J’ai déjà discuté en détail des changements de teinte, donc pour éviter que le code ne devienne encore plus compliqué, nous nous concentrerons sur l’ajustement du nombre et du rayon des balles. Cela ne nécessite que quelques modifications des fonctions existantes.

<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 . . .
}

J’espère que vous avez apprécié notre tutoriel sur les animations de classes JavaScript ! Même à ce niveau de complexité, cet effet n’est que la pointe de l’iceberg en termes de ce que vous pouvez réaliser avec le canvas HTML. De plus, je ne le considère pas encore terminé. Si nous visons la perfection, vous remarquerez que les collisions de balles sont détectées une image avant qu’elles ne se produisent, ce qui signifie qu’elles ne se produisent souvent pas réellement. Les collisions sont également simplifiées en termes de transfert d’énergie, puisque la vitesse résultante ne prend en compte que la vitesse actuelle de la balle et ne tient pas compte de l’énergie de l’autre balle. Enfin, vérifier la position de chaque balle plusieurs fois par boucle est inefficace en termes de complexité temporelle et pourrait être optimisé avec une mémoïsation de base.

En fin de compte, soyez toujours critique de vos effets. Ils peuvent généralement être améliorés d’une façon ou d’une autre, et la différence entre un effet médiocre et un excellent effet vient souvent simplement de le regarder et de vous permettre de le désapprouver au moins une fois.

Pour un dernier tutoriel étape par étape, consultez notre section Callbacks, où je présente le processus de capture de données de compteur pour activer et désactiver les effets.