• Unity3D独立游戏开发日记(二):摆放建筑物


    在沙盒游戏里,能自由建造是很重要的特点,比如说风靡全球的《我的世界》,用一个个方块就能搭建出规模宏大的世界。甚至有偏激的人说,没有自由建造,就不是一个真正的沙盒游戏。的确,沙盒游戏的魅力有很大一部分是能自由构建一个游戏世界。看着自己一砖一瓦搭建起一个城堡世界会很有成就感的。

    现如今的手游,大多数就是一个争斗和炫耀的世界。不管是传奇类的狂霸拽酷,还是连连看,消消乐等好友排名,就是消费国人的虚荣心。其实,游戏是第九艺术,要上升到艺术的角度。在游戏里,玩家需要一种情感的宣泄和寄托以及体验。

    说了这么多,还是回到正题。我是如何在自己编写的独立游戏《中世纪之路》里实现建筑物的摆放呢?

    在实现这个功能的时候,我首先考虑的不是代码。而是考虑其他游戏是如何实现这样功能的,有那些可以借鉴的地方。我首先想到了《魔兽争霸》里建筑物的摆放,可以自由的拖放。然后想到了《部落冲突》(简称COC)里的建筑放置方式,在COC里建筑是按格子摆放的,在一个平面上。

    在我的游戏《中世纪之路》里,我既不想让玩家一砖一瓦的搭建建筑,又想让玩家一下就把建筑整体放置到地上了。我觉得像搭积木那样,既可以节省玩家时间,又有建造的乐趣。选定了这种方式后,我就要考虑建筑模块的数据储存方式了:我需要储存单个模块的三维空间里的坐标值(x,y,z的值),同时为了让模型选择,我还需要储存模型的旋转角度数。

    然后,我还需要考虑如何用鼠标去执行这个操作。我构想的方案是:点击背包里的物品后,一个模型就动态产生,然后跟随鼠标在地面移动。然后按"E"键就放置到地面上鼠标所指的位置(E键放置,是我学《兽人必须死》得来的)。而组件的旋转呢?我考虑的是让鼠标滚轮(也就是中键)滚动时,就绕着Y轴旋转。当然如果更近一步,可以做到绕三个轴都能自由旋转。具体操作见下图:

    建筑摆放动画

    使用鼠标放置篝火

    通过上面的操作,我们可以看到代码的实现效果很好,完美达到了我们的需求。不过想通了上面的操作原理后,还需要我们动脑筋来构思,如何用代码来实现这些操作功能。这对新手来说可能过难了点,但对于有经验的开发者就能比较快的找到近似的解决方案,然后加以改进。

    我首先想到的是我曾经在手机上做过2D积木的搭建功能。把我们的操作动作拆解开来,无非就是三个步骤:

    1.第一次按下手指或者鼠标,找到初始坐标,让物体动态出现在坐标位置上。

    2.然后判断移动情况,让物体跟随鼠标或者手指的移动。

    3.最后抬起手指或者鼠标,让物体固定在最后的坐标位置,把坐标数据写入到文本或者数据库里。

    想清楚了这三个步骤,我们就心里底了,我们只要实现了这三步操作代码,基本上物体摆放功能就可以实现了。

    在我实现第一个步骤的时候,我就遇到了个问题。我之前在写搭积木游戏的时候是2D的,坐标很好获取。但是在《中世纪之路》里,我可是要获取的是鼠标在地面上的坐标点啊。我开始用的是两行代码:

    Vector3 mousePosition= Input.mousePosition;    //获取鼠标所在的坐标
    
    Vector3 mouseWorldPosition =Camera.main.ScreenToWorldPoint(mousePosition) ; //把鼠标的坐标变成3D游戏世界里的空间坐标

    我以为我的思路是没问题的,结果运行代码一看。哈哈,建筑物完全不是摆在地面上的啊,是在空中的啊。后来查了相关文档才知道,这个鼠标位置是鼠标在屏幕这个立体面上的位置(你可以想象屏幕是一个透明的立体墙,这个墙是树立在地面上的)。再说得专业点,是从主摄像机为起点,鼠标所指为终点的一个射线,与屏幕所在立体面相交的点。对于这个立体面的直观感觉,大家可以看看在3D空间里2D UI界面所在的那个面。

    所以,上面两行代码是不能够解决问题的。不过这么一分析,我就接触到了射线的概念。我转念一想,如果我找到这个射线和地面相交的焦点不就行了嘛!我们的思路就变成了以下的伪代码:

    1.创建一个从主摄像机为起点,鼠标所指为终点的一个射线Ray。

    2.找到Ray和地面Terrain的交点position。

    3.把物体的坐标动态创建于position。

    核心代码如下:

    if (Input.GetMouseButton(0))
     {
         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    
           RaycastHit hit;
    
           if(Physics.Raycast(ray, out hit))
           {
    
                   if(hit.transform.name=="Terrain")
                   {
                               position = hit.point;//得到与地面碰撞点的坐标
                   } 
            }
    }

    解决这个核心难题后,移动的代码就很好写了。只要在手指或者鼠标移动的时候,动态更新物体的坐标为新的position就可以了。旋转物体的代码也可以写到一起,动态更新物体的旋转度就可以了。这段代码很容易写了,我就不贴出来了,新手可以锻炼自我动手能力。老手早就不用看在眼里了。当然需要提醒的是这段代码是需要在update函数里去运行的。

    最后的固定物体坐标的代码,也就演变成了把最后的position记录于文本或者写入数据库了。这些代码都不是有多难写的。

    最后回顾下,我们整个解决问题的思路:

    构思操作步骤和方式->分解操作步骤->用代码实现分解后的操作步骤->完善和修正代码

    如果大家问我要整个代码,坦白的说:我觉得"授人于鱼,不如授于渔"。大家能获取正确解决问题的思路就够了,然后记住核心代码就可以了。做程序员到一个“手中无剑,心中有剑”的境界就够了。当然新手还是多练练剑,比划比划下招式。

    PS:游戏DEMO试玩群:198035671  Unity3d技术交流群:308185833  斗鱼游戏开发直播地址:www.douyutv.com/unity3d

  • 相关阅读:
    【平衡规划】JZOJ4616. 【NOI2016模拟7.12】二进制的世界
    函数中,如何修改形参的默认值
    默认形参在函数定义阶段就已经被赋值,在调用时就可以不用再次赋值了。
    在函数调用时:位置形参、位置实参、关键字实参的特点
    return之为什么能够终止函数,代码演练
    深度理解return具体用法
    函数基础重点掌握内容:创建函数、return返回单个值、return返回多个值、函数名加括号与不加括号的区别
    python之encode和decode编码
    python利用setsockopt获得端口重用
    python并发之多进程
  • 原文地址:https://www.cnblogs.com/wangergo/p/5001810.html
Copyright © 2020-2023  润新知