立绘眨眼插件


/*:
* @plugindesc 立绘眨眼插件 - 终极无闪烁版
* @author Doubao
*
* @param eyeOpenSuffix
* @text 睁眼图片后缀
* @desc 睁眼状态图片的文件名后缀
* @default _open
*
* @param eyeCloseSuffix
* @text 闭眼图片后缀
* @desc 闭眼状态图片的文件名后缀
* @default _close
*
* @param blinkInterval
* @text 眨眼间隔(帧)
* @desc 平均眨眼间隔时间(游戏帧,60帧=1秒)
* @default 180
*
* @param blinkSpeed
* @text 眨眼速度(帧)
* @desc 眨眼动画持续帧数
* @default 12
*
* @help
* 使用方法:
* 1. 将睁眼和闭眼的立绘图片放在img/pictures文件夹中
* 2. 确保两张图片文件名相同,仅后缀不同(默认为_open和_close)
* 3. 在事件中使用"显示图片"命令显示立绘
* 4. 使用插件命令控制眨眼效果
*
* 插件命令:
* EyeBlink init [图片ID] [基础名称] - 初始化立绘
* EyeBlink blink [图片ID] - 触发眨眼
* EyeBlink auto [图片ID] - 启用自动眨眼
* EyeBlink stop [图片ID] - 停止自动眨眼
*/

