找回密码
 注册帐号

扫一扫,访问微社区

士郎 如何当好反派?——用Unity简单复刻《勇者别嚣张》

32
回复
3330
查看
[ 复制链接 ]
排名
1
昨日变化

7442

主题

7985

帖子

3万

积分

Rank: 16

UID
1231
好友
186
蛮牛币
9422
威望
30
注册时间
2013-7-29
在线时间
3797 小时
最后登录
2019-3-26

社区QQ达人活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

2018-12-29 10:47:48 显示全部楼层 阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
这期准备复刻一个曾经沉迷好久的PSP上的小游戏:《勇者别嚣张》(勇者のくせになまいきだ)。

不得不说一下,这个游戏名的翻译真的是很出彩,完美体现了游戏诙谐幽默的风格。

首先简单介绍一下游戏玩法:

游戏一反玩家代表正义的传统套路,扮演一个破坏神指引大魔王攻占地表。

大魔王一开始蜷缩在地下并且没有任何战斗力,所能依赖的只有一把锄头。敲开地牢内的砖块能根据砖块吸收的养分生成不同类型的魔物。
1.1.gif


魔物并不受大魔王的控制,而是有着自己的行为逻辑与生息规则。除此之外当发现勇者时会主动展开进攻。
游戏内每间隔一段时间会有勇者来进攻,如果让勇者抓到大魔王并带出地牢,游戏就失败了。反之如果能杀光每一波来犯的勇者,坚持一定波数后大魔王就能占领地表。
1.3.gif


游戏的类型是策略类,核心玩法也是最为有趣的地方是通过敲砖块构成一个合理的生态链,来抵御勇者的攻击。
1.2.gif


接下来就用相对简单的方法尝试在Unity中去复刻这个游戏的基本玩法,那么就不多说了,开始干活。
(另:就用这篇文章祭奠我那成天上课不好好听讲,趴在最后一排玩从同桌那借来的PSP的高中生活....)

素材获取
这个步骤本来没打算写,但是想到可能会有同学在复刻小项目时受限于资源问题,加之这次寻找素材的经历有点纠结,就顺带简单提一下,权当给大家一个参考。

既然是复刻一般是先去找有没原版素材,这种游戏要是去扣图怕是得累死。从常见的素材网站找了一圈最后一无所获,只能把打主意在解包上面。
之前没有接触过PSP游戏的解包加上年代久远,寻找办法着实费了一番功夫,最后在一个PSP汉化的帖子里得到方法。

简而言之就是:
1.能被PSP模拟器识别的.iso格式文件可以直接修改扩展名为.zip进行解压缩。
2.图片类的素材文件一般保存在解压缩后的"PSP_GAME\USRDIR\data\graph""路径中。
3.PSP游戏内的图片格式是.gim,还需要专门的gim->png转换工具转换一道。

经过以上三个步骤后,得到了大部分的图片素材,但唯独缺少了勇者和怪物的动画素材。我之前认为这种像素游戏的动画可能也是以帧动画的形式逐帧保存为图片文件的,但翻遍了整个解包出来的文件夹也找不到,最后目光落在了graph文件夹内的一个文件上:

1.png

看着这个可疑的名字和大小,我猜测很大几率这就是我要找的东西,但是使尽浑身解数也没弄明白这个.fbe的格式究竟是个啥,无奈之下只能另找别的素材来代替原版的角色动画。希望知道的大佬能在评论区留言以解我心头之惑。

最后再多嘴一句,原则上来说不应该发布解包的过程和素材,无论如何也算是侵权的行为,以往文章中的复刻小项目尽量都是通过别的方法绕开的素材限制。

但这次的游戏想要自制或是替换素材都很麻烦,也很难体现原版的玩法和风格,所以大部分还是使用的原版素材,希望感兴趣的同学们仅作学习之用,不要滥用。

地图搭建
总而言之素材基本准备齐全了,开始动工。
1.png
首先开始搭建地图,原版游戏里的场景里除了上面的背景,其他都是网格状的结构。这应该是最适合使用TileMap组件的类型了,所以千万别傻傻的手动去搭地图了。

