用unity自己制作连连看小教程

2015年03月15日 18:14 5 点赞 0 评论 更新于 2020-04-25 15:08

周末有空,我花了些时间完善了之前做的连连看游戏,顺便写个教程分享给大家。 之前上传的工程文件和exe包已更新,增加了画线功能,还修改了边缘不能拐点的问题。

连通的类型

连连看的连通类型主要有以下三种:

  1. 直线型:分为横向和竖向两种情况。
  2. 一个拐点:两点的x坐标和y坐标都不同时,可能通过一个拐点连接。
  3. 两个拐点:在不符合直线和一个拐点连接的情况下,考虑两个拐点的连接方式。

    直线型

    直线型连接分为横向和竖向:

    • 横向连接:当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;
      }
      }
      }
      

      通过以上脚本,我们可以实现一个简单的连连看游戏。希望这个教程对你有所帮助!

作者信息

boke

boke

共发布了 101 篇文章