各位读者早午晚上好鸭_(:з」∠)_,这里是中学生,一个正在学习中的萌新,好不容易拿到了编写文章的机会,今天由我来介绍——如何制作一个基础城镇NPC
(中学生:来来来!各位NPC们,干活啦!)
关于分类的那些事
首先我们来康康城镇NPC都分为哪些:(玩过TR的可以选择跳过)
1:按照入住方式,分为三类:常驻NPC,游商NPC,和第三类NPC
常驻类,你家向导和其他那些需要一座房子才可以入住并且死后会复生的NPC即为常驻类,这也是本文的主要内容
游商,像旅行商人那样,白天有几率刷新,黄昏时消失的NPC,ExampleMod的Example Traveling Merchant有提供例子,这里不再多说
第三类,也就是像骷髅商人和地牢老人那样的,允许对话或交易,却无法入住的NPC,之所以叫第三类是因为实在想不出一个好名词(起名废×)
2:按照攻击方式,分为四类:射手类,投掷类,法师类,战士类。(可以按照玩家职业理解只不过投手取代了召唤师)
射手类:还是像你家向导那样,单次攻击只显示一帧动画,发射弹幕;一般这种NPC会持有武器,后面会讲如何给NPC设置武器。
投掷类:像商人一样,单次攻击有四帧动画,发射弹幕,不持有武器;这种NPC最为常见,也是最好做的NPC。
法师类:和魔法师一样,单次攻击有两帧,发射弹幕,一般会自带一个(发光发热的)魔法光环;同样的,不持有武器。
战士类:和染料商一样, 单次攻击有四帧动画,不发射弹幕,持有武器 。
好了,讲了一堆关于分类的废话,我们终于能进入正题了
先别疑惑,前面这些分类准则后面会用上的
设置基础属性
首先,用你的VS新建一个项目吧!这里就叫TestNPC
你可能注意到了上面的[AutoloadHead],这段代码就是自动加载一个NPC的小地图图标用的,小地图图标的名字为:该NPC类名加_Head(比如TestNPC_Head)。
Boss的小地图图标也是如此,只不过换成了[AutoloadBossHead],后缀名也改成了_Head_Boss
好,下一步就是SetDefaults…先等一下,这里还有一点东西:
public override string[] AltTextures { get { return new string[] { "TeaNPC/NPCs/CODES/TestNPCParty" }; } }
看到这段代码了没,这是你的NPC的替换材质,作用就是在开始派对时换上该材质,当然,没有的话可以不写
然后,输入public override 选择方法 Autoload
public override bool Autoload(ref string name) { return mod.Properties.Autoload; }
这是一个选带项,你可以在这里动一下手脚(比如在return后面带上是否加载某模组),因为如果autoload返回为false,该NPC就不会出现
接下来,终于来到SetDefaults环节,当然,别忘了SetStaticDefaults,大部分NPC行为相关的属性都在这里:
public override void SetStaticDefaults() { DisplayName.SetDefault("测试小伙"); //该NPC的游戏内显示名 Main.npcFrameCount[npc.type] = 25; //NPC总共帧图数,一般为16+下面两种帧的帧数 NPCID.Sets.ExtraFramesCount[npc.type] = 5; //额外活动帧,一般为5 //关于这个,一般来说是除了攻击帧以外的那几帧(包括坐下、谈话等动作),但实际上填写包含攻击帧在内的帧数也不影响(比如你写9),如果有知道的可以解释一下。 NPCID.Sets.AttackFrameCount[npc.type] = NPCID.Sets.AttackFrameCount[NPCID.Merchant]; //攻击帧,这个帧数取决于你的NPC攻击类型,射手填5,战士和投掷填4,法师填2,当然,也可以多填,就是不知效果如何(这里直接引用商人的) NPCID.Sets.DangerDetectRange[npc.type] = 1000; //巡敌范围,以像素为单位,这个似乎是半径 NPCID.Sets.AttackType[npc.type] = 0; //攻击类型,默认为0,想要模仿其他NPC就填他们的AttackType NPCID.Sets.AttackTime[npc.type] = 240; //单次攻击持续时间,越短,则该NPC攻击越快(可以用来模拟长时间施法的NPC) NPCID.Sets.AttackAverageChance[npc.type] = 5; //NPC遇敌的攻击优先度,该数值越大则NPC遇到敌怪时越会优先选择逃跑,反之则该NPC越好斗。 //最小一般为1,你可以试试0或负数LOL~ //NPCID.Sets.MagicAuraColor[base.npc.type] = Color.Purple; //如果该NPC属于法师类,你可以加上这个来改变NPC的魔法光环颜色,这里用紫色 }
public override void SetDefaults() { npc.townNPC = true; //必带项,没有为什么 //加上这个后NPC就不会在退出地图后消失了,你可以灵活运用一下 npc.friendly = true; //如果你想写敌对NPC也行 npc.width = 22; //碰撞箱宽 npc.height = 32; //碰撞箱高 npc.aiStyle = 7; //必带项,如果你能自己写出城镇NPC的AI可以不带 //PS:这个决定了NPC是否能入住房屋 npc.damage = 10; //碰撞伤害,由于城镇NPC没有碰撞伤害所以可以忽略 npc.defense = 15; //防御力 npc.lifeMax = 250; //生命值 npc.HitSound = SoundID.NPCHit1; //受伤音效 npc.DeathSound = SoundID.NPCDeath1; //死亡音效 npc.knockBackResist = 0.5f; //抗击退性,数字越小抗性越高,0就是完全抗击退 animationType = NPCID.Merchant; //如果你的NPC属于除投掷类NPC以外的其他攻击类型,请带上,值可以填对应NPC的ID }
关于AttackType讲一下:
默认的NPC攻击类型都是投掷类NPC,如果我们想要模仿其他类型的NPC呢?最简单的方法就是套用这些NPC的属性,如果你在AttackType里填了法师的ID,那么你的NPC将会拥有魔法师那样的攻击方式,并且自带一个白色魔法光环,注意!,如果你填了其他NPC的ID,我的建议是连animationType也一起套用,防止出现一些意外情况
关于animationType也讲一下:如果使用了这个,建议看看你套用的NPC的实际帧数,比如渔夫NPC虽然算是投掷类,但他只有23帧,不同于商人的25帧;如果帧数写错了或者 animationType 套错了,你的NPC可能就会变成“变形金刚”()
接下来..AI和FindFrame就跳过了,因为我们套用了原版城镇NPC的行为
作为一个城镇NPC,必然要有入住条件,否则你就没法见到他了(在不使用其他手段的情况下),所以,我们将要添加一个能让NPC入住的条件判定:
public override bool CanTownNPCSpawn(int numTownNPCs, int money) { //该入住条件为:已拥有三个或以上的城镇NPC,玩家拥有钱数大于等于一银,且击败克苏鲁之眼 if (numTownNPCs >= 3 && money >= 100 && NPC.downedBoss1) { return true; } return false; }
然后是该NPC可能会拥有的名字,这个可以不加
public override string TownNPCName() { switch (WorldGen.genRand.Next(3)) { case 0: return "sans"; case 1: return "Frisk"; default: return "Chara"; } }
(不要在意为啥是这些名字啦!)
对话与商店
你当然可以和城镇NPC对话,所以让我们给他加一些对话内容
public override string GetChat() { WeightedRandom<string> chat = new WeightedRandom<string>(); { if (!Main.bloodMoon && !Main.eclipse) { //无家可归时 if (npc.homeless) { chat.Add("我想我们已经认识对方了,所以....能给个住的地方吗?"); } else { chat.Add("你好!"); } } //日食时 if (Main.eclipse) { chat.Add("我相信你可以挺过去,让我没事就行!"); } //血月时 if (Main.bloodMoon) { chat.Add("不!不要血月!!!"); } return chat; } }
你想让这个NPC卖点东西?我们首先需要给他设置一下对话按钮,不然和他对话时就只有一个关闭按钮了
public override void SetChatButtons(ref string button, ref string button2) { //翻译“商店文本” button = Language.GetTextValue("LegacyInterface.28"); }
你可以给他加入第二个按钮,设置button2就行
光有按钮不行,还得给按钮加功能,我要让这个按钮开启商店
public override void OnChatButtonClicked(bool firstButton, ref bool shop) { //如果按下第一个按钮,则开启商店 if (firstButton) { shop = true; } //在if之后可以写第二个按钮的作用,自己想想能加点啥 }
接下来,使用SetupShop来规定该NPC的商品
public override void SetupShop(Chest shop, ref int nextSlot) { //设置商品 shop.item[nextSlot].SetDefaults(ItemID.StarCloak); //设置价格 shop.item[nextSlot].value = 10; //切换到下一栏,否则你的NPC只能卖一件物品 nextSlot++; //你觉得这件商品会被显示么 shop.item[nextSlot].SetDefaults(ItemID.StardustAxe); nextSlot++; }
好了!一个能和你聊天以及做PY交易的NPC做好了!
(时间转到晚上,从你的小镇的某间房子里传来一声惨叫)
哦不!!我们的测试小伙被怪物杀死了!这种情况经常出现,我们的对策是什么?加厚防御和血量?不!我们要赋予这个NPC还击的能力:
NPC攻击
//设置该NPC的近战/抛射物伤害和击退(取决于NPC攻击类型) public override void TownNPCAttackStrength(ref int damage, ref float knockback) { damage = 36; knockback = 3f; }
//NPC攻击一次后的间隔 public override void TownNPCAttackCooldown(ref int cooldown, ref int randExtraCooldown) { cooldown = 1; randExtraCooldown = 1; //间隔的算法:实际间隔会大于或等于cooldown的值且总是小于cooldown+randExtraCooldown的总和(TR总整这些莫名其妙的玩意) }
//弹幕设置 public override void TownNPCAttackProj(ref int projType, ref int attackDelay) { projType = ProjectileID.TopazBolt; //使用黄玉法杖的弹幕 attackDelay = 120; //NPC在出招后多长时间才会发射弹幕 }
注意:如果你的NPC是近战型,就算加了这个,他也打不出弹幕来。
//弹幕的向量相关 public override void TownNPCAttackProjSpeed(ref float multiplier, ref float gravityCorrection, ref float randomOffset) { multiplier = 15f; //弹幕射速 gravityCorrection = 0f; //重力修正,描述作用是抛射物在发射时向上偏移的程度,但我感觉影响不大 randomOffset = 5f; //这个是NPC的攻击精准度,数值越低瞄的越准,最低为0 }
//射手NPC专属:手持武器(选带项) public override void DrawTownAttackGun(ref float scale, ref int item, ref int closeness) { scale = 1f; //大小 item = ItemID.StarCannon; //手持武器类型,这个是物品形式,必须有相关物品才能应用 closeness = 18; //武器更接近NPC(以像素为单位, 数字越大离NPC越近, 当武器位置不对时调整) }
//近战NPC专属:手持武器(默认为铁剑) public override void DrawTownAttackSwing(ref Texture2D item, ref int itemSize, ref float scale, ref Vector2 offset) { item = Main.itemTexture[ItemID.BreakerBlade]; //破坏者大剑hhhhh(注意,这里的Item是Texture2D形式,也就是说,只要有材质就够了) //itemSize这个属性目前意义不明,本质上和下面的TownNPCAttackSwing差不多,平时用不到 scale = 1f; //贴图大小,和实际尺寸无关 //offset这个向量值是决定武器绘制在NPC的哪个位置,平时不用 }
//近战NPC专属:挥动武器的实际大小 public override void TownNPCAttackSwing(ref int itemWidth, ref int itemHeight) { itemWidth = 50; itemHeight = 50; }
//法师NPC专属:魔法光环的照明范围,最多起装饰作用(选带项) public override void TownNPCAttackMagic(ref float auraLightMultiplier) { auraLightMultiplier = 2f; }
好的!这样一来,这个NPC就拥有了作战能力,他可以自己保护自己了!
(测试小伙被僵尸杀死了,死因是攻击速度过慢,反射弧过长)
………..
(╯‵□′)╯︵┻━┻
我们先看看这个NPC做成啥样了吧:
展示我们的测试小伙
这就是我们的测试小伙,一个城镇NPC做好了!
一些有用的小提示
上面那些关于NPC攻击设置函数并非只能填写固有内容,TownNPCAttackProj表示的是当这个NPC发射弹幕时的状态,你可以在里面写一些奇奇怪怪的东西
即使近战NPC无法使用TownNPCAttackProj,我们还是可以在TownNPCAttackSwing里动手脚
这里补充一个小知识:每一种不同的攻击类型都对应了该NPC不同的ai[0]值,近战为15,魔法为14,投掷为13,远程为12,运用好这些,你能搞出更多骚操作来!
更多拓展:你可以在PreAI或者PreDraw这块对NPC动手脚以进行更自由的编辑,这些都在前面的教程中提到过,当然,别忘了维持原有的行为。
好了,如何创建一个基础城镇NPC,就是这样!你觉得你学会了么?
不如我们来布置一点作业测试一下好了(你连怎么布置都不会好吗)
本篇教程由TeaNPCs和来自Fargo’s Mod的几位NPC以及Terraria NPCs提供展示环节_(:з」∠)_
不愧是你,我会了
暖贴
暖暖但是SetDefaults和SetStaticDefaults拼错了!!
啊这(现在已经改过来了×)
好,i了
ut好评
好耶!会了(应该吧