Source: models/coin.class.js

// coin.class.js

/**
 * @class Coin
 * @extends CollectibleObject
 *
 * Represents a collectible coin in the game.
 * Can be picked up by the player to increase the coin bar progress.
 * Reaching 100% restores some of the player's health.
 */
class Coin extends CollectibleObject {
  IMAGES = ["assets/img/8_coin/coin_1.png", "assets/img/8_coin/coin_2.png"];

  /**
   * Array of image paths for the coin's animation frames.
   * Used to alternate between two images for a simple spinning effect.
   * @type {string[]}
   */
  offset = {
    top: 16,
    bottom: 16,
    left: 16,
    right: 16,
  };

  /**
   * Width of the coin in pixels.
   * Affects how large the coin is rendered.
   * @type {number}
   */
  width = 70;

  /**
   * Height of the coin in pixels.
   * Affects how large the coin is rendered.
   * @type {number}
   */
  height = 70;

  frameCount = 0;

  /**
   * Creates a new coin instance at the specified position.
   * Loads all required images and initializes animation after world is ready.
   *
   * @param {number} x - Horizontal position of the coin on the canvas.
   * @param {number} y - Vertical position of the coin on the canvas.
   */
  constructor(x, y) {
    super(x, y);
    this.loadImage(this.IMAGES[0]);
    this.loadImages(this.IMAGES);
    this.initCoinLoops();
  }

  /**
   * Initializes the coin's animation loop once the world is ready.
   * Uses a recursive setTimeout to wait for world initialization.
   */
  initCoinLoops() {
    if (!worldIsReady) {
      setTimeout(() => {
        this.initCoinLoops();        
      }, 200);
    } else {
      this.animate();
    }
  }

  /**
   * Starts the animation loop for the coin, synchronized with the display refresh rate.
   * Updates the coin's animation every 10 frames when in sync with world timing.
   * Uses requestAnimationFrame for smooth rendering and optimal performance.
   */
  animate() {
    const updateEvery = 10;

    if (world.isInSync()) {
      if (this.frameCount === 0) {
        this.playAnimation(this.IMAGES);
      }

      this.frameCount = (this.frameCount + 1) % updateEvery;
    }

    setStoppableRAF(() => this.animate());
  }
}