各位读者早午晚上好啊_(:з」∠)_,这里是中学生。
你是否想过要创造一个 “流浪商人” 或 “神秘商人” 这种,可以随机遇见的,像原版的骷髅商人那样的NPC?但直接塞一个常规城镇NPC肯定行不通,这一篇,我们将讲解一下如何创建一个 “类骷髅商人” NPC。
!注意:讲解之前请确保你已熟读这篇文章,因为本篇的很多内容都是基于这篇文章讲解的。
废话不多说,开始正片!
特征
如果骷髅商人的构成和常规城镇NPC一样,也就不会单独拉出来讲,所以我们首先要认识一下骷髅商人的独有特征以及其与常规NPC的区别,这样我们才能更好的理解骷髅商人的写法。
特征一:没有小地图头像
骷髅商人并不会呈现在地图上,这也算是游戏常识。
特征二:不会被地图保存
骷髅商人的一个特征就是如果你退出地图后,它都会像普通生物一样消失不见。这就和一直存在于地图上的常规城镇NPC很不一样了。
特征三:刷新机制不一样
骷髅商人遵循和普通生物一样的刷新机制,在特定环境下随机刷新,离开屏幕太远会消失,并且不会像常规NPC一样达成一定条件后入住。
看完这三条你就会发现:骷髅商人不就是个可以对话、可以卖东西的普通生物么?只要把城镇NPC中关于对话和商店的部分搬过去不就好了?可没有那么简单。接下来,咱们进入实操环节。
设置基础属性
首先,创建一个新项,我们这次叫他 TestSkeletonMerchant
你发现 [AutoloadHead]
不见了?毕竟骷髅商人不需要这个功能,自然也就不会用到。
首先我们来看看 SetStaticDefaults 里的变化:
int npcID = NPCID.Merchant; public override void SetStaticDefaults() { DisplayName.SetDefault("测试骷髅小伙"); Main.npcFrameCount[Type] = Main.npcFrameCount[npcID]; NPCID.Sets.ExtraFramesCount[Type] = NPCID.Sets.ExtraFramesCount[npcID]; NPCID.Sets.AttackFrameCount[Type] = NPCID.Sets.AttackFrameCount[npcID]; NPCID.Sets.AttackType[Type] = NPCID.Sets.AttackType[npcID]; NPCID.Sets.HatOffsetY[Type] = NPCID.Sets.HatOffsetY[npcID]; NPCID.Sets.NPCFramingGroup[Type] = NPCID.Sets.NPCFramingGroup[npcID]; NPCID.Sets.AttackTime[Type] = NPCID.Sets.AttackTime[npcID]; NPCID.Sets.DangerDetectRange[Type] = 500; NPCID.Sets.AttackAverageChance[Type] = 10; //让一个NPC在AI、动画等方面表现的像一个城镇NPC,但是并非一个标准城镇NPC, //如果你想要实现骷髅商人那种可以对话并交易,但是不会出现在入住菜单里、没有幸福度选项、且不会出现在小地图上的NPC, //可以在 NPC.townNPC 设置为 false 时将此选项设置为true。 //若 NPC.townNPC 设置为 true。则这条将不再需要设置。 NPCID.Sets.ActsLikeTownNPC[Type] = true; //该NPC生成时是否会顺便生成一个GivenName, //如果设置了false,那么该NPC生成时将会和普通NPC一样只显示DisplayName NPCID.Sets.SpawnsWithCustomName[Type] = true; NPCID.Sets.TownNPCBestiaryPriority.Add(Type); NPCID.Sets.NPCBestiaryDrawModifiers drawModifiers = new NPCID.Sets.NPCBestiaryDrawModifiers(0) { Velocity = 1f, }; NPCID.Sets.NPCBestiaryDrawOffset.Add(Type, drawModifiers); }
由于骷髅商人不需要幸福度选项,我们也就不用再为他设置 NPC.Happiness
。
乍一看好像没啥变化,但这次出现了两个新的属性: ActsLikeTownNPC
和 SpawnsWithCustomName
。咱们分别来说
首先是 ActsLikeTownNPC
,顾名思义,“让NPC表现得像一个城镇NPC”。这里特指允许让该NPC拥有类似城镇NPC的AI,动画和攻击方式,但是并不会伴生其他城镇NPC特有的属性,比如幸福度按钮和呈现在小地图上。这就是骷髅商人的核心所在。你也可以利用这一条创造各种各样的“非常规城镇NPC”。
其次是 SpawnsWithCustomName
,当这个设置为true时你的NPC在生成时就会随机获得一个SetNPCNameList重写函数内包含的名字。
上面这两个属性好像是城镇NPC自带的功能啊?为什么还要手动设置?这就要牵扯到接下来我们要填写的部分了:SetDefaults。
public override void SetDefaults() { NPC.friendly = true; NPC.width = 18; NPC.height = 40; NPC.aiStyle = NPCAIStyleID.Passive; NPC.damage = 10; NPC.defense = 15; NPC.lifeMax = 250; NPC.HitSound = SoundID.NPCHit1; NPC.DeathSound = SoundID.NPCDeath1; NPC.knockBackResist = 0.5f; NPC.rarity = 2;//设置稀有度 AnimationType = npcID; }
和普通NPC相比,这里缺少了一个对城镇NPC而言十分重要的东西:NPC.townNPC
。正是这个属性为我们自动(或者说强制)设置了很多城镇NPC的特性比如幸福度、生成姓名、刷新入住机制等等。这些好一部分都是骷髅商人不需要的,我们也就没必要再设置了,只需要把我们需要的属性手动打开即可。
NPC.rarity
用来表示NPC的稀有度。数字越高,在稀有生命体分析仪上显示的权重就越大。
由于骷髅商人需要对话功能,但我们又没有设置 NPC.townNPC
,所以我们需要一个重写函数手动打开对话功能:CanChat。
//NPCID.Sets.ActsLikeTownNPC不会自动设置这里 //仍然需要手动设置为true public override bool CanChat() { return true; }
注意一下:这个重写函数并非基于NPC的Type而是基于NPC的单独实例。也就是说,只要你的NPC的这一块设置为true,无论后续它的Type怎么变,都将是可以对话的。(想想被缚NPC)
生成
既然骷髅商人是在环境内随机刷新,那我们就不能再使用CanTownNPCSpawn这个函数了。应当选择普通生物生成时需要的函数:SpawnChance。
public override float SpawnChance(NPCSpawnInfo spawnInfo) { //当 玩家处于洞穴层 且 世吞or克脑 被击败时 以地下敌怪的概率生成 if ((spawnInfo.Player.ZoneDirtLayerHeight || spawnInfo.Player.ZoneRockLayerHeight) && NPC.downedBoss2) return SpawnCondition.Underground.Chance; return 0f; }
ZoneDirtLayerHeight
就是泥土层,ZoneRockLayerHeight
就是岩石层,是构成泰拉世界的地下(除了地狱)的两个基本层次。
商店
上回不是说了商店了吗?没关系,你应该知道骷髅商人会按月相卖东西对吧?咱们就来看看怎么根据月相设置商品:
public override void SetupShop(Chest shop, ref int nextSlot) { shop.item[nextSlot].SetDefaults(ItemID.SpelunkerGlowstick); nextSlot++; //满月时 出售挖掘药水 if (Main.GetMoonPhase() == MoonPhase.Full) { shop.item[nextSlot].SetDefaults(ItemID.MiningPotion); nextSlot++; } //新月时 出售危险感药水 if (Main.GetMoonPhase() == MoonPhase.Empty) { shop.item[nextSlot].SetDefaults(ItemID.TrapsightPotion); nextSlot++; } //任意 峨眉月时 出售探矿药水 if (Main.GetMoonPhase() is MoonPhase.QuarterAtLeft or MoonPhase.QuarterAtRight) { shop.item[nextSlot].SetDefaults(ItemID.SpelunkerPotion); nextSlot++; } //任意 弦月时 出售狩猎药水 if (Main.GetMoonPhase() is MoonPhase.HalfAtLeft or MoonPhase.HalfAtRight) { shop.item[nextSlot].SetDefaults(ItemID.HunterPotion); nextSlot++; } //任意 凸月时 出售建筑工药水 if (Main.GetMoonPhase() is MoonPhase.ThreeQuartersAtLeft or MoonPhase.ThreeQuartersAtRight) { shop.item[nextSlot].SetDefaults(ItemID.BuilderPotion); nextSlot++; } }
Main.GetMoonPhase
就是原版的获取月相的方法。虽然还有一个 Main.moonPhase
同样可用,但RE并没有提供MoonPhaseID这类的东西,不过为了可读性,还是加入了 MoonPhase
这个枚举类。同时我们也仅仅是想要获取月相,所以综合考虑,使用这个方法是更佳的选择。
Profile?
骷髅商人这类NPC一般不需要设置TownNPCProfile,如果你有需求那加上也无妨。
其他方面就和常规城镇NPC基本一致了,稍微设置一下姓名啦,攻击属性啦。一个类骷髅商人NPC就组装完毕了!
展示我们的测试骷髅小伙
喜闻乐见的展示环节:
总结
是不是很简单?只需要用一个常规城镇NPC稍加改造即可!
我是中学生,一个专门霍霍城镇NPC的屑仍在学习的模组作者,我们下次再见_(:з」∠)_
(这个屑已经越来越懒得写总结了)
我認為其實特徵三應該再細分成三個特徵來看,
1.可否入住
2.是否會自動消失
3.生成與活動特性
因為要細分NPC的種類,目前共有四五種。
常駐型:(寵物型也很類似這種,故這兩類放在一起)
1.有 小地圖頭像 (且會被淹死)
2.會 被地圖保存
3.可 入住
4.不會 自動消失 (除非手動或代碼消失)
5.會在一定範圍內活動,但可能被手動改變位置 (生成條件說來複雜)
[這一兩類就像平常那樣就不必多說。]
骷髏商人型:
1.沒有 小地圖頭像 (也不會被淹死)
2.不會 被地圖保存
3.不可 入住
4.會 自動消失 (只要玩家遠離一段距離)
5.會在一定範圍內活動,生成條件就像隨機生物一樣
[所以這類可以看成跟常駐類是幾乎為相對特性的]
然而之後的兩類 才是作mod代碼比較困難的。
旅商型:
1.有 小地圖頭像 (且會被淹死)
2.不會 被地圖保存 (一旦離開遊戲後只能等下次自動生成)
3.不可 入住
4.(玩家遠離)不會 自動消失 (但離開遊戲或代碼有條件的會讓他消失,可再次生成)
5.只會在一定範圍內活動,但出現位置與生成條件卻很扭,在城鎮NPC附近生成,但一旦決定位置後就不會被改變
[這一類還蠻尷尬的,不屬於前兩類的定義,然而之後的類型更加奧秘與困難]
老人型:
1.沒有 小地圖頭像 (也不會被淹死)
2.會 被地圖保存
3.不可 入住
4.不會 自動消失 (但代碼有條件的會讓他消失,可能無法再次生成)
5.只會在一定範圍內活動,且生成位置永遠就在固定位置,也無法被手動改變位置
[這一類似乎更尷尬,不屬於前兩類的定義,卻跟旅商類卻是部分相對特性的]
然而老人型因為tmod API的緣故,想用代碼實現老人類就變得非常困難了。
而目前原版的玩家是沒辦法召喚出任何村人類型的NPC來,能召喚的僕從也只是彈幕罷了。
但Boss生物卻可能召喚出更多敵對的NPC生物來。
如果想做出玩家可召喚的NPC,卻又不想變成常駐型NPC,該採取怎樣的策略呢?
首先,第一个,关于特征三。
这里的特征划分主要是为了和普通城镇NPC区分,因而没有进一步细分。
第二个,关于老人NPC。
要实现其实不难,只需要固定住NPC.homeTile即可,ModNPC内存在一个叫做NeedSaving的重写函数,用这个即可实现任何NPC的保存。
第三个。
你是想实现城镇NPC样式的召唤物么?
手动召唤的话我认为你应该去基础NPC或Boss篇查看相关资料
不可入住这一点可以参考现在的骷髅商人和将来我要写的旅商NPC(或者直接去ExampleMod看)
如果你还有其他问题可以加群询问。
喔,感謝回覆。不知道這個網站的IP是否在中國,之前發表過的回覆不知何故會失敗。
另外很抱歉,本人非中國人,故沒有所謂的QQ群。
說實在的原版雖然有我說的這四五類,但並非能應付任何情況。
例如:如果希望NPC能像原神的凱瑟琳那樣,該如何實現?看起來最類似老人NPC,因為希望
能 被地圖保存,不可 入住,不會 自動消失,生成位置在固定位置(且無法被玩家改變),
但又希望 能 在小地圖出現頭像,
這又該如何實現呢?
(不過如果一個世界有太多的凱瑟琳的話,會很佔記憶體吧)
其實我是想實現可被玩家召喚的戰鬥型村人NPC,但又希望不同玩家召喚的NPC各自獨立運作(各一個),
也就是有不同玩家召喚專屬的NPC,以避免出現爭搶召喚的情況,但如何聯機同步又是另外的問題。
而我所希望被玩家召喚的村人NPC,我思考一番希望符合的條件有
1.沒有 小地圖頭像
2.不會 被地圖保存
3.不可 入住
4.當玩家遠離一段距離 不會 立刻自動消失,但一直忽略的話會在必要時自動消失,避免佔記憶體
5.生成條件為玩家召喚,改變位置為玩家召喚處,活動範圍由玩家選擇的策略決定
看起來這些條件很類似骷髏商人型,但第4、5點就不符合骷髏商人型。
然而如何決定活動範圍是個複雜的問題,有時希望他一直跟著玩家,但寫AI尋找路徑是很困難的事,
有時希望他能自動打敵對怪(成為玩家的戰鬥夥伴),當生命值太低時能尋找遮蔽處躲避敵人等。
但我認為最困難的莫過於AI尋路,時常有太高而跳不上去,寫得不好的話,甚至可能落入深淵起不來,
遇到牆壁或阻礙時如何尋找可走的路徑是個相當困難的問題。(當然可穿牆的就另當別論)
首先,方便说一下你是哪里人吗?这样有助于我们找到一个更方便沟通的方法。
其次,你的需求可能并不是三言两语就能解决的,评论区里肯定不适合。
想办法建立一个更方便的沟通渠道吧。
如果我談論的對象不是中國人,就不必那麼難以啟齒了。但我可以給你提示讓你猜。
慣用繁體中文,使用Google搜尋引擎不會遇到被牆的情況。
至於聯絡方式,我目前有的其中一組e-mail: gatanada@protonmail.com 。
如果你問我為什麼用protonmail,其中一個原因是考慮到是否會被中國的防火長城擋住,
(如果使用的是gmail,那麼就會有大概率會被牆。儘管protonmail用起來沒那麼方便。)
相信你應該會比我更清楚中國的情況吧。
如果你身在中國而有些話不方便說的話,請告知我一聲,你是否目前身在中國。