用unity自己制作连连看小教程
周末有空,我花了些时间完善了之前做的连连看游戏,顺便写个教程分享给大家。 之前上传的工程文件和exe包已更新,增加了画线功能,还修改了边缘不能拐点的问题。
连通的类型
连连看的连通类型主要有以下三种:
- 直线型:分为横向和竖向两种情况。
- 一个拐点:两点的x坐标和y坐标都不同时,可能通过一个拐点连接。
- 两个拐点:在不符合直线和一个拐点连接的情况下,考虑两个拐点的连接方式。
直线型
直线型连接分为横向和竖向:
- 横向连接:当A、B两点的x坐标相同,且图片类型相同时,从A点开始到B点进行检测。若AB两点之间没有其他图片,则销毁AB两个图片。
- 竖向连接:与横向连接类似,当A、B两点的y坐标相同,且图片类型相同时,从A点开始到B点进行检测。若AB两点之间没有其他图片,则销毁AB两个图片。
一个拐点
当AB两点的x坐标和y坐标都不相同的时候,开始检测一个拐点是否可以连接。通过AB两点计算出C、D两点,然后分别检测AC、BC、AD、BD是否可以通过直线型连接到一起。若满足条件,则AB两点可以通过A > C,C > B的方式连接到一起。
两个拐点
对于AB两点,从A开始,横向和竖向找出所有和A能直线连接的点,用这些点和B点做一个拐点的检测。例如,A点下边的某个点可以通过绿色的那个点以一个拐点的方式和B点连接起来。 以上就是连连看的核心内容。接下来,我将以注释的形式详细介绍各个脚本的作用。
脚本介绍
GameManager.cs
游戏的核心代码,负责产生图片、判断是否可以销毁等操作。
using UnityEngine; using System.Collections; using System.Collections.Generic; public class GameManager : MonoBehaviour { public DrawLine drawLine; // 画线 public GameObject tilePrefab; // tile的预制 public List<Tile> tiles; // 开始实例化的时候存放tile public List<Tile> _tiles; // 存放随机摆放的tile public List<Tile> tilesEdge; // 为了边界处可以有拐点,把棋盘四周的tile放在这里,这里的tile看不到 public int x, y; // 棋牌的大小,两个数必须是偶数 private Tile tileA; private Tile tileB; private bool destroy; private Vector3 mousePos; private enum stepType // 控制游戏的状态 { one, two, three } private stepType _stepType; void Start() { this.gameObject.transform.position = Vector3.zero; Spawn(); _stepType = stepType.one; } private void Spawn() // 实例化tile { float num = (x * y - (2 * x + 2 * y - 4)) * 0.5f; for (int i = 0; i < num; i++) { int idTex = Random.Range(20, 45); GameObject obj = Instantiate(tilePrefab) as GameObject; GameObject obj2 = Instantiate(tilePrefab) as GameObject; Tile tile = obj.GetComponent<Tile>(); Tile tile2 = obj2.GetComponent<Tile>(); tile.Init(idTex); tile2.Init(idTex); tiles.Add(tile); tiles.Add(tile2); } for (int i = 0; i < ((2 * x + 2 * y) - 4); i++) // 实例化边缘的tile { GameObject obj = Instantiate(tilePrefab) as GameObject; obj.name = "edage"; Tile tile = obj.GetComponent<Tile>(); tilesEdge.Add(tile); } CreatTile(); for (int i = 0; i < _tiles.Count; i++) { _tiles[i].transform.name = i.ToString(); _tiles[i].id = i; } this.transform.position = new Vector3(-(x / 2.0f - 0.5f), -(y / 2.0f - 0.5f), 0); } private void CreatTile() // 随机摆放tile,如果是边缘的就放在边缘位置 { int idTex = 0; float _y = 0.0f; for (int i = 0; i < y; i++) { float _x = 0.0f; for (int j = 0; j < x; j++) { if (i == 0 j == 0 i == y - 1 j == x - 1) { tilesEdge[0].transform.position = new Vector3(_x, _y, 0); tilesEdge[0].pos = new Vector2(_x, _y); tilesEdge[0].transform.rotation = new Quaternion(0, 0, 180, 0); tilesEdge[0].transform.parent = this.gameObject.transform; _tiles.Add(tilesEdge[0]); tilesEdge[0].transform.localScale = Vector3.zero; tilesEdge[0].type = false; tilesEdge.RemoveAt(0); } else { int id = Mathf.FloorToInt(Random.Range(0, tiles.Count)); tiles[id].transform.position = new Vector3(_x, _y, 0); tiles[id].pos = new Vector2(_x, _y); tiles[id].transform.rotation = new Quaternion(0, 0, 180, 0); tiles[id].transform.parent = this.gameObject.transform; _tiles.Add(tiles[id]); tiles.RemoveAt(id); } _x += 1; } _y += 1; } } private void SelectTile() // 开始选择图片,通过射线方式选中,如果tileA和tileB不相同,则tileA等于tileB开始下一个检测 { Ray ray = Camera.main.ScreenPointToRay(mousePos); RaycastHit hit; int mask = 1 << 8; if (Physics.Raycast(ray, out hit, mask)) { if (tileA == null) { tileA = hit.transform.GetComponent<Tile>(); tileA.SetTileTexture(1); } else { tileB = hit.transform.GetComponent<Tile>(); tileB.SetTileTexture(1); Compare(tileA, tileB); if (tileA == null tileB == null) { // print("a and b is null"); } } } } private void Compare(Tile tile1, Tile tile2) // 比较两个点是否可以连接到一起 { // same card _stepType = stepType.one; drawLine.waypoints.Add(tile1.transform); // 第一个选择的tile是画线的起始位置 drawLine.transform.position = tile1.transform.position; destroy = false; print("compare"); if (tile1.pos.x == tile2.pos.x && tile1.pos.y == tile2.pos.y) // 如果选中的是同一个图片返回 { tileA.SetTileTexture(0); tileA = tileB; tileB = null; return; } else if (tile1.pos.x == tile2.pos.x && tile1.pos.y != tile2.pos.y) // 如果两点的x相等,竖向检测 { print("check y"); destroy = CheckY(tile1, tile2); if (destroy) drawLine.waypoints.Add(tile2.transform); } else if (tile1.pos.x != tile2.pos.x && tile1.pos.y == tile2.pos.y) // 如果两点的y相等,横向检测 { print("check x"); destroy = CheckX(tile1, tile2); if (destroy) drawLine.waypoints.Add(tile2.transform); } if (!destroy) // 不符合直线连接方式的开始进行一个拐点的检测 { _stepType = stepType.two; destroy = CheckTwoStep(tile1, tile2); print("check two step"); if (!destroy) // 不符合直线和一个拐点检测的开始进行两个拐点的检测 { _stepType = stepType.three; destroy = CheckThreeStep(tile1, tile2); print("check three:" + destroy); print("tile1.idTex:" + tile1.idTex + " tile1.idTex:" + tile1.idTex); } } if (destroy) // 如果符合销毁条件销毁图片,并开始画线 { tile1.transform.localScale = Vector3.zero; tile2.transform.localScale = Vector3.zero; tile1.type = false; tile2.type = false; tileA = null; tileB = null; drawLine.MoveToWaypoint(); } else // 不符合的话,清除画线中的路径 { drawLine.ClearPath(); tileA.SetTileTexture(0); tileA = tileB; tileB = null; return; } } // one step横向检测 private bool CheckX(Tile a, Tile b) { bool compare = true; int _min, _max; if (a.idTex == b.idTex) { CompareID(a, b, out _min, out _max); _min += 1; if (_min == _max) return true; for (int i = _min; i < _max; i++) { if (_tiles[i].type == true) { compare = false; break; } } return compare; } else return false; } // 竖向检测 private bool CheckY(Tile a, Tile b) { bool compare = true; int _min, _max; if (a.idTex == b.idTex) { CompareID(a, b, out _min, out _max); _min += x; if (_min == _max) return true; for (int i = _min; i < _max; i += x) { if (_tiles[i].type == true) { compare = false; break; } } return compare; } else return false; } // two step一个拐点的检测 private bool CheckTwoStep(Tile a, Tile b) { if (a.pos.x == b.pos.x a.pos.y == b.pos.y) return false; int id1 = (int)(a.pos.y * x + b.pos.x); if (_tiles[id1].type == false) { _tiles[id1].idTex = a.idTex; if (CheckY(_tiles[id1], b)) { if (CheckX(a, _tiles[id1])) { if (_stepType == stepType.two) { drawLine.waypoints.Add(_tiles[id1].transform); drawLine.waypoints.Add(b.transform); } else if (_stepType == stepType.three) { drawLine.waypoints.Add(_tiles[id1].transform); print("=====================:" + 1); } return true; } } } int id2 = (int)(b.pos.y * x + a.pos.x); if (_tiles[id2].type == false) { _tiles[id2].idTex = b.idTex; if (CheckY(a, _tiles[id2])) { if (CheckX(b, _tiles[id2])) { if (_stepType == stepType.two) { drawLine.waypoints.Add(_tiles[id2].transform); drawLine.waypoints.Add(b.transform); } else if (_stepType == stepType.three) { drawLine.waypoints.Add(_tiles[id2].transform); print("=====================:" + 2); } return true; } } } return false; } // three step两个拐点的检测 private bool CheckThreeStep(Tile a, Tile b) { print("a:" + a.idTex + " b:" + b.idTex); bool returnValue = false; print("returnValue:" + returnValue); List<Tile> _comparrPointsB; ComparePoints(b, out _comparrPointsB); // 返回b点可以横竖直线相连的点 for (int i = 0; i < _comparrPointsB.Count; i++) { returnValue = CheckTwoStep(a, _comparrPointsB[i]); if (returnValue) { drawLine.waypoints.Add(_comparrPointsB[i].transform); drawLine.waypoints.Add(b.transform); return returnValue; } } if (!returnValue) { List<Tile> _comparrPointsA; ComparePoints(a, out _comparrPointsA); print(a.name); print(b.name); for (int i = 0; i < _comparrPointsA.Count; i++) { print("--------------" + b.idTex); returnValue = CheckTwoStep(b, _comparrPointsA[i]); if (returnValue) { drawLine.waypoints.Add(_comparrPointsA[i].transform); drawLine.waypoints.Add(b.transform); return returnValue; } } } return returnValue; } // 两个拐点的时候返回可以与a横竖直线相连的点 private void ComparePoints(Tile a, out List<Tile> comparePoints) { print("a.idtex" + a.idTex); comparePoints = new List<Tile>(); comparePoints.Clear(); for (int i = (int)a.pos.y - 1; i > -1; i--) { int id = (int)(i * x + a.pos.x); if (_tiles[id].type == false) { comparePoints.Add(_tiles[id]); _tiles[id].idTex = a.idTex; print("_tiles [id].idTex = a.idTex; " + _tiles[id].idTex); } else break; } for (int i = (int)a.pos.y + 1; i < y; i++) { int id = (int)(i * x + a.pos.x); if (_tiles[id].type == false) { comparePoints.Add(_tiles[id]); _tiles[id].idTex = a.idTex; print("_tiles [id].idTex = a.idTex; " + _tiles[id].idTex); } else break; } for (int i = (int)a.pos.x - 1; i > -1; i--) { int id = (int)(a.pos.y * x + i); if (_tiles[id].type == false) { comparePoints.Add(_tiles[id]); _tiles[id].idTex = a.idTex; print("_tiles [id].idTex = a.idTex; " + _tiles[id].idTex); } else break; } for (int i = (int)a.pos.x + 1; i < x; i++) { int id = (int)(a.pos.y * x + i); if (_tiles[id].type == false) { comparePoints.Add(_tiles[id]); _tiles[id].idTex = a.idTex; print("_tiles [id].idTex = a.idTex; " + _tiles[id].idTex); } else break; } } private void CompareID(Tile a, Tile b, out int min, out int max) { if (a.id < b.id) { min = a.id; max = b.id; } else { min = b.id; max = a.id; } } Vector2 TexSize() { Vector2 size = new Vector2(1 / x, 1 / y); return size; } Vector2 TexOffset(int _idTex) { int a = (int)(_idTex / x); int b = (int)(_idTex % x); Vector2 offset = new Vector2(b / x, (y - 1 - a) / y); return offset; } void Update() { if (Input.GetMouseButtonUp(0)) { mousePos = Input.mousePosition; SelectTile(); } } private void ClearTiles(List<Tile> tiles) { tiles.Clear(); } }
DrawLine.cs
画线脚本,使用了iTween。
using UnityEngine; using System.Collections; using System.Collections.Generic; public class DrawLine : MonoBehaviour { public List<Transform> waypoints = new List<Transform>(); public float rate = 1; private int currentWaypoint = 1; public void MoveToWaypoint() { print("public void MoveToWaypoint (): " + waypoints.Count); StartCoroutine("move"); } public void ClearPath() { waypoints.Clear(); print("path.Clear ();"); } IEnumerator move() { for (int i = 0; i < waypoints.Count; i++) { iTween.MoveTo(this.gameObject, waypoints[i].position, rate); print("now id:" + i); yield return new WaitForSeconds(rate); } waypoints.Clear(); } }
Tile.cs
using UnityEngine; using System.Collections; public class Tile : MonoBehaviour { public int id; public int idTex; // 通过这个判断两个图片是否相同 public Vector2 pos; public bool type = true; // 控制图片的状态,当销毁的时候为false,其他判断的时候可以通过该点 public float x, y; public Texture texA, texB; // 鼠标选中的时候可以换贴图 public GameObject mask; // 鼠标选中的时候上边显示的框框 public void Init(int _idTex) { idTex = _idTex; Vector2 offset = TexOffset(_idTex); this.renderer.material.SetTextureOffset("_MainTex", offset); this.renderer.material.SetTextureScale("_MainTex", new Vector2(0.2f, 0.1f)); } // 设置tile显示的贴图和大小 public void SetTileTexture(int i) { if (i == 0) { this.renderer.material.mainTexture = texA; mask.transform.localScale = Vector3.zero; } if (i == 1) { this.renderer.material.mainTexture = texB; mask.transform.localScale = new Vector3(0.1380835f, 0.1380835f, 0.1380835f); } } // 这个就是裁剪一张大图,变成一个个小的,贴到tile上 Vector2 TexOffset(int _idTex) { int a = (int)(_idTex / x); int b = (int)(_idTex % x); Vector2 offset = new Vector2(b / x, (y - 1 - a) / y); return offset; } Vector2 TexSize() { Vector2 size = new Vector2(1 / x, 1 / y); return size; } }
Menu.cs
添加了两个按钮,可以重新开始游戏。
using UnityEngine; using System.Collections; public class Menu : MonoBehaviour { public GameManager gameManager; private GameManager _gameManger; private bool start = true; void OnGUI() { if (start) { if (GUI.Button(new Rect(10, 10, 100, 50), "start")) { start = false; _gameManger = Instantiate(gameManager) as GameManager; } } if (GUI.Button(new Rect(10, 70, 100, 50), "restart")) { if (_gameManger != null) { Destroy(_gameManger.gameObject); print("Destroy(_gameManger.gameObject);"); } _gameManger = Instantiate(gameManager) as GameManager; } } }
通过以上脚本,我们可以实现一个简单的连连看游戏。希望这个教程对你有所帮助!