3D游戏设计作业9:游戏智能

坦克对战游戏 AI 设计

游戏截图:
在这里插入图片描述
1,作业要求

从商店下载游戏:“Kawaii” Tank 或 其他坦克模型,构建 AI 对战坦克。具体要求

  • 使用“感知-思考-行为”模型,建模 AI 坦克
  • 场景中要放置一些障碍阻挡对手视线
  • 坦克需要放置一个矩阵包围盒触发器,以保证 AI坦克能使用射线探测对手方位
  • AI 坦克必须在有目标条件下使用导航,并能绕过障碍。(失去目标时策略自己思考)
  • 实现人机对战

2,设计思路
主要分为7个步骤
1,坦克基类Tank.cs:血量,以及被攻击后血量改变的相关函数,还有开炮对应的操作
2,玩家类的实现:继承Tank类
3,AI坦克类的实现,也是要继承Tank类
4,子弹bullet类,有些要求是要注意的:设置发射子弹的坦克类型, 因为如果射到队友是不能算伤害的;如果是玩家发出的子弹伤害高一点,AI坦克的子弹伤害低一点
5,接着就是工厂类了,工厂类无非就是场景要“演员”的时候就生产出来,不需要的时候就回收
6,场景类:获得工厂实例,做好初始化工作,接受从UI类里传过来的要求实现对应的操作
7,最后就是UI类和导演Director类了,UI类就是检测玩家的输入,再对player坦克进行对应的操作

3,关键代码

坦克基类Tank:

public class Tank : MonoBehaviour {
     private float hp =500.0f;
    // 初始化
     public Tank()
     {
         hp = 500.0f;
     }

     public float getHP()
     {
         return hp;
     }

     public void setHP(float hp)
     {
         this.hp = hp;
     }
     //标明子弹所属的坦克类型,TankType是枚举变量,在工厂类里MyFactory定义。
     public void shoot(TankType type)
     {
         GameObject bullet = Singleton<MyFactory>.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);
     }
}

玩家类Player:

public class Player : Tank{
   // player被摧毁时发布信息;
   public delegate void DestroyPlayer();
   public static event DestroyPlayer destroyEvent;
   void Start () {
       setHP(500);
}

// Update is called once per frame
void Update () {
    if(getHP() <= 0)    // Tank is destoryed
       {
           this.gameObject.SetActive(false);
           destroyEvent();
       }
}

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

   //通过水平轴上的增量,改变玩家坦克的欧拉角,从而实现坦克转向
   public void turn(float offsetX)
   {
       float x = gameObject.transform.localEulerAngles.x;
       float y = gameObject.transform.localEulerAngles.y + offsetX*2;
       gameObject.transform.localEulerAngles = new Vector3(x, y, 0);
   }
}

AI坦克类Enemy:

public class Enemy : Tank {
   public delegate void RecycleEnemy(GameObject enemy);
   //当enemy被摧毁时,通知工厂回收;
   public static event RecycleEnemy recycleEnemy;
   // player 的位置
   private Vector3 playerLocation;
   //游戏是否结束
   private bool gameover;
   private void Start()
   {
       playerLocation = GameDirector.getInstance().currentSceneController.getPlayer().transform.position;
       StartCoroutine(shoot());
   }

   void Update() {
       playerLocation = GameDirector.getInstance().currentSceneController.getPlayer().transform.position;
       gameover = GameDirector.getInstance().currentSceneController.getGameOver();
       if (!gameover)
       {
           if (getHP() <= 0 && recycleEnemy != null)
           {
               recycleEnemy(this.gameObject);
           }
           else
           {
               // 自动向player移动
               NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();
               agent.SetDestination(playerLocation);
              // gameObject.transform.position = new Vector3(transform.position.x+0.01f,transform.position.y,transform.position.z);
           }
       }
       else
       {
           //游戏结束,停止寻路
           NavMeshAgent agent = gameObject.GetComponent<NavMeshAgent>();
           agent.velocity = Vector3.zero;
           agent.ResetPath();
       }
   }
   // 协程实现每隔1s进行射击,开始喜欢协程了
   IEnumerator shoot()
   {
       while (!gameover)
       {
           for(float i =1;i> 0; i -= Time.deltaTime)
           {
               yield return 0;
           }
           if(Vector3.Distance(playerLocation,gameObject.transform.position) < 14)
           {
               shoot(TankType.ENEMY);
           }
       }
   }
}

