登月模拟游戏开发:矢量图形、物理引擎与遗传算法

本章我们将深入探讨游戏开发中的矢量图形处理、物理模拟以及使用遗传算法实现自动控制系统,以登月飞行模拟为核心案例。我们将从基础矢量数学开始,构建一个完整的登月模拟游戏,包括人工控制和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提供了Vector2Vector3类来处理二维和三维矢量。

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 习题

以下是一些练习题,帮助你巩固本章所学知识:

  1. 矢量图形练习:创建一个可以绘制不同形状(如圆形、矩形、三角形)的工具,使用LineRenderer或自定义Mesh实现。

  2. 物理模拟练习:实现一个简单的太空物理模拟器,模拟多个天体之间的引力作用。

  3. 控制系统练习:为登月模拟添加更多控制选项,如侧向推进器或姿态控制系统。

  4. 地形生成练习:改进月球地形生成算法,添加更多地形特征,如山脉、陨石坑或洞穴。

  5. 遗传算法练习:修改适应度函数,优化特定任务的AI控制器,如最快着陆、最省燃料或最精确着陆。

  6. 用户界面练习:为登月模拟创建一个更完善的用户界面,显示更多信息如姿态、角速度和推力向量。

  7. 挑战任务:实现一个多阶段的登月任务,包括轨道进入、降轨和最终着陆阶段。

这些练习将帮助你进一步探索游戏物理和AI的结合,并将这些概念应用到更复杂的游戏开发项目中。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