登月模拟游戏开发:矢量图形、物理引擎与遗传算法
《登月模拟游戏开发:矢量图形、物理引擎与遗传算法》 摘要: 本章详细介绍了基于Unity的登月模拟游戏开发全过程,涵盖矢量图形处理、物理引擎实现和遗传算法控制三大核心技术。首先讲解了矢量图形的数学基础与Unity实现方法,包括顶点变换和矩阵运算。随后深入解析了游戏物理系统构建,从牛顿力学基础到月球重力环境模拟。在人工控制部分,完整实现了包含燃料管理、姿态控制和碰撞检测的着陆器系统。最后创新性地引入
登月模拟游戏开发:矢量图形、物理引擎与遗传算法
本章我们将深入探讨游戏开发中的矢量图形处理、物理模拟以及使用遗传算法实现自动控制系统,以登月飞行模拟为核心案例。我们将从基础矢量数学开始,构建一个完整的登月模拟游戏,包括人工控制和AI控制两种方式。
6.1 创建和处理矢量图形
在开发登月模拟游戏之前,我们需要先了解如何在Unity中创建和处理矢量图形。矢量图形是基于数学表达式的图形,由点、线、曲线等几何元素组成,不受分辨率影响,可以任意缩放而不失真。
基础概念:矢量图形
矢量图形的核心元素包括:
- 顶点(Vertex):定义图形中的点
- 路径(Path):连接顶点的线段或曲线
- 填充(Fill):封闭路径内部的颜色或纹理
- 描边(Stroke):路径本身的颜色和宽度
在Unity中,我们可以使用LineRenderer组件或通过编程方式创建和操作矢量图形。
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 基础矢量图形生成器
/// </summary>
public class VectorGraphicsGenerator : MonoBehaviour
{
[Header("线条设置")]
public float lineWidth = 0.1f;
public Color lineColor = Color.white;
private LineRenderer lineRenderer;
void Start()
{
// 初始化LineRenderer组件
lineRenderer = gameObject.AddComponent<LineRenderer>();
lineRenderer.startWidth = lineWidth;
lineRenderer.endWidth = lineWidth;
lineRenderer.startColor = lineColor;
lineRenderer.endColor = lineColor;
lineRenderer.useWorldSpace = false;
// 设置材质以获得更好的外观
lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
// 生成示例图形
DrawRocket();
}
// 绘制简单的火箭矢量图形
void DrawRocket()
{
List<Vector3> points = new List<Vector3>();
// 火箭头部
points.Add(new Vector3(0, 2, 0));
points.Add(new Vector3(1, 0, 0));
points.Add(new Vector3(0.5f, 0, 0));
points.Add(new Vector3(0.5f, -3, 0));
points.Add(new Vector3(-0.5f, -3, 0));
points.Add(new Vector3(-0.5f, 0, 0));
points.Add(new Vector3(-1, 0, 0));
points.Add(new Vector3(0, 2, 0)); // 闭合路径
// 设置点数和位置
lineRenderer.positionCount = points.Count;
lineRenderer.SetPositions(points.ToArray());
}
// 绘制月球着陆器
public void DrawLander()
{
List<Vector3> points = new List<Vector3>();
// 着陆器舱体
points.Add(new Vector3(-1, 0.5f, 0));
points.Add(new Vector3(1, 0.5f, 0));
points.Add(new Vector3(1, -0.5f, 0));
points.Add(new Vector3(-1, -0.5f, 0));
points.Add(new Vector3(-1, 0.5f, 0)); // 闭合路径
// 着陆器着陆腿
points.Add(new Vector3(-1, -0.5f, 0));
points.Add(new Vector3(-1.5f, -1.5f, 0));
points.Add(new Vector3(1, -0.5f, 0));
points.Add(new Vector3(1.5f, -1.5f, 0));
// 引擎喷口
points.Add(new Vector3(-0.2f, -0.5f, 0));
points.Add(new Vector3(0.2f, -0.5f, 0));
// 设置点数和位置
lineRenderer.positionCount = points.Count;
lineRenderer.SetPositions(points.ToArray());
}
}
这个基础示例展示了如何使用LineRenderer创建简单的矢量图形。但在实际开发中,我们通常需要更复杂的图形和更灵活的控制。
6.1.1 顶点和顶点缓冲
顶点是构建任何图形的基础单元。在创建复杂的矢量图形时,我们需要高效地管理这些顶点数据。
顶点缓冲的概念
顶点缓冲(Vertex Buffer)是存储顶点数据的内存区域,它允许我们高效地管理和操作大量顶点。在Unity中,我们可以使用Mesh类和自定义的顶点数组来实现这一功能。
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 高级矢量图形生成器
/// </summary>
public class AdvancedVectorGraphics : MonoBehaviour
{
[Header("网格设置")]
public Material material;
private MeshFilter meshFilter;
private MeshRenderer meshRenderer;
void Start()
{
// 初始化组件
meshFilter = gameObject.AddComponent<MeshFilter>();
meshRenderer = gameObject.AddComponent<MeshRenderer>();
if (material == null)
material = new Material(Shader.Find("Sprites/Default"));
meshRenderer.material = material;
// 创建月球着陆器图形
CreateLanderMesh();
}
void CreateLanderMesh()
{
Mesh mesh = new Mesh();
// 定义顶点
Vector3[] vertices = new Vector3[]
{
// 主舱体
new Vector3(-1, 0.5f, 0),
new Vector3(1, 0.5f, 0),
new Vector3(1, -0.5f, 0),
new Vector3(-1, -0.5f, 0),
// 左腿
new Vector3(-1, -0.5f, 0),
new Vector3(-1.5f, -1.5f, 0),
// 右腿
new Vector3(1, -0.5f, 0),
new Vector3(1.5f, -1.5f, 0),
// 引擎
new Vector3(-0.2f, -0.5f, 0),
new Vector3(0.2f, -0.5f, 0),
new Vector3(0, -1f, 0)
};
// 定义三角形
int[] triangles = new int[]
{
// 主舱体
0, 1, 2,
0, 2, 3,
// 引擎
8, 9, 10
};
// 定义UV坐标(简化)
Vector2[] uv = new Vector2[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
uv[i] = new Vector2(vertices[i].x + 1, vertices[i].y + 1) * 0.5f;
}
// 设置网格数据
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uv;
// 计算法线
mesh.RecalculateNormals();
// 分配网格
meshFilter.mesh = mesh;
}
// 创建线网格(用于绘制线条而非填充图形)
public Mesh CreateLineMesh(Vector3[] points, float width)
{
Mesh mesh = new Mesh();
List<Vector3> vertices = new List<Vector3>();
List<int> indices = new List<int>();
for (int i = 0; i < points.Length - 1; i++)
{
Vector3 p1 = points[i];
Vector3 p2 = points[i + 1];
Vector3 dir = (p2 - p1).normalized;
Vector3 perpendicular = new Vector3(-dir.y, dir.x, 0) * width * 0.5f;
// 为每条线段创建四个顶点
vertices.Add(p1 + perpendicular);
vertices.Add(p1 - perpendicular);
vertices.Add(p2 + perpendicular);
vertices.Add(p2 - perpendicular);
// 为每条线段创建两个三角形(一个四边形)
int baseIndex = i * 4;
indices.Add(baseIndex);
indices.Add(baseIndex + 1);
indices.Add(baseIndex + 2);
indices.Add(baseIndex + 1);
indices.Add(baseIndex + 3);
indices.Add(baseIndex + 2);
}
mesh.vertices = vertices.ToArray();
mesh.triangles = indices.ToArray();
return mesh;
}
}
这个高级矢量图形生成器使用了网格(Mesh)来创建更复杂的图形,它允许我们不仅绘制线条,还能填充图形并设置纹理。
6.1.2 顶点变换
在图形编程中,顶点变换是一个核心概念。它允许我们对图形进行平移、旋转、缩放等操作。在月球着陆器模拟中,我们需要实时变换图形来表示着陆器的位置和方向。
变换矩阵的基础
变换矩阵是执行顶点变换的数学工具。Unity中使用4x4的矩阵来表示三维变换。让我们创建一个可以实时变换的月球着陆器:
csharp
using UnityEngine;
/// <summary>
/// 可变换的月球着陆器
/// </summary>
public class TransformableLander : MonoBehaviour
{
[Header("图形设置")]
public float scale = 1.0f;
public Color color = Color.white;
public float outlineWidth = 0.05f;
[Header("变换")]
public Vector2 position;
public float rotation;
private LineRenderer bodyRenderer;
private LineRenderer flameRenderer;
private Transform landerTransform;
// 着陆器外形的顶点定义
private Vector2[] landerVertices = new Vector2[]
{
new Vector2(-1.0f, 0.5f), // 左上
new Vector2(1.0f, 0.5f), // 右上
new Vector2(1.0f, -0.5f), // 右下
new Vector2(-1.0f, -0.5f), // 左下
new Vector2(-1.0f, 0.5f), // 闭合到左上
// 着陆腿(左)
new Vector2(-1.0f, -0.5f),
new Vector2(-1.5f, -1.5f),
// 着陆腿(右)
new Vector2(1.0f, -0.5f),
new Vector2(1.5f, -1.5f)
};
// 引擎火焰的顶点定义
private Vector2[] flameVertices = new Vector2[]
{
new Vector2(-0.2f, -0.5f),
new Vector2(0.0f, -1.2f),
new Vector2(0.2f, -0.5f)
};
void Start()
{
// 创建着陆器图形容器
GameObject landerGraphics = new GameObject("LanderGraphics");
landerTransform = landerGraphics.transform;
landerTransform.SetParent(transform);
// 创建着陆器主体渲染器
bodyRenderer = landerGraphics.AddComponent<LineRenderer>();
ConfigureLineRenderer(bodyRenderer, color, outlineWidth, landerVertices.Length);
// 创建引擎火焰渲染器
GameObject flameObject = new GameObject("EngineFire");
flameObject.transform.SetParent(landerTransform);
flameRenderer = flameObject.AddComponent<LineRenderer>();
ConfigureLineRenderer(flameRenderer, new Color(1, 0.5f, 0), outlineWidth, flameVertices.Length);
// 初始变换
UpdateTransform();
}
void Update()
{
// 更新变换
UpdateTransform();
// 更新引擎火焰(闪烁效果)
UpdateEngineFlame();
}
// 配置LineRenderer组件
void ConfigureLineRenderer(LineRenderer renderer, Color lineColor, float width, int pointCount)
{
renderer.startWidth = width;
renderer.endWidth = width;
renderer.startColor = lineColor;
renderer.endColor = lineColor;
renderer.positionCount = pointCount;
renderer.useWorldSpace = false;
renderer.material = new Material(Shader.Find("Sprites/Default"));
}
// 更新变换
void UpdateTransform()
{
// 设置位置
transform.position = new Vector3(position.x, position.y, 0);
// 设置旋转
transform.rotation = Quaternion.Euler(0, 0, rotation);
// 设置缩放
transform.localScale = Vector3.one * scale;
// 更新线渲染器顶点
UpdateLanderVertices();
}
// 更新着陆器顶点
void UpdateLanderVertices()
{
// 主体线条
for (int i = 0; i < landerVertices.Length; i++)
{
bodyRenderer.SetPosition(i, new Vector3(landerVertices[i].x, landerVertices[i].y, 0));
}
}
// 更新引擎火焰(添加随机闪烁效果)
void UpdateEngineFlame()
{
// 根据是否推力来控制火焰可见性
bool showFlame = Input.GetKey(KeyCode.Space);
flameRenderer.enabled = showFlame;
if (showFlame)
{
// 随机变化火焰长度来创建闪烁效果
float flameScale = 1.0f + 0.2f * Mathf.Sin(Time.time * 10);
for (int i = 0; i < flameVertices.Length; i++)
{
Vector2 vertex = flameVertices[i];
// 对于中心点,添加更多变化
if (i == 1)
{
vertex.y *= flameScale;
}
flameRenderer.SetPosition(i, new Vector3(vertex.x, vertex.y, 0));
}
}
}
// 设置引擎推力(控制火焰大小)
public void SetThrusterPower(float power)
{
if (power > 0)
{
flameRenderer.enabled = true;
// 调整火焰长度
Vector2 middlePoint = flameVertices[1];
middlePoint.y = -0.5f - 0.7f * power;
flameRenderer.SetPosition(1, new Vector3(middlePoint.x, middlePoint.y, 0));
}
else
{
flameRenderer.enabled = false;
}
}
// 应用自定义变换矩阵(高级用法)
public void ApplyCustomTransform(Matrix4x4 transformMatrix)
{
Vector3[] transformedVertices = new Vector3[landerVertices.Length];
for (int i = 0; i < landerVertices.Length; i++)
{
Vector3 vertex = new Vector3(landerVertices[i].x, landerVertices[i].y, 0);
transformedVertices[i] = transformMatrix.MultiplyPoint(vertex);
}
for (int i = 0; i < transformedVertices.Length; i++)
{
bodyRenderer.SetPosition(i, transformedVertices[i]);
}
}
}
在这个类中,我们使用了Unity的变换系统来移动、旋转和缩放月球着陆器。我们还添加了一个引擎火焰效果,它会根据输入动态改变。此外,还提供了一个应用自定义变换矩阵的方法,这在某些高级应用场景中可能有用。
动画变换序列
在游戏中,我们经常需要一系列平滑的变换来创建动画效果。下面我们将创建一个简单的动画系统,使着陆器能够平滑地移动和旋转:
csharp
using UnityEngine;
using System.Collections;
/// <summary>
/// 着陆器动画控制器
/// </summary>
public class LanderAnimationController : MonoBehaviour
{
public TransformableLander lander;
[Header("动画设置")]
public float moveDuration = 1.0f;
public float rotateDuration = 0.5f;
private Vector2 targetPosition;
private float targetRotation;
private Coroutine moveCoroutine;
private Coroutine rotateCoroutine;
void Start()
{
if (lander == null)
lander = GetComponent<TransformableLander>();
targetPosition = lander.position;
targetRotation = lander.rotation;
}
// 移动到目标位置
public void MoveTo(Vector2 newPosition, bool animate = true)
{
targetPosition = newPosition;
if (animate)
{
// 停止任何现有的移动动画
if (moveCoroutine != null)
StopCoroutine(moveCoroutine);
// 开始新的移动动画
moveCoroutine = StartCoroutine(AnimateMovement());
}
else
{
// 立即设置位置
lander.position = newPosition;
}
}
// 旋转到目标角度
public void RotateTo(float newRotation, bool animate = true)
{
targetRotation = newRotation;
if (animate)
{
// 停止任何现有的旋转动画
if (rotateCoroutine != null)
StopCoroutine(rotateCoroutine);
// 开始新的旋转动画
rotateCoroutine = StartCoroutine(AnimateRotation());
}
else
{
// 立即设置旋转
lander.rotation = newRotation;
}
}
// 移动动画协程
private IEnumerator AnimateMovement()
{
Vector2 startPosition = lander.position;
float elapsedTime = 0;
while (elapsedTime < moveDuration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / moveDuration);
// 使用平滑插值函数
float smoothT = Mathf.SmoothStep(0, 1, t);
lander.position = Vector2.Lerp(startPosition, targetPosition, smoothT);
yield return null;
}
// 确保最终位置精确
lander.position = targetPosition;
moveCoroutine = null;
}
// 旋转动画协程
private IEnumerator AnimateRotation()
{
float startRotation = lander.rotation;
float elapsedTime = 0;
while (elapsedTime < rotateDuration)
{
elapsedTime += Time.deltaTime;
float t = Mathf.Clamp01(elapsedTime / rotateDuration);
// 使用平滑插值函数
float smoothT = Mathf.SmoothStep(0, 1, t);
lander.rotation = Mathf.Lerp(startRotation, targetRotation, smoothT);
yield return null;
}
// 确保最终旋转精确
lander.rotation = targetRotation;
rotateCoroutine = null;
}
// 执行随机移动(用于测试)
public void PerformRandomMovement()
{
Vector2 randomPosition = new Vector2(
Random.Range(-5f, 5f),
Random.Range(-5f, 5f)
);
float randomRotation = Random.Range(-180f, 180f);
MoveTo(randomPosition);
RotateTo(randomRotation);
}
}
这个动画控制器使用协程来创建平滑的过渡效果。它允许我们为着陆器设置目标位置和旋转,然后自动处理从当前状态到目标状态的过渡。
6.1.3 矩阵
矩阵是图形编程中的基础数学工具,用于执行各种变换操作。Unity内置了矩阵支持,通过Matrix4x4类提供了丰富的矩阵操作功能。
矩阵基础
矩阵是一个数字的矩形数组。在3D图形中,我们通常使用4x4矩阵来表示变换,它可以同时处理平移、旋转和缩放操作。
csharp
using UnityEngine;
/// <summary>
/// 矩阵变换演示
/// </summary>
public class MatrixTransformDemo : MonoBehaviour
{
public TransformableLander lander;
[Header("变换参数")]
public Vector2 translation = Vector2.zero;
public float rotation = 0;
public Vector2 scale = Vector2.one;
public Vector2 shear = Vector2.zero;
void Update()
{
if (lander == null)
return;
// 创建变换矩阵
Matrix4x4 transformMatrix = CreateTransformMatrix(translation, rotation, scale, shear);
// 应用变换
lander.ApplyCustomTransform(transformMatrix);
}
// 创建组合变换矩阵
Matrix4x4 CreateTransformMatrix(Vector2 translation, float rotation, Vector2 scale, Vector2 shear)
{
// 平移矩阵
Matrix4x4 translationMatrix = Matrix4x4.Translate(new Vector3(translation.x, translation.y, 0));
// 旋转矩阵(围绕Z轴)
Matrix4x4 rotationMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, 0, rotation));
// 缩放矩阵
Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(scale.x, scale.y, 1));
// 剪切矩阵(x和y方向)
Matrix4x4 shearMatrix = Matrix4x4.identity;
shearMatrix[0, 1] = shear.x; // X方向剪切
shearMatrix[1, 0] = shear.y; // Y方向剪切
// 组合矩阵(注意乘法顺序很重要)
// 变换顺序:缩放 -> 剪切 -> 旋转 -> 平移
return translationMatrix * rotationMatrix * shearMatrix * scaleMatrix;
}
// 演示矩阵插值
public void InterpolateMatrices(Matrix4x4 startMatrix, Matrix4x4 endMatrix, float t)
{
// 从矩阵中提取变换组件
Vector3 startTranslation = ExtractTranslation(startMatrix);
Quaternion startRotation = ExtractRotation(startMatrix);
Vector3 startScale = ExtractScale(startMatrix);
Vector3 endTranslation = ExtractTranslation(endMatrix);
Quaternion endRotation = ExtractRotation(endMatrix);
Vector3 endScale = ExtractScale(endMatrix);
// 插值各个组件
Vector3 interpolatedTranslation = Vector3.Lerp(startTranslation, endTranslation, t);
Quaternion interpolatedRotation = Quaternion.Slerp(startRotation, endRotation, t);
Vector3 interpolatedScale = Vector3.Lerp(startScale, endScale, t);
// 创建新的变换矩阵
Matrix4x4 interpolatedMatrix = Matrix4x4.TRS(
interpolatedTranslation,
interpolatedRotation,
interpolatedScale
);
// 应用插值后的矩阵
lander.ApplyCustomTransform(interpolatedMatrix);
}
// 从矩阵中提取平移部分
Vector3 ExtractTranslation(Matrix4x4 matrix)
{
return new Vector3(matrix[0, 3], matrix[1, 3], matrix[2, 3]);
}
// 从矩阵中提取旋转部分
Quaternion ExtractRotation(Matrix4x4 matrix)
{
// 注意:这种方法假设矩阵不包含非均匀缩放或剪切
return Quaternion.LookRotation(
new Vector3(matrix[0, 2], matrix[1, 2], matrix[2, 2]),
new Vector3(matrix[0, 1], matrix[1, 1], matrix[2, 1])
);
}
// 从矩阵中提取缩放部分
Vector3 ExtractScale(Matrix4x4 matrix)
{
return new Vector3(
new Vector3(matrix[0, 0], matrix[1, 0], matrix[2, 0]).magnitude,
new Vector3(matrix[0, 1], matrix[1, 1], matrix[2, 1]).magnitude,
new Vector3(matrix[0, 2], matrix[1, 2], matrix[2, 2]).magnitude
);
}
}
这个示例演示了如何使用矩阵执行各种变换操作,包括平移、旋转、缩放和剪切。它还展示了如何在两个矩阵之间进行插值,这在动画中非常有用。
矩阵栈
在复杂的图形应用中,我们经常需要使用矩阵栈来管理层次化的变换。例如,月球着陆器上的各个部件可能需要相对于着陆器本身进行变换。
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 矩阵栈管理器
/// </summary>
public class MatrixStackManager : MonoBehaviour
{
private Stack<Matrix4x4> matrixStack = new Stack<Matrix4x4>();
private Matrix4x4 currentMatrix = Matrix4x4.identity;
// 复合着陆器绘制器
public void DrawCompositeLander(Vector2 position, float rotation, float scale)
{
// 保存当前矩阵
PushMatrix();
// 应用主体变换
TranslateMatrix(position);
RotateMatrix(rotation);
ScaleMatrix(new Vector2(scale, scale));
// 绘制主体
DrawLanderBody();
// 绘制左着陆腿
PushMatrix();
TranslateMatrix(new Vector2(-1, -0.5f));
RotateMatrix(30); // 略微倾斜
DrawLanderLeg();
PopMatrix();
// 绘制右着陆腿
PushMatrix();
TranslateMatrix(new Vector2(1, -0.5f));
RotateMatrix(-30); // 略微倾斜
DrawLanderLeg();
PopMatrix();
// 绘制引擎
PushMatrix();
TranslateMatrix(new Vector2(0, -0.5f));
DrawEngine();
PopMatrix();
// 恢复原始矩阵
PopMatrix();
}
// 将当前矩阵压入栈
void PushMatrix()
{
matrixStack.Push(currentMatrix);
}
// 从栈中弹出矩阵并设置为当前矩阵
void PopMatrix()
{
if (matrixStack.Count > 0)
currentMatrix = matrixStack.Pop();
}
// 平移当前矩阵
void TranslateMatrix(Vector2 translation)
{
Matrix4x4 translationMatrix = Matrix4x4.Translate(new Vector3(translation.x, translation.y, 0));
currentMatrix = translationMatrix * currentMatrix;
}
// 旋转当前矩阵
void RotateMatrix(float degrees)
{
Matrix4x4 rotationMatrix = Matrix4x4.Rotate(Quaternion.Euler(0, 0, degrees));
currentMatrix = rotationMatrix * currentMatrix;
}
// 缩放当前矩阵
void ScaleMatrix(Vector2 scale)
{
Matrix4x4 scaleMatrix = Matrix4x4.Scale(new Vector3(scale.x, scale.y, 1));
currentMatrix = scaleMatrix * currentMatrix;
}
// 绘制着陆器主体
void DrawLanderBody()
{
// 示例:使用当前矩阵变换顶点并绘制
Vector2[] bodyVertices = new Vector2[]
{
new Vector2(-1, 0.5f),
new Vector2(1, 0.5f),
new Vector2(1, -0.5f),
new Vector2(-1, -0.5f),
new Vector2(-1, 0.5f)
};
DrawLineStrip(bodyVertices);
}
// 绘制着陆腿
void DrawLanderLeg()
{
Vector2[] legVertices = new Vector2[]
{
new Vector2(0, 0),
new Vector2(0, -1)
};
DrawLineStrip(legVertices);
}
// 绘制引擎
void DrawEngine()
{
Vector2[] engineVertices = new Vector2[]
{
new Vector2(-0.2f, 0),
new Vector2(0, -0.5f),
new Vector2(0.2f, 0)
};
DrawLineStrip(engineVertices);
}
// 绘制线条
void DrawLineStrip(Vector2[] vertices)
{
// 在实际应用中,这里会使用当前矩阵变换顶点并调用图形API绘制
// 这里只是示例逻辑
Vector3[] transformedVertices = new Vector3[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
Vector3 vertex = new Vector3(vertices[i].x, vertices[i].y, 0);
transformedVertices[i] = currentMatrix.MultiplyPoint(vertex);
}
// 这里可以调用实际的绘制函数,例如使用LineRenderer或GL
Debug.Log("Drawing line strip with " + transformedVertices.Length + " vertices");
}
}
矩阵栈允许我们管理复杂的层次化变换,非常适合绘制由多个部件组成的复合对象,如月球着陆器。通过压入和弹出矩阵,我们可以轻松地在不同的坐标系统之间切换。
6.2 矢量
矢量是游戏物理中的基础概念,表示具有大小和方向的量。在登月模拟中,我们需要处理各种矢量,如位置、速度、加速度和力。
矢量基础概念
矢量可以表示为具有方向和大小的箭头。在2D空间中,矢量通常用两个分量(x和y)表示。Unity提供了Vector2和Vector3类来处理二维和三维矢量。
csharp
using UnityEngine;
/// <summary>
/// 矢量演示
/// </summary>
public class VectorDemo : MonoBehaviour
{
[Header("矢量可视化")]
public bool showVectors = true;
public float vectorScale = 1.0f;
public Color positionColor = Color.white;
public Color velocityColor = Color.green;
public Color accelerationColor = Color.red;
[Header("动态属性")]
public Vector2 position;
public Vector2 velocity;
public Vector2 acceleration;
// 矢量可视化器
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
// 更新物体状态(简单的物理模拟)
velocity += acceleration * Time.deltaTime;
position += velocity * Time.deltaTime;
// 更新可视化
if (showVectors)
{
visualizer.ClearVectors();
visualizer.DrawVector(position, Vector2.zero, positionColor * 0.5f);
visualizer.DrawVector(position, velocity * vectorScale, velocityColor);
visualizer.DrawVector(position, acceleration * vectorScale * 0.5f, accelerationColor);
}
// 更新游戏对象位置(可选)
transform.position = new Vector3(position.x, position.y, 0);
}
// 设置初始条件
public void SetInitialConditions(Vector2 pos, Vector2 vel, Vector2 acc)
{
position = pos;
velocity = vel;
acceleration = acc;
}
// 应用力(基于F=ma,假设质量为1)
public void ApplyForce(Vector2 force)
{
acceleration += force;
}
// 重置加速度
public void ResetAcceleration()
{
acceleration = Vector2.zero;
}
}
/// <summary>
/// 矢量可视化器
/// </summary>
public class VectorVisualizer : MonoBehaviour
{
private List<LineRenderer> lineRenderers = new List<LineRenderer>();
private int currentLineIndex = 0;
// 绘制矢量
public void DrawVector(Vector2 origin, Vector2 vector, Color color)
{
// 确保有足够的LineRenderer
if (currentLineIndex >= lineRenderers.Count)
{
CreateNewLineRenderer();
}
// 获取当前LineRenderer
LineRenderer lineRenderer = lineRenderers[currentLineIndex++];
lineRenderer.enabled = true;
// 设置颜色
lineRenderer.startColor = color;
lineRenderer.endColor = color;
// 设置点
lineRenderer.positionCount = 2;
lineRenderer.SetPosition(0, new Vector3(origin.x, origin.y, 0));
lineRenderer.SetPosition(1, new Vector3(origin.x + vector.x, origin.y + vector.y, 0));
// 可选:绘制箭头
DrawArrowhead(origin + vector, vector.normalized, color);
}
// 绘制箭头
private void DrawArrowhead(Vector2 position, Vector2 direction, Color color)
{
float arrowSize = 0.2f;
Vector2 right = new Vector2(-direction.y, direction.x); // 垂直于方向的向量
Vector2 arrowLeft = position - direction * arrowSize + right * arrowSize * 0.5f;
Vector2 arrowRight = position - direction * arrowSize - right * arrowSize * 0.5f;
// 确保有足够的LineRenderer
if (currentLineIndex >= lineRenderers.Count)
{
CreateNewLineRenderer();
}
// 获取当前LineRenderer
LineRenderer lineRenderer = lineRenderers[currentLineIndex++];
lineRenderer.enabled = true;
// 设置颜色
lineRenderer.startColor = color;
lineRenderer.endColor = color;
// 设置点
lineRenderer.positionCount = 3;
lineRenderer.SetPosition(0, new Vector3(arrowLeft.x, arrowLeft.y, 0));
lineRenderer.SetPosition(1, new Vector3(position.x, position.y, 0));
lineRenderer.SetPosition(2, new Vector3(arrowRight.x, arrowRight.y, 0));
}
// 创建新的LineRenderer
private void CreateNewLineRenderer()
{
GameObject lineObj = new GameObject("VectorLine_" + lineRenderers.Count);
lineObj.transform.SetParent(transform);
LineRenderer lineRenderer = lineObj.AddComponent<LineRenderer>();
lineRenderer.startWidth = 0.05f;
lineRenderer.endWidth = 0.05f;
lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
lineRenderers.Add(lineRenderer);
}
// 清除所有矢量
public void ClearVectors()
{
currentLineIndex = 0;
// 禁用所有LineRenderer
foreach (LineRenderer renderer in lineRenderers)
{
renderer.enabled = false;
}
}
}
这个示例展示了如何在Unity中使用和可视化矢量。VectorDemo类模拟了一个简单的物理系统,其中物体具有位置、速度和加速度。VectorVisualizer类用于绘制矢量箭头,帮助我们可视化这些物理量。
6.2.1 矢量加、减法
矢量的加法和减法是矢量运算的基础。在Unity中,我们可以直接使用+和-运算符来进行矢量运算。
csharp
using UnityEngine;
/// <summary>
/// 矢量运算演示
/// </summary>
public class VectorOperationsDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 vectorA = new Vector2(1, 2);
public Vector2 vectorB = new Vector2(3, 1);
[Header("可视化设置")]
public bool showVectors = true;
public float vectorScale = 1.0f;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
if (showVectors)
{
visualizer.ClearVectors();
// 绘制原始矢量
visualizer.DrawVector(Vector2.zero, vectorA, Color.red);
visualizer.DrawVector(Vector2.zero, vectorB, Color.blue);
// 绘制矢量加法
Vector2 vectorSum = vectorA + vectorB;
visualizer.DrawVector(Vector2.zero, vectorSum, Color.green);
// 可视化平行四边形法则
visualizer.DrawVector(vectorA, vectorB, Color.blue);
visualizer.DrawVector(vectorB, vectorA, Color.red);
// 绘制矢量减法
Vector2 vectorDiff = vectorA - vectorB;
visualizer.DrawVector(Vector2.zero, vectorDiff, Color.yellow);
// 可视化减法(B到A的向量)
visualizer.DrawVector(vectorB, vectorDiff, Color.yellow);
}
// 在控制台输出矢量运算结果
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Vector A: " + vectorA);
Debug.Log("Vector B: " + vectorB);
Debug.Log("A + B: " + (vectorA + vectorB));
Debug.Log("A - B: " + (vectorA - vectorB));
Debug.Log("Length of A: " + vectorA.magnitude);
Debug.Log("Normalized A: " + vectorA.normalized);
}
}
// 矢量加法示例函数
public Vector2 AddVectors(Vector2 v1, Vector2 v2)
{
return new Vector2(v1.x + v2.x, v1.y + v2.y);
}
// 矢量减法示例函数
public Vector2 SubtractVectors(Vector2 v1, Vector2 v2)
{
return new Vector2(v1.x - v2.x, v1.y - v2.y);
}
}
这个示例演示了矢量的加法和减法,以及它们的几何解释。矢量加法遵循平行四边形法则,而矢量减法可以被解释为从一个点到另一个点的向量。
6.2.2 计算矢量大小
矢量的大小(或长度、模)是矢量的一个重要属性。在Unity中,我们可以使用magnitude属性或Vector2.Distance方法来计算矢量的大小。
csharp
using UnityEngine;
/// <summary>
/// 矢量大小计算演示
/// </summary>
public class VectorMagnitudeDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 vector = new Vector2(3, 4);
[Header("可视化设置")]
public bool showVector = true;
public bool showComponents = true;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
if (showVector)
{
visualizer.ClearVectors();
// 绘制矢量
visualizer.DrawVector(Vector2.zero, vector, Color.blue);
// 绘制矢量的分量
if (showComponents)
{
visualizer.DrawVector(Vector2.zero, new Vector2(vector.x, 0), Color.red);
visualizer.DrawVector(new Vector2(vector.x, 0), new Vector2(0, vector.y), Color.green);
}
// 显示矢量的大小
float magnitude = vector.magnitude;
Debug.DrawLine(Vector2.zero, new Vector2(magnitude, 0), Color.yellow);
}
}
// 使用勾股定理计算2D矢量的大小
public float CalculateMagnitude(Vector2 v)
{
return Mathf.Sqrt(v.x * v.x + v.y * v.y);
}
// 使用Unity内置函数计算矢量大小
public float GetMagnitude(Vector2 v)
{
// 方法1:使用magnitude属性
float magnitude1 = v.magnitude;
// 方法2:使用Vector2.Distance
float magnitude2 = Vector2.Distance(Vector2.zero, v);
// 两种方法结果相同
return magnitude1;
}
// 获取矢量的平方大小(避免开平方操作,提高性能)
public float GetSquareMagnitude(Vector2 v)
{
return v.sqrMagnitude; // 等价于 v.x * v.x + v.y * v.y
}
// 检查矢量是否在某个范围内(使用平方大小比较,更高效)
public bool IsVectorInRange(Vector2 v, float maxDistance)
{
return v.sqrMagnitude <= maxDistance * maxDistance;
}
}
这个示例演示了如何计算矢量的大小,以及如何使用平方大小进行高效的距离比较。在游戏开发中,我们经常使用平方大小来避免昂贵的平方根运算。
6.2.3 矢量的数乘
矢量与标量的乘法(数乘)是矢量运算中的另一个基本操作。它改变矢量的大小,但保持其方向(如果标量为正)。
csharp
using UnityEngine;
/// <summary>
/// 矢量数乘演示
/// </summary>
public class VectorScalarMultiplicationDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 baseVector = new Vector2(1, 1);
[Header("标量输入")]
[Range(-2.0f, 2.0f)]
public float scalar = 1.0f;
[Header("可视化设置")]
public bool showVectors = true;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
if (showVectors)
{
visualizer.ClearVectors();
// 绘制基础矢量
visualizer.DrawVector(Vector2.zero, baseVector, Color.blue);
// 绘制数乘结果
Vector2 scaledVector = baseVector * scalar;
Color resultColor = scalar >= 0 ? Color.green : Color.red;
visualizer.DrawVector(Vector2.zero, scaledVector, resultColor);
// 显示单位向量
Vector2 unitVector = baseVector.normalized;
visualizer.DrawVector(Vector2.zero, unitVector, Color.yellow);
}
}
// 矢量数乘
public Vector2 ScalarMultiply(Vector2 v, float s)
{
return new Vector2(v.x * s, v.y * s);
}
// 获取单位向量
public Vector2 Normalize(Vector2 v)
{
float magnitude = v.magnitude;
if (magnitude > 0)
return v / magnitude;
else
return Vector2.zero;
}
// 使用Unity内置函数获取单位向量
public Vector2 GetUnitVector(Vector2 v)
{
return v.normalized;
}
}
矢量数乘是实现物理模拟中缩放力和速度的基础操作。例如,在登月模拟中,我们可以通过标量乘法来实现引擎推力。
6.2.4 矢量的规范化
矢量规范化是指将矢量转换为具有相同方向但大小为1的单位向量。这在计算方向时非常有用。
csharp
using UnityEngine;
/// <summary>
/// 矢量规范化演示
/// </summary>
public class VectorNormalizationDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 inputVector = new Vector2(3, 4);
[Header("可视化设置")]
public bool showVectors = true;
public bool showUnitCircle = true;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
if (showVectors)
{
visualizer.ClearVectors();
// 绘制原始矢量
visualizer.DrawVector(Vector2.zero, inputVector, Color.blue);
// 绘制单位向量
Vector2 normalizedVector = inputVector.normalized;
visualizer.DrawVector(Vector2.zero, normalizedVector, Color.green);
// 绘制单位圆
if (showUnitCircle)
{
DrawUnitCircle();
}
}
}
// 手动实现矢量规范化
public Vector2 NormalizeVector(Vector2 v)
{
float magnitude = Mathf.Sqrt(v.x * v.x + v.y * v.y);
if (magnitude > 0)
return new Vector2(v.x / magnitude, v.y / magnitude);
else
return Vector2.zero;
}
// 绘制单位圆
void DrawUnitCircle()
{
int segments = 36;
float angleStep = 360.0f / segments;
for (int i = 0; i < segments; i++)
{
float angle1 = angleStep * i * Mathf.Deg2Rad;
float angle2 = angleStep * (i + 1) * Mathf.Deg2Rad;
Vector2 point1 = new Vector2(Mathf.Cos(angle1), Mathf.Sin(angle1));
Vector2 point2 = new Vector2(Mathf.Cos(angle2), Mathf.Sin(angle2));
Debug.DrawLine(point1, point2, Color.yellow);
}
}
// 计算两个向量之间的夹角(返回弧度)
public float AngleBetweenVectors(Vector2 v1, Vector2 v2)
{
// 确保向量已规范化
v1.Normalize();
v2.Normalize();
// 使用点积计算夹角余弦值
float cosAngle = Vector2.Dot(v1, v2);
// 使用反余弦函数获取角度(弧度)
return Mathf.Acos(Mathf.Clamp(cosAngle, -1.0f, 1.0f));
}
// 使用Unity内置函数计算角度(返回角度)
public float GetAngle(Vector2 v1, Vector2 v2)
{
return Vector2.Angle(v1, v2);
}
}
矢量规范化是处理方向问题的重要工具。在登月模拟中,我们可以使用规范化向量来表示着陆器的朝向或引擎推力的方向。
6.2.5 矢量分解
矢量分解是将矢量分解为沿着特定方向的分量的过程。这在物理模拟中非常有用,例如将力分解为水平和垂直分量。
csharp
using UnityEngine;
/// <summary>
/// 矢量分解演示
/// </summary>
public class VectorDecompositionDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 inputVector = new Vector2(3, 4);
[Header("分解方向")]
public Vector2 direction1 = new Vector2(1, 0);
public Vector2 direction2 = new Vector2(0, 1);
[Header("可视化设置")]
public bool showVectors = true;
public bool autoOrthogonalize = true;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
// 确保方向向量是单位向量
direction1.Normalize();
direction2.Normalize();
}
void Update()
{
// 如果启用自动正交化,确保direction2垂直于direction1
if (autoOrthogonalize)
{
direction1.Normalize();
direction2 = new Vector2(-direction1.y, direction1.x); // 逆时针旋转90度
}
if (showVectors)
{
visualizer.ClearVectors();
// 绘制原始矢量
visualizer.DrawVector(Vector2.zero, inputVector, Color.blue);
// 绘制基底向量
visualizer.DrawVector(Vector2.zero, direction1, Color.green);
visualizer.DrawVector(Vector2.zero, direction2, Color.red);
// 计算分解的分量
Vector2 component1 = ProjectVector(inputVector, direction1);
Vector2 component2 = ProjectVector(inputVector, direction2);
// 绘制分解的分量
visualizer.DrawVector(Vector2.zero, component1, new Color(0, 1, 0, 0.5f));
visualizer.DrawVector(Vector2.zero, component2, new Color(1, 0, 0, 0.5f));
// 绘制从component1到inputVector的向量(应该等于component2)
visualizer.DrawVector(component1, component2, new Color(1, 0, 0, 0.5f));
}
}
// 将向量投影到指定方向上
public Vector2 ProjectVector(Vector2 vector, Vector2 direction)
{
// 确保方向是单位向量
direction.Normalize();
// 计算投影长度(点积)
float projectionLength = Vector2.Dot(vector, direction);
// 计算投影向量
return direction * projectionLength;
}
// 将向量分解为平行和垂直于指定方向的分量
public void DecomposeVector(Vector2 vector, Vector2 direction, out Vector2 parallel, out Vector2 perpendicular)
{
// 计算平行分量
parallel = ProjectVector(vector, direction);
// 计算垂直分量
perpendicular = vector - parallel;
}
// 将向量分解为任意两个非平行方向的分量
public bool DecomposeVectorGeneral(Vector2 vector, Vector2 dir1, Vector2 dir2, out float component1, out float component2)
{
// 确保方向向量非零
if (dir1.sqrMagnitude < 0.0001f || dir2.sqrMagnitude < 0.0001f)
{
component1 = 0;
component2 = 0;
return false;
}
// 标准化方向向量
dir1.Normalize();
dir2.Normalize();
// 检查方向是否接近平行
float dot = Vector2.Dot(dir1, dir2);
if (Mathf.Abs(dot) > 0.9999f)
{
component1 = 0;
component2 = 0;
return false;
}
// 解线性方程组:vector = component1 * dir1 + component2 * dir2
// 即:vector.x = component1 * dir1.x + component2 * dir2.x
// vector.y = component1 * dir1.y + component2 * dir2.y
float determinant = dir1.x * dir2.y - dir1.y * dir2.x;
component1 = (vector.x * dir2.y - vector.y * dir2.x) / determinant;
component2 = (dir1.x * vector.y - dir1.y * vector.x) / determinant;
return true;
}
}
矢量分解在物理模拟中非常重要,例如在登月模拟中,我们需要将重力和引擎推力分解为水平和垂直分量,以便正确计算着陆器的运动。
6.2.6 矢量的点积
点积是两个矢量的重要运算,它返回一个标量值,表示两个矢量在方向上的相似程度。
csharp
using UnityEngine;
/// <summary>
/// 矢量点积演示
/// </summary>
public class VectorDotProductDemo : MonoBehaviour
{
[Header("矢量输入")]
public Vector2 vectorA = new Vector2(1, 0);
public Vector2 vectorB = new Vector2(0, 1);
[Header("可视化设置")]
public bool showVectors = true;
public bool showProjection = true;
private VectorVisualizer visualizer;
void Start()
{
// 创建矢量可视化器
GameObject visualizerObj = new GameObject("VectorVisualizer");
visualizer = visualizerObj.AddComponent<VectorVisualizer>();
}
void Update()
{
if (showVectors)
{
visualizer.ClearVectors();
// 绘制原始矢量
visualizer.DrawVector(Vector2.zero, vectorA, Color.blue);
visualizer.DrawVector(Vector2.zero, vectorB, Color.red);
// 计算点积
float dotProduct = Vector2.Dot(vectorA, vectorB);
// 显示点积结果
string dotProductText = string.Format("点积: {0:F2}", dotProduct);
if (dotProduct > 0)
dotProductText += " (夹角<90°)";
else if (dotProduct < 0)
dotProductText += " (夹角>90°)";
else
dotProductText += " (夹角=90°)";
Debug.Log(dotProductText);
// 显示向量B在向量A上的投影
if (showProjection)
{
float projectionLength = Vector2.Dot(vectorB, vectorA.normalized);
Vector2 projectionVector = vectorA.normalized * projectionLength;
visualizer.DrawVector(Vector2.zero, projectionVector, new Color(1, 0.5f, 0, 0.7f));
visualizer.DrawVector(projectionVector, vectorB - projectionVector, new Color(0, 1, 0, 0.7f));
}
}
}
// 手动计算点积
public float CalculateDotProduct(Vector2 v1, Vector2 v2)
{
return v1.x * v2.x + v1.y * v2.y;
}
// 使用点积检查向量是否在同一方向
public bool AreVectorsInSameDirection(Vector2 v1, Vector2 v2)
{
return Vector2.Dot(v1, v2) > 0;
}
// 使用点积检查向量是否垂直
public bool AreVectorsPerpendicular(Vector2 v1, Vector2 v2)
{
return Mathf.Abs(Vector2.Dot(v1, v2)) < 0.0001f;
}
// 使用点积计算夹角余弦值
public float CosineOfAngle(Vector2 v1, Vector2 v2)
{
float magnitudeProduct = v1.magnitude * v2.magnitude;
if (magnitudeProduct > 0)
return Vector2.Dot(v1, v2) / magnitudeProduct;
else
return 0;
}
}
点积在游戏物理中有许多应用,如计算投影、判断向量关系、计算角度等。在登月模拟中,我们可以使用点积来检测着陆器是否与月球表面平行,以确保安全着陆。
6.2.7 SVector2D 实用工具类
为了方便在登月模拟中使用矢量,我们可以创建一个实用工具类,封装常用的矢量操作:
csharp
using UnityEngine;
/// <summary>
/// 2D矢量实用工具类
/// </summary>
public static class SVector2D
{
// 创建从点p1到点p2的向量
public static Vector2 FromTo(Vector2 p1, Vector2 p2)
{
return p2 - p1;
}
// 获取矢量的大小
public static float Magnitude(Vector2 v)
{
return v.magnitude;
}
// 获取矢量的平方大小(更高效)
public static float SqrMagnitude(Vector2 v)
{
return v.sqrMagnitude;
}
// 规范化矢量
public static Vector2 Normalize(Vector2 v)
{
float magnitude = v.magnitude;
if (magnitude > 0)
return v / magnitude;
else
return Vector2.zero;
}
// 计算点积
public static float Dot(Vector2 v1, Vector2 v2)
{
return v1.x * v2.x + v1.y * v2.y;
}
// 计算叉积(在2D中返回标量,表示面积的两倍)
public static float Cross(Vector2 v1, Vector2 v2)
{
return v1.x * v2.y - v1.y * v2.x;
}
// 计算两个向量之间的角度(返回角度)
public static float Angle(Vector2 v1, Vector2 v2)
{
return Vector2.Angle(v1, v2);
}
// 计算两个向量之间的有符号角度(返回角度)
public static float SignedAngle(Vector2 v1, Vector2 v2)
{
return Vector2.SignedAngle(v1, v2);
}
// 将向量投影到方向上
public static Vector2 Project(Vector2 vector, Vector2 onNormal)
{
return Vector2.Project(vector, onNormal);
}
// 将向量投影到平面上(在2D中是垂直于法线的线)
public static Vector2 ProjectOnPlane(Vector2 vector, Vector2 planeNormal)
{
return Vector2.ProjectOnPlane(vector, planeNormal);
}
// 返回向量的垂直向量(逆时针旋转90度)
public static Vector2 Perpendicular(Vector2 v)
{
return new Vector2(-v.y, v.x);
}
// 向量插值
public static Vector2 Lerp(Vector2 a, Vector2 b, float t)
{
return Vector2.Lerp(a, b, t);
}
// 向量平滑插值
public static Vector2 SmoothDamp(Vector2 current, Vector2 target, ref Vector2 velocity, float smoothTime, float maxSpeed = Mathf.Infinity)
{
return Vector2.SmoothDamp(current, target, ref velocity, smoothTime, maxSpeed);
}
// 向量旋转
public static Vector2 Rotate(Vector2 v, float degrees)
{
float radians = degrees * Mathf.Deg2Rad;
float sin = Mathf.Sin(radians);
float cos = Mathf.Cos(radians);
return new Vector2(
v.x * cos - v.y * sin,
v.x * sin + v.y * cos
);
}
// 计算点到线段的最近点
public static Vector2 ClosestPointOnLine(Vector2 point, Vector2 lineStart, Vector2 lineEnd)
{
Vector2 line = lineEnd - lineStart;
float len = line.magnitude;
line.Normalize();
Vector2 v = point - lineStart;
float d = Vector2.Dot(v, line);
d = Mathf.Clamp(d, 0f, len);
return lineStart + line * d;
}
// 检查点是否在多边形内部
public static bool IsPointInPolygon(Vector2 point, Vector2[] polygon)
{
bool result = false;
int j = polygon.Length - 1;
for (int i = 0; i < polygon.Length; i++)
{
if (polygon[i].y < point.y && polygon[j].y >= point.y || polygon[j].y < point.y && polygon[i].y >= point.y)
{
if (polygon[i].x + (point.y - polygon[i].y) / (polygon[j].y - polygon[i].y) * (polygon[j].x - polygon[i].x) < point.x)
{
result = !result;
}
}
j = i;
}
return result;
}
}
这个实用工具类封装了许多常用的矢量操作,使我们在登月模拟中处理矢量计算更加方便。它提供了标准Vector2类的替代方法,并添加了一些额外的有用功能。
6.3 相关的物理知识
要创建一个真实的登月模拟,我们需要了解一些基本的物理概念,如时间、长度、质量、力、速度和加速度。
6.3.1 时间
在物理模拟中,时间是一个基本量。在Unity中,我们通常使用Time.deltaTime来获取每帧的时间间隔。
csharp
using UnityEngine;
/// <summary>
/// 时间处理示例
/// </summary>
public class TimeHandlingExample : MonoBehaviour
{
[Header("时间设置")]
public float simulationSpeed = 1.0f;
public bool pauseSimulation = false;
private float simulationTime = 0.0f;
private float realTimeElapsed = 0.0f;
void Update()
{
// 更新真实时间
realTimeElapsed += Time.deltaTime;
// 更新模拟时间(考虑暂停和速度)
if (!pauseSimulation)
{
simulationTime += Time.deltaTime * simulationSpeed;
}
// 显示时间信息
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log($"真实时间: {realTimeElapsed:F2}秒");
Debug.Log($"模拟时间: {simulationTime:F2}秒");
Debug.Log($"时间比率: {simulationSpeed:F2}x");
}
}
// 设置模拟速度
public void SetSimulationSpeed(float speed)
{
simulationSpeed = Mathf.Max(0, speed);
}
// 暂停/继续模拟
public void TogglePause()
{
pauseSimulation = !pauseSimulation;
}
// 获取调整后的增量时间(考虑暂停和速度)
public float GetAdjustedDeltaTime()
{
if (pauseSimulation)
return 0;
else
return Time.deltaTime * simulationSpeed;
}
}
在登月模拟中,我们可能需要调整时间流速或暂停模拟,这个类提供了基本的时间控制功能。
6.3.2 长度
长度是物理模拟中的另一个基本量。在游戏中,我们需要决定一个合适的比例尺来表示实际距离。
csharp
using UnityEngine;
/// <summary>
/// 距离计算与转换
/// </summary>
public class DistanceUtility : MonoBehaviour
{
[Header("比例尺设置")]
public float metersPerUnit = 1000.0f; // 每个Unity单位代表1000米
[Header("测试点")]
public Transform pointA;
public Transform pointB;
void Update()
{
if (pointA != null && pointB != null)
{
// 计算Unity单位的距离
float unityDistance = Vector3.Distance(pointA.position, pointB.position);
// 转换为实际距离(米)
float realDistance = unityDistance * metersPerUnit;
// 显示距离
Debug.DrawLine(pointA.position, pointB.position, Color.yellow);
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log($"Unity距离: {unityDistance:F2}单位");
Debug.Log($"实际距离: {realDistance:F2}米 ({realDistance/1000:F2}公里)");
}
}
}
// 将实际距离(米)转换为Unity单位
public float MetersToUnits(float meters)
{
return meters / metersPerUnit;
}
// 将Unity单位转换为实际距离(米)
public float UnitsToMeters(float units)
{
return units * metersPerUnit;
}
// 将实际速度(米/秒)转换为Unity速度(单位/秒)
public float MetersPerSecondToUnitsPerSecond(float metersPerSecond)
{
return metersPerSecond / metersPerUnit;
}
// 将Unity速度(单位/秒)转换为实际速度(米/秒)
public float UnitsPerSecondToMetersPerSecond(float unitsPerSecond)
{
return unitsPerSecond * metersPerUnit;
}
}
在登月模拟中,我们需要设置一个合适的比例尺,以便在游戏空间中合理表示月球和着陆器的尺寸和距离。
6.3.3 质量
质量是物体的基本属性,影响其在力的作用下的加速度。在Unity中,我们可以使用Rigidbody2D组件的mass属性来设置物体的质量。
csharp
using UnityEngine;
/// <summary>
/// 质量和惯性示例
/// </summary>
public class MassAndInertiaExample : MonoBehaviour
{
[Header("物理属性")]
public float mass = 1.0f;
[Header("测试力")]
public Vector2 appliedForce = new Vector2(10.0f, 0.0f);
public bool applyForce = false;
private Rigidbody2D rb;
private Vector2 initialVelocity;
void Start()
{
// 获取或添加Rigidbody2D组件
rb = GetComponent<Rigidbody2D>();
if (rb == null)
rb = gameObject.AddComponent<Rigidbody2D>();
// 设置质量
rb.mass = mass;
// 记录初始速度
initialVelocity = rb.velocity;
}
void FixedUpdate()
{
// 如果启用了力应用
if (applyForce)
{
// 应用力
rb.AddForce(appliedForce);
// 显示加速度(根据F=ma)
Vector2 acceleration = appliedForce / rb.mass;
Debug.Log($"质量: {rb.mass}kg, 力: {appliedForce}, 加速度: {acceleration}");
// 重置标志,仅应用一次
applyForce = false;
}
}
// 重置物体
public void ResetObject()
{
transform.position = Vector3.zero;
rb.velocity = initialVelocity;
}
// 设置质量并更新刚体
public void SetMass(float newMass)
{
mass = Mathf.Max(0.001f, newMass);
if (rb != null)
rb.mass = mass;
}
// 计算动能
public float CalculateKineticEnergy()
{
return 0.5f * rb.mass * rb.velocity.sqrMagnitude;
}
// 计算动量
public Vector2 CalculateMomentum()
{
return rb.mass * rb.velocity;
}
}
在登月模拟中,着陆器的质量会影响其对引擎推力和重力的响应程度。质量较大的着陆器需要更大的推力才能产生相同的加速度。
6.3.4 力
力是导致物体加速度变化的原因。在Unity中,我们可以使用Rigidbody2D.AddForce方法对物体施加力。
csharp
using UnityEngine;
/// <summary>
/// 力的应用示例
/// </summary>
public class ForceApplicationExample : MonoBehaviour
{
[Header("物理设置")]
public float mass = 1.0f;
[Header("力设置")]
public Vector2 constantForce = new Vector2(0, -9.8f); // 重力
public Vector2 impulseForce = new Vector2(5.0f, 10.0f);
public bool applyImpulse = false;
private Rigidbody2D rb;
private Vector2 netForce;
void Start()
{
// 获取或添加Rigidbody2D组件
rb = GetComponent<Rigidbody2D>();
if (rb == null)
rb = gameObject.AddComponent<Rigidbody2D>();
// 设置质量
rb.mass = mass;
// 禁用Unity的重力,我们将手动应用
rb.gravityScale = 0;
}
void FixedUpdate()
{
// 重置净力
netForce = Vector2.zero;
// 应用恒定力(如重力)
netForce += constantForce;
// 应用其他力(可以添加风力、摩擦力等)
// ...
// 应用冲量力(一次性)
if (applyImpulse)
{
rb.AddForce(impulseForce, ForceMode2D.Impulse);
applyImpulse = false;
}
// 应用净力
rb.AddForce(netForce);
// 计算并显示加速度
Vector2 acceleration = netForce / rb.mass;
Debug.DrawRay(transform.position, acceleration * 0.5f, Color.red);
}
// 计算施加在物体上的净力
public Vector2 CalculateNetForce()
{
// 在实际应用中,这可能包括多种力的组合
return constantForce;
}
// 应用自定义力
public void ApplyForce(Vector2 force, ForceMode2D mode = ForceMode2D.Force)
{
if (rb != null)
{
rb.AddForce(force, mode);
}
}
// 在GUI中显示力和加速度
void OnGUI()
{
if (rb != null)
{
Vector2 acceleration = netForce / rb.mass;
GUI.Label(new Rect(10, 10, 300, 20), $"质量: {rb.mass}kg");
GUI.Label(new Rect(10, 30, 300, 20), $"净力: {netForce}");
GUI.Label(new Rect(10, 50, 300, 20), $"加速度: {acceleration}");
GUI.Label(new Rect(10, 70, 300, 20), $"速度: {rb.velocity}");
}
}
}
在登月模拟中,主要的力包括月球重力和着陆器引擎产生的推力。我们需要正确计算这些力并应用到着陆器上,以实现真实的物理行为。
6.3.5 运动-速度
速度是描述物体运动状态的物理量,表示物体位置变化的快慢和方向。在Unity中,我们可以使用Rigidbody2D.velocity属性来获取和设置物体的速度。
csharp
using UnityEngine;
/// <summary>
/// 速度控制示例
/// </summary>
public class VelocityControlExample : MonoBehaviour
{
[Header("初始设置")]
public Vector2 initialVelocity = new Vector2(5.0f, 0.0f);
[Header("控制设置")]
public float maxSpeed = 10.0f;
public float acceleration = 1.0f;
public float deceleration = 2.0f;
private Rigidbody2D rb;
private Vector2 targetVelocity;
void Start()
{
// 获取或添加Rigidbody2D组件
rb = GetComponent<Rigidbody2D>();
if (rb == null)
rb = gameObject.AddComponent<Rigidbody2D>();
// 设置初始速度
rb.velocity = initialVelocity;
targetVelocity = initialVelocity;
// 禁用重力
rb.gravityScale = 0;
}
void Update()
{
// 处理用户输入
float horizontalInput = Input.GetAxis("Horizontal");
float verticalInput = Input.GetAxis("Vertical");
// 更新目标速度
targetVelocity = new Vector2(
horizontalInput * maxSpeed,
verticalInput * maxSpeed
);
}
void FixedUpdate()
{
// 平滑地改变速度到目标速度
rb.velocity = Vector2.MoveTowards(
rb.velocity,
targetVelocity,
(rb.velocity.magnitude > targetVelocity.magnitude ? deceleration : acceleration) * Time.fixedDeltaTime
);
// 绘制速度向量
Debug.DrawRay(transform.position, rb.velocity, Color.green);
}
// 设置速度
public void SetVelocity(Vector2 velocity)
{
if (rb != null)
{
rb.velocity = velocity;
targetVelocity = velocity;
}
}
// 添加速度
public void AddVelocity(Vector2 velocityChange)
{
if (rb != null)
{
rb.velocity += velocityChange;
targetVelocity = rb.velocity;
}
}
// 限制速度大小
public void ClampVelocityMagnitude(float maxMagnitude)
{
if (rb != null && rb.velocity.magnitude > maxMagnitude)
{
rb.velocity = rb.velocity.normalized * maxMagnitude;
}
}
// 在GUI中显示速度信息
void OnGUI()
{
if (rb != null)
{
GUI.Label(new Rect(10, 10, 300, 20), $"当前速度: {rb.velocity} (大小: {rb.velocity.magnitude:F2})");
GUI.Label(new Rect(10, 30, 300, 20), $"目标速度: {targetVelocity} (大小: {targetVelocity.magnitude:F2})");
// 显示距离和时间估计
float distance = Vector2.Distance(Vector2.zero, transform.position);
float timeElapsed = Time.time;
float averageSpeed = distance / timeElapsed;
GUI.Label(new Rect(10, 50, 300, 20), $"距离: {distance:F2},时间: {timeElapsed:F2}秒");
GUI.Label(new Rect(10, 70, 300, 20), $"平均速度: {averageSpeed:F2}单位/秒");
}
}
}
在登月模拟中,着陆器的速度控制是关键。玩家需要通过控制引擎推力来调整着陆器的速度,实现安全着陆。
6.3.6 运动-加速度
加速度是速度变化的快慢,由施加在物体上的力和物体的质量决定(F=ma)。
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 加速度演示
/// </summary>
public class AccelerationDemo : MonoBehaviour
{
[Header("物理设置")]
public float mass = 1.0f;
[Header("力设置")]
public Vector2 gravity = new Vector2(0, -9.8f);
public Vector2 thrust = new Vector2(0, 15.0f);
public bool applyThrust = false;
[Header("历史记录")]
public int historyLength = 100;
public float recordInterval = 0.1f;
private Rigidbody2D rb;
private List<Vector2> positionHistory = new List<Vector2>();
private List<Vector2> velocityHistory = new List<Vector2>();
private List<Vector2> accelerationHistory = new List<Vector2>();
private float recordTimer = 0;
private Vector2 lastVelocity;
void Start()
{
// 获取或添加Rigidbody2D组件
rb = GetComponent<Rigidbody2D>();
if (rb == null)
rb = gameObject.AddComponent<Rigidbody2D>();
// 设置质量
rb.mass = mass;
// 禁用Unity的重力,我们将手动应用
rb.gravityScale = 0;
// 记录初始数据
RecordData();
}
void Update()
{
// 检查输入
applyThrust = Input.GetKey(KeyCode.Space);
// 更新记录计时器
recordTimer += Time.deltaTime;
if (recordTimer >= recordInterval)
{
RecordData();
recordTimer = 0;
}
}
void FixedUpdate()
{
// 计算净力
Vector2 netForce = gravity * mass; // 重力
if (applyThrust)
{
netForce += thrust; // 推力
}
// 应用力
rb.AddForce(netForce);
// 计算实际加速度
Vector2 currentAcceleration = (rb.velocity - lastVelocity) / Time.fixedDeltaTime;
lastVelocity = rb.velocity;
// 绘制力和加速度向量
Debug.DrawRay(transform.position, netForce / 10.0f, Color.red);
Debug.DrawRay(transform.position, rb.velocity / 2.0f, Color.green);
Debug.DrawRay(transform.position, currentAcceleration / 5.0f, Color.blue);
}
// 记录位置、速度和加速度数据
void RecordData()
{
// 记录位置
positionHistory.Add(rb.position);
if (positionHistory.Count > historyLength)
positionHistory.RemoveAt(0);
// 记录速度
velocityHistory.Add(rb.velocity);
if (velocityHistory.Count > historyLength)
velocityHistory.RemoveAt(0);
// 计算加速度(速度变化率)
if (velocityHistory.Count >= 2)
{
Vector2 velocityChange = velocityHistory[velocityHistory.Count - 1] - velocityHistory[velocityHistory.Count - 2];
Vector2 acceleration = velocityChange / recordInterval;
accelerationHistory.Add(acceleration);
if (accelerationHistory.Count > historyLength)
accelerationHistory.RemoveAt(0);
}
}
// 绘制历史轨迹
void OnDrawGizmos()
{
if (!Application.isPlaying)
return;
// 绘制位置历史(轨迹)
Gizmos.color = Color.white;
for (int i = 1; i < positionHistory.Count; i++)
{
Gizmos.DrawLine(positionHistory[i - 1], positionHistory[i]);
}
// 选择性地绘制速度和加速度历史
if (Input.GetKey(KeyCode.V))
{
Gizmos.color = Color.green;
for (int i = 0; i < velocityHistory.Count; i++)
{
Vector2 position = positionHistory[Mathf.Min(i, positionHistory.Count - 1)];
Gizmos.DrawRay(position, velocityHistory[i] * 0.1f);
}
}
if (Input.GetKey(KeyCode.A))
{
Gizmos.color = Color.blue;
for (int i = 0; i < accelerationHistory.Count; i++)
{
Vector2 position = positionHistory[Mathf.Min(i + 1, positionHistory.Count - 1)];
Gizmos.DrawRay(position, accelerationHistory[i] * 0.01f);
}
}
}
// 在GUI中显示当前物理状态
void OnGUI()
{
if (rb != null)
{
GUI.Label(new Rect(10, 10, 300, 20), $"位置: {rb.position}");
GUI.Label(new Rect(10, 30, 300, 20), $"速度: {rb.velocity} (大小: {rb.velocity.magnitude:F2})");
if (accelerationHistory.Count > 0)
{
Vector2 currentAcceleration = accelerationHistory[accelerationHistory.Count - 1];
GUI.Label(new Rect(10, 50, 300, 20), $"加速度: {currentAcceleration} (大小: {currentAcceleration.magnitude:F2})");
}
GUI.Label(new Rect(10, 70, 300, 20), $"推力状态: {(applyThrust ? "开启" : "关闭")}");
}
}
}
这个示例演示了加速度的概念和计算方法。它跟踪物体的位置、速度和加速度历史,并以可视化方式展示这些物理量。在登月模拟中,理解加速度对于控制着陆器至关重要。
6.3.7 力、质量、加速度三者的关系
牛顿第二定律(F=ma)描述了力、质量和加速度之间的关系。在游戏物理中,这是一个核心原理。
csharp
using UnityEngine;
/// <summary>
/// 牛顿第二定律演示
/// </summary>
public class NewtonsSecondLawDemo : MonoBehaviour
{
[Header("物理对象")]
public GameObject[] objects;
public float[] masses;
[Header("力设置")]
public Vector2 appliedForce = new Vector2(10.0f, 0.0f);
public bool applyForce = false;
void Start()
{
// 初始化物体
if (objects != null && masses != null && objects.Length == masses.Length)
{
for (int i = 0; i < objects.Length; i++)
{
if (objects[i] != null)
{
// 设置初始位置
objects[i].transform.position = new Vector3(i * 2.0f, 0, 0);
// 添加或获取Rigidbody2D
Rigidbody2D rb = objects[i].GetComponent<Rigidbody2D>();
if (rb == null)
rb = objects[i].AddComponent<Rigidbody2D>();
// 设置质量
rb.mass = masses[i];
// 禁用重力
rb.gravityScale = 0;
}
}
}
}
void Update()
{
if (applyForce)
{
ApplyForceToAllObjects();
applyForce = false;
}
}
// 对所有物体施加相同的力
void ApplyForceToAllObjects()
{
if (objects == null)
return;
for (int i = 0; i < objects.Length; i++)
{
if (objects[i] != null)
{
Rigidbody2D rb = objects[i].GetComponent<Rigidbody2D>();
if (rb != null)
{
// 应用力
rb.AddForce(appliedForce);
// 计算并显示加速度
Vector2 acceleration = appliedForce / rb.mass;
Debug.Log($"物体 {i}: 质量 = {rb.mass}kg, 力 = {appliedForce}, 加速度 = {acceleration}");
// 绘制加速度向量
Debug.DrawRay(objects[i].transform.position, acceleration, Color.red, 1.0f);
}
}
}
}
// 重置所有物体
public void ResetObjects()
{
if (objects == null)
return;
for (int i = 0; i < objects.Length; i++)
{
if (objects[i] != null)
{
// 重置位置
objects[i].transform.position = new Vector3(i * 2.0f, 0, 0);
// 重置速度
Rigidbody2D rb = objects[i].GetComponent<Rigidbody2D>();
if (rb != null)
{
rb.velocity = Vector2.zero;
}
}
}
}
// 在GUI中显示关系
void OnGUI()
{
GUI.Label(new Rect(10, 10, 300, 20), "牛顿第二定律: F = ma");
GUI.Label(new Rect(10, 30, 300, 20), $"应用的力: {appliedForce}");
if (objects != null)
{
for (int i = 0; i < objects.Length; i++)
{
if (objects[i] != null)
{
Rigidbody2D rb = objects[i].GetComponent<Rigidbody2D>();
if (rb != null)
{
float y = 50 + i * 40;
GUI.Label(new Rect(10, y, 300, 20), $"物体 {i}: 质量 = {rb.mass}kg");
GUI.Label(new Rect(10, y + 20, 300, 20), $"加速度 = {appliedForce / rb.mass}, 速度 = {rb.velocity}");
}
}
}
}
if (GUI.Button(new Rect(10, Screen.height - 60, 100, 50), "应用力"))
{
applyForce = true;
}
if (GUI.Button(new Rect(120, Screen.height - 60, 100, 50), "重置"))
{
ResetObjects();
}
}
}
这个示例展示了牛顿第二定律的应用。它创建了多个具有不同质量的物体,并对它们施加相同的力,从而展示了质量与加速度之间的反比关系。在登月模拟中,着陆器的质量会随着燃料消耗而减少,这会影响其对相同推力的响应。
6.3.8 引力
引力是一种特殊的力,它作用于所有有质量的物体之间。在登月模拟中,月球引力是一个关键因素。
csharp
using UnityEngine;
/// <summary>
/// 引力模拟
/// </summary>
public class GravitySimulation : MonoBehaviour
{
[Header("引力源")]
public Transform gravitationalBody;
public float gravitationalMass = 7.34e22f; // 月球质量(kg)
[Header("受引力物体")]
public float objectMass = 15000.0f; // 着陆器质量(kg)
[Header("物理常数")]
public float gravitationalConstant = 6.674e-11f; // 万有引力常数
[Header("模拟设置")]
public float scaleFactor = 1.0e9f; // 缩放因子,使引力在游戏中可感知
private Rigidbody2D rb;
void Start()
{
// 获取或添加Rigidbody2D
rb = GetComponent<Rigidbody2D>();
if (rb == null)
rb = gameObject.AddComponent<Rigidbody2D>();
// 设置质量
rb.mass = objectMass;
// 禁用Unity的重力,我们将手动计算引力
rb.gravityScale = 0;
}
void FixedUpdate()
{
if (gravitationalBody != null)
{
// 计算引力
Vector2 force = CalculateGravitationalForce();
// 应用引力
rb.AddForce(force);
// 绘制引力向量
Debug.DrawRay(transform.position, force / rb.mass, Color.red);
}
}
// 计算引力
Vector2 CalculateGravitationalForce()
{
// 计算方向和距离
Vector2 direction = (Vector2)(gravitationalBody.position - transform.position);
float distance = direction.magnitude;
// 避免除以零
if (distance < 0.1f)
distance = 0.1f;
// 计算引力大小(牛顿万有引力定律)
float forceMagnitude = gravitationalConstant * gravitationalMass * rb.mass / (distance * distance);
// 应用缩放因子,使引力在游戏中可感知
forceMagnitude *= scaleFactor;
// 计算引力向量
return direction.normalized * forceMagnitude;
}
// 设置初始速度以实现轨道运动
public void SetOrbitalVelocity(float altitude)
{
if (gravitationalBody != null && rb != null)
{
// 计算轨道速度(圆轨道)
float distance = altitude + 1738000.0f; // 月球半径约1738km
float orbitalSpeed = Mathf.Sqrt(gravitationalConstant * gravitationalMass / distance);
// 应用缩放因子
orbitalSpeed *= Mathf.Sqrt(scaleFactor);
// 设置垂直于引力方向的速度
Vector2 gravityDirection = ((Vector2)gravitationalBody.position - (Vector2)transform.position).normalized;
Vector2 orbitalDirection = new Vector2(-gravityDirection.y, gravityDirection.x);
rb.velocity = orbitalDirection * orbitalSpeed;
Debug.Log($"设置轨道速度: {orbitalSpeed} m/s");
}
}
// 计算逃逸速度
public float CalculateEscapeVelocity()
{
if (gravitationalBody != null)
{
Vector2 direction = (Vector2)(gravitationalBody.position - transform.position);
float distance = direction.magnitude;
// 逃逸速度公式: v = sqrt(2*G*M/r)
float escapeSpeed = Mathf.Sqrt(2 * gravitationalConstant * gravitationalMass / distance);
// 应用缩放因子
escapeSpeed *= Mathf.Sqrt(scaleFactor);
return escapeSpeed;
}
return 0;
}
// 在GUI中显示引力信息
void OnGUI()
{
if (gravitationalBody != null && rb != null)
{
Vector2 direction = (Vector2)(gravitationalBody.position - transform.position);
float distance = direction.magnitude;
Vector2 force = CalculateGravitationalForce();
Vector2 acceleration = force / rb.mass;
GUI.Label(new Rect(10, 10, 300, 20), $"距离: {distance:F2}单位");
GUI.Label(new Rect(10, 30, 300, 20), $"引力: {force.magnitude:F2}");
GUI.Label(new Rect(10, 50, 300, 20), $"加速度: {acceleration.magnitude:F2}");
GUI.Label(new Rect(10, 70, 300, 20), $"速度: {rb.velocity.magnitude:F2}");
GUI.Label(new Rect(10, 90, 300, 20), $"逃逸速度: {CalculateEscapeVelocity():F2}");
// 显示轨道参数
float kineticEnergy = 0.5f * rb.mass * rb.velocity.sqrMagnitude;
float potentialEnergy = -gravitationalConstant * gravitationalMass * rb.mass / distance * scaleFactor;
float totalEnergy = kineticEnergy + potentialEnergy;
GUI.Label(new Rect(10, 110, 300, 20), $"动能: {kineticEnergy:E2}");
GUI.Label(new Rect(10, 130, 300, 20), $"势能: {potentialEnergy:E2}");
GUI.Label(new Rect(10, 150, 300, 20), $"总能量: {totalEnergy:E2}");
}
}
}
这个示例模拟了万有引力,可以用于创建月球引力场。它计算引力大小和方向,并应用到物体上。在登月模拟中,月球引力是着陆器必须克服的主要力。
6.4 人工控制的登月工程
现在,让我们开始构建登月模拟游戏的核心部分。首先,我们需要创建一个用户可控制的着陆器系统。
6.4.1 Ccontroller 类的定义
控制器类负责处理用户输入并控制着陆器的行为。
csharp
using UnityEngine;
/// <summary>
/// 着陆器控制器基类
/// </summary>
public abstract class LanderController : MonoBehaviour
{
[Header("着陆器引用")]
public Rigidbody2D landerRigidbody;
public Transform landerTransform;
[Header("控制参数")]
public float maxThrust = 20.0f;
public float rotationSpeed = 45.0f;
public float fuelConsumptionRate = 1.0f;
[Header("状态")]
public float currentFuel = 100.0f;
public float currentThrust = 0.0f;
public float currentRotation = 0.0f;
protected bool isEngineActive = false;
// 初始化
protected virtual void Start()
{
if (landerRigidbody == null)
landerRigidbody = GetComponent<Rigidbody2D>();
if (landerTransform == null)
landerTransform = transform;
}
// 应用引擎推力
protected void ApplyThrust(float thrustAmount)
{
// 限制推力范围
thrustAmount = Mathf.Clamp(thrustAmount, 0, maxThrust);
// 检查燃料
if (currentFuel <= 0)
{
currentThrust = 0;
isEngineActive = false;
return;
}
// 设置当前推力
currentThrust = thrustAmount;
isEngineActive = currentThrust > 0;
// 消耗燃料
if (isEngineActive)
{
float fuelUsed = currentThrust * fuelConsumptionRate * Time.fixedDeltaTime;
currentFuel = Mathf.Max(0, currentFuel - fuelUsed);
}
// 计算推力方向(基于当前旋转)
Vector2 thrustDirection = CalculateThrustDirection();
// 应用推力
if (landerRigidbody != null && isEngineActive)
{
landerRigidbody.AddForce(thrustDirection * currentThrust);
}
}
// 旋转着陆器
protected void RotateLander(float rotationAmount)
{
currentRotation += rotationAmount * rotationSpeed * Time.deltaTime;
// 将旋转应用到变换
if (landerTransform != null)
{
landerTransform.rotation = Quaternion.Euler(0, 0, currentRotation);
}
}
// 计算推力方向
protected Vector2 CalculateThrustDirection()
{
// 转换旋转角度为弧度
float radians = currentRotation * Mathf.Deg2Rad;
// 计算方向向量(假设0度是向上,顺时针旋转)
return new Vector2(Mathf.Sin(radians), Mathf.Cos(radians));
}
// 重置控制器
public virtual void Reset()
{
currentFuel = 100.0f;
currentThrust = 0.0f;
currentRotation = 0.0f;
isEngineActive = false;
if (landerTransform != null)
{
landerTransform.rotation = Quaternion.identity;
}
if (landerRigidbody != null)
{
landerRigidbody.velocity = Vector2.zero;
landerRigidbody.angularVelocity = 0;
}
}
// 检查着陆状态
public bool CheckLanding(Collision2D collision)
{
// 检查碰撞速度
float landingSpeed = collision.relativeVelocity.magnitude;
// 检查着陆角度(与地面的夹角)
float landingAngle = Vector2.Angle(Vector2.up, landerTransform.up);
// 判断是否安全着陆
bool isSafeLanding = landingSpeed < 2.0f && landingAngle < 15.0f;
Debug.Log($"着陆速度: {landingSpeed:F2},着陆角度: {landingAngle:F2},安全着陆: {isSafeLanding}");
return isSafeLanding;
}
// 抽象方法,由子类实现具体控制逻辑
public abstract void UpdateControl();
}
这个控制器基类提供了控制着陆器的基本功能,包括应用推力、旋转着陆器和检查着陆状态。子类可以实现具体的控制逻辑。
6.4.2 CLander 类的定义
着陆器类是游戏的核心组件,它代表玩家控制的月球着陆器。
csharp
using UnityEngine;
/// <summary>
/// 月球着陆器类
/// </summary>
public class Lander : MonoBehaviour
{
[Header("组件引用")]
public Rigidbody2D rb;
public ParticleSystem engineParticles;
public Transform centerOfMass;
public LineRenderer trajectoryRenderer;
[Header("着陆器参数")]
public float initialMass = 15000.0f; // kg
public float fuelMass = 10000.0f; // kg
public float maxThrust = 45000.0f; // N
public float fuelConsumptionRate = 5.0f; // kg/s per unit thrust
[Header("状态")]
public float currentFuel;
public float currentThrust;
public bool isEngineActive = false;
public bool hasLanded = false;
public bool hasCrashed = false;
[Header("调试")]
public bool showForces = true;
public bool showTrajectory = true;
// 事件
public delegate void LanderEvent(Lander lander);
public event LanderEvent OnLand;
public event LanderEvent OnCrash;
public event LanderEvent OnFuelEmpty;
private Vector2 thrustDirection;
private Vector2 gravityForce;
private Vector2[] trajectoryPoints;
private int trajectoryLength = 100;
private float trajectoryTimeStep = 0.1f;
void Awake()
{
// 获取组件
if (rb == null)
rb = GetComponent<Rigidbody2D>();
// 初始化状态
currentFuel = fuelMass;
// 初始化轨迹点数组
trajectoryPoints = new Vector2[trajectoryLength];
}
void Start()
{
// 设置质量和质心
UpdateMass();
if (centerOfMass != null)
rb.centerOfMass = centerOfMass.localPosition;
// 禁用Unity重力,我们将手动应用月球重力
rb.gravityScale = 0;
// 初始化轨迹渲染器
if (trajectoryRenderer != null)
{
trajectoryRenderer.positionCount = trajectoryLength;
}
}
void Update()
{
// 更新引擎粒子效果
UpdateEngineParticles();
// 预测轨迹
if (showTrajectory && !hasLanded && !hasCrashed)
PredictTrajectory();
}
void FixedUpdate()
{
// 更新质量(考虑燃料消耗)
UpdateMass();
// 应用引擎推力
if (isEngineActive && currentFuel > 0)
{
ApplyThrust();
}
else if (isEngineActive && currentFuel <= 0)
{
isEngineActive = false;
if (OnFuelEmpty != null)
OnFuelEmpty(this);
}
// 应用月球重力
ApplyGravity();
// 绘制力向量
if (showForces)
{
Debug.DrawRay(rb.position, gravityForce / rb.mass, Color.red);
if (isEngineActive)
Debug.DrawRay(rb.position, thrustDirection * currentThrust / rb.mass, Color.green);
}
}
// 更新质量(考虑燃料消耗)
void UpdateMass()
{
rb.mass = initialMass - fuelMass + currentFuel;
}
// 应用引擎推力
void ApplyThrust()
{
// 消耗燃料
float fuelUsed = currentThrust * fuelConsumptionRate * Time.fixedDeltaTime;
currentFuel = Mathf.Max(0, currentFuel - fuelUsed);
// 应用推力
rb.AddForce(thrustDirection * currentThrust);
}
// 应用月球重力
void ApplyGravity()
{
// 月球重力加速度约为1.625 m/s²
gravityForce = Vector2.down * 1.625f * rb.mass;
rb.AddForce(gravityForce);
}
// 更新引擎粒子效果
void UpdateEngineParticles()
{
if (engineParticles != null)
{
var emission = engineParticles.emission;
if (isEngineActive && currentFuel > 0)
{
if (!engineParticles.isPlaying)
engineParticles.Play();
// 根据推力调整粒子发射率
emission.rateOverTime = currentThrust / maxThrust * 50;
}
else
{
if (engineParticles.isPlaying)
engineParticles.Stop();
}
}
}
// 预测轨迹
void PredictTrajectory()
{
if (trajectoryRenderer == null)
return;
// 使用物理模拟预测轨迹
Vector2 pos = rb.position;
Vector2 vel = rb.velocity;
float mass = rb.mass;
for (int i = 0; i < trajectoryLength; i++)
{
// 应用重力
Vector2 gravity = Vector2.down * 1.625f;
vel += gravity * trajectoryTimeStep;
// 更新位置
pos += vel * trajectoryTimeStep;
// 存储点
trajectoryPoints[i] = pos;
}
// 更新轨迹渲染器
for (int i = 0; i < trajectoryLength; i++)
{
trajectoryRenderer.SetPosition(i, new Vector3(trajectoryPoints[i].x, trajectoryPoints[i].y, 0));
}
}
// 设置引擎推力
public void SetThrust(float thrust, Vector2 direction)
{
currentThrust = Mathf.Clamp(thrust, 0, maxThrust);
thrustDirection = direction.normalized;
isEngineActive = currentThrust > 0;
}
// 碰撞检测
void OnCollisionEnter2D(Collision2D collision)
{
if (hasLanded || hasCrashed)
return;
// 检查是否是月球表面
if (collision.gameObject.CompareTag("MoonSurface"))
{
// 检查着陆速度和角度
float landingSpeed = collision.relativeVelocity.magnitude;
float landingAngle = Vector2.Angle(Vector2.up, transform.up);
// 安全着陆条件
if (landingSpeed < 2.0f && landingAngle < 15.0f)
{
hasLanded = true;
// 停止物理模拟
rb.velocity = Vector2.zero;
rb.angularVelocity = 0;
rb.isKinematic = true;
Debug.Log("安全着陆!");
if (OnLand != null)
OnLand(this);
}
else
{
hasCrashed = true;
Debug.Log($"坠毁!着陆速度: {landingSpeed:F2} m/s, 角度: {landingAngle:F2}°");
if (OnCrash != null)
OnCrash(this);
}
}
}
// 重置着陆器
public void Reset(Vector2 position, Vector2 velocity, float rotation)
{
transform.position = new Vector3(position.x, position.y, 0);
transform.rotation = Quaternion.Euler(0, 0, rotation);
rb.velocity = velocity;
rb.angularVelocity = 0;
rb.isKinematic = false;
currentFuel = fuelMass;
currentThrust = 0;
isEngineActive = false;
hasLanded = false;
hasCrashed = false;
}
}
着陆器类实现了月球着陆器的物理行为,包括推力、重力、燃料消耗和着陆检测。它还提供了轨迹预测和可视化功能,帮助玩家规划着陆路径。
6.4.3 UpdateShip 函数
现在,我们需要实现用户控制着陆器的逻辑。以下是一个人工控制器类的实现:
csharp
using UnityEngine;
/// <summary>
/// 人工控制器类
/// </summary>
public class HumanController : LanderController
{
[Header("输入设置")]
public KeyCode thrustKey = KeyCode.Space;
public KeyCode rotateLeftKey = KeyCode.A;
public KeyCode rotateRightKey = KeyCode.D;
[Header("推力控制")]
public float thrustIncreaseRate = 10.0f;
public float thrustDecreaseRate = 20.0f;
[Header("UI引用")]
public UnityEngine.UI.Slider thrustSlider;
public UnityEngine.UI.Text fuelText;
public UnityEngine.UI.Text velocityText;
public UnityEngine.UI.Text altitudeText;
void Update()
{
// 处理用户输入
HandleInput();
// 更新UI
UpdateUI();
}
void FixedUpdate()
{
// 更新控制
UpdateControl();
}
// 处理用户输入
void HandleInput()
{
// 处理推力输入
if (Input.GetKey(thrustKey) && currentFuel > 0)
{
currentThrust = Mathf.Min(currentThrust + thrustIncreaseRate * Time.deltaTime, maxThrust);
isEngineActive = true;
}
else
{
currentThrust = Mathf.Max(currentThrust - thrustDecreaseRate * Time.deltaTime, 0);
isEngineActive = currentThrust > 0;
}
// 处理旋转输入
float rotationInput = 0;
if (Input.GetKey(rotateLeftKey))
rotationInput -= 1;
if (Input.GetKey(rotateRightKey))
rotationInput += 1;
if (rotationInput != 0)
RotateLander(rotationInput);
}
// 更新控制
public override void UpdateControl()
{
// 应用推力
ApplyThrust(currentThrust);
}
// 更新UI
void UpdateUI()
{
if (thrustSlider != null)
thrustSlider.value = currentThrust / maxThrust;
if (fuelText != null)
fuelText.text = $"燃料: {currentFuel:F1}";
if (velocityText != null && landerRigidbody != null)
velocityText.text = $"速度: {landerRigidbody.velocity.magnitude:F2} m/s";
if (altitudeText != null)
{
// 计算高度(假设y=0是月球表面)
float altitude = transform.position.y;
altitudeText.text = $"高度: {altitude:F1} m";
}
}
// 重写Reset方法
public override void Reset()
{
base.Reset();
// 重置控制相关变量
currentThrust = 0;
isEngineActive = false;
// 更新UI
UpdateUI();
}
// 实现自定义的着陆检测方法
public bool CheckSafeLanding(Collision2D collision)
{
if (landerRigidbody == null)
return false;
// 检查碰撞速度
float landingSpeed = collision.relativeVelocity.magnitude;
// 检查着陆器姿态
float landingAngle = Vector2.Angle(Vector2.up, transform.up);
// 安全着陆条件
bool isSafeLanding = landingSpeed < 2.0f && landingAngle < 15.0f;
Debug.Log($"着陆速度: {landingSpeed:F2} m/s, 姿态角: {landingAngle:F2}°, 安全着陆: {isSafeLanding}");
return isSafeLanding;
}
// 显示调试信息
void OnGUI()
{
if (landerRigidbody != null)
{
GUI.Label(new Rect(10, 10, 300, 20), $"推力: {currentThrust:F1} ({(currentThrust/maxThrust*100):F1}%)");
GUI.Label(new Rect(10, 30, 300, 20), $"燃料: {currentFuel:F1}");
GUI.Label(new Rect(10, 50, 300, 20), $"速度: {landerRigidbody.velocity.magnitude:F2} m/s");
GUI.Label(new Rect(10, 70, 300, 20), $"垂直速度: {-landerRigidbody.velocity.y:F2} m/s");
GUI.Label(new Rect(10, 90, 300, 20), $"水平速度: {landerRigidbody.velocity.x:F2} m/s");
GUI.Label(new Rect(10, 110, 300, 20), $"方向: {current
csharp
GUI.Label(new Rect(10, 110, 300, 20), $"方向: {currentRotation:F1}°");
GUI.Label(new Rect(10, 130, 300, 20), $"高度: {transform.position.y:F1} m");
}
}
}
这个人工控制器类实现了用户通过键盘控制着陆器的功能。它处理推力和旋转输入,并更新UI显示关键信息如燃料、速度和高度。
下面让我们完成游戏管理器类,它将协调整个登月模拟:
csharp
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
/// <summary>
/// 登月模拟游戏管理器
/// </summary>
public class LunarLanderGameManager : MonoBehaviour
{
[Header("场景引用")]
public Lander lander;
public Transform moonSurface;
public Camera mainCamera;
[Header("UI引用")]
public Text statusText;
public Text scoreText;
public Button restartButton;
public GameObject gameOverPanel;
[Header("游戏参数")]
public Vector2 initialPosition = new Vector2(0, 500);
public Vector2 initialVelocity = new Vector2(10, 0);
public float initialRotation = 0;
public float gameTime = 120; // 游戏时间限制(秒)
[Header("得分参数")]
public float fuelBonus = 10; // 剩余燃料奖励系数
public float landingSpeedPenalty = 20; // 着陆速度惩罚系数
private float currentGameTime;
private bool isGameActive = false;
private int currentScore = 0;
private float landingPrecision = 0; // 着陆精度(与目标的距离)
void Start()
{
// 初始化UI
if (gameOverPanel != null)
gameOverPanel.SetActive(false);
if (restartButton != null)
restartButton.onClick.AddListener(RestartGame);
// 注册着陆器事件
if (lander != null)
{
lander.OnLand += OnLanderLanded;
lander.OnCrash += OnLanderCrashed;
lander.OnFuelEmpty += OnLanderFuelEmpty;
}
// 开始游戏
StartGame();
}
void Update()
{
if (!isGameActive)
return;
// 更新游戏时间
currentGameTime -= Time.deltaTime;
// 检查游戏时间是否耗尽
if (currentGameTime <= 0)
{
currentGameTime = 0;
EndGame("时间耗尽!");
}
// 更新UI
UpdateUI();
// 测试用重启键
if (Input.GetKeyDown(KeyCode.R))
{
RestartGame();
}
}
// 开始游戏
public void StartGame()
{
// 重置游戏状态
currentGameTime = gameTime;
isGameActive = true;
currentScore = 0;
// 重置着陆器
if (lander != null)
{
lander.Reset(initialPosition, initialVelocity, initialRotation);
}
// 隐藏游戏结束面板
if (gameOverPanel != null)
gameOverPanel.SetActive(false);
// 更新UI
UpdateUI();
if (statusText != null)
statusText.text = "开始着陆任务!";
}
// 结束游戏
public void EndGame(string reason)
{
isGameActive = false;
// 显示游戏结束理由
if (statusText != null)
statusText.text = reason;
// 显示游戏结束面板
if (gameOverPanel != null)
gameOverPanel.SetActive(true);
// 计算并显示最终得分
CalculateFinalScore();
if (scoreText != null)
scoreText.text = $"得分: {currentScore}";
}
// 重启游戏
public void RestartGame()
{
StartGame();
}
// 更新UI
private void UpdateUI()
{
if (statusText != null && isGameActive)
{
statusText.text = $"时间: {currentGameTime:F1}s";
}
}
// 计算最终得分
private void CalculateFinalScore()
{
if (lander == null)
return;
// 基础分数
currentScore = 1000;
// 剩余燃料奖励
int fuelScore = Mathf.RoundToInt(lander.currentFuel * fuelBonus);
currentScore += fuelScore;
// 着陆速度惩罚
if (!lander.hasCrashed)
{
float landingSpeed = lander.rb.velocity.magnitude;
int speedPenalty = Mathf.RoundToInt(landingSpeed * landingSpeedPenalty);
currentScore -= speedPenalty;
}
// 着陆精度奖励
if (lander.hasLanded)
{
// 找到最近的着陆点
float distanceToTarget = CalculateDistanceToNearestLandingPad();
landingPrecision = Mathf.Max(0, 100 - distanceToTarget);
int precisionScore = Mathf.RoundToInt(landingPrecision * 10);
currentScore += precisionScore;
}
// 坠毁惩罚
if (lander.hasCrashed)
{
currentScore = Mathf.RoundToInt(currentScore * 0.5f);
}
// 确保分数不为负
currentScore = Mathf.Max(0, currentScore);
}
// 计算到最近着陆台的距离
private float CalculateDistanceToNearestLandingPad()
{
// 在实际游戏中,你需要实现这个方法来找到最近的着陆台
// 这里简化为与月球表面中心的距离
if (moonSurface != null && lander != null)
{
return Mathf.Abs(lander.transform.position.x - moonSurface.position.x);
}
return 100; // 默认值
}
// 着陆器事件处理
private void OnLanderLanded(Lander lander)
{
EndGame("成功着陆!");
// 可选:播放着陆成功音效
AudioSource audioSource = GetComponent<AudioSource>();
if (audioSource != null)
{
audioSource.Play();
}
}
private void OnLanderCrashed(Lander lander)
{
EndGame("着陆器坠毁!");
// 可选:播放坠毁音效和特效
StartCoroutine(ShowCrashEffect());
}
private void OnLanderFuelEmpty(Lander lander)
{
// 仅显示警告,不结束游戏
if (statusText != null)
statusText.text = "警告:燃料耗尽!";
}
// 显示坠毁特效
private IEnumerator ShowCrashEffect()
{
if (lander != null)
{
// 禁用着陆器渲染
Renderer[] renderers = lander.GetComponentsInChildren<Renderer>();
foreach (Renderer r in renderers)
{
r.enabled = false;
}
// 创建爆炸效果
GameObject explosion = new GameObject("Explosion");
explosion.transform.position = lander.transform.position;
ParticleSystem particles = explosion.AddComponent<ParticleSystem>();
var main = particles.main;
main.startColor = Color.red;
main.startSize = 1.0f;
main.startSpeed = 5.0f;
main.startLifetime = 1.0f;
main.duration = 0.5f;
var emission = particles.emission;
emission.rateOverTime = 100;
// 播放爆炸粒子
particles.Play();
// 相机震动效果
if (mainCamera != null)
{
Vector3 originalPosition = mainCamera.transform.position;
for (float t = 0; t < 0.5f; t += Time.deltaTime)
{
mainCamera.transform.position = originalPosition + Random.insideUnitSphere * 0.3f;
yield return null;
}
mainCamera.transform.position = originalPosition;
}
// 延迟销毁爆炸效果
Destroy(explosion, 2.0f);
}
}
}
游戏管理器类负责整个游戏的流程控制,包括开始游戏、结束游戏、重启游戏和计分。它还处理着陆器的事件,如着陆成功、坠毁和燃料耗尽。
最后,我们需要实现月球地形生成器:
csharp
using UnityEngine;
/// <summary>
/// 月球地形生成器
/// </summary>
public class MoonTerrainGenerator : MonoBehaviour
{
[Header("地形设置")]
public float terrainWidth = 1000.0f;
public float minHeight = -10.0f;
public float maxHeight = 30.0f;
public int resolution = 100;
public float roughness = 0.5f;
[Header("着陆台设置")]
public int landingPadCount = 3;
public float landingPadWidth = 20.0f;
[Header("组件")]
public LineRenderer terrainRenderer;
public EdgeCollider2D terrainCollider;
private Vector2[] terrainPoints;
private Vector2[] landingPadPositions;
void Start()
{
// 获取或添加组件
if (terrainRenderer == null)
terrainRenderer = gameObject.AddComponent<LineRenderer>();
if (terrainCollider == null)
terrainCollider = gameObject.AddComponent<EdgeCollider2D>();
// 生成地形
GenerateTerrain();
}
// 生成月球地形
void GenerateTerrain()
{
// 初始化地形点数组
terrainPoints = new Vector2[resolution + 1];
// 使用分形噪声生成地形
GenerateFractalTerrain();
// 添加着陆台
AddLandingPads();
// 设置线渲染器
ConfigureLineRenderer();
// 设置碰撞器
ConfigureCollider();
}
// 使用分形算法生成地形
void GenerateFractalTerrain()
{
// 设置起点和终点高度
float startHeight = Random.Range(minHeight / 2, maxHeight / 2);
float endHeight = Random.Range(minHeight / 2, maxHeight / 2);
// 初始化起点和终点
terrainPoints[0] = new Vector2(-terrainWidth / 2, startHeight);
terrainPoints[resolution] = new Vector2(terrainWidth / 2, endHeight);
// 递归地生成中间点
GenerateMidpoints(0, resolution, roughness);
}
// 中点位移算法
void GenerateMidpoints(int startIndex, int endIndex, float currentRoughness)
{
if (endIndex - startIndex <= 1)
return;
int midIndex = (startIndex + endIndex) / 2;
float midX = (terrainPoints[startIndex].x + terrainPoints[endIndex].x) / 2;
float midY = (terrainPoints[startIndex].y + terrainPoints[endIndex].y) / 2;
// 添加随机位移
midY += Random.Range(-currentRoughness, currentRoughness);
// 约束高度
midY = Mathf.Clamp(midY, minHeight, maxHeight);
// 设置中点
terrainPoints[midIndex] = new Vector2(midX, midY);
// 递归生成左右子区间
float newRoughness = currentRoughness * roughness;
GenerateMidpoints(startIndex, midIndex, newRoughness);
GenerateMidpoints(midIndex, endIndex, newRoughness);
}
// 添加着陆台
void AddLandingPads()
{
landingPadPositions = new Vector2[landingPadCount];
// 均匀分布着陆台
float padSpacing = terrainWidth / (landingPadCount + 1);
for (int i = 0; i < landingPadCount; i++)
{
// 计算着陆台中心位置
float padCenterX = -terrainWidth / 2 + padSpacing * (i + 1);
// 找到最接近的地形点索引
int closestIndex = Mathf.RoundToInt((padCenterX + terrainWidth / 2) / terrainWidth * resolution);
// 确保索引在有效范围内
closestIndex = Mathf.Clamp(closestIndex, 1, resolution - 1);
// 计算平坦区域的起始和结束索引
int padStartIndex = Mathf.Max(0, closestIndex - Mathf.RoundToInt(landingPadWidth / 2 / terrainWidth * resolution));
int padEndIndex = Mathf.Min(resolution, closestIndex + Mathf.RoundToInt(landingPadWidth / 2 / terrainWidth * resolution));
// 获取平均高度
float avgHeight = 0;
for (int j = padStartIndex; j <= padEndIndex; j++)
{
avgHeight += terrainPoints[j].y;
}
avgHeight /= (padEndIndex - padStartIndex + 1);
// 设置平坦区域
for (int j = padStartIndex; j <= padEndIndex; j++)
{
terrainPoints[j] = new Vector2(terrainPoints[j].x, avgHeight);
}
// 存储着陆台中心位置
landingPadPositions[i] = new Vector2(padCenterX, avgHeight);
}
}
// 配置线渲染器
void ConfigureLineRenderer()
{
if (terrainRenderer != null)
{
terrainRenderer.positionCount = terrainPoints.Length;
for (int i = 0; i < terrainPoints.Length; i++)
{
terrainRenderer.SetPosition(i, new Vector3(terrainPoints[i].x, terrainPoints[i].y, 0));
}
terrainRenderer.startWidth = 1.0f;
terrainRenderer.endWidth = 1.0f;
terrainRenderer.material = new Material(Shader.Find("Sprites/Default"));
terrainRenderer.startColor = Color.gray;
terrainRenderer.endColor = Color.gray;
}
}
// 配置碰撞器
void ConfigureCollider()
{
if (terrainCollider != null)
{
terrainCollider.points = terrainPoints;
}
}
// 获取最近的着陆台位置
public Vector2 GetNearestLandingPad(Vector2 position)
{
if (landingPadPositions == null || landingPadPositions.Length == 0)
return Vector2.zero;
Vector2 nearest = landingPadPositions[0];
float minDistance = Vector2.Distance(position, nearest);
for (int i = 1; i < landingPadPositions.Length; i++)
{
float distance = Vector2.Distance(position, landingPadPositions[i]);
if (distance < minDistance)
{
minDistance = distance;
nearest = landingPadPositions[i];
}
}
return nearest;
}
// 可视化着陆台
void OnDrawGizmos()
{
if (landingPadPositions != null)
{
Gizmos.color = Color.green;
foreach (Vector2 padPosition in landingPadPositions)
{
Gizmos.DrawSphere(new Vector3(padPosition.x, padPosition.y, 0), 2.0f);
Gizmos.DrawLine(
new Vector3(padPosition.x - landingPadWidth / 2, padPosition.y, 0),
new Vector3(padPosition.x + landingPadWidth / 2, padPosition.y, 0)
);
}
}
}
}
月球地形生成器使用分形算法创建随机的月球表面,并添加平坦的着陆台。这为游戏提供了有趣的地形变化和明确的着陆目标。
6.5 遗传算法控制的登月飞船
现在,让我们通过遗传算法实现一个自动控制系统,让AI来控制着陆器。
6.5.1 为基因组编码
首先,我们需要定义基因组的编码方式,表示AI控制器的决策规则:
csharp
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 登月AI的基因组
/// </summary>
public class LanderGenome
{
// 基因组长度
public const int GENE_LENGTH = 32;
// 基因(控制参数)
public float[] genes;
// 适应度评分
public float fitness = 0;
// 创建随机基因组
public LanderGenome()
{
genes = new float[GENE_LENGTH];
for (int i = 0; i < GENE_LENGTH; i++)
{
genes[i] = Random.Range(-1.0f, 1.0f);
}
}
// 从已有基因创建基因组
public LanderGenome(float[] newGenes)
{
genes = new float[GENE_LENGTH];
System.Array.Copy(newGenes, genes, GENE_LENGTH);
}
// 克隆基因组
public LanderGenome Clone()
{
return new LanderGenome(genes);
}
// 从神经网络权重创建基因组
public static LanderGenome FromNeuralNetwork(LanderNeuralNetwork network)
{
return new LanderGenome(network.GetWeights());
}
// 转换为神经网络权重
public void ApplyToNeuralNetwork(LanderNeuralNetwork network)
{
network.SetWeights(genes);
}
// 解码基因组获取控制参数
public Dictionary<string, float> DecodeControlParameters()
{
Dictionary<string, float> parameters = new Dictionary<string, float>();
// 这些参数需要根据你的控制系统进行调整
parameters["thrustSensitivity"] = Mathf.Abs(genes[0]);
parameters["rotationSensitivity"] = Mathf.Abs(genes[1]);
parameters["hoverHeight"] = Mathf.Abs(genes[2]) * 20.0f + 5.0f; // 5-25米
parameters["landingSpeedThreshold"] = Mathf.Abs(genes[3]) * 2.0f + 0.5f; // 0.5-2.5 m/s
parameters["fuelConservation"] = Mathf.Abs(genes[4]);
parameters["stabilityPreference"] = Mathf.Abs(genes[5]);
return parameters;
}
}
/// <summary>
/// 着陆器神经网络
/// </summary>
public class LanderNeuralNetwork
{
// 网络结构
private int inputSize = 8; // 输入节点数
private int hiddenSize = 12; // 隐藏层节点数
private int outputSize = 2; // 输出节点数(推力和旋转)
// 网络权重
private float[] weights;
private int weightCount;
// 创建新的神经网络
public LanderNeuralNetwork()
{
// 计算权重数量
weightCount = (inputSize * hiddenSize) + (hiddenSize * outputSize) + hiddenSize + outputSize;
weights = new float[weightCount];
// 随机初始化权重
for (int i = 0; i < weightCount; i++)
{
weights[i] = Random.Range(-1.0f, 1.0f);
}
}
// 前向传播计算输出
public float[] FeedForward(float[] inputs)
{
if (inputs.Length != inputSize)
{
Debug.LogError("输入大小不匹配");
return new float[outputSize];
}
// 隐藏层节点
float[] hiddenNodes = new float[hiddenSize];
// 输入层到隐藏层
int weightIndex = 0;
for (int h = 0; h < hiddenSize; h++)
{
float sum = weights[weightIndex++]; // 偏置
for (int i = 0; i < inputSize; i++)
{
sum += inputs[i] * weights[weightIndex++];
}
// 激活函数 (tanh)
hiddenNodes[h] = Mathf.Tanh(sum);
}
// 隐藏层到输出层
float[] outputs = new float[outputSize];
for (int o = 0; o < outputSize; o++)
{
float sum = weights[weightIndex++]; // 偏置
for (int h = 0; h < hiddenSize; h++)
{
sum += hiddenNodes[h] * weights[weightIndex++];
}
// 激活函数 (tanh)
outputs[o] = Mathf.Tanh(sum);
}
return outputs;
}
// 获取所有权重
public float[] GetWeights()
{
return weights;
}
// 设置所有权重
public void SetWeights(float[] newWeights)
{
if (newWeights.Length != weightCount)
{
Debug.LogError("权重数量不匹配");
return;
}
System.Array.Copy(newWeights, weights, weightCount);
}
}
基因组类定义了AI控制器的基因编码,包括神经网络的权重。神经网络用于将输入状态(如高度、速度、燃料等)映射到控制输出(推力和旋转)。
6.5.2 杂交和变异操作
接下来,我们需要实现基因组的杂交和变异操作,用于遗传算法的进化过程:
csharp
using UnityEngine;
/// <summary>
/// 遗传算法操作
/// </summary>
public static class GeneticOperations
{
// 杂交操作 - 均匀交叉
public static LanderGenome Crossover(LanderGenome parent1, LanderGenome parent2)
{
float[] childGenes = new float[LanderGenome.GENE_LENGTH];
for (int i = 0; i < LanderGenome.GENE_LENGTH; i++)
{
// 随机选择父母基因
if (Random.value < 0.5f)
childGenes[i] = parent1.genes[i];
else
childGenes[i] = parent2.genes[i];
}
return new LanderGenome(childGenes);
}
// 变异操作 - 高斯变异
public static void Mutate(LanderGenome genome, float mutationRate, float mutationStrength)
{
for (int i = 0; i < LanderGenome.GENE_LENGTH; i++)
{
if (Random.value < mutationRate)
{
// 添加高斯随机噪声
float noise = Random.Range(-1.0f, 1.0f) * mutationStrength;
genome.genes[i] += noise;
// 限制在[-1, 1]范围内
genome.genes[i] = Mathf.Clamp(genome.genes[i], -1.0f, 1.0f);
}
}
}
// 选择操作 - 锦标赛选择
public static LanderGenome TournamentSelection(LanderGenome[] population, int tournamentSize)
{
LanderGenome best = null;
float bestFitness = float.MinValue;
for (int i = 0; i < tournamentSize; i++)
{
int randomIndex = Random.Range(0, population.Length);
LanderGenome candidate = population[randomIndex];
if (best == null || candidate.fitness > bestFitness)
{
best = candidate;
bestFitness = candidate.fitness;
}
}
return best.Clone();
}
// 精英保留 - 找出并保留最佳基因组
public static LanderGenome[] GetElites(LanderGenome[] population, int eliteCount)
{
// 创建暂存数组
LanderGenome[] sortedPopulation = new LanderGenome[population.Length];
System.Array.Copy(population, sortedPopulation, population.Length);
// 按适应度排序
System.Array.Sort(sortedPopulation, (a, b) => b.fitness.CompareTo(a.fitness));
// 复制精英
LanderGenome[] elites = new LanderGenome[eliteCount];
for (int i = 0; i < eliteCount; i++)
{
elites[i] = sortedPopulation[i].Clone();
}
return elites;
}
}
这个类实现了常见的遗传算法操作,包括杂交、变异、选择和精英保留。这些操作用于在进化过程中创建新一代基因组。
6.5.3 适应性函数
适应性函数用于评估基因组的质量,决定哪些基因组更适合繁殖:
csharp
using UnityEngine;
/// <summary>
/// 适应度评估
/// </summary>
public class LanderFitnessEvaluator : MonoBehaviour
{
[Header("着陆器引用")]
public Lander lander;
public MoonTerrainGenerator terrain;
[Header("评估参数")]
public float safetyWeight = 0.4f; // 安全着陆权重
public float fuelWeight = 0.2f; // 燃料效率权重
public float speedWeight = 0.2f; // 着陆速度权重
public float precisionWeight = 0.2f; // 着陆精度权重
// 评估基因组适应度
public float EvaluateGenome(LanderGenome genome, LanderSimulation simulation)
{
// 运行模拟
LanderSimulationResult result = simulation.RunSimulation(genome);
// 计算各方面得分
float safetyScore = CalculateSafetyScore(result);
float fuelScore = CalculateFuelScore(result);
float speedScore = CalculateSpeedScore(result);
float precisionScore = CalculatePrecisionScore(result);
// 计算加权总分
float totalScore = safetyScore * safetyWeight +
fuelScore * fuelWeight +
speedScore * speedWeight +
precisionScore * precisionWeight;
return totalScore;
}
// 安全着陆得分
private float CalculateSafetyScore(LanderSimulationResult result)
{
if (result.hasCrashed)
return 0.0f;
if (result.hasLanded)
return 1.0f;
// 如果既没着陆也没坠毁(如超时或燃料耗尽),给予部分分数
float distanceToGround = result.finalHeight;
float maxHeight = 500.0f; // 假设模拟的最大高度
// 越接近地面,分数越高
return 1.0f - Mathf.Clamp01(distanceToGround / maxHeight);
}
// 燃料效率得分
private float CalculateFuelScore(LanderSimulationResult result)
{
// 燃料剩余比例
float fuelRatio = result.finalFuel / result.initialFuel;
if (result.hasCrashed)
{
// 如果坠毁,燃料得分降低
return fuelRatio * 0.5f;
}
if (result.hasLanded)
{
// 如果成功着陆,满分
return fuelRatio;
}
// 其他情况,部分得分
return fuelRatio * 0.7f;
}
// 着陆速度得分
private float CalculateSpeedScore(LanderSimulationResult result)
{
if (!result.hasLanded && !result.hasCrashed)
return 0.0f;
// 安全着陆速度阈值
float safeSpeed = 2.0f;
// 如果着陆速度低于安全阈值,给予高分
if (result.finalSpeed < safeSpeed)
return 1.0f;
// 否则,根据速度给予部分分数
float maxDangerousSpeed = 10.0f;
return 1.0f - Mathf.Clamp01((result.finalSpeed - safeSpeed) / (maxDangerousSpeed - safeSpeed));
}
// 着陆精度得分
private float CalculatePrecisionScore(LanderSimulationResult result)
{
if (!result.hasLanded)
return 0.0f;
// 计算到最近着陆台的距离
Vector2 nearestPad = terrain.GetNearestLandingPad(result.finalPosition);
float distance = Vector2.Distance(result.finalPosition, nearestPad);
// 着陆台宽度
float padWidth = 20.0f;
// 如果在着陆台上,满分
if (distance < padWidth / 2)
return 1.0f;
// 否则,根据距离给予部分分数
float maxDistance = 100.0f;
return 1.0f - Mathf.Clamp01((distance - padWidth / 2) / (maxDistance - padWidth / 2));
}
}
// 模拟结果类
public class LanderSimulationResult
{
public bool hasLanded = false;
public bool hasCrashed = false;
public float simulationTime = 0.0f;
public float initialFuel = 0.0f;
public float finalFuel = 0.0f;
public float finalSpeed = 0.0f;
public float finalHeight = 0.0f;
public Vector2 finalPosition;
public float maxAltitude = 0.0f;
public float fuelUsed = 0.0f;
public float distanceToTarget = 0.0f;
}
适应度评估器综合考虑了安全着陆、燃料效率、着陆速度和着陆精度等因素,为每个基因组计算一个综合得分。这个得分用于指导遗传算法的选择过程。
6.5.4 更新函数
现在,我们需要实现AI控制器类,它使用神经网络来控制着陆器:
csharp
using UnityEngine;
/// <summary>
/// 遗传算法控制器
/// </summary>
public class GeneticController : LanderController
{
[Header("神经网络")]
public LanderNeuralNetwork neuralNetwork;
[Header("感知参数")]
public float maxHeight = 500.0f;
public float maxSpeed = 20.0f;
public float terrainScanDistance = 100.0f;
public int terrainScanRays = 5;
[Header("调试")]
public bool showDebugInfo = true;
public bool showTerrainScan = true;
// 初始化
protected override void Start()
{
base.Start();
// 创建神经网络
neuralNetwork = new LanderNeuralNetwork();
}
// 更新控制
public override void UpdateControl()
{
// 获取感知输入
float[] inputs = GetSensorInputs();
// 通过神经网络计算控制输出
float[] outputs = neuralNetwork.FeedForward(inputs);
// 应用控制输出
float thrustOutput = Mathf.Clamp01((outputs[0] + 1.0f) / 2.0f); // 将[-1,1]映射到[0,1]
float rotationOutput = outputs[1];
// 设置推力和旋转
currentThrust = thrustOutput * maxThrust;
RotateLander(rotationOutput);
// 应用推力
ApplyThrust(currentThrust);
// 显示调试信息
if (showDebugInfo)
{
Debug.Log($"高度: {inputs[0]:F2}, 垂直速度: {inputs[1]:F2}, 水平速度: {inputs[2]:F2}, " +
$"推力: {thrustOutput:F2}, 旋转: {rotationOutput:F2}");
}
}
// 获取感知输入
private float[] GetSensorInputs()
{
// 创建输入数组
float[] inputs = new float[8];
// 基本传感器数据
Vector2 position = landerTransform.position;
Vector2 velocity = landerRigidbody.velocity;
float height = position.y;
float angle = currentRotation;
// 规范化输入
inputs[0] = Mathf.Clamp01(height / maxHeight); // 归一化高度
inputs[1] = Mathf.Clamp(velocity.y / maxSpeed, -1.0f, 1.0f); // 归一化垂直速度
inputs[2] = Mathf.Clamp(velocity.x / maxSpeed, -1.0f, 1.0f); // 归一化水平速度
inputs[3] = Mathf.Sin(angle * Mathf.Deg2Rad); // 角度的正弦值
inputs[4] = Mathf.Cos(angle * Mathf.Deg2Rad); // 角度的余弦值
inputs[5] = currentFuel / 100.0f; // 归一化燃料
// 地形扫描
float[] terrainData = ScanTerrain();
inputs[6] = terrainData[0]; // 最近地形高度
inputs[7] = terrainData[1]; // 地形坡度
return inputs;
}
// 扫描地形
private float[] ScanTerrain()
{
float[] result = new float[2] { 0, 0 };
// 向下发射射线检测地形
Vector2 origin = landerTransform.position;
RaycastHit2D hit = Physics2D.Raycast(origin, Vector2.down, terrainScanDistance, LayerMask.GetMask("Terrain"));
if (hit.collider != null)
{
// 地形高度(归一化)
float terrainHeight = hit.point.y;
float relativeHeight = (origin.y - terrainHeight) / terrainScanDistance;
result[0] = Mathf.Clamp01(relativeHeight);
// 计算地形坡度
Vector2 normal = hit.normal;
float slope = Vector2.Dot(normal, Vector2.up); // 1表示平坦,0表示垂直
result[1] = slope;
// 显示地形扫描
if (showTerrainScan)
{
Debug.DrawLine(origin, hit.point, Color.yellow);
Debug.DrawRay(hit.point, normal * 2.0f, Color.cyan);
}
}
else
{
// 如果没有检测到地形,假设地形很远
result[0] = 1.0f;
result[1] = 1.0f;
if (showTerrainScan)
{
Debug.DrawRay(origin, Vector2.down * terrainScanDistance, Color.red);
}
}
return result;
}
// 设置基因组
public void SetGenome(LanderGenome genome)
{
genome.ApplyToNeuralNetwork(neuralNetwork);
}
// 重置控制器
public override void Reset()
{
base.Reset();
}
}
遗传控制器使用神经网络来处理感知输入并生成控制输出。它通过获取着陆器的高度、速度、角度、燃料和地形信息作为输入,然后使用神经网络计算推力和旋转控制。
6.5.5 运行程序
最后,我们需要实现遗传算法的主循环,负责进化和优化控制器:
csharp
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
/// <summary>
/// 遗传算法训练管理器
/// </summary>
public class GeneticTrainingManager : MonoBehaviour
{
[Header("遗传算法参数")]
public int populationSize = 50;
public int generationCount = 100;
public float mutationRate = 0.1f;
public float mutationStrength = 0.2f;
public int eliteCount = 5;
public int tournamentSize = 5;
[Header("模拟参数")]
public int simulationsPerGeneration = 10;
public float simulationTimeLimit = 60.0f;
[Header("引用")]
public GeneticController aiController;
public Lander lander;
public MoonTerrainGenerator terrain;
public LanderFitnessEvaluator fitnessEvaluator;
[Header("UI")]
public Text generationText;
public Text bestFitnessText;
public Toggle trainingToggle;
// 训练状态
private LanderGenome[] population;
private LanderGenome bestGenome;
private float bestFitness = 0;
private int currentGeneration = 0;
private bool isTraining = false;
// 开始
void Start()
{
// 初始化UI
if (trainingToggle != null)
trainingToggle.onValueChanged.AddListener(SetTrainingActive);
// 初始化种群
InitializePopulation();
// 更新UI
UpdateUI();
}
// 初始化种群
private void InitializePopulation()
{
population = new LanderGenome[populationSize];
for (int i = 0; i < populationSize; i++)
{
population[i] = new LanderGenome();
}
// 重置代数和最佳适应度
currentGeneration = 0;
bestFitness = 0;
bestGenome = null;
}
// 设置训练状态
public void SetTrainingActive(bool active)
{
isTraining = active;
if (isTraining)
StartCoroutine(TrainingCoroutine());
}
// 训练协程
private IEnumerator TrainingCoroutine()
{
while (isTraining && currentGeneration < generationCount)
{
yield return StartCoroutine(EvaluateGeneration());
// 创建新一代
LanderGenome[] newPopulation = CreateNextGeneration();
population = newPopulation;
// 更新代数
currentGeneration++;
// 更新UI
UpdateUI();
// 短暂暂停,避免界面卡顿
yield return new WaitForSeconds(0.1f);
}
// 训练完成
if (currentGeneration >= generationCount)
{
Debug.Log("训练完成!");
isTraining = false;
if (trainingToggle != null)
trainingToggle.isOn = false;
}
}
// 评估当前代
private IEnumerator EvaluateGeneration()
{
Debug.Log($"评估第 {currentGeneration} 代...");
for (int i = 0; i < populationSize; i++)
{
// 如果训练被取消,提前退出
if (!isTraining)
yield break;
LanderGenome genome = population[i];
// 创建模拟器
LanderSimulation simulation = new LanderSimulation(
lander, terrain, simulationTimeLimit, simulationsPerGeneration);
// 评估适应度
float fitness = fitnessEvaluator.EvaluateGenome(genome, simulation);
genome.fitness = fitness;
// 更新最佳基因组
if (bestGenome == null || fitness > bestFitness)
{
bestFitness = fitness;
bestGenome = genome.Clone();
// 将最佳基因组应用到AI控制器
if (aiController != null)
aiController.SetGenome(bestGenome);
// 更新UI
UpdateUI();
}
// 每评估几个基因组暂停一下
if (i % 5 == 0)
yield return null;
}
Debug.Log($"第 {currentGeneration} 代评估完成,最佳适应度: {bestFitness:F4}");
}
// 创建下一代
private LanderGenome[] CreateNextGeneration()
{
LanderGenome[] newPopulation = new LanderGenome[populationSize];
// 保留精英
LanderGenome[] elites = GeneticOperations.GetElites(population, eliteCount);
for (int i = 0; i < eliteCount; i++)
{
newPopulation[i] = elites[i];
}
// 填充剩余位置
for (int i = eliteCount; i < populationSize; i++)
{
// 锦标赛选择父代
LanderGenome parent1 = GeneticOperations.TournamentSelection(population, tournamentSize);
LanderGenome parent2 = GeneticOperations.TournamentSelection(population, tournamentSize);
// 杂交
LanderGenome child = GeneticOperations.Crossover(parent1, parent2);
// 变异
GeneticOperations.Mutate(child, mutationRate, mutationStrength);
// 添加到新种群
newPopulation[i] = child;
}
return newPopulation;
}
// 更新UI
private void UpdateUI()
{
if (generationText != null)
generationText.text = $"代数: {currentGeneration} / {generationCount}";
if (bestFitnessText != null)
bestFitnessText.text = $"最佳适应度: {bestFitness:F4}";
}
// 保存最佳基因组
public void SaveBestGenome()
{
if (bestGenome == null)
return;
string json = JsonUtility.ToJson(new GenomeSaveData(bestGenome));
PlayerPrefs.SetString("BestLanderGenome", json);
PlayerPrefs.Save();
Debug.Log("最佳基因组已保存");
}
// 加载最佳基因组
public void LoadBestGenome()
{
if (!PlayerPrefs.HasKey("BestLanderGenome"))
{
Debug.Log("没有找到保存的基因组");
return;
}
string json = PlayerPrefs.GetString("BestLanderGenome");
GenomeSaveData saveData = JsonUtility.FromJson<GenomeSaveData>(json);
bestGenome = new LanderGenome(saveData.genes);
bestFitness = saveData.fitness;
// 应用到AI控制器
if (aiController != null)
aiController.SetGenome(bestGenome);
Debug.Log("最佳基因组已加载,适应度: " + bestFitness);
// 更新UI
UpdateUI();
}
}
// 用于保存基因组的类
[System.Serializable]
public class GenomeSaveData
{
public float[] genes;
public float fitness;
public GenomeSaveData(LanderGenome genome)
{
genes = genome.genes;
fitness = genome.fitness;
}
}
// 模拟器类
public class LanderSimulation
{
private Lander lander;
private MoonTerrainGenerator terrain;
private float timeLimit;
private int simulationCount;
public LanderSimulation(Lander lander, MoonTerrainGenerator terrain, float timeLimit, int simulationCount)
{
this.lander = lander;
this.terrain = terrain;
this.timeLimit = timeLimit;
this.simulationCount = simulationCount;
}
// 运行模拟
public LanderSimulationResult RunSimulation(LanderGenome genome)
{
// 这里应该有一个完整的物理模拟实现
// 为简化起见,这里使用一个假模拟
LanderSimulationResult result = new LanderSimulationResult();
// 假设模拟结果(在实际应用中,这应该是物理模拟的结果)
result.hasLanded = Random.value < 0.3f;
result.hasCrashed = !result.hasLanded && Random.value < 0.5f;
result.simulationTime = Random.Range(10.0f, timeLimit);
result.initialFuel = 100.0f;
result.finalFuel = Random.Range(0.0f, 80.0f);
result.finalSpeed = Random.Range(0.0f, 10.0f);
result.finalHeight = Random.Range(0.0f, 100.0f);
result.finalPosition = new Vector2(Random.Range(-50.0f, 50.0f), 0);
result.maxAltitude = 500.0f;
result.fuelUsed = result.initialFuel - result.finalFuel;
// 计算到目标的距离
Vector2 targetPosition = terrain.GetNearestLandingPad(result.finalPosition);
result.distanceToTarget = Vector2.Distance(result.finalPosition, targetPosition);
// 考虑基因组特性调整结果
// 这里我们假设基因组中的某些值影响结果
if (genome.genes[0] > 0.5f) // 高推力敏感度
{
result.fuelUsed *= 1.2f;
result.finalFuel = Mathf.Max(0, result.initialFuel - result.fuelUsed);
result.finalSpeed *= 0.8f;
}
if (genome.genes[1] > 0.5f) // 高旋转敏感度
{
result.finalSpeed *= 1.1f;
}
if (genome.genes[2] > 0.7f) // 优先悬停
{
result.finalSpeed *= 0.7f;
result.fuelUsed *= 1.3f;
result.finalFuel = Mathf.Max(0, result.initialFuel - result.fuelUsed);
}
return result;
}
}
遗传训练管理器实现了遗传算法的核心流程,包括初始化种群、评估适应度、选择、杂交、变异和创建新一代。它还提供了保存和加载最佳基因组的功能,以便在游戏中使用经过训练的AI控制器。
6.6 总结
在本章中,我们深入探讨了矢量图形处理、物理模拟和遗传算法在游戏开发中的应用,以登月模拟为核心案例。
我们首先学习了如何创建和处理矢量图形,包括顶点、线条和形状的绘制与变换。然后,我们探讨了物理模拟的基础知识,包括力、质量、速度和加速度等概念,以及它们在游戏中的应用。
接下来,我们实现了一个完整的登月模拟游戏,包括月球地形生成、着陆器物理模拟和用户控制系统。我们学习了如何处理引擎推力、燃料消耗、重力和碰撞检测等关键元素。
最后,我们使用遗传算法开发了一个自动控制系统,可以学习并优化着陆策略。我们实现了基因组编码、适应度评估、杂交和变异等核心遗传算法组件,创建了能够自主控制着陆器的AI。
通过这个项目,我们看到了如何将数学、物理和人工智能的概念应用到实际游戏开发中,创造出既有趣又具有挑战性的游戏体验。
6.7 习题
以下是一些练习题,帮助你巩固本章所学知识:
-
矢量图形练习:创建一个可以绘制不同形状(如圆形、矩形、三角形)的工具,使用LineRenderer或自定义Mesh实现。
-
物理模拟练习:实现一个简单的太空物理模拟器,模拟多个天体之间的引力作用。
-
控制系统练习:为登月模拟添加更多控制选项,如侧向推进器或姿态控制系统。
-
地形生成练习:改进月球地形生成算法,添加更多地形特征,如山脉、陨石坑或洞穴。
-
遗传算法练习:修改适应度函数,优化特定任务的AI控制器,如最快着陆、最省燃料或最精确着陆。
-
用户界面练习:为登月模拟创建一个更完善的用户界面,显示更多信息如姿态、角速度和推力向量。
-
挑战任务:实现一个多阶段的登月任务,包括轨道进入、降轨和最终着陆阶段。
这些练习将帮助你进一步探索游戏物理和AI的结合,并将这些概念应用到更复杂的游戏开发项目中。
更多推荐


所有评论(0)