【Unity】重力场中的路径预测方法

前言

笔者前些天参加完了一场72小时的GameJam游戏开发比赛。这次比赛的主题是“探索”,笔者做了一个名为《探索者号》的探索宇宙的游戏(游戏名一开始叫做《星际拾荒者》,但这不重要)。

在开发过程中,笔者遇到了一些问题,特此做下记录和分享,希望对大家和今后的我有所帮助。

笔者本次的参赛作品,在实现路径预测可视化时使用了RK4方法,效果还不错:

【72小时极限游戏开发挑战赛】探索者号

《探索者号》核心玩法

  • 玩家可以控制飞船加速和转向,并可以射击障碍物来保证自身不被撞毁,探索7颗星球。
  • 玩家的每个操作,还有随着时间流逝,都会消耗燃料。
  • 燃料耗尽后,玩家将无法操控飞船,但5秒后会消耗生命值来补充一定燃料。
  • 玩家每接近一个星球,并使用引力弹弓离开时,会将燃料加满。
  • 生命值耗尽,游戏失败。

简而言之,就是借助引力弹弓来补充燃料和加速,以到达更远距离,探索更多星球的目的。

重力场中路径预测可视化

为什么有这种需求?

该游戏的难点在于,玩家无法凭空推算出或感觉出在飞船靠近行星时,应该如何调整自身方向,才能保证不撞到星球上,并且完成有效的“引力弹弓”动作。
所以笔者希望在游戏中添加一条路径预测的引导线,有了这根线,将大大降低新人玩家的上手难度。

核心思路

笔者的方法是在飞船对象上附加一个LineRenderer组件,利用它来绘制飞船在未来时间点的预测路径。

具体实现方式:首先利用当前的飞船速度和所受的引力影响,计算出飞船在“下一瞬间”的预期位置,并将这个位置设置为LineRenderer的第一个节点。接着基于这个预测位置,再计算出飞船在“下下一瞬间”的位置,将其设置为LineRenderer的第二个节点。通过重复这一过程,我们能够逐步构建出一系列时间点上的飞船位置节点。
通过将这些节点相连,形成了一条连续的引导线,这条引导线基于飞船的初始速度向量、飞船与行星之间的引力互动、以及它们的相对位置关系。这样在单个渲染帧内,我们就能够预测并展示飞船在接下来一段时间内的运动轨迹。

这种可视化的路径预测不仅增强了游戏的互动性和玩家的体验,还提供了一个直观的方式来理解和预测物体在复杂重力场中的动态行为。通过这种方法,玩家可以更好地规划飞船的航线,避免撞到星球,优化飞行轨迹。

常规方法

高中物理,略。

