unity实现坦克对战

AI坦克建模
  • 感知
    在游戏中,程序是可以获得游戏世界任意信息的,设计一个杀死玩家的算法通常是比较容易的,因此如何限制信息获取是设计不同级别 agent 的核心问题。在AI坦克大战的游戏中,AI坦克获取的信息是通过视觉(Vision)得到的。每个AI坦克获取的信息是导航信息,但是因为有障碍物的阻挡,使得AI坦克不会“一窝蜂”的同时涌向玩家。同时,AI坦克并不能实时瞄准玩家,也增加了游戏的可玩性。

  • 思考
    主要使用了Unity自带的寻路组件Navigation 进行“思考”寻路。

  • 行动
    每个AI坦克都会发射子弹,但是不能让AI坦克一直发射子弹,这样就没得玩了。所以会有一个子弹的准备时间,每间隔一段时间再发射子弹。同时地形凹凸不平,也会使得子弹的轨迹有所偏差。

游戏实现
1. 下载资源

Assets Store下载tanks tutorial资源

2.动作状态机

在这里插入图片描述

3. 制作地图

在这里插入图片描述
将想要的颜色所对应的材质拖到地图上
在这里插入图片描述
最终地图为
在这里插入图片描述

4. 制作子弹

在这里插入图片描述
在这里插入图片描述

Bullet

实现了子弹的相关设计,设置了子弹的爆炸范围,对于玩家和敌方坦克,子弹会有不同的伤害

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

// 子弹设置 √
public class Bullet : MonoBehaviour {
    // 设置爆炸范围
    public float range = 3.0f;
    private TankType tankType;

    // 设置坦克的类型
    public void setTankType (TankType type) {
        tankType = type;
    }

    // 设置碰撞事件
    private void OnCollisionEnter (Collision collision) {
        // 打到自己不算
        if (collision.transform.gameObject.tag == "Enemy" && this.tankType == TankType.ENEMY ||
            collision.transform.gameObject.tag == "Player" && this.tankType == TankType.PLAYER)
            return;

        Factory factory = Singleton<Factory>.Instance;
        ParticleSystem explosion = factory.getParticleSystem ();
        explosion.transform.position = gameObject.transform.position;

        // 爆炸范围内的所有物体都受到伤害
        Collider[] colliders = Physics.OverlapSphere (gameObject.transform.position, range);

        foreach (var collider in colliders) {
            // 设置范围伤害
            float dis = Vector3.Distance (collider.transform.position, gameObject.transform.position);
            float damage;

            // 玩家和敌人设置不同的伤害
            if (collider.tag == "Enemy" && this.tankType == TankType.PLAYER) {
                damage = 50.0f / dis;
                collider.GetComponent<Tank> ().setHP (collider.GetComponent<Tank> ().getHP () - damage);
            } else if (collider.tag == "Player" && this.tankType == TankType.ENEMY) {
                damage = 20.0f / dis;
                collider.GetComponent<Tank> ().setHP (collider.GetComponent<Tank> ().getHP () - damage);
            }

            explosion.Play ();
        }
        if (gameObject.activeSelf) factory.recycleBullet (gameObject);
    }
}
5.制作玩家

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Player

实现了玩家的设计,设置了一个代理事件,主要控制玩家坦克被销毁;设置了玩家坦克初始的生命值,当坦克的生命值低于0则被销毁;控制坦克的移动;控制坦克的左右转向

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

// 玩家设置 √
public class Player : Tank {
    // 玩家坦克被摧毁
    public delegate void DestroyPlayer ();
    public static event DestroyPlayer destroyEvent;

    // 设置初始生命值
    void Start () { setHP (100); }

    void Update () {
        if (getHP () <= 0) {
            this.gameObject.SetActive (false);
            destroyEvent ();
        }
    }

