跳至正文

基础弹幕

Projectile,又名抛射物,也就是我们常说的弹幕。在泰拉瑞亚中,弹幕就是发射出去的那些东西,但是其实有一些武器本身就是弹幕。弹幕可以用来搞很花的东西(什么弹幕UI)

OK,现在是弹幕区了,之前的物品是放在Items文件夹下,现在你们可以新建一个Projectiles文件夹来存放弹幕文件。当然,这是个人习惯。

那么进入正题,弹幕要继承的类是 ModProjectile

设置弹幕的基础属性

SetStaticDefaults

没错又是SSD,不过弹幕这边这个钩子用的相对比较少。毕竟没必要给弹幕写介绍,甚至名字也懒得写()。

不写的话tr会根据你的类名自动分配一个名字。

比如,你的弹幕类名是 ExCalibur ,那么自动分配会给到 Ex Calibur ,它会读取大写字母。嗯,你会在自动更新本地化看到这个效果。

namespcae XXX.Projectiles

public class ExCalibur : ModProjectile
{
    // 继承类型变了哦
}

另外就是IDSet和帧图了吧?

public override void SetStaticDefaults()
{
    // Type是弹幕类型哦
    // Main.projFrames[Type] = 1;
    // 这个弹幕的帧图数量,不写就是1

    // 下面三个玩意就先权当认识一下吧

    // ProjectileID.Sets.DrawScreenCheckFluff[Type] = 2000;
    // 这行代码的作用是修改弹幕的最大绘制距离,不写则是出屏幕就不绘制,单位像素
    
    // ProjectileID.Sets.TrailCacheLength[Type] = 10;
    // 弹幕的旧数据记录数组长度,10为记录10个数据,新的数据会覆盖更早的数据,默认为10

    // ProjectileID.Sets.TrailingMode[Type] = -1;
    // 弹幕的旧数据记录类型,默认为-1,即不记录
    // 0,记录位置(Projectile.oldPos),这里注意,还有一个玩意儿叫Projectile.oldPosition,这是弹幕上一帧的位置,而oldPos是一个数组
    // 1,请不要使用这个
    // 2,记录位置、角度(Projectile.oldRot)、绘制方向(Projecitle.oldSpriteDirection)
    // 3,与2相同,但会以插值来平滑这个数据
    // 4,与2相同,但会调整数据以跟随弹幕的主人
}

乐,全都注释了,弹幕的SSD?根本不写。

SetDefaults

你们最喜欢的SD来了(不是),会在弹幕实例创建的时候执行。

public override void SetDefaults()
{
    Projectile.width = 16; // 弹幕的碰撞箱宽度
    Projectile.height = 16; // 弹幕的碰撞箱高度
    // 这两个字段不赋值弹幕会射不出来!16*16的碰撞箱相当于泰拉里一个物块那么大
    // 特别注意,请不要搞什么碰撞箱大小设为贴图大小的骚操作,那会造成奇怪的后果

    Projectile.scale = 1f; // 弹幕缩放倍率,会影响碰撞箱大小,默认1f
    Projectile.ignoreWater = true; // 弹幕是否忽视水
    Projectile.tileCollide = true; // 弹幕撞到物块会创死吗
    Projectile.penetrate = 1; // 弹幕的穿透数,默认1次
    Projectile.timeLeft = 180; // 弹幕的存活时间,它会从弹幕生成开始每次更新减1,为零时弹幕会被kill,默认3600
    Projectile.alpha = 0; // 弹幕的透明度,0 ~ 255,0是完全不透明(int)
    // Projectile.Opacity = 1; // 弹幕的不透明度,0 ~ 1,0是完全透明,1是完全不透明(float),用哪个你们自己挑,这两是互相影响的
    Projectile.friendly = true; // 弹幕是否攻击敌方,默认false
    Projectile.hostile = false; // 弹幕是否攻击友方和城镇NPC,默认false
    Projectile.DamageType = DamageClass.Default; // 弹幕的伤害类型,默认default,npc射的弹幕用这种,玩家的什么类型武器就设为什么吧

    // Projectile.aiStyle = ProjAIStyleID.Arrow; // 弹幕使用原版哪种弹幕AI类型
    // AIType = ProjectileID.FireArrow; // 弹幕模仿原版哪种弹幕的行为
    // 上面两条,第一条是某种行为类型,可以查源码看看,这里是箭矢,第二条要有第一条才有效果,是让这个弹幕能执行对应弹幕的特殊判定行为
    Projectile.aiStyle = -1; // 不用原版的就写这个,也可以不写

    // Projectile.extraUpdates = 0; // 弹幕每帧的额外更新次数,默认0,这个之后细讲
    // 以及写一些关于无敌帧的设定
}

关于 ignoreWater ,如果它是 false ,那么弹幕在所有液体中都会减速。同时,几个相关字段:

wet ,弹幕在水中时为 true ,以及下面两个在蜂蜜中、在岩浆中。

当然,如果 ignoreWatertrue ,那么以上字段即使弹幕在对应液体内也不会被设为 true

这些差不多是弹幕的基础设定。有关无敌帧,请点击参考。

弹幕的行为

