10.11更新了下载地址
——————————————————
大家好,说好的一个月一发的五子棋也横空出世了,这个五子棋可以说是上一个圈圈叉叉的升级版(所以建议新手先看看教程1)
这个文章后面逻辑层部分比较混乱 请结合UI层一起看..
欢迎大家下载这个游戏来玩 ~,我目前还没有发现什么Bug,玩的方式也和圈圈叉叉很类似....玩完之后可以拖进度条看录像...
自我感觉录像功能比较拽
先来个最终效果图:
最好还是下个看看最终的游戏是什么个样子的....
Wuziqi.zip
棋盘资源请点这里:导入前请确保你已经导入过NGUI了
qipan.unitypackage
棋子资源请点这里:
qizi.unitypackage
没有黑色的棋子的....黑色的棋子是通过白色棋子改变颜色来的....
相比于圈圈叉叉,五子棋的功能增加了不少:
1 棋盘的随意拖动(左键拖动)
2 棋盘的放大放小(鼠标滚轮和右边的拖动条)
3 悔棋功能
4 回放录像功能
在里面我们将分成3部分来深度解析
------分析---------
让我们看看需要做什么
首先给这个游戏划分结构层次,我还是按3大层次来划分,数据层,UI/交互层,逻辑层
面板拖动的部分不用你写代码,直接拖NGUI的就好了
游戏的4个状态:
enum GameState {
MyAction,
AiAction,
GameOver,
Replay
}
具体来说是 我的状态 <-> ai的状态(还是人脑,看成玩家2就好了)
结束了之后进入 GameOver状态(显示谁赢谁输了)
点击Replay按键 ->可回放下棋的过程
每个棋子按下之后怎么处理 -> 先得到name -> 得出位置 ->GameStep储存位置->BoxMatrix【】改变值 -> 改变UI棋子的颜色
判断输赢的逻辑处理,和圈圈叉叉比较相似,下面会详细讲出
————————— 数据层 ————————————
private Vector2[] GameStep = new Vector2[361]; // 初始化时生成361个,记录已经下在棋盘上的每一个棋子的x和y的坐标
private int GameStepPoint=0; //当前的GameStep数组游标
//这个数据层用来执行悔棋的操作,还有最后的录像操作
—————————— UI/交互层 ————————————
1 各种UI的放置
2 棋盘的放大放小
3 棋盘的拖动
4 NGUI的事件判定机制
NGUi的script用了许多个,比较重要的有:
UIDraggable Panel
UIDragPanel Contents
UIButtonMessage
在Panel里面摆放了各种UI
棋盘的放大放小通过它们的父物体offset实现
键盘的拖动通过Panel Drag实现
最终UI图对应关系: 理清结构图比较重要
可以仿照我这个结构来建造五子棋.....
UI Root怎么设置的...
Number 那里19表示 19*19的格子数...
Twoheng Width表示每个棋子间的间隔
camera怎么设置的
这个size我改成了2 方便后面我的缩放操作....
游戏面板的控制,特别注意打开Clipping, 我使用的是软边 所以你会看到边缘有模糊效果。。。
注意这里的HScrollBar 和 VScrollBar 就是要拖到这里的
Scroll Wheel Factor的值也要设为0 不然滚轮就只触发这个拖动事件不触发放大缩小的事件了
再看看UI层怎么放的....
绿色部分为可触发事件的面积,粉红色部分为游戏面板的面积 可以看到触发面积大很多
面板会使得棋盘一定保持在面板中
结构图大概是这个样子
UIButtonMessage就是传给UI Root的button事件
UIDragPanelContents是触发拖动面板的事件
MouseScrollWheelEvent(这个是自己写的 不是NGUI自带的)是触发鼠标滚轮事件
----------------------------NGUi的事件判断————
Ngui的事件判断机制是:“返回第一个碰到的能触发NGUI事件的collider物体”,如上图,btn1和btn2放一起,当触发他们的时候,只会触发更靠近相机的一个物体
所以我们应该给 棋子 和 棋盘 都要有拖动和触发滚轮事件的函数
这个图可以看到怎么触发这些个事件的....
当然,BoxCollider也可以换成别的Collider,
每一个棋子放了什么...
更正一下,下面的UI Root (3D)其实是放了UI Root (2D)
这个是一个棋子 0_0表示的是左下角第0个第0列
这个是后面的棋盘
————————————— 逻辑层 ————————————
1 判定方法
2 悔棋和录像的制作方法
(回放功能,下面的进度条需要自己拖动....)
———————游戏判断输赢—————————————
判定方法和圈圈叉叉的差不多,都是以“下的最后一个棋子为标准,分4次检测横列,竖列,左斜列,右斜列”的棋子,
遇到0和与自己不相同的数则回退,遇到和自己相同的则加1,一直加如果加到4,游戏结束
如上图所示是一个可能的结果:
假设中间用括号区分的就是现在下了的棋子,则它会首先检查左边部分,第一个是1,则Count+1
第二个也是1,则Count+1,
第三个是-1,-1不等于1,则游标返回中间点,下一次向右搜索
注意第4个0就不会再搜索了
然后往右边开始数,第一个是1,则Count+1
第二个是-1,则本次搜索结束
此时Count==4,则棋子加起来一共有4只了,则结束本列的搜索,
Count恢复成1 ,继续 竖列 和 2个斜列 的搜索
-,- 这样的搜索一列方式,最差的情况是搜索8次就结束(左边一个也没有 全在右边)
最好的情况是搜索2次就结束(左右两边第一个都不是)
列一个最差的情况的例子:
0,0,0,-1,(1),-1,1,1,0
此时往左边数第一个不是1,则结束,往右边数第一个也不是1,也结束 Count此时还是0 所以这一步1没赢
不知道现在这样描述能不能浅显易懂...
贴一个判断横边的。。。
#region hengbian
{
while (!isstop)
{
point_X--;
if (point_X > -1 ;; point_X > x - 5)
{
if (BoxMatrix[y,point_X] == Qizi)
{
WuziCount+=Qizi;
//Debug.Log(WuziCount+","+Qizi);
}
else
{
isstop = true;
}
}
else
{
isstop = true;
}
}
//复原
point_X = x;
isstop = false;
while (!isstop)
{
point_X++;
if (point_X < BoxWidth ;; point_X < x + 5)
{
if (BoxMatrix[y,point_X] == Qizi)
{
WuziCount+=Qizi;
}
else
{
isstop = true;
}
}
else
{
isstop = true;
}
}
if (WuziCount >= 5 || WuziCount <= -5)
{
isgamewin = true;
}
}
#endregion
判断竖边和2个斜边的和这个也很类似,注意每次判断前应该先将变量恢复原始的值
———————悔棋和回放录像————————————
悔棋和录像的方法一致,就是要记录当前的操作步,现在做的这个游戏和时间没什么关系,则我们只需按步骤记录点数即可 所以每当我下了一步的时候
GameStep【GameStepPoint】= new Vector2(x,y)
GameStepPoint++;
回放录像功能就是从头开始播放而已
public void OnReplayChange(float changeFloat) //这个值是0-1
{
if (gameState == GameState.Replay)
{
//Debug.Log("fuck you" + changeFloat);
int tmp = Convert.ToInt32(changeFloat * GameStepPoint);
for (int i = 0; i < GameStepPoint ; i++)
{
int _column = Convert.ToInt32(GameStep .y);
int _row = Convert.ToInt32(GameStep.x);
string str = _column + "_" + _row;
UISlicedSprite thisSprite = fatherQizi.transform.FindChild(str).transform.FindChild("Background").GetComponent();
if (i >= tmp)
{
thisSprite.enabled = false;
}
else {
thisSprite.enabled = true;
}
}
}
}
播放录像前还会进行一个棋盘的“清空”操作,就是将thisSprite.enabled = false;
fatherQizi就是棋子上一层的父对象,通过创建这种空的父对象可以很快速的查找子物体
————————其他部分———————————
一共写了 两个 c#文件
一个为了触发鼠标滚轮的OnScroll的方法(),放到棋盘的面板和每一个棋子中,名字是MouseScrollWheelEvent.cs
鼠标的任何滚动,都会使得delta的值变成0.1 或者 -0.1
public class MouseScrollWheelEvent : MonoBehaviour
{
public WuZiQi wuZiQi;
public void OnScroll(float delta)
{
if (enabled ;; gameObject.active)
{
wuZiQi.OnScrollChange(delta);
}
}
}
一个用来负责总体的逻辑,放到Root中,名字是WuZiQi.cs
截个图...
这代码太长了,我分步讲解:
//这里看不懂的可参考圈圈叉叉的部分是怎么个创建的
void Start () {
1 确定左下角的原点
2 复制棋子,初始化棋盘,初始化棋盘数组
for (int i = 0; i<boxheight ;i++="" )="" {="" for="" (int="" j="0;" j<boxwidth="" ;j++="" gameobject="" _newqizi="Instantiate(_Qizi)" as="" gameobject;="" _newqizi.transform.parent="_Qizi.transform.parent;" _newqizi.transform.localposition="new" vector3(originpoint.x="" +="" *="" twohengwidth="" 0.5f,="" originpoint.y="" i="" 0);="" _newqizi.transform.localscale="new" vector3(1,="" 1,="" 1);="" _newqizi.name="i" "_"="" j;="" _newqizi.transform.findchild("background").getcomponent().enabled = false;
BoxMatrix[i,j]=0;
}
}
GameStep = new Vector2[361]; //初始化数据层的值
}
然后是拖动右滑竿的动作和鼠标触发的动作的事件
可以看到滑动右边滑竿的话 是和鼠标滚轮没什么关系
但是用鼠标滚轮滑动的话 则必须操作右边滑竿的值
public void OnSliderChange(float changeFloat) //滑动右边的滑竿触发的事件
{
nowScale = (1 - changeFloat) * (maxScale - minScale) + minScale;
offset.transform.localScale = new Vector3(nowScale, nowScale, 1); //就这一句简单的来改变棋盘和棋子的大小
}
public void OnScrollChange(float delta) //鼠标滚轮的滚动触发的事件
{
nowScale += delta;
if (nowScale >= maxScale) { nowScale = maxScale; }
else if (nowScale <= minScale) { nowScale = minScale; }
offset.transform.localScale = new Vector3(nowScale, nowScale, 1);
uiSlider.sliderValue = 1 - (nowScale - minScale) / (maxScale - minScale);//改变右边滑竿的值 我这里maxScale是3 minScale是1
}
按钮按下的动作处理。。这个就是放到每一个棋子上触发的
bool isbtnlock = false;
//触发式的逻辑判断
public void buttonPress(GameObject o)
{
//Debug.Log(o.name);
//isbtnlock 是防止同时出发多个btn而设置的一个锁
if (!isbtnlock)
{
if (gameState == GameState.GameOver || gameState == GameState.Replay)
{
return;
}
isbtnlock = true;
UISlicedSprite thisSprite = o.transform.FindChild("Background").GetComponent();
thisSprite.enabled = true;
String[] _strArr = (o.name).Split(new char[] { '_' });
int _row = Convert.ToInt32(_strArr[1]);
int _column = Convert.ToInt32(_strArr[0]);
if (isNullBlock(BoxMatrix, _column, _row))
{
if (gameState == GameState.MyAction)
{
//Debug.Log(_column + "," + _row+",while");
GameStep[GameStepPoint] = new Vector2(_row, _column);
GameStepPoint++;
thisSprite.color = Color.white;
BoxMatrix[_column, _row] = 1;
Logic(_column, _row, 1);
}
else if (gameState == GameState.AiAction)
{
//Debug.Log(_column + "," + _row + ",black");
GameStep[GameStepPoint] = new Vector2(_row ,_column);
GameStepPoint++;
thisSprite.color = Color.black;
BoxMatrix[_column, _row] = -1;
Logic(_column, _row, -1);
}
}
isbtnlock = false;
}
}
仍未实现的功能:五子棋的AI(这个要搞基
起来真是个大工程)
本期的内容比较多 也比较繁杂, 写得也比较乱 望谅解
顺便预告下,下期的教程可能是“打气球”,肯定没这期的这个复杂,我看到unity store上有个卖的...不过价格真坑
最后还是吹一下我们的口号—— 自给自足,娱乐娱乐
——————————————————
大家好,说好的一个月一发的五子棋也横空出世了,这个五子棋可以说是上一个圈圈叉叉的升级版(所以建议新手先看看教程1)
这个文章后面逻辑层部分比较混乱 请结合UI层一起看..
欢迎大家下载这个游戏来玩 ~,我目前还没有发现什么Bug,玩的方式也和圈圈叉叉很类似....玩完之后可以拖进度条看录像...
自我感觉录像功能比较拽

