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

Project1

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

[有事请教] 在写一份简单的物品合成插件时遇到的问题

[复制链接]

Lv1.梦旅人

梦石
0
星屑
165
在线时间
18 小时
注册时间
2025-2-4
帖子
16
跳转到指定楼层
1
发表于 14 小时前 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

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

x
本帖最后由 雪狼的天空 于 2025-9-6 09:02 编辑

如题所示。
我要写的这个插件,它本身有着从外界读取合成配方的功能,并且使用内部定义区分了不同操作台。例如灶台只能做饭,铁砧只能打造武器等等。
它分为主管配方控制和物品合成核心功能的核心类SynthesisSystem,用于打开合成场景的Scene_Synthesis,用于显示配方列表的窗口类Window_RecipeList,用于确认合成的对话框类Window_Confirmation。
我的问题是:对于显示配方列表的窗口类Window_RecipeList,它是Window_Command类的扩展,所以在Window_RecipeList的构造函数中,需要先用super引用父类的构造函数Window_Command(rectangle),但是,我无论super传入一个什么样的rectangle,就算是new Rectangle(0,0,1,1),都会显示报错“Failed to execute 'createLinearGradient' on 'CanvasRenderingContext2D': The provided double value is non-finite.”进一步追查,其原因在于Bitmap.gradientFillRect,以及更上一层的Window_Confirmation.Window_Selectable.drawBackgroundRect;这说明了是渐变传入参数的问题,但我也没有自己定义传入任何渐变参数呀
因此向大神们请教一下问题到底出在哪里;我问了deepseek它的解法完全不可行。另,我的各个窗口件长宽高都是随手写的,但我想应该不会是窗口件重叠造成的问题吧。
以下是代码内容:
  1. /*:
  2. * @target MZ
  3. * @plugindesc 物品合成系统 v1.0 - 支持多种配方和操作台的合成系统
  4. * @author Snowwolf
  5. *
  6. * @param RecipeFile
  7. * @text 配方文件
  8. * @desc 合成配方JSON文件的路径
  9. * @type string
  10. * @default data/SynthesisRecipe.json
  11. *
  12. * @help
  13. * 使用说明:
  14. * 1. 准备配方文件:在指定路径创建SynthesisRecipe.json文件
  15. * 2. 在事件中使用插件命令调用合成界面
  16. * 3. 或在脚本中使用Window_SynthesisSystem.open("stove")调用
  17. *
  18. * 插件命令:
  19. *   OpenSynthesis workstationType - 打开指定类型的合成界面
  20. */

  21. (() => {
  22.     // 插件参数
  23.     const parameters = PluginManager.parameters('ItemSynthesisSystem');
  24.     const recipeFile = parameters.RecipeFile || 'data/SynthesisRecipe.json';
  25.    
  26.     // 内部定义的操作台类型
  27.     const WORKSTATIONS = {
  28.         herbalist_table: { name: "炼金台", icon: 224 },
  29.         anvil: { name: "铁砧", icon: 225 },
  30.         stove: { name: "灶台", icon: 226 },
  31.         sewing: { name: "缝纫台", icon: 227 }
  32.     };
  33.    
  34.     // 合成系统核心类
  35.     class SynthesisSystem {
  36.         constructor() {
  37.             this._recipes = [];
  38.             this._currentWorkstation = null;
  39.             this.loadRecipes();
  40.         }
  41.         
  42.         // 加载配方文件
  43.         loadRecipes() {
  44.             const xhr = new XMLHttpRequest();
  45.             xhr.open('GET', recipeFile);
  46.             xhr.overrideMimeType('application/json');
  47.             xhr.onload = () => {
  48.                 if (xhr.status < 400) {
  49.                     this._recipes = JSON.parse(xhr.responseText);
  50.                     console.log("配方加载成功:", this._recipes.length, "个配方");
  51.                 }
  52.             };
  53.             xhr.onerror = () => {
  54.                 console.error("无法加载配方文件:", recipeFile);
  55.             };
  56.             xhr.send();
  57.         }
  58.         
  59.         // 获取指定操作台的配方
  60.         getRecipesByWorkstation(workstationType) {
  61.             return this._recipes.filter(recipe =>
  62.                 recipe.workstation === workstationType &&
  63.                 (!recipe.requiredSwitch || $gameSwitches.value(recipe.requiredSwitch))
  64.             );
  65.         }
  66.         
  67.         // 检查材料是否足够
  68.         hasEnoughMaterials(recipe) {
  69.             return recipe.materials.every(material => {
  70.                 let count = 0;
  71.                 switch (material.type) {
  72.                     case 'item': count = $gameParty.numItems($dataItems[material.id]); break;
  73.                     case 'weapon': count = $gameParty.numItems($dataWeapons[material.id]); break;
  74.                     case 'armor': count = $gameParty.numItems($dataArmors[material.id]); break;
  75.                 }
  76.                 return count >= material.count;
  77.             });
  78.         }
  79.         
  80.         // 执行合成
  81.         performSynthesis(recipe) {
  82.             if (!this.hasEnoughMaterials(recipe)) return false;
  83.             
  84.             // 消耗材料
  85.             recipe.materials.forEach(material => {
  86.                 let item = null;
  87.                 switch (material.type) {
  88.                     case 'item': item = $dataItems[material.id]; break;
  89.                     case 'weapon': item = $dataWeapons[material.id]; break;
  90.                     case 'armor': item = $dataArmors[material.id]; break;
  91.                 }
  92.                 $gameParty.loseItem(item, material.count);
  93.             });
  94.             
  95.             // 获得结果
  96.             let resultItem = null;
  97.             switch (recipe.result.type) {
  98.                 case 'item':
  99.                     resultItem = $dataItems[recipe.result.id];
  100.                     $gameParty.gainItem(resultItem, recipe.result.count);
  101.                     break;
  102.                 case 'weapon':
  103.                     resultItem = $dataWeapons[recipe.result.id];
  104.                     $gameParty.gainItem(resultItem, recipe.result.count);
  105.                     break;
  106.                 case 'armor':
  107.                     resultItem = $dataArmors[recipe.result.id];
  108.                     $gameParty.gainItem(resultItem, recipe.result.count);
  109.                     break;
  110.             }
  111.             
  112.             return true;
  113.         }
  114.         
  115.         // 设置当前操作台
  116.         setCurrentWorkstation(workstationType) {
  117.             this._currentWorkstation = workstationType;
  118.         }

  119.         // 获取当前操作台
  120.         getCurrentWorkstation() {
  121.             return this._currentWorkstation;
  122.         }
  123.         
  124.         // 获取操作台信息
  125.         getWorkstationInfo(type) {
  126.             return WORKSTATIONS[type] || { name: "未知工作台", icon: 0 };
  127.         }
  128.     }
  129.         
  130.     // 全局合成系统实例
  131.     window.SynthesisSystem = new SynthesisSystem();
  132.    
  133.     // 全局访问函数
  134.     window.Window_SynthesisSystem = {
  135.         open: function(workstationType) {
  136.             window.SynthesisSystem.setCurrentWorkstation(workstationType);
  137.             SceneManager.push(Scene_Synthesis);
  138.         }
  139.     };
  140.    
  141.     // 合成场景
  142.     class Scene_Synthesis extends Scene_MenuBase {
  143.         create() {
  144.             super.create();
  145.             this.createHelpWindow();
  146.             this.createRecipeListWindow();
  147.             this._confirmationWindow = null; // 状态窗口将在需要时创建
  148.         }
  149.         
  150.         createHelpWindow() {
  151.             const workstation = window.SynthesisSystem.getWorkstationInfo(
  152.                 window.SynthesisSystem.getCurrentWorkstation()
  153.             );
  154.             const rect = new Rectangle(0, 0, Graphics.boxWidth/2, 40);
  155.             this._helpWindow = new Window_Help(rect);
  156.             this._helpWindow.setText(workstation.name);
  157.             this.addWindow(this._helpWindow);
  158.         }
  159.         
  160.         createRecipeListWindow() {
  161.             const wy = this._helpWindow.height;
  162.             const wh = Graphics.boxHeight - wy;
  163.             const rect = new Rectangle(0, wy, Graphics.boxWidth, wh);
  164.             
  165.             this._recipeListWindow = new Window_RecipeList(rect);
  166.             this._recipeListWindow.setHandler('ok', this.onRecipeOk.bind(this));
  167.             this._recipeListWindow.setHandler('cancel', this.popScene.bind(this));
  168.             
  169.             this.addWindow(this._recipeListWindow);
  170.             this._recipeListWindow.activate();
  171.         }
  172.         
  173.         onRecipeOk() {
  174.             const recipe = this._recipeListWindow.currentRecipe();
  175.             this.showConfirmationDialog(recipe);
  176.         }
  177.         
  178.         showConfirmationDialog(recipe) {
  179.             // 创建确认对话框
  180.             const width = 400;
  181.             const height = 250;
  182.             const x = Graphics.boxWidth/2;
  183.             const y = Graphics.boxHeight/2;
  184.             const rect = new Rectangle(x, y, Graphics.boxWidth/2, Graphics.boxHeight);
  185.             
  186.             this._confirmationWindow = new Window_Confirmation(rect, recipe);
  187.             this._confirmationWindow.setHandler('yes', this.onConfirmYes.bind(this, recipe));
  188.             this._confirmationWindow.setHandler('no', this.onConfirmNo.bind(this));
  189.             
  190.             this.addWindow(this._confirmationWindow);
  191.             this._confirmationWindow.activate();
  192.             this._recipeListWindow.deactivate();
  193.         }
  194.         
  195.         onConfirmYes(recipe) {
  196.             if (window.SynthesisSystem.hasEnoughMaterials(recipe)) {
  197.                 const success = window.SynthesisSystem.performSynthesis(recipe);
  198.                 if (success) {
  199.                     $gameMessage.add("合成成功!");
  200.                 }
  201.             } else {
  202.                 $gameMessage.add("材料不足!");
  203.             }
  204.             
  205.             this.removeWindow(this._confirmationWindow);
  206.             this._recipeListWindow.activate();
  207.             this._recipeListWindow.refresh();
  208.         }
  209.         
  210.         onConfirmNo() {
  211.             this.removeWindow(this._confirmationWindow);
  212.             this._recipeListWindow.activate();
  213.         }
  214.     }
  215.    
  216.     // 配方列表窗口
  217.     class Window_RecipeList extends Window_Selectable {
  218.         initialize(rect) {
  219.             super.initialize(rect);
  220.             this.refresh();
  221.         }
  222.         
  223.         maxItems() {
  224.             if (!this.recipes()) return 0;
  225.             return this.recipes().length;
  226.         }
  227.         
  228.         recipes() {
  229.             return window.SynthesisSystem.getRecipesByWorkstation(
  230.                 window.SynthesisSystem.getCurrentWorkstation()
  231.             );
  232.         }
  233.         
  234.         currentRecipe() {
  235.             return this.recipes()[this.index()];
  236.         }
  237.         
  238.         refresh() {
  239.             this.contents.clear();

  240.             // 检查是否有配方
  241.             const recipes = this.recipes();
  242.             if (!recipes) {
  243.                 this.drawText("没有可用配方", 0, 0, this.contentsWidth, 'center');
  244.                 return;
  245.             }

  246.             // 绘制所有配方
  247.             for (let i = 0; i < this.maxItems(); i++) {
  248.                 this.drawItem(i);
  249.             }
  250.         }
  251.         
  252.         drawItem(index) {
  253.             const recipe = this.recipes()[index];
  254.             if (!recipe) return;
  255.             
  256.             const rect = this.itemRect(index);
  257.             const canCraft = window.SynthesisSystem.hasEnoughMaterials(recipe);
  258.             
  259.             this.changePaintOpacity(canCraft);
  260.             this.drawText(recipe.name, rect.x, rect.y, rect.width);
  261.             this.changePaintOpacity(true);
  262.             console.log("DrawItemIndex是", index, "\n");
  263.         }
  264.         
  265.         isCurrentItemEnabled() {
  266.             const recipe = this.currentRecipe();
  267.             return recipe && window.SynthesisSystem.hasEnoughMaterials(recipe);
  268.         }
  269.     }
  270.    
  271.     // 确认对话框窗口
  272.     class Window_Confirmation extends Window_Command {
  273.         constructor(rect, recipe) {
  274.             super(rect);
  275.             this._recipe = recipe;
  276.             this.refresh();
  277.         }

  278.         makeCommandList() {
  279.             this.addCommand("是", 'yes');
  280.             this.addCommand("否", 'no');
  281.         }
  282.         
  283.         refresh() {
  284.             super.refresh();
  285.             this.drawRecipeInfo();
  286.             
  287.         }
  288.         
  289.         drawRecipeInfo() {
  290.             if (!this._recipe) return;
  291.             
  292.             // 绘制配方名称
  293.             let y = this.lineHeight();
  294.             this.drawText(`合成 ${this._recipe.name} 需要:`, 0, y, this.contentsWidth, 'center');
  295.             y += this.lineHeight() * 1.5;
  296.             
  297.             // 绘制材料需求
  298.             this._recipe.materials.forEach(material => {
  299.                 let itemName = "";
  300.                 let currentCount = 0;
  301.                
  302.                 // 获取材料信息
  303.                 switch (material.type) {
  304.                     case 'item':
  305.                         itemName = $dataItems[material.id].name;
  306.                         currentCount = $gameParty.numItems($dataItems[material.id]);
  307.                         break;
  308.                     case 'weapon':
  309.                         itemName = $dataWeapons[material.id].name;
  310.                         currentCount = $gameParty.numItems($dataWeapons[material.id]);
  311.                         break;
  312.                     case 'armor':
  313.                         itemName = $dataArmors[material.id].name;
  314.                         currentCount = $gameParty.numItems($dataArmors[material.id]);
  315.                         break;
  316.                 }
  317.                
  318.                 // 根据材料是否足够选择颜色
  319.                 const hasEnough = currentCount >= material.count;
  320.                 //this.changeTextColor(hasEnough ? this.normalColor() : this.crisisColor());
  321.                
  322.                 // 绘制材料信息
  323.                 this.drawText(`${itemName} x${material.count} (现有:${currentCount})`, 0, y, this.contentsWidth, 'center');
  324.                 y += this.lineHeight();
  325.             });
  326.             
  327.             // 绘制提示文本
  328.             y += this.lineHeight();
  329.             // this.changeTextColor(this.normalColor());
  330.             this.drawText("要合成该物品吗?", 0, y, this.contentsWidth, 'center');
  331.         }
  332.         
  333.         // 将命令按钮放在窗口底部
  334.         itemRect(index) {
  335.             const rect = super.itemRect(index);
  336.             rect.y = this.contentsHeight - rect.height * 2;
  337.             return rect;
  338.         }
  339.     }
  340.    
  341.     // 插件命令处理
  342.     const _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
  343.     Game_Interpreter.prototype.pluginCommand = function(command, args) {
  344.         _Game_Interpreter_pluginCommand.call(this, command, args);
  345.         
  346.         if (command === 'OpenSynthesis') {
  347.             const workstationType = args[0];
  348.             Window_SynthesisSystem.open(workstationType);
  349.         }
  350.     };
  351. })();
