跳至正文

自定义额外跳跃(多段跳ExtraJump)

云瓶,沙尘暴瓶,暴雪瓶等在泰拉瑞亚前期的机动力中具有相当重要的地位,接下来就让我们看一下如何实现模组的额外跳跃吧!

创建一个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.swimTime仅用于改变玩家的动作帧

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


结束语

ExtraJump 实际上算是一个比较新的东西,我也是一边做一边研究再一边写教程。tml在这块也是进行了比较彻底的重写,所以源码看起来很方便易懂,可以说是该有的效果都有了。

目前在我印象中,好像没有做了额外跳跃的模组(也可能只是我模组玩的少吧),希望看完后你能学会制作模组二段跳!

发表回复