从原版来看游戏的场景地图分两种:背景图和能敲碎的砖块。关于背景图搭建就不一步步的细说了,记得有些连在一起的地面图片先切分一下,然后参考上面那篇文章,最后搭出来大概是这个样子:
2.png
这里主要简单说下能敲碎的砖块的处理。
砖块就没办法光用TileMap组件本身就能实现,因为要处理砖块敲开的逻辑并且每一个砖块都会根据所吸收的养分显示不同的样子,所以每一个砖块是独立的预制体,也就是说不能把所有砖块精灵图片纳入一个tilemap内。那是不是意味着砖块只能靠手搭?并不是。
TileMap不止是能把精灵图片按网格大小均匀排列的,同样的逻辑也能用在预制体上,这一点在官方TileMap教程视频上有展示过。
1.1.gif


这个扩展Brush(笔刷)集成在官方的2d-extras包内,下载导入GitHub上的工程包后,在Project面板中就能新建Prefab Brush,命名后拖入预制体就能使用。
3.png
然后选取刚创建的预制体笔刷,指定想要画出的预制体。如果你添加了不止一个预制体,它会在这些预制体中随机选取进行绘制。
4.png

交互逻辑
在原版游戏中唯一能做的事就是敲砖块生成道路和魔物(其实还能直接敲死魔物和迷宫中随机生成的宝箱,先暂时不考虑),接下来就是实现这个功能。
第一步是检测到鼠标点击的砖块,这里开始遇到一个小问题:砖块显示在场景内是用的Sprite Renderer组件。它无法像UI下的Image组件一样响应鼠标点击事件,如果把显示组件换成Image也感觉怪怪的,因为砖块是属于场景中的物体,跟UI并没有什么关系。
那么备选方案还有射线检测,从鼠标点击的位置打一条射线到场景中。不过同样有个问题,3D的射线无法检测到2D的碰撞盒,所以砖块上要挂上3D的Box Collider组件。作为一个2D游戏来说虽然可行,但同样显得怪怪的。
所以以上方法通通PASS,用个稍微绕一些的方法。(原因以后会说)
把整张地图看做一个二维数组,那么地图上的砖块都有一个唯一的整数二维坐标。所要做的就是在地图初始化的时候把每个砖块在场景中的坐标转换成二维数组坐标存入字典里。
在鼠标点击时获取鼠标当前的屏幕坐标转换到世界坐标再转成二维数组坐标,用这个二维坐标去字典中获取砖块。

