跳至正文

从零开始的NPC编写

Hello,我是steve_666,一个模组,怪物是不可或缺的部分(新物品肯定不能全靠交易、挖矿合成获得啊)

现在,我们来写一个基础的npc吧

创建&属性


首先,要用 public class XXX : ModNPC 来创建一个基础的npc类

当然,npc也要有一个图片,这里选用的是原版的僵尸新郎的图片

僵尸新郎

接下来就是属性的部分,与弹幕、物品相同,NPC的属性也可以用 SetStaticDefaultsSetDefaults 来定义

SetStaticDefaults 很好理解,也很容易写,主要就是名字和帧图数量等

僵尸测试员是瞎取的,不要在意啦

接下来就是 SetDefaults 了,首先让我们看看代码

public override void SetDefaults()
{
    npc.width = 34;
    npc.height = 162/3;
    npc.damage = 200;
    npc.lifeMax = 1100;
    npc.defense = 100;
    npc.knockBackResist = 0f;
    npc.aiStyle = -1;
    npc.HitSound = SoundID.NPCHit1;
    npc.DeathSound = SoundID.NPCDeath6;
    npc.value = Item.buyPrice(0, 0, 15, 0);
    npc.buffImmune[BuffID.Poisoned] = true;
    npc.buffImmune[BuffID.Venom] = true;
    banner = npc.type;
    bannerItem = ItemID.Gel;
    Main.npcFrameCount[npc.type] = 3;
    npc.noGravity = false;
    npc.noTileCollide = false;
    aiType = -3;//这边为了方便直接写了ID(绝对不是懒
    animationType = -3;
    npc.boss=true;
}

是不是看上去很多?来,我们一条一条解析

npc.widthnpc.height 是NPC的长和宽,由于NPC是帧图,所以这个也是每一帧的长和宽。

npc.damagenpc.lifeMaxnpc.defense就不用多说了吧,毕竟从字面上就可以看出

npc.knockBackResist 需要注意一下,虽然翻译为击退抗性但是这个值越大反而受到的击退越高设置成0为免疫击退!

npc.HitSoundnpc.DeathSound是受伤和死亡的声音

bannerbannerItem 是他们的旗帜类型和旗帜掉落物品,掉落物品一般是设置成这个NPC的旗帜物品ID,当然你想设置别的也可以 ,比如我这是凝胶 ,而 banner 则一般是这个npc的类型,即 npc.type ,当然,设置成别的也可以,比如你的npc是僵尸的变种,你想要它的击杀数算到僵尸的击杀数里,你就可以用僵尸的ID。它们的前面没有npc.

banneritem改成凝胶的后果

接下来就是重点 npc.aiStyle

比如你想要这个NPC像史莱姆一样,你就可以填入史莱姆的AI类型,可问题来了,这么多史莱姆,我只想用猩红史莱姆的AI,该怎么办?

确实,所以 aiStyle 只能复制一个大类的AI,要精确到单独的NPC还得用 aitypeaitype 填入单独的ID即可。但是这样子,还是有一个问题,那就是,我们的NPC它的动画没有完全复制,所以,我们的 animationType 就要来了,它可以完全套用另一个帧图动画,也是填NPC的类型ID,我这里填的是史莱姆的ID。但是这两个属性的前面都没有npc.

还有很多比如 npc.buffImmune 后面的 [] 里面只要填buff的ID再加上 =true 即可让npc免疫这个buff,npc.value 是npc掉落的钱,npc.noGravity控制了npc是否受重力影响,而 npc.noTileCollide 则代表了npc是否受可以穿墙(如果 npc.noGravitytrue,那么这个最好为false,否则npc会掉到地狱底层),npc.boss如果为true会让TR把它标记成一个boss,他就会死亡掉落生命恢复药水、大量心,并且还会有“XXX已被打败”等!

实用の重写函数:自然生成NPC


npc属性搞定了,但是这个npc不会自然生成,所以我们要定义生成几率

public override float SpawnChance(NPCSpawnInfo spawnInfo)
{
    if (Main.bloodMoon)
        return 1.0f;
    return 0.25f;
}

这段代码的意思是:在血月时有1.0(注意:1.0的生成几率很高!)的几率生成,其他时候只有0.25的几率生成。

而如果我们想要让它的生成几率是地表史莱姆的0.5倍时,我们可以:

public override float SpawnChance(NPCSpawnInfo spawnInfo)
{
    return SpawnCondition.OverworldDaySlime.Chance * 0.5f;
}

SpawnCondition 里面预制了很多原版生物生成条件

如果我们想要一些奇奇怪怪的生成位置,比如…在下雨的白天的神圣之地的天空中的黑曜石上有0.1的几率生成

首先,下雨的我们可以用Main.raining,如果在雨它会返回true否则会返回false

