车辆多视角3D漫游系统(中山大学3D游戏大作业)

  • 一、简介
  • 二、项目结构
  • 1.UML
  • 2.script
  • 三、代码分析
  • 四、参考文章



github仓库:https://github.com/linfn3/game_car b站视频:https://www.bilibili.com/video/BV1GP4y1C7bs/

一、简介

利用粒子系统、物理系统、UI系统等技术以多款赛车为载体,设置多视角,实现多场景身临其境的车辆漫游,以及人物交互、天气系统、常用游戏界面、小地图等功能

Unity漫游代码 unity3d漫游系统_3d


二、项目结构

1.UML

Unity漫游代码 unity3d漫游系统_Unity漫游代码_02

2.script

Unity漫游代码 unity3d漫游系统_Source_03


Unity漫游代码 unity3d漫游系统_游戏_04

三、代码分析

游戏场景:

Unity漫游代码 unity3d漫游系统_课程设计_05


Unity漫游代码 unity3d漫游系统_3d_06


粒子系统:

在Dust_Thresh.cs文件中使用粒子系统。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Dust_Thresh : MonoBehaviour
{
    // 指定车辆的刚体和烟尘粒子系统
    public Rigidbody carRigidbody;
    public ParticleSystem dustParticles;

    // 定义一个阈值,用于控制烟尘效果的播放
    public float dustThreshold = 45f;
    void Update()
    {
        // 如果车辆的速度超过阈值,则播放烟尘效果
        if (carRigidbody.velocity.magnitude > dustThreshold)
        {
            dustParticles.Play();
        }
        else
        {
            dustParticles.Stop();
        }
    }
}

Unity漫游代码 unity3d漫游系统_课程设计_07


车辆漫游:

具体代码写于Car_Control_opm.cs,包括运动的控制、速度设置、调节引擎音效等功能。

常见问题:

位移突兀,没有摩擦阻力;速度无限制,容易穿模;急刹车时位移和速度突变为0

解决方法:

使用Input.GetKey()函数实时捕捉键盘wasd键按键状态,判断速度边界,使用加速度改变车辆实时速度,从而实现位移的平稳过渡;刹车时,使用Math .Lerp函数对速度按时间Δt进行内插,达到平稳过渡效果。

车轮运动通过获取车轮碰撞体的物理转动(轮轴转速、摆动角度),使得车轮mesh模型随之旋转

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Car_Control_opm : MonoBehaviour
{
    private float y;
    //车辆控制速度参数
    public float speedOne = 0f;    //车辆实时速度
    private float speedMax = 50f/2.8f;  //车辆最大速度
    private float speedMin = -30f;  //车辆最小速度(倒车最大速度)
    private float speedUpA = 3f;    //车辆加速加速度(A键控制)
    private float speedDownS = 4f;  //车辆减速加速度(S键控制)
    private float speedTend = 0.5f; //无操作实时速度趋于0时加速度
    private float speedBack = 1f;   //车辆倒车加速度

    //引擎音效范围
    public float minVolume = 0.5f; // 引擎声音的最小音量
    public float maxVolume = 0.85f; // 引擎声音的最大音量
    public float minPitch = 0.4f; // 引擎声音的最小音调
    public float maxPitch = 1.2f; // 引擎声音的最大音调
    public AudioClip CarSound;
    private bool isIgnited = false;


    private MeshRenderer[] wheelMesh;
    private WheelCollider[] wheel;
    private float h;
    private float v;
    float maxAngle = 35;//最大转角

    public ParticleSystem N2Particles;


    void Start()
    {
        wheelMesh = transform.GetChild(2).GetComponentsInChildren<MeshRenderer>();
        wheel = transform.GetChild(3).GetComponentsInChildren<WheelCollider>();
    }
    void Update()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            N2Particles.Play();
            speedMax = 50f / 2.8f * 1.2f;
            speedUpA = 6f;
        }
        else
        {
            N2Particles.Stop();
            speedMax = 50f / 2.8f;
            speedUpA = 3f;
        }

        // 判断是否按下 B 键
        if (Input.GetKeyDown(KeyCode.B))
        {
            // 如果已经点火,则忽略
            if (isIgnited)
            {
                return;
            }
            // 点火
            isIgnited = true;
        }

        // 判断是否已经点火
        if (!isIgnited)
        {
            return;
        }

        AudioSource audioSource = GetComponent<AudioSource>();
        audioSource.clip = CarSound;

        // 计算引擎声音的音量
        float volume = Mathf.Lerp(minVolume, maxVolume, speedOne / 80.0f);
        // 设置引擎声音的音量
        audioSource.volume = volume;
        // 计算引擎声音的音调
        float pitch = Mathf.Lerp(minPitch, maxPitch, speedOne / 100.0f);
        // 设置引擎声音的音调
        audioSource.pitch = pitch;


        //按下W键并且速度没达到最大,则速度增加
        if (Input.GetKey(KeyCode.W) && speedOne < speedMax)
        {
            speedOne = speedOne + Time.deltaTime * speedUpA;
        }
        //按下S键并且速度没达到零,则速度减小
        if (Input.GetKey(KeyCode.S) && speedOne > 0f)
        {
            speedOne = speedOne - Time.deltaTime * speedDownS;
        }
        //没有执行速度操作并且速度大于最小速度,则缓慢操作
        if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S) && speedOne > 0f)
        {
            speedOne = speedOne - Time.deltaTime * speedTend;
        }
        if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S) && speedOne < 0f)
        {
            speedOne = speedOne + Time.deltaTime * speedTend;
        }

        //按下S键并且速度没有达到倒车速度最大时,且车辆处于可以倒车状态时车辆倒车
        if (Input.GetKey(KeyCode.S) && speedOne > speedMin && speedOne <= 0)
        {
            speedOne = speedOne - Time.deltaTime * speedBack;
        }

        //按下空格,则汽车停止
        if (Input.GetKey(KeyCode.Space) && speedOne != 0)
        {

            speedOne = Mathf.Lerp(speedOne, 0, Time.deltaTime * 2f);
            if (speedOne < 2f) speedOne = 0;
        }


        transform.Translate(Vector3.forward * speedOne * Time.deltaTime);

        //使用A和D来控制物体左右旋转
        if (speedOne > 1f || speedOne < -1f)
        {
            y = Input.GetAxis("Horizontal") * 60f * Time.deltaTime;
            transform.Rotate(0, y, 0);
        }

    
        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");
        if (0 == Mathf.Abs(h) && 0 == Mathf.Abs(v)) return;
        else
        {
            for (int i = 0; i < 2; i++)
            {
                wheel[i].steerAngle = h * maxAngle;
            }
            for (int i = 0; i < 4; i++)
            {
                wheelMesh[i].transform.localRotation = Quaternion.Euler(wheel[i].rpm * 360 / 60, wheel[i].steerAngle, 0);

            }
        }
    }
}

