首页 > 解决方案 > 在 p5.js 中,是否可以在 setup 或 preload 函数中加载 gif,然后在其他地方多次使用该 gif?

问题描述

我有一个班级(我们称之为“敌人”),我希望他们在足够近的时候攻击我(它会显示一个动画 gif,看起来像咬人)。

我已经完成了所有这些工作,除了我能弄清楚的唯一方法是将 loadImage("attack.gif") 放在类中。这真的很快,因为每次有敌人产生时,它都必须重新加载那个 gif。

我尝试在课堂上使用 setup() 中加载的 gif,但他们的所有攻击都是同步的。

还有另一种方法可以做到这一点吗?

标签: imagegifp5.js

解决方案


您可以预加载 gif(或多个 gif)并将它们存储在一个数组中,以后可以重复使用该数组在多个位置绘制(例如,在您的情况下是多个生成的敌人)。

这是一个演示的基本示例:

  • 将图像加载到数组中
  • 使用加载的图像在屏幕上(重新)生成对象

let images;
let enemies = [];

function preload(){
  // load images and store them in an array
  images = [
 loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAPFBMVEX///8AAADf399fX19/f38/Pz+/v78fHx+fn58XFxeHh4cPDw+np6fX19fHx8evr6+3t7dvb2/39/dPT08jn1NmAAAA+UlEQVR4nO3ZSRKCMBBAUQnzPHj/u7qwOwtS0QhBwfpviaH9bDTG2w0AAAAAgNNLE2HC7zF6T0oAAQT8Q0CTC1M8Df61gywxek8TIcAq5anKXUsIIICACwaMmWhlept5OUvGCAH2o3iLqN8FBBBAwJcDJt3U1Dqsk+1O538/Z0mtU6aPAypnuu4JjfNKwJKKAAIIuEzAi+lxvPuFSQABBJwnoHY2JFu4U4ID7GYqzpYs+KiLAAIIOHPA7D+aWZsPCdh1Wk4AAQRcJ2CRQ5ai1yu9XrnnK/YfqWb9SuqfsoQ/xpoehSf+x1PHHtcTQAABPwsAAAAAAOAYD7+UFrmL/czUAAAAAElFTkSuQmCC"),  loadImage("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAclBMVEX///8AAAAMDAwUFBRtbW20tLTKysrr6+s0NDSMjIzo6OgrKytzc3Py8vJKSkolJSWSkpIODg5jY2OlpaW7u7ubm5urq6tEREQaGhr29vZRUVHg4ODDw8Pc3Nw/Pz9VVVWAgIDU1NQ3NzdcXFx6enovLy+wzkUiAAACjElEQVR4nO2a63qCMAxAlU5QEEEUuSMivP8rjptS2iid0qmfOT/XfM0BHGlLZjMEQRAE+ShMXQBTXn79aAgQ6tIE4u1cgG0sTWAvJrBHARRAART4bIFEvUm8Fsk/nxe3p1BH60RskJsIXX+FcnsKkp5GBAQv8nGyEQFHtoCDAiiAAihwP38gXyAAE592LbYhW6B0u1QJnX9nKS2y09d0mTIqv5n+R2KGH0pAW6IACqAACqDAewtYGwaPQGHEY+OsaQRK1WTJoLiMC1PLSQToyI4FULeVBR/3M4nA+nGBeztNFEABFEABcQFgM7WDBHZc2N2NnrgAcVYMWQ7F5Rkb54BF688CkkABFEABFEABWsDcvEBgUGRjm8YdAthtHTbItR2gQp7Z8kQh3mWw4if2ND5M83jPaVoZAIEN0CcCPEciTSCFBPg7IFEAeATAcas8gRz6DfDLJKLKEli+WqAEPgEDbzN5AgYgoPOnEfIELEiA/9YhT8CHBPgTIYkC0TsK+FzYdh8EgWma2lMNdsH5OqHihS1r6E1YhOGxxuurUn4+hhsvTcOxtgUItVg3OH3jRB4FLZrbDfbYwawb1IHy6T4gYPPTeJcL3/Obvv4fg7plV+yJBYDt+TsJHL9eIESBFwtAi/wvE+BXp1MJXFfjQF8fJXD2LYYSOEMcoB449JXCYl3f6JpD2EFqoaxHbLu51hRFU6tp/xIldEGtnho7HzmcFixU00WgsoP3692iKopVVczzZVka1S3xfVJQw8B6Ejj3fAbg49HI9hwFUAAFUOB/BYDDDeUwqUDBCwyO6dzSal7QPcY027oL6pJQk9fJjOEVdjVKjxqSJAE2f0+h13M2XCok3NiIIMgX8guBhVuAILp8rwAAAABJRU5ErkJggg=="),
     ];
}

function setup(){
  createCanvas(600, 600);
  imageMode(CENTER);
}

function draw(){
  background(255);
  // update + draw enemies
  for(let i = 0; i < enemies.length; i++){
    enemies[i].update();
  }
  text("click to spawn", 10, 15);
}

function mousePressed(){
  // pick a random image
  let randomImage = images[int(random(images.length))];
  // make a new enemy
  enemies.push(new Enemy(randomImage, mouseX, mouseY));
}

