云瓶,沙尘暴瓶,暴雪瓶等在泰拉瑞亚前期的机动力中具有相当重要的地位,接下来就让我们看一下如何实现模组的额外跳跃吧!
创建一个ExtraJump
首先当然是新建一个文件,随便写一个类名,并继承ExtraJump
这个类。
public class MyDoubleJump : ExtraJump { }
如果你的编译器没有罢工,那么MyDoubleJump
就会被编译器标红线,双击选中你的划红线类名后按下Alt+Enter快速呼出黄色小灯泡并选择下面这一项一键实现抽象类。

补全后记得把两行throw
给删掉,当然由于没有返回值会划红线,接下来就来说明这两个东西。
首先是自动补全出的GetDefaultPosition
这个方法,它的作用是用于决定二段跳的优先级。
例如这样写就表示在云瓶的优先级之后。
public override Position GetDefaultPosition() { return new After(CloudInABottle); //另外,以下这行表示优先级在云瓶之前 //return new Before(CloudInABottle); //所有的原版多段跳优先级会在文章末尾写出 }
GetDurationMultiplier
的作用是自定义跳跃时长,为1时二段跳持续时间就和玩家跳跃一样,例如沙暴瓶的这个东西返回了3,所以沙暴瓶可以跳很长一段时间。
public override float GetDurationMultiplier(Player player) { return 1f;//我们这里就简单返回1好了 }
这样一个最最基本的二段跳就做好了,接下来是在饰品里启用它,代码如下。
(什么,你不知道饰品怎么做?看这个教程吧。)
public override void UpdateAccessory(Player player, bool hideVisual) { player.GetJumpState<MyDoubleJump>().Enable(); //GetJumpState会返回一个ExtraJumpState。 //下面这行可以单独禁用一个多段跳,如果要禁用全部的多段跳请使用Player.blockExtraJumps //player.GetJumpState<MyDoubleJump>().Disable(); }
好了,进入游戏看一看效果吧!我专门穿上了云瓶,可以看到我们的二段跳的优先级确实是在云瓶之后。

添加视觉效果
现在的二段跳虽然能生效了但是只有一个云瓶同款音效,应该为它增加一点视觉效果!
使用ShowVisuals
这个方法就可以在二段跳期间生成各种视觉效果,先简单写一个粒子效果吧。
public override void ShowVisuals(Player player) { //粒子的位置,随机一个以玩家中心为圆心,半径24的圆内的位置。 Vector2 position = player.Center + Main.rand.NextVector2Circular(24, 24); //粒子的速度,大概是玩家的速度的反向,再加上随机的角度偏移与大小缩放。 Vector2 velocity = -player.velocity.RotatedBy(Main.rand.NextFloat(-0.5f, 0.5f)) * Main.rand.NextFloat(0.2f, 0.5f); //传送站的粒子,我个人很喜欢 Dust.NewDustPerfect(position, DustID.Teleporter, velocity,Scale: 1.5f); }

OnStarted
在开始二段跳时调用,让我们试试在开始二段跳时生成一点Gore
。
该方法中的playSound
如果设为false
的话就不会产生原版云瓶的音效,你可以在这个方法里播放你自己的音效。
public override void OnStarted(Player player, ref bool playSound) { Gore.NewGoreDirect(player.GetSource_FromThis(), player.Center , Vector2.Zero, GoreID.FartCloud2); }

OnEnded
可以在二段跳结束时产生效果,在结束时生成一个弹幕来演示它吧!
public override void OnEnded(Player player) { //生成老版本泰拉刃的剑气 //注意!以下内容大概率多人不同步,请自行书写多人相关判定 Projectile.NewProjectile(player.GetSource_FromThis(), player.Center , (Main.MouseWorld - player.Center).SafeNormalize(Vector2.Zero) * 8 , ProjectileID.TerraBeam, 100, 0, player.whoAmI); }

OnRefreshed
在二段跳重置时执行,让我们在里面跳个字。
另外如果需要手动重置各种二段跳请使用Player.RefreshExtraJumps
。
重置各种移动效果,如翅膀飞行时间,火箭靴飞行时间请使用Player.RefreshMovementAbilities
,该方法默认也会重置二段跳。
public override void OnRefreshed(Player player) { //这个方法在重置二段跳的变量(也就是Available)之前调用 //所以我们判断当前二段跳不可用时才生成文本 //能重置二段跳的时候会持续调用该方法,不这么做的话就会一直跳字 ExtraJumpState state = player.GetJumpState<MyDoubleJump>(); if (!state.Available) { //如果真要写这样的效果请使用本地化而不是硬编码的文本 PopupText.NewText(new AdvancedPopupRequest() { Color = Color.Coral, Text = "让我们重新战斗吧!", DurationInFrames = 60, Velocity = new Vector2(0, -2) }, player.Top); } }

好了,主要就用上面4个东西就可以满足绝大多数二段跳的视觉效果了,另外还有个CanShowVisuals
,它的效果如字面意思所示,返回false
就能阻止ShowVisuals
被调用。
原版默认Y方向速度与重力速度方向相同时不产生二段跳视觉效果,源码如下。

额外的移动倍率
在UpdateHorizontalSpeeds
这个东西里面我们可以修改玩家横向移动的一些参数,比如最强横向机动臭屁瓶就是这么写的。
public override void UpdateHorizontalSpeeds(Player player) { //加速度 的倍率 player.runAcceleration *= 3f; //最大移速 的倍率 player.maxRunSpeed *= 1.75f; }
可重复使用的多段跳
要实现这个功能,首先需要了解关于ModPlayer
的相关内容,具体可以看这个教程。
首先在自己的ModPlayer
中添加一个字段,用于存储剩余多段跳的次数,代码如下。
(我这里封装了一堆东西,如果你不喜欢这样可以不这么写)
public class MyPlayer : ModPlayer { /// <summary> /// <see cref="MyDoubleJump"/> 的剩余跳跃次数 /// </summary> public int MyExtraJumpCount { get; private set; } /// <summary> /// <see cref="MyDoubleJump"/> 的跳跃次数最大值 /// </summary> private const int MyExtraJumpCountMax = 3; //我这里写的3,实际上你可以改为任意的数值 /// <summary> /// 能否使用 <see cref="MyDoubleJump"/> /// </summary> public bool CanUseMyDoubleJump => MyExtraJumpCount > 0; /// <summary> /// <see cref="MyDoubleJump"/> 是否已经消耗过了 /// </summary> public bool MyDoubleJumpCosted => MyExtraJumpCount != MyExtraJumpCountMax; /// <summary> /// 重置 <see cref="MyDoubleJump"/> 的跳跃次数 /// </summary> public void ResetMyDoubleJump() => MyExtraJumpCount = MyExtraJumpCountMax; /// <summary> /// 消耗 <see cref="MyDoubleJump"/> 的跳跃次数 /// </summary> public void CostMyDoubleJump() { if (MyExtraJumpCount > 0) MyExtraJumpCount--; } }
接着,在上文中教过的OnRefreshed
中重置这个字段。
public override void OnRefreshed(Player player) { //这个跳字还是保留了,如果你不喜欢它可以直接删掉 //以及下面这一行稍微改了一下,就算只消耗了一段,在重置时也会触发跳字 if (player.TryGetModPlayer(out MyPlayer myPlayer2) && myPlayer2.MyDoubleJumpCosted) { PopupText.NewText(new AdvancedPopupRequest() { Color = Color.Coral, Text = "让我们重新战斗吧!", DurationInFrames = 60, Velocity = new Vector2(0, -2) }, player.Top); } //获取ModPlayer,使用TryGetModPlayer只是我的个人喜好 if (player.TryGetModPlayer(out MyPlayer myPlayer)) myPlayer.ResetMyDoubleJump(); }
那么问题来了,要如何判断这个多段跳是否能使用呢?
使用CanStart
检测能否使用多段跳,代码如下。
public override bool CanStart(Player player) { return player.TryGetModPlayer(out MyPlayer myPlayer) && myPlayer.CanUseMyDoubleJump; }
说点悄悄话,其实上面只是为了展示CanStart
这个东西,按我的写法来说不写它也完全没问题… …
接着,我们在开始跳跃的时候减少跳跃的使用次数,并将跳跃状态设置为可用。
public override void OnStarted(Player player, ref bool playSound) { //获取ModPlayer,使用TryGetModPlayer只是我的个人喜好 if (player.TryGetModPlayer(out MyPlayer myPlayer)) { myPlayer.CostMyDoubleJump();//消耗跳跃次数 if (myPlayer.CanUseMyDoubleJump) player.GetJumpState<MyDoubleJump>().Available = true;//重新启用跳跃 } //同样是之前保留的东西,如果不喜欢可以删掉 Gore.NewGoreDirect(player.GetSource_FromThis() , player.Center, Vector2.Zero, GoreID.FartCloud2); }
下面就进入游戏看看效果吧!

其他杂项
首先是原版的二段跳,优先级如图所示。(上高下低)

如果你有很多个模组多段跳,并且想确定它们之间的优先级顺序时,可以使用GetModdedConstraints
,例如下面这样。
public class MyDoubleJump1 : ExtraJump { public override Position GetDefaultPosition() => new After(CloudInABottle); public override float GetDurationMultiplier(Player player) => 1f; public override IEnumerable<Position> GetModdedConstraints() { //表示优先级在 MyDoubleJump2 之后 yield return new After(ModContent.GetInstance<MyDoubleJump2>()); } } public class MyDoubleJump2 : ExtraJump { public override Position GetDefaultPosition() => new After(CloudInABottle); public override float GetDurationMultiplier(Player player) => 1f; }
在水里游泳的跳跃居然也是多段跳?只能说源码,很神奇吧,来看看它怎么做的。
(从中其实可以了解到无限使用的多段跳该如何制作)

如果你在视觉效果或者其他地方需要一个跳跃时的计时器,可以考虑使用Player.jump
。这个字段在跳跃开始时设置为Player.jumpHeight
,之后每帧递减直到为0。而Player.jumpHeight
是跳跃高度,受到各种饰品以及GetDurationMultiplier
的影响。

结束语
ExtraJump
实际上算是一个比较新的东西,我也是一边做一边研究再一边写教程。tml在这块也是进行了比较彻底的重写,所以源码看起来很方便易懂,可以说是该有的效果都有了。
目前在我印象中,好像没有做了额外跳跃的模组(也可能只是我模组玩的少吧),希望看完后你能学会制作模组二段跳!