じ☆ve冰风 发表于 2026-2-28 15:01:35

【MZ插件】CRT显示器扫描线和随机故障等效果

指挥AI写的,效果还不赖,分享出来,功能文档里写的很明白了,不过自己感觉故障效果没那么完美,勉强可以用,MV 不知道能不能用可以自行测试



本来录屏的转gif有点糊,明白我意思就好

JAVASCRIPT 代码下载

/*:
* @target MZ
* @plugindesc CRT 显示器滤镜,包含扫描线、屏幕曲率、暗角、色差和随机故障效果。
* @author enpitsulin
*
* @param scanlineIntensity
* @text 扫描线强度
* @desc 扫描线效果的强度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.35
*
* @param curvatureAmount
* @text 屏幕曲率
* @desc 桶形畸变的程度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.25
*
* @param vignetteIntensity
* @text 暗角强度
* @desc 屏幕边缘暗化的程度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.30
*
* @param chromaticAberration
* @text 色差强度
* @desc RGB 通道分离的程度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.15
*
* @param colorBleeding
* @text 色溢强度
* @desc 红色通道水平扩散的程度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.12
*
* @param glitchFrequency
* @text 故障频率
* @desc 故障效果平均间隔秒数。
* @type number
* @decimals 1
* @min 0
* @default 5.0
*
* @param glitchIntensity
* @text 故障强度
* @desc 故障效果的最大强度 (0.0 - 1.0)。
* @type number
* @decimals 2
* @min 0
* @max 1
* @default 0.60
*
* @param glitchEnabled
* @text 启用故障效果
* @desc 是否启用随机故障效果(手动触发仍然可用)。
* @type boolean
* @default true
*
* @param enabled
* @text 默认启用
* @desc 是否默认启用 CRT 滤镜效果。
* @type boolean
* @default true
*
* @command setEnabled
* @text 设置启用状态
* @desc 启用或禁用 CRT 滤镜效果。
*
* @arg enabled
* @text 启用
* @type boolean
* @default true
*
* @command setGlitchEnabled
* @text 设置故障效果启用状态
* @desc 启用或禁用随机故障效果(手动触发仍然可用)。
*
* @arg enabled
* @text 启用
* @type boolean
* @default true
*
* @command triggerGlitch
* @text 触发故障效果
* @desc 手动触发一次故障效果。
*
* @arg duration
* @text 持续时间(帧)
* @desc 故障效果的持续帧数(默认:随机 10-40)。
* @type number
* @min 1
* @default 0
*
* @command setGlitchFrequency
* @text 设置故障频率
* @desc 更改自动故障效果的平均间隔。
*
* @arg frequency
* @text 频率(秒)
* @type number
* @decimals 1
* @min 0
* @default 5.0
*
* @help
* CRTFilter.js
*
* 为游戏画面应用 CRT(阴极射线管)显示器效果,包括扫描线、桶形畸变、
* 暗角、色差和周期性故障效果。
*
* 此滤镜钩入 Spriteset_Base,因此会同时应用于地图场景和战斗场景。
*
* 插件命令:
*   setEnabled          - 开启/关闭 CRT 效果
*   setGlitchEnabled    - 开启/关闭自动故障效果
*   triggerGlitch       - 手动触发故障效果
*   setGlitchFrequency- 更改自动故障间隔
*/

