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
,以及下面两个在蜂蜜中、在岩浆中。
当然,如果 ignoreWater
是 true
,那么以上字段即使弹幕在对应液体内也不会被设为 true
。
这些差不多是弹幕的基础设定。有关无敌帧,请点击参考。
弹幕的行为
整活的开始(
Onspawn
在弹幕生成的那一帧执行的函数,只会执行一次。当然你可以在其它地方调用它(?
这个函数给了一个参数,是弹幕的生成源。至于怎么利用这个生成源?这就要根据你们的想法咯~
等讲到全局修改(Global系列)的时候,会给出一些这个钩子的使用例。
AI
AI是弹幕的灵魂,它的所有行动都在这里面。
AI有三个相关的函数,分别是 bool PreAI,void AI,void PostAI
。
正常来说只会用到 AI
,PreAI
是在 AI
之前执行的,如果它返回 false
,那么 AI
中的代码就不会执行,默认返回 true
。
PostAI
则是在 AI
后执行,讲究一个代码顺序问题,请根据实际情况合理的使用它们。
弹幕的一些常用行为就看老教程吧,数学上的东西没什么版本区别。
命中目标时
打到玩家和打到NPC,不难理解吧。
撞到物块时
注意,这个钩子要先前的 Projectile.tileCollide = true
,才会在碰撞时执行。
它给的参数是入射速度。注意,当返回值是 true
时,弹幕会执行原版的判定,就是它会因为撞到物块而寄了。
所以你想搞什么反弹之类的就要返回 false
,然后用这个钩子给的oldVel和弹幕当前的vel判定他怎么弹的,反弹这个效果原版已经帮你做好了。
弹幕死亡时
当弹幕的剩余时间耗尽,或是撞到物块,或是穿透次数用尽,这些时候都会执行死亡。
PreKill
,在 Kill
执行之前调用,返回 false
时会停止调用跟死亡有关的钩子,比如这个 Kill
就不再会执行,默认返回 true
。注意,返回值不会改变这个弹幕已经挂了的事实。
Kill
,死亡时调用,可以搞点什么这个弹幕死了原地发射一个爆炸弹幕的效果。
弹幕的常用玩意儿
字段/属性
名称 | 值类型 | 说明 |
---|---|---|
ai | float[] | 弹幕自带的天然计时器,不需要写收发包,长度为3 注意,当你使用了原版ai的时候,这个东西就不能随便改了 |
localAI | float[] | 同上,但要写入收发包 |
Hitbox | Rectangle | 弹幕的碰撞箱 |
position | Vector2 | 弹幕的碰撞箱位置(左上角坐标) |
Center | Vector2 | 碰撞箱中心坐标 |
rotation | float | 弹幕的视觉角度,弧度制(需要手动赋值) |
velocity | Vector2 | 弹幕的速度 |
direction | int | 方向,设为-1是朝左,1是朝右(不好用) |
spriteDirection | int | 纹理方向,设为-1是朝左,1是朝右(常用于绘制) |
owner | int | 弹幕的主人在Main.player[](玩家数组)中的索引 |
frame | int | 弹幕的帧 |
frameCounter | int | 弹幕的帧数计时器 |
damage | int | 弹幕的伤害 |
knockBack | float | 弹幕的击退力 |
whoAmI | int | 弹幕在Main.projectile[](弹幕数组)中的索引 |
active | bool | 弹幕是否活跃,如果直接改成false弹幕就没了,也是遍历判定的必要条件 |
type | int | 弹幕的类型 |
方法
发射弹幕
这是复习——
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; }
常用的应该是这些,有些特殊的东西后续会在特别的地方讲到。
至此,你是否对弹幕有个基本的了解了呢?