小夭的游戏编程教室——(二)五子棋

10.11更新了下载地址 
—————————————————— 
  
大家好,说好的一个月一发的五子棋也横空出世了,这个五子棋可以说是上一个圈圈叉叉的升级版(所以建议新手先看看教程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怎么设置的... 
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上有个卖的...不过价格真坑 
最后还是吹一下我们的口号—— 自给自足,娱乐娱乐
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值