独立游戏到目前为止的所做的内容

到目前为止主工程已经有 241个提交,从2019年12月20日起到2021年3月。讲实话,很佛系的开发速度。

不过从多年的挖坑经历来看,已经坚持最久的一个了。

2019年12月~2020年2月底

虽然不能说完全不会 U3D,但是并没有系统性的学习过,这一时期主要还通过U3D自己的一个FPS项目来学习一些基本概念以及资产之间的交互。添加基础场景和角色单位进行移动,阴影设置,光照探针,场景烘焙测试,内置UI,以及光源和下雨特效等。

嗯,墙就是N个CUBE拖出来的

可以看出墙体因为是直接 CUBE 改缩放得来的,所以UV不会变,烘焙的效果会有点奇怪,意识到可能还是得自己建模。于是决定跑去学 Blender。

2020年3月到6月

这一时期工作过后都在学习 Blender,为什么是 Blender?不是游戏业界常用的 3dsmax 之类。因为,那些软件要用的话,版本有点难找。Blender 免费精巧,又没有一大堆东西,就它了。

当时是在一个不存在的网站看的这个视频学习的 Blender:

https://www.youtube.com/watch?v=TPrnSACiTJ4

照着它做了个甜甜圈:

依葫芦画瓢

当然,做完90%的东西就忘记,目前也只会用来建个基本模型而已。

后面照着另外一个教程做了点室内内容:

没有渲染图,将就下

Blender 的快捷键与vim有异曲同工之妙,vim不看教程是不会退出的,Blender 不看教程是不会拖得动那个新建工程的默认方块的。

再后来研究看有没有快速拖墙体的插件 (Archipack 之类),结果发现大部分并不适用于导出到 Unity 中使用。

因为 UV 布局的问题,当初都是先手动备份,然后 apply 布尔之类的效果,调整缝合边,布局 UV,再导出 fbx 到Unity中使用,所以快速建墙的插件会有些问题。但是这样又有另外一个问题,当场景被改版了的话,上述的调整缝合边和 UV 布局又得再来一次。

后面发现 Unity3d 可以直接导入 Blender 源文件:

https://docs.unity3d.com/Manual/HOWTO-ImportObjectsFrom3DApps.html

原谅我没有好好看文档,这一块可能还要再看看。

而后就是漫长的佛系低低模(是的你没有看错不是lowpoly, 比它还要low)建模阶段,这段时间工作也比较忙,整到 Unity 里大概是这么个效果:

室内测试1
室内测试2
室内测试全景
室外测试1
室外测试2
室外测试全景。窗户的排列和随机明亮当然是写代码生成的,一个个拖要拖死

看起来还是有点怪怪的,不过大致场景的基本验证可以到这里结束一个阶段。

2020年7月到8月

个人原因无进度2个月

2020年9月到2021年3月

这个阶段主要着手于基础框架,之前的一些基础代码基本上都是 just work。

配置表系统

配置表通过csv格式的文件来定义原始配置。代码生成到 C# 类文件,运行时读取二进制内容反序列化到实体类。

支持常用数据类型以及它们的数组类型,扩展自定义数据类型扩展。扩展的自定义数据类型可以做很多导出时的数据检查以及数据转换(例如资源路径变成哈希值路径,直接配置的显示文本转换成id,并提取到 language 文件)。

目前基本够用。未来可能需要扩展 patch 机制以适应从 AssetBundle 加载额外的数据,目前只能一口气把所有内容加载完。

状态机行为树系统

单一使用行为树并不理想。不方便于事件驱动(当然也可能是我孤陋寡闻),例如 playmaker 的状态机进入了状态之后,之后仅限于并行或串行执行 action。

于是乎自己纯手动打造了一套基础行为树和状态机,状态机的状态被替换成行为树。同时状态机也和配置表一样,从二进制初始化实例。

通过反射导出所有相关 C# 相关的 Action 和 ParentTasks 元信息到 Lua 文件,类似于如下的代码

--- @function Create FsmAction 'LogAction'
--- @param _message any
function _ENV.Action.Debug.Log(_message)
    local __fb = fsmbuild.Create()
    __fb:WriteUInt8(1) -- action
    __fb:WriteUInt16(5) -- id
    -- write "_message"
    __fb:WriteString(_message, "LogAction._message");
    return __fb:GetBytes()
