设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 441|回复: 2
打印 上一主题 下一主题

[原创发布] 【自制UI插件】实现显示圆圈形状的倒计时进度条

[复制链接]

Lv1.梦旅人

梦石
0
星屑
109
在线时间
16 小时
注册时间
2026-2-18
帖子
3
跳转到指定楼层
1
发表于 2026-2-20 01:24:30 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

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

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

x
本帖最后由 莱纳嘤狐 于 2026-2-20 01:24 编辑

一开始对mv底层框架不太熟悉,就先后尝试使用html5 canvas和webgl绘制图像,结果总是一点反应也没有orz
后面经历了多次搜集资料和对框架核心源码的解读(这个过程真的很痛苦啊啊啊)就明白了其中的原理:mv里的许多东西都是对PIXI库里的东西的一层层封装,我们的游戏实质上就是一个巨大的PIXI容器(Container对象),里面的许多UI元素(如Sprite、Scene、Window等)都需要被add到里面去。
但是绘制这种圆弧形状用canvas2dContext的方法会更方便,因为用picture实在是不现实。然后我就发现,Bitmap里就封装了context对象,于是乎我就捣鼓出了下面的代码:
JAVASCRIPT 代码复制
  1. Bitmap.prototype.drawArc = function(x, y, radius, startAngle, endAngle, lineWidth, color) {
  2.         var context = this._context;
  3.         context.clearRect(0, 0, Graphics.boxWidth, Graphics.boxHeight);
  4.  
  5.         context.beginPath();
  6.         context.arc(x, y, radius, startAngle, endAngle, true);
  7.  
  8.         context.strokeStyle = color;
  9.         context.lineWidth = lineWidth;
  10.         context.stroke();
  11.  
  12.         context.save();
  13.         this._setDirty();
  14. };

自己为Bitmap定义一个新的绘制圆弧的方法,毕竟直接在外部对Bitmap操作很可能行不通(因为_context是Bitmap的私有成员,不向外提供)。
紧接着在反复测试中我还学习到,Bitmap不能直接显示在游戏画面上,还需要封装在Sprite对象中,并被add到PIXI容器里面,放在当前场景上,由SceneManager.update去负责倒计时动画的更新。
完整源码我贴在下面了,还有一些示例截图,欢迎交流,有需改进之处麻烦大佬指出了,非常感谢!!!!新人rm开发者感谢支持!!!

