最近看到有人做的拼图游戏感觉很好玩。就在网上搜罗了一下关于unity3d拼图游戏的制作方法,在这也给大家分享一下。

今天写一个简单的拼图游戏实例来和大家分享一下Unity中material.mainTextureOffset和material.mainTextureScale的作用和用法
mainTextureOffset和mainTextureScale是Material类中的两个方法
mainTextureOffset指的是主纹理偏移
mainTextureScale指的是主纹理缩放
默认一个material的mainTextureOffset是0,0,mainTextureScale是1,1

表示原图正常显示,没有缩放,如下图

        

当我们改变mainTextureScale的值时纹理只会显示一部分,其范围是[0,1];

如我们将mainTextureScale.x设置为0.5时,可以看到纹理只显示u方向的50%

       

同理,我们将mainTextureScale.y设置为0.5时,可以看到纹理只显示v方向的50%

       

mainTextureOffset属性表示纹理的起始偏移,为0时没有偏移,mainTextureOffset.x指u方向的偏移量,mainTextureOffset.y指v方向的偏移量。其范围也是[0,1]。这里要注意:偏移的起点在图像的左下角

我们设置mainTextureOffset.x为0.2,如下图:

       

可以看到纹理向左偏移了20%

我们设置mainTextureOffset.y为0.2,如下图:

       

可以看到纹理向下偏移了20%

应用这两个属性,我们可以只截取图片的一部分来显示,如:
mainTextureOffset = new Vector2(0.5, 0.5);
mainTextureScale= new Vector2(0.5, 0.5);

将只显示原图的右上角1/4区域,如下图:

       

有了对以上两个属性的了解,我们可以来制作一个简单的拼图游戏
思路:
1.将一张图片切分为raw*volumn的raw*volumn张碎片
2.每一张碎片只显示图片的一部分
3.将它们按一个顺序和位置排列,使其看起来像一张完整的图片
玩法:
点击Start按钮后,开始游戏。选中碎片并将其拖放在正确的位置上,如放置正确则不可再被拖动。直到所有碎片放置正确。
点击Next Texture按钮可切换背景和碎片显示的图片
制作流程:

新建一个游戏场景Test,设置摄像机属性如下(采用正交摄像机(2D)将其标签设置MainCamera):

                   

创建两个plane,其中一个改名为Background并为其选择一个材质;另一个plane也选择一个材质要和前一个不同,选择shader为Unlit/Transparent (自发光),将其设为不可见。创建一个空对象,起名为Body,如下图:

                    

创建一个c#脚本main.cs,绑定在Body对象下。Inspector设置如下:

                

main.cs脚本如下:
  1. using UnityEngine;
  2. using System.Collections;
  3. public class main : MonoBehaviour
  4.  {
  5. public GameObject _plane;        //用来实例碎片的对象
  6. public GameObject _planeParent; //碎片对象所要绑定的父节点
  7. public GameObject _background;    //显示暗色的背景图
  8. public Texture2D[] _texAll;        //用来更换的纹理
  9. public Vector3[] _RandomPos;    //开始时, 碎片随机分布的位置
  10. public int raw = 3;                //图形切分的行数
  11. public int volumn = 3;            //图形切分的列数
  12. public float factor = 0.25f;    //一个范围比例因子, 用来判断碎片是否在正确位置范围内
  13. GameObject[] _tempPlaneAll;
  14. float sideLength = 0;            //背景图的边长(正方形)
  15. int finishCount = 0;            //完成的碎片数量
  16. int _index = 0;
  17. Vector2 originPoint;            //第一个碎片的位置
  18. Vector2 space;                    //碎片与碎片之间的间隔(中心距x,y)
  19. void Start()
  20. {
  21. sideLength = _background.transform.localScale.x;
  22. space.x = sideLength / volumn;
  23. space.y = sideLength / raw;
  24. originPoint.x = -((volumn - 1) * space.x) / 2;
  25. originPoint.y = ((raw - 1) * space.y) / 2;
  26. Vector2 range;
  27. range.x = space.x * factor * 10f;
  28. range.y = space.y * factor * 10f;
  29. _tempPlaneAll = new GameObject[raw * volumn];
  30. int k = 0;
  31. //完成所有碎片的有序排列位置和uv纹理的截取
  32. for(int i = 0 ; i != raw ; ++i)
  33. {
  34. for(int j = 0 ; j != volumn ; ++j)
  35. {
  36. GameObject tempObj = (GameObject)Instantiate(_plane);
  37. tempObj.name = "Item" + k;
  38. tempObj.transform.parent = _planeParent.transform;
  39. tempObj.transform.localPosition = new Vector3((originPoint.x + space.x * j) * 10f, (originPoint.y - space.y * i) * 10f, 0);
  40. tempObj.transform.localScale = new Vector3(space.x, 1f, space.y);
  41. Vector2 tempPos = new Vector2(originPoint.x + space.x * j, originPoint.y - space.y * i);
  42. float offset_x = (tempPos.x <= 0 + Mathf.Epsilon) ? (0.5f - Mathf.Abs((tempPos.x - space.x / 2) / sideLength)) : (0.5f + (tempPos.x - space.x / 2) / sideLength);
  43. float offset_y = (tempPos.y <= 0 + Mathf.Epsilon) ? (0.5f - Mathf.Abs((tempPos.y - space.y / 2) / sideLength)) : (0.5f + (tempPos.y - space.y / 2) / sideLength);
  44. float scale_x = Mathf.Abs(space.x / sideLength);
  45. float scale_y = Mathf.Abs(space.y / sideLength);
  46. tempObj.renderer.material.mainTextureOffset = new Vector2(offset_x, offset_y);
  47. tempObj.renderer.material.mainTextureScale = new Vector2(scale_x, scale_y);
  48. tempObj.SendMessage("SetRange", range);
  49. _tempPlaneAll[k] = tempObj;
  50. ++k;
  51. }
  52. }
  53. }
  54. void OnGUI()
  55. {
  56. //开始游戏
  57. if(GUI.Button(new Rect(10, 10, 100, 30), "Play"))
  58. StartGame();
  59. //更换纹理
  60. if(GUI.Button(new Rect(10, 80, 100, 30), "Next Textrue"))
  61. ChangeTex();
  62. }
  63. void StartGame()
  64. {
  65. //将所有碎片随机分布在左右两边
  66. for(int i = 0 ; i != _tempPlaneAll.Length ; ++i)
  67. {
  68. int tempRank = Random.Range(0, _RandomPos.Length);
  69. _tempPlaneAll[i].transform.localPosition = new Vector3(_RandomPos[tempRank].x, _RandomPos[tempRank].y, 0f);
  70. }
  71. //通知所有子对象, 开始游戏
  72. gameObject.BroadcastMessage("Play");
  73. }
  74. void SetIsMoveFale()
  75. {
  76. gameObject.BroadcastMessage("IsMoveFalse");
  77. }
  78. void IsFinish()
  79. {
  80. //计算放置正确的碎片数量
  81. ++finishCount;
  82. if(finishCount == raw * volumn)
  83. Debug.Log("Finish!");
  84. }
  85. void ChangeTex()
  86. {
  87. _background.renderer.material.mainTexture = _texAll[_index];
  88. gameObject.BroadcastMessage("SetTexture", _texAll[_index++]);
  89. if(_index > _texAll.Length - 1)
  90. _index = 0;
  91. }
  92. }