end

然后就支持了从 Lua 构建状态机配置的功能,一个 Lua 配置的状态机如下,本质上它的功能就是生成一堆二进制内容:

local waitAndFinish = Parent.Sequence() {
    Action.Wait(2),
    Action.Event.DispatchFsmEvent(Enum.FsmEventTypes.FINISHED)
}

DefFsm "Test1" {
    StartState (State.DUMMY_1),

    DefState (State.DUMMY_1, Parent.Parallel() {
        Action.Debug.LogError("DUMMY_1"),
        Parent.RepeatForever() {
            Parent.Sequence() {
                Action.Wait(0.5),
                Action.Debug.Log("DUMMY_1 wait"),
            }
        },
        waitAndFinish
    }, { -- transition
        { Enum.FsmEventTypes.FINISHED, State.DUMMY_2 },
    }),

    DefState (State.DUMMY_2, Parent.Sequence() {
        Action.Debug.LogError("DUMMY_2"),
        waitAndFinish
    }, { -- transition
        { Enum.FsmEventTypes.FINISHED, State.DUMMY_3 },
    }),

    DefState (State.DUMMY_3, Parent.Sequence() {
        Action.Debug.LogError("DUMMY_3"),
        Action.Debug.Break(),
        waitAndFinish
    }, { -- transition
        { Enum.FsmEventTypes.FINISHED, State.DUMMY_1 },
    }),
}

这样设计的唯一考虑点有两点:

第一点。其它依赖状态机的子系统可以放到 Lua 来做,例如要实现一个对话系统,它的对话层次结构可能非常深:

-- 未实现的伪代码
DefDialog "dialog_1" {
    {
        show = "你选择了A",
        exec = {
            {
                show = "你确定要A?"
                exec = {
                    Action.AddItem("A"),
                    Action.EndDialog(),
                }
            },
            {
                show = "我不要了"
                exec = {
                    Action.EndDialog(),
                }
            },
        }
    }
}

例如对话选项 A->B->C->D->E 之后 E 可能会退回到 D 或者结束整个对话,或者是跳到另外一个对话树A->B2->C2->D2->E2,的 E2 位置。

这里可以由 DefDialog 展开为平行结构的状态机配置。

如果用树状结构的配置来配置对话的话,明显比直接配置平行结构的状态机要友好,强行把树状结构配成平行结构,内容少的话还好,多的话心智负担非常重,重构难。

但是为什么对话系统不直接用行为树?因为行为树本质上还是一个深度优先执行的树(常规思路来讲,忽略乱序随机执行节点)。每个父节点内部含有状态,想要随机跳到一个任意一个节点去执行逻辑并不行,会破坏行为树本身的功能。这种任意跳转还是状态机合适。

第二点。Lua 生成状态机的话,可以把关键内容放到服务器提供弱联机/防盗版/MOD支持。

还是上面的例子,在不改变配置的情况下,可以把对白展开成多个状态机而非一个状态机。本地配置只有一个入口状态机,点击对话后,请求服务器获得下一级对白状态机并展示。而且返回回来的对白中状态机包含的下一级对白索引,可以根据当前客户端IP地址随机来索引下一个状态机。和关键NPC的对白和主线任务的每一个步骤必须走服务器可以提供一定程度上的防盗版(这里要点名夸奖无主之地3,网络不好hotfix没加载进来武器都变菜了)。

弱联机的话可以通过某些公共状态返回特殊的行为。

MOD支持的话,把 Lua 部分核心源码公开。可以提供用户编写状态机的功能,而且客户端最终加载的仅仅只是二进制配置,不会执行 Lua 虚拟机,不存在安全隐患。

基础框架搭建以及常用子系统

逻辑层显示层逻辑分层,消息机制解耦,对象缓存池,资源加载,AssetBundle 导出加载,AssetBundle 名哈希混淆,内置编辑工具面板,等等。

后续

后续还需要集成 fairygui,弄点界面(是的我只会弄黑白的),还要抽象一下内部模块。目前就一个战斗场景模块 stage,然后得学习下 PhotoShop 纸片人物制作了。

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注