class Enemy {
  constructor(skin, x, y) {
    this.skin = skin;
    this.position = createVector(x, y);
    this.velocity = createVector(random(-1,1), random(-1,1));
  }
  update(){
    // add velocity
    this.position.add(this.velocity);
    // check borders and flip direction (roughly)
    if(this.position.x < 0 || this.position.x > width ||
       this.position.y < 0 || this.position.y > height){
       this.velocity.mult(-1);
    }
    // render
    push();
    blendMode(MULTIPLY);
    image(this.skin, this.position.x, this.position.y);
    pop();
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>

在上面的示例中,我使用 base64 编码图像来避免 CORS 问题,但是您应该可以加载自定义图像。这个想法是全局存储图像,以便稍后在实例化新敌人时重新使用它们。(如果你有几个图像,每个图像都应该使用单独的变量,否则数组会更容易管理)

注意在Enemy构造函数中只是接收对先前加载的图像的引用:

// when declared
constructor(skin, x, y) {
    this.skin = skin;
// when intantiated
new Enemy(randomImage, mouseX, mouseY)

剩下要做的就是根据需要渲染图像:

image(this.skin, this.position.x, this.position.y);

更新基于评论和共享代码,每个敌人共享相同的 gif,该 gif 以相同的速率以相同的帧数同步更新。

一种选择是images用同一图像的多个副本填充数组,基本上每个敌人重新加载一次 gif,尽管这似乎很浪费。

不幸的是p5.Image.get(),返回当前帧的快照,并且没有神奇的函数来克隆加载的 gif,但是未记录的gifProperties保存了手动执行此操作所需的数据:

let images;
let enemies = [];

function preload(){
  // load images and store them in an array
  images = [
    loadImage("data:image/gif;base64,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"),
  ];
}

function setup(){
  createCanvas(600, 600);
  imageMode(CENTER);
}

function draw(){
  background(255);
  // update + draw enemies
  for(let i = 0; i < enemies.length; i++){
    enemies[i].update();
  }
  text("click to spawn", 10, 15);
}

function mousePressed(){
  // pick a random image
  let randomImage = images[int(random(images.length))];
  // offset the start frame of each enemy by one
  let startFrame = enemies.length % randomImage.numFrames();
  // make a new enemy
  enemies.push(new Enemy(cloneGif(randomImage,startFrame), mouseX, mouseY));
}

function cloneGif(gif, startFrame){
  let gifClone = gif.get();
  // access original gif properties
  gp = gif.gifProperties;
  // make a new object for the clone
  gifClone.gifProperties = {
    displayIndex: gp.displayIndex,
    // we still point to the original array of frames
    frames: gp.frames,
    lastChangeTime: gp.lastChangeTime,
    loopCount: gp.loopCount,
    loopLimit: gp.loopLimit,
    numFrames: gp.numFrames,
    playing: gp.playing,
    timeDisplayed: gp.timeDisplayed
  };
  // optional tweak the start frame
  gifClone.setFrame(startFrame);
  
  return gifClone;
}

class Enemy {
  constructor(skin, x, y) {
    this.skin = skin;
    this.x = x;
    this.y = y;
  }
  update(){
    image(this.skin, this.x, this.y);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>

生成一个新对象(这cloneGif意味着新的起始帧等),但是它应该指向原始 gif 的frames(保存像素 / ImageData

现在每个敌人都从一个新的帧开始。

我注意到更改延迟似乎会影响所有敌人:一旦将延迟设置为 1,似乎底层图像数据会以相同的速率复制。

如果您需要不同的延迟时间,您仍然可以管理访问相同的 gif frame ImageData,但不是依靠p5.Image控制 gif 播放(例如setFrame()/ delay()),您需要手动管理它并渲染到 p5 的画布:

let images;
let enemies = [];

function preload(){
  // load images and store them in an array
  images = [
    loadImage("data:image/gif;base64,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"),
  ];
}

function setup(){
  createCanvas(600, 600);
  imageMode(CENTER);
}

function draw(){
  background(255);
  // update + draw enemies
  for(let i = 0; i < enemies.length; i++){
    enemies[i].update();
  }
  text("click to spawn", 10, 15);
}

function mousePressed(){
  // pick a random image
  let randomImage = images[int(random(images.length))];
  // make a new enemy
  let enemy = new Enemy({frames: randomImage.gifProperties.frames, gifWidth: randomImage.width, gifH: randomImage.height}, mouseX, mouseY);
  // pick a random start frame
  enemy.currentFrame = int(random(images.length));
  // pick a random per gif frame delay (e.g. the larger the number the slower the gif will play)
  enemy.frameDelay = int(random(40, 240));
  enemies.push(enemy);
}

class Enemy {
  constructor(gifData, x, y) {
    this.frames = gifData.frames;
    this.offX = -int(gifData.gifWidth * 0.5);
    this.offY = -int(gifData.gifHeight * 0.5);
    this.currentFrame = 0;
    this.numFrames = this.frames.length;
    this.frameDelay = 100;
    this.lastFrameUpdate = millis();
    this.x = x;
    this.y = y;
  }
  
  update(){
    let millisNow = millis();
    // increment frame
    if(millisNow - this.lastFrameUpdate >= this.frameDelay){
      this.currentFrame = (this.currentFrame + 1) % this.numFrames;  
      this.lastFrameUpdate = millisNow;
    }
     // render directly to p5's canvas context
 drawingContext.putImageData(this.frames[this.currentFrame].image, this.x + this.offX, this.y + this.offY);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.0/p5.min.js"></script>

还要注意 p5.Image 在透明度方面做得很好(即使原始 gif 缺少它):如果采用这种较低级别的路线,您需要手动解决这些问题。


推荐阅读