再创建一个c#脚本为plane.cs,绑定在Plane对象下。Inspector设置如下:

                                


注意:要为Plane对象添加一个Collider组件,因为Plane对象的scale与Unity的基本单位(米)的比例为10:1,因此这里Collider的Size要设置为10

plane.cs脚本如下:
  1. using UnityEngine;
  2. using System.Collections;
  3. public class plane : MonoBehaviour {
  4. Transform mTransform;
  5. Vector3 offsetPos;                    //鼠标点与所选位置的偏移
  6. Vector3 finishPos = Vector3.zero;    //当前碎片的正确位置
  7. Vector2 range;                        //碎片正确位置的范围, 由SetRange函数设置
  8. float z = 0;
  9. bool isPlay = false;                //是否进行游戏?
  10. bool isMove = false;                //当前碎片是否跟随鼠标移动
  11. void Start()
  12. {
  13. mTransform = transform;
  14. finishPos = mTransform.localPosition;
  15. }
  16. void Update()
  17. {
  18. if(!isPlay)
  19. return ;
  20. //当鼠标进入碎片中按下时, 记录与碎片中心位置的偏差; 并使碎片跟随鼠标移动(多张碎片叠在一起时,只选其中一张跟随)
  21. Vector3 tempMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
  22. if(Input.GetMouseButtonDown(0) && tempMousePos.x > collider.bounds.min.x && tempMousePos.x < collider.bounds.max.x
  23. && tempMousePos.y > collider.bounds.min.y && tempMousePos.y < collider.bounds.max.y)
  24. {
  25. mTransform.parent.SendMessage("SetIsMoveFale");
  26. offsetPos = mTransform.position - tempMousePos;
  27. z = mTransform.position.z;
  28. isMove = true;
  29. }
  30. //跟随鼠标移动
  31. if(isMove && Input.GetMouseButton(0))
  32. {
  33. tempMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
  34. mTransform.position = new Vector3(tempMousePos.x + offsetPos.x, tempMousePos.y + offsetPos.y, z - 0.1f);
  35. }
  36. //鼠标放开后停止跟随
  37. if(Input.GetMouseButtonUp(0))
  38. {
  39. mTransform.position = new Vector3(mTransform.position.x, mTransform.position.y, z);
  40. isMove = false;
  41. }
  42. //判断是否到达正确位置(如进入正确位置范围, 碎片自动设置在正确位置, 并不可被再移动)
  43. IsFinish();
  44. }
  45. void IsFinish()
  46. {
  47. if(mTransform.localPosition.x > finishPos.x - range.x && mTransform.localPosition.x < finishPos.x + range.x
  48. && mTransform.localPosition.y > finishPos.y - range.y && mTransform.localPosition.y < finishPos.y + range.y)
  49. {
  50. isPlay = false;
  51. mTransform.localPosition = finishPos;
  52. mTransform.parent.SendMessage("IsFinish");
  53. }
  54. }
  55. //开始游戏
  56. void Play()
  57. {
  58. isPlay = true;
  59. }
  60. void IsMoveFalse()
  61. {
  62. isMove = false;
  63. }
  64. void SetRange(Vector2 _range)
  65. {
  66. range = _range;
  67. }
  68. //更换纹理
  69. void SetTexture(Texture2D _tex)
  70. {
  71. mTransform.renderer.material.mainTexture = _tex;
  72. }
  73. }

现在可以开始游戏了,如下图:



 程序源码及打包文件在此下载


使用方法:打开一个空Unity场景,点击Assets->Import Package->Custom Package导入即可。注意:不要有中文路径