JAVASCRIPT 代码复制下载
  1. //=============================================================================
  2. // L_CircleGaugeUI.js
  3. //=============================================================================
  4.  
  5. /*:
  6.  * @target MV
  7.  * @plugindesc 自制UI:圆圈形的倒计时进度条
  8.  * @author レナ
  9.  * @help L_CircleGaugeUI.js
  10.  *
  11.  * 插件命令([]括号为可选项):
  12.  * · L_CircleGaugeUI start 倒计时 圆圈半径 [中心X] [中心Y]
  13.  *  开启倒计时。
  14.  * · L_CircleGaugeUI stop
  15.  *  停止倒计时。
  16.  *
  17.  * ◆注意:为保证插件正常使用,请先指定一张空png图片!
  18.  *
  19.  * 使用条款:完全免费使用,可随意更改,允许二次发布,无任何版权限制。
  20.  *
  21.  * ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆
  22.  *
  23.  * @param duration
  24.  * @text 持续时间
  25.  * @type number
  26.  * @min 1
  27.  * @max 300
  28.  * @default 10
  29.  * @desc 默认倒计时,单位为秒。
  30.  *
  31.  * @param radius
  32.  * @text 圆圈半径
  33.  * @type number
  34.  * @min 50
  35.  * @max 300
  36.  * @default 200
  37.  * @desc 默认圆圈半径,单位为像素。
  38.  *
  39.  * @param centerX
  40.  * @text 圆圈中心X
  41.  * @type number
  42.  * @min 0
  43.  * @max 816
  44.  * @desc 默认圆圈中心(X坐标),单位为像素。
  45.  *
  46.  * @param centerXVariableId
  47.  * @parent centerX
  48.  * @text 变量id
  49.  * @type number
  50.  * @desc 绑定到的变量id。
  51.  *
  52.  * @param centerY
  53.  * @text 圆圈中心Y
  54.  * @type number
  55.  * @min 0
  56.  * @max 624
  57.  * @desc 默认圆圈中心(Y坐标),单位为像素。
  58.  *
  59.  * @param centerYVariableId
  60.  * @parent centerY
  61.  * @text 变量id
  62.  * @type number
  63.  * @desc 绑定到的变量id。
  64.  *
  65.  * @param picName
  66.  * @text 图片
  67.  * @type file
  68.  * @dir img/pictures/
  69.  * @default 0
  70.  */
  71.  
  72. (function() {
  73.     Bitmap.prototype.drawArc = function(x, y, radius, startAngle, endAngle, lineWidth, color) {
  74.         var context = this._context;
  75.         context.clearRect(0, 0, Graphics.boxWidth, Graphics.boxHeight);
  76.  
  77.         context.beginPath();
  78.         context.arc(x, y, radius, startAngle, endAngle, true);
  79.  
  80.         context.strokeStyle = color;
  81.         context.lineWidth = lineWidth;
  82.         context.stroke();
  83.  
  84.         context.save();
  85.         this._setDirty();
  86.     };
  87.  
  88.     var pluginName = 'L_CircleGaugeUI';
  89.     var params = PluginManager.parameters(pluginName);
  90.  
  91.     var duration = Number(params['duration']) || 10;
  92.     var radius = Number(params['radius']) || 50;
  93.     var centerXVariableId = Number(params['centerXVariableId']) || 0;
  94.     var centerYVariableId = Number(params['centerYVariableId']) || 0;
  95.     var centerX = Number(params['centerX']) || Graphics.boxWidth / 2;
  96.     var centerY = Number(params['centerY']) || Graphics.boxHeight / 2;
  97.     var picName = params['picName'] || '0';
  98.  
  99.     var strokeWidth = 15;   // 圆环宽度(粗细)
  100.  
  101.     var isCounting = false;
  102.  
  103.     var bgColor = '#999999';
  104.     var gaugeColor = '#ffffff';
  105.  
  106.     var bgCircle = null;
  107.     var gaugeCircle = null;
  108.     var gaugeContainer = null;
  109.  
  110.     function draw(percent) {
  111.         if (percent >= 0) {
  112.             // 计算对应角度(100%=360°, 0%=0°)
  113.             var startAngle = -Math.PI / 2;
  114.             var endAngle = percent * 2 * Math.PI - Math.PI / 2;
  115.             gaugeCircle.drawArc(centerX, centerY, radius, startAngle, endAngle, strokeWidth, gaugeColor);
  116.         }
  117.     }
  118.  
  119.     // 倒计时启动方法
  120.     Game_Temp.prototype.startCountdown = function(time, radius, startCallback, endCallback, okCallback) {
  121.         if (startCallback) startCallback();
  122.         // 若已有倒计时,先停止
  123.         if (isCounting) this.stopCountdown();
  124.  
  125.         isCounting = true;
  126.         var totalMillis = time * 1000;
  127.         var elapsedMillis = 0;
  128.         var startTime = Date.now();
  129.  
  130.         if (!gaugeContainer) {
  131.             gaugeContainer = new PIXI.Container();
  132.             SceneManager._scene.addChild(gaugeContainer);
  133.         }
  134.  
  135.         bgCircle = ImageManager.loadPicture(picName);
  136.         gaugeCircle = ImageManager.loadPicture(picName);
  137.  
  138.         // 绘制底层灰色时间条
  139.         bgCircle.drawArc(centerX, centerY, radius, 0, 2 * Math.PI, strokeWidth, bgColor);
  140.         var bgCircleSprite = new Sprite(bgCircle);
  141.         gaugeContainer.addChild(bgCircleSprite);
  142.  
  143.         // 绘制白色时间条
  144.         var gaugeCircleSprite = new Sprite(gaugeCircle);
  145.         gaugeContainer.addChild(gaugeCircleSprite);
  146.  
  147.         // 动画更新逻辑
  148.         function update() {
  149.             if (!isCounting) return;
  150.  
  151.             elapsedMillis = Date.now() - startTime;
  152.             var progress = Math.max(0, 1 - (elapsedMillis / totalMillis));
  153.  
  154.             draw(progress);
  155.             gaugeContainer.addChild(gaugeCircleSprite);
  156.  
  157.             // 倒计时结束
  158.             if (progress <= 0) {
  159.                 $gameTemp.stopCountdown();
  160.                 if (endCallback) endCallback();
  161.                 return;
  162.             }
  163.  
  164.             animationId = requestAnimationFrame(update);
  165.         }
  166.  
  167.         // 启动动画
  168.         update();
  169.  
  170.         // 监听确定键
  171.         function okHandler() {
  172.             if (isCounting) {
  173.                 //alert('按下了确定键');
  174.                 $gameTemp.stopCountdown();
  175.                 if (okCallback) okCallback();
  176.             }
  177.         }
  178.  
  179.         this._countdownOkHandler = okHandler;
  180.     };
  181.  
  182.     // 停止倒计时方法
  183.     Game_Temp.prototype.stopCountdown = function() {
  184.         if (!isCounting) return;
  185.  
  186.         // 停止动画、清空容器
  187.         cancelAnimationFrame(animationId);
  188.         if (gaugeContainer) {
  189.             gaugeContainer.removeChildren();
  190.             //SceneManager._scene.removeChild(gaugeContainer);
  191.         }
  192.  
  193.         // 清除确定键监听、重置状态
  194.         this._countdownOkHandler = null;
  195.         isCounting = false;
  196.         elapsedMillis = 0;
  197.     };
  198.  
  199.     /* 这个暂时没啥用
  200.     // 监听确定键
  201.     var _Scene_Base_updateInput = Scene_Base.prototype.updateInput;
  202.     Scene_Base.prototype.updateInput = function() {
  203.         _Scene_Base_updateInput.call(this);
  204.         // 按下确定键且有倒计时时触发回调
  205.         if (Input.isTriggered('ok') && $gameTemp._countdownOkHandler) {
  206.             $gameTemp._countdownOkHandler();
  207.         }
  208.     };
  209.     */
  210.  
  211.     var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  212.     Game_Interpreter.prototype.pluginCommand = function(command, args) {
  213.         _Game_Interpreter_pluginCommand.call(this, command, args);
  214.         if (command === pluginName) {
  215.             switch(args[0]) {
  216.                 case 'start':
  217.                     duration = Number(args[1]) || duration;
  218.                     radius = Number(args[2]) || radius;
  219.                     centerX = Number(args[3]) || Number($gameVariables.value(centerXVariableId)) || centerX || Graphics.boxWidth / 2;
  220.                     centerY = Number(args[4]) || Number($gameVariables.value(centerYVariableId)) || centerY || Graphics.boxHeight / 2;
  221.  
  222.                     // ◆开启倒计时
  223.                     $gameTemp.startCountdown(duration, radius);
  224.                     break;
  225.                 case 'stop':
  226.                     // ◆停止倒计时
  227.                     $gameTemp.stopCountdown();
  228.                     break;
  229.             }
  230.         }
  231.  
  232.         };
  233. })();

L_CircleGaugeUI.zip (2.4 KB, 下载次数: 4, 售价: 1 星屑)


(感谢论坛某个像素画大佬做的图块素材嘿嘿嘿!!!游戏都还没开始写,时间全花在魔改引擎和ui设计上了orz)

Lv1.梦旅人

梦石
0
星屑
109
在线时间
16 小时
注册时间
2026-2-18
帖子
3
2
 楼主| 发表于 2026-2-20 01:25:34 | 只看该作者
目前测试发现个bug:底层的灰色条没有显示出来,不知道有哪位佬能帮我看下该怎么弄orz
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
109
在线时间
16 小时
注册时间
2026-2-18
帖子
3
3
 楼主| 发表于 2026-2-20 01:30:30 | 只看该作者
顺便解释下为什么要在一张空图片上做bitmap绘制,因为我试过改成ImageManager.loadEmptyBitmap()结果发现没反应。。。不知道为什么
回复 支持 反对

使用道具 举报

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

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2026-6-4 21:03

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表