复制代码


以下为测试用的SynthesisRecipe文件内容:
  1. [
  2.   {
  3.     "id": "healing_potion_1",
  4.     "name": "初级金创药",
  5.     "result": {
  6.       "type": "item",
  7.       "id": 9,
  8.       "count": 1
  9.     },
  10.     "materials": [
  11.       {
  12.         "type": "item",
  13.         "id": 7,
  14.         "count": 1
  15.       },
  16.       {
  17.         "type": "item",
  18.         "id": 11,
  19.         "count": 1
  20.       }
  21.     ],
  22.     "workstation": "stove"
  23.   },
  24.   {
  25.     "id": "healing_potion_2",
  26.     "name": "二级金创药",
  27.     "result": {
  28.       "type": "item",
  29.       "id": 9,
  30.       "count": 2
  31.     },
  32.     "materials": [
  33.       {
  34.         "type": "item",
  35.         "id": 7,
  36.         "count": 2
  37.       },
  38.       {
  39.         "type": "item",
  40.         "id": 12,
  41.         "count": 1
  42.       }
  43.     ],
  44.     "workstation": "herbalist_table"
  45.   },
  46.   {
  47.     "id": "iron_sword",
  48.     "name": "铁剑",
  49.     "result": {
  50.       "type": "weapon",
  51.       "id": 5,
  52.       "count": 1
  53.     },
  54.     "materials": [
  55.       {
  56.         "type": "item",
  57.         "id": 236,
  58.         "count": 5
  59.       },
  60.       {
  61.         "type": "item",
  62.         "id": 237,
  63.         "count": 1
  64.       }
  65.     ],
  66.     "workstation": "anvil",
  67.     "requiredSwitch": 10
  68.   }
  69. ]
