unity3d拼图游戏
最近看到有人做的拼图游戏,感觉十分有趣。于是我在网上搜罗了关于Unity3D拼图游戏的制作方法,现在就分享给大家。
一、mainTextureOffset 和 mainTextureScale 的作用和用法
今天,我将通过一个简单的拼图游戏实例,来介绍Unity中 Material 类的 mainTextureOffset 和 mainTextureScale 这两个属性的作用和用法。
1. 属性定义
mainTextureOffset:指的是主纹理偏移。mainTextureScale:指的是主纹理缩放。
默认情况下,一个 Material 的 mainTextureOffset 是 (0, 0),mainTextureScale 是 (1, 1),这表示原图正常显示,没有缩放。
2. mainTextureScale 的效果
当我们改变 mainTextureScale 的值时,纹理只会显示一部分,其取值范围是 [0, 1]。
- 若将
mainTextureScale.x设置为0.5,纹理将只显示u方向的 50%。 - 同理,将
mainTextureScale.y设置为0.5,纹理将只显示v方向的 50%。
3. mainTextureOffset 的效果
mainTextureOffset 属性表示纹理的起始偏移,值为 0 时没有偏移。mainTextureOffset.x 指 u 方向的偏移量,mainTextureOffset.y 指 v 方向的偏移量,其范围也是 [0, 1]。需要注意的是,偏移的起点在图像的左下角。
- 当设置
mainTextureOffset.x为0.2时,纹理会向左偏移 20%。 - 当设置
mainTextureOffset.y为0.2时,纹理会向下偏移 20%。
4. 截取图片部分区域
通过应用这两个属性,我们可以截取图片的一部分来显示。例如:
mainTextureOffset = new Vector2(0.5, 0.5);
mainTextureScale = new Vector2(0.5, 0.5);
这样设置后,将只显示原图的右上角 1/4 区域。
二、拼图游戏思路和玩法
1. 思路
- 将一张图片切分为
raw * volumn张碎片。 - 每张碎片只显示图片的一部分。
- 将这些碎片按一定顺序和位置排列,使其看起来像一张完整的图片。
2. 玩法
- 点击
Start按钮后开始游戏。玩家选中碎片并将其拖放到正确的位置上,若放置正确则该碎片不可再被拖动,直到所有碎片都放置正确。 - 点击
Next Texture按钮可切换背景和碎片显示的图片。
三、制作流程
1. 新建游戏场景
新建一个游戏场景 Test,设置摄像机属性如下(采用正交摄像机(2D),并将其标签设置为 MainCamera)。
2. 创建游戏对象
创建两个 Plane 对象:
- 其中一个改名为
Background,并为其选择一个材质。 - 另一个
Plane也选择一个材质,该材质要与前一个不同,选择shader为Unlit/Transparent(自发光),并将其设为不可见。 创建一个空对象,命名为Body。
3. 编写 main.cs 脚本
创建一个 C# 脚本 main.cs,并将其绑定在 Body 对象下。以下是 main.cs 脚本的代码:
using UnityEngine;
using System.Collections;
public class main : MonoBehaviour
{
public GameObject _plane; // 用来实例碎片的对象
public GameObject _planeParent; // 碎片对象所要绑定的父节点
public GameObject _background; // 显示暗色的背景图
public Texture2D[] _texAll; // 用来更换的纹理
public Vector3[] _RandomPos; // 开始时,碎片随机分布的位置
public int raw = 3; // 图形切分的行数
public int volumn = 3; // 图形切分的列数
public float factor = 0.25f; // 一个范围比例因子,用来判断碎片是否在正确位置范围内
GameObject[] _tempPlaneAll;
float sideLength = 0; // 背景图的边长(正方形)
int finishCount = 0; // 完成的碎片数量
int _index = 0;
Vector2 originPoint; // 第一个碎片的位置
Vector2 space; // 碎片与碎片之间的间隔(中心距 x, y)
void Start()
{
sideLength = _background.transform.localScale.x;
space.x = sideLength / volumn;
space.y = sideLength / raw;
originPoint.x = -((volumn - 1) * space.x) / 2;
originPoint.y = ((raw - 1) * space.y) / 2;
Vector2 range;
range.x = space.x * factor * 10f;
range.y = space.y * factor * 10f;
_tempPlaneAll = new GameObject[raw * volumn];
int k = 0;
// 完成所有碎片的有序排列位置和 uv 纹理的截取
for (int i = 0; i != raw; ++i)
{
for (int j = 0; j != volumn; ++j)
{
GameObject tempObj = (GameObject)Instantiate(_plane);
tempObj.name = "Item" + k;
tempObj.transform.parent = _planeParent.transform;
tempObj.transform.localPosition = new Vector3((originPoint.x + space.x * j) * 10f, (originPoint.y - space.y * i) * 10f, 0);
tempObj.transform.localScale = new Vector3(space.x, 1f, space.y);
Vector2 tempPos = new Vector2(originPoint.x + space.x * j, originPoint.y - space.y * i);
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);
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);
float scale_x = Mathf.Abs(space.x / sideLength);
float scale_y = Mathf.Abs(space.y / sideLength);
tempObj.renderer.material.mainTextureOffset = new Vector2(offset_x, offset_y);
tempObj.renderer.material.mainTextureScale = new Vector2(scale_x, scale_y);
tempObj.SendMessage("SetRange", range);
_tempPlaneAll[k] = tempObj;
++k;
}
}
}
void OnGUI()
{
// 开始游戏
if (GUI.Button(new Rect(10, 10, 100, 30), "Play"))
StartGame();
// 更换纹理
if (GUI.Button(new Rect(10, 80, 100, 30), "Next Textrue"))
ChangeTex();
}
void StartGame()
{
// 将所有碎片随机分布在左右两边
for (int i = 0; i != _tempPlaneAll.Length; ++i)
{
int tempRank = Random.Range(0, _RandomPos.Length);
_tempPlaneAll[i].transform.localPosition = new Vector3(_RandomPos[tempRank].x, _RandomPos[tempRank].y, 0f);
}
// 通知所有子对象,开始游戏
gameObject.BroadcastMessage("Play");
}
void SetIsMoveFale()
{
gameObject.BroadcastMessage("IsMoveFalse");
}
void IsFinish()
{
// 计算放置正确的碎片数量
++finishCount;
if (finishCount == raw * volumn)
Debug.Log("Finish!");
}
void ChangeTex()
{
_background.renderer.material.mainTexture = _texAll[_index];
gameObject.BroadcastMessage("SetTexture", _texAll[_index++]);
if (_index > _texAll.Length - 1)
_index = 0;
}
}
4. 编写 plane.cs 脚本
再创建一个 C# 脚本 plane.cs,绑定在 Plane 对象下。需要注意的是,要为 Plane 对象添加一个 Collider 组件,由于 Plane 对象的 scale 与 Unity 的基本单位(米)的比例为 10:1,因此这里 Collider 的 Size 要设置为 10。以下是 plane.cs 脚本的代码:
using UnityEngine;
using System.Collections;
public class plane : MonoBehaviour
{
Transform mTransform;
Vector3 offsetPos; // 鼠标点与所选位置的偏移
Vector3 finishPos = Vector3.zero; // 当前碎片的正确位置
Vector2 range; // 碎片正确位置的范围,由 SetRange 函数设置
float z = 0;
bool isPlay = false; // 是否进行游戏
bool isMove = false; // 当前碎片是否跟随鼠标移动
void Start()
{
mTransform = transform;
finishPos = mTransform.localPosition;
}
void Update()
{
if (!isPlay)
return;
// 当鼠标进入碎片中按下时,记录与碎片中心位置的偏差;并使碎片跟随鼠标移动(多张碎片叠在一起时,只选其中一张跟随)
Vector3 tempMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButtonDown(0) && tempMousePos.x > collider.bounds.min.x && tempMousePos.x < collider.bounds.max.x
&& tempMousePos.y > collider.bounds.min.y && tempMousePos.y < collider.bounds.max.y)
{
mTransform.parent.SendMessage("SetIsMoveFale");
offsetPos = mTransform.position - tempMousePos;
z = mTransform.position.z;
isMove = true;
}
// 跟随鼠标移动
if (isMove && Input.GetMouseButton(0))
{
tempMousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mTransform.position = new Vector3(tempMousePos.x + offsetPos.x, tempMousePos.y + offsetPos.y, z - 0.1f);
}
// 鼠标放开后停止跟随
if (Input.GetMouseButtonUp(0))
{
mTransform.position = new Vector3(mTransform.position.x, mTransform.position.y, z);
isMove = false;
}
// 判断是否到达正确位置(如进入正确位置范围,碎片自动设置在正确位置,并不可被再移动)
IsFinish();
}
void IsFinish()
{
if (mTransform.localPosition.x > finishPos.x - range.x && mTransform.localPosition.x < finishPos.x + range.x
&& mTransform.localPosition.y > finishPos.y - range.y && mTransform.localPosition.y < finishPos.y + range.y)
{
isPlay = false;
mTransform.localPosition = finishPos;
mTransform.parent.SendMessage("IsFinish");
}
}
// 开始游戏
void Play()
{
isPlay = true;
}
void IsMoveFalse()
{
isMove = false;
}
void SetRange(Vector2 _range)
{
range = _range;
}
// 更换纹理
void SetTexture(Texture2D _tex)
{
mTransform.renderer.material.mainTexture = _tex;
}
}
四、开始游戏
完成上述步骤后,就可以开始游戏了。
五、源码及使用方法
程序源码及打包文件可在此下载。使用方法:打开一个空 Unity 场景,点击 Assets -> Import Package -> Custom Package 导入即可。注意:不要有中文路径。