吃豆人(pacman)游戏需要实现功能:

  1. 游戏场景的制作
  2. pacman的动画控制以及移动,豆子制作以及吃掉沿途的豆子。
  3. 敌人的移动及其的巡逻逻辑,制作假的AI,吃掉玩家。
  4. 游戏场景的特殊功能:超级豆子的制作及其逻辑(当pacman吃到超级豆子,暂停敌人的移动,此时pacman可以吃掉敌人让敌人回到初始位置)。
  5. UI设计(游戏开始界面,游戏计分板,游戏gameover界面,游戏win界面)
  6. 以上的实现需要GameManager脚本控制管理实现

1.游戏场景的制作

准备一张地图(Maze)拖入场景中,在Inspector面板中为每个墙重复添加组件“Box Collider 2D”,设置Transfrom中的position(0,0,0)

unity大量敌人优化_System

 


Pacman动画控制以及移动

将pacman拖入场景中,在其Inspector面板中添加PacmanMove脚本,Animator,Circle Collider 2D,Rigibody 2D。

unity大量敌人优化_System_02

 

 

 在Animator面板中创建动画逻辑,添加两个float类型的变量DirX,DirY。当Pacman向左方向移动(DirX<-0.1,DirY=0)播放向左的动画,Pacman向右方向移动(DirX>0.1,DirY=0)播放向左的动画

当Pacman向上方向移动(DirX=0,DirY>0.1)播放向上的动画,Pacman向下方向移动(DirX=,DirY<-0.1)向下的动画。

unity大量敌人优化_unity大量敌人优化_03

 

 

 

unity大量敌人优化_List_04

 PacmanMove代码:吃豆人移动

1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 public class PacmanMove : MonoBehaviour
 6 {
 7     public float speed = 0.35f;
 8     private Vector2 dest = Vector2.zero;//目标位置
 9     public GameObject Object;
10     private bool isMove=true;
11     private void Start()
12     {
13         dest = transform.position;//获取初始坐标
14 
15     }
16 
17     private void FixedUpdate()//每一帧都调用一次
18     {
19         //pacman移动方法
20         Vector2 temp= Vector2.MoveTowards(transform.position, dest, speed);//MoveTowards(Vector2 current,Vector2 target,float maxDistanceDelta)
21         GetComponent<Rigidbody2D>().MovePosition(temp);//通过刚体组件来移动pacman
22 
23         //必须先达到上一个dest的位置才可以发出新的目的地设置指令
24         if ((Vector2)transform.position != dest&&isMove==true ) 
25         {
26             Attack();
27             isMove = false;
28             
29         }
30         if ((Vector2)transform.position == dest)//当前位置到达目标位置
31         {
32             
33             if ((Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W))&&Valid(Vector2.up))
34             {
35                 dest = (Vector2)transform.position + Vector2.up;
36                 
37             }
38             if ((Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S)) && Valid(Vector2.down))
39             {
40                 dest = (Vector2)transform.position + Vector2.down;
41                 
42             }
43             if ((Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A) )&& Valid(Vector2.left))
44             {
45                 dest = (Vector2)transform.position + Vector2.left;
46                 
47             }
48             if ((Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D))&& Valid(Vector2.right))
49             {
50                 dest = (Vector2)transform.position + Vector2.right;
51                 
52             }
53            //目标位置-当前位置 获得正负值判断移动的方向 控制动画
54             Vector2 dir = dest - (Vector2)transform.position;
55             GetComponent<Animator>().SetFloat("DirX", dir.x);
56             GetComponent<Animator>().SetFloat("DirY", dir.y);
57             isMove = true;
58         }
59         
60     }
61 
62     //通过射线检测的方式“目标位置”发出的射线到“当前位置”的collider是否为pacman自身的collider
63     //如果是自身的collider那么返回为真,不是则为false。此方法目的是为了防止pacman卡死不动
64     private bool Valid(Vector2 dir)
65     {
66         Vector2 pos = transform.position;
67         RaycastHit2D hit = Physics2D.Linecast(pos + dir, pos);
68         return (hit.collider != Object.GetComponent<Collider2D>());
69     }
70    private void Attack()
71    {
72         dest = transform.position;
73    }
74 }

