我看了一眼,原来旧教程有这个啊,那我不写了
经过Global系列的探索,你们应该也大概猜到 ModPlayer
是长什么样子得了,本篇教程就来详细介绍一下这个类里的常用函数,以及如何自定义玩家属性达到特殊目的(重点)。
那么就新建文件,让你的类继承 ModPlayer
吧。
常用函数
善用补全哦~
注:在继承了 ModPlayer
的类中,你可以像类似于 ModXXX
那样,直接用 Player
就能获取当前玩家实例。当然你在这里写静态方法的话那一样是不能用这个的。
修改玩家的初始物品
有两个函数, AddStartingItems
和 ModifyStartingInventory
,前者更早执行。
// 这个函数可以给玩家添加初始物品 // 这里给的参数是说玩家是否是中核难度 // 因为中核玩家死亡时物品栏会被清空然后重置为初始开局物品 public override IEnumerable<Item> AddStartingItems(bool mediumCoreDeath) { // 返回一个集合,不管是list还是数组都可以 return new[] { // 给玩家9999个木头作为初始物品 new Item(ItemID.Wood, 9999), // 再附赠1个香蕉 new Item(ItemID.Banana) }; } // 这个函数用于修改玩家的初始物品,可以把原版自带的东西删掉 public override void ModifyStartingInventory(IReadOnlyDictionary<string, List<Item>> itemsByMod, bool mediumCoreDeath) { // Terraria为原版的键,用它就能拿到原版提供的初始物品 // 把原版提供的铜镐删掉 itemsByMod["Terraria"].RemoveAll(item => item.type == ItemID.CopperPickaxe); // 把刚刚我们的Mod提供的木头也删掉(乐 itemsByMod[Mod.Name].RemoveAll(item => item.type == ItemID.Wood); // 这里的键名即是Mod的内部名 }
当玩家进入世界
完完全全的翻译,它叫 OnEnterWorld
。
public override void OnEnterWorld() { // 这个是随机传送药水的效果 // 也就是当玩家进入世界的时候就会被扔到一个怪地方 Player.TeleportationPotion(); }
一大堆更新
下面按照更新先后顺序。可用性指是否推荐用于更新自定义属性及其加成。
函数名 | 描述 | 调用顺序 | 备注 | 可用性 |
---|---|---|---|---|
PreUpdate | 预更新 | 在所有东西更新之前 | 无 | 禁止 |
ResetEffects | 重设效果 | 最早的一项更新 | 用于每帧重置自定义变量 | 禁止 |
PreUpdate Buffs | 预更新Buff | 在玩家身上的Buff更新之前 | 此时玩家的几乎所有 “某效果是否启动”的字段 都是False | 不推荐 |
PostUpdateBuffs | Buff更新后 | 在玩家身上的Buff更新之后 | 现在很多效果都已经启动了 | 不推荐 |
Update Equips | 装备更新 | 乱七八糟的装在身上装备 绑定的效果bool都启动之后 | 你可以在这里判定玩家是否 拥有某个效果 | 不推荐 |
PostUpdate Equips | 装备更新后 | 装备的啥啥加成都计算好了之后 | 原版该应用的正经加成都已经计算 | 推荐 |
PostUpdate MiscEffects | 杂项效果 更新后 | 其他莫名其妙的加成更新 | 反正够乱的,你的自定加成系统 放这里就也没关系了 | 推荐 |
Update BadLifeRegen | 更新负面 生命回复 | 已经计算好所有加成 开始更新生命值回复 | 这里才是正经的操作玩家 lifeRegen的地方,通过特判 来操作负面生命回复 | 针对 如果你的Mod 添加了类似于 |
Update LifeRegen | 更新正面 生命回复 | 先负再正很合理毕竟 负了的话这儿就不用管辣 | 同上,但是操作正面回复 | 生命值/魔法值 这样的属性 |
Natural LifeRegen (ref float regen) | 更新自然 生命回复 | 正面的也算好后,来修改 回复倍率的,所以插在这 | 可以修改自然回复的效力 比如篝火( regen *= 1.1f )正在移动( regen *= 0.5f ) | 你就可以 在这里写 |
PostUpdate RunSpeeds | 你给路达哟 更新后 | 同样是已经算好了所有东西后 开始更新移动 | 此时玩家的位置还没有更新 可以用来修改 maxRunSpeed 、accRunSpeed 、runAcceleration 等移速相关变量 | 针对 移动 |
PreUpdate Movement | 预更新移动 | 接下去就会更新玩家位置了 在这之前调用 | 可以直接在这里修改玩家速度 一般来说你想写玩家冲刺效果 就要放在这里 | 针对 冲刺 |
PostUpdate | 最终更新 | 这是最后一个了 | 有什么想要全部计算完 才被更新的东西就放这儿 | 推荐吗? 看情况用 |
攻击目标相关
就那一堆 ModifyHit
,OnHit
,OnHurt
什么什么的,之前的教程看过的话都认识,不在赘述。
来自物品的函数的玩家版本
基本物品那章讲过的函数这边基本都有一份复刻版,用法都一样的。
其他
还有一些绘制相关的,零散的东西,可以用补全自己康康,重在探索啊(x
自定义玩家属性
讲完了常用函数,接下来就是学会运用它们。
顺带一提 ModPlayer
不需要重写那个叫 InstancePerEntity
的玩意。
本篇会以如下内容作为例子。
- 玩家每次攻击NPC时,会附加一个有2秒内置冷却的额外伤害
- 用物品造成效果是100%冷却,但弹幕只有50%
- 这个伤害的大小是造成伤害的实体的伤害的50%
- 但某些装备/Buff可以让这个伤害被加成
- 还有某些装备/Buff可以降低这个效果的冷却时间
- 弹幕暴击时让冷却减少10帧
- NPC受到三次这个效果之后会被持续附加灵液Debuff
看起来很复杂,不过这些都是对Global系列和ModPlayer的综合运用。
其实你可以先自己试着写一遍,然后看看跟教程有什么区别。以上并没有什么标准规范写法,不必纠结。
在 ModPlayer
中:
public class SkirtPlayer : ModPlayer { // 声明变量 附加伤害修正 public StatModifier ExtraDmg = new(0.5f, 1f); // 冷却计数器 public int cd = 0; // 冷却倍率 public float cdMult = 1; public override void ResetEffects() { // 把额外伤害修正重置为加算区0.5f,乘算区1f ExtraDmg = new(0.5f, 1f); // 重置冷却倍率 cdMult = 1; // 更新冷却时间 if (cd > 0) { cd--; } } /// <summary> /// 封装函数,用于调用效果 /// </summary> private void AddExtraDmg(float damage, NPC target, ref NPC.HitModifiers modifiers, bool proj) { // 这个判定没必要用 cd == 0 真没必要 if (cd <= 0) { // 重置CD float scale = proj ? 0.5f : 1; cd = (int)(120 * cdMult * scale); // 计算并附加伤害 float dmg = ExtraDmg.CombineWith(modifiers.FinalDamage).ApplyTo(damage); modifiers.FlatBonusDamage += dmg; // 获取NPC的效果受击计数,把它+1 target.GetGlobalNPC<SkirtNPC>().effectCount++; } } public override void ModifyHitNPCWithItem(Item item, NPC target, ref NPC.HitModifiers modifiers) { AddExtraDmg(item.damage, target, ref modifiers, false); } public override void ModifyHitNPCWithProj(Projectile proj, NPC target, ref NPC.HitModifiers modifiers) { AddExtraDmg(proj.damage, target, ref modifiers, false); } }
在 GlobalNPC
中:
public class SkirtNPC : GlobalNPC { public override bool InstancePerEntity => true; // 声明 受击效果计数器 public int effectCount = 0; public override void AI(NPC npc) { // 受到三次这个效果以上 if (effectCount >= 3) { // 持续附加灵液Debuff npc.AddBuff(BuffID.Ichor, 2); } } }
在 GlobalProjectile
中:
public class SkirtProj : GlobalProjectile { public override void OnHitNPC(Projectile projectile, NPC target, NPC.HitInfo hit, int damageDone) { if (hit.Crit) { Player player = Main.player[projectile.owner]; SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>(); skp.cd -= 10; } } }
在 GlobalItem
中:
public class SkirtItem : GlobalItem { public override void UpdateAccessory(Item item, Player player, bool hideVisual) { // 获取ModPlayer实例 SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>(); // 用ref偷懒 ref float mult = ref skp.cdMult; ref StatModifier extra = ref skp.ExtraDmg; // 因为是展示,此处仅为原版饰品附加效果 switch (item.type) { // 泰坦手套 让伤害率提高10% case ItemID.TitanGlove: extra += 0.1f; break; // 强力手套 让伤害率提高15% case ItemID.PowerGlove: extra += 0.15f; break; // 消防手套或狂战士手套,均让伤害率提高20% case ItemID.FireGauntlet: case ItemID.BerserkerGlove: extra += 0.2f; break; // 魔力花 让冷却减少10% case ItemID.ManaFlower: mult -= 0.1f; break; // 天界手铐 让冷却减少20% case ItemID.CelestialCuffs: mult -= 0.2f; break; } } public override void UpdateEquip(Item item, Player player) { // 获取ModPlayer实例 SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>(); // 日耀和星璇头盔能让额外伤害翻倍 if (item.type is ItemID.SolarFlareHelmet or ItemID.VortexHelmet) { skp.ExtraDmg *= 2; } // 星云和星辰头盔能减半冷却 else if (item.type is ItemID.NebulaHelmet or ItemID.StardustHelmet) { skp.cdMult /= 2f; } } }
在 GlobalBuff
中:
public class SkirtBuff : GlobalBuff { public override void Update(int type, Player player, ref int buffIndex) { // 获取ModPlayer实例 SkirtPlayer skp = player.GetModPlayer<SkirtPlayer>(); // 原版提高10%伤害的愤怒药水效果 if (type == BuffID.Wrath) { // 减少10%冷却 skp.cdMult -= 0.1f; } // 这个是魔力再生药水 else if (type == BuffID.ManaRegeneration) { // 提高5%伤害率 skp.ExtraDmg += 0.05f; } // 魔力病 else if (type == BuffID.ManaSickness) { int buffTime = player.buffTime[buffIndex]; // 魔力病的剩余时间每秒导致1%的伤害率降低 skp.ExtraDmg -= buffTime / 60f * 0.01f; } } }
OK,以上就是个简单的使用例了。也许看着很复杂,但是相对来说确实很基础。
数据的保存与读取
先前我们讲的都是用完就扔的变量。不过有些时候这些数据需要被保存下来,比如你写了能“永久”增加玩家的基础魔力的东西(StatManaMax
,魔力上限基准)。
这时候就要用到数据的保存和读取了——位于 ModPlayer
的 SaveData
和 LoadData
。(其实别的地方也有比如 ModNPC
和 ModItem
,以及他们对应的Global系列,请根据实际需要选择使用)
TagCompound
,是个没见过的类型呢。
于是乎这里放一段很接地气但是看不懂的介绍(
System.IO.TagCompound
是一个 C#
中用于序列化和反序列化数据的类。它通常用于保存和加载游戏中的数据或配置文件。它提供了一种有序的、有层次结构的数据存储方式。可以存储各种类型的数据,如整数、浮点数、字符串等。
嗯,正如上文所说,他可以接收很多种类型的参数。
写法就是这么个写法,在save中把数据存进去,用文字索引,然后在load用索引把他拿出来。
就这么简单。
顺便再(夹带私货)介绍一个东西,可以用这个来看存储的数据。NBTExplore简要介绍>>>