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

ops/2025/3/14 15:17:07/

概述

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

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

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

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

卡牌攻击箭头

用之前编写过的函数,快速的搭建一个场景测试,其中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)

http://www.ppmy.cn/ops/165694.html

相关文章

电子电气架构 ---常见车规MCU安全启动方案

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

Linux中grep、sed和awk常见用法总结

1.概述 Linux系统下,grep、sed和awk三个命令是最常用的、非常强大的文本处理工具,可以用于搜索、替换、过滤、排序等多种操作,掌握这三种工具的用法,可以大大提高我们在Linux下处理文本的效率。 2.grep命令 grep是一种非常常见…

嵌入式学习L6网络编程D3TCP

TCP编程 写代码 第一步socket 绑定 先填充 点分十进制转换成32位整数 client 然后就连接成功了就可以读写数据了 client #include "net.h"int main (void) {int fd -1;struct sockaddr_in sin;/* 1. 创建socket fd */if ((fd socket (AF_INET, SOCK_S…

2025-3-12 leetcode刷题情况(贪心算法--区间问题)

一、452.用最少数量的箭引爆气球 1.题目描述 2.代码 3.思路 使用 Arrays.sort 方法对 points 数组按照气球的起始坐标进行排序。这里使用 Integer.compare(a[0], b[0]) 作为比较器,确保气球按起始坐标从小到大排列。将箭的数量 count 初始化为 1,因为至…

从零开始的python学习(五)P75+P76+P77+P78+P79+P80

本文章记录观看B站python教程学习笔记和实践感悟,视频链接:【花了2万多买的Python教程全套,现在分享给大家,入门到精通(Python全栈开发教程)】 https://www.bilibili.com/video/BV1wD4y1o7AS/?p6&share_sourcecopy_web&v…

TypeScript泛型深度剖析:对比JavaScript的灵活与严谨

一、类型系统的进化之路 在JavaScript的灵活性与TypeScript的严谨性之间,泛型(Generics)架起了一座精妙的桥梁。当我们从JavaScript迁移到TypeScript时,经常会遇到这样的困境:如何在不丧失灵活性的前提下保证类型安全…

【Android】adb shell基本使用教程

adb shell 是 Android Debug Bridge (ADB) 工具中的一个命令,用于在连接的 Android 设备或模拟器上执行 shell 命令。通过 adb shell,你可以直接与设备的 Linux 内核交互,执行各种操作。 基本用法 启动 adb shell: 在终端或命令提…

用nodejs加electron加roboticjs判断鼠标点击时是否在浏览器内部

要实现这个功能,你需要使用 Electron 来创建一个桌面应用,然后使用 RobotJS 监听鼠标点击事件,最后判断鼠标点击的位置是否在浏览器内部。以下是一个简单的示例: 首先,确保你已经安装了 Node.js 和 npm。然后&#xff…