概述
之前研究贝塞尔曲线绘制,完全是以绘图函数,以及实现节点连接为思考。并没有实际考虑贝塞尔曲线在游戏中的应用。今日偶然看到悦千简一年多前发的一个用贝塞尔曲线实现追踪弹或箭矢效果,还有玩物不丧志的老李杀戮尖塔系列中的卡牌动态箭头。想起来确实很需要实现和总结动态贝塞尔曲线。
可以用在简单的弹道轨迹和卡牌或战旗攻击箭头的生成。
可以封装成几个类,无限复用。
两点之间贝塞尔的快速生成
卡牌攻击箭头
用之前编写过的函数,快速的搭建一个场景测试,其中Marker2D
节点是箭头起始位置。
代码如下:
# ====================================================
# 卡牌贝塞尔箭头测试
# ====================================================
extends Node2D
@onready var marker: Marker2D = $Marker2Dvar p1:Vector2 # 点1
var p2:Vector2 # 点2
var ctl_1 = pVector2(-90,100) # 控制点1
var ctl_2 = pVector2(-45,100) # 控制点2var steps = 100; # 点的数目,越多曲线越平滑var curve_color:= Color.WHITE # 曲线绘制颜色
var ctl_color:= Color.AQUAMARINE # 控制点和连线绘制颜色# =================================== 虚函数 ===================================
func _draw() -> void:draw_bezier_curve(self,p1,p2,ctl_1,ctl_2,50)func _ready() -> void:p1 = marker.position # 设定P1的位置func _process(delta: float) -> void:p2 = get_global_mouse_position()queue_redraw()# =================================== 自定义函数 ===================================
# 极坐标点函数 - 通过角度和长度定义一个点
func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))return dir * length# 求两点之间的贝塞尔曲线
func bezier_curve(p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10) -> PackedVector2Array:var points:PackedVector2Array = []# 求曲线点集for i in range(points_count+1):var p = p1.bezier_interpolate(p1+ctl_1,p2+ctl_2,p2,i/float(points_count))points.append(p)return points# 绘制贝塞尔曲线
func draw_bezier_curve(canvas:CanvasItem,p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10):var points:PackedVector2Array = [] # 曲线点集合points.append_array(bezier_curve(p1,p2,ctl_1,ctl_2,points_count))# 绘制控制点draw_arc(p1+ctl_1,2,0,TAU,10,ctl_color,1)draw_arc(p2+ctl_2,2,0,TAU,10,ctl_color,1)# 绘制曲线端点与控制点的连线draw_line(p1,p1+ctl_1-Vector2(1,0),ctl_color,1)draw_line(p2,p2+ctl_2-Vector2(1,0),ctl_color,1)# 绘制贝塞尔曲线draw_polyline(points,curve_color,1)
控制点2的方向,应该等于控制点1的方向-2θ
在左侧时,就应该是+2θ
func _process(delta: float) -> void:p2 = get_global_mouse_position() # 设定P2的位置为鼠标位置var ang = (p2-p1).angle() # 与X轴夹角ctl_2 = pVector2(-90 + 2 * rad_to_deg(ang),100) # 控制点2queue_redraw()
目前效果看起来就是控制点的长度不是动态的,有点僵硬。
我们设定全局变量ctl_len
,然后在_process
动态计算。
var ctl_len = 100 # 控制点长度func _process(delta: float) -> void:p2 = get_global_mouse_position() # 设定P2的位置为鼠标位置var ang = (p2-p1).angle() # 与X轴夹角ctl_len = (p2-p1).length() / 3.0 # 动态控制点长度ctl_1 = pVector2(-90,ctl_len) # 控制点1ctl_2 = pVector2(-90 + 2 * rad_to_deg(ang),ctl_len) # 控制点2queue_redraw()
效果如下:
绘制箭头
func _draw() -> void:draw_bezier_curve(self,p1,p2,ctl_1,ctl_2,50)var a1 = p2+ctl_2.rotated(deg_to_rad(-30))var a2 = p2+ctl_2.rotated(deg_to_rad(30))var dir_a12 = a1.direction_to(a2)draw_line(p2,a1,ctl_color)draw_line(p2,a2,ctl_color)var b_len = ctl_len / 5.0 # 箭头内陷宽度# 计算箭头顶点var b1 = a1 + dir_a12 * b_lenvar b2 = a2 - dir_a12 * b_len# 绘制draw_line(a1,b1,ctl_color)draw_line(a2,b2,ctl_color)# 绘制draw_bezier_curve(self,p1,b1,ctl_1,ctl_2,50)draw_bezier_curve(self,p1,b2,ctl_1,ctl_2,50)
# ====================================================
# 卡牌贝塞尔箭头测试
# ====================================================
extends Node2D
@onready var marker: Marker2D = $Marker2Dvar p1:Vector2 # 点1
var p2:Vector2 # 点2
var ctl_len = 100
var ctl_1 = pVector2(-90,ctl_len) # 控制点1
var ctl_2 = pVector2(-45,ctl_len) # 控制点2var steps = 100; # 点的数目,越多曲线越平滑var curve_color:= Color.WHITE # 曲线绘制颜色
var ctl_color:= Color.AQUAMARINE # 控制点和连线绘制颜色# =================================== 虚函数 ===================================
func _draw() -> void:draw_bezier_curve(self,p1,p2,ctl_1,ctl_2,50)var a1 = p2+ctl_2.rotated(deg_to_rad(-30))var a2 = p2+ctl_2.rotated(deg_to_rad(30))var dir_a12 = a1.direction_to(a2)draw_line(p2,a1,ctl_color)draw_line(p2,a2,ctl_color)var b_len = ctl_len / 5.0 # 箭头内陷宽度# 计算箭头顶点var b1 = a1 + dir_a12 * b_lenvar b2 = a2 - dir_a12 * b_len# 绘制draw_line(a1,b1,ctl_color)draw_line(a2,b2,ctl_color)# 绘制draw_bezier_curve(self,p1,b1,ctl_1,ctl_2,50)draw_bezier_curve(self,p1,b2,ctl_1,ctl_2,50)func _ready() -> void:p1 = marker.position # 设定P1的位置func _process(delta: float) -> void:p2 = get_global_mouse_position() # 设定P2的位置为鼠标位置var ang = (p2-p1).angle() # 与X轴夹角ctl_len = (p2-p1).length() / 3.0 # 动态控制点长度ctl_1 = pVector2(-90,ctl_len) # 控制点1ctl_2 = pVector2(-90 + 2 * rad_to_deg(ang),ctl_len) # 控制点2queue_redraw()# =================================== 自定义函数 ===================================
# 极坐标点函数 - 通过角度和长度定义一个点
func pVector2(angle:float = 0.0,length:float =0.0) -> Vector2:var dir = Vector2.RIGHT.rotated(deg_to_rad(angle))return dir * length# 求两点之间的贝塞尔曲线
func bezier_curve(p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10) -> PackedVector2Array:var points:PackedVector2Array = []# 求曲线点集for i in range(points_count+1):var p = p1.bezier_interpolate(p1+ctl_1,p2+ctl_2,p2,i/float(points_count))points.append(p)return points# 绘制贝塞尔曲线
func draw_bezier_curve(canvas:CanvasItem,p1:Vector2,p2:Vector2,ctl_1:=Vector2(),ctl_2:=Vector2(),points_count:=10):var points:PackedVector2Array = [] # 曲线点集合points.append_array(bezier_curve(p1,p2,ctl_1,ctl_2,points_count))# 绘制控制点draw_arc(p1+ctl_1,2,0,TAU,10,ctl_color,1)draw_arc(p2+ctl_2,2,0,TAU,10,ctl_color,1)# 绘制曲线端点与控制点的连线draw_line(p1,p1+ctl_1-Vector2(1,0),ctl_color,1)draw_line(p2,p2+ctl_2-Vector2(1,0),ctl_color,1)# 绘制贝塞尔曲线draw_polyline(points,curve_color,1)