先来个最终效果图:

最好还是下个看看最终的游戏是什么个样子的....
【附件】10.11更新了ceeger网盘地址: |
Wuziqi.zip
棋盘资源请点这里:导入前请确保你已经导入过NGUI了
qipan.unitypackage
棋子资源请点这里:
qizi.unitypackage

相比于圈圈叉叉,五子棋的功能增加了不少:
1 棋盘的随意拖动(左键拖动)
2 棋盘的放大放小(鼠标滚轮和右边的拖动条)
3 悔棋功能
4 回放录像功能

在里面我们将分成3部分来深度解析
------分析---------
让我们看看需要做什么
首先给这个游戏划分结构层次,我还是按3大层次来划分,数据层,UI/交互层,逻辑层
面板拖动的部分不用你写代码,直接拖NGUI的就好了
游戏的4个状态:
enum GameState {
MyAction,
AiAction,
GameOver,
Replay
}
具体来说是 我的状态 <-> ai的状态(还是人脑,看成玩家2就好了)
结束了之后进入 GameOver状态(显示谁赢谁输了)
点击Replay按键 ->可回放下棋的过程
每个棋子按下之后怎么处理 -> 先得到name -> 得出位置 ->GameStep储存位置->BoxMatrix【】改变值 -> 改变UI棋子的颜色
判断输赢的逻辑处理,和圈圈叉叉比较相似,下面会详细讲出