整活的开始(

Onspawn

在弹幕生成的那一帧执行的函数,只会执行一次。当然你可以在其它地方调用它(?

这个函数给了一个参数,是弹幕的生成源。至于怎么利用这个生成源?这就要根据你们的想法咯~

等讲到全局修改(Global系列)的时候,会给出一些这个钩子的使用例。

AI

AI是弹幕的灵魂,它的所有行动都在这里面。

来写一个抓大猫吃弹幕?

AI有三个相关的函数,分别是 bool PreAI,void AI,void PostAI

正常来说只会用到 AIPreAI 是在 AI 之前执行的,如果它返回 false ,那么 AI 中的代码就不会执行,默认返回 true

PostAI 则是在 AI 后执行,讲究一个代码顺序问题,请根据实际情况合理的使用它们。

弹幕的一些常用行为就看老教程吧,数学上的东西没什么版本区别。

命中目标时

打到玩家和打到NPC,不难理解吧。

撞到物块时

注意,这个钩子要先前的 Projectile.tileCollide = true ,才会在碰撞时执行。

它给的参数是入射速度。注意,当返回值是 true 时,弹幕会执行原版的判定,就是它会因为撞到物块而寄了。

所以你想搞什么反弹之类的就要返回 false ,然后用这个钩子给的oldVel和弹幕当前的vel判定他怎么弹的,反弹这个效果原版已经帮你做好了。

弹幕死亡时

当弹幕的剩余时间耗尽,或是撞到物块,或是穿透次数用尽,这些时候都会执行死亡。

PreKill ,在 Kill 执行之前调用,返回 false 时会停止调用跟死亡有关的钩子,比如这个 Kill 就不再会执行,默认返回 true 。注意,返回值不会改变这个弹幕已经挂了的事实。

Kill ,死亡时调用,可以搞点什么这个弹幕死了原地发射一个爆炸弹幕的效果。

弹幕的常用玩意儿

字段/属性

名称值类型说明
aifloat[]弹幕自带的天然计时器,不需要写收发包,长度为3
注意,当你使用了原版ai的时候,这个东西就不能随便改了
localAIfloat[]同上,但要写入收发包
HitboxRectangle弹幕的碰撞箱
positionVector2弹幕的碰撞箱位置(左上角坐标)
CenterVector2碰撞箱中心坐标
rotationfloat弹幕的视觉角度,弧度制(需要手动赋值)
velocityVector2弹幕的速度
directionint方向,设为-1是朝左,1是朝右(不好用)
spriteDirectionint纹理方向,设为-1是朝左,1是朝右(常用于绘制)
ownerint弹幕的主人在Main.player[](玩家数组)中的索引
frameint弹幕的帧
frameCounterint弹幕的帧数计时器
damageint弹幕的伤害
knockBackfloat弹幕的击退力
whoAmIint弹幕在Main.projectile[](弹幕数组)中的索引
activebool弹幕是否活跃,如果直接改成false弹幕就没了,也是遍历判定的必要条件
typeint弹幕的类型

方法

发射弹幕

这是复习——

public override void AI()
{
    // 现在我要让这个弹幕会每帧朝着上、左、右三个方向发射泰拉剑气
    // 但是我会分别使用发射弹幕的两种方法和它们的重载
    // 请注意返回值的类型
    Vector2 unit = Vector2.UnitX; // 这是(1,0)
    IEntitySource source = Projectile.GetSource_FromAI();

    // 因为泰拉坐标系的Y轴正方向是朝下的,所以这里是-Vector2.UnitY,即(0,-1),这样才是向上的速度,这里的返回值是弹幕实例
    Projectile proj = Projectile.NewProjectileDirect(source, Projectile.Center, -Vector2.UnitY, ProjectileID.TerraBeam, 10, 6, Projectile.owner);

    // 这里是朝左,而且返回值是int类型,返回的是弹幕在Main.projectile[](弹幕数组)中的索引
    int p = Projectile.NewProjectile(source, Projectile.Center, -unit, ProjectileID.TerraBeam, 10, 6, Projectile.owner);
    // 这是朝右,但是发射位置和发射速度是拆开来写,返回值同上
    int p2 = Projectile.NewProjectile(source, Projectile.Center.X, Projectile.Center.Y, unit.X, unit.Y, ProjectileID.TerraBeam, 10, 6, Projectile.owner);

    // 你们就根据情况进行实际应用吧
}

攻击特定目标

类似命中效果,用于一些特殊弹幕。

public override bool? CanHitNPC(NPC target)
{
    // 只能攻击是boss的NPC
    return target.boss;
    // 这个函数的返回值是bool?,当返回null时则执行默认的原版判定
}
public override bool CanHitPlayer(Player target)
{
    // 只能攻击没有拿着(当前选中物品是)天顶剑的玩家
    return target.HeldItem.type != ItemID.Zenith;
}
public override bool CanHitPvp(Player target)
{
    // pvp下只能攻击背包中没有沙块的玩家,联机大约是得出问题
    return !target.HasItem(ItemID.SandBlock);
}

可否造成伤害

可以用这个钩子来让弹幕在某些情况下不造成伤害。

public override bool? CanDamage()
{
    // 当弹幕剩余存活时间小于0.5s时不造成伤害
    if (Projectile.timeleft < 30) return false;
    // 返回null时则执行默认的原版判定
    return null;
}

是否进行位置更新

常用于蓄力型弹幕,弹幕的位置会每帧加上速度(所谓移动),使用这个钩子可以禁用掉这个效果,使你的弹幕在保留速度的情况下不进行运动。

public override bool ShouldUpdatePosition()
{
    return true;
}

常用的应该是这些,有些特殊的东西后续会在特别的地方讲到。

至此,你是否对弹幕有个基本的了解了呢?

标签:

发表回复