子弹类Bullet:

public class Bullet : MonoBehaviour {
    public float explosionRadius = 3.0f;
    private TankType tankType;

    //设置发射子弹的坦克类型, 因为如果射到队友是不能算伤害的.
    public void setTankType(TankType type)
    {
        tankType = type;
    }
    private void OnCollisionEnter(Collision collision)
    {
        if(collision.transform.gameObject.tag == "tankEnemy" && this.tankType == TankType.ENEMY ||
            collision.transform.gameObject.tag == "tankPlayer" && this.tankType == TankType.PLAYER)
        {
            return;
        }
        MyFactory factory = Singleton<MyFactory>.Instance;
        ParticleSystem explosion = factory.getParticleSystem();
        explosion.transform.position = gameObject.transform.position;
        //获取爆炸范围内的所有碰撞体
        Collider[] colliders = Physics.OverlapSphere(gameObject.transform.position, explosionRadius);

        foreach(var collider in colliders)
        {
            //被击中坦克与爆炸中心的距离
            float distance = Vector3.Distance(collider.transform.position, gameObject.transform.position);
            float hurt;
            // 如果是玩家发出的子弹伤害高一点
            if (collider.tag == "tankEnemy" && this.tankType == TankType.PLAYER)
            {
                hurt = 300.0f / distance;
                collider.GetComponent<Tank>().setHP(collider.GetComponent<Tank>().getHP() - hurt);
            }
            else if(collider.tag == "tankPlayer" && this.tankType == TankType.ENEMY)
            {
                hurt = 100.0f / distance;
                collider.GetComponent<Tank>().setHP(collider.GetComponent<Tank>().getHP() - hurt);
            }
            explosion.Play();
        }

        if (gameObject.activeSelf)
        {
            factory.recycleBullet(gameObject);
        }
    }

}

工厂类MyFactory:

//其中public的四个变量是用来在unity中将player,enemy,bullet,explosion预制拖进代码里用于实例化对象的。
//其它变量和方法应该看名称都可以看懂。
public enum TankType { PLAYER , ENEMY};
public class MyFactory : MonoBehaviour {

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

   private List<GameObject> usingTanks;
   private List<GameObject> freeTanks;
   private List<GameObject> usingBullets;
   private List<GameObject> freeBullets;
   private GameObject role;
   private List<ParticleSystem> particles;

   private void Awake()
   {
       usingTanks = new List<GameObject>();
       freeTanks = new List<GameObject>();
       usingBullets = new List<GameObject>();
       freeBullets = new List<GameObject>();
       particles = new List<ParticleSystem>();

       role = GameObject.Instantiate<GameObject>(player) as GameObject;
       role.SetActive(true);
       role.transform.position = Vector3.zero;
   }
   // Use this for initialization
   void Start () {
       Enemy.recycleEnemy += recycleEnemy;
   }

// Update is called once per frame
public GameObject getPlayer()
   {      
       return role;
   }

   public GameObject getEnemys()
   {
       GameObject newTank = null;
       if (freeTanks.Count <= 0)
       {
           newTank = GameObject.Instantiate<GameObject>(enemy) as GameObject;
           usingTanks.Add(newTank);
           newTank.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
       }
       else
       {
           newTank = freeTanks[0];
           freeTanks.RemoveAt(0);
           usingTanks.Add(newTank);
       }
       newTank.SetActive(true);
       return newTank;
   }