————————— 数据层 ————————————
private Vector2[] GameStep = new Vector2[361]; // 初始化时生成361个,记录已经下在棋盘上的每一个棋子的x和y的坐标
private int GameStepPoint=0; //当前的GameStep数组游标
//这个数据层用来执行悔棋的操作,还有最后的录像操作
—————————— UI/交互层 ————————————
1 各种UI的放置
2 棋盘的放大放小
3 棋盘的拖动
4 NGUI的事件判定机制
NGUi的script用了许多个,比较重要的有:
UIDraggable Panel
UIDragPanel Contents
UIButtonMessage
在Panel里面摆放了各种UI
棋盘的放大放小通过它们的父物体offset实现
键盘的拖动通过Panel Drag实现
最终UI图对应关系: 理清结构图比较重要

可以仿照我这个结构来建造五子棋.....
UI Root怎么设置的...

Twoheng Width表示每个棋子间的间隔
camera怎么设置的

游戏面板的控制,特别注意打开Clipping, 我使用的是软边 所以你会看到边缘有模糊效果。。。

Scroll Wheel Factor的值也要设为0 不然滚轮就只触发这个拖动事件不触发放大缩小的事件了
再看看UI层怎么放的....

绿色部分为可触发事件的面积,粉红色部分为游戏面板的面积 可以看到触发面积大很多
面板会使得棋盘一定保持在面板中