复制代码

Lv6.析梦学徒

老鹰

梦石
40
星屑
35850
在线时间
6859 小时
注册时间
2012-5-26
帖子
3285

极短24评委极短23参与极短22参与极短21评委老司机慢点开短篇十吟唱者组别冠军开拓者剧作品鉴家

2
发表于 11 小时前 | 只看该作者
我这里开个新工程没有报错啊,可以正常通过脚本打开 Window_SynthesisSystem.open("herbalist_table")
你是怎么测试的,是否用了其它修改了 Window_Selectable 的插件?
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
165
在线时间
18 小时
注册时间
2025-2-4
帖子
16
3
 楼主| 发表于 9 小时前 | 只看该作者
百里_飞柳 发表于 2025-9-6 12:26
我这里开个新工程没有报错啊,可以正常通过脚本打开 Window_SynthesisSystem.open("herbalist_table")
你是 ...

是这样的:这个插件通过脚本或者事件打开都不会报错。我也没有任何其他插件。
报错的时候是运行时。运行的时候能够加载出屏幕左侧的配方选择框,用鼠标点击以后会出错。
点击可用的配方时,在控制台下断点,可以观察到代码175行的onRecipeOk(),再到180行showConfirmationDialog(recipe),注意这个时候会运行到188行this._confirmationWindow = new Window_Confirmation(rect, recipe);=>这一句是关键处,因为它直接调用了Window_Confirmation的构造函数。
而277行Window_Confirmation的构造函数constructor(rect, recipe),只要运行278行super(rect)这一句,就必然会报错。
回复 支持 反对

