I got a red damage tint/flash working with a shared material across many entities. The trick is to set a custom render instance parameter on each entity’s mesh instances. Then override the shader chunk for the diffuse. feel free to use my code here. Hope this helps someone.
import * as pc from 'playcanvas';
export class DamageTint {
//time to fade back to regular color
static readonly DMG_FADE_DURATION = 0.4;
//max tint amount (1.0 is fully red)
static readonly MaxTintAmount = 0.8; //1.0 is fully red
static showDamageTintOnEntity(entity: pc.Entity) {
this.addTintScriptIfNeeded(entity);
//reset tint timer
(entity.script.get("damageTint") as DamageTintFade).resetTint();
this.applyRedTintToEntity(entity, 1.0);
}
static initializeRedTintShader(material: pc.StandardMaterial) {
if (!(material as any).hasRedTintShader) {
//limit tint to certain materials
if (!material.name.startsWith("MasterMaterial")) {
console.log("Only tint master material");
return;
}
console.warn(`Adding a new shader to material '${material.name}' to handle red tinting`);
// Check if material chunks are available
if (!material.chunks) {
console.log("Material does not have chunks");
return;
}
material.chunks.diffusePS = `
uniform float red_tint_amount;
uniform vec3 material_diffuse;
uniform sampler2D texture_diffuseMap;
void getAlbedo() {
// Start with white as the base color
vec3 baseColor = vec3(1.0, 1.0, 1.0);
#ifdef MAPTEXTURE
vec2 uv = $UV;
baseColor *= gammaCorrectInput(texture2DBias(texture_diffuseMap, uv, textureBias).$CH);
#else
baseColor *= material_diffuse;
#endif
// Apply the red tint only if the red_tint_amount is greater than 0.0
if (red_tint_amount > 0.0) {
baseColor = mix(baseColor, vec3(1.0, 0.0, 0.0), red_tint_amount);
}
dAlbedo = baseColor;
}
`;
//parameter for all instances of this material
material.setParameter('red_tint_amount', 0);
// Mark the material as updated
material.update();
// Mark the material to indicate it has been modified
(material as any).hasRedTintShader = true;
}
}
static applyRedTintToEntity(entity: pc.Entity, amount: number) {
// Find all render components in the entity
var renders: pc.RenderComponent[] = entity.findComponents('render') as pc.RenderComponent[];
for (var i = 0; i < renders.length; ++i) {
var meshInstances = renders[i].meshInstances;
for (var j = 0; j < meshInstances.length; j++) {
var material = meshInstances[j].material as pc.StandardMaterial;
// Ensure the shader has been initialized for this material
this.initializeRedTintShader(material);
// Apply the red tint amount as a per-instance uniform
meshInstances[j].setParameter("red_tint_amount", amount);
meshInstances[j].updateKey(); // This ensures that the instance-specific uniform is applied correctly
}
}
}
// Static method to add the DamageTint script to the entity
static addTintScriptIfNeeded(entity: pc.Entity) {
if (!entity.script) {
entity.addComponent('script');
}
// Check if the entity already has a DamageTint script
if (!entity.script.has('damageTint')) {
entity.script.create('damageTint');
}
}
// Static method to reset the red tint on an entity by finding its material
static resetRedTintOnEntity(entity: pc.Entity) {
this.applyRedTintToEntity(entity, 0.0);
}
}
// The DamageTint script that handles fading the red tint over time
class DamageTintFade extends pc.ScriptType {
private fadeDuration: number;
private elapsedTime: number;
initialize() {
this.fadeDuration = DamageTint.DMG_FADE_DURATION; // Use the standardized fade duration
this.elapsedTime = 0.0;
}
update(dt: number) {
if (this.elapsedTime < this.fadeDuration) {
this.elapsedTime += dt;
let tintAmount = 1.0 - (this.elapsedTime / this.fadeDuration);
tintAmount = pc.math.clamp(tintAmount, 0.0, 1.0) * DamageTint.MaxTintAmount;
DamageTint.applyRedTintToEntity(this.entity, tintAmount);
}
}
resetTint() {
this.elapsedTime = 0.0;
}
}
pc.registerScript(DamageTintFade, 'damageTint');
export default { DamageTintFade, DamageTint };