发现一个宝藏Unity开源AVG框架,视觉小说的脚手架

是真正意义上的AVG完整框架:

我发现这个框架的时候是第二个,第一个框架挺垃圾的,还花了一些时间

https://github.com/Lunatic-Works/Nova/wiki

曾经我师傅说过:

凡是都是2:8原则,当你一生有十个女朋友,那么第三个女朋友是最适合结婚当老婆的

不要怀疑,后面越选,越挑剔,反而越来越差

明显,这是非常科学,非常合理的

同理,我本来计划挑五个框架,所以第2~3个开源项目就是最好的

结果,第二个项目,就让我发现这个框架了

关键字,**AVG,框架,ToLua,**这些就不详细展开说了,直接说功能;

假设你对AVG有兴趣,想做点游戏,又刚好会点编程,然后又在找一些入门资料(这个框架对程序友好),这个框架就挺适合你

Show简单背景

Lua 复制代码
Show(bg,'')

其中,bg是Lua的名字,也可以在Inspector(节点 Sprites/BackgroundImage , SpriteController.cs)修改对应,总而言之,言而总之,就是Lua那一套,也必须大小写区分

*一般不需要改bg,fg,且作为保留关键字吧

人物加载+动画

创建角色,需要严格执行下面步骤:

  1. 在Unity Editor的Hierarchy中的Characters下找到示例角色的game object(如Ergong),把它复制一份
  2. 将game object的名字改成你的角色的名字(如Xiaoming),这个名字不会用在脚本中,只是为了在Hierarchy中好看
  3. 在Inspector中的GameCharacterController component中(刚才克隆的GameObject):
    • luaGlobalName改成你的角色的名字(如xiaoming),脚本中使用的都是这个名字,注意大小写
    • 这个名字必须符合Lua变量名的要求,只能包含字母、数字、下划线,并且不能以数字开头,不能包含中文、空格、其他符号等等
    • imageFoldervoiceFolder改成对应的文件夹
  4. Resources/目录,复制一份,(框架是期待有Body Face Normal等几个贴图的,我暂时用单一贴图测试)

所有这些步骤都在 Main.unity 唯一第一场景中管理

导入

  1. 把所有部件放到Assets/StandingsUncropped/<角色名称>/文件夹下
  2. 可以适当剪裁,框架提供功能

我跳过,只要保证Resources/ 目录下有图片,就不会卡lua 代码(会报错,但是不会卡主流程)

按钮Command--二次开发

Lua 复制代码
<|
show(hui1999,'normal')
--show(gaotian, 'normal')
add_button()
|>
@<| jump_to 'tut01_2' |>

@<| label 'tut01_2' |>

稍微解释一下;

Assets/Nova/Lua/目录下必有几个.lua脚本,例如alert.lua,框架启动时必然全部加载一遍;所以才能调用 alert() 弹出对话框,所以,我们只要添加一个自定义方法 add_button();必然可以全局调用,测试结果: log 正确;就可以实现各种扩展功能

至于怎么扩展,怎么能在文字中间插入按钮,希望框架能顺带提供API(其实已经是无限扩展,脚手架都已经搭好),C# + ToLua + Unity本身就无敌,即使Unity是闭源,并不开放

配音

??

一些游戏视觉小技巧

<纯口嗨,不详细提供例子和演示了>部分内容就在这个AVG框架内有提供API

  • 改颜色Tint
  • 基于Tint改变白天黑夜
  • 滤镜
  • 过场
  • 2D~2.5D动画

如何清空原来已完成的剧情历史

???

有几个很坑的 lua 代码的点

假如复制一个对话文件(代码),非常容易犯错"指向错误"

又因为国人很喜欢简写英文+ 喜欢数学

所以经常会:

t000001

t000001_2

i0001_2

就非常容易傻傻分不清楚

t 和 00001根本没有任何含义,写了等于没写

其实复制了两份,aa.lua , bb.lua 原始文件, tut01.lua

所以最红 bb.lua 把 aa 和 tut01 的都覆盖了

对话结束和分支的代码逻辑

结束

Lua 复制代码
--Script_loader.lua

--- set the current node as an end node
--- should be called at the end of the node
--- a name can be assigned to an end point, which can differ from the node name
--- the name should be unique among all end point names
--- if no name is given, the name of the current node will be used
--- all nodes without child nodes should be marked as end nodes
--- if is_end() is not called under those nodes, they will be marked as end nodes automatically
function is_end()
    if not check_eager('is_end') then
        return
    end
    __Nova.scriptLoader:SetCurrentAsEnd()
end
cs 复制代码
//ScriptLoader.cs
        public void SetCurrentAsEnd()
        {
            CheckCurrentNodeDefaultLocale();
            currentNode.type = FlowChartNodeType.End;
            flowChartGraph.AddEnd(currentNode);
            currentNode = null;
        }

//其实就很简单,粗暴,的 Set CurrentNode,和 FlowChart的节点处理

分支

  • 设置剧情分支 branch(branches)
    • branches是一个array,每个元素是一个table,它的key有dest, text, mode, cond
    • dest是一个字符串,表示目标节点的名称
    • text是一个字符串,表示显示在按钮上的文本
    • mode是一个字符串,可以为'normal'|'jump'|'show'|'enable'
      • 'normal'表示普通选项,此时cond必须省略
      • 'jump'表示如果cond返回true,就会直接跳转到目标节点,否则不显示这个选项,此时text必须省略
        • 如果省略cond,则一定会跳转
        • 如果有多个选项是'jump',则会按顺序测试每个cond,按照第一个返回true的来跳转
      • 'show'表示如果cond返回true,这个选项才会显示
      • 'enable'表示如果cond返回true,这个选项才能点击
    • cond是一个函数,没有参数,返回一个boolean
      • 如果cond是字符串,就会把它parse成表达式,然后转换成返回这个表达式的函数
      • 这个函数必须没有副作用
      • 这个函数必须返回boolean,我们不会进行类型转换,因为Lua会把任何数值和字符串转换为true
    • 具体的例子可以参考Assets/Resources/Scenarios/test_branch.txt