(function() {
// 获取插件参数
var parameters = PluginManager.parameters('EyeBlink');
var eyeOpenSuffix = String(parameters['eyeOpenSuffix'] || '_open');
var eyeCloseSuffix = String(parameters['eyeCloseSuffix'] || '_close');
var blinkInterval = Number(parameters['blinkInterval'] || 180);
var blinkSpeed = Number(parameters['blinkSpeed'] || 12);

// 存储立绘数据
var _portraitData = {};
var _bitmapCache = {};

// 预加载图片并确保已加载
function preloadBitmap(name) {
if (!_bitmapCache[name]) {
_bitmapCache[name] = ImageManager.loadPicture(name);
}
return _bitmapCache[name];
}

// 确保两张图片都已加载完成
function ensureBitmapsLoaded(baseName) {
var openBitmap = preloadBitmap(baseName + eyeOpenSuffix);
var closeBitmap = preloadBitmap(baseName + eyeCloseSuffix);

return openBitmap.isReady() && closeBitmap.isReady();
}

// 创建睁眼和闭眼精灵层
function createPortraitSprites(pictureId, baseName) {
var originalSprite = SceneManager._scene._spriteset._pictureContainer.children[pictureId - 1];
if (!(originalSprite instanceof Sprite_Picture)) {
console.error(`[EyeBlink] 图片ID ${pictureId} 不存在或不是立绘`);
return null;
}

// 创建睁眼精灵(底层)
var openSprite = new Sprite();
openSprite.bitmap = preloadBitmap(baseName + eyeOpenSuffix);

// 创建闭眼精灵(上层)
var closeSprite = new Sprite();
closeSprite.bitmap = preloadBitmap(baseName + eyeCloseSuffix);

// 设置精灵属性(使用更安全的方法)
function setupSprite(sprite) {
sprite.x = originalSprite.x;
sprite.y = originalSprite.y;
sprite.z = originalSprite.z;
sprite.anchor.x = originalSprite.anchor.x;
sprite.anchor.y = originalSprite.anchor.y;
sprite.scale.x = originalSprite.scale.x;
sprite.scale.y = originalSprite.scale.y;
sprite.rotation = originalSprite.rotation;
sprite.opacity = 0; // 初始都不可见
}

setupSprite(openSprite);
setupSprite(closeSprite);

// 添加到容器
var parent = originalSprite.parent;
parent.addChild(openSprite);
parent.addChild(closeSprite);

// 隐藏原始精灵
originalSprite.visible = false;

return { openSprite, closeSprite };
}

// 清理立绘精灵
function cleanupPortraitSprites(pictureId) {
var data = _portraitData[pictureId];
if (!data) return;

// 从父容器中移除精灵
if (data.openSprite && data.openSprite.parent) {
data.openSprite.parent.removeChild(data.openSprite);
}

if (data.closeSprite && data.closeSprite.parent) {
data.closeSprite.parent.removeChild(data.closeSprite);
}

// 显示原始精灵
if (data.originalSprite) {
data.originalSprite.visible = true;
}

// 删除数据
delete _portraitData[pictureId];
console.log(`[EyeBlink] 清理立绘 ID${pictureId}`);
}

// 初始化立绘(增强版)
function initPortrait(pictureId, baseName) {
// 先清理可能存在的旧精灵
cleanupPortraitSprites(pictureId);

// 预加载两张图片
if (!ensureBitmapsLoaded(baseName)) {
console.log(`[EyeBlink] 图片ID ${pictureId} 资源未完全加载,延迟初始化`);

// 延迟检查
setTimeout(() => {
if (ensureBitmapsLoaded(baseName)) {
initPortrait(pictureId, baseName);
} else {
console.error(`[EyeBlink] 图片ID ${pictureId} 资源加载超时`);
}
}, 300);

return false;
}

// 创建精灵
var sprites = createPortraitSprites(pictureId, baseName);
if (!sprites) return false;

// 初始化数据
_portraitData[pictureId] = {
baseName: baseName,
isBlinking: false,
blinkPhase: 0, // 0:睁眼, 1:闭眼过渡, 2:闭眼, 3:睁眼过渡
phaseCounter: 0,
autoBlink: false,
intervalCounter: Math.floor(Math.random() * blinkInterval),
openSprite: sprites.openSprite,
closeSprite: sprites.closeSprite,
originalSprite: SceneManager._scene._spriteset._pictureContainer.children[pictureId - 1],
// 存储原始精灵的可见性和透明度
originalVisible: true,
originalOpacity: 255
};

// 默认显示睁眼状态
sprites.openSprite.opacity = 255;
sprites.closeSprite.opacity = 0;

console.log(`[EyeBlink] 初始化立绘 ID${pictureId}: ${baseName}`);
return true;
}

// 执行眨眼
function blinkPortrait(pictureId) {
var data = _portraitData[pictureId];
if (!data || data.isBlinking) return;

// 只在图片可见且不透明时眨眼
if (!data.originalVisible || data.originalOpacity < 10) return; data.isBlinking = true; data.blinkPhase = 1; // 开始闭眼过渡 data.phaseCounter = 0; console.log(`[EyeBlink] 立绘 ID${pictureId} 开始眨眼`); } // 更新眨眼动画(平滑过渡版) function updateBlinkAnimation() { for (var pictureId in _portraitData) { var data = _portraitData[pictureId]; if (!data.isBlinking) continue; // 如果图片不可见或完全透明,停止眨眼动画 if (!data.originalVisible || data.originalOpacity < 10) { data.isBlinking = false; data.blinkPhase = 0; data.openSprite.opacity = data.originalOpacity; data.closeSprite.opacity = 0; continue; } data.phaseCounter++; // 眨眼阶段控制 switch (data.blinkPhase) { case 1: // 闭眼过渡 var closeProgress = data.phaseCounter / (blinkSpeed / 2); if (closeProgress >= 1) {
data.blinkPhase = 2;
data.phaseCounter = 0;
data.openSprite.opacity = 0;
data.closeSprite.opacity = data.originalOpacity;
} else {
data.openSprite.opacity = data.originalOpacity * (1 - closeProgress);
data.closeSprite.opacity = data.originalOpacity * closeProgress;
}
break;

case 2: // 闭眼状态
if (data.phaseCounter >= 2) { // 保持2帧
data.blinkPhase = 3;
data.phaseCounter = 0;
}
break;

case 3: // 睁眼过渡
var openProgress = data.phaseCounter / (blinkSpeed / 2);
if (openProgress >= 1) {
data.blinkPhase = 0;
data.isBlinking = false;
data.openSprite.opacity = data.originalOpacity;
data.closeSprite.opacity = 0;
console.log(`[EyeBlink] 立绘 ID${pictureId} 眨眼结束`);
} else {
data.openSprite.opacity = data.originalOpacity * openProgress;
data.closeSprite.opacity = data.originalOpacity * (1 - openProgress);
}
break;
}
}
}

