(function UMD(name, context, definition) {
if (typeof define === "function" && define.amd) {
else if (typeof module !== "undefined" && module.exports) {
module.exports = definition();
context[name] = definition(name, context);
})("deePool", this, function DEF(name, context) {
const EMPTY_SLOT = Object.freeze(Object.create(null));
function create(objectFactory = () => ({})) {
if (nextFreeSlot == null || nextFreeSlot == objPool.length) {
grow(objPool.length || 5);
var objToUse = objPool[nextFreeSlot];
objPool[nextFreeSlot++] = EMPTY_SLOT;
if (nextFreeSlot == null || nextFreeSlot == -1) {
objPool[objPool.length] = obj;
objPool[--nextFreeSlot] = obj;
function grow(count = objPool.length) {
if (count > 0 && nextFreeSlot == null) {
var curLen = objPool.length;
objPool.length += Number(count);
for (var i = curLen; i < objPool.length; i++) {
objPool[i] = objectFactory();
class GameMessageSystem {
this.messageWindow = new GameMessageWindow(3);
this.displayingMessage = '';
this.currentFrameMessageList = [];
this.currentFrameMessageList.push(message);
if (this.currentFrameMessageList.length >= 1) {
this.messageLog.push(this.currentFrameMessageList.join(' '));
this.messageWindow.updateMessage(this.messageLog);
this.currentFrameMessageList.length = 0;
this.messageWindow.draw();
class GameMessageWindow {
constructor(messageLineCount, windowColor = color(0, 192), textColor = color(224)) {
this.messageLineCount = messageLineCount;
this.windowWidth = width;
this.windowHeight = (1 + messageLineCount) * textLeading();
this.windowColor = windowColor;
this.textColor = textColor;
this.windowTopPositionY = height - this.windowHeight;
this.marginWidth = textWidth('M');
this.textAreaWidth = this.windowWidth - 2 * this.marginWidth;
this.textLeadingValue = textLeading();
this.currentMessage = '';
this.renderedGraphics = createGraphics(this.windowWidth, this.windowHeight);
this.renderedGraphics.blendMode(BLEND);
this.renderedGraphics.noStroke();
this.renderedGraphics.textFont(textFont(), textSize());
this.renderedGraphics.textLeading(textLeading());
updateMessage(messageLog) {
const messageCount = Math.min(this.messageLineCount, messageLog.length);
const temporalArray = subset(messageLog, messageLog.length - messageCount, messageCount);
for (let i = 0, len = temporalArray.length; i < len; i += 1) {
const wordList = split(temporalArray[i], ' ');
if (wordList.length === 0)
temporalLine = wordList[0];
for (let k = 1, kLen = wordList.length; k < kLen; k += 1) {
if (textWidth(temporalLine + ' ' + wordList[k]) > this.textAreaWidth) {
lineList.push(temporalLine);
temporalLine = wordList[k];
temporalLine += ' ' + wordList[k];
lineList.push(temporalLine);
const displayingLineCount = Math.min(this.messageLineCount, lineList.length);
= subset(lineList, lineList.length - displayingLineCount, displayingLineCount).join('\n');
this.renderedGraphics.clear();
this.renderedGraphics.fill(this.windowColor);
this.renderedGraphics.rect(0, 0, this.windowWidth, this.windowHeight);
this.renderedGraphics.fill(this.textColor);
this.renderedGraphics.text(this.currentMessage, this.marginWidth, (0.25 + 1) * this.textLeadingValue);
image(this.renderedGraphics, 0, this.windowTopPositionY);
constructor(runFunction) {
this.properFrameCount = 0;
this.runFunction = runFunction;
function prepareGameStates() {
gameStates.normal = new GameState((state) => {
if (myStructure.ideaQueue.length >= 5 || state.properFrameCount > 60 * 60) {
if (Math.random() < 0.1) {
currentGameState = gameStates.encounterBoss;
gameMessageSystem.pushMessage('Large structure incoming.');
else if (enemyStructureGroup.structureSet.length < myStructure.ideaQueue.length / 2) {
if (Math.random() < 0.03)
if (myStructureGroup.structureSet.length === 0) {
gameMessageSystem.pushMessage('You lost all Ideas.');
currentGameState = gameStates.gameOver;
state.properFrameCount += 1;
gameStates.encounterBoss = new GameState((state) => {
if (state.properFrameCount === 240) {
createNonMovingEffect(0.5 * width, 0.15 * height, color(0), 300 * unitLength, 60, drawBossShadow);
else if (state.properFrameCount === 300) {
screenShake.set(10 * unitLength);
const structureParameter = {
mainColor: enemyStructureGroup.mainColor,
initialIdeas: ideaSet.popRandomSet(16, true),
if (structure.properFrameCount % 420 < 240 && structure.properFrameCount > 30) {
structure.isBlocking = false;
structure.isBlocking = true;
enemyStructureGroup.addStructure(new Structure(structureParameter));
currentGameState = gameStates.boss;
if (myStructureGroup.structureSet.length === 0) {
gameMessageSystem.pushMessage('You lost all Ideas.');
currentGameState = gameStates.gameOver;
state.properFrameCount += 1;
gameStates.boss = new GameState((state) => {
if (enemyStructureGroup.structureSet.length === 0) {
gameMessageSystem.pushMessage('The enemy lost all Ideas.');
currentGameState = gameStates.gameOver;
if (myStructureGroup.structureSet.length === 0) {
gameMessageSystem.pushMessage('You lost all Ideas.');
currentGameState = gameStates.gameOver;
gameStates.gameOver = new GameState((state) => {
if (state.properFrameCount === 0) {
gameMessageSystem.pushMessage('Game over. Press X key to reset.');
if (keyIsDown(KEY_CODE_X)) {
state.properFrameCount += 1;
currentGameState = gameStates.normal;
class CrossReferenceArray extends Array {
return Object.create(CrossReferenceArray.prototype);
element.belongingArray = this;
function updateSprites(array) {
for (let i = array.length - 1; i >= 0; i -= 1) {
function drawSprites(array) {
for (let i = array.length - 1; i >= 0; i -= 1) {
function distSq(v1, v2) {
return sq(v2.x - v1.x) + sq(v2.y - v1.y);
this.position = createVector();
this.velocity = createVector();
this.displayColor = color(random(255), random(255), random(255));
this.drawSprite = (sprite) => {
fill(sprite.displayColor);
ellipse(sprite.position.x, sprite.position.y, sprite.displaySize, sprite.displaySize);
this.belongingArray = null;
this.belongingArray = null;
this.behaviorList.length = 0;
this.destructionBehavior = null;
this.collisionRadius = 10;
this.isRotatable = false;
this.rotationVelocity = 0;
this.lifespanFrameCount = null;
this.properFrameCount = 0;
if (this.destructionBehavior)
this.destructionBehavior.run(this);
if (this.belongingArray) {
const index = this.belongingArray.indexOf(this, 0);
this.belongingArray.splice(index, 1);
setVelocity(speed, directionAngle) {
this.velocity.x = speed * cos(directionAngle);
this.velocity.y = speed * sin(directionAngle);
fitRotationToVelocity() {
this.rotationAngle = this.velocity.heading();
this.position.add(this.velocity);
this.rotationAngle += this.rotationVelocity;
this.velocity.mult(1 - this.friction);
if (this.behaviorList.length >= 1) {
for (const eachBehavior of this.behaviorList) {
if (this.behaviorList.length === 0)
this.properFrameCount += 1;
if (this.lifespanFrameCount && this.properFrameCount >= this.lifespanFrameCount) {
if (!this.lifespanFrameCount)
return this.properFrameCount / this.lifespanFrameCount;
const marginLength = this.displaySize + (margin || 0);
if (this.position.x < -marginLength)
if (this.position.x > width + marginLength)
if (this.position.y < -marginLength)
if (this.position.y > height + marginLength)
overlap(other, handleCollision) {
if (distSq(this.position, other.position) > sq(this.collisionRadius + other.collisionRadius)) {
handleCollision(this, other);
collide(other, handleCollision) {
const distanceSquared = distSq(this.position, other.position);
const collisionDistance = this.collisionRadius + other.collisionRadius;
if (distanceSquared > sq(collisionDistance)) {
const displacement = useNewVector(other.position.x - this.position.x, other.position.y - this.position.y).normalize().mult(-0.5 * (collisionDistance - sqrt(distanceSquared)));
other.position.sub(displacement);
other.position.sub(displacement);
else if (other.immovable) {
this.position.add(displacement);
this.position.add(displacement);
this.position.add(displacement);
other.position.sub(displacement);
vectorPool.recycle(displacement);
handleCollision(this, other);
bounce(other, handleCollision) {
if (!this.collide(other))
const direction = useNewVector(other.position.x - this.position.x, other.position.y - this.position.y).normalize().mult(-1);
const relativeVelocity = useNewVector(other.velocity.x - this.velocity.x, other.velocity.y - this.velocity.y);
const velocityChangeMagnitude = p5.Vector.dot(relativeVelocity, direction);
const velocityChange = direction.mult(velocityChangeMagnitude);
this.velocity.add(velocityChange);
other.velocity.sub(velocityChange);
vectorPool.recycle(direction);
vectorPool.recycle(relativeVelocity);
handleCollision(this, other);
const relativePosition = useNewVector(other.position.x - this.position.x, other.position.y - this.position.y);
const magnitude = factor / Math.min(1, relativePosition.magSq());
const direction = relativePosition.normalize();
const acceleration = direction.mult(magnitude);
this.velocity.add(acceleration);
other.velocity.sub(acceleration);
attractToPoint(x, y, factor) {
const relativePosition = useNewVector(x - this.position.x, y - this.position.y);
const magnitude = factor / Math.min(1, relativePosition.magSq());
const direction = relativePosition.normalize();
const acceleration = direction.mult(magnitude);
this.velocity.add(acceleration);
this.fireDirectionType = 0 ;
this.fireSpeed = 800 * unitSpeed;
this.bulletBehavior = null;
this.createBullets = Idea.defaultBulletPattern;
this.fireIntervalFrameCount = 6;
this.belongingStructure = Structure.nullObject;
const dummySprite = new Sprite();
this.node1 = dummySprite;
this.node2 = dummySprite;
this.node3 = dummySprite;
this.isPersistent = false;
this.removingAction = (idea) => { };
this.structureLevelEffect = {
continuousHealing: false,
static initializeStatic() {
this.defaultBulletPattern = (idea, directionAngle) => {
const strong = idea.fireCount % 8 === 0;
idea.createBullet(strong, directionAngle);
const newIdea = new Idea('');
const properties = Object.keys(this);
for (let i = 0, len = properties.length; i < len; i += 1) {
newIdea[properties[i]] = this[properties[i]];
setFireDirectionType(type) {
this.fireDirectionType = type;
setBulletBehavior(behavior) {
this.bulletBehavior = behavior;
setBulletPattern(pattern) {
this.createBullets = pattern;
setFireInterval(frames) {
this.fireIntervalFrameCount = Math.max(1, Math.floor(frames));
this.isPersistent = true;
setStructureLevelEffect(name) {
this.structureLevelEffect[name] = true;
setRemovingAction(action) {
this.removingAction = action;
if (this.belongingStructure.properFrameCount % this.fireIntervalFrameCount === 0) {
this.createBullets(this, directionAngle);
createBullet(strong, directionAngle, speedFactor = 1, offsetX = 0, offsetY = 0) {
const newBullet = useNewBullet();
newBullet.position.set(this.belongingStructure.position.x + offsetX, this.belongingStructure.position.y + offsetY);
newBullet.setVelocity(speedFactor * this.fireSpeed, directionAngle);
newBullet.fitRotationToVelocity();
newBullet.drawSprite = drawBullet;
const group = this.belongingStructure.parentGroup;
newBullet.damagePoint = 50;
newBullet.graphics = group.strongShotBulletGraphics;
newBullet.damagePoint = 5;
newBullet.graphics = group.weakShotBulletGraphics;
newBullet.behaviorList.push(this.bulletBehavior);
this.belongingStructure.parentGroup.addBullet(newBullet);
getFireDirectionAngle() {
if (!this.belongingStructure)
const structure = this.belongingStructure;
switch (this.fireDirectionType) {
return structure.parentGroup.facingDirectionAngle;
return Math.random() * TWO_PI;
return noise(0.01 * frameCount) * TWO_PI + HALF_PI;
if (structure.nearestEnemy) {
return getDirectionAngle(structure.position, structure.nearestEnemy.position);
return structure.parentGroup.facingDirectionAngle;
if (structure.farestEnemy) {
return getDirectionAngle(structure.position, structure.farestEnemy.position);
return structure.parentGroup.facingDirectionAngle;
return (node === this.node1 || node === this.node2 || node === this.node3);
setBelongingStructure(structure) {
this.belongingStructure = structure;
const nodeCount = structure.nodeSprites.length;
this.node1 = structure.nodeSprites[Math.floor(Math.random() * (nodeCount - 2))];
this.node2 = structure.nodeSprites[nodeCount - 2];
this.node3 = structure.nodeSprites[nodeCount - 1];
this.updateDefaultAimDirectionAngle();
updateDefaultAimDirectionAngle() {
if (this.bulletBehavior instanceof BulletGoForwardBehavior &&
this.belongingStructure &&
this.belongingStructure.parentGroup) {
this.bulletBehavior = new BulletGoForwardBehavior(this.belongingStructure.parentGroup.facingDirectionAngle);
this.node1.bounce(this.node2);
this.node2.bounce(this.node3);
this.node3.bounce(this.node1);
triangle(this.node1.position.x, this.node1.position.y, this.node2.position.x, this.node2.position.y, this.node3.position.x, this.node3.position.y);
this.defaultFireDirectionSubsetArray = [];
this.nonDefaultFireDirectionSubsetArray = [];
this.structureLevelEffect = {
continuousHealing: false,
static initializeStatic() {
this.defaultFireDirectionOffsetAngleArray = [
[radians(-10), radians(10)],
[radians(-120), 0, radians(120)],
[radians(-110), radians(-15), radians(15), radians(110)],
this.updateStructureLevelEffect();
this.updateSubsetArrays();
const len = this.array.length;
let removedIdea = undefined;
for (let i = 0; i < len; i += 1) {
if (!this.array[i].isPersistent) {
removedIdea = this.array.splice(i, 1)[0];
removedIdea = this.array.splice(0, 1)[0];
this.updateStructureLevelEffect();
this.updateSubsetArrays();
this.defaultFireDirectionSubsetArray.length = 0;
this.nonDefaultFireDirectionSubsetArray.length = 0;
for (const eachIdea of this.array) {
if (eachIdea.fireDirectionType === 0 ) {
this.defaultFireDirectionSubsetArray.push(eachIdea);
this.nonDefaultFireDirectionSubsetArray.push(eachIdea);
updateStructureLevelEffect() {
for (const eachIdea of this.array) {
const properties = Object.keys(this.structureLevelEffect);
for (const property of properties) {
this.structureLevelEffect[property]
= this.structureLevelEffect[property] | eachIdea.structureLevelEffect[property];
for (const eachIdea of this.array) {
if (eachIdea.name === name)
for (const eachIdea of this.array) {
if (eachIdea.refers(node))
return this.array.length;
for (const eachIdea of this.array) {
for (const eachIdea of this.array) {
this.fireDefaultFireDirectionIdeas(directionAngle);
this.fireNonDefaultFireDirectionIdeas();
fireDefaultFireDirectionIdeas(directionAngle) {
const len = this.defaultFireDirectionSubsetArray.length;
for (let i = len - 1; i >= 0; i -= 1) {
this.defaultFireDirectionSubsetArray[i].fire(directionAngle + IdeaQueue.defaultFireDirectionOffsetAngleArray[len][i]);
const fireDirectionAngleInterval = TWO_PI / len;
let fireDirectionAngle = directionAngle;
for (let i = len - 1; i >= 0; i -= 1) {
this.defaultFireDirectionSubsetArray[i].fire(fireDirectionAngle);
fireDirectionAngle += fireDirectionAngleInterval;
fireNonDefaultFireDirectionIdeas() {
const len = this.nonDefaultFireDirectionSubsetArray.length;
for (let i = len - 1; i >= 0; i -= 1) {
this.nonDefaultFireDirectionSubsetArray[i].fire(this.nonDefaultFireDirectionSubsetArray[i].getFireDirectionAngle());
setDefaultAimDirectionAngle() {
for (let i = this.array.length - 1; i >= 0; i -= 1) {
this.array[i].updateDefaultAimDirectionAngle();
for (let i = this.array.length - 1; i >= 0; i -= 1) {
ideaSet.push(this.array[i]);
constructor(initialIdeas) {
arrayCopy(initialIdeas, this.array);
this.updateSubsetArray();
this.updateSubsetArray();
const selectedIdea = this.array.splice(index, 1)[0];
this.updateSubsetArray();
popRandomSet(ideaCount, rare) {
for (let i = 0; i < ideaCount; i += 1) {
const newIdea = this.popRandom(rare);
this.rareSubsetArray = this.array.filter((idea) => { return idea.isRare; });
this.nonRareSubsetArray = this.array.filter((idea) => { return !idea.isRare; });
if (rare === undefined) {
if (this.array.length === 0)
return this.pop(Math.floor(Math.random() * this.array.length));
const subsetArray = rare ? this.rareSubsetArray : this.nonRareSubsetArray;
if (subsetArray.length === 0)
const selectedIdea = subsetArray[Math.floor(Math.random() * subsetArray.length)];
return this.pop(this.array.indexOf(selectedIdea));
class FloatingIdea extends Sprite {
constructor(idea, previousStructure) {
this.acceleration = createVector();
this.position.set(previousStructure.position.x, previousStructure.position.y);
const directionAngle = previousStructure.parentGroup.facingDirectionAngle + random(-HALF_PI, HALF_PI);
const speed = 400 * unitSpeed;
this.setVelocity(speed, directionAngle);
this.displaySize = 30 * unitLength;
this.drawSprite = FloatingIdea.staticDraw;
this.behaviorList.push(FloatingIdea.staticBehavior);
this.collisionRadius = 20 * unitLength;
this.rotationVelocity = 0.5 * UNIT_ANGLE_VELOCITY;
static initializeStatic() {
this.staticDraw = (sprite) => {
translate(sprite.position.x, sprite.position.y);
rotate(sprite.rotationAngle);
stroke(sprite.displayColor);
fill(colorAlpha(sprite.displayColor, 32));
drawRegularTriangle(sprite.displaySize);
rotate(-sprite.rotationAngle);
translate(-sprite.position.x, -sprite.position.y);
floatingIdea.velocity.add(floatingIdea.acceleration);
let nearestDistanceSquared = 0;
let nearestStructure = null;
for (const eachStructure of floatingIdea.belongingGroup.structureSet) {
const distanceSquared = distSq(floatingIdea.position, eachStructure.position);
if (!nearestStructure || distanceSquared < nearestDistanceSquared) {
nearestDistanceSquared = distanceSquared;
nearestStructure = eachStructure;
distSq(floatingIdea.position, nearestStructure.position) < sq(160 * unitLength)) {
floatingIdea.attract(nearestStructure.coreSprite, 0.2);
if (!floatingIdea.isInScreen()) {
ideaSet.push(floatingIdea.idea);
setBelongingGroup(group) {
this.belongingGroup = group;
this.acceleration.set(3 * cos(group.facingDirectionAngle + PI) * unitSpeed, 3 * sin(group.facingDirectionAngle + PI) * unitSpeed);
this.displayColor = group.mainColor;
class Bullet extends Sprite {
static initializeStatic() {
this.staticBehaviorList = [];
this.staticBehaviorList.push(new DieIfOutOfScreenBehavior(0));
this.staticDestructionBehavior = new BulletDestructionBehavior();
arrayCopy(Bullet.staticBehaviorList, this.behaviorList);
this.collisionRadius = this.displaySize * 0.5;
this.destructionBehavior = Bullet.staticDestructionBehavior;
function initializeObjectPools() {
spritePool = deePool.create(() => { return new Sprite(); });
vectorPool = deePool.create(() => { return createVector(); });
bulletPool = deePool.create(() => { return new Bullet(); });
function useNewVector(x, y) {
const newVector = vectorPool.use();
function cloneVector(v) {
const newVector = vectorPool.use();
function useNewSprite(x, y) {
const newSprite = spritePool.use();
newSprite.position.x = x;
newSprite.position.y = y;
function removeSprite(sprite) {
spritePool.recycle(sprite);
function useNewBullet(x, y) {
const newBullet = bulletPool.use();
newBullet.position.x = x;
newBullet.position.y = y;
function removeBullet(bullet) {
bulletPool.recycle(bullet);
function copySprite(from, to) {
to.position.set(from.position);
to.velocity.set(from.velocity);
to.displaySize = from.displaySize;
to.displayColor = from.displayColor;
to.drawSprite = from.drawSprite;
arrayCopy(from.behaviorList, to.behaviorList);
to.destructionBehavior = from.destructionBehavior;
to.friction = from.friction;
to.collisionRadius = from.collisionRadius;
to.isRotatable = from.isRotatable;
to.rotationAngle = from.rotationAngle;
to.rotationVelocity = from.rotationVelocity;
to.immovable = from.immovable;
to.lifespanFrameCount = from.lifespanFrameCount;
function prepareIdeas() {
const createIdea = (name) => {
const newIdea = new Idea(name);
const copyIdea = (baseIdea, name) => {
const newIdea = baseIdea.clone();
const createRadialBulletPattern = (wayCount) => {
return (idea, directionAngle) => {
const strong = idea.fireCount % 5 === 0;
const angleInterval = TWO_PI / wayCount;
for (let i = 0; i < wayCount; i += 1) {
idea.createBullet(strong, directionAngle + i * angleInterval);
const createLinearBulletPattern = (bulletCount, minSpeedFactor) => {
const speedFactorChange = (1 - minSpeedFactor) / bulletCount;
return (idea, directionAngle) => {
const angle = idea.belongingStructure.parentGroup.facingDirectionAngle + random(-HALF_PI, HALF_PI);
const distance = 0.7 * idea.belongingStructure.shellRadius;
for (let i = 0; i < bulletCount; i += 1) {
idea.createBullet(i === 0, directionAngle, speedFactor, distance * cos(angle), distance * sin(angle));
speedFactor -= speedFactorChange;
.setFireDirectionType(1 )
.setBulletPattern((idea, directionAngle) => { })
.setRemovingAction((idea) => {
gameMessageSystem.pushMessage('Effect of the Idea of emptiness was triggered.');
for (let i = 0; i < 12; i += 1) {
const newIdea = ideaSet.popRandom();
idea.belongingStructure.addIdea(newIdea);
const defaultIdea = new Idea('dummy')
.setFireSpeed(400 * unitSpeed)
.setBulletBehavior(new BulletGoForwardBehavior(HALF_PI));
copyIdea(defaultIdea, 'beauty');
copyIdea(defaultIdea, 'justice');
copyIdea(defaultIdea, 'victory');
copyIdea(defaultIdea, 'truth')
.setStructureLevelEffect('viewInfo');
copyIdea(defaultIdea, 'knowledge')
.setStructureLevelEffect('viewInfo');
copyIdea(defaultIdea, 'civilization');
copyIdea(defaultIdea, 'harmony');
copyIdea(defaultIdea, 'freedom');
copyIdea(defaultIdea, 'life')
.setStructureLevelEffect('continuousHealing');
copyIdea(defaultIdea, 'wealth');
copyIdea(defaultIdea, 'purity');
copyIdea(defaultIdea, 'hope');
copyIdea(defaultIdea, 'order');
.setFireDirectionType(5 )
.setBulletBehavior(new BrakeAccelBehavior(15, 0.15, 30 * unitSpeed))
.setBulletPattern((idea, directionAngle) => {
for (let i = 0; i < 64; i += 1) {
const angle = Math.random() * TWO_PI;
const distance = (Math.random() * 80) * unitLength;
idea.createBullet(true, directionAngle + random(radians(-5), radians(5)), 1, distance * cos(angle), distance * sin(angle));
const deceleration = new DecelerateBehavior(0.02, 200 * unitSpeed);
.setStructureLevelEffect('continuousHealing')
.setFireDirectionType(5 )
.setFireSpeed(600 * unitSpeed)
.setBulletBehavior(deceleration);
.setStructureLevelEffect('viewInfo')
.setFireDirectionType(5 )
.setFireSpeed(800 * unitSpeed)
.setBulletBehavior(deceleration);
const hatred = createIdea('hatred')
.setStructureLevelEffect('continuousDamage')
.setFireDirectionType(4 )
.setFireSpeed(400 * unitSpeed)
.setBulletBehavior(deceleration);
copyIdea(hatred, 'sacrifice');
const revolution = createIdea('revolution')
.setFireDirectionType(5 )
.setFireSpeed(600 * unitSpeed)
.setBulletBehavior(deceleration)
.setBulletPattern(createLinearBulletPattern(7, 0.2));
copyIdea(revolution, 'rebellion')
.setFireDirectionType(4 );
sprite.velocity.rotate((noise(0.1 * sprite.properFrameCount) - 0.5) * 0.01 * TWO_PI);
sprite.fitRotationToVelocity();
const insanity = createIdea('insanity')
.setFireDirectionType(3 )
.setBulletBehavior(randomWalk)
.setFireSpeed(100 * unitSpeed)
.setBulletPattern(createRadialBulletPattern(3));
copyIdea(insanity, 'chaos');
const peace = createIdea('peace')
.setFireDirectionType(5 )
.setBulletBehavior(new BrakeAccelBehavior(15, 0.1, 20 * unitSpeed));
copyIdea(peace, 'silence')
.setBulletBehavior(new BrakeAccelBehavior(20, 0.1, 20 * unitSpeed));
copyIdea(peace, 'oblivion')
.setBulletBehavior(new BrakeAccelBehavior(25, 0.1, 20 * unitSpeed));
.setStructureLevelEffect('continuousHealing');
const solitude = createIdea('solitude')
.setFireDirectionType(4 )
.setBulletBehavior(new BrakeAccelBehavior(30, 0.1, 20 * unitSpeed));
copyIdea(solitude, 'sorrow')
.setBulletBehavior(new BrakeAccelBehavior(35, 0.1, 20 * unitSpeed));
copyIdea(solitude, 'sin')
.setBulletBehavior(new BrakeAccelBehavior(40, 0.1, 20 * unitSpeed));
const complexFire = (idea, directionAngle) => {
if (!idea.belongingStructure)
const strong = idea.fireCount % 14 === 0;
const angle = ((0.3 * frameCount) / IDEAL_FRAME_RATE) * TWO_PI;
const angle2 = -((1.1 * frameCount) / IDEAL_FRAME_RATE) * TWO_PI;
const distance2 = 30 * unitLength;
const distance = (idea.belongingStructure) ? idea.belongingStructure.shellRadius : 1
idea.createBullet(strong, directionAngle, 1, distance * cos(angle) + distance2 * cos(angle2), distance * sin(angle) + distance2 * sin(angle2));
createIdea('ephemerality')
.setFireDirectionType(1 )
.setBulletBehavior(new BrakeAccelBehavior(60, 0.1, 9 * unitSpeed))
.setFireSpeed(200 * unitSpeed)
.setBulletPattern(complexFire);
.setFireDirectionType(5 )
.setBulletBehavior(new BrakeAccelBehavior(15, 0.1, 6 * unitSpeed))
.setFireSpeed(100 * unitSpeed)
.setBulletPattern(complexFire);
.setFireDirectionType(4 )
.setBulletBehavior(new AccelerateBehavior(3 * unitSpeed))
.setFireSpeed(1 * unitSpeed)
.setBulletPattern(complexFire);
.setFireDirectionType(3 )
.setBulletBehavior(nullBehavior)
.setFireSpeed(160 * unitSpeed)
.setBulletPattern(createRadialBulletPattern(7));
.setFireDirectionType(5 )
.setBulletBehavior(nullBehavior)
.setFireSpeed(400 * unitSpeed)
.setBulletPattern((idea, directionAngle) => {
const strong = idea.fireCount % 10 === 0;
const angleInterval = radians(2);
for (let i = -1; i < 2; i += 1) {
idea.createBullet(strong, directionAngle + i * angleInterval);
.setFireDirectionType(2 )
.setBulletBehavior(new BrakeAccelBehavior(15, 0.15, 3 * unitSpeed))
.setFireSpeed(160 * unitSpeed)
.setBulletPattern(createRadialBulletPattern(5));
const createSideGunsPattern = (distance) => {
return (idea, directionAngle) => {
const strong = idea.fireCount % 8 === 0;
idea.createBullet(strong, directionAngle, 1, -distance);
idea.createBullet(strong, directionAngle, 1, +distance);
.setStructureLevelEffect('protection')
.setFireDirectionType(1 )
.setBulletBehavior(deceleration)
.setBulletPattern(createSideGunsPattern(15 * unitLength));
.setStructureLevelEffect('protection')
.setFireDirectionType(1 )
.setBulletBehavior(deceleration)
.setBulletPattern(createSideGunsPattern(30 * unitLength));
.setStructureLevelEffect('continuousHealing')
.setFireDirectionType(1 )
.setBulletBehavior(deceleration)
.setBulletPattern(createSideGunsPattern(45 * unitLength));
p5.disableFriendlyErrors = true;
const SKETCH_NAME = 'CollapsingIdeas';
const USE_WEB_FONT = false;
const IDEAL_FRAME_RATE = 60;
const UNIT_ANGLE_VELOCITY = (2 * Math.PI) / IDEAL_FRAME_RATE;
const ONE_AND_HALF_PI = 1.5 * Math.PI;
const ROOT_THREE = 1.73205080757;
const KEY_CODE_SPACE = 32;
const effectSpriteSet = CrossReferenceArray.create();
const fontPath = 'Ubuntu-Regular.ttf';
const fontName = 'Ubuntu';
const keyDown = Array(100).fill(false);
this.offsetX = Math.random() * this.value;
this.offsetY = Math.random() * this.value;
translate(this.offsetX, this.offsetY);
this.value = this.value * 0.95;
this.value = Math.max(this.value, value);
translate(-this.offsetX, -this.offsetY);
rect(0, 0, width, height);
this.value -= this.valueChange;
set(value, durationSeconds) {
this.valueChange = value / (durationSeconds * IDEAL_FRAME_RATE);
function colorAlpha(c, alphaValue) {
return color(red(c), green(c), blue(c), alpha(c) * alphaValue / 255);
function getDirectionAngle(from, to) {
return atan2(to.y - from.y, to.x - from.x);
function drawRegularTriangle(shapeSize) {
triangle(0, (-2 / 3) * shapeSize, -(1 / ROOT_THREE) * shapeSize, (1 / 3) * shapeSize, +(1 / ROOT_THREE) * shapeSize, (1 / 3) * shapeSize);
function drawNode(sprite) {
fill(sprite.displayColor);
ellipse(sprite.position.x, sprite.position.y, sprite.displaySize, sprite.displaySize);
function drawCore(sprite) {
stroke(sprite.displayColor);
fill(colorAlpha(sprite.displayColor, 32));
ellipse(sprite.position.x, sprite.position.y, sprite.displaySize, sprite.displaySize);
function drawBullet(sprite) {
const position = sprite.position;
translate(position.x, position.y);
rotate(sprite.rotationAngle + HALF_PI);
image(sprite.graphics, 0, 0);
rotate(-(sprite.rotationAngle + HALF_PI));
translate(-position.x, -position.y);
function drawParticle(sprite) {
const position = sprite.position;
stroke(colorAlpha(sprite.displayColor, 255 * (1 - sprite.getProgressRatio())));
translate(position.x, position.y);
rotate(sprite.rotationAngle);
drawRegularTriangle(sprite.displaySize);
rotate(-sprite.rotationAngle);
translate(-position.x, -position.y);
function drawBossShadow(sprite) {
const progressRatio = sprite.getProgressRatio();
fill(colorAlpha(sprite.displayColor, progressRatio * 64));
const angleInterval = TWO_PI / 5;
const angleOffset = (frameCount / 60) * TWO_PI;
const distance = (1 - progressRatio) * sprite.displaySize;
const diameter = progressRatio * sprite.displaySize;
for (let i = 0; i < 5; i += 1) {
const offsetX = distance * cos(i * angleInterval + angleOffset);
const offsetY = distance * sin(i * angleInterval + angleOffset);
ellipse(sprite.position.x + offsetX, sprite.position.y + offsetY, 0.7 * diameter, 0.7 * diameter);
ellipse(sprite.position.x + offsetX, sprite.position.y + offsetY, diameter, diameter);
function createDrawRiplleFunction(startSizeFactor, endSizeFactor, startStrokeWeight, startFillAlpha) {
const sizeFactorChange = endSizeFactor - startSizeFactor;
const progressRatio = sprite.getProgressRatio();
const fadeRatio = 1 - progressRatio;
const diameter = (startSizeFactor + sizeFactorChange * (pow(progressRatio - 1, 5) + 1)) * sprite.displaySize;
stroke(colorAlpha(sprite.displayColor, fadeRatio * 255));
strokeWeight(fadeRatio * startStrokeWeight * unitLength);
fill(colorAlpha(sprite.displayColor, fadeRatio * startFillAlpha));
ellipse(sprite.position.x, sprite.position.y, diameter, diameter);
strokeWeight(1 * unitLength);
const drawExpandingRipple = createDrawRiplleFunction(0, 1, 2, 16);
const drawShrinkingRipple = createDrawRiplleFunction(1, 0, 1, 16);
class DieIfOutOfScreenBehavior {
if (!sprite.isInScreen(this.margin)) {
if (sprite instanceof Bullet) {
class BulletGoForwardBehavior {
constructor(directionAngle) {
this.directionAngle = directionAngle;
this.acceleration = 10 * unitSpeed;
this.triggerFrameCount = 4;
if (sprite.properFrameCount === this.triggerFrameCount) {
sprite.setVelocity(sprite.velocity.mag(), this.directionAngle);
sprite.rotationAngle = this.directionAngle;
if (sprite.properFrameCount > this.triggerFrameCount) {
sprite.velocity.setMag(sprite.velocity.mag() + this.acceleration);
class AccelerateBehavior {
constructor(acceleration) {
this.acceleration = acceleration;
sprite.velocity.setMag(sprite.velocity.mag() + this.acceleration);
class DecelerateBehavior {
constructor(friction, terminalSpeed) {
this.decelerationFactor = 1 - friction;
this.terminalSpeed = terminalSpeed;
this.terminalSpeedSquared = sq(terminalSpeed);
const speedSquared = sprite.velocity.magSq();
if (speedSquared > this.terminalSpeedSquared)
sprite.velocity.mult(this.decelerationFactor);
if (speedSquared < this.terminalSpeedSquared)
sprite.velocity.setMag(this.terminalSpeed);
class BrakeAccelBehavior {
constructor(frameCountThreshold, friction, acceleration) {
this.frameCountThreshold = frameCountThreshold;
this.acceleration = acceleration;
this.decelerationFactor = 1 - friction;
if (sprite.properFrameCount < this.frameCountThreshold) {
sprite.velocity.mult(this.decelerationFactor);
sprite.velocity.setMag(sprite.velocity.mag() + this.acceleration);
class BulletDestructionBehavior {
if (!sprite.isInScreen())
createParticles(sprite.position.x, sprite.position.y, 360 * unitSpeed, 10, 0.5 * IDEAL_FRAME_RATE, 3);
const dieIfOutOfScreen = new DieIfOutOfScreenBehavior(0);
function createParticles(x, y, maxSpeed, particleSize, lifespan, particleCount) {
const particleColor = color(128);
for (let i = 0; i < particleCount; i += 1) {
const newParticle = useNewSprite(x, y);
const speed = (0.2 + 0.8 * Math.random()) * maxSpeed;
const directionAngle = Math.random() * TWO_PI;
newParticle.setVelocity(speed, directionAngle);
newParticle.displaySize = particleSize;
newParticle.displayColor = particleColor;
newParticle.drawSprite = drawParticle;
newParticle.behaviorList.push(dieIfOutOfScreen);
newParticle.friction = 0.05;
newParticle.isRotatable = true;
newParticle.rotationAngle = 0;
newParticle.rotationVelocity = 1 * UNIT_ANGLE_VELOCITY;
newParticle.lifespanFrameCount = lifespan;
effectSpriteSet.add(newParticle);
function createNonMovingEffect(x, y, effectColor, effectSize, lifespan, effectDrawFunction) {
const newEffect = useNewSprite(x, y);
newEffect.velocity.set(0, 0);
newEffect.displayColor = effectColor;
newEffect.displaySize = effectSize;
newEffect.drawSprite = effectDrawFunction;
newEffect.lifespanFrameCount = lifespan;
effectSpriteSet.add(newEffect);
const defaultEnemyAction = {
const frame = structure.properFrameCount;
structure.coreSprite.velocity.y += 5 * unitSpeed;
structure.coreSprite.velocity.y = 400 * unitSpeed;
structure.coreSprite.velocity.y += 50 * unitSpeed;
if (structure.position.y >= 1.5 * height)
if (ideaSet.nonRareSubsetArray.length < 2)
const enemy = new Structure({
x: random(0.2, 0.8) * width,
mainColor: enemyStructureGroup.mainColor,
initialIdeas: ideaSet.popRandomSet(2, false),
action: defaultEnemyAction,
enemyStructureGroup.addStructure(enemy);
currentFont = loadFont(fontPath);
node = window.document.getElementById(SKETCH_NAME);
const canvasSize = getCanvasSize();
canvas = createCanvas(canvasSize.x, canvasSize.y);
frameRate(IDEAL_FRAME_RATE);
IdeaQueue.initializeStatic();
Structure.initializeStatic();
FloatingIdea.initializeStatic();
Bullet.initializeStatic();
updateSprites(effectSpriteSet);
drawSprites(effectSpriteSet);
enemyStructureGroup.act();
myStructureGroup.update();
enemyStructureGroup.update();
enemyStructureGroup.draw();
myStructureGroup.collide(enemyStructureGroup);
enemyStructureGroup.collide(myStructureGroup);
gameMessageSystem.update();
gameMessageSystem.draw();
unitLength = getCanvasSize().x / 640;
unitSpeed = unitLength / IDEAL_FRAME_RATE;
strokeWeight(1 * unitLength);
currentFontSize = 14 * unitLength;
textFont(USE_WEB_FONT ? fontName : currentFont, currentFontSize);
textLeading(currentFontSize * 1.7);
gameMessageSystem = new GameMessageSystem();
ideaSet = new IdeaSet(prepareIdeas());
myStructureGroup = new StructureGroup(color(32, 32, 128), ONE_AND_HALF_PI, 1);
highSpeed: 120 * unitSpeed,
lowSpeed: 30 * unitSpeed,
structure.isBlocking = true;
structure.isBlocking = false;
if (keyDown[LEFT_ARROW]) {
structure.coreSprite.velocity.x -= speed;
if (keyDown[RIGHT_ARROW]) {
structure.coreSprite.velocity.x += speed;
structure.coreSprite.velocity.y -= speed;
if (keyDown[DOWN_ARROW]) {
structure.coreSprite.velocity.y += speed;
if (keyDown[KEY_CODE_Z]) {
myStructure = new Structure({
mainColor: myStructureGroup.mainColor,
initialIdeas: ideaSet.popRandomSet(3, false),
const constrainPlayerMove = {
const radius = myStructure.coreRadius;
sprite.position.x = constrain(sprite.position.x, radius, width - radius);
sprite.position.y = constrain(sprite.position.y, radius, height - gameMessageSystem.messageWindow.windowHeight - radius);
myStructure.coreSprite.behaviorList.push(constrainPlayerMove);
myStructureGroup.addStructure(myStructure);
enemyStructureGroup = new StructureGroup(color(160, 32, 32), HALF_PI, 0.1);
myStructureGroup.enemyGroup = enemyStructureGroup;
enemyStructureGroup.enemyGroup = myStructureGroup;
const instruction = useNewSprite(0.25 * width, 0.5 * height);
instruction.velocity.set(0, 0);
instruction.displayColor = color(0);
instruction.drawSprite = (sprite) => {
fill(sprite.displayColor);
text('Z key: Shoot\nSHIFT key: Block\nARROW keys: Move', sprite.position.x, sprite.position.y);
instruction.lifespanFrameCount = 300;
effectSpriteSet.length = 0;
effectSpriteSet.add(instruction);
function windowResized() {
const canvasSize = getCanvasSize();
resizeCanvas(canvasSize.x, canvasSize.y);
function getSketchHolderSize() {
const containerRect = node.getBoundingClientRect();
return { x: containerRect.width, y: containerRect.height };
return { x: windowWidth, y: windowHeight };
function getSquareCanvasSize() {
const sketchHolderSize = getSketchHolderSize();
const sideLength = Math.min(sketchHolderSize.x, sketchHolderSize.y);
function getCanvasSize() {
return getSquareCanvasSize();
window.document.onkeydown = function (event) {
const code = event.which;
if (code === UP_ARROW || code === DOWN_ARROW)
window.document.onkeyup = function (event) {
const code = event.which;
if (code === UP_ARROW || code === DOWN_ARROW)
this.mainColor = params.mainColor;
this.nodeColor = color(0, 192);
this.nodeSprites = CrossReferenceArray.create();
this.coreSprite = new Sprite();
this.coreSprite.position.set(params.x, params.y);
this.coreSprite.velocity.set(0, 0);
this.coreSprite.displayColor = this.mainColor;
this.coreSprite.friction = 0.2;
this.coreSprite.drawSprite = drawCore;
this.coreSprite.immovable = true;
this.setCoreSize(10 * unitLength);
if (!params.nullObject) {
this.action = params.action;
this.action = { run: (s) => { } };
this.isPlayer = params.player || false;
this.ideaQueue = new IdeaQueue();
for (const idea of params.initialIdeas) {
this.nearestEnemy = null;
this.properFrameCount = 0;
this.breakdownRamainingFrameCount = 0;
static initializeStatic() {
this.drawProtectionEffect = createDrawRiplleFunction(1, 2, 1, 0);
this.nullObject = new Structure({ x: 0, y: 0, mainColor: color(0), initialIdeas: [], nullObject: true });
this.parentGroup = group;
this.ideaQueue.setDefaultAimDirectionAngle();
return this.coreSprite.position;
if (this.belongingArray) {
const index = this.belongingArray.indexOf(this, 0);
this.belongingArray.splice(index, 1);
if (this.coreSprite.isInScreen()) {
createNonMovingEffect(this.position.x, this.position.y, this.mainColor, 10 * this.shellSize, 45, drawExpandingRipple);
screenShake.set(this.parentGroup.screenShakeSensitivity * 120 * unitLength);
this.ideaQueue.recycleAllIdeas();
idea.setBelongingStructure(this);
this.ideaQueue.enqueue(idea);
createNonMovingEffect(this.position.x, this.position.y, this.mainColor, 0.1 * (canvasSize - this.shellSize), 120, drawShrinkingRipple);
gameMessageSystem.pushMessage(`You got the Idea of ${idea.name}.`);
const removingIdea = this.ideaQueue.dequeue();
removingIdea.removingAction(removingIdea);
if (Math.random() < 0.5 && this.parentGroup.enemyGroup) {
this.parentGroup.enemyGroup.addFloatingIdea(new FloatingIdea(removingIdea, this));
ideaSet.push(removingIdea);
for (let i = this.nodeSprites.length - 1; i >= 0; i -= 1) {
const eachNode = this.nodeSprites[i];
if (!this.ideaQueue.refers(eachNode)) {
if (this.coreSprite.isInScreen()) {
createNonMovingEffect(this.position.x, this.position.y, this.mainColor, 1.1 * this.shellSize, 30, drawExpandingRipple);
createParticles(this.coreSprite.position.x, this.coreSprite.position.y, 600 * unitSpeed, 20, 1 * IDEAL_FRAME_RATE, 30);
screenShake.set(this.parentGroup.screenShakeSensitivity * 30 * unitLength);
gameMessageSystem.pushMessage(`You lost the Idea of ${removingIdea.name}.`);
this.ideaQueue.fire(this.parentGroup.facingDirectionAngle);
if (this.breakdownRamainingFrameCount === 0) {
else if (this.properFrameCount % 4 <= 1) {
const properShakeValue = 0.25 * this.breakdownRamainingFrameCount;
const offsetX = random(-properShakeValue, properShakeValue);
const offsetY = random(-properShakeValue, properShakeValue);
translate(offsetX, offsetY);
translate(-offsetX, -offsetY);
translate(this.coreSprite.position.x, this.coreSprite.position.y);
drawSprites(this.nodeSprites);
translate(-this.coreSprite.position.x, -this.coreSprite.position.y);
this.drawBlockingEffect();
if (this.breakdownRamainingFrameCount >= 1)
this.breakdownRamainingFrameCount -= 1;
if (this.damagePoint >= 100) {
if (this.ideaQueue.length <= 0) {
this.breakdownRamainingFrameCount = 100;
this.coreSprite.update();
updateSprites(this.nodeSprites);
for (const eachNode of this.nodeSprites) {
const distanceFromOriginSquared = eachNode.position.magSq();
if (distanceFromOriginSquared >= sq(this.shellRadius) ||
distanceFromOriginSquared <= sq(this.coreRadius)) {
eachNode.position.setMag(constrain(sqrt(distanceFromOriginSquared), this.coreRadius, this.shellRadius));
reflectOffCircularBoundary(eachNode);
if (this.ideaQueue.structureLevelEffect.continuousDamage) {
this.damagePoint += 0.05;
if (this.ideaQueue.structureLevelEffect.continuousHealing) {
this.damagePoint = Math.max(0, this.damagePoint - 0.05);
this.updateNearestFarestEnemies();
this.properFrameCount += 1;
handleBulletCollision(bullet) {
if (this.ideaQueue.structureLevelEffect.protection) {
if (Math.random() < 0.2) {
createNonMovingEffect(this.position.x, this.position.y, this.mainColor, this.coreSize, 15, Structure.drawProtectionEffect);
const damageFactor = this.isBlocking ? 0.01 : 1;
this.damagePoint += damageFactor * bullet.damagePoint;
return this.coreSprite.isInScreen(this.shellRadius + (margin || 0));
updateNearestFarestEnemies() {
if (this.properFrameCount % IDEAL_FRAME_RATE !== 0) {
if (this.nearestEnemy && this.nearestEnemy.isRemoved)
this.nearestEnemy = null;
if (this.farestEnemy && this.farestEnemy.isRemoved)
if (!this.parentGroup.enemyGroup)
let foundAnyEnemy = false;
for (const enemy of this.parentGroup.enemyGroup.structureSet) {
const distantceSquared = distSq(this.position, enemy.position);
farestDistSq = distantceSquared;
if (distantceSquared > farestDistSq) {
farestDistSq = distantceSquared;
this.nearestEnemy = nearest;
this.farestEnemy = farest;
this.coreSize = this.coreSprite.displaySize = coreSize;
this.coreRadius = this.coreSprite.collisionRadius = 0.5 * coreSize;
this.shellSize = 2 * coreSize;
this.shellRadius = 0.5 * this.shellSize;
for (const eachSprite of this.nodeSprites) {
const magnitudeSquared = eachSprite.position.magSq();
if (magnitudeSquared < sq(this.coreRadius)) {
eachSprite.position.mult(1.1 * this.coreRadius / sqrt(magnitudeSquared));
if (magnitudeSquared > sq(this.shellRadius)) {
eachSprite.position.mult(0.9 * this.shellRadius / sqrt(magnitudeSquared));
this.setCoreSize(this.coreSize * 1.2);
this.setCoreSize(this.coreSize / 1.2);
const angle = Math.random() * TWO_PI;
const distance = random(this.coreRadius, this.shellRadius);
const newSprite = useNewSprite(distance * cos(angle), distance * sin(angle));
newSprite.setVelocity(30 * unitSpeed, Math.random() * 360);
newSprite.displayColor = this.nodeColor;
newSprite.displaySize = 7 * unitLength;
newSprite.collisionRadius = 0.5 * newSprite.displaySize;
newSprite.drawSprite = drawNode;
this.nodeSprites.add(newSprite);
stroke(192, 255 * (0.5 + 0.5 * sin((0.5 * frameCount / IDEAL_FRAME_RATE) * TWO_PI)));
ellipse(this.coreSprite.position.x, this.coreSprite.position.y, this.shellSize, this.shellSize);
if (myStructure.ideaQueue.structureLevelEffect.viewInfo) {
strokeWeight(3 * unitLength);
translate(this.position.x, this.position.y);
arc(0, 0, this.shellSize, this.shellSize, 0, (1 - (this.damagePoint % 100) / 100) * TWO_PI);
translate(-this.position.x, -this.position.y);
strokeWeight(1 * unitLength);
this.ideaQueue.drawShapes();
const alphaFactor = Math.sin(2 * (this.properFrameCount / IDEAL_FRAME_RATE) * TWO_PI);
stroke(colorAlpha(this.mainColor, 192 + 63 * alphaFactor));
fill(colorAlpha(this.mainColor, 8 + 8 * alphaFactor));
const diameter = this.shellSize + 10 * unitLength;
ellipse(this.position.x, this.position.y, diameter, diameter);
function reflectOffCircularBoundary(sprite) {
const directionFromOrigin = cloneVector(sprite.position).normalize();
sprite.velocity.add(directionFromOrigin.mult(-2 * p5.Vector.dot(sprite.velocity, directionFromOrigin)));
vectorPool.recycle(directionFromOrigin);
constructor(mainColor, facingDirectionAngle, screenShakeSensitivity) {
this.structureSet = CrossReferenceArray.create();
this.bulletSet = CrossReferenceArray.create();
this.floatingIdeaSet = CrossReferenceArray.create();
this.enemyGroup = undefined;
this.mainColor = mainColor;
this.facingDirectionAngle = facingDirectionAngle;
this.screenShakeSensitivity = screenShakeSensitivity;
const graphicsSize = Math.floor(bulletSize * 1.3);
this.weakShotBulletGraphics = createGraphics(graphicsSize, graphicsSize);
const weakGr = this.weakShotBulletGraphics;
weakGr.translate(0.5 * weakGr.width, 0.5 * weakGr.height);
weakGr.stroke(colorAlpha(this.mainColor, 128));
weakGr.triangle(0, -0.7 * bulletSize, -0.3 * bulletSize, 0.3 * bulletSize, +0.3 * bulletSize, 0.3 * bulletSize);
this.strongShotBulletGraphics = createGraphics(graphicsSize, graphicsSize);
const strongGr = this.strongShotBulletGraphics;
strongGr.translate(0.5 * strongGr.width, 0.5 * strongGr.height);
strongGr.fill(this.mainColor);
strongGr.triangle(0, -0.7 * bulletSize, -0.3 * bulletSize, 0.3 * bulletSize, +0.3 * bulletSize, 0.3 * bulletSize);
addStructure(structure) {
this.structureSet.add(structure);
structure.setParentGroup(this);
addBullet(bulletSprite) {
this.bulletSet.add(bulletSprite);
addFloatingIdea(floatingIdea) {
this.floatingIdeaSet.add(floatingIdea);
floatingIdea.setBelongingGroup(this);
for (let i = this.structureSet.length - 1; i >= 0; i -= 1) {
this.structureSet[i].act();
for (let i = this.structureSet.length - 1; i >= 0; i -= 1) {
this.structureSet[i].update();
updateSprites(this.floatingIdeaSet);
updateSprites(this.bulletSet);
for (let i = this.structureSet.length - 1; i >= 0; i -= 1) {
this.structureSet[i].draw();
drawSprites(this.bulletSet);
drawSprites(this.floatingIdeaSet);
this.collideSprites(this.floatingIdeaSet, handleFloatingIdeaCollision);
this.collideSprites(other.bulletSet, handleBulletCollision);
collideSprites(sprites, callBack) {
for (const thisStructure of this.structureSet) {
for (const sprite of sprites) {
if (thisStructure.coreSprite.overlap(sprite)) {
callBack(thisStructure, sprite);
function handleBulletCollision(structure, bullet) {
if (structure.breakdownRamainingFrameCount >= 1)
structure.handleBulletCollision(bullet);
function handleFloatingIdeaCollision(structure, floatingIdea) {
structure.addIdea(floatingIdea.idea);