【Godot4.0】贝塞尔曲线在游戏中的实际应用

概述

之前研究贝塞尔曲线绘制,完全是以绘图函数,以及实现节点连接为思考。并没有实际考虑贝塞尔曲线在游戏中的应用。今日偶然看到悦千简一年多前发的一个用贝塞尔曲线实现追踪弹或箭矢效果,还有玩物不丧志的老李杀戮尖塔系列中的卡牌动态箭头。想起来确实很需要实现和总结动态贝塞尔曲线。

可以用在简单的弹道轨迹和卡牌或战旗攻击箭头的生成。

可以封装成几个类,无限复用。

两点之间贝塞尔的快速生成

卡牌攻击箭头

用之前编写过的函数,快速的搭建一个场景测试,其中Marker2D节点是箭头起始位置。

代码如下:

# ====================================================
# 卡牌贝塞尔箭头测试
# ====================================================
extends Node2D
@onready var marker: Marker2D = $Marker2D


var p1:Vector2   # 点1
var p2:Vector2   # 点2
var ctl_1 = pVector2(-90,100)  # 控制点1
var ctl_2 = pVector2(-45,100)  # 控制点2


var 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)  # 控制点2
	queue_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)  # 控制点1
	ctl_2 = pVector2(-90 + 2 * rad_to_deg(ang),ctl_len)  # 控制点2
	queue_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_len
	var 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 = $Marker2D


var 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)  # 控制点2


var 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_len
	var 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)  # 控制点1
	ctl_2 = pVector2(-90 + 2 * rad_to_deg(ang),ctl_len)  # 控制点2
	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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巽星石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值