这时候就需要自己定义一个结构来保存二维数组坐标,顺便写上转换函数:
[mw_shl_code=applescript,true]public struct Pos:IEquatable<Pos>
{
   public int x;
   public int y;

   
    public Pos(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString()
    {
        return "X:" + x + "|" + "Y:" + y;
    }

    public static Pos Float2IntPos(Vector2 pos)
    {
        int x = Mathf.FloorToInt((pos.x + 0.09f) / 0.18f);
        int y = Mathf.FloorToInt((pos.y + 0.09f )/ 0.18f);
        return new Pos(x, y);
    }

    public static Vector2 Pos2Vector2(Pos pos)
    {
        float x = pos.x * 0.18f ;
        float y = pos.y * 0.18f ;
        return new Vector2(x, y);
    }
    public bool Equals(Pos p)
    {
        if (this.x == p.x && this.y == p.y)
        {
            return true;
        }
        return false;
    }
}[/mw_shl_code]

为什么从int转float是乘以0.18f?因为图片里一个block的大小就是18*18:
5.png

注意精灵图片的Filter Mode选Point(no filter),不然像素类的图片看起来会很模糊
这样一来就把问题转换了一下,通过在字典里匹配鼠标转换坐标的方式解决了这个问题。
还有一个需要注意的地方,按原版游戏逻辑,能够消除的砖块必须是四面至少有一个缺口的,不然把大魔王方到地牢中央的死路中勇者就只能抓瞎了....
这一步就很简单了,用2D射线检测或是2D相交球检测,在执行点击操作之前判断一下周围邻居的个数。
[mw_shl_code=applescript,true]public bool IsCanBroke
    {
        get
        {
            return !CheckNeighbor(Vector2.up) || !CheckNeighbor(Vector2.down) || !CheckNeighbor(Vector2.left) || !CheckNeighbor(Vector2.right);
        }
    }

bool CheckNeighbor(Vector2 dir)
    {
        RaycastHit2D [] hit;
        hit = Physics2D.RaycastAll(transform.position, dir, 0.18f,1<<LayerMask.NameToLayer("Block"));
        return hit.Length>1;
    }[/mw_shl_code]

接下来编辑砖块的脚本,让砖块显示的的图片根据当前所储存的养料变化。
[mw_shl_code=applescript,true]public Sprite[] sprites;

SpriteRenderer Renderer;

int nutrient = 0;

private void Awake()
    {
        Renderer = GetComponent<SpriteRenderer>();
        nutrient = Random.Range(-9, 15);
        
    }
void Start ()
    {
        SpriteUpdate();

        }
void SpriteUpdate()
    {
        //change by nutrient
        int n=0;
        if(nutrient<=-50)
        {
            MonsterIndex = 5;
            n = 6;
        }
        else if(nutrient<=-30)
        {
            MonsterIndex = 4;
            n = 5;
        }
        else if(nutrient<=-10)
        {
            MonsterIndex = 3;
            n = 4;
        }
        else if(nutrient<=10)
        {
            MonsterIndex = -1;
            n = 3;
        }
        else if(nutrient<=30)
        {
            MonsterIndex = 2;
            n = 2;
        }
        else if(nutrient<=50)
        {
            MonsterIndex = 1;
            n = 1;
        }
        else if(n>50)
        {
            MonsterIndex = 0;
            n = 0;
        }

        Renderer.sprite = sprites[2 * n + Random.Range(0, 2)];
    }
public bool OnClick()
    {
        if(IsCanBroke)
        {
            if(MonsterIndex>=0)
            {
                Debug.Log("on click");
            }
            Destroy(gameObject);
            return true;
        }
        else
        {    Debug.Log("cant broke");
            return false;
        }
    }


    public void ChangeNutrient(int vaule)
    {
        nutrient += vaule;
        SpriteUpdate();

    }[/mw_shl_code]

接下来试一试效果:
1.2.gif
应该只算是把准备工作基本做完了,可以看到代码方面基本没什么难度,主要是熟悉TileMap组件的使用,即便新手也能轻松做出来。顺便说一句,TileMap能做到的事远不止如此,对画笔的自定义扩展能极大的提升开发2D游戏的效率,有兴趣的同学可以自行查阅。

本期工程地址:tank1018702/unity-005

地图搭建完毕后就应该是游戏的核心玩法:魔物和勇者的行为逻辑实现了。
功能分析
魔物行为逻辑
这个看似休闲的小游戏某种意义上来说相当硬核,怎么说?
原版游戏里的魔物生态规则看似简单,实则复杂而严谨,小时候因为没有汉化,玩得糊里糊涂,基本是很难撑到5关以后,到后来看了攻略才明白。
简单来说魔物之间除了简单的移动攻击行动外还存在着一条生态链,高级魔物吃低级魔物除了补充生命值外还能促进繁殖,魔物的生命值即使没有遭受攻击也会随着时间慢慢减少。
生成魔物的种类又与砖块吸收的养分和魔份有关系,处于魔物生态链底端的史莱姆和鬼火负责养分和魔份的供给。所以想要能抵御越来越强的勇者进攻,较为稳定的生态链是一个金字塔形状:
6.png
至于其他更复杂的养殖催生魔物进化等等就不展开说了,都快变成游戏攻略了。
总之就是游戏的魔物行为逻辑没有看起来那么简单,加上缺少动画素材,所以复刻时只选择了一些重点功能(主要是因为懒)。归纳一下就是:
1.移动
2.养分运送
3.攻击
4.食物链(大怪物吃小怪物)
5.随时间减少的生命力

勇者行为逻辑
相较来说勇者的行为逻辑就简单多了,就是找魔王->干掉路上遇到的魔物->找到魔王->捆回家这么个过程。关键就在这个找魔王上,有经验的同学大概能想到这里可能会用到寻路算法,但具体用哪一种以及怎么用?
基于观察原版游戏勇者的寻找魔王得出的规律:
1.勇者会走到岔路里。
2.如果有多个勇者,在分岔路口会主动分路寻找。
3.如果进入死路,会沿着原路返回到上一个拥有其他未探寻岔路的节点继续寻找。
其实到这结论已经呼之欲出了,这种一条道走到黑的寻路方法与之最接近的是深度优先搜索算法(Depth-First-Search),接下来所要做的就是把寻路的过程通过勇者的行动显示在游戏界面中。

角色移动逻辑
先写移动逻辑是因为这是魔物与勇者通用的方法,在上期文章实现鼠标点击逻辑时卖了一个关子:为什么选择了一个坐标系转换自定义二维数组的方法?因为移动逻辑同样也以此作为基础。
7.png
因为游戏里场景内的物体都是遵循TileMap里网格大小的正方形,所以把世界坐标转换为我们自定义的二维数组坐标后,移动相关的逻辑就变成一个类似控制台小游戏的移动逻辑了。
假设现在要从游戏地图里的蓝点沿着最短曼哈顿距离移动到红点,如果没有这个自定义的二维坐标,计算起来会非常麻烦。而现在就简单了,设蓝点为(0,0),右和上分别为X,Y的正方向,那么移动到红点的路径则可以表示为:(0,0)->(0,-1)->(0,-2)->(0,-3)->(1,-3)->(2,-3),再用之前写好的转换函数得到场景内的实际坐标,两点之间用差值计算移动过程。
8.png

实现代码:
[mw_shl_code=applescript,true] protected IEnumerator Move(Vector2 dir)
    {
        Vector2 correctionPos = Pos.Pos2Vector2(Pos.Float2IntPos(transform.position));
        Vector2 endPos = correctionPos + (dir * 0.18f);
        if (!isMoving)
            yield return SmoothMovement(endPos);
    }

    protected IEnumerator MoveTo(Vector2 pos)
    {
        if (!isMoving)
            yield return SmoothMovement(pos);
    }

IEnumerator SmoothMovement(Vector2 endPos)
    {
        isMoving = true;

        float Distance = Vector2.Distance(new Vector2(transform.position.x, transform.position.y), endPos);


        while (Distance > float.Epsilon)
        {
            Vector2 newPos = Vector2.MoveTowards(new Vector2(transform.position.x, transform.position.y), endPos, MoveSpeed * Time.deltaTime);
           
            transform.position = newPos;

            Distance = Vector2.Distance(new Vector2(transform.position.x, transform.position.y), endPos);
        }
      
        isMoving = false;
        yield return null;
    }[/mw_shl_code]

以上是纯移动逻辑,在之后加上动画状态机运行效果如下:
1.3.gif


相当于每个每个网格的中心就代表一个唯一的二维数组坐标,所以移动的路径也一定是呈网格状的
魔物状态机
在上面的分析中已经把魔物所需要的的功能都罗列出来了,其中移动已经实现了。现在要做的就是把剩下的功能实现并用状态机组装成魔物的AI。
状态机大家应该不陌生,首先把魔物的动画状态机做出来:
9.png

2D游戏四方向动画里,左右方向一般是用一套素材。因为动画本身没变,只是朝向改了。在代码里旋转角色或者Scale改成-1都行。这里特地在画图软件里镜像了一份相反朝向的素材是错误的做法,主要因为强迫症犯了...
很简单的四方向动画,在待机->移动->攻击之间切换,然后思考一下魔物状态机的流程:
10.png
大概流程就是这样,这一步大家自己实现时不必完全一样。
表现到代码里其实很简单,首先是单方向的障碍检测函数,根据layer返回检测结果:
   [mw_shl_code=applescript,true] Vector2 Direction2Vector2(Direction dir)
    {
        Vector2 direction = Vector2.zero;
        switch (dir)
        {
            case Direction.Up:
                direction = Vector2.up;
                break;
            case Direction.Down:
                direction = Vector2.down;
                break;
            case Direction.Left:
                direction = Vector2.left;
                break;
            case Direction.Right:
                direction = Vector2.right;
                break;
        }
        return direction;
    }
  protected bool ObstacleCheck(Direction dir, LayerMask layer)
    {
        RaycastHit2D[] hit;
        hit = Physics2D.RaycastAll(transform.position, Direction2Vector2(dir), 0.18f, layer);
        return hit.Length > 0;
    }[/mw_shl_code]

然后是各个行为函数:
   [mw_shl_code=applescript,true] protected IEnumerator CheckAndAttackEnemyAround()
    {
        Collider2D[] arounds = Physics2D.OverlapCircleAll(transform.position, 0.09f, Enemylayer);

        if(arounds.Length>0)
        {
            
            targetDir = GetDirection(transform.position, arounds[0].transform.position);
            yield return Attack(targetDir);
         
        }
      
    }
protected IEnumerator Attack(Direction dir)
    {
        RaycastHit2D[] hit;

        
        while (ObstacleCheck(dir, Enemylayer, out hit))
        {
            _animator.SetBool("MoveState", false);

            _animator.SetInteger("Dir", (int)dir);
            yield return null;

            _animator.SetTrigger("Attack");

            for (int i = 0; i < hit.Length; i++)
            {
                Character c = hit.transform.GetComponent<Character>();
                if (c)
                {
                    c.OnBeHit(damage);
                }
            }
            yield return AttackInterval;
        }
    }
IEnumerator Idle()
    {
        float time = idleTime;
        while(time>float.Epsilon)
        {
            time -= Time.deltaTime;
            CheckAndAttackEnemyAround();
            yield return null;
        }
    }
    protected virtual IEnumerator TransportNutrients()
    {
        RaycastHit2D[] hit;
        if (ObstacleCheck(CurDir, WallLayer, out hit))
        {
            _animator.SetTrigger("Attack");

            Block script = hit[0].transform.GetComponent<Block>();
            if (script)
            {
                script.ChangeNutrient(nutrient);
            }

        }
        yield return IdleTime;
    }[/mw_shl_code]

然后把流程逻辑写在一整个协程函数中:
[mw_shl_code=applescript,true] protected virtual IEnumerator Action()
    {
        while (true)
        {
            RandomDir();

            if (Random.Range(0, 4) > 2)
            {
                yield return Idle();
            }
            else
            {
                yield return null;
                if (ObstacleCheck(CurDir, TempLayer))
                {
                    yield return Behaviour();

                    continue;

                }
                yield return Move(CurDir);
            }
        }
    }[/mw_shl_code]

应该有人注意到了所有行为逻辑的代码都是协程,这里为什么要用到协程?
因为不管是移动过程也好,动画播放也好都要涉及到时间,也就是每个单独的逻辑函数可能执行时间都不一样,全部用协程实现会让状态转移的时候更加方便。
所以整个AI本质上是一个协程状态机,然后把魔物的预制体填入砖块生成中,运行试一试效果:
1.4.gif



勇者寻路逻辑
与魔物不同,勇者的AI其实就是一个寻路的过程,在移动过程中如果遇到了魔物就会暂时中断并与魔物展开战斗,所以攻击的行为是写在移动过程里的。剩下要做的事就是把这个寻找的过程表现出来。
之前分析的时候说最接近这个表现形式的寻路算法是DFS,为什么这么说呢?把每个格子看做一个节点,当前格子能去往下一个格子的路线看做分支,那么整个地图可以看做是一张无向图:
11.png
再看看《算法导论》中关于DFS的说明:
深度优先搜索算法所使用的策略就像其名字所隐含的:只要可能,就在图中尽量“深入”。深度优先搜索总是对最近才发现的结点v的出发边进行探索,直到该结点的所有出发边都被发现为止。
一旦节点v的所有出发边都被发现,搜索则"回溯"到v的前驱结点(v是经过该节点才被发现的),来搜索该前驱结点的出发边。该过程一直持续到从源节点可以达到的所有结点都被发现为止。
如果还存在尚未发现的结点,深度优先搜索将从这些未被发现的结点中任选一个作为新的源节点,并重复同样的搜索过程。该算法重复整个过程,直到图中的所有结点都被发现为止。
简单点来说就是在每个结点探寻到新的路,都以那条最后找到的路为最优先级走到黑,一旦无路可走再返回到之前还有未探寻路径的结点继续走到黑,这恰好与分析中原版游戏中的勇者寻路规律一模一样。
把游戏里的地图转换成结点图来看如下:
12.png
假如现在起点是A,终点是B,在结点1的位置进入向下的岔路,则会一直走到C结点。然后原路返回至1,再探寻其他的路直到找到B为止。
把寻路算法用步骤用代码表示出来如下:
[mw_shl_code=applescript,true]using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Hero : Character
{

    Dictionary<Pos, bool> map;
    Pos target;

    bool IsFindTarget;

  IEnumerator HeroSearchRoad(Pos next)
    {
        //把当前世界坐标转换为二维数组坐标
        Pos cur = Pos.Float2IntPos(transform.position);
        
      
        //如果找到目标,跳出递归
        if (next.Equals(target))
        {
            IsFindTarget = true;
            yield return new WaitForSeconds(2f);
            yield break;
        }
        //移动到下一个目标点
        yield return MoveTo(Pos.Pos2Vector2(next));

        //字典中标记下一个坐标点为已探寻,防止回旋绕路
        map[next] = false;

        //获取当前位置能移动到的其他节点坐标
        List<Pos> curNode = GetCurrentNode();

        //根据与目标距离对节点目标进行排序(可选)
        if (curNode.Count > 1)
        {
            curNode.Sort((a, b) => Pos.GetManhattanDistance(a, target).CompareTo(Pos.GetManhattanDistance(b, target)));
        }

        
        //沿着所有开辟出的新节点寻路
        for (int i = 0; i < curNode.Count; i++)
        {
            //找到目标就不继续其他节点的探索了
            if (IsFindTarget)
            {
                break;
            }
            yield return HeroSearchRoad(curNode);

        }

        //所有能走的节点都走完了,只能回头
        //回溯
        yield return MoveTo(Pos.Pos2Vector2(cur));
    }



List<Pos> GetCurrentNode()
    {
        List<Pos> list = new List<Pos>();

        List<Pos> resut = new List<Pos>();

        Pos cur = Pos.Float2IntPos(transform.position);

        if (!ObstacleCheck(Direction.Up, WallLayer))
        {
            list.Add(new Pos(cur.x, cur.y + 1));

        }
        if (!ObstacleCheck(Direction.Down, WallLayer))
        {
            list.Add(new Pos(cur.x, cur.y - 1));

        }
        if (!ObstacleCheck(Direction.Right, WallLayer))
        {
            list.Add(new Pos(cur.x + 1, cur.y));

        }
        if (!ObstacleCheck(Direction.Left, WallLayer))
        {
            list.Add(new Pos(cur.x - 1, cur.y));

        }
        for (int i = 0; i < list.Count; i++)
        {
            if (!map.ContainsKey(list))
            {
               
                resut.Add(list);
                map.Add(list, true);
            }
            else
            {
               
                if (map[list] == true)
                {
                    resut.Add(list);
                }
            }
        }
        return resut;
    }
}[/mw_shl_code]

代码里除了原本的逻辑,额外多加了一步:在每次探寻下一个结点时优先选择相对目标点最近的那一个,所以最后勇者的寻路算法就变成了有距离指导的DFS。
弄个稍微复杂点的地图,运行的效果如下:
1.5.gif


为演示效果行走速度调的略快,有些鬼畜...
结尾
至此游戏虽不完整,但主体逻辑已经还原的七七八八了。限于篇幅,还有一些参数调整和提升表现力的工作就由大家自己去尝试出专属己的规则。 1.6.gif




通简单复刻,可以发现这样简单的小游戏其实蕴含了相当多的细节在里面,虽然整体算不上太难,但积累起来就很可观了。
我们只是把最基本的核心逻辑还原就费了不少功夫,更别说原版游戏还有更多游戏性上的细节工作。现在的游戏画面日趋精良,但像这样设计师的心思都花在玩法上的游戏反而少了。
扯得有些远了,但愿能有更多像这样能带给玩家最本质的乐趣的游戏吧,感谢观看至此。
本期工程地址:tank1018702/unity-005
知乎@沈琰

参与人数 2鲜花 +7 收起 理由
小青年2号 + 5 很给力!
野麻 + 2 逻辑全都梳理出来了

查看全部评分总评分 : 鲜花 +7

回复

使用道具 举报

7日久生情
1514/5000
排名
1839
昨日变化

10

主题

254

帖子

1514

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
252607
好友
5
蛮牛币
18244
威望
0
注册时间
2017-11-5
在线时间
518 小时
最后登录
2019-3-26

一掷千金

2018-12-29 11:39:55 显示全部楼层
233333333333333333333333333333333333
回复 支持 反对

使用道具 举报

4四处流浪
449/500
排名
8329
昨日变化

3

主题

176

帖子

449

积分

Rank: 4

UID
252298
好友
1
蛮牛币
459
威望
0
注册时间
2017-11-3
在线时间
125 小时
最后登录
2019-3-26
2018-12-29 13:52:51 显示全部楼层
77777777777777
回复

使用道具 举报

7日久生情
2209/5000
排名
1393
昨日变化

0

主题

685

帖子

2209

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
135463
好友
0
蛮牛币
266
威望
0
注册时间
2016-1-23
在线时间
634 小时
最后登录
2019-3-26
2018-12-30 08:59:54 显示全部楼层
6666666666666666666
回复 支持 反对

使用道具 举报

4四处流浪
359/500
排名
7476
昨日变化

3

主题

78

帖子

359

积分

Rank: 4

UID
146160
好友
1
蛮牛币
87
威望
0
注册时间
2016-4-20
在线时间
108 小时
最后登录
2019-1-1
2019-1-1 16:24:22 显示全部楼层
很强大啊,值得学习一下
回复 支持 反对

使用道具 举报

5熟悉之中
823/1000
排名
5174
昨日变化

1

主题

287

帖子

823

积分

Rank: 5Rank: 5

UID
258102
好友
1
蛮牛币
1031
威望
0
注册时间
2017-12-6
在线时间
257 小时
最后登录
2019-3-8

迈向小康

2019-1-2 09:26:36 显示全部楼层
回复

使用道具 举报

5熟悉之中
875/1000
排名
2980
昨日变化

0

主题

184

帖子

875

积分

Rank: 5Rank: 5

UID
264326
好友
0
蛮牛币
9728
威望
0
注册时间
2018-1-14
在线时间
195 小时
最后登录
2019-3-26
2019-1-2 12:50:16 显示全部楼层
回复

使用道具 举报

7日久生情
1904/5000
排名
939
昨日变化

7

主题

247

帖子

1904

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
26016
好友
1
蛮牛币
2868
威望
0
注册时间
2014-5-21
在线时间
542 小时
最后登录
2019-3-22
2019-1-2 15:01:11 显示全部楼层
厉害厉害    下载 学习学习学习   哈哈哈
回复 支持 反对

使用道具 举报

5熟悉之中
985/1000
排名
2236
昨日变化

1

主题

131

帖子

985

积分

Rank: 5Rank: 5

UID
232255
好友
1
蛮牛币
1278
威望
0
注册时间
2017-7-15
在线时间
225 小时
最后登录
2019-3-26
2019-1-3 08:42:53 显示全部楼层
6666666666666666
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1128/1500
排名
2333
昨日变化

1

主题

157

帖子

1128

积分

Rank: 6Rank: 6Rank: 6

UID
236305
好友
1
蛮牛币
1573
威望
0
注册时间
2017-8-7
在线时间
362 小时
最后登录
2019-3-26
2019-1-3 10:44:12 显示全部楼层
牛皮 学习了。。。
回复

使用道具 举报

5熟悉之中
633/1000
排名
8912
昨日变化

0

主题

340

帖子

633

积分

Rank: 5Rank: 5

UID
36289
好友
0
蛮牛币
8
威望
0
注册时间
2014-7-25
在线时间
161 小时
最后登录
2019-3-26
2019-1-3 16:33:15 显示全部楼层

很强大啊,值得学习
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1422/1500
排名
1701
昨日变化

0

主题

220

帖子

1422

积分

Rank: 6Rank: 6Rank: 6

UID
224749
好友
1
蛮牛币
2159
威望
0
注册时间
2017-6-2
在线时间
434 小时
最后登录
2019-3-26
2019-1-3 16:59:50 显示全部楼层
回复

使用道具 举报

3偶尔光临
249/300
排名
12881
昨日变化

0

主题

120

帖子

249

积分

Rank: 3Rank: 3Rank: 3

UID
307869
好友
1
蛮牛币
265
威望
0
注册时间
2018-12-11
在线时间
59 小时
最后登录
2019-3-4
2019-1-3 20:43:58 显示全部楼层
277777777777777777777777777
回复 支持 反对

使用道具 举报

7日久生情
4702/5000
排名
1669
昨日变化

0

主题

3310

帖子

4702

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
185339
好友
1
蛮牛币
3371
威望
0
注册时间
2016-11-20
在线时间
614 小时
最后登录
2019-3-26
2019-1-4 10:48:14 显示全部楼层
{:88:}
回复

使用道具 举报

7日久生情
2645/5000
排名
2230
昨日变化

1

主题

1679

帖子

2645

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蛮牛币
2729
威望
0
注册时间
2015-8-21
在线时间
335 小时
最后登录
2019-3-23
2019-1-4 16:29:40 显示全部楼层
谢谢楼主大大。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则