泰拉瑞亚-坐骑Mod开发
前言
- 入门级教程请参考上一篇文章
- Git仓库:https://github.com/Cyborg2077/PolWorldMounts
- Steam创意工坊:https://steamcommunity.com/sharedfiles/filedetails/?id=3292980622
- 本文进度会略微落后于创意工坊版本
如何自定义坐骑
- 原版Terraria里的坐骑,首先都要有一个召唤物。
- 其次就是有一个buff图标
最后就是坐骑本体
所以首先我们要先来制作这个召唤物。那么第一步我们先来创建一个Mod项目吧。
制作召唤物
- 召唤物的本质实际上就是一个Item,所以我们创建一个类,继承ModItem即可。
- 在这个类中,我们来定义召唤物的一些属性,以及合成材料
1 | using PolWorldMounts.Content.Mounts; |
- 物品制作完毕后,我们还需要制作贴图,这里我就以帕鲁球为例制作贴图了。画风最好是符合泰拉瑞亚的像素风,这样看起来比较舒服。
- 那我们直接启动幻兽帕鲁,进游戏截一张帕鲁球的图
- 拿到图肯定不能直接用,我们首先要扣掉背景,随后转为像素风,调整尺寸为32*32。扣背景我这里直接用的百度AI图片助手,扣好了之后下载到本地,我们来进一步处理像素风
- 像素风的处理,我这里直接用的Python的PIL库来进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13from PIL import Image
def pixelate(image_path, block_size):
img = Image.open(image_path)
w, h = img.size
img_small = img.resize((w // block_size, h // block_size), resample=Image.NEAREST)
result = img_small.resize(img.size, Image.NEAREST)
return result
pixelated_image = pixelate('帕鲁球.png', 7)
pixelated_image.save('pixelated.png') - 处理后的效果还是不错的,那么云海鹿召唤物的功能,我们就开发完毕了
制作坐骑
- 坐骑的创建,我们同样需要继承ModMount,随后就是定义坐骑的基本属性,云海鹿的特性有二段跳,所以我们这里不阻止使用额外跳跃,只要你装备了云朵瓶或者气球束之类的会产生额外跳跃的饰品,当你在骑行云海鹿的时候,都可以正常触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class FenglopeMount : ModMount
{
public override void SetStaticDefaults()
{
// 定义坐骑的基本属性
MountData.jumpHeight = 20; // 坐骑能跳多高
MountData.acceleration = 0.19f; // 加速率,即坐骑加速的速度
MountData.jumpSpeed = 20f; // 当按下跳跃键时,坐骑和玩家向上跳跃的速度
MountData.blockExtraJumps = false; // 是否阻止使用额外跳跃(如瓶子中的云)
MountData.constantJump = true; // 是否允许按住跳跃键进行连续跳跃
MountData.heightBoost = 20; // 坐骑与地面之间的高度
MountData.fallDamage = 0f; // 从高处跌落时受到的伤害倍率
MountData.runSpeed = 11f; // 坐骑的奔跑速度
MountData.dashSpeed = 8f; // 冲刺速度
MountData.flightTimeMax = 0; // 最大飞行时间,0表示不能飞行
// 设置疲劳最大值为0,意味着骑乘时不会产生疲劳
MountData.fatigueMax = 0;
// 关联坐骑的Buff,即骑乘时玩家获得的状态效果
MountData.buff = ModContent.BuffType<Buffs.FenglopeMountBuff>();
// 动画帧数和玩家偏移量设置
MountData.totalFrames = 5; // 总共的动画帧数,取决于你自己的贴图
MountData.playerYOffsets = Enumerable.Repeat(20, MountData.totalFrames).ToArray(); // 玩家相对于坐骑的Y轴偏移量数组,用于微调玩家在坐骑贴图的位置
MountData.xOffset = 20; // 玩家相对于坐骑的X轴偏移量,用于微调玩家在坐骑贴图上的位置
MountData.yOffset = -12; // 玩家相对于坐骑的Y轴偏移量,用于微调玩家在坐骑贴图上的位置
MountData.playerHeadOffset = 22; // 玩家头部相对于坐骑的偏移量,用于微调玩家在坐骑贴图上的位置
MountData.bodyFrame = 3; // 玩家身体在坐骑上的帧号
// 不同状态下的动画设置
// 站立
MountData.standingFrameCount = 0;
MountData.standingFrameDelay = 12;
MountData.standingFrameStart = 0;
// 奔跑
MountData.runningFrameCount = 4;
MountData.runningFrameDelay = 120;
MountData.runningFrameStart = 1;
// 飞行
MountData.flyingFrameCount = 0;
MountData.flyingFrameDelay = 0;
MountData.flyingFrameStart = 2;
// 空中
MountData.inAirFrameCount = 1;
MountData.inAirFrameDelay = 12;
MountData.inAirFrameStart = 2;
// 闲置
MountData.idleFrameCount = 4;
MountData.idleFrameDelay = 12;
MountData.idleFrameStart = 0;
MountData.idleFrameLoop = true;
// 游泳
MountData.swimFrameCount = MountData.inAirFrameCount;
MountData.swimFrameDelay = MountData.inAirFrameDelay;
MountData.swimFrameStart = MountData.inAirFrameStart;
}
} - 直接叫上一个好兄弟当工具人,去游戏里简单录一段云海鹿的跑步动画,然后自己截图扣下来
- 整个Mod开发耗时最久的就是抠图,哈哈哈哈,一张一张扣完之后,下载保存到本地,然后我简单写了个合成脚本,把多张图垂直排列合并,并缩放,代码目录下按你的顺序存放1.png~5.png即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46from PIL import Image
def pixelate(image_path, block_size):
img = Image.open(image_path)
w, h = img.size
img_small = img.resize((w // block_size, h // block_size), resample=Image.NEAREST)
result = img_small.resize(img.size, Image.NEAREST)
return result
def scale_image(image, scale_factor):
# 获取当前图片的尺寸
width, height = image.size
# 计算新的尺寸
new_width = width // scale_factor
new_height = height // scale_factor
# 返回缩放后的图片
return image.resize((new_width, new_height), Image.ANTIALIAS)
# 保存处理后的图片对象和尺寸
processed_images = []
total_width = 0
total_height = 0
scale_factor = 4 # 缩放比例
# 处理图片并计算总高度和宽度
for i in range(5):
pixelated_image = pixelate(f'{i + 1}.png', 10)
scaled_image = scale_image(pixelated_image, scale_factor)
processed_images.append(scaled_image)
total_width = max(total_width, scaled_image.width)
total_height += scaled_image.height
# 创建一张新图片,背景颜色为透明
new_image = Image.new('RGBA', (total_width, total_height), color=(0, 0, 0, 0))
# 将每张处理后的图片粘贴到新图片上
y_offset = 0
for img in processed_images:
new_image.paste(img, (0, y_offset), img)
y_offset += img.height
# 保存新图片
new_image.save('combined_pixelated.png', 'PNG') - 最终的效果
制作坐骑BUFF
- 玩家使用坐骑,其实是对自身施加一个buff,所以我们还要编写一个云海鹿的坐骑BUFF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17using PolWorldMounts.Content.Mounts; // 引入模组中 Mounts 目录下的命名空间
using Terraria; // 引入游戏主引擎的命名空间
using Terraria.ModLoader; // 引入模组加载器的命名空间
namespace PolWorldMounts.Content.Buffs { // 定义模组 Buffs 目录下的命名空间
public class FenglopeMountBuff : ModBuff { // 定义一个继承自 ModBuff 的公共类 FenglopeMountBuff
public override void SetStaticDefaults() { // 重写父类的方法 SetStaticDefaults
Main.buffNoTimeDisplay[Type] = true; // 设置此Buff不会显示持续时间
Main.buffNoSave[Type] = true; // 设置此Buff不会被保存,即在死亡或退出世界后消失
}
public override void Update(Player player, ref int buffIndex) { // 重写父类的方法 Update,当Buff应用到玩家身上时被调用
player.mount.SetMount(ModContent.MountType<FenglopeMount>(), player); // 设置玩家的坐骑为 FenglopeMount
player.buffTime[buffIndex] = 10; // 设置Buff的持续时间为10帧,实际作用是持续刷新,因为会不断被重新应用
}
}
} - 简单制作一个Buff图标,同样是使用百度抠图,随后将图片下载到本地,随后像素化,并且指定图片尺寸为32x32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from PIL import Image
def pixelate(image_path, output_size):
img = Image.open(image_path)
img_small = img.resize(output_size, resample=Image.NEAREST)
block_size_w = img.width // output_size[0]
block_size_h = img.height // output_size[1]
img_pixelated = img_small.resize((img.width // block_size_w, img.height // block_size_h), resample=Image.NEAREST)
result = img_pixelated.resize(output_size, Image.NEAREST)
return result
pixelated_image = pixelate('云海鹿.png', (32, 32))
pixelated_image.save('pixelated_image_云海鹿.png')
泰拉瑞亚 启动!
- 重新生成并加载Mod,我们的云海鹿就已经可以使用了,只需要一块泥土进行制作,不需要任何合成台
增强云海鹿
不再需要云瓶进行二段跳
- 带个云瓶太麻烦了,能不能自己想想办法来实现二段跳呢?当然是有的,我们可以先创建一个二段跳的标志位。标识玩家是否已经进行了二段跳。
- 随后我们可以通过判断玩家的Y轴速度是否为0来判断是否处于地面。
- 如果是的话,那么重置一下二段跳的标志位。
- 如果不是的话,就可以进行二段跳了。
- 那么接下来看代码逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24private bool hasDoubleJumped = false; // 标志位:是否已经二段跳
// 二段跳逻辑
if (player.controlJump) // 玩家按下了跳跃键
{
if (player.velocity.Y == 0)
{
hasDoubleJumped = false; // 重置二段跳标志位
}
else if (!hasDoubleJumped && player.releaseJump) // 确保没有执行二段跳且玩家松开了跳跃键
{
player.velocity.Y = -18; // 设置跳跃速度
hasDoubleJumped = true; // 设置二段跳标志位,防止玩家无限跳跃
for (int i = 0; i < 200; i++) // 这里只是加了一个粒子特效,模仿云瓶的二段跳效果,只是为了好看,没有其他作用
{
Vector2 dustPosition = player.position;
Dust dust = Dust.NewDustDirect(dustPostion, player.width, player.height, DustID.Snow, 0f, 0f, 100, Color.White, 1.5f);
dust.velocity *= 1.2f;
dust.color = Color.White;
dust.noGravity = true;
}
}
} - 这样的话,就不再需要云瓶来实现二段跳啦。
增加冲刺技能
- 云海鹿的招牌技能:阴云之岚,这里当然也是要尽量还原一下的啦,不过贴图和特效就还原不了了,除非能让我白嫖一个画师,哈哈哈哈。
- 技能实现方式其实很简单,本质上就是一个BUFF技能。施放技能后,玩家会被附加一个DEBUFF,持续一段时间,在此期间无法再次释放技能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62// 检查冲刺冷却时间,如果冷却中,减少冷却时间
if (dashCooldown > 0)
{
dashCooldown--;
}
// 检查冲刺时间是否还剩余
if (dashTimeLeft > 0)
{
// 仍在冲刺中,减少剩余冲刺时间
dashTimeLeft--;
// 设置玩家的速度,根据当前方向和冲刺速度
player.velocity.X = player.direction * DashSpeed;
// 生成玩家的冲刺命中框
Rectangle hitbox = new Rectangle((int)(player.position.X + player.velocity.X),
(int)(player.position.Y + player.velocity.Y),
player.width,
player.height);
// 遍历所有NPC,检测是否与冲刺命中框相交
for (int i = 0; i < Main.maxNPCs; i++)
{
NPC target = Main.npc[i];
// 检查NPC是否活跃且不是友好的
if (target.active && !target.friendly && target.Hitbox.Intersects(hitbox))
{
// 创建击中信息,设置伤害、击退和命中方向
NPC.HitInfo hitInfo = new NPC.HitInfo
{
Damage = DashDamage,
Knockback = DashKnockBack,
HitDirection = player.direction
};
// 对目标NPC造成伤害
target.StrikeNPC(hitInfo);
}
}
// 生成粒子特效
for (int i = 0; i < 10; i++) // 每帧生成10个粒子
{
// 生成粒子位置,基于玩家的中心位置并添加随机偏移
Vector2 dustPosition = player.Center + new Vector2(Main.rand.Next(-20, 5), Main.rand.Next(-20, 5));
// 创建新粒子,设置速度和生命周期
Dust.NewDust(dustPosition, 0, 0, DustID.Snow, player.velocity.X * 0.5f, player.velocity.Y * 0.5f, 100, default, 1.5f);
}
}
// 如果冷却时间为0,并且玩家没有被“疲劳”BUFF影响
else if (dashCooldown == 0 && !player.HasBuff(ModContent.BuffType<Buffs.FenglopeExhaustedBuff>()))
{
// 获取冲刺按键的配置
mountDashKey = ModContent.GetInstance<PolworldModConfig>().MountDashKey;
// 检查玩家是否按下了冲刺按键
if (Main.keyState.IsKeyDown(mountDashKey))
{
// 开始冲刺,设置剩余冲刺时间和冷却时间
dashTimeLeft = DashDuration;
dashCooldown = DashCooldown;
// 为玩家添加疲劳BUFF,持续600帧(10秒)
player.AddBuff(ModContent.BuffType<Buffs.FenglopeExhaustedBuff>(), 600);
}
}
草莽猪
- 草莽猪只不过移速降低了一点,原版能挖矿,但是泰拉里没有石头矿能撞,所以就改成了撞断沿途的树木。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170using Microsoft.Xna.Framework;
using Terraria;
using Terraria.ModLoader;
using Terraria.ID;
using Terraria.GameInput;
using System.Linq;
using Terraria.Audio;
using Microsoft.Xna.Framework.Input;
using PolWorldMounts.Content;
namespace PolWorldMounts.Content.Mounts
{
public class RushoarMount : ModMount
{
private Keys mountDashKey;
private const int DashCooldown = 60; // 冲刺冷却时间,单位:帧
private const int DashDuration = 90; // 冲刺持续时间,单位:帧
private const float DashSpeed = 12f; // 冲刺速度
private const int DashDamage = 50; // 冲刺造成的伤害
private const float DashKnockBack = 10f; // 冲刺造成的击退力
private int dashTimeLeft = 0;
private int dashCooldown = 0;
public override void SetStaticDefaults()
{
MountData.jumpHeight = 5; // How high the mount can jump.
MountData.acceleration = 0.19f; // The rate at which the mount speeds up.
MountData.jumpSpeed = 5f; // The rate at which the player and mount ascend towards (negative y velocity) the jump height when the jump button is pressed.
MountData.blockExtraJumps = true; // 阻止饰品增加跳跃次数
MountData.constantJump = true; // Allows you to hold the jump button down.
MountData.heightBoost = -20; // Height between the mount and the ground
MountData.fallDamage = 0f; // Fall damage multiplier.
MountData.runSpeed = 8f; // The speed of the mount
MountData.dashSpeed = 8f; // The speed the mount moves when in the state of dashing.
MountData.flightTimeMax = 0; // The amount of time in frames a mount can be in the state of flying.
MountData.fatigueMax = 0;
MountData.buff = ModContent.BuffType<Buffs.RushoarMountBuff>(); // The ID number of the buff assigned to the mount.
// Effects
// MountData.spawnDust = ModContent.DustType<Dusts.Sparkle>(); // The ID of the dust spawned when mounted or dismounted.
// Frame data and player offsets
MountData.totalFrames = 5; // Amount of animation frames for the mount
MountData.playerYOffsets = Enumerable.Repeat(20, MountData.totalFrames).ToArray(); // Fills an array with values for less repeating code
MountData.xOffset = 20;
MountData.yOffset = -12;
MountData.playerHeadOffset = 22;
MountData.bodyFrame = 3;
// Standing
MountData.standingFrameCount = 0;
MountData.standingFrameDelay = 12;
MountData.standingFrameStart = 0;
// Running
MountData.runningFrameCount = 4;
MountData.runningFrameDelay = 50;
MountData.runningFrameStart = 0;
// Flying
MountData.flyingFrameCount = 0;
MountData.flyingFrameDelay = 0;
MountData.flyingFrameStart = 2;
// In-air
MountData.inAirFrameCount = 1;
MountData.inAirFrameDelay = 12;
MountData.inAirFrameStart = 1;
// Idle
MountData.idleFrameCount = 0;
MountData.idleFrameDelay = 12;
MountData.idleFrameStart = 0;
MountData.idleFrameLoop = true;
// Swim
MountData.swimFrameCount = MountData.inAirFrameCount;
MountData.swimFrameDelay = MountData.inAirFrameDelay;
MountData.swimFrameStart = MountData.inAirFrameStart;
mountDashKey = ModContent.GetInstance<PolworldModConfig>().MountDashKey;
if (!Main.dedServ)
{
MountData.textureWidth = MountData.backTexture.Width() + 20;
MountData.textureHeight = MountData.backTexture.Height();
}
}
public override void UpdateEffects(Player player)
{
if (dashCooldown > 0)
{
dashCooldown--;
}
if (dashTimeLeft > 0)
{
dashTimeLeft--;
player.velocity.X = player.direction * DashSpeed;
Rectangle hitbox = new Rectangle((int)(player.position.X + player.velocity.X), (int)(player.position.Y + player.velocity.Y), player.width, player.height);
BreakTreesAlongPath(player, hitbox);
for (int i = 0; i < Main.maxNPCs; i++)
{
NPC target = Main.npc[i];
if (target.active && !target.friendly && target.Hitbox.Intersects(hitbox))
{
NPC.HitInfo hitInfo = new NPC.HitInfo
{
Damage = DashDamage,
Knockback = DashKnockBack,
HitDirection = player.direction
};
target.StrikeNPC(hitInfo);
}
}
// 生成粒子特效
for (int i = 0; i < 10; i++) // 每帧生成10个粒子
{
Vector2 dustPosition = player.Center + new Vector2(Main.rand.Next(-20, 5), Main.rand.Next(-20, 5));
Dust.NewDust(dustPosition, 0, 0, DustID.Dirt, player.velocity.X * 0.5f, player.velocity.Y * 0.5f, 100, default, 1.5f);
}
}
else if (dashCooldown == 0 && !player.HasBuff(ModContent.BuffType<Buffs.RushoarExhaustedBuff>()))
{
mountDashKey = ModContent.GetInstance<PolworldModConfig>().MountDashKey;
if (Main.keyState.IsKeyDown(mountDashKey))
{
dashTimeLeft = DashDuration;
dashCooldown = DashCooldown;
player.AddBuff(ModContent.BuffType<Buffs.RushoarExhaustedBuff>(), 600);
}
}
}
private void BreakTreesAlongPath(Player player, Rectangle hitbox)
{
int tileStartX = hitbox.Left / 16;
int tileEndX = hitbox.Right / 16;
int tileStartY = hitbox.Top / 16;
int tileEndY = hitbox.Bottom / 16;
for (int x = tileStartX; x <= tileEndX; x++)
{
for (int y = tileStartY; y <= tileEndY; y++)
{
Tile tile = Framing.GetTileSafely(x, y);
if ((tile.TileType == TileID.Trees
//tile.TileType == TileID.PalmTree ||
//tile.TileType == TileID.VanityTreeSakura
)
&& tile.HasTile)
{
// 如果是树顶
if (tile.TileFrameY == 0)
{
WorldGen.KillTile(x, y, false, false, true);
SoundEngine.PlaySound(SoundID.Item14);
}
// 如果是树身
else if (tile.TileFrameY % 22 == 0)
{
WorldGen.KillTile(x, y, false, false, true);
SoundEngine.PlaySound(SoundID.Item14);
}
}
}
}
}
}
}
空涡龙
1 | using System; |
- 光束彗星一开始会沿着鼠标方向发射,随后会自动追踪800像素范围内的敌怪。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace PolWorldMounts.Content.Projectiles
{
public class BeamCometProjectile : ModProjectile
{
public override void SetDefaults()
{
Projectile.width = 10; // 弹幕宽度
Projectile.height = 10; // 弹幕高度
Projectile.friendly = true; // 是否对玩家友好
Projectile.hostile = false; // 是否对敌人友好
Projectile.tileCollide = true; // 是否与瓷砖碰撞
Projectile.penetrate = 5; // 穿透数量
Projectile.timeLeft = 600; // 弹幕的存活时间(帧)
Projectile.light = 0.5f; // 弹幕的光亮度
Projectile.ignoreWater = true; // 是否忽略水
Projectile.extraUpdates = 1; // 每帧更新次数
}
public override void AI()
{
Player player = Main.player[Projectile.owner];
Vector2 mousePosition = Main.MouseWorld;
NPC target = FindClosestNPC(800f); // 设定追踪范围为800像素
if (target != null)
{
// 如果找到目标,计算弹幕朝向目标的方向
Vector2 directionToTarget = target.Center - Projectile.Center;
directionToTarget.Normalize();
// 平滑过渡到目标方向
float smoothFactor = 0.05f; // 调整平滑的因子,0到1之间,数值越小转向越平滑
Projectile.velocity = Vector2.Lerp(Projectile.velocity, directionToTarget * 4f, smoothFactor);
}
// 粒子效果
for (int i = 0; i < 1; i++) // 每帧生成1个粒子
{
int dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Pink, Projectile.velocity.X * 0.2f, Projectile.velocity.Y * 0.2f, 100, new Color(205, 71, 208), 0.7f);
Dust dust = Main.dust[dustIndex];
dust.noGravity = true;
dust.velocity *= 0.3f;
dust.scale *= 0.95f;
}
// 添加光效
Lighting.AddLight(Projectile.Center, 0.84f, 0.48f, 0.73f);
}
// 辅助方法,查找最近的敌人
private NPC FindClosestNPC(float maxDetectDistance)
{
NPC closestNPC = null;
float sqrMaxDetectDistance = maxDetectDistance * maxDetectDistance;
for (int i = 0; i < Main.maxNPCs; i++)
{
NPC npc = Main.npc[i];
if (npc.CanBeChasedBy(this))
{
float sqrDistanceToNPC = Vector2.DistanceSquared(npc.Center, Projectile.Center);
if (sqrDistanceToNPC < sqrMaxDetectDistance)
{
sqrMaxDetectDistance = sqrDistanceToNPC;
closestNPC = npc;
}
}
}
return closestNPC;
}
public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
{
target.AddBuff(BuffID.OnFire, 300);
}
public override bool PreDraw(ref Color lightColor)
{
// 获取当前帧的源矩形
Rectangle sourceRectangle = new Rectangle(0, Projectile.frame * Projectile.height, Projectile.width, Projectile.height);
// 获取弹幕的贴图
Texture2D texture = Terraria.GameContent.TextureAssets.Projectile[Projectile.type].Value;
// 拖影效果参数
float alpha = 0.5f; // 拖影的透明度
float scale = Projectile.scale; // 拓影的缩放
// 获取弹幕的速度方向
Vector2 velocity = Projectile.velocity;
velocity.Normalize(); // 归一化速度向量,用于确定拖影的方向
// 计算拖影偏移量
float offsetDistance = 10f; // 拖影偏移量的距离(可以根据需要调整)
// 绘制拖影
for (int i = 0; i < 20; i++) // 绘制5个拖影(可以调整数量)
{
// 计算每个拖影的位置
Vector2 offset = velocity * offsetDistance * i; // 沿着速度方向的偏移量
Color color = Color.Lerp(lightColor, Color.Transparent, alpha / 5f * i); // 逐渐变透明的颜色
Main.spriteBatch.Draw(
texture,
Projectile.Center - Main.screenPosition - offset,
sourceRectangle,
color,
Projectile.rotation,
new Vector2(Projectile.width / 2, Projectile.height / 2),
scale,
SpriteEffects.None,
0f
);
}
// 绘制正常的弹幕
Main.spriteBatch.Draw(
texture,
Projectile.Center - Main.screenPosition,
sourceRectangle,
lightColor,
Projectile.rotation,
new Vector2(Projectile.width / 2, Projectile.height / 2),
scale,
SpriteEffects.None,
0f
);
return false; // 返回false,表示我们自己绘制了弹幕
}
}
}
- 烈焰风暴个人感觉还是很不错的,抠图就扣了老半天了,会赋予敌人燃烧效果,并且会有吸附效果,清兵神器,自己调试的时候,把CD改成0.1,直接秒天秒地了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102using System;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using PolWorldMounts.Content.Projectiles;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace PolWorldMounts.Content.Projectiles
{
public class FlareStormProjectile : ModProjectile
{
public override void SetDefaults()
{
Projectile.width = 168; // 弹幕宽度
Projectile.height = 164; // 弹幕高度
Projectile.friendly = true; // 是否对玩家友好
Projectile.hostile = false; // 是否对敌人友好
Projectile.tileCollide = true; // 是否与瓷砖碰撞
Projectile.penetrate = 9999; // 穿透数量
Projectile.timeLeft = 300; // 弹幕的存活时间(帧)
Projectile.light = 0.8f; // 弹幕的光亮度
Projectile.ignoreWater = true; // 是否忽略水
Projectile.extraUpdates = 1; // 每帧更新次数
Projectile.aiStyle = -1; // 自定义AI
Main.projFrames[Projectile.type] = 4; // 设置弹幕的帧数
}
public override void AI()
{
// 创建火焰粒子特效
if (Main.rand.NextBool(3))
{
Dust dust = Dust.NewDustDirect(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Yellow);
dust.noGravity = true; // 粒子不受重力影响
dust.velocity *= 1.2f; // 增加粒子速度
dust.scale *= 1.5f; // 增加粒子规模
}
// 设置弹幕沿地面前进
if (Projectile.velocity.Y == 0f) // 检查弹幕是否在地面上
{
if (Projectile.velocity.X > 0f)
{
Projectile.velocity.X = Math.Max(Projectile.velocity.X - 0.1f, 0); // 左移减速
}
else if (Projectile.velocity.X < 0f)
{
Projectile.velocity.X = Math.Min(Projectile.velocity.X + 0.1f, 0); // 右移减速
}
if (Math.Abs(Projectile.velocity.X) < 0.2f)
{
Projectile.velocity.X = 0f; // 停止移动
}
}
else
{
Projectile.velocity.Y += 0.2f; // 重力作用
}
// 添加光效
Lighting.AddLight(Projectile.Center, 0.8f, 0.4f, 0.1f); // 添加橙色光效
// 更新动画帧
Projectile.frameCounter++;
if (Projectile.frameCounter >= 5) // 每5帧切换一次图片
{
Projectile.frame++;
Projectile.frame %= 4; // 循环帧动画
Projectile.frameCounter = 0;
}
// 吸附敌怪
foreach (NPC npc in Main.npc)
{
if (npc.active && !npc.friendly && !npc.dontTakeDamage && Vector2.Distance(Projectile.Center, npc.Center) < 300f)
{
Vector2 direction = Projectile.Center - npc.Center;
direction.Normalize();
npc.velocity += direction * 0.1f; // 吸附效果
}
}
}
public override void OnHitNPC(NPC target, NPC.HitInfo hit, int damageDone)
{
target.AddBuff(BuffID.OnFire, 300); // 造成燃烧效果,持续5秒
}
public override bool OnTileCollide(Vector2 oldVelocity)
{
Projectile.velocity = Vector2.Zero; // 停止移动,但不消失
return false; // 不摧毁弹幕
}
public override void Kill(int timeLeft)
{
// 可以在弹幕摧毁时添加特效
}
}
}
- 龙彗星倒是没有什么特别的,感觉这个不是很好还原的其实,弹幕生成的不会很丝滑,运动轨迹,emmm 也很奇怪,可能是会有更平滑的函数实现吧。
- 每次发射的时候都会沿鼠标方向。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace PolWorldMounts.Content.Projectiles
{
public class DragonMeteorProjectile : ModProjectile
{
private bool hasAccelerated = false; // 是否已经开始加速
private int hoverTime = 60; // 悬停时间(帧)
public override void SetDefaults()
{
Projectile.width = 32; // 弹幕宽度
Projectile.height = 32; // 弹幕高度
Projectile.friendly = true; // 是否对玩家友好
Projectile.hostile = false; // 是否对敌人友好
Projectile.tileCollide = true; // 是否与瓷砖碰撞
Projectile.penetrate = -1; // 穿透数量,设置为-1表示不会消失
Projectile.timeLeft = 300; // 弹幕的存活时间(帧)
Projectile.light = 0.8f; // 弹幕的光亮度
Projectile.ignoreWater = true; // 是否忽略水
Projectile.extraUpdates = 1; // 每帧更新次数
Main.projFrames[Projectile.type] = 4; // 设置弹幕的帧数
}
public override void AI()
{
if (hoverTime > 0)
{
hoverTime--;
Projectile.velocity = Vector2.Zero; // 悬停时速度为零
// 不产生粒子特效
if (hoverTime % 10 == 0) // 每10帧产生一次粒子特效(可以调整频率)
{
int dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Pink, 0f, 0f, 100, default(Color), 1.0f);
Main.dust[dustIndex].velocity *= 0.5f;
Main.dust[dustIndex].scale *= 1.2f;
Main.dust[dustIndex].noGravity = true;
}
}
else if (!hasAccelerated)
{
Vector2 targetPosition = Main.MouseWorld;
Vector2 direction = targetPosition - Projectile.Center;
direction.Normalize();
Projectile.velocity = direction * 2f; // 设置初始速度
hasAccelerated = true;
}
else
{
Projectile.velocity *= 1.05f; // 缓慢加速
}
// 添加粒子效果或其他视觉效果
if (hoverTime <= 0) // 只有在加速阶段才产生粒子特效
{
int dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Red, Projectile.velocity.X * 0.5f, Projectile.velocity.Y * 0.5f, 100, default(Color), 1.0f);
Main.dust[dustIndex].velocity *= 0.5f;
Main.dust[dustIndex].scale *= 1.2f;
Main.dust[dustIndex].noGravity = true;
}
}
public override void Kill(int timeLeft)
{
// 创建爆炸效果的范围伤害
float explosionRadius = 150f; // 爆炸范围
int damage = 300; // 爆炸伤害
// 查找爆炸范围内的敌人并造成伤害
for (int i = 0; i < Main.maxNPCs; i++)
{
NPC npc = Main.npc[i];
if (npc.CanBeChasedBy(this) && Vector2.Distance(npc.Center, Projectile.Center) < explosionRadius)
{
// 计算伤害并应用
npc.SimpleStrikeNPC(damage, 0, false, 0, null, false, 0, false); // hitDirection 设置为 -1 表示无特定方向
}
}
// 播放音效
for (int i = 0; i < 20; i++)
{
int dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Smoke, 0f, 0f, 100, default(Color), 2f);
Main.dust[dustIndex].velocity *= 1.4f;
}
for (int i = 0; i < 10; i++)
{
int dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Blue, 0f, 0f, 100, default(Color), 3f);
Main.dust[dustIndex].noGravity = true;
Main.dust[dustIndex].velocity *= 5f;
dustIndex = Dust.NewDust(Projectile.position, Projectile.width, Projectile.height, DustID.Firework_Red, 0f, 0f, 100, default(Color), 2f);
Main.dust[dustIndex].velocity *= 3f;
}
}
public override bool PreDraw(ref Color lightColor)
{
// 获取当前帧的源矩形
Rectangle sourceRectangle = new Rectangle(0, Projectile.frame * Projectile.height, Projectile.width, Projectile.height);
// 绘制弹幕
Main.spriteBatch.Draw(
Terraria.GameContent.TextureAssets.Projectile[Projectile.type].Value,
Projectile.Center - Main.screenPosition,
sourceRectangle,
lightColor,
Projectile.rotation,
new Vector2(Projectile.width / 2, Projectile.height / 2),
Projectile.scale,
SpriteEffects.None,
0f
);
return false; // 返回false,表示我们自己绘制了弹幕
}
}
}
帕鲁工作台
工作台实际上就是一个物块而已
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35using Terraria;
using Terraria.ModLoader;
using Terraria.ID;
namespace PolWorldMounts.Content.Items.WorkBench
{
public class PolworldBasicWorkBenchItem : ModItem
{
public override void SetDefaults()
{
Item.width = 28;
Item.height = 14;
Item.maxStack = 99;
Item.useTurn = true;
Item.autoReuse = true;
Item.useAnimation = 15;
Item.useTime = 10;
Item.useStyle = ItemUseStyleID.Swing;
Item.consumable = true;
Item.createTile = ModContent.TileType<Tiles.PolworldBasicWorkBench>(); // 设置对应的Tile
Item.rare = ItemRarityID.Blue;
}
public override void AddRecipes()
{
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.Wood, 30);
recipe.AddIngredient(ItemID.Sapphire, 10);
recipe.AddIngredient(ItemID.Silk, 10);
recipe.AddTile(TileID.WorkBenches);
recipe.Register();
}
}
}随后在制作物品的时候,指明需要的物块即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34using PolWorldMounts.Content.Mounts;
using PolWorldMounts.Content.Tiles;
using Terraria;
using Terraria.ID;
using Terraria.ModLoader;
namespace PolWorldMounts.Content.Items.Mounts
{
public class FenglopeMountItem : ModItem
{
public override void SetDefaults() {
Item.width = 20;
Item.height = 30;
Item.useTime = 20;
Item.useAnimation = 20;
Item.useStyle = ItemUseStyleID.Swing;
Item.value = Item.sellPrice(gold: 3);
Item.rare = ItemRarityID.Yellow;
Item.UseSound = SoundID.Item79;
Item.noMelee = true;
Item.mountType = ModContent.MountType<FenglopeMount>();
}
public override void AddRecipes() {
Recipe recipe = CreateRecipe();
recipe.AddIngredient(ItemID.HallowedBar, 20);
recipe.AddIngredient(ItemID.SoulofFlight, 5);
recipe.AddIngredient(ItemID.SoulofLight, 10);
recipe.AddIngredient(ItemID.Sapphire, 5);
recipe.AddTile(Mod, "PolworldBasicWorkBench"); // 这里指明需要帕鲁基础工作台就好啦,这样的话必须使用工作台才能制作哦
recipe.Register();
}
}
}
评论