结构图大概是这个样子
UIButtonMessage就是传给UI Root的button事件
UIDragPanelContents是触发拖动面板的事件
MouseScrollWheelEvent(这个是自己写的 不是NGUI自带的)是触发鼠标滚轮事件
----------------------------NGUi的事件判断————

Ngui的事件判断机制是:“返回第一个碰到的能触发NGUI事件的collider物体”,如上图,btn1和btn2放一起,当触发他们的时候,只会触发更靠近相机的一个物体
所以我们应该给 棋子 和 棋盘 都要有拖动和触发滚轮事件的函数

这个图可以看到怎么触发这些个事件的....
当然,BoxCollider也可以换成别的Collider,
每一个棋子放了什么...
更正一下,下面的UI Root (3D)其实是放了UI Root (2D)


————————————— 逻辑层 ————————————
1 判定方法
2 悔棋和录像的制作方法
(回放功能,下面的进度条需要自己拖动....)

———————游戏判断输赢—————————————
判定方法和圈圈叉叉的差不多,都是以“下的最后一个棋子为标准,分4次检测横列,竖列,左斜列,右斜列”的棋子,
遇到0和与自己不相同的数则回退,遇到和自己相同的则加1,一直加如果加到4,游戏结束

如上图所示是一个可能的结果:
假设中间用括号区分的就是现在下了的棋子,则它会首先检查左边部分,第一个是1,则Count+1
第二个也是1,则Count+1,
第三个是-1,-1不等于1,则游标返回中间点,下一次向右搜索
注意第4个0就不会再搜索了
然后往右边开始数,第一个是1,则Count+1
第二个是-1,则本次搜索结束
此时Count==4,则棋子加起来一共有4只了,则结束本列的搜索,
Count恢复成1 ,继续 竖列 和 2个斜列 的搜索
-,- 这样的搜索一列方式,最差的情况是搜索8次就结束(左边一个也没有 全在右边)
最好的情况是搜索2次就结束(左右两边第一个都不是)
列一个最差的情况的例子:
0,0,0,-1,(1),-1,1,1,0
此时往左边数第一个不是1,则结束,往右边数第一个也不是1,也结束 Count此时还是0 所以这一步1没赢
不知道现在这样描述能不能浅显易懂...
贴一个判断横边的。。。
#region hengbian
{
while (!isstop)
{
point_X--;
if (point_X > -1 ;; point_X > x - 5)
{
if (BoxMatrix[y,point_X] == Qizi)
{
WuziCount+=Qizi;
//Debug.Log(WuziCount+","+Qizi);
}
else
{
isstop = true;
}
}
else
{
isstop = true;
}
}
//复原
point_X = x;
isstop = false;
while (!isstop)
{
point_X++;
if (point_X < BoxWidth ;; point_X < x + 5)
{
if (BoxMatrix[y,point_X] == Qizi)
{
WuziCount+=Qizi;
}
else
{
isstop = true;
}
}
else
{
isstop = true;
}
}
if (WuziCount >= 5 || WuziCount <= -5)
{
isgamewin = true;
}
}
#endregion
判断竖边和2个斜边的和这个也很类似,注意每次判断前应该先将变量恢复原始的值
———————悔棋和回放录像————————————
悔棋和录像的方法一致,就是要记录当前的操作步,现在做的这个游戏和时间没什么关系,则我们只需按步骤记录点数即可 所以每当我下了一步的时候
GameStep【GameStepPoint】= new Vector2(x,y)
GameStepPoint++;
回放录像功能就是从头开始播放而已
public void OnReplayChange(float changeFloat) //这个值是0-1
{
if (gameState == GameState.Replay)
{
//Debug.Log("fuck you" + changeFloat);
int tmp = Convert.ToInt32(changeFloat * GameStepPoint);
for (int i = 0; i < GameStepPoint ; i++)
{
int _column = Convert.ToInt32(GameStep .y);
int _row = Convert.ToInt32(GameStep.x);
string str = _column + "_" + _row;
UISlicedSprite thisSprite = fatherQizi.transform.FindChild(str).transform.FindChild("Background").GetComponent();
if (i >= tmp)
{
thisSprite.enabled = false;
}
else {
thisSprite.enabled = true;
}
}
}
}
播放录像前还会进行一个棋盘的“清空”操作,就是将thisSprite.enabled = false;
fatherQizi就是棋子上一层的父对象,通过创建这种空的父对象可以很快速的查找子物体
————————其他部分———————————
一共写了 两个 c#文件
一个为了触发鼠标滚轮的OnScroll的方法(),放到棋盘的面板和每一个棋子中,名字是MouseScrollWheelEvent.cs
鼠标的任何滚动,都会使得delta的值变成0.1 或者 -0.1
public class MouseScrollWheelEvent : MonoBehaviour
{
public WuZiQi wuZiQi;
public void OnScroll(float delta)
{
if (enabled ;; gameObject.active)
{
wuZiQi.OnScrollChange(delta);
}
}
}
一个用来负责总体的逻辑,放到Root中,名字是WuZiQi.cs
截个图...