豆子的制作:这个是个无脑操作,只能手动将一个个豆子放在地图(Maze)下作为子物体,为每一个pacdot添加Pacdot脚本,Box Collider 2D组件(勾选:Is Trriger)

unity大量敌人优化_System_05

 

 

 Pacdot代码:

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

public class Pacdot : MonoBehaviour
{
    public bool isSuperPacdot = false;//判断是否为超级豆子,默认为false
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Pacman")//判断碰撞的物体的“名字”==“Pacman”
        {
            if (isSuperPacdot)//当是超级豆子时
            {
                
                GameManager.Instance.OnEatSuperPacdot();//调用GameManager脚本中的OnEatSuperPacdot方法
                Destroy(gameObject);//销毁此物体
            }
            else
            {
                GameManager.Instance.OnEatPacdot(gameObject);//调用GameManager脚本中的OnEatPacdot(gameobject)方法
                Destroy(gameObject);//销毁此物体
            }
            
        }
    }
}

敌人的移动:

敌人的移动制作的时候先考虑制作一个敌人的移动轨迹,因为要制作一个假的AI功能,所以代码的逻辑要比pacman的要复杂一些。既然是假的AI功能,那么理所当然

敌人的巡逻轨迹都是开发者事先布置好的。首先创建一个空物体名为“WayPoints”在其下面添加子物体标记每个移动的位置,然后将其作为预制体放入prefabs文件夹中

这里建议做四个或者以上不同的“WayPoints”的巡逻轨迹。注意:每个标记位置都必须按照顺序排放,不然会出现敌人穿墙的现象。

unity大量敌人优化_List_06

 

 

 敌人的制作(Enemy):这里制作四个敌人,每个敌人身上的组件都是一样的Animator,GhostMove脚本,Circle Collider 2D(勾选:Is Trigger),Rigibody 2D。

注意:敌人身上的Animator.Controller和pacman身上的一样。但为了避免重复操作,Unity为我们提供了复写的方法。复写四个不同的Animation.Contoller并赋值

unity大量敌人优化_System_07

unity大量敌人优化_System_08

 

 

unity大量敌人优化_unity大量敌人优化_09

 GhostMove代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GhostMove : MonoBehaviour
{
    public GameObject[] wayPointsGo;//将所有的“路径”作为
    private List<Vector3> wayPoints = new List<Vector3>();//存放“路径点"
    public float speed = 0.2f;
    private int index = 0;
    private Vector3 startPos;
    private void Start()
    {
        startPos = transform.position + new Vector3(0, 3, 0);//设置初始路径点

        //通过层级的方式分配给敌人不同的“路径”避免敌人叠加走相同的路径
        LoadPath(wayPointsGo[GameManager.Instance.usingIndex[GetComponent<SpriteRenderer>().sortingOrder - 2]]);
    }
    private void FixedUpdate()
    {
        //敌人自动巡逻逻辑---------------------------------
        if (transform.position != wayPoints[index])
        {
            Vector2 temp = Vector2.MoveTowards(transform.position, wayPoints[index], speed);
            GetComponent<Rigidbody2D>().MovePosition(temp);    
        }
        else
        {
            index++;
            if (index >= wayPoints.Count)
            {
                index = 0;//当巡逻到最后一个位置点时,位置点归零,从头开始巡逻
                LoadPath(wayPointsGo[Random.Range(0, wayPointsGo.Length)]);//随机加载“路径”
            }
        }
        //--------------------------------------------------
        //方向
        Vector2 dir = wayPoints[index] - transform.position;
        GetComponent<Animator>().SetFloat("DirX", dir.x);
        GetComponent<Animator>().SetFloat("DirY", dir.y);
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.name == "Pacman")
        {
            if (GameManager.Instance.isSuperPacman)//如果碰到超级吃豆人,将敌人送回出生点,重新开始巡逻
            {
                
                transform.position = startPos - new Vector3(0, 3, 0);
                index = 0;
                GameManager.Instance.score += 500;//分数添加500分
            }
            else
            {
                collision.gameObject.SetActive(false);
                GameManager.Instance.gamePanel.SetActive(false);//隐藏游戏面板
                Instantiate(GameManager.Instance.gameOverPrefab);//实例化游戏结束预制体
                Invoke("Restart", 3f);//延迟三秒调用重开
            }
            
        }
    }

    //加载路径
    private void LoadPath(GameObject go)
    {
        wayPoints.Clear();
        foreach (Transform t in go.transform)//遍历父物体下的子物体的Transfrom组件,将所有路径点添加到“路径点”中
        {
            wayPoints.Add(t.position);
        }
        wayPoints.Insert(0, startPos);//Insert(int,vector3)插入(位置,向量位置)
        wayPoints.Add(startPos);
        
    }

    private void Restart()//重新加载场景
    {
        SceneManager.LoadScene(0);
    }
}

