草庐IT

unity实现坦克对战

weixin_51376828 2023-12-23 原文

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覆盖原来的文件夹,打开后即可正常运行。

有关unity实现坦克对战的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  3. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  4. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  5. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  6. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

随机推荐