// 更新自动眨眼
function updateAutoBlink() {
for (var pictureId in _portraitData) {
var data = _portraitData[pictureId];
if (!data.autoBlink || data.isBlinking) continue;

// 只在图片可见且不透明时自动眨眼
if (!data.originalVisible || data.originalOpacity < 10) continue; data.intervalCounter--; if (data.intervalCounter <= 0) { blinkPortrait(parseInt(pictureId)); data.intervalCounter = blinkInterval + Math.floor(Math.random() * blinkInterval/2); } } } // 同步原始精灵的属性到新精灵 function syncSpriteProperties() { for (var pictureId in _portraitData) { var data = _portraitData[pictureId]; var originalSprite = data.originalSprite; // 如果原始精灵已被移除(如地图切换),清理立绘 if (!originalSprite || !originalSprite.parent) { cleanupPortraitSprites(pictureId); continue; } // 存储原始精灵的可见性和透明度 data.originalVisible = originalSprite.visible; data.originalOpacity = originalSprite.opacity; // 如果图片不可见或完全透明,直接隐藏所有精灵 if (!originalSprite.visible || originalSprite.opacity === 0) { data.openSprite.visible = false; data.closeSprite.visible = false; continue; } // 显示精灵 data.openSprite.visible = true; data.closeSprite.visible = true; // 如果没有在眨眼,根据当前状态设置透明度 if (!data.isBlinking) { data.openSprite.opacity = originalSprite.opacity; data.closeSprite.opacity = 0; } } } // 处理插件命令 var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand; Game_Interpreter.prototype.pluginCommand = function(command, args) { _Game_Interpreter_pluginCommand.call(this, command, args); if (command === 'EyeBlink') { var pictureId = Number(args[1] || 1); switch (args[0]) { case 'init': var baseName = String(args[2] || ''); if (baseName) { initPortrait(pictureId, baseName); } break; case 'blink': blinkPortrait(pictureId); break; case 'auto': if (_portraitData[pictureId]) { _portraitData[pictureId].autoBlink = true; console.log(`[EyeBlink] 立绘 ID${pictureId} 启用自动眨眼`); } break; case 'stop': if (_portraitData[pictureId]) { _portraitData[pictureId].autoBlink = false; console.log(`[EyeBlink] 立绘 ID${pictureId} 停止自动眨眼`); } break; case 'cleanup': cleanupPortraitSprites(pictureId); break; } } }; // 地图切换时清理所有立绘 var _Scene_Map_terminate = Scene_Map.prototype.terminate; Scene_Map.prototype.terminate = function() { _Scene_Map_terminate.call(this); // 清理所有立绘 for (var pictureId in _portraitData) { cleanupPortraitSprites(pictureId); } }; // 游戏主循环更新 var _Scene_Base_update = Scene_Base.prototype.update; Scene_Base.prototype.update = function() { _Scene_Base_update.call(this); updateBlinkAnimation(); updateAutoBlink(); syncSpriteProperties(); // 同步精灵属性 }; })();

使用方法
自动清理(推荐):
使用修改后的插件,地图切换时会自动清理所有立绘,无需额外操作。
手动清理:
在事件中使用EyeBlink cleanup [图片ID]命令手动清理特定立绘。


插件命令:EyeBlink cleanup 1 # 清理图片ID为1的立绘

确保地图切换前消除立绘:
在跳转地图前,使用原生的「消失图片」命令消除立绘。

消失图片:1
转移玩家:目标地图ID, X坐标, Y坐标


显示图片:1, character_open.png, 画面中央, 0, 100%, 100%, 255, 通常合成
插件命令:EyeBlink init 1 character
插件命令:EyeBlink auto 1
等待:180帧
消失图片:1

图片 ID 必须一致
初始化立绘(EyeBlink init)、启用眨眼(EyeBlink auto)和消除立绘(消失图片)时使用的 图片 ID 必须相同。例如:
显示图片时用 图片 ID=1。
插件命令用 EyeBlink init 1 character。
消除立绘时用 消失图片:1。


发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注