Hello, I am simulating simple plant (tree-like) organisms for a universe simulation. I have issues with max clone amount, as currently it doesn’t work, and plants often over populate or just go extinct. I want to make sure that no clones can exceed the max amount derived from the original entity.
Start of simulation test run 1 (1 entity died, added 2 new organisms which inherit it’s dna)
Populations over taking almost the full surface of the planets:
It looks like they go extinct at a point, but really they all die, and their clones grow and repeat the same process. I will share the code, just be warned that it is very very long.
var Life = pc.createScript('life');
// DNA attribute as a string with nine characters
Life.attributes.add('dna', { type: 'string', default: '114221200', title: 'DNA' });
// Number of genomes to mutate
Life.attributes.add('mutationRate', { type: 'number', default: 1, title: 'Mutation Rate' });
// Max clones attribute
Life.attributes.add('maxClones', { type: 'number', default: 50, title: 'Max Clones' });
// Materials array
Life.attributes.add('materials', { type: 'asset', assetType: 'material', array: true, title: 'Materials' });
// Entities array for the first genome
Life.attributes.add('entities1', { type: 'entity', array: true, title: 'Entities 1' });
// Entities array for the second genome
Life.attributes.add('entities2', { type: 'entity', array: true, title: 'Entities 2' });
// Position entities array for entities1
Life.attributes.add('posEntities1', { type: 'entity', array: true, title: 'Position Entities 1' });
// Position entities array for entities2
Life.attributes.add('posEntities2', { type: 'entity', array: true, title: 'Position Entities 2' });
Life.prototype.initialize = function() {
this.dnaString = String(this.dna);
this.applyEnvironmentalEffects();
this.applyDnaMaterials();
this.enableAndGrowEntities();
this.startReproduction();
this.startLifespanCountdown();
};
Life.prototype.applyEnvironmentalEffects = function() {
var environmentalEffects = this.getEnvironmentalEffects();
if (environmentalEffects) {
this.adjustDnaForEnvironmentalEffects(environmentalEffects);
}
};
Life.prototype.getEnvironmentalEffects = function() {
var environmentalScript = this.app.root.findByName('EnvironmentalManager').script.environmentalGenomeEffect;
var activeSun = environmentalScript.getActiveSun();
return activeSun ? environmentalScript.sunEffects[activeSun] : null;
};
Life.prototype.adjustDnaForEnvironmentalEffects = function(effects) {
var environmentalManager = this.app.root.findByName('EnvironmentalManager');
var environmentalScript = environmentalManager.script.environmentalGenomeEffect;
var dnaValueForColor = this.dnaString.charAt(6);
var entityColor = environmentalScript.dnaColorMapping[dnaValueForColor];
if (entityColor === effects.favoredColor) {
this.dnaString = this.dnaString.substr(0, 1) +
this.increaseDnaValue(this.dnaString.charAt(1), effects.reproductionRateIncrease) +
this.dnaString.substr(2, 4) +
this.increaseDnaValue(this.dnaString.charAt(6), effects.sizeIncrease) +
this.dnaString.substr(7, 1) +
this.increaseDnaValue(this.dnaString.charAt(8), effects.extraOffspring);
}
};
Life.prototype.increaseDnaValue = function(dnaValue, increaseAmount) {
var newValue = parseInt(dnaValue) + increaseAmount;
if (newValue > 9) newValue = 9;
return newValue.toString();
};
// Add your new functions here
Life.prototype.enableEntities = function() {
this.enableEntitiesByDna(this.entities1, this.dnaString.charAt(9)); // 10th genome for entity1
this.enableEntitiesByDna(this.entities2, this.dnaString.charAt(10)); // 11th genome for entity2
};
Life.prototype.enableEntitiesByDna = function(entitiesArray, dnaValue) {
var index = parseInt(dnaValue, 10) - 1;
entitiesArray.forEach(function(entity, i) {
entity.enabled = (i === index);
});
};
Life.prototype.applyDnaMaterials = function() {
this.applyMaterialByDna(this.entities1, this.dnaString.charAt(0));
this.applyMaterialByDna(this.entities2, this.dnaString.charAt(6));
};
Life.prototype.applyMaterialByDna = function(entitiesArray, dnaValue) {
var materialIndex = parseInt(dnaValue, 10) - 1;
if (materialIndex < 0 || materialIndex >= this.materials.length) {
console.error('Invalid DNA material index: ', materialIndex + 1);
return;
}
var materialAsset = this.materials[materialIndex];
if (!materialAsset || !materialAsset.resource) {
console.error('Material asset not found for DNA: ', dnaValue);
return;
}
entitiesArray.forEach(function(entity) {
if (entity) {
var renders = entity.findComponents('render');
renders.forEach(function(render) {
render.meshInstances.forEach(function(meshInstance) {
meshInstance.material = materialAsset.resource;
});
});
}
});
};
Life.prototype.enableAndGrowEntities = function() {
this.enableEntitiesByDna(this.entities1, this.dnaString.charAt(9));
this.teleportEntityToEnabledPosition(this.entities1, this.posEntities1);
this.enableEntitiesByDna(this.entities2, this.dnaString.charAt(10));
this.teleportEntityToEnabledPosition(this.entities2, this.posEntities2);
};
Life.prototype.enableAndGrowEntities = function() {
// Using the second last character of the DNA string for entities1
this.enableEntitiesByDna(this.entities1, this.dnaString.charAt(this.dnaString.length - 2));
this.teleportEntityToEnabledPosition(this.entities1, this.posEntities1);
// Using the last character of the DNA string for entities2
this.enableEntitiesByDna(this.entities2, this.dnaString.charAt(this.dnaString.length - 1));
this.teleportEntityToEnabledPosition(this.entities2, this.posEntities2);
};
Life.prototype.teleportEntityToEnabledPosition = function(entitiesArray, posArray) {
var enabledPosEntity = posArray.find(function(posEntity) {
return posEntity && posEntity.enabled;
});
if (enabledPosEntity) {
var enabledPos = enabledPosEntity.getPosition();
entitiesArray.forEach(function(entity) {
if (entity) {
entity.setPosition(enabledPos);
}
});
}
};
Life.prototype.enableEntitiesByDna = function(entitiesArray, dnaValue) {
var index = parseInt(dnaValue, 10) - 1;
entitiesArray.forEach(function(entity, i) {
entity.enabled = (i === index);
});
};
Life.prototype.applyGrowth = function(entity, growthDna) {
var growthTargetGenome = parseInt(growthDna, 10);
var scaleFactor = ((growthTargetGenome - 1) / 8) * 0.0008 + 0.0001;
this.scaleEntityOverTime(entity, scaleFactor, 5);
};
Life.prototype.scaleEntityOverTime = function(entity, targetScale, duration) {
var currentWorldScale = entity.getWorldTransform().getScale();
var initialScale = entity.getLocalScale().clone();
var targetLocalScale = new pc.Vec3(
targetScale * initialScale.x / currentWorldScale.x,
targetScale * initialScale.y / currentWorldScale.y,
targetScale * initialScale.z / currentWorldScale.z
);
var startTime = Date.now();
var scaleInterval = setInterval(function() {
var elapsedTime = (Date.now() - startTime) / 1000;
var progress = elapsedTime / duration;
if (progress < 1.0) {
var newScale = new pc.Vec3(
pc.math.lerp(initialScale.x, targetLocalScale.x, progress),
pc.math.lerp(initialScale.y, targetLocalScale.y, progress),
pc.math.lerp(initialScale.z, targetLocalScale.z, progress)
);
entity.setLocalScale(newScale);
} else {
entity.setLocalScale(targetLocalScale);
clearInterval(scaleInterval);
}
}, 1000 / 60);
};
Life.prototype.startReproduction = function() {
var reproductionRateGenome = parseInt(this.dnaString.charAt(2), 10);
var reproductionRate = reproductionRateGenome * 10;
var reproductionAmountGenome = parseInt(this.dnaString.charAt(3), 10);
var reproductionAmount = reproductionAmountGenome;
this.reproductionTimerId = this.setReproductionTimer(reproductionRate, reproductionAmount);
};
Life.prototype.setReproductionTimer = function(rate, amount) {
var self = this;
return setInterval(function() {
if (self.entity && self.entity.parent) {
for (var i = 0; i < amount; i++) {
self.reproduceEntity();
}
}
}, rate * 1000);
};
Life.prototype.reproduceEntity = function() {
var cloneCount = this.entity.parent.findByName(this.entity.name).length;
if (!this.entity || !this.entity.parent || cloneCount >= this.maxClones) {
return;
}
var clone = this.entity.clone();
clone.enabled = false;
this.entity.parent.addChild(clone);
// Get the parent's rotation
var parentRotation = this.entity.getEulerAngles();
// Determine the additional rotation from DNA
var spreadAmountGenome = parseInt(this.dnaString.charAt(5), 10);
var rotationMultiplier = spreadAmountGenome;
var additionalRotation = new pc.Vec3(
(Math.random() * 10 - 5) * rotationMultiplier,
(Math.random() * 10 - 5) * rotationMultiplier,
(Math.random() * 10 - 5) * rotationMultiplier
);
// Combine the rotations
var combinedRotation = parentRotation.add(additionalRotation);
clone.setEulerAngles(combinedRotation);
setTimeout(function() { clone.enabled = true; }, 1000);
var cloneLifeScript = clone.script.life;
if (cloneLifeScript) {
cloneLifeScript.dna = this.mutateDna(this.dna);
}
};
Life.prototype.spreadAmountGenome = function() {
var spreadAmountGenome = parseInt(this.dnaString.charAt(5), 10);
var rotationMultiplier = spreadAmountGenome;
var randomRotation = new pc.Vec3(
(Math.random() * 50 - 5) * rotationMultiplier,
(Math.random() * 50 - 5) * rotationMultiplier,
(Math.random() * 50 - 5) * rotationMultiplier
);
clone.setEulerAngles(randomRotation);
setTimeout(function() { clone.enabled = true; }, 1000);
var cloneLifeScript = clone.script.life;
if (cloneLifeScript) {
cloneLifeScript.dna = this.mutateDna(this.dna);
}
};
Life.prototype.startLifespanCountdown = function() {
var lifespanGenome = parseInt(this.dnaString.charAt(4), 10); // Lifespan
var lifespanDuration = lifespanGenome * 10;
setTimeout(function() {
if (this.entity) {
this.entity.destroy();
}
}.bind(this), lifespanDuration * 1000);
};
Life.prototype.mutateDna = function(dna) {
var dnaArray = dna.split('');
for (var i = 0; i < this.mutationRate; i++) {
var indexToMutate = Math.floor(Math.random() * dnaArray.length);
var currentValue = parseInt(dnaArray[indexToMutate]);
var mutationChance = Math.random();
var mutation = mutationChance < 0.5 ? -1 : 1;
var mutationScale = 0.1 + (this.mutationRate - 1) * 0.1;
var newValue = currentValue + mutation * mutationScale;
if (newValue < 1) newValue = 1;
if (newValue > 9) newValue = 9;
dnaArray[indexToMutate] = Math.round(newValue).toString();
}
return dnaArray.join('');
};
Life.prototype.destroy = function() {
if (this.timerId) {
clearInterval(this.timerId);
}
if (this.reproductionTimerId) {
clearInterval(this.reproductionTimerId);
}
};
If someone could help me ensure no clones are created when the max amount is reached, unless 1 organism dies, and 1 more is born to limit overpopulation. Thanks for reading, you can test the project here.