【Godot4.3】简单物理模拟之圆粒子碰撞检测

概述

最近开始研究游戏物理的内容,研究运动、速度、加速度之类的内容。也开始模仿一些简单的粒子模拟。这些都是一些基础、简单且古老的算法,但是对于理解游戏内的物理模拟很有帮助。甚至你可以在js、Python或者其他程序语言中实现它们。

图形的碰撞检测是第一个我想要实践的内容,而在碰撞检测中最简单的应该就是圆的碰撞检测了,本篇就简单实践一下圆的碰撞检测。

从圆开始

为什么,因为圆的圆心到其表面的距离是一样的。那么判断两个圆之间的重叠就非常简单,只要判断两个圆圆心之间的距离是否小于两者半径之和就可以了。如果是大小相同的粒子系统,每个圆的半径一样。

  • 重叠距离:等于两者半径之和-两圆心之间的距离。
  • 检测到重叠之后,将它们沿着圆形连线所在直线,沿着相反方向各自推动1/2的重叠距离,就可以将它们互相推开。
  • 当然这里没有考虑粒子各自的质量,默认将它们都是相同的质量,否则各自推开的距离应该是不一样的,比如小圆质量小应该推开更大的距离

测试

实现两个圆形粒子间的重叠检测和推开

swift 复制代码
extends Node2D

var particles:PackedVector2Array = Points2D.Vec2Arr("300,420 320,450")
var radius = 50.0
var color = Color.AQUA

func _draw() -> void:
	for i in range(particles.size()):
		draw_circle(particles[i],radius,color,false,1)

# 鼠标中键改变第二个圆的位置
func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_RIGHT:
			particles[1] = get_global_mouse_position()
			queue_redraw()

# 点击按钮执行重叠检测
func _on_button_pressed() -> void:
	test_overlap()

# 检测圆之间是否重叠
func test_overlap():
	var p1 = particles[0]  # 圆1
	var p2 = particles[1]  # 圆2
	var dis = p1.distance_to(p2)  # 距离
	var dir = p1.direction_to(p2) # 方向
	if dis < radius * 2:
		var over_dis = radius * 2 - dis
		particles[0] = particles[0] - dir  * over_dis/2.0 # 推开重叠距离的1/2
		particles[1] = particles[1] + dir  * over_dis/2.0 # 推开重叠距离的1/2
		queue_redraw()

运行效果:

点击"执行检测"按钮,如果两个圆之间的距离小于它们半径之和,表示有重叠,就会沿两者圆心连线方向将彼此推开重叠距离的1/2。推开后的效果:

这样,基于两个圆之间的重叠检测和推开功能已经实现。

动态检测

通过用鼠标动态控制两圆中的一个,并且将碰撞检测放到_process()中,就可以实现动态的碰撞检测。

swift 复制代码
extends Node2D

var particles:PackedVector2Array = Points2D.Vec2Arr("300,420 320,450")
var radius = 50.0
var color = Color.AQUA


func _process(delta: float) -> void:
	particles[1] = get_global_mouse_position()
	queue_redraw()
	test_overlap()

func _draw() -> void:
	for i in range(particles.size()):
		draw_circle(particles[i],radius,color,false,1)


# 检测圆之间是否重叠
func test_overlap():
	var p1 = particles[0]  # 圆1
	var p2 = particles[1]  # 圆2
	var dis = p1.distance_to(p2)  # 距离
	var dir = p1.direction_to(p2) # 方向
	if dis < radius * 2:
		var over_dis = radius * 2 - dis
		particles[0] = particles[0] - dir  * over_dis/2.0 # 推开重叠距离的1/2
		particles[1] = particles[1] + dir  * over_dis/2.0 # 推开重叠距离的1/2
		queue_redraw()

运行效果:

可以看到有一种用一个圆推动另一个圆的效果。

多粒子测试

在两个粒子时用PackedVector2Array是没有问题的,但是当粒子比较多时,最好还是使用一个简单的自定义类型来表示粒子。

这里我申明一个Particle类,表示简单的圆形粒子:

swift 复制代码
# 粒子类型
class Particle:
	var position:Vector2  # 位置
	var radius:float      # 半径
	
	func _init(position:Vector2,radius:float) -> void:
		self.position = position
		self.radius = radius

则测试代码可以改成:

swift 复制代码
extends Node2D

var particles:Array[Particle]
var radius = 50.0
var color = Color.AQUA
var max_count = 100   # 粒子数目

func _ready() -> void:
	set_process(false) # 禁用process
	var rect = get_viewport_rect()
	# 创建粒子
	for i in range(max_count):
		var pos = Vector2(randf_range(0,rect.size.x),randf_range(0,rect.size.y))
		var p = Particle.new(pos,radius)
		particles.append(p)

func _process(delta: float) -> void:
	for i in range(particles.size()):
		test_overlap_all(i)

func _draw() -> void:
	for i in range(particles.size()):
		draw_circle(particles[i].position,radius,color,false,1)


# 遍历检测所有的粒子
func test_overlap_all(idx:int):
	var p1 = particles[idx]
	for i in range(particles.size()):
		if i != idx:
			var p2 = particles[i]
			test_overlap(p1,p2)

# 检测圆之间是否重叠
func test_overlap(p1:Particle,p2:Particle):
	var dis = p1.position.distance_to(p2.position)  # 距离
	var dir = p1.position.direction_to(p2.position) # 方向
	var min_dis = p1.radius + p2.radius
	if dis < min_dis:
		var over_dis = min_dis - dis
		p1.position = p1.position - dir  * over_dis/2.0 # 推开重叠距离的1/2
		p2.position = p2.position + dir  * over_dis/2.0 # 推开重叠距离的1/2
		queue_redraw()

# 启用重叠检测
func _on_button_pressed() -> void:
	set_process(true)

这里:

  • 在运行时创建随机位置的100个半径为50像素的圆粒子,初始禁用_process和重叠检测
  • 点击"开启检测"按钮时,启用_process和重叠检测
  • 通过不断的遍历所有圆粒子和其他粒子,并进行重叠检测和推开操作,可以让所有粒子都不重叠

通过鼠标操纵其中的一个圆的位置,便可以实现动态的碰撞检测效果。

swift 复制代码
func _process(delta: float) -> void:
	particles[1].position = get_global_mouse_position()
	queue_redraw()
	for i in range(particles.size()):
		test_overlap_all(i)

运行效果:

总结

  • 本篇简述在Godot用CanvasItem绘图函数实现简单的圆粒子和元粒子的重叠检测与推开操作
  • 这是图形碰撞检测算法的第一篇,后续文章将讨论矩形包围盒的算法
  • 这个算法在粒子数目较多时有明显的延迟,后文将讨论优化算法
相关推荐
锋风Fengfeng1 小时前
安卓15预置第三方apk时签名报错问题解决
android
User_undefined1 小时前
uniapp Native.js原生arr插件服务发送广播到uniapp页面中
android·javascript·uni-app
程序员厉飞雨2 小时前
Android R8 耗时优化
android·java·前端
丘狸尾4 小时前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~6 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
Crossoads10 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
li_liuliu11 小时前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime13 小时前
自建MD5解密平台-续
android
鲤籽鲲15 小时前
C# Random 随机数 全面解析
android·java·c#
m0_5485147718 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php