计算过程:
对于每个时间点  i : 预测位置:  S i = S i − 1 + U i t + 1 2 a i t 2 更新加速度:  a i = gravityStrength r i 2 更新速度:  V i = U i − 1 + a i t (假设 G × M 为行星的重力强度: g r a v i t y S t r e n g t h ) \text{对于每个时间点 } i: \\ \text{ 预测位置: } S_i = S_{i-1} + U_it + \frac{1}{2}a_it^2 \\ \text{ 更新加速度: } a_i = \frac{\text{gravityStrength}}{r_i^2} \\ \text{ 更新速度: } V_i = U_{i-1} + a_it \\ \\ (假设G×M为行星的重力强度:gravityStrength) 对于每个时间点 i 预测位置: Si=Si1+Uit+21ait2 更新加速度: ai=ri2gravityStrength 更新速度: Vi=Ui1+ait(假设G×M为行星的重力强度:gravityStrength

核心代码:

    // 目标行星Transformpublic Transform targetPlanet;// 行星重力强度public float gravityStrength;// 路径点数public int pathResolution = 50;// 预测路径总时长public float pathPredictTime = 5f;private LineRenderer lineRenderer;void Start(){lineRenderer = gameObject.AddComponent<LineRenderer>();lineRenderer.positionCount = pathResolution;}void Update(){// 其他运动逻辑// 调用UpdatePath进行路径预测UpdatePath(currentPos, currentVelocity, currentAcceleration);}// 更新路径预测private void UpdatePath(Vector2 currentPos, Vector2 currentVelocity, Vector2 currentAcceleration){// 每一步的时间间隔float t = pathPredictTime / pathResolution;for (int i = 0; i < pathResolution; i++){// 使用基本运动方程预测位置Vector2 predictedPos = currentPos + currentVelocity * t + 0.5f * currentAcceleration * t * t;// 将计算的位置设置为轨迹的一部分lineRenderer.SetPosition(i, predictedPos);// 基于新的预测位置,计算下一点的重力加速度Vector2 gravityDirection = (Vector2)targetPlanet.position - predictedPos;currentAcceleration = gravityDirection.normalized * gravityStrength / gravityDirection.sqrMagnitude;// 更新当前位置和速度currentPos = predictedPos;currentVelocity += currentAcceleration * t;}}

RK4方法

Runge-Kutta第四阶(RK4)算法,是一种用于求解常微分方程初值问题的数值方法。给定一个常微分方程
d y d t = f ( t , y ) \frac{\mathrm{d} y}{\mathrm{d} t} = f(t,y) dtdy=f(t,y),
及其初始条件
y ( t 0 ) = y 0 y(t_0)=y_0 y(t0)=y0,
RK4方法通过以下步骤来估计在处的值,其中 h h h是步长:
k 1 = f ( t , y ) , k 2 = f ( t + h 2 , y + h 2 k 1 ) , k 3 = f ( t + h 2 , y + h 2 k 2 ) , k 4 = f ( t + h , y + h k 3 ) , y ( t + h ) = y + h 6 ( k 1 + 2 k 2 + 2 k 3 + k 4 ) . \begin{align*} k_1 &= f(t, y), \\ k_2 &= f\left(t + \frac{h}{2}, y + \frac{h}{2}k_1\right), \\ k_3 &= f\left(t + \frac{h}{2}, y + \frac{h}{2}k_2\right), \\ k_4 &= f(t + h, y + hk_3), \\ \\ y(t + h) &= y + \frac{h}{6}(k_1 + 2k_2 + 2k_3 + k_4). \end{align*} k1k2k3k4y(t+h)=f(t,y),=f(t+2h,y+2hk1),=f(t+2h,y+2hk2),=f(t+h,y+hk3),=y+6h(k1+2k2+2k3+k4).

这个过程提供了一种高精度的方式来逼近常微分方程的解,通过将整个步长 h h h分为更小的部分并计算在这些部分上的斜率,然后将这些斜率的加权平均值用于最终的估计。

应用到游戏中:
△ t = T n k 1 v = v k 1 a = a ( p ) k 2 v = v + k 1 a ⋅ Δ t 2 k 2 a = a ( p + k 1 v ⋅ Δ t 2 ) k 3 v = v + k 2 a ⋅ Δ t 2 k 3 a = a ( p + k 2 v ⋅ Δ t 2 ) k 4 v = v + k 3 a ⋅ Δ t k 4 a = a ( p + k 3 v ⋅ Δ t ) v new = v + ( k 1 a + 2 k 2 a + 2 k 3 a + k 4 a ) ⋅ Δ t 6 p new = p + ( k 1 v + 2 k 2 v + 2 k 3 v + k 4 v ) ⋅ Δ t 6 a ( p ) = g ⋅ r 2 ∥ d ∥ 2 其中: 初始位置 p 和速度 v 需要根据游戏中实际情况确定 △ t :每一步的时间间隔 T :总预测时间 n :分辨率(对应 L i n e R e n d e r e r 的节点数) k 1... k 4 :四组斜率 d :物体到行星中心的向量 g :模拟行星重力强度(相当于 G M ) r :行星半径 ∥ d ∥ 2 : d 的平方模长 a :加速度 \begin{align*} \triangle t &= \frac{T}{n} \\ k1_v &= v \\ k1_a &= a(p) \\ k2_v &= v + k1_a \cdot \frac{\Delta t}{2} \\ k2_a &= a\left(p + k1_v \cdot \frac{\Delta t}{2}\right) \\ k3_v &= v + k2_a \cdot \frac{\Delta t}{2} \\ k3_a &= a\left(p + k2_v \cdot \frac{\Delta t}{2}\right) \\ k4_v &= v + k3_a \cdot \Delta t \\ k4_a &= a(p + k3_v \cdot \Delta t) \\ \\ v_{\text{new}} &= v + \frac{(k1_a + 2k2_a + 2k3_a + k4_a) \cdot \Delta t}{6} \\ p_{\text{new}} &= p + \frac{(k1_v + 2k2_v + 2k3_v + k4_v) \cdot \Delta t}{6} \\ \\ a(p) &= \frac{g \cdot r^2}{\|d\|^2} \\ 其中 :& \\ &初始位置p和速度v需要根据游戏中实际情况确定 \\ \triangle t&:每一步的时间间隔 \\ T &:总预测时间 \\ n &:分辨率(对应LineRenderer的节点数) \\ k1...k4 &:四组斜率 \\ d &:物体到行星中心的向量 \\ g &:模拟行星重力强度(相当于GM) \\ r &:行星半径 \\ \|d\|^2 &:d的平方模长 \\ a &:加速度 \end{align*} tk1vk1ak2vk2ak3vk3ak4vk4avnewpnewa(p)其中:tTnk1...k4dgrd2a=nT=v=a(p)=v+k1a2Δt=a(p+k1v2Δt)=v+k2a2Δt=a(p+k2v2Δt)=v+k3aΔt=a(p+k3vΔt)=v+6(k1a+2k2a+2k3a+k4a)Δt=p+6(k1v+2k2v+2k3v+k4v)Δt=d2gr2初始位置p和速度v需要根据游戏中实际情况确定:每一步的时间间隔:总预测时间:分辨率(对应LineRenderer的节点数):四组斜率:物体到行星中心的向量:模拟行星重力强度(相当于GM:行星半径d的平方模长:加速度

核心代码:

using UnityEngine;public class PathPrediction : MonoBehaviour
{// 玩家的初始位置和速度public Vector2 initialPosition;public Vector2 initialVelocity;// 表示重力场源的行星public Transform planetTransform;// 行星的重力强度public float planetGravity;// 行星的半径public float planetRadius;// 路径分辨率,即路径上的点数public int pathResolution = 100;// 预测路径的总时长public float pathPredictTime = 5f;private LineRenderer lineRenderer;private void Start(){lineRenderer = GetComponent<LineRenderer>();lineRenderer.positionCount = pathResolution;UpdatePathWithRK4();}// 使用RK4算法更新路径private void UpdatePathWithRK4(){Vector2 currentPos = initialPosition;Vector2 currentVelocity = initialVelocity;float deltaTime = pathPredictTime / pathResolution;for (int i = 0; i < pathResolution; i++){// RK4方法的四个步骤Vector2 k1_vel = currentVelocity;Vector2 k1_acc = CalculateAcceleration(currentPos);Vector2 k2_vel = currentVelocity + k1_acc * (deltaTime / 2f);Vector2 k2_acc = CalculateAcceleration(currentPos + k1_vel * (deltaTime / 2f));Vector2 k3_vel = currentVelocity + k2_acc * (deltaTime / 2f);Vector2 k3_acc = CalculateAcceleration(currentPos + k2_vel * (deltaTime / 2f));Vector2 k4_vel = currentVelocity + k3_acc * deltaTime;Vector2 k4_acc = CalculateAcceleration(currentPos + k3_vel * deltaTime);// 使用四个斜率的加权平均值来更新速度和位置currentVelocity += (k1_acc + 2f * (k2_acc + k3_acc) + k4_acc) * (deltaTime / 6f);currentPos += (k1_vel + 2f * (k2_vel + k3_vel) + k4_vel) * (deltaTime / 6f);// 更新LineRenderer以显示路径lineRenderer.SetPosition(i, new Vector3(currentPos.x, currentPos.y, 0));}}// 计算给定位置处的加速度,考虑重力场的影响private Vector2 CalculateAcceleration(Vector2 position){Vector2 gravityDirection = (Vector2)planetTransform.position - position;// 使用万有引力公式计算加速度return gravityDirection.normalized * (planetGravity * Mathf.Pow(planetRadius, 2) / gravityDirection.sqrMagnitude);}
}

总结

简单方法

优点:

  • 简单直观,适用于线性系统或短时间内预测。
  • 计算速度快。

缺点:

  • 对于非线性系统或需要长时间预测的情况,简单的逼近方法可能不够精确,尤其是在引力场强烈变化的区域。

RK4方法

优点:

  • 精度高,适用于复杂的动态系统,特别是需要准确模拟物理行为的系统。
  • 稳定性强,在处理较平滑的动力学问题,拥有较高的稳定性。

缺点:

  • 与简单方法相比,RK4需要在每个时间步长中计算四次斜率,这增加了每个时间步的计算负担。
  • 实现更复杂,不易理解,需要更多的编码工作和调试。

(本游戏由于场景简单,多在路径预测上多花些资源也不算过分,于是使用了RK4方法,效果如文章开头的视频中所示)
请添加图片描述

实际使用中,我们可以根据不同的场景,选择更加合适的方法。

大佬们如果有优化思路,或者更多实现方式,也请多多指点!

吉祥话

最后祝大家新年快乐,长命百岁!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/673571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

006集——where语句进行属性筛选——arcgis

在arcgis中&#xff0c; dBASE 文件除了 WHERE 语句以外&#xff0c;不支持 其它 SQL 命令。选择窗口如下&#xff1a; 首先&#xff0c;我们了解下什么是where语句。 WHERE语句是SQL语言中使用频率很高的一种语句。它的作用是从数据库表中选择一些特定的记录行来进行操作。WHE…

[VulnHub靶机渗透] dpwwn: 1

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

Python 线性回归可视化 并将回归函数放置到图像上

import matplotlib.pyplot as plt import scipy import seaborn as sns# 加载内置的数据集 df sns.load_dataset(tips)#create regplot p sns.regplot(xtotal_bill, ytip, datadf)#calculate slope and intercept of regression equation slope, intercept, r, p, sterr sci…

Android:国际化弹出框

3.13 风格与主题、国际化 1、应用国际化 应用国际化&#xff0c;通过修改系统语言&#xff0c;应用显示语言跟着改变。 选择Locale,点击>>符号。 创建多个国家&#xff0c;地区strings.xml文件&#xff0c;有一个默认strings.xml文件&#xff0c;各个stirngs.xml中<…

C语言指针函数学习2

之前写过一篇指针函数的博文&#xff1b;复习再学习一下&#xff1b; 指针函数&#xff0c;是一个函数&#xff0c;它的返回值是指针类型&#xff1b; 之前写了一个指针函数&#xff0c;返回一个 int * 类型的指针&#xff1b;下面做一个程序&#xff0c;返回一个结构体指针&a…

【golang】23、gorilla websocket 源码:examples、数据结构、流程

文章目录 一、examples1.1 echo1.1.1 server.go1.1.2 client.go 1.2 command1.2.1 功能和启动方式1.2.2 home.html1.2.3 main.go 1.3 filewatch1.3.1 html1.3.2 serveHome 渲染模板1.3.3 serveWs1.3.4 writer() 1.4 buffer pool1.4.1 server1.4.2 client 1.5 chat1.5.1 server1…

【Java】ArrayList和LinkedList的区别是什么

目录 1. 数据结构 2. 性能特点 3. 源码分析 4. 代码演示 5. 细节和使用场景 ArrayList 和 LinkedList 分别代表了两类不同的数据结构&#xff1a;动态数组和链表。它们都实现了 Java 的 List 接口&#xff0c;但是有着各自独特的特点和性能表现。 1. 数据结构 ArrayList…

Leetcode刷题笔记题解(C++):64. 最小路径和

思路一&#xff1a;dfs深度优先搜索&#xff0c;然后取最小路径值&#xff0c;但是时间消耗较大&#xff0c;时间复杂度可能不满足&#xff0c;代码如下&#xff1a; class Solution { public:int res 1000000;int rows,cols;int minPathSum(vector<vector<int>>…

力扣面试题 05.06. 整数转换(位运算)

Problem: 面试题 05.06. 整数转换 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.通过将两个数进行异或操作求出两个数中不同的位(不同的位异或后为二进制1); 2.统计异或后不同的位的个数(即异或后二进制为1的个数) 复杂度 时间复杂度: O ( 1 ) O(1) O(1) 空间…

实现远程开机(电脑)的各种方法总结

一.为什么要远程开机 因为工作需要&#xff0c;总是需要打开某台不在身边的电脑&#xff0c;相信很多值友也遇到过相同的问题&#xff0c;出门在外&#xff0c;或者在公司&#xff0c;突然需要的一个文件存在家里的电脑上&#xff0c;如果家里有人可以打个电话回家&#xff0c…

基于SpringBoot+Vue的校园博客管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

Maven之安装自定义jar到本地Maven仓库中

Maven之安装自定义jar到本地Maven仓库中 文章目录 Maven之安装自定义jar到本地Maven仓库中1. 命令行窗口安装方式1. 常用参数说明2. 安装实例 2. IDEA中安装方式3. 使用 1. 命令行窗口安装方式 安装指定文件到本地仓库命令&#xff1a;mvn install:install-file; 在windows的cm…

Docker-Learn(一)使用Dockerfile创建Docker镜像

1.创建并运行容器 编写Dockerfile&#xff0c;文件名字就是为Dockerfile 在自己的工作工作空间当中新建文件&#xff0c;名字为Docerfile vim Dockerfile写入以下内容&#xff1a; # 使用一个基础镜像 FROM ubuntu:latest # 设置工作目录 WORKDIR /app # 复制当前目…

Qt QVariant类应用

QVariant类 QVariant类本质为C联合(Union)数据类型&#xff0c;它可以保存很多Qt类型的值&#xff0c;包括 QBrush&#xff0c;QColor&#xff0c;QString等等&#xff0c;也能存放Qt的容器类型的值。 QVariant::StringList 是 Qt 定义的一个 QVariant::type 枚举类型的变量&…

IntelliJ IDEA 2023.3发布,AI 助手出世,新特性杀麻了!!

目录 关键亮点 对 Java 21 功能的完全支持 调试器中的 Run to Cursor&#xff08;运行到光标)嵌入选项 带有编辑操作的浮动工具栏 用户体验优化 Default&#xff08;默认&#xff09;工具窗口布局选项 默认颜色编码编辑器标签页 适用于 macOS 的新产品图标 Speed Sear…

Android中的MVVM

演变 开发常用的框架包括MVC、MVP和本文的MVVM&#xff0c;三种框架都是为了分离ui界面和处理逻辑而出现的框架模式。mvp、mvvm都由mvc演化而来&#xff0c;他们不属于某种语言的框架&#xff0c;当存在ui页面和逻辑代码时&#xff0c;我们就可以使用这三种模式。 model和vie…

WordPress突然后台无法管理问题

登录WordPress后台管理评论&#xff0c;发现点击编辑、回复均无反应。 尝试清除缓存、关闭CF连接均无效。 查看插件时发现关闭wp-china-yes插件可以解决问题。 后来又测试了下发现加速管理后台这项&#xff0c;在启用时会发生点击无效问题&#xff0c;禁用就好了&#xff0c;不…

npm 下载报错

报错信息 : 证书过期 (CERT_HAS_EXPIRED) D:\Apps\nodejs-v18.16.1\npx.cmd --yes create-next-app"latest" . --ts npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.org/create-next-app failed…

详解各种LLM系列|LLaMA 1 模型架构、预训练、部署优化特点总结

作者 | Sunnyyyyy 整理 | NewBeeNLP https://zhuanlan.zhihu.com/p/668698204 后台留言『交流』&#xff0c;加入 NewBee讨论组 LLaMA 是Meta在2023年2月发布的一系列从 7B到 65B 参数的基础语言模型。LLaMA作为第一个向学术界开源的模型&#xff0c;在大模型爆发的时代具有标…

CDN相关和HTTP代理

CDN相关和HTTP代理 参考&#xff1a; 《透视 HTTP 协议》——chrono 把这两个放在一起是因为容易搞混&#xff0c;我一开始总以为CDN就是HTTP代理&#xff0c;但是看了极客时间里透视HTTP协议的讲解&#xff0c;感觉又不仅于此&#xff0c;于是专门写下来。 先说结论&#xf…