    // 前后方向移动
    public void moveForWard () {
        gameObject.GetComponent<Rigidbody> ().velocity = gameObject.transform.forward * 20;
    }
    public void moveBackWard () {
        gameObject.GetComponent<Rigidbody> ().velocity = gameObject.transform.forward * (-20);
    }

    // 实现左右转向
    public void turn (float turnX) {
        float x = gameObject.transform.localEulerAngles.x;
        float y = gameObject.transform.localEulerAngles.y + turnX * 2;
        gameObject.transform.localEulerAngles = new Vector3 (x, y, 0);
    }
}
6.制作敌方坦克

在这里插入图片描述
在这里插入图片描述

Enemy

实现了敌人坦克的设置。由于用到了工厂模式,所以在坦克被销毁的时候需要进行回收;并且实现了 AI 功能,启动了协程,控制坦克的运动和射击操作;在坦克看到玩家的时候会进行跟踪移动,然后在射程范围内进行攻击。

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

// 敌人坦克设置 √
public class Enemy : Tank {
    // 敌人坦克被摧毁,进行回收
    public delegate void RecycleEnemy (GameObject enemy);
    public static event RecycleEnemy recycleEnemy;

    // 玩家位置
    private Vector3 playerPos;

    // 游戏是否结束
    private bool gameover;

    private void Start () {
        playerPos = GameDirector.getInstance ().currentSceneController.getPlayer ().transform.position;
        // 启动协程
        StartCoroutine (shoot ());
    }

    void Update () {
        playerPos = GameDirector.getInstance ().currentSceneController.getPlayer ().transform.position;
        gameover = GameDirector.getInstance ().currentSceneController.GameOver ();
        if (!gameover) {
            if (getHP () <= 0 && recycleEnemy != null) recycleEnemy (this.gameObject);
            else {
                // 向玩家移动
                NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent> ();
                agent.SetDestination (playerPos);
            }
        } else {
            // 游戏结束后停止运动
            NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent> ();
            agent.velocity = Vector3.zero;
            agent.ResetPath ();
        }
    }

    // 控制射击
    IEnumerator shoot () {
        while (!gameover) {
            for (float i = 1; i > 0; i -= Time.deltaTime) yield return 0;

            // 射程
            if (Vector3.Distance (playerPos, gameObject.transform.position) < 10) shoot (TankType.ENEMY);
        }
    }
}
7. Factory

实现了工厂模式,来生产和销毁坦克和子弹;主要设计了初始化游戏物体;生产坦克,生产子弹;回收坦克,回收子弹

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

public enum TankType { PLAYER, ENEMY }

// 工厂模式
public class Factory : MonoBehaviour {

    public GameObject player;
    public GameObject enemy;
    public GameObject bullet;
    public ParticleSystem explosion;

    private List<GameObject> usedTanks;
    private List<GameObject> freeTanks;
    private List<GameObject> usedBullets;
    private List<GameObject> freeBullets;

    private GameObject player_t;
    private List<ParticleSystem> particles;

    // 初始化
    private void Awake () {
        usedTanks = new List<GameObject> ();
        freeTanks = new List<GameObject> ();
        usedBullets = new List<GameObject> ();
        freeBullets = new List<GameObject> ();
        particles = new List<ParticleSystem> ();

        player_t = GameObject.Instantiate<GameObject> (player) as GameObject;
        player_t.SetActive (true);
        player_t.transform.position = Vector3.zero;
    }

    void Start () { Enemy.recycleEnemy += recycleEnemy; }

    // 获取玩家
    public GameObject getPlayer () {return player_t;}

    // 获取敌人
    public GameObject getEnemys () {
        GameObject newT = null;
        if (freeTanks.Count <= 0) {
            newT = GameObject.Instantiate<GameObject> (enemy) as GameObject;
            usedTanks.Add (newT);
            newT.transform.position = new Vector3 (Random.Range (-100, 100), 0, Random.Range (-100, 100));
        } else {
            newT = freeTanks[0];
            freeTanks.RemoveAt (0);
            usedTanks.Add (newT);
        }

        newT.SetActive (true);
        return newT;
    }