这代码太长了,我分步讲解:
//这里看不懂的可参考圈圈叉叉的部分是怎么个创建的
void Start () {
1 确定左下角的原点
2 复制棋子,初始化棋盘,初始化棋盘数组
for (int i = 0; i<boxheight ;i++="" )="" {="" for="" (int="" j="0;" j<boxwidth="" ;j++="" gameobject="" _newqizi="Instantiate(_Qizi)" as="" gameobject;="" _newqizi.transform.parent="_Qizi.transform.parent;" _newqizi.transform.localposition="new" vector3(originpoint.x="" +="" *="" twohengwidth="" 0.5f,="" originpoint.y="" i="" 0);="" _newqizi.transform.localscale="new" vector3(1,="" 1,="" 1);="" _newqizi.name="i" "_"="" j;="" _newqizi.transform.findchild("background").getcomponent().enabled = false;
BoxMatrix[i,j]=0;
}
}
GameStep = new Vector2[361]; //初始化数据层的值
}
然后是拖动右滑竿的动作和鼠标触发的动作的事件
可以看到滑动右边滑竿的话 是和鼠标滚轮没什么关系
但是用鼠标滚轮滑动的话 则必须操作右边滑竿的值
public void OnSliderChange(float changeFloat) //滑动右边的滑竿触发的事件
{
nowScale = (1 - changeFloat) * (maxScale - minScale) + minScale;
offset.transform.localScale = new Vector3(nowScale, nowScale, 1); //就这一句简单的来改变棋盘和棋子的大小
}
public void OnScrollChange(float delta) //鼠标滚轮的滚动触发的事件
{
nowScale += delta;
if (nowScale >= maxScale) { nowScale = maxScale; }
else if (nowScale <= minScale) { nowScale = minScale; }
offset.transform.localScale = new Vector3(nowScale, nowScale, 1);
uiSlider.sliderValue = 1 - (nowScale - minScale) / (maxScale - minScale);//改变右边滑竿的值 我这里maxScale是3 minScale是1
}
按钮按下的动作处理。。这个就是放到每一个棋子上触发的
bool isbtnlock = false;
//触发式的逻辑判断
public void buttonPress(GameObject o)
{
//Debug.Log(o.name);
//isbtnlock 是防止同时出发多个btn而设置的一个锁
if (!isbtnlock)
{
if (gameState == GameState.GameOver || gameState == GameState.Replay)
{
return;
}
isbtnlock = true;
UISlicedSprite thisSprite = o.transform.FindChild("Background").GetComponent();
thisSprite.enabled = true;
String[] _strArr = (o.name).Split(new char[] { '_' });
int _row = Convert.ToInt32(_strArr[1]);
int _column = Convert.ToInt32(_strArr[0]);
if (isNullBlock(BoxMatrix, _column, _row))
{
if (gameState == GameState.MyAction)
{
//Debug.Log(_column + "," + _row+",while");
GameStep[GameStepPoint] = new Vector2(_row, _column);
GameStepPoint++;
thisSprite.color = Color.white;
BoxMatrix[_column, _row] = 1;
Logic(_column, _row, 1);
}
else if (gameState == GameState.AiAction)
{
//Debug.Log(_column + "," + _row + ",black");
GameStep[GameStepPoint] = new Vector2(_row ,_column);
GameStepPoint++;
thisSprite.color = Color.black;
BoxMatrix[_column, _row] = -1;
Logic(_column, _row, -1);
}
}
isbtnlock = false;
}
}
仍未实现的功能:五子棋的AI(这个要
本期的内容比较多 也比较繁杂, 写得也比较乱 望谅解
顺便预告下,下期的教程可能是“打气球”,肯定没这期的这个复杂,我看到unity store上有个卖的...不过价格真坑
最后还是吹一下我们的口号—— 自给自足,娱乐娱乐
