吃豆人(pacman)游戏需要实现功能:
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 }
原文:https://www.cnblogs.com/mikoor/p/14047077.html