杀戮尖塔的卡牌箭头
杀戮尖塔里面使用卡牌时的箭头是这样的:
贝塞尔曲线
箭头的形态非常符合贝塞尔曲线。
PS 中的钢笔工具就是用的贝塞尔曲线:
如图,一条贝塞尔曲线需要用四个点来确定,一个起点,一个终点,加上两个控制点。
我们把四个点分别命名:起点(startPos),终点(endPos),控制点 A(ctrlAPos),控制点 B(ctrlBPos)
贝塞尔曲线的公式是:
position = startPos*(1-t)*(1-t)*(1-t) + 3*ctrlAPos*t*(1-t)*(1-t) + 3*ctrlBPos*t*t*(1-t) + endPos*t*t*t
四个点都确定后,公式里的 t
就是唯一的变量,t
是指从起点到终点的百分比,取值是 0~1
。
比如 0
代表曲线起点,0.2
代表曲线从起点开始 20%
的位置,0.5
代表曲线中间位置,1
代表曲线终点。
公式的计算结果 position
就是当前 t
值所对应的曲线上的点。
不过在游戏中我们是使用两个点来确定曲线的,卡牌所在位置是曲线的起点,鼠标所在位置是曲线的终点。
那么两个控制点就要根据起点和终点来进行计算。
杀戮尖塔里的曲线大致是这样:
我们可以大致的写出控制点的计算公式:
ctrlAPos.x = startPos.x + (startPos.x - endPos.x) * 0.2 ctrlAPos.y = endPos.y - (endPos.y - startPos.y) * 0.2 ctrlBPos.x = startPos.x - (startPos.x - endPos.x) * 0.2 ctrlBPos.y = endPos.y + (endPos.y - startPos.y) * 0.2
当然这个计算公式可以自己微调,使曲线更符合自己想要的形态
Godot 中的实现
理解了曲线的原理,现在开始在 godot 中实现。
我画了两个箭头,箭头 1 和箭头 2,如图。
在 godot 中新建一个场景,新建 Node2D
,命名为贝塞尔箭头。添加脚本。
开始写脚本。首先我们的箭头有20节,我们需要在初始化的时候准备好。
之后更新箭头时重新排好每一节就能形成一条曲线。
extends Node2D var list=[] #数组,用来保存20节小箭头 func _ready(): #生成19节尾巴小箭头,用箭头1的图片 for i in range(19): var sprite=Sprite.new() #新建 Sprite 节点 add_child(sprite) #添加到场景里 list.append(sprite) #添加到数组里 sprite.texture=load("res://Sprites/箭头1.png") #把图片换成箭头1 sprite.scale=Vector2(1,1)*(0.2+float(i)/18*0.8) #改变缩放,根据杀戮尖塔,箭头是一节节越来越大的 sprite.offset=Vector2(-25,0) #由于我画的图片中心点在箭头中间, #这里改变一下图片偏移,把图片中心点移动到箭头头部 #最后生成终点的箭头,用箭头2的图片 var sprite=Sprite.new() add_child(sprite) list.append(sprite) sprite.texture=load("res://Sprites/箭头2.png") sprite.offset=Vector2(-25,0)
然后我们需要一个函数来设置箭头的起点和终点
func reset(startPos,endPos): #根据传入的起点和终点来计算两个控制点 var ctrlAPos=Vector2() var ctrlBPos=Vector2() ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.1 #这里我把参数做了微调,感觉这样更加符合杀戮尖塔的效果 ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2 ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.3 ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.3 #根据贝塞尔曲线重新设置所有小箭头的位置 for i in range(20): var t=float(i)/19 var pos=startPos*(1-t)*(1-t)*(1-t)+3*ctrlAPos*t*(1-t)*(1-t)+3*ctrlBPos*t*t*(1-t)+endPos*t*t*t list[i].position=pos #虽然更改了箭头的位置,不过还需要重新计算箭头的方向 updateAngle() #重新计算所有箭头的方向
接下来我们需要完成 updateAngle()这个函数
思路是每个小箭头根据前一个箭头和自己的位置来计算角度
func updateAngle(): for i in range(20): if i==0: list[0].rotation_degrees=270 #第一个小箭头就让他固定朝上好了 else: var current=list[i] #当前的小箭头 var last=list[i-1] #前一个小箭头 var lenVec=current.position-last.position #两个箭头连线的向量 var a=lenVec.angle() #计算这个向量的角度,这个 angle()返回值是弧度 a=rad2deg(a) #弧度转成角度 current.rotation_degrees=a #更新小箭头的方向
效果
我们的贝塞尔箭头就做好了,外界只要调用 reset 方法就能更新箭头,
不需要用的时候可以用 visible = false
把它隐藏掉
我把它放到游戏中看看效果
非常完美!
我觉得写得挺好的,虽然不懂编程。但为了点个赞特意注册这个帐号。
LZ,什么时候你的作品在steam上架了告诉我一声哈。
正在使用Unity尝试你的效果,非常感谢
已使用此方法,感谢大佬
讲得非常好
已在unity实现,十分感谢
在cocos creator上实现了 点赞收藏感谢
通俗易懂,这事ao的
文章写的很好,顺便提一句,在godot4中,Vector2类型自带了bezier_interpolate()和bezier_derivative()这两个函数,因此只要确定好起点终点以及对应的两个控制点,直接调用这两个函数就行,前者确定点的位置,后者确定曲线上某一点的斜率,刚好可以化为旋转的弧度