跳至正文

创建一个基础城镇NPC(1.3版本)

各位读者早午晚上好鸭_(:з」∠)_,这里是中学生,一个正在学习中的萌新,好不容易拿到了编写文章的机会,今天由我来介绍——如何制作一个基础城镇NPC

(中学生:来来来!各位NPC们,干活啦!)


关于分类的那些事

首先我们来康康城镇NPC都分为哪些:(玩过TR的可以选择跳过)

1:按照入住方式,分为三类:常驻NPC游商NPC,和第三类NPC

常驻类,你家向导和其他那些需要一座房子才可以入住并且死后会复生的NPC即为常驻类,这也是本文的主要内容

一个典型的常驻NPC,其入住房屋会标有旗帜(这不废话嘛×)
(该NPC来自FargowiltasMod)

游商,像旅行商人那样,白天有几率刷新,黄昏时消失的NPC,ExampleMod的Example Traveling Merchant有提供例子,这里不再多说

一个典型游商(NPC),注意入住列表里没有他的头像

第三类,也就是像骷髅商人和地牢老人那样的,允许对话或交易,却无法入住的NPC,之所以叫第三类是因为实在想不出一个好名词(起名废×)

一个第三类NPC,这里提一下:地牢老人的“家”就是地牢门口

2:按照攻击方式,分为四类:射手类投掷类法师类战士类。(可以按照玩家职业理解只不过投手取代了召唤师)

射手类:还是像你家向导那样,单次攻击只显示一帧动画,发射弹幕;一般这种NPC会持有武器,后面会讲如何给NPC设置武器。

射手类NPC正在攻击

投掷类:像商人一样,单次攻击有四帧动画,发射弹幕,不持有武器;这种NPC最为常见,也是最好做的NPC。

投掷类NPC正在攻击,图中所示为商人攻击帧数的第三帧

法师类:和魔法师一样,单次攻击有两帧,发射弹幕,一般会自带一个(发光发热的)魔法光环;同样的,不持有武器。

法师类NPC正在攻击,注意魔法师是原版中唯三具有多抛射物能力的城镇NPC,另外两个是蒸汽朋克人蘑菇人

战士类:和染料商一样, 单次攻击有四帧动画,不发射弹幕,持有武器 。

战士类NPC正在攻击

好了,讲了一堆关于分类的废话,我们终于能进入正题了

先别疑惑,前面这些分类准则后面会用上的


设置基础属性

首先,用你的VS新建一个项目吧!这里就叫TestNPC

城镇NPC属于生物,所以要继承ModNPC类

你可能注意到了上面的[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的魔法光环颜色,这里用紫色
}
一个法师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做成啥样了吧:


展示我们的测试小伙

一只叫sans的测试小伙,材质来自ExampleMod
聊天,以及我们添加的商店按钮
由于星尘斧属于不可获得物,所以这里只有一种商品
进攻!!(打的真“准”
派对粉(帽子怎么戴的啊喂!

这就是我们的测试小伙,一个城镇NPC做好了!


一些有用的小提示

上面那些关于NPC攻击设置函数并非只能填写固有内容,TownNPCAttackProj表示的是当这个NPC发射弹幕时的状态,你可以在里面写一些奇奇怪怪的东西

在 TownNPCAttackProj 里设置NPC发射弹幕的方式,这样还可以忽略TownNPCAttackProjSpeed

即使近战NPC无法使用TownNPCAttackProj,我们还是可以在TownNPCAttackSwing里动手脚

食我泰拉刃杂修!

这里补充一个小知识:每一种不同的攻击类型都对应了该NPC不同的ai[0]值,近战为15,魔法为14,投掷为13,远程为12,运用好这些,你能搞出更多骚操作来!

LOL

更多拓展:你可以在PreAI或者PreDraw这块对NPC动手脚以进行更自由的编辑,这些都在前面的教程中提到过,当然,别忘了维持原有的行为。


好了,如何创建一个基础城镇NPC,就是这样!你觉得你学会了么?

不如我们来布置一点作业测试一下好了(你连怎么布置都不会好吗

本篇教程由TeaNPCs和来自Fargo’s Mod的几位NPC以及Terraria NPCs提供展示环节_(:з」∠)_

《创建一个基础城镇NPC(1.3版本)》有7个想法

发表回复