パーティクルを追加

パーティクルを追加

小さな粒や破片を大量に散らかすことで、様々な効果が得られる。
これは「パーティクル」(直訳:粒子)と呼ばれる

パーティクルを新たなエンティティの一種として追加し、
複数のブロックを動かすのと同じ要領で動作させることができる

一定時間で消えるようにするには

パーティクルエンティティに持たせるデータとして、「時間経過で減少する数値」を増やす。
ここでは life(ライフ)と名付けた

※ 速度については、今回の例では一定スピード&ランダムな角度で、
さらに少し左側に流れるように vx をいじっている

function createParticle(x, y) {
  let direction = random(TWO_PI);
  let speed = 2;

  return {
    x,
    y,
    vx: -2 + speed * cos(direction),
    vy: speed * sin(direction),
    life: 1 // = 100%
  };
}

加えて、次のように関数を用意すれば、
life を徐々に減らしていってゼロになったら削除、といった使い方ができる

function decreaseLife(particle) {
  particle.life -= 0.02;
}

function particleIsAlive(particle) {
  return particle.life > 0;
}

▶ 動かしてみる

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// ---- エンティティ関連の関数 ---------------------------------------------

// 全エンティティ共通

function updatePosition(entity) {
  entity.x += entity.vx;
  entity.y += entity.vy;
}

// プレイヤーエンティティ用

function createPlayer() {
  return {
    x: 200,
    y: 300,
    vx: 0,
    vy: 0
  };
}

function applyGravity(entity) {
  entity.vy += 0.15;
}

function applyJump(entity) {
  entity.vy = -5;
}

function drawPlayer(entity) {
  square(entity.x, entity.y, 40);
}

function playerIsAlive(entity) {
  // プレイヤーの位置が生存圏内なら true を返す。
  // 600 は画面の下端
  return entity.y < 600;
}

// ブロックエンティティ用

function createBlock(y) {
  return {
    x: 900,
    y,
    vx: -2,
    vy: 0
  };
}

function drawBlock(entity) {
  rect(entity.x, entity.y, 80, 400);
}

function blockIsAlive(entity) {
  // ブロックの位置が生存圏内なら true を返す。
  // -100 は適当な値(ブロックが見えなくなる位置であればよい)
  return -100 < entity.x;
}

// パーティクルエンティティ用

function createParticle(x, y) {
  let direction = random(TWO_PI);
  let speed = 2;

  return {
    x,
    y,
    vx: -2 + speed * cos(direction),
    vy: speed * sin(direction),
    life: 1 // = 100%
  };
}

function decreaseLife(particle) {
  particle.life -= 0.02;
}

function particleIsAlive(particle) {
  return particle.life > 0;
}

function drawParticle(particle) {
  push();
  noStroke();
  fill(255, particle.life * 255);
  square(particle.x, particle.y, particle.life * 10);
  pop();
}

// 複数のエンティティを処理する関数

/**
 * 2つのエンティティが衝突しているかどうかをチェックする
 *
 * @param entityA 衝突しているかどうかを確認したいエンティティ
 * @param entityB 同上
 * @param collisionXDistance 衝突しないギリギリのx距離
 * @param collisionYDistance 衝突しないギリギリのy距離
 * @returns 衝突していたら `true` そうでなければ `false` を返す
 */
function entitiesAreColliding(
  entityA,
  entityB,
  collisionXDistance,
  collisionYDistance
) {
  // xとy、いずれかの距離が十分開いていたら、衝突していないので false を返す

  let currentXDistance = abs(entityA.x - entityB.x); // 現在のx距離
  if (collisionXDistance <= currentXDistance) return false;

  let currentYDistance = abs(entityA.y - entityB.y); // 現在のy距離
  if (collisionYDistance <= currentYDistance) return false;

  return true; // ここまで来たら、x方向でもy方向でも重なっているので true
}

// ---- ゲーム全体に関わる部分 --------------------------------------------

/** プレイヤーエンティティ */
let player;

/** ブロックエンティティの配列 */
let blocks;

/** パーティクルエンティティの配列 */
let particles;

/** ゲームの状態。"play" か "gameover" を入れるものとする */
let gameState;

/** ブロックを上下ペアで作成し、`blocks` に追加する */
function addBlockPair() {
  let y = random(-100, 100);
  blocks.push(createBlock(y)); // 上のブロック
  blocks.push(createBlock(y + 600)); // 下のブロック
}

/** ゲームオーバー画面を表示する */
function drawGameoverScreen() {
  background(0, 192); // 透明度 192 の黒
  fill(255);
  textSize(64);
  textAlign(CENTER, CENTER); // 横に中央揃え & 縦にも中央揃え
  text("GAME OVER", width / 2, height / 2); // 画面中央にテキスト表示
}

/** ゲームのリセット */
function resetGame() {
  // 状態をリセット
  gameState = "play";

  // プレイヤーを作成
  player = createPlayer();

  // ブロックの配列準備
  blocks = [];

  // パーティクルの配列準備
  particles = [];
}

/** ゲームの更新 */
function updateGame() {
  // ゲームオーバーなら更新しない
  if (gameState === "gameover") return;

  // ブロックの追加
  if (frameCount % 120 === 1) addBlockPair(blocks); // 一定間隔で追加

  // パーティクルの追加
  particles.push(createParticle(player.x, player.y)); // プレイヤーの位置で

  // 死んだエンティティの削除
  blocks = blocks.filter(blockIsAlive);
  particles = particles.filter(particleIsAlive);

  // 全エンティティの位置を更新
  updatePosition(player);
  for (let block of blocks) updatePosition(block);
  for (let particle of particles) updatePosition(particle);

  // プレイヤーに重力を適用
  applyGravity(player);

  // パーティクルのライフ減少
  for (let particle of particles) decreaseLife(particle);

  // プレイヤーが死んでいたらゲームオーバー
  if (!playerIsAlive(player)) gameState = "gameover";

  // 衝突判定
  for (let block of blocks) {
    if (entitiesAreColliding(player, block, 20 + 40, 20 + 200)) {
      gameState = "gameover";
      break;
    }
  }
}

/** ゲームの描画 */
function drawGame() {
  // 全エンティティを描画
  background(0);
  drawPlayer(player);
  for (let block of blocks) drawBlock(block);
  for (let particle of particles) drawParticle(particle);

  // ゲームオーバー状態なら、それ用の画面を表示
  if (gameState === "gameover") drawGameoverScreen();
}

/** マウスボタンが押されたときのゲームへの影響 */
function onMousePress() {
  switch (gameState) {
    case "play":
      // プレイ中の状態ならプレイヤーをジャンプさせる
      applyJump(player);
      break;
    case "gameover":
      // ゲームオーバー状態ならリセット
      resetGame();
      break;
  }
}

// ---- setup/draw 他 --------------------------------------------------

function setup() {
  createCanvas(800, 600);
  rectMode(CENTER);

  resetGame();
}

function draw() {
  updateGame();
  drawGame();
}

function mousePressed() {
  onMousePress();
}

動かしたり改造したりしてみましょう。

個々のパーティクルの移動や表示を調整すれば、雰囲気が変わってきます。

他の使用例としては、ゲームオーバーになったときに爆発(一度に多数のパーティクルを生成)させるのも良いかもしれません。

その場合、ゲームオーバー状態のときには

  • パーティクルだけ更新する
  • プレイヤーは表示しない

などの変更が必要そうです(簡易的な対処ではありますが)。