所以,如果我们想让一个敌怪只在下雨时有0.25生成,那么可以

public override float SpawnChance(NPCSpawnInfo spawnInfo)
{
	
    if (Main.raining)
        return 0.25f;
    return 0;
}

当然还有很多Main函数,比如:

Main.hardMode 会在困难模式返回true,Main.expertMode 会在专家模式返回true,Main.dayTime 会在白天返回true(晚上返回false),还有很多Main.的那种,这里只说比较常用的,别的交给自动补全吧!

NPC.AnyNPCs(NPCID.IceGolem) 如果世界上有冰傀儡,就返回true(召唤师怪物预定)

Math.Abs(spawnInfo.spawnTileX - Main.spawnTileX) > Main.maxTilesX / 3 – 如果生成图块位于地图的外侧三分之一上,则为 true

NPC.waveNumber 事件期间的波数,会返回一个值(全地图式傻蛋撒旦军队预定)

NPC.CountNPCS(NPCID.IceGolem) < 2 如果世界上存在少于2个NPC,则为真(有召唤上限的召唤师预定)

当然这些还不够,如果我们要让怪物在大气层生成,该怎么办呢?

在官网中有一张图:

一张位置图

这就很好了,但是直接在if里面写上 ZoneSkyHeight ,如果不出意外的话,那这段代码就肯定出了意外。

那要怎么写呢。。。等等,我们重写函数中的 spawnInfo 还没用啊,所以我们可以。。。

if(spawnInfo.player.ZoneSkyHeight) return 1.0f;

OHHH,成功了!!!!

例子:

return spawnInfo.spawnTileType == mod.TileType<Tiles.CrystalBlock>() ? .1f : 0f;//在自己的物块上生成(类if格式:条件 ? .成立数值:不成立数值)
return spawnInfo.player.GetModPlayer<modplayer2>().wuhu ? .1f : 0f;//当modplayer2的wuhu属性为true时生成
//在丛林神庙中生成
return spawnInfo.spawnTileType == TileID.LihzahrdBrick && spawnInfo.lihzahrd ? .1f : 0f;

实用の重写函数:掉落物


要用掉落物,我们能用NPCLoot来定义

public override void NPCLoot()
{
    // 2% 几率掉落
    if (Main.rand.Next(50) == 0)
    {
        Item.NewItem((int)npc.position.X, (int)npc.position.Y, npc.width, npc.height, 物品ID,数量);
        //具体内容可以试试自动补全
    }
}

实用の重写函数:AI


public override void AI()
{
}

这个以后会讲它的编写

实用の重写函数:FindFrame


NPC的动画也是要自己编写的(用了animationType的当我没说)

与弹幕不同的是,NPC在frame上面,有专门的函数TML区别对待,双标实锤

public override void FindFrame(int frameHeight)
{
//代码放这
}

NPC的帧数很奇怪,它是 npc.frame.Y 来控制的,这个东西,它的更改是精确到像素的!并不是像弹幕一样精确到高度的,所以要用到这个重写函数。

注意到那个 frameHeight 了吗?它是每一帧的高度。

一般来说,我们只需要设置 npc.frame.Y 为 第X个帧 * frameHeight 就可以切换到第X帧了。当然,注意下标是从0开始的。

小结:


这一讲的内容基本结束了,虽然没讲自定义AI,但是学会后最好自己试着写一下AI,即使是一个定时发射弹幕的AI也行。

《从零开始的NPC编写》有5个想法

  1. 对于在特定生物群系生成的问题,可以用if(spawnInfo.player.XXX),一下是XXX列表
    ZoneDungeon – 地牢
    ZoneCorrupt – 腐化
    ZoneHoly – 神圣
    ZoneMeteor – 流星
    ZoneJungle – 丛林
    ZoneSnow – 雪原
    ZoneCrimson – 血腥
    ZoneWaterCandle – 水蜡烛附近
    ZonePeaceCandle – 和平蜡烛附近
    ZoneTowerSolar – 日耀柱
    ZoneTowerVortex – 星璇柱
    ZoneTowerNebula – 星云柱
    ZoneTowerStardust – 星尘柱
    ZoneDesert – 沙漠
    ZoneGlowshroom – 发光蘑菇
    ZoneUndergroundDesert – 地下沙漠
    ZoneSkyHeight – 天空层
    ZoneOverworldHeight – 地表层
    ZoneDirtLayerHeight – 泥土层
    ZoneRockLayerHeight – 岩石层
    ZoneUnderworldHeight – 地狱层
    ZoneBeach – 沙滩
    ZoneRain – 雨
    ZoneSandstorm – 沙尘暴

  2. Pingback: 创建一个基础城镇NPC - 裙中世界

  3. Pingback: Boss的创建与基础重写函数 - 裙中世界

发表回复