分支代码实现-按钮逻辑

Lua 复制代码
--- add branches to the current node
--- should be called at the end of the node
--- should be called only once for each node, i.e., all branches of the node should be added at once
--- branches should be a list of 'branch'. A 'branch' is a table with the following structure:
--- {
---    dest = 'name of the destination node'
---    text = 'text on the button', should not use if mode is jump
---    image = {'image_name', {x, y, scale}}, should not use if mode is jump
---    mode = 'normal|jump|show|enable', optional, default is normal
---    cond = a function that returns a bool, should not use if mode is show, optional if mode is jump
--- }
--- if cond is a string, it will be converted to a function returning that expression
function branch(branches)
    if not check_eager('branch') then
        return
    end

    for i, branch in ipairs(branches) do
        local name = tostring(i)

        local dest = try_get_local_name(branch.dest)

        local image_info = nil
        if branch.image then
            local image_name, image_coord = unpack(branch.image)
            local pos_x, pos_y, scale = unpack(image_coord)
            image_info = Nova.ChoiceImageInformation(image_name, pos_x, pos_y, scale)
        end

        local mode = Nova.BranchMode.Normal
        if branch.mode == nil or branch.mode == 'normal' then
            -- pass
        elseif branch.mode == 'jump' then
            mode = Nova.BranchMode.Jump
        elseif branch.mode == 'show' then
            mode = Nova.BranchMode.Show
        elseif branch.mode == 'enable' then
            mode = Nova.BranchMode.Enable
        else
            warn('Unknown branch mode: ' .. dump(branch.mode) .. ', text: ' .. dump(branch.text))
            return
        end

        local cond = branch.cond
        if type(cond) == 'string' then
            local cond_str = cond
            cond = loadstring('return ' .. cond)
            if cond == nil then
                warn('Failed to parse cond: ' .. cond_str)
            end
        end

        if __Nova.scriptLoader.stateLocale == Nova.I18n.DefaultLocale then
            __Nova.scriptLoader:RegisterBranch(name, dest, branch.text, image_info, mode, cond)
        else
            __Nova.scriptLoader:AddLocalizedBranch(name, dest, branch.text)
        end
    end
    __Nova.scriptLoader:EndRegisterBranch()
end
cs 复制代码
void RegisterBranches(..._)
{
    currentNode.type = FlowChartNodeType.Branching;
    lazyBindingLinks.Add(new LazyBindingEntry(currentNode, destination,
                new BranchInformation(name, text, imageInfo, mode, condition)));
}
cs 复制代码
//GameState.cs
  private void StepAtEndOfNode(FlowChartNode node)
        {

            //.....

            switch (node.type)
            {
                case FlowChartNodeType.Normal:
                   
                    break;
                case FlowChartNodeType.Branching:
                    ExecuteAction(DoBranch(node));
                    break;
                case FlowChartNodeType.End:
                   
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
cs 复制代码
//GameState.cs -> DoBranch(...)

            //....

            AcquireActionPause();

            RaiseChoices(choices);
            while (coroutineHelper.fence == null)
            {
                yield return null;
            }

            ReleaseActionPause();

            var index = (int)coroutineHelper.TakeFence();
            SelectBranch(node, choiceNames[index]);

UI的处理和显示Buttons

最终,还是ChoiceController.cs的代码

cs 复制代码
        public void On_RaiseChoices(IReadOnlyList<ChoiceOccursData.Choice> choices)
        {
            if (choices.Count == 0)
            {
                throw new ArgumentException("Nova: No active selection.");
            }

            if (backPanel != null)
            {
                backPanel.SetActive(true);
            }

            for (var i = 0; i < choices.Count; i++)
            {
                var choice = choices[i];
                var index = i;
                var button = Instantiate(choiceButtonPrefab, transform);
                // Prevent showing the button before init
                button.gameObject.SetActive(false);
                button.Init(choice.texts, choice.imageInfo, imageFolder, () => Select(index),
                    choice.interactable);
                button.gameObject.SetActive(true);
            }

            buttons = GetComponentsInChildren<Button>();
            activeChoiceCount = choices.Count;
        }
相关推荐
运维-大白同学2 小时前
2025最全面开源devops运维平台功能介绍
linux·运维·kubernetes·开源·运维开发·devops
Aileen_0v08 小时前
【Gemini3.0的国内use教程】
android·人工智能·算法·开源·mariadb
wei_shuo9 小时前
100% AI 写的开源项目三周多已获得 800 star 了
开源·开发者·sealos
快乐的学习15 小时前
开源相关术语及提交commit关键字总结
驱动开发·开源
沉默金鱼1 天前
Unity实用技能-格式化format文字
ui·unity·游戏引擎
NocoBase1 天前
8 个最佳 Google Sheets 替代方案(附成本与能力分析)
低代码·开源·github
jyy_991 天前
通过网页地址打开unity的exe程序,并传参
unity
8***v2571 天前
开源模型应用落地-FastAPI-助力模型交互-进阶篇-中间件(四)
开源·交互·fastapi
love530love1 天前
【笔记】ComfUI RIFEInterpolation 节点缺失问题(cupy CUDA 安装)解决方案
人工智能·windows·笔记·python·插件·comfyui