    // 获取子弹
    public GameObject getBullets (TankType type) {
        GameObject newBullet;
        if (freeBullets.Count <= 0) {
            newBullet = GameObject.Instantiate<GameObject> (bullet) as GameObject;
            usedBullets.Add (newBullet);
            newBullet.transform.position = new Vector3 (Random.Range (-100, 100), 0, Random.Range (-100, 100));
        } else {
            newBullet = freeBullets[0];
            freeBullets.RemoveAt (0);
            usedBullets.Add (newBullet);
        }

        newBullet.GetComponent<Bullet> ().setTankType (type);
        newBullet.SetActive (true);
        return newBullet;
    }

    // 获取粒子系统
    public ParticleSystem getParticleSystem () {
        foreach (var particle in particles){
            if (!particle.isPlaying) return particle;
        }

        ParticleSystem newT = GameObject.Instantiate<ParticleSystem> (explosion);
        particles.Add (newT);
        return newT;
    }

    // 回收坦克
    public void recycleEnemy (GameObject enemyTank) {
        usedTanks.Remove (enemyTank);
        freeTanks.Add (enemyTank);
        enemyTank.GetComponent<Rigidbody> ().velocity = Vector3.zero;
        enemyTank.SetActive (false);
    }

    // 回收子弹
    public void recycleBullet (GameObject Bullet) {
        usedBullets.Remove (Bullet);
        freeBullets.Add (Bullet);
        Bullet.GetComponent<Rigidbody> ().velocity = Vector3.zero;
        Bullet.SetActive (false);
    }
}

GameDirector

实现了总导演的设计,主要是得到实例化

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

// 总导演 √
public class GameDirector : System.Object {
    private static GameDirector _instance;
    public SceneController currentSceneController { get; set; }

    private GameDirector () { }

    public static GameDirector getInstance () {
        if (_instance == null) _instance = new GameDirector ();

        return _instance;
    }
}

IUserAction

实现了动作的接口;主要有控制玩家坦克的转动以及行进;控制射击;控制游戏是否结束

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

// 动作接口 √
public interface IUserAction {
    // 控制转动和行进
    void moveForWard ();
    void moveBackWard ();
    void turn (float turnX);
    // 控制射击
    void shoot ();
    // 控制游戏结束
    bool GameOver ();
}

IUserGUI

实现了游戏界面的设置;主要是设计了监听键盘的输入,来控制坦克的行进以及攻击的操作;并且在游戏结束后显示 Game Over!

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

// 设置界面 √
public class IUserGUI : MonoBehaviour {
    IUserAction action;

    void Start () {
        action = GameDirector.getInstance ().currentSceneController as IUserAction;
    }

    void Update () {
        if (!action.GameOver ()) {
            // 监听键盘
            if (Input.GetKey (KeyCode.W)) action.moveForWard ();
            if (Input.GetKey (KeyCode.S)) action.moveBackWard ();
            if (Input.GetKeyDown (KeyCode.Space)) action.shoot ();
            float turnX = Input.GetAxis ("Horizontal");

            action.turn (turnX);
        }
    }

    void OnGUI () {
        // 游戏结束
        if (action.GameOver ()) {
            GUIStyle font = new GUIStyle ();
            font.fontSize = 50;
            font.normal.textColor = Color.red;
            GUI.Label (new Rect (Screen.width / 2 - 100, Screen.height / 2 - 50, 200, 50), "Game Over!", font);
        }
    }
}

SceneController

实现了场景控制器。用来生产玩家和敌人;并且实现了第一视角的设置,将相机的位置始终跟随玩家;可以获取玩家的物体;控制玩家进行移动;控制玩家进行设计;返回游戏的状态并且可以设置游戏的状态

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

// 场景控制器 √
public class SceneController : MonoBehaviour, IUserAction {
    public GameDirector director;
    public GameObject player;