   public GameObject getBullets(TankType type)
   {
       GameObject newBullet;
       if(freeBullets.Count <= 0)
       {
           newBullet = GameObject.Instantiate<GameObject>(bullet) as GameObject;
           usingBullets.Add(newBullet);
           newBullet.transform.position = new Vector3(Random.Range(-100, 100), 0, Random.Range(-100, 100));
       }
       else
       {
           newBullet = freeBullets[0];
           freeBullets.RemoveAt(0);
           usingBullets.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 newPS = GameObject.Instantiate<ParticleSystem>(explosion);
       particles.Add(newPS);
       return newPS;
   }

   public void recycleEnemy(GameObject enemyTank)
   {
       usingTanks.Remove(enemyTank);
       freeTanks.Add(enemyTank);
       enemyTank.GetComponent<Rigidbody>().velocity = Vector3.zero;
       enemyTank.SetActive(false);
   }

   public void recycleBullet(GameObject Bullet)
   {
       usingBullets.Remove(Bullet);
       freeBullets.Add(Bullet);
       Bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;
       Bullet.SetActive(false);
   }
}

场景类SceneController:

// SceneController.cs
public class SceneController : MonoBehaviour,IUserAction{
    public GameObject player;
    private int enemyCount = 6;
    private bool gameOver = false;
    private GameObject[] enemys;
    private MyFactory myFactory;
    public GameDirector director;
    private void Awake()
    {
        director = GameDirector.getInstance();
        director.currentSceneController = this;
        enemys = new GameObject[enemyCount];
        gameOver = false;
        myFactory = Singleton<MyFactory>.Instance;

    }

    void Start () {
        player = myFactory.getPlayer();
        for (int i = 0; i < enemyCount; i++)
        {
            enemys[i]=myFactory.getEnemys();
        }
        Player.destroyEvent += setGameOver;
    }

    // Update is called once per frame
    void Update () {
        Camera.main.transform.position = new Vector3(player.transform.position.x, 18, player.transform.position.z);
    }

    //返回玩家坦克的位置
    public GameObject getPlayer()
    {
        return player;
    }

    //返回游戏状态
    public bool getGameOver()
    {
        return gameOver;
    }

    //设置游戏结束
    public void setGameOver()
    {
        gameOver = true;
    }

    public void moveForward()
    {
        player.GetComponent<Player>().moveForward();
    }
    public void moveBackWard()
    {
        player.GetComponent<Player>().moveBackWard();
    }

    //通过水平轴上的增量,改变玩家坦克的欧拉角,从而实现坦克转向
    public void turn(float offsetX)
    {
        player.GetComponent<Player>().turn(offsetX);
    }

    public void shoot()
    {
        player.GetComponent<Player>().shoot(TankType.PLAYER);
    }
}

//IUserAction.cs
public interface IUserAction
{
    void moveForward();
    void moveBackWard();
    void turn(float offsetX);
    void shoot();
    bool getGameOver();
}

UI类IUserGUI:

//IUserGUI.cs
public class IUserGUI : MonoBehaviour {
  IUserAction action;

    // Use this for initialization
    void Start () {
       action = GameDirector.getInstance().currentSceneController as IUserAction;
    }

// Update is called once per frame
    void Update () {
     if (!action.getGameOver())
     {
         if (Input.GetKey(KeyCode.W))
         {
             action.moveForward();
         }

         if (Input.GetKey(KeyCode.S))
         {
             action.moveBackWard();
         }


         if (Input.GetKeyDown(KeyCode.Space))
         {
             action.shoot();
         }
         //获取水平轴上的增量,目的在于控制玩家坦克的转向
         float offsetX = Input.GetAxis("Horizontal");
         action.turn(offsetX);
     }
}

 void OnGUI()
 {
     //gameover时生成提示
     if (action.getGameOver())
     {
         GUIStyle fontStyle = new GUIStyle();
         fontStyle.fontSize = 30;
         fontStyle.normal.textColor = new Color(0, 0, 0);
         GUI.Button(new Rect(Screen.width/2-50, Screen.height/2-50, 200, 50), "GameOver!");
     }
   }
}

导演Director类GameDirector:

//GameDirector.cs
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;
   }
}

4,实验总结
其他部分和之前的作业基本是一样的,重点是增加了电脑敌人的AI设计。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值