查看: 951|回复: 1

[原创发布] 【MZ插件】CRT显示器扫描线和随机故障等效果

[复制链接]
梦石
0
星屑
2306
在线时间
1010 小时
注册时间
2012-4-30
回帖
1455

开拓者

发表于 2026-2-28 15:20:28 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

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



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

[pre lang="javascript" file="CRTFilter.js"]/*:
* @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 () {
    return this.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);
      }
    }
  );
})();
[/pre]

评分

参与人数 2赞 +2 收起 理由
714784864 + 1 塞糖
OK一个好用户名 + 1 塞糖

查看全部评分

梦石
0
星屑
675
在线时间
18 小时
注册时间
2025-4-3
回帖
5

RM创作大赛01参赛者

发表于 2026-3-31 09:04:29 | 显示全部楼层
感谢,刚好能用上
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部