excaliburjs / Excalibur

🎮 Your friendly TypeScript 2D game engine for the web 🗡️
https://excaliburjs.com
BSD 2-Clause "Simplified" License
1.83k stars 192 forks source link

Allow for a Graphics Tiling Effect #2744

Open eonarheim opened 1 year ago

eonarheim commented 1 year ago

See comment

https://github.com/excaliburjs/Excalibur/discussions/2621#discussioncomment-5634534

Discussed in https://github.com/excaliburjs/Excalibur/discussions/2621

Originally posted by **KokoDoko** April 15, 2023 In CSS and in javascript canvas, there's an option to create an infinitely repeating pattern. [See this link](https://developer.mozilla.org/en-US/docs/Web/API/CanvasPattern/setTransform). I struggled a while to find out how to apply this in Excalibur, I eventually came up with this solution! ``` export class InfiniteAnimation extends Actor { offsetx = 0 pattern constructor(pos) { super({ width: 300, height: 300 }) this.pos = new Vector(10,10) } onInitialize(engine) { const img = Resources.YourImage.data const canvas = new Canvas({ cache: false, draw: (ctx) => this.drawPattern(ctx) }) this.pattern = canvas.ctx.createPattern(img, "repeat") this.graphics.use(canvas) } drawPattern(ctx) { this.offsetx-- const matrix = new DOMMatrix().translate(this.offsetx, 0) this.pattern.setTransform(matrix) ctx.fillStyle = this.pattern ctx.fillRect(0, 0, 300, 300) } } ```
eonarheim commented 1 year ago

It's not 100% clear yet what the API would look like

github-actions[bot] commented 1 year ago

This issue hasn't had any recent activity lately and is being marked as stale automatically.

mattjennings commented 11 months ago

For reference on possible API, PixiJS has a TilingSprite component

eonarheim commented 11 months ago

@mattjennings Thanks, this is a great reference. I might be able to sneak tiling into the existing API into the option bag constructor? Potentially if the repeat flag is set on a dimension, it will tile if the destSize is greater than the sourceView's corresponding dimension?

export interface SpriteOptions {
  /**
   * Image to create a sprite from
   */
  image: ImageSource;
  /**
   * By default the source is the entire dimension of the [[ImageSource]]
   */
  sourceView?: { x: number; y: number; width: number; height: number };
  /**
   * By default the size of the final sprite is the size of the [[ImageSource]]
   */
  destSize?: { width: number; height: number };

  repeatX: boolean;
  repeatY: boolean;
}
eonarheim commented 11 months ago

Perhaps a new type would be best tho... tileScale and tilePosition seem pretty useful.

mattjennings commented 11 months ago

Hmm. To be honest I'm not entirely sure what tileScale is as opposed to regular scale, and what tilePosition would do as opposed to changing the sprite origin. This is the rendering logic for those properties if that sheds any light.

My thinking is if you had repeatX and repeatY properties, I could manipulate the sprite scale & origin to achieve the same effect as tileScale and tilePosition... but it's probably not the most intuitive. Also not sure if origin wraps in Excalibur? tilePosition would have that behaviour if not, and could justify that property.

github-actions[bot] commented 9 months ago

This issue hasn't had any recent activity lately and is being marked as stale automatically.

eonarheim commented 7 months ago

We accidentally implemented this feature (see discord conversation for full context https://discord.com/channels/1195771303215513671/1229051072027562074)

https://github.com/excaliburjs/Excalibur/assets/612071/8844704a-ac60-4c3f-b65b-2d63d5337834

The gist for sprites, is if the ImageWrapping.Repeat is set AND you have a source view bigger than the natural width/height of the image source it will tile!

var game = new ex.Engine({
  canvasElementId: 'game',
  width: 600,
  height: 400,
  displayMode: ex.DisplayMode.FitScreenAndFill,
  pixelArt: true
  // antialiasing: false
});

var tex = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png', {
  wrapping: ex.ImageWrapping.Repeat
});

var loader = new ex.Loader([tex]);

var sprite = new ex.Sprite({
  image: tex,
  sourceView: {
    x: 0,
    y: 0,
    width: 500,
    height: 500
  },
  destSize: {
    width: 1000,
    height: 1000
  }
});
var actor = new ex.Actor({ 
  x: 0, y: 0, 
  anchor: ex.vec(0, 0),
  coordPlane: ex.CoordPlane.Screen,
  z: -10
});
actor.onInitialize = () => {
  actor.graphics.add(sprite);
};
actor.onPostUpdate = (engine, delta) => {
  sprite.sourceView.x += .05 * delta;
}
game.add(actor);
game.start(loader);

game.currentScene.camera.pos = actor.pos;

Current hack for animation tiling

this.backgroundAnim = Resources.Background.getAnimation('default')!;
  // Terrible terrible to enable animation tiling
  for (let frame of this.backgroundAnim.frames) {
      const sprite = (frame.graphic as Sprite);
      sprite.image.wrapping = { x: ImageWrapping.Repeat, y: ImageWrapping.Repeat };
      sprite.image.image.setAttribute('wrapping-x', ImageWrapping.Repeat);
      sprite.image.image.setAttribute('wrapping-y', ImageWrapping.Repeat);
      sprite.image.image.setAttribute('forceUpload', 'true');
      sprite.sourceView.width *= 5;
      sprite.sourceView.height *= 5;
      sprite.destSize.width *= 5;
      sprite.destSize.height *= 5;
  }
github-actions[bot] commented 1 month ago

This issue hasn't had any recent activity lately and is being marked as stale automatically.