附上视频地址:https://www.bilibili.com/video/av25226932/
还有我github的地址:传送门,大家有兴趣可以去下载我的项目自己在电脑上运行起来玩一玩
使用NavMesh实现坦克大战,要求敌方坦克能够自动寻路,并且能够主动攻击我方坦克
2. 效果预览
3. NavMesh与AI
从预览图里面我们可以看到,绿色坦克是我们要控制的坦克,而红色的坦克是敌方坦克,也就是我们的AI坦克,他们总可以移向玩家坦克所在的位置,这里需要用到的组件叫做NavMesh,那么关于这个NavMesh组件的使用,我在这里有几个推荐,这几个博客都是我从上届师兄的博客中找到的推荐
你可以在这几篇博客的指导下,自己动手玩一下NavMesh,以便对它有个初步的了解
4. 游戏实现
首先,你得找好素材包,这里我用到的素材是在别人的网盘里找到的,这里我也放出一个百度网盘的链接,里面包含有素材、视频教学还有我的代码。但是注意,如果你只是跟着教程一步步的做,只能实现双人对战坦克,而不能实现AI坦克,但是我想这些视屏教学对你也应该有启发
百度网盘链接 密码:k8ms
1. 创建场景并烘焙地图
导入素材包之后,会有现成的地图给你用,长这样:
然后你需要对地图进行烘焙,首先在windows下面找到navigation,点击之后你就会发现Inspector栏目旁边就会出现Navgation栏目了
然后你要设置Terrain的Navigation,首先你可以看一看你的地图上都有些什么东西,一般来说绝大多数都是障碍物,但是有些东西如地形什么的就不能算作障碍物了。选中你地图上的所有障碍物,进行烘焙操作:
2. AI坦克寻路与攻击
你还需要给两只坦克都加上NavMeshAgent组件,注意是两个坦克都要加;因为你一定要保证被追的坦克能被找到,追坦克的坦克能找到被追的坦克(说起来像顺口溜哈哈哈
这样,只要我们指定一个target,那么寻路坦克就能根据我们所制定的目标进行寻路了
至于攻击的话,有两个方法,首先肯定是要在AI坦克上面挂在脚本的,这无疑。然后你可以利用协程操作,指定坦克的攻击频率还有攻击判定条件;还有一种就是你在updata里面,指定一个framecount,只有在固定帧的时候,如果满足了条件才发起攻击【其实和协程差不多】
5. 项目完整代码(顺带说一下,我的AI坦克其实还支持人工键盘操作的哈哈哈哈)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameOver : MonoBehaviour {
public GameObject player1;
public GameObject player2;
public Text gameInfo;
// Use this for initialization
void Start () {
gameInfo.text = "";
}
// Update is called once per frame
void Update () {
if(player1 == null || player2 == null)
{
if(player1 == null)
{
gameInfo.text = "You Lose!";
}
else
{
gameInfo.text = "You Win!";
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Enemy : MonoBehaviour {
public Transform player1;
public GameObject shellPrefab;
private Transform firePosition;
public float shellSpeed = 10;
private int framenumber = 0;
// Use this for initialization
void Start () {
//StartCoroutine(Fire());
firePosition = transform.Find("FirePosition");
}
// Update is called once per frame
void Update()
{
framenumber++;
if (player1 != null)
{
NavMeshAgent agent = this.GetComponent();
agent.SetDestination(player1.position);
if (Vector3.Distance(transform.position, player1.transform.position) < 20 && (framenumber % 24 == 0))
{
GameObject go = GameObject.Instantiate(shellPrefab, firePosition.position, firePosition.rotation) as GameObject;
go.GetComponent().velocity = go.transform.forward * shellSpeed;
}
}
else
{
NavMeshAgent agent = this.GetComponent();
agent.velocity = Vector3.zero;
agent.ResetPath();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowTarget : MonoBehaviour {
public Transform player1;
public Transform player2;
private Vector3 offset;
private Camera camera;
// Use this for initialization
void Start () {
offset = transform.position - (player1.position + player2.position) / 2;
camera = this.GetComponent();
}
// Update is called once per frame
void Update () {
if (player1 == null || player2 == null) return;
transform.position = (player1.position + player2.position) / 2 + offset;
float distance = Vector3.Distance(player1.position, player2.position);
float size = distance * 0.58f;
camera.orthographicSize = size;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankHealth : MonoBehaviour {
public int hp = 100;
public GameObject tankExplosion;
public AudioClip tankExplosionAudio;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void TakeDamage()
{
if (hp <= 0) return;
if(hp > 0)
hp -= Random.Range(10, 20);
if(hp <= 0)//受到伤害之后,血量为0,控制死亡效果
{
AudioSource.PlayClipAtPoint(tankExplosionAudio, transform.position);
GameObject.Instantiate(tankExplosion, transform.position + Vector3.up, transform.rotation);
GameObject.Destroy(this.gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shell : MonoBehaviour {
public GameObject shellExplosionPrefab;
public AudioClip shellExplosionAudio;
public void OnTriggerEnter(Collider collider)
{
AudioSource.PlayClipAtPoint(shellExplosionAudio,transform.position);
GameObject.Instantiate(shellExplosionPrefab, transform.position, transform.rotation);
GameObject.Destroy(this.gameObject);
if(collider.tag == "Tank")
{
collider.SendMessage("TakeDamage");
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankAttack : MonoBehaviour {
public GameObject shellPrefab;
private Transform firePosition;
public KeyCode fireKey = KeyCode.Space;
public float shellSpeed = 10;
public AudioClip shotAudio;
// Use this for initialization
void Start () {
firePosition = transform.Find("FirePosition");
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(fireKey)) {
AudioSource.PlayClipAtPoint(shotAudio, transform.position);
GameObject go = GameObject.Instantiate(shellPrefab, firePosition.position, firePosition.rotation) as GameObject;
go.GetComponent().velocity = go.transform.forward * shellSpeed;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TankMovement : MonoBehaviour {
public float speed = 5;
public float angularSpeed = 30;
private Rigidbody rigidbody;
public float number = 1; //增加一个玩家的编号,通过编号区分控制
public AudioClip idleAduio;
public AudioClip drivingAduio;
private AudioSource audio;
// Use this for initialization
void Start () {
rigidbody = this.GetComponent();
audio = this.GetComponent();
}
private void FixedUpdate()
{
float v = Input.GetAxis("Verticalplayer" + number);
rigidbody.velocity = transform.forward * v * speed;
float h = Input.GetAxis("Horizontalplayer" + number);
rigidbody.angularVelocity = transform.up * h * angularSpeed;
if(Mathf.Abs(h) > 0.1 || Mathf.Abs(v) > 0.1)
{
audio.clip = drivingAduio;
if(audio.isPlaying == false)
audio.Play();
}
else
{
audio.clip = idleAduio;
if(audio.isPlaying == false)
audio.Play();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyForTime : MonoBehaviour {
public float time;
// Use this for initialization
void Start () {
Destroy(this.gameObject, time);
}
// Update is called once per frame
void Update () {
}
}