使用道具 举报

Lv6.析梦学徒

老鹰

梦石
40
星屑
35850
在线时间
6859 小时
注册时间
2012-5-26
帖子
3285

极短24评委极短23参与极短22参与极短21评委老司机慢点开短篇十吟唱者组别冠军开拓者剧作品鉴家

4
发表于 7 小时前 | 只看该作者
本帖最后由 百里_飞柳 于 2025-9-6 15:38 编辑
雪狼的天空 发表于 2025-9-6 13:35
是这样的:这个插件通过脚本或者事件打开都不会报错。我也没有任何其他插件。
报错的时候是运行时。运行 ...


因为 initialize 方法里会调用 refresh,而 refresh 里重绘时会调用 itemRect ,你修改了 itemRect ,
于是我在 itemRect 返回前增加 console.log(rect) ,看到 y 是 nan,
再查看发现应该改成 this.contentsHeight() 返回数值,而不是 this.contentsHeight 返回方法。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
165
在线时间
18 小时
注册时间
2025-2-4
帖子
16
5
 楼主| 发表于 2 小时前 | 只看该作者
百里_飞柳 发表于 2025-9-6 15:36
因为 initialize 方法里会调用 refresh,而 refresh 里重绘时会调用 itemRect ,你修改了 itemRect ,
于 ...

非常感谢!
差不多也在同一时间,我也发现了是constructor里的rect内容中的y被修改成了nan,由此想到可能是修改了rect.y的itemRect函数的问题。于是我把它整个删掉了,这个问题就得到了解决。
还是大神你解释的清楚,发现是contentsHeight函数的问题,需要是contentsHeight()而不是方法。

过去不是做javascript编程的,还在用C#的思维来看待,果然找问题的时候就有好多的想不到。

哎总之非常感谢大神您愿意花费时间帮我!

-----------------------------------
这份代码里面其实还有很多问题。
一个是drawRecipeInfo()绘制配方名称、绘制材料信息和绘制提示文本三个方面,我删了itemRect()以后还是显示不出来。
另外一个是325行this.changeTextColor(hasEnough ? this.normalColor() : this.crisisColor());这一句显示normalColor()和crisisColor()不存在,不知道怎么改,索性就删掉了。
最后一个是还没有做配方多的时候的分页。
不过这些应该不是太大问题,我可以一点一点自己试着改。
回复 支持 反对

使用道具 举报

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

本版积分规则

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

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

GMT+8, 2025-9-6 23:29

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

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