相机跟随设置:Follow.cs
相机跟随的核心思想:让相机始终和汽车中心保持有平滑度的距离差,而非固定的距离差,通过内插实现,而非固定

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Follow : MonoBehaviour
{
    private Transform targetPos;//跟随的目标
    private Vector3 offsetPos;//固定位置
    private Vector3 tempPos;//内插出来的位置
    private bool is_follow = false;
    // Start is called before the first frame update
    void Start()
    {
        offsetPos = new Vector3 (1, 2, -4);
        targetPos = GameObject.FindGameObjectWithTag("Player").transform;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if(Input.GetKey(KeyCode.B))
        {
            is_follow = true;
        }
        if(is_follow)
        {
            tempPos = targetPos.position + targetPos.TransformDirection(offsetPos);//坐标转为车辆朝向方向
            transform.position = Vector3.Lerp(transform.position, tempPos, Time.fixedDeltaTime * 1.8f);//内插使得摄像机平滑跟随
            transform.LookAt(targetPos);//使得摄像机始终望向车辆
        }
    }
}

音效播放通过brake.cs处理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Brake : MonoBehaviour
{
    // 刹车音效
    public AudioClip BrakeSound;
    public ParticleSystem speckleParticles;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        AudioSource audioSource = GetComponent<AudioSource>();

        // 判断是否按下 Space 键
        if (Input.GetKey(KeyCode.Space))
        {
            // 播放点火音效
            speckleParticles.Play();
            audioSource.clip = BrakeSound;
            audioSource.Play();
        }
        else { 
            speckleParticles.Stop();
            audioSource.Stop();
        }

    }
}

PauseMenu.cs控制菜单

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class PauseMenu : MonoBehaviour
{
    public static bool GameIsPaused = false;
    public GameObject pauseMenuUI;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            if (GameIsPaused)
            {
                Resume();
            }
            else
            {
                Pause();
            }
        }
        else
        {
            Debug.Log("Back key not pressed");
        }
    }

    public void Resume()
    {
        pauseMenuUI.SetActive(false);
        Time.timeScale = 1.0f;
        GameIsPaused = false;
    }

    public void Pause()
    {
        pauseMenuUI.SetActive(true);
        Time.timeScale = 0.0f;
        GameIsPaused = true;

    }

    public void MainMenu()
    {
        Time.timeScale = 1.0f;
        GameIsPaused = false;
        SceneManager.LoadScene("StartMenu");
    }

    public void QuitGame()
    {
        Debug.Log("QUIT!");
        Application.Quit();
    }


}

