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

news2024/11/15 10:58:01

前言

笔者前些天参加完了一场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

核心代码:

    // 目标行星Transform
    public 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.coloradmin.cn/o/1440041.html

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

相关文章

2 月 7 日算法练习- 数据结构-并查集

并查集 并查集是一种图形数据结构&#xff0c;用于存储图中结点的连通关系。 每个结点有一个父亲&#xff0c;可以理解为“一只伸出去的手”&#xff0c;会指向另外一个点&#xff0c;初始时指向自己。 一个点的根节点是该点的父亲的父亲的的父亲&#xff0c;直到某个点的父亲…

探索NLP中的N-grams:理解,应用与优化

简介 n-gram[1] 是文本文档中 n 个连续项目的集合&#xff0c;其中可能包括单词、数字、符号和标点符号。 N-gram 模型在许多与单词序列相关的文本分析应用中非常有用&#xff0c;例如情感分析、文本分类和文本生成。 N-gram 建模是用于将文本从非结构化格式转换为结构化格式的…

Elasticsearch(四)

是这样的前面的几篇笔记&#xff0c;感觉对我没有形成知识体系&#xff0c;感觉乱糟糟的&#xff0c;只是大概的了解了一些基础知识&#xff0c;仅此而已&#xff0c;而且对于这技术栈的学习也是为了在后面的java开发使用&#xff0c;但是这里的API学的感觉有点乱&#xff01;然…

[VulnHub靶机渗透] dpwwn: 1

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

接口测试--参数实现MD5加密签名规则

最近有个测试接口需求&#xff0c;接口有签名检查&#xff0c;签名规范为将所有请求参数按照key字典排序并连接起来进行md5加密&#xff0c;格式是&#xff1a;md5(bar2&baz3&foo1),得到签名&#xff0c;将签名追加到参数末尾。由于需要对参数进行动态加密并且做压力测…

有道ai写作,突破免费限制,无限制使用

预览效果 文末提供源码包及apk下载地址有道ai写作python版 import hashlib import time import json import ssl import base64 import uuidfrom urllib.parse import quote import requests from requests_toolbelt.multipart.encoder import MultipartEncoder from Crypto.C…

11 串口发送应用之使用状态机实现多字节数据发送

1. 使用串口发送5个字节数据到电脑 uart协议规定&#xff0c;发送的数据位只能是6&#xff0c;7&#xff0c;8位&#xff0c;如果数据位不符合&#xff0c;接收者接收不到数据。所以我们需要将40位数据data分为5个字节数据分别发送&#xff0c;那么接收者就能通过uart协议接收…

Mobile ALOHA 2: An Enhanced Low-Cost Hardware for Bimanual Teleoperation

文章目录 1. Mobile ALOHA 11.1 项目地址 2. Mobile ALOHA 22.1 相关链接2.2 Whats upgraded in II ? Reference Stanford 最新家务机器人 1. Mobile ALOHA 1 Mobile ALOHA: Learning Bimanual Mobile Manipulation with Low-Cost Whole-Body Teleoperation 1.1 项目地址 htt…

CoreSight学习笔记

文章目录 1 Components1.1 ROM Table 2 使用场景2.1 Debug Monitor中断2.1.1 参考资料 2.2 Programming the cross halt2.2.1 编程实现2.2.2 参考资料 2.3 CTI中断2.3.1 编程实现2.3.1.1 准备工作2.3.1.2 触发中断2.3.1.3 中断响应 2.3.2 参考资料 1 Components 1.1 ROM Table…

力扣热门100题 - 4.寻找两个正序数组的中位数

力扣热门100题 - 4.寻找两个正序数组的中位数 题目描述&#xff1a;示例&#xff1a;提示&#xff1a;解题思路&#xff1a;代码&#xff1a; 题目链接&#xff1a;4.寻找两个正序数组的中位数 题目描述&#xff1a; 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&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…