    private Factory myFactory;
    private GameObject[] enemies;
    private int enemyCount = 5;
    private bool gameOver = false;

    // 初始化
    private void Awake () {
        director = GameDirector.getInstance ();
        director.currentSceneController = this;

        myFactory = Singleton<Factory>.Instance;
        enemies = new GameObject[enemyCount];

        gameOver = false;
    }

    // 生产玩家和敌人 
    void Start () {
        player = myFactory.getPlayer ();
        for (int i = 0; i < enemyCount; ++i) enemies[i] = myFactory.getEnemys ();
        Player.destroyEvent += setGameOver;
    }

    // 设置相机的位置
    void Update () {
        Camera.main.transform.position = new Vector3 (player.transform.position.x, 20, player.transform.position.z);
    }

    // 获取玩家游戏物体
    public GameObject getPlayer () { return player; }

    // 控制玩家移动
    public void moveForWard () { player.GetComponent<Player> ().moveForWard (); }
    public void moveBackWard () { player.GetComponent<Player> ().moveBackWard (); }
    public void turn (float turnX) { player.GetComponent<Player> ().turn (turnX); }

    // 控制玩家射击
    public void shoot () { player.GetComponent<Player> ().shoot (TankType.PLAYER); }

    // 返回游戏状态
    public bool GameOver () { return gameOver; }
    // 游戏结束
    public void setGameOver () { gameOver = true; }
}

IUserGUISceneControllerFactory挂载到相机上
在这里插入图片描述

Singleton

实现了单实例模式

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

// 场景单实例模式 √
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
    protected static T instance;
    public static T Instance {
        get {
            if (instance == null) instance = (T) FindObjectOfType (typeof (T));

            return instance;
        }
    }
}

Tank

实现了坦克的相关属性;主要设置了坦克初始的生命值为100;并且设置了 get/set 方法。以及设置被攻击后生命值的减少;射击子弹射击的方向以及力度

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

// 设置坦克相关属性 √
public class Tank : MonoBehaviour {
    private float health = 100.0f;

    // 初始化生命值
    public Tank () { health = 100.0f; }

    // 设置/获取生命值
    public void setHP (float health) { this.health = health; }
    public float getHP () { return health; }

    // 被攻击
    public void Attacked () { health -= 20; }

    // 设置子弹射击
    public void shoot (TankType type) {
        GameObject bullet = Singleton<Factory>.Instance.getBullets (type);

        // 设置子弹射的方向
        bullet.transform.position = new Vector3 (transform.position.x, 1.5f, transform.position.z) + transform.forward * 1.5f;
        bullet.transform.forward = transform.forward;
        bullet.GetComponent<Rigidbody> ().AddForce (bullet.transform.forward * 20, ForceMode.Impulse);
    }
}

8. 导航

之前已经给玩家和敌方坦克添加了Nav Mesh Agent组件,在菜单栏点击窗口-AI-导航,在右侧窗口点击烘培,点击下方Bake按钮,生成导航网格图
在这里插入图片描述
在这里插入图片描述

游戏说明

玩家通过键盘上的wsad按键控制坦克的移动,玩家是蓝色坦克,敌人是绿色坦克,当玩家暴露在敌人的视野中,敌人就会跟踪玩家,并向玩家发送子弹,玩家要尽可能躲避子弹,按空格键可以向敌人发射子弹。每个坦克都有自己的生命值,当生命值小于零时会被销毁,当玩家坦克被销毁时,游戏结束。

实现效果
  • 游戏地图
    在这里插入图片描述
  • 游戏进行中
    在这里插入图片描述
  • 游戏结束
    在这里插入图片描述
视频效果

视频效果

项目地址

项目地址

Assets文件夹下载到本地,然后直接通过unity打开即可成功运行,或者新建一个项目,用Assets覆盖原来的文件夹,打开后即可正常运行。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值