Ignite.cs,用于车辆点火

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ignite : MonoBehaviour
{
    // 是否已经点火
    private bool isIgnited = false;
    // 点火音效
    public AudioClip ignitionSound;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        // 判断是否按下 B 键
        if (Input.GetKeyDown(KeyCode.B))
        {
            // 如果已经点火,则忽略
            if (isIgnited)
            {
                return;
            }
            // 点火
            isIgnited = true;
            // 播放点火音效
            AudioSource audioSource = GetComponent<AudioSource>();
            audioSource.clip = ignitionSound;
            audioSource.Play();
        }

        // 判断是否已经点火
        if (!isIgnited)
        {
            return;
        }
    }
}

View_Change.cs用于变换视角,按下键盘2代表驾驶员,4代表副驾驶,3代表旁观者

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class View_Change : MonoBehaviour
{
    // 指定第一人称摄像机和第三人称摄像机
    public Camera First_Person_View;
    public Camera Third_Person_View;
    public Camera Driver_View;
    public Camera Side_View;

    // Start is called before the first frame update
    void Start()
    {
        First_Person_View.enabled = false;
        Driver_View.enabled = false;
        Third_Person_View.enabled = true;
        Side_View.enabled = false;
    }


    // 定义一个标志变量,用于指示当前是否为第一人称视角

    void Update()
    {
        // 如果按下了1键,则切换为第一人称
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            SwitchView1();
        }
        // 如果按下了2键,则切换为driver
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            SwitchView2();
        }
        // 如果按下了3键,则切换为第三人稱
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            SwitchView3();
        }
        // 如果按下了4键,则切换为Side
        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            SwitchView4();
        }

    }

    void SwitchView1()
    {
            First_Person_View.enabled = true;
            Third_Person_View.enabled = false;
            Driver_View.enabled = false;
        Side_View.enabled = false;

    }
    void SwitchView2()
    {
        First_Person_View.enabled = false;
        Third_Person_View.enabled = false;
        Driver_View.enabled =true ;
        Side_View.enabled = false;

    }
    void SwitchView3()
    {
        First_Person_View.enabled = false;
        Third_Person_View.enabled =true ;
        Driver_View.enabled = false;
        Side_View.enabled = false;

    }
    void SwitchView4()
    {
        First_Person_View.enabled = false;
        Third_Person_View.enabled = false;
        Driver_View.enabled = false;
        Side_View.enabled = true;
    }

}

weather.cs天气系统,可以实现晴天、雨天、阴天、下雪天变换

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class weather : MonoBehaviour
{
    public Material[] mats;
    // Start is called before the first frame update
    void Start()
    {
        RenderSettings.skybox = mats[0];
    }

 //   public ParticleSystem clearWeather; // 晴天效果
 //   public ParticleSystem cloudWeather;//阴天效果
    public ParticleSystem rainWeather; // 下雨天效果
    public ParticleSystem snowWeather; // 下雪天效果
    // Update is called once per frame
    void Update()
    {
            // 监听数字键盘的“F1”、“F2”、“F3”、“F4”按键
          if (Input.GetKeyDown(KeyCode.F1))
          {
            // 如果按下“F1”键,则切换到晴天效果
            RenderSettings.skybox = mats[0];
            rainWeather.gameObject.SetActive(false);
            snowWeather.gameObject.SetActive(false);
          }
          else if (Input.GetKeyDown(KeyCode.F2))
          {
            // 如果按下“F2”键,则切换到阴天效果
            Debug.Log("Back key not pressed");
            RenderSettings.skybox = mats[1];
            rainWeather.gameObject.SetActive(false);
            snowWeather.gameObject.SetActive(false);
          }
          else if (Input.GetKeyDown(KeyCode.F3))
          {
            // 如果按下“F3”键,则切换到下雨天效果
            RenderSettings.skybox = mats[1];
            rainWeather.gameObject.SetActive(true);
            snowWeather.gameObject.SetActive(false);
          }
          else if (Input.GetKeyDown(KeyCode.F4))
          {
            // 如果按下“F4”键,则切换到下雪天效果
            RenderSettings.skybox = mats[1];
            rainWeather.gameObject.SetActive(false);
            snowWeather.gameObject.SetActive(true);
          }

    }


}

晴天:

Unity漫游代码 unity3d漫游系统_Unity漫游代码_08


阴天:

Unity漫游代码 unity3d漫游系统_Unity漫游代码_09


Minimap.cs控制小地图

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MiniMap : MonoBehaviour
{
    // Start is called before the first frame update
    public Camera minicamera;//小地图对应的摄影机
    public Transform player;//赛车对应的位置
    public Transform miniplayerIcon;//赛车对应的箭头的位置

    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //将游戏对象上方确定距离的位置赋值给小地图的相机位置
        minicamera.transform.position = new Vector3(player.position.x,
            minicamera.transform.position.y, player.position.z);
        //将箭头指向游戏对象的正向使其随游戏对象移动
        miniplayerIcon.eulerAngles = new Vector3(0, 0, -player.eulerAngles.y);
    }
}