掌握虚拟化与网络配置之道:深入浅出VMware及远程管理技巧

目录 虚拟机介绍 虚拟机的关键字 服务器架构的发展 为什么用虚拟机VMware 虚拟机和阿里云的区别 功能角度 价格因素 应用场景 优势方面 找到windows的服务管理 配置VMware 关于VMware安装的几个服务 vmware如何修改各种网络配置 关于NAT的详细信息(了解) NAT(网…

【芯片设计- RTL 数字逻辑设计入门 11 -- 移位运算与乘法】

请阅读【嵌入式开发学习必备专栏 】 文章目录 移位运算与乘法Verilog Codeverilog 拼接运算符&#xff08;{}&#xff09;Testbench CodeVCS 波形仿真 问题小结 移位运算与乘法 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输…

LLaMA 入门指南

LLaMA 入门指南 LLaMA 入门指南LLaMA的简介LLaMA模型的主要结构Transformer架构多层自注意力层前馈神经网络Layer Normalization和残差连接 LLaMA模型的变体Base版本Large版本Extra-Large版本 LLaMA模型的特点大规模数据训练 LLaMA模型常用数据集介绍公共数据来源已知的数据集案…

物理信息神经网络(PINN): 将物理知识融合到深度学习中

物理信息神经网络&#xff08;PINN&#xff09;: 将物理知识融合到深度学习中 物理信息神经网络&#xff08;PINN&#xff09;简介PINN的工作原理PINN模型如何利用物理法则指导模型训练1. 定义物理问题和相应的物理定律2. 构建神经网络3. 定义损失函数数据误差项 (Data-fidelit…

Docker-Learn(三)创建镜像Docker(换源)

根据之前的内容基础&#xff0c;本小点的内容主要涉及到的内容是比较重要的文本Dockerfile 1. 编辑Dockerfile 启动命令行终端&#xff08;在自己的工作空间当中&#xff09;,创建和编辑Dockerfile。 vim Dockerfile然后写入以下内容 # 使用一个基础镜像 FROM ubuntu:late…

Mysql为什么使用B+Tree作为索引结构

B树和B树 一般来说&#xff0c;数据库的存储引擎都是采用B树或者B树来实现索引的存储。首先来看B树&#xff0c;如图所示&#xff1a; B树是一种多路平衡树&#xff0c;用这种存储结构来存储大量数据&#xff0c;它的整个高度会相比二叉树来说&#xff0c;会矮很多。 而对于数…

苹果推出新型开源AI图像编辑模型“MGIE”;可汗学院辅助学习的GPT,Prompt 质量非常高

&#x1f989; AI新闻 &#x1f680; 苹果推出新型开源AI图像编辑模型“MGIE” 摘要&#xff1a;苹果公司最近发布了一个名为“MGIE”的开源人工智能模型&#xff0c;旨在通过自然语言指令对图片进行编辑。MGIE&#xff0c;全称MLLM-Guided Image Editing&#xff0c;依赖于多…

文献速递:肿瘤分割---- 弱监督教师-学生网络用于非增强图像的肝脏肿瘤分割

文献速递&#xff1a;肿瘤分割---- 弱监督教师-学生网络用于非增强图像的肝脏肿瘤分割 01 文献速递介绍 准确的肝脏肿瘤分割对放射科医师来说是必不可少的&#xff0c;以诊断和治疗肝脏肿瘤并提高患者的生存率&#xff08;Radtke 等人&#xff0c;2007年&#xff09;。特别是…

代码随想录算法训练营29期|day44 任务以及具体任务

动态规划&#xff1a;完全背包理论基础 本题力扣上没有原题&#xff0c;大家可以去卡码网第52题 (opens new window)去练习&#xff0c;题意是一样的。 #算法公开课 《代码随想录》算法视频公开课 (opens new window)&#xff1a;带你学透完全背包问题&#xff01; (opens …