游戏玩法功能设计: (生成超级豆子,超级吃豆人,游戏胜利,游戏结束)

玩法描述:吃豆人吃了超级豆子称为超级吃豆人,可以冻结并吃掉敌人,但是有时间限制,时间结束变为普通吃豆人。当吃豆人吃完所有的豆子游戏胜利,当吃豆人被敌人消灭则游戏结束

这里要先创建两个UI一个是GamePanel和StartPanel

unity大量敌人优化_List_10

 

unity大量敌人优化_System_11

 接下来就是最重要的GameManager脚本,控制着整个游戏的逻辑,废话不多说上代码好好体会吧

 GameManager代码:

1 using System.Collections;
  2 using System.Collections.Generic;
  3 using UnityEngine;
  4 using UnityEngine.UI;
  5 using UnityEngine.SceneManagement;
  6 
  7 public class GameManager : MonoBehaviour
  8 {
  9     private static GameManager _instance;
 10     public static GameManager Instance
 11     {
 12         get
 13         {
 14             return _instance;
 15         }
 16     }
 17     public GameObject pacman;//主角
 18     public GameObject binky;//敌人1
 19     public GameObject clyde;//敌人2
 20     public GameObject inky;//敌人3
 21     public GameObject pinky;//敌人4
 22 
 23     public GameObject startPanel;//开始面板
 24     public GameObject gamePanel;//结束面板
 25     public GameObject gameOverPrefab;//结束动画预制体
 26     public GameObject winPrefab;//胜利动画预制体
 27     public GameObject StartCountDownPrefab;//倒计时动画预制体
 28     public AudioClip startClip;//开始音效
 29 
 30     public Text nowText;//文本:还剩多少豆子
 31     public Text remainText;//文本:吃掉多少豆子
 32     public Text scoreText;//文本:分数
 33 
 34     private int pacdotNum=0;
 35     private int nowEat = 0;
 36     public  int score = 0;
 37 
 38     public bool isSuperPacman=false;
 39     public List<int> usingIndex = new List<int>();//使用了的路径
 40     public List<int> rawIndex = new List<int> { 0,1,2,3};
 41     private List<GameObject> pacdotGos = new List<GameObject>();
 42     private void Awake()
 43     {
 44 
 45         _instance = this;
 46         int tempCount = rawIndex.Count;
 47         for(int i = 0; i < tempCount; i++)
 48         {
 49             int tempIndex = Random.Range(0, rawIndex.Count);
 50             usingIndex.Add(rawIndex[tempIndex]);
 51             rawIndex.RemoveAt(tempIndex);
 52         }
 53         foreach(Transform t in GameObject.Find("Maze").transform)//遍历“Maze”
 54         {
 55             pacdotGos.Add(t.gameObject);
 56         }
 57         pacdotNum = GameObject.Find("Maze").transform.childCount;
 58     }
 59 
 60     private void Start()
 61     {
 62         SetGameState(false);
 63     }
 64     private void Update()
 65     {
 66         //游戏胜利时(吃掉的豆子数=总豆子数 且 pacman为true时)
 67         if (nowEat == pacdotNum&&pacman.GetComponent<PacmanMove>().enabled!=false)
 68         {
 69             gamePanel.SetActive(false);
 70             Instantiate(winPrefab);
 71             StopAllCoroutines();//停止所有的协程
 72             
 73         }
 74         if (nowEat == pacdotNum)
 75         {
 76             if (Input.anyKey)
 77             {
 78                 SceneManager.LoadScene(0);
 79             }
 80         }
 81         if (gamePanel.activeInHierarchy)//gamePanel是否处于激活状态
 82         {
 83             remainText.text = "Remain:\n\n" + (pacdotNum - nowEat);
 84             nowText.text="Eaten:\n\n"+nowEat;
 85             scoreText.text = "Score:\n\n" + score;
 86         }
 87         
 88     }
 89     public void OnStartButton()//开始按钮
 90     {
 91         StartCoroutine(PlayStartCountDown());
 92         AudioSource.PlayClipAtPoint(startClip, new Vector3(0,0,-5));
 93         startPanel.SetActive( false);
 94         
 95     }
 96 
 97     public void OnExitButton()//退出按钮
 98     {
 99         Application.Quit();
100     }
101 
102     IEnumerator PlayStartCountDown()//在游戏开始按钮按下时,实例化倒计时动画,四秒后销毁
103     {
104         GameObject go = Instantiate(StartCountDownPrefab);
105         yield return new WaitForSeconds(4f);
106         Destroy(go);
107         SetGameState(true);
108         Invoke("CreateSuperPacdot", 10f);
109         gamePanel.SetActive(true);
110         GetComponent<AudioSource>().Play();
111         
112     }
113     //吃豆子
114     public void OnEatPacdot(GameObject go)
115     {
116         nowEat++;
117         score += 100;
118         pacdotGos.Remove(go);
119     }
120 
121     //当吃掉超级豆子,冻结敌人
122     public void OnEatSuperPacdot()
123     {
124         Invoke("CreateSuperPacdot", 10f);
125         score += 200;
126         isSuperPacman = true;
127         FrezzeEnemy();
128         StartCoroutine(RecoveryEnemy());
129     }
130     IEnumerator RecoveryEnemy()//敌人在被冻结时立刻计时,3s后恢复,此时调用协程
131     {
132         yield return new WaitForSeconds(3f);
133         DisFrezzeEnemy();
134         isSuperPacman = false;
135     }
136     //生成超级豆子
137     private void CreateSuperPacdot()
138     {
139         if (pacdotGos.Count < 5)
140         {
141             return;
142         }
143         int tempIndex = Random.Range(0, pacdotGos.Count);
144         pacdotGos[tempIndex].transform.localScale = new Vector3(3, 3, 3);
145         pacdotGos[tempIndex].GetComponent<Pacdot>().isSuperPacdot = true;
146     }
147 
148     //冻结鬼
149     private void FrezzeEnemy()
150     {
151         binky.GetComponent<GhostMove>().enabled = false;
152         clyde.GetComponent<GhostMove>().enabled = false;
153         inky.GetComponent<GhostMove>().enabled = false;
154         pinky.GetComponent<GhostMove>().enabled = false;
155         binky.GetComponent<SpriteRenderer>().color = new Color(0.7f,0.7f,0.7f,0.7f);
156         clyde.GetComponent<SpriteRenderer>().color = new Color(0.7f, 0.7f, 0.7f, 0.7f);
157         inky.GetComponent<SpriteRenderer>().color = new Color(0.7f, 0.7f, 0.7f, 0.7f);
158         pinky.GetComponent<SpriteRenderer>().color = new Color(0.7f, 0.7f, 0.7f, 0.7f);
159     }
160 
161     /// <summary>
162     /// 解冻鬼
163     /// </summary>
164     private void DisFrezzeEnemy()
165     {
166         binky.GetComponent<GhostMove>().enabled = true;
167         clyde.GetComponent<GhostMove>().enabled = true;
168         inky.GetComponent<GhostMove>().enabled = true;
169         pinky.GetComponent<GhostMove>().enabled = true;
170         binky.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
171         clyde.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
172         inky.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
173         pinky.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 1f);
174     }
175     //游戏状态
176     private void SetGameState(bool state)
177     {
178         pacman.GetComponent<PacmanMove>().enabled = state;
179         binky.GetComponent<GhostMove>().enabled = state;
180         clyde.GetComponent<GhostMove>().enabled = state;
181         inky.GetComponent<GhostMove>().enabled = state;
182         pinky.GetComponent<GhostMove>().enabled = state;
183     }
184 }