void(function(){
"use strict";

var PLUGIN_NAME = "CRTFilter";
var params = PluginManager.parameters(PLUGIN_NAME);

var SETTINGS = {
    scanlineIntensity: Number(params["scanlineIntensity"] || 0.35),
    curvatureAmount: Number(params["curvatureAmount"] || 0.25),
    vignetteIntensity: Number(params["vignetteIntensity"] || 0.30),
    chromaticAberration: Number(params["chromaticAberration"] || 0.15),
    colorBleeding: Number(params["colorBleeding"] || 0.12),
    glitchFrequency: Number(params["glitchFrequency"] || 5.0),
    glitchIntensity: Number(params["glitchIntensity"] || 0.60),
    glitchEnabled: params["glitchEnabled"] !== "false",
    enabled: params["enabled"] !== "false",
};

// =========================================================================
// GlitchStateMachine Class - Manages glitch timing and state
// =========================================================================

function GlitchStateMachine(){
    this.initialize.apply(this, arguments);
}

GlitchStateMachine.prototype.initialize = function(options){
    this.state = "cooldown"; // cooldown | attack | sustain | release
    this.timer = 0;
    this.phaseTimer = 0;
    this.duration = 0;
    this.maxIntensity = options.maxIntensity || 0.6;
    this.frequency = options.frequency || 5.0;
    this.enabled = options.glitchEnabled !== false;
    this.currentIntensity = 0;
    this.cooldownTarget = this._randomCooldown();
};

GlitchStateMachine.prototype._randomCooldown = function(){
    var base = this.frequency * 60;
    return Math.floor(base * 0.5 + Math.random() * base);
};

GlitchStateMachine.prototype._randomDuration = function(){
    return Math.floor(10 + Math.random() * 30);
};

GlitchStateMachine.prototype.update = function(){
    switch(this.state){
      case"cooldown":
      this.timer++;
      if(this.enabled && this.timer >= this.cooldownTarget){
          this.trigger(0);
      }
      break;

      case"attack": {
      this.phaseTimer++;
      var attackLen = Math.max(1, Math.floor(this.duration * 0.2));
      var t = this.phaseTimer / attackLen;
      this.currentIntensity = t * this.maxIntensity;
      if(this.phaseTimer >= attackLen){
          this.state = "sustain";
          this.phaseTimer = 0;
      }
      break;
      }

      case"sustain": {
      this.phaseTimer++;
      var sustainLen = Math.max(
          1,
          Math.floor(this.duration * 0.5)
      );
      this.currentIntensity = this.maxIntensity;
      if(this.phaseTimer >= sustainLen){
          this.state = "release";
          this.phaseTimer = 0;
      }
      break;
      }

      case"release": {
      this.phaseTimer++;
      var releaseLen = Math.max(
          1,
          Math.floor(this.duration * 0.3)
      );
      var rt = 1 - this.phaseTimer / releaseLen;
      this.currentIntensity = rt * this.maxIntensity;
      if(this.phaseTimer >= releaseLen){
          this._end();
      }
      break;
      }
    }
};

GlitchStateMachine.prototype.trigger = function(duration){
    this.state = "attack";
    this.phaseTimer = 0;
    this.duration = duration > 0 ? duration : this._randomDuration();
};

GlitchStateMachine.prototype._end = function(){
    this.state = "cooldown";
    this.timer = 0;
    this.phaseTimer = 0;
    this.currentIntensity = 0;
    this.cooldownTarget = this._randomCooldown();
};

GlitchStateMachine.prototype.setEnabled = function(value){
    this.enabled = value;
};

GlitchStateMachine.prototype.setFrequency = function(seconds){
    this.frequency = seconds;
};

GlitchStateMachine.prototype.getIntensity = function(){
    returnthis.currentIntensity;
};

// =========================================================================
// Fragment Shader (GLSL ES 1.0)
// =========================================================================

var FRAGMENT_SRC =
    "precision mediump float;" +
    "varying vec2 vTextureCoord;" +
    "uniform sampler2D uSampler;" +
    "uniform float uTime;" +
    "uniform float uScanlineIntensity;" +
    "uniform float uCurvature;" +
    "uniform float uVignette;" +
    "uniform float uChromatic;" +
    "uniform float uBleeding;" +
    "uniform float uGlitchStrength;" +
    "uniform float uGlitchSeed;" +
    "" +
    "float rand(vec2 co) {" +
    "return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);" +
    "}" +
    "" +
    "vec2 curveUV(vec2 uv, float amount) {" +
    "vec2 c = uv - 0.5;" +
    "float r2 = dot(c, c);" +
    "vec2 curved = uv + c * r2 * amount * 0.5;" +
    "return curved;" +
    "}" +
    "" +
    "void main() {" +
    "vec2 uv = vTextureCoord;" +
    "" +
    "uv = curveUV(uv, uCurvature);" +
    "" +
    "if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {" +
    "    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);" +
    "    return;" +
    "}" +
    "" +
    "float bandY = floor(uv.y * 20.0);" +
    "float bandRand = rand(vec2(bandY, uGlitchSeed));" +
    "float displacement = 0.0;" +
    "if (uGlitchStrength > 0.0 && bandRand > 0.7) {" +
    "    displacement = (bandRand - 0.7) / 0.3 * 0.06 * uGlitchStrength;" +
    "    displacement *= sign(rand(vec2(bandY + 1.0, uGlitchSeed)) - 0.5);" +
    "}" +
    "vec2 glitchUV = vec2(uv.x + displacement, uv.y);" +
    "" +
    "float caBase = uChromatic * 0.004;" +
    "float caGlitch = uGlitchStrength * 0.012;" +
    "float caAmount = caBase + caGlitch;" +
    "" +
    "float r = texture2D(uSampler, vec2(glitchUV.x + caAmount, glitchUV.y)).r;" +
    "float g = texture2D(uSampler, glitchUV).g;" +
    "float b = texture2D(uSampler, vec2(glitchUV.x - caAmount, glitchUV.y)).b;" +
    "float a = texture2D(uSampler, glitchUV).a;" +
    "" +
    "float bleedOffset = uBleeding * 0.003;" +
    "float rBleed = texture2D(uSampler, vec2(glitchUV.x + bleedOffset, glitchUV.y)).r;" +
    "r = mix(r, rBleed, 0.5);" +
    "" +
    "float scanline = 1.0 - uScanlineIntensity * 0.5 * (1.0 - cos(vTextureCoord.y * 1000.0 * 3.14159 * 2.0));" +
    "r *= scanline;" +
    "g *= scanline;" +
    "b *= scanline;" +
    "" +
    "vec2 vig = uv - 0.5;" +
    "float vigDist = dot(vig, vig);" +
    "float vigFactor = 1.0 - vigDist * uVignette * 2.5;" +
    "vigFactor = clamp(vigFactor, 0.0, 1.0);" +
    "r *= vigFactor;" +
    "g *= vigFactor;" +
    "b *= vigFactor;" +
    "" +
    "if (uGlitchStrength > 0.0) {" +
    "    float flicker = 1.0 + (rand(vec2(uTime, uGlitchSeed)) - 0.5) * 0.3 * uGlitchStrength;" +
    "    r *= flicker;" +
    "    g *= flicker;" +
    "    b *= flicker;" +
    "}" +
    "" +
    "if (uGlitchStrength > 0.0) {" +
    "    float noiseLine = rand(vec2(floor(uv.y * 300.0), uGlitchSeed + 7.0));" +
    "    if (noiseLine > 0.97) {" +
    "      float noise = rand(vec2(uv.x * 100.0, uGlitchSeed + uv.y)) * uGlitchStrength;" +
    "      r = mix(r, noise, 0.4 * uGlitchStrength);" +
    "      g = mix(g, noise, 0.4 * uGlitchStrength);" +
    "      b = mix(b, noise, 0.4 * uGlitchStrength);" +
    "    }" +
    "}" +
    "" +
    "gl_FragColor = vec4(clamp(r, 0.0, 1.0), clamp(g, 0.0, 1.0), clamp(b, 0.0, 1.0), a);" +
    "}";

// =========================================================================
// CRTFilter Class
// =========================================================================

function CRTFilter(){
    this.initialize.apply(this, arguments);
}

CRTFilter.prototype = Object.create(PIXI.Filter.prototype);
CRTFilter.prototype.constructor = CRTFilter;

CRTFilter.prototype.initialize = function(){
    PIXI.Filter.call(this, null, FRAGMENT_SRC);
    this.uniforms.uTime = 0;
    this.uniforms.uScanlineIntensity = SETTINGS.scanlineIntensity;
    this.uniforms.uCurvature = SETTINGS.curvatureAmount;
    this.uniforms.uVignette = SETTINGS.vignetteIntensity;
    this.uniforms.uChromatic = SETTINGS.chromaticAberration;
    this.uniforms.uBleeding = SETTINGS.colorBleeding;
    this.uniforms.uGlitchStrength = 0;
    this.uniforms.uGlitchSeed = 0;
    this._enabled = SETTINGS.enabled;
    this._time = 0;

    this._glitch = new GlitchStateMachine({
      maxIntensity: SETTINGS.glitchIntensity,
      frequency: SETTINGS.glitchFrequency,
      glitchEnabled: SETTINGS.glitchEnabled,
    });
};

CRTFilter.prototype.update = function(){
    if(!this._enabled){
      this.uniforms.uGlitchStrength = 0;
      return;
    }

    this._time++;
    this.uniforms.uTime = this._time * 0.01;

    this._glitch.update();
    this.uniforms.uGlitchStrength = this._glitch.getIntensity();
    if(this.uniforms.uGlitchStrength > 0){
      this.uniforms.uGlitchSeed = Math.random() * 100;
    }
};

CRTFilter.prototype.setEnabled = function(value){
    this._enabled = value;
    this.enabled = value;
};

CRTFilter.prototype.setGlitchEnabled = function(value){
    this._glitch.setEnabled(value);
};

CRTFilter.prototype.triggerGlitch = function(duration){
    if(this._enabled){
      this._glitch.trigger(duration || 0);
    }
};

CRTFilter.prototype.setGlitchFrequency = function(seconds){
    this._glitch.setFrequency(seconds);
};

// Make available globally for save/load compatibility
window.CRTFilter = CRTFilter;
window.GlitchStateMachine = GlitchStateMachine;

// =========================================================================
// Engine Hooks
// =========================================================================

var _Spriteset_Base_createOverallFilters =
    Spriteset_Base.prototype.createOverallFilters;
Spriteset_Base.prototype.createOverallFilters = function(){
    _Spriteset_Base_createOverallFilters.call(this);
    this._crtFilter = new CRTFilter();
    if(!this._crtFilter._enabled){
      this._crtFilter.enabled = false;
    }
    this.filters.push(this._crtFilter);
};

var _Spriteset_Base_update = Spriteset_Base.prototype.update;
Spriteset_Base.prototype.update = function(){
    _Spriteset_Base_update.call(this);
    if(this._crtFilter){
      this._crtFilter.update();
    }
};

// =========================================================================
// Plugin Commands
// =========================================================================

PluginManager.registerCommand(PLUGIN_NAME, "setEnabled", function(args){
    var scene = SceneManager._scene;
    if(scene && scene._spriteset && scene._spriteset._crtFilter){
      var enabled = args.enabled === "true";
      scene._spriteset._crtFilter.setEnabled(enabled);
    }
});

PluginManager.registerCommand(PLUGIN_NAME, "setGlitchEnabled", function(args){
    var scene = SceneManager._scene;
    if(scene && scene._spriteset && scene._spriteset._crtFilter){
      var enabled = args.enabled === "true";
      scene._spriteset._crtFilter.setGlitchEnabled(enabled);
    }
});

PluginManager.registerCommand(
    PLUGIN_NAME,
    "triggerGlitch",
    function(args){
      var scene = SceneManager._scene;
      if(scene && scene._spriteset && scene._spriteset._crtFilter){
      var duration = Number(args.duration) || 0;
      scene._spriteset._crtFilter.triggerGlitch(duration);
      }
    }
);

PluginManager.registerCommand(
    PLUGIN_NAME,
    "setGlitchFrequency",
    function(args){
      var scene = SceneManager._scene;
      if(scene && scene._spriteset && scene._spriteset._crtFilter){
      var frequency = Number(args.frequency) || 5.0;
      scene._spriteset._crtFilter.setGlitchFrequency(frequency);
      }
    }
);
})();

            本帖来自P1论坛作者铅笔描绘的思念,因Project1站服务器在国外有时候访问缓慢不方便作者交流学习,经联系P1站长fux2同意署名转载一起分享游戏制作经验,共同为国内独立游戏作者共同创造良好交流环境,原文地址:https://rpg.blue/forum.php?mod=viewthread&tid=498644若有侵权,发帖作者可联系底部站长QQ在线咨询功能删除,谢谢。
页: [1]
查看完整版本: 【MZ插件】CRT显示器扫描线和随机故障等效果