Unity拼图小游戏的实现
目前做了一款拼图小游戏,不规则的图形,自由拖拽,网上其他拼图大部分都是基于九宫格的类似华容道拼图模式,我做的时候没有找到我这种类型的相关的项目代码(有一付费下载,然后发现是用3d实现碰撞,然后正交相机来模拟2d),所以现在来记录一下自己的完成思路。
下附一张效果截图。
一、主要难点
1.不规则图形的显示和碰撞
2.拼图自动生成
3.拼图拼合移动
二、难点思路
1.显示和碰撞
我之前是一直卡在不规则图形的显示和碰撞上,找了很多资料,基本要切成上图的样子只有用算法。(这里是一个基于算法实现凹凸拼图切割的博客) 很强大,很正统,正确思路应该就是这么做,但是我不会,所以没有用,以后再啃吧。我的实现思路就很简单,用unity自带的mask遮罩,附一张我的思路图。
这里我把完整拼图切成216(18×12)小块,然后分成,3×4,4×3,3×5,5×3的不等小块,再用四种mask,旋转再各自匹配以后,基本就得到了这类拼图游戏的所有拼图模板,如下。
2019.6.20更新
这里我把完整拼图切成216(18×12)小块,然后分成,3×4,4×3,3×5,5×3的4种小块,然后配合14种mask,来得到我这个模式下所有的14种拼图基类,所有mask样式如下。之所以要有这么多mask,是为了后面射线检测的的时候能用统一的代码来控制(旋转后个体的坐标轴会变,目前我无法解决,故用这个笨方法)。
然后为了更加规格化,我加入了插入点和重心点的概念,加入以后分解图如下
白色点是每块拼图的插入点,因为每块拼图是由一个图片组构成的,每次生成需要加载一组sprite依次放入模板的image里。比如左上角第一块3×4,插入点为1的拼图,他要加载的sprite组是:~
1,2,3,
19,20,21,
37,38,39,
55,56,57
这一组sprite,当把加载方法封装好以后,调用只需要传入插入点位置的sprite编号,就可以创建一块完成拼图出来了
2019.6.20更新
第二个版本我换了切图的方法,切完以后子图片的顺序变成了,从下往上,从左往右,
3 6 9
2 5 8
1 4 7
就是上面的结构,于是对插入点做了调整,简单来说就是从左上调整到了左下,比如左下角第一块4×3,插入点为1的拼图,他要加载的sprite组就变成了:
3 15 27 39
2 14 26 38
1 13 25 37
黑色点是每块拼图的重心点,用来挂上碰撞检测的脚本,这样每块拼图之间的距离检测就一样了,也是为了更加的。。简单?随他去吧,我是懒得一个个设置碰撞距离,想到了这个偷懒的方法 = =。
然后我设定,只有鼠标选择的拼图块才激活碰撞检测脚本,避免bug,碰撞以后把碰撞的两个拼图放到一个父级下面实现拼好一起移动的功能。
最后加入插入点以后,拼图模板有六个,分别是,(拼图大小,插入点)(3×4,1),(3×4,2),(4×3,4),(4×3,1),(5×3,4),(3×5,2)。
2019.6.20更新
重心点概念还是这样,只是调整了代码结构以后,射线检测脚本也挂到了mask物体上,不再额外挂到子图片上。
2.拼图的生成
显示和碰撞搞定了,剩下就是如何自动生成每块拼图了,首先,准备一张图,然后切成216块(18×12),这里用unity自带的处理方法就好,sprite mode 设置成multiple以后打开sprite Editor,设置如下
单个拼图的加载基本就是这样,调用图片模板,加载并根据插入点传入sprite组,给重心点的图片加载碰撞检测脚本,然后加载mask,完成
2019.6.20更新
因为被要求代码动态切图,所以弃用sprite Editor切图,改用SetPixel方法来切图,这个方法网上有大量的实例,这里不再说明,只是说一下这个方法得到的子图片是从左到右,从下往上排列的,和像素点的排列顺序一样
3.拼图拼合移动
2019.6.20更新
这点在之前是没有的,我只是把拼合好的拼图放到一个父级下面实现共同移动,后面遇到了很多bug,于是想了一个思路。
拼图的拼合,现在是每块拼图会记录它上下左右其他拼图和它自身的一个偏移量,如果检测以后是正确的碰撞,就把该拼图的位置设置为当前拼图的位置+这个偏移量。、
现在的拼图从开始到结束全在一个父级下面,每个拼图会记录一个list和一个transform,list里面是所有跟它拼合的拼图,transform是记录的当前鼠标进入的拼图,当拖动的时候,list里所有拼图和transform计算一个偏移量,然后所有拼图的位置就等于鼠标移动的实时位置+这个偏移量,这就实现了一个“虚拟父级”的概念,完成了拼合好的拼图一起移动的功能。
对于拼图旋转,设定一个整型数值用来记录旋转次数,有这么几种0,1,2,3,超过4次就重新从0开始,然后射线检测的时候加个检测双方这个数值是否相等的语句,即可分离不同旋转次数的拼图的碰撞检测。
三、Show me the code
2019.6.20更新
几乎更新了所有代码,现在还是把主要的代码放出来,工程在下面。
1.生成拼图的GameStart 类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameStart : MonoBehaviour
{
//切完的小图
private List<Texture2D> textureList = new List<Texture2D>();
//要切的大图
private Texture2D mCurTexture;
//14种mask
private List<GameObject> maskList = new List<GameObject>();
//拼图生成在jps下,存在jigsawPuzzleList里
private Transform jps;
private List<GameObject> jigsawPuzzleList = new List<GameObject>();
void Start()
{
InIt();
}
//初始化
private void InIt()
{
jps = GameObject.Find("JPs").transform;
//切图
mCurTexture = Resources.Load<Texture2D>("Textures/ow");
textureList = DePackTexture(GameManager.Instance.lieNum * 3, GameManager.Instance.hangNum * 3, GameManager.Instance.SideLength, mCurTexture);
//读取14种mask预制体
for (int i = 0; i < 14; i++)
{
maskList.Add(Resources.Load<GameObject>("Prefabs/Mask/mask" + (i + 1)));
}
CreateJigsawPuzzle(GameManager.Instance.hangNum, GameManager.Instance.lieNum);
//随机位置
for (int i = 0; i < jigsawPuzzleList.Count; i++)
{
int posX = Random.Range(-300, 350);
int posY = Random.Range(600, -200);
jigsawPuzzleList[i].transform.localPosition = new Vector3(posX, posY, 0f);
jigsawPuzzleList[i].AddComponent<SingleJigsawPuzzle>();
}
Debug.Log("拼图生成完毕");
}
//列数,行数,小图的长宽
private List<Texture2D> DePackTexture(int PicColumnCount, int PicRowCount, int SideLength, Texture2D CurTexture)
{
List<Texture2D> newTList = new List<Texture2D>();
for (int i = 0; i < PicColumnCount; ++i)
{
for (int j = 0; j < PicRowCount; ++j)
{
int curX = i * SideLength;
int curY = j * SideLength;
Texture2D newTexture = new Texture2D(SideLength, SideLength);
for (int m = curY; m < curY + SideLength; ++m)
{
for (int n = curX; n < curX + SideLength; ++n)
{
newTexture.SetPixel(n - curX, m - curY, CurTexture.GetPixel(n, m));
}
}
newTexture.Apply();
newTList.Add(newTexture);
}
}
return newTList;
}
//5*3,插入点为4
private void FiveThreeIndex4(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 3; j++)
{
ttList.Add(textureList[Index - GameManager.Instance.hangNum * 3 + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 3; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(60f + i * 30f, 30f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 3 + j], new Rect(0, 0, ttList[i * 3 + j].width, ttList[i * 3 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//3*5,插入点为2
private void ThreeFiveIndex2(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
ttList.Add(textureList[Index - 1 + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(-30f + i * 30f, -60f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 5 + j], new Rect(0, 0, ttList[i * 5 + j].width, ttList[i * 5 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//3*4,插入点为1
private void ThreeFourIndex1(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
ttList.Add(textureList[Index + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(-60f + i * 30f, -60f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 4 + j], new Rect(0, 0, ttList[i * 4 + j].width, ttList[i * 4 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//3*4,插入点为2
private void ThreeFourIndex2(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
ttList.Add(textureList[Index - 1 + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(-60f + i * 30f, -60f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 4 + j], new Rect(0, 0, ttList[i * 4 + j].width, ttList[i * 4 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//4*3,插入点为1
private void FourThreeIndex1(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 3; j++)
{
ttList.Add(textureList[Index + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 3; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(-60f + i * 30f, -30f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 3 + j], new Rect(0, 0, ttList[i * 3 + j].width, ttList[i * 3 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//4*3,插入点为4
private void FourThreeIndex4(int Index, Transform Parent)
{
List<Texture2D> ttList = new List<Texture2D>();
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 3; j++)
{
ttList.Add(textureList[Index - GameManager.Instance.hangNum * 3 + j + i * GameManager.Instance.hangNum*3]);
}
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 3; j++)
{
GameObject go = new GameObject();
go.AddComponent<Image>();
go.transform.SetParent(Parent);
go.transform.localPosition = new Vector3(-60f + i * 30f, -30f + j * 30f, 0f);
Image ie = go.GetComponent<Image>();
ie.rectTransform.sizeDelta = new Vector2(30f, 30f);
ie.sprite = Sprite.Create(ttList[i * 3 + j], new Rect(0, 0, ttList[i * 3 + j].width, ttList[i * 3 + j].height), new Vector2(0.5f, 0.5f));
}
}
}
//生成拼图hangNum*lieNum
private void CreateJigsawPuzzle(int hangNum, int lieNum)
{
int index = 0;
int nameNum = 0;
for (int i = 0; i < lieNum; i++)
{
for (int j = 0; j < hangNum; j++)
{
index = j * 3 + i * 9 * hangNum;
nameNum = j + i * hangNum;
//边框
//左下角
if (i == 0 && j == 0)
{