吃豆人(pacman)游戏需要实现功能:
- 游戏场景的制作
- pacman的动画控制以及移动,豆子制作以及吃掉沿途的豆子。
- 敌人的移动及其的巡逻逻辑,制作假的AI,吃掉玩家。
- 游戏场景的特殊功能:超级豆子的制作及其逻辑(当pacman吃到超级豆子,暂停敌人的移动,此时pacman可以吃掉敌人让敌人回到初始位置)。
- UI设计(游戏开始界面,游戏计分板,游戏gameover界面,游戏win界面)
- 以上的实现需要GameManager脚本控制管理实现
1.游戏场景的制作
准备一张地图(Maze)拖入场景中,在Inspector面板中为每个墙重复添加组件“Box Collider 2D”,设置Transfrom中的position(0,0,0)
Pacman动画控制以及移动
将pacman拖入场景中,在其Inspector面板中添加PacmanMove脚本,Animator,Circle Collider 2D,Rigibody 2D。
在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)向下的动画。
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)
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”的巡逻轨迹。注意:每个标记位置都必须按照顺序排放,不然会出现敌人穿墙的现象。
敌人的制作(Enemy):这里制作四个敌人,每个敌人身上的组件都是一样的Animator,GhostMove脚本,Circle Collider 2D(勾选:Is Trigger),Rigibody 2D。
注意:敌人身上的Animator.Controller和pacman身上的一样。但为了避免重复操作,Unity为我们提供了复写的方法。复写四个不同的Animation.Contoller并赋值
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
接下来就是最重要的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 }