UNITY way to explore https:https://cdn.v2ex.com/navatar/428f/ca9b/463_normal.png?m=1442837324 https:https://cdn.v2ex.com/navatar/428f/ca9b/463_large.png?m=1442837324 2025-09-15T01:14:51Z Copyright © 2010-2018, V2EX unity 国区账号如何下载 unity 商店资源? tag:www.v2ex.com,2025-09-15:/t/1159192 2025-09-15T01:14:51Z 2025-09-15T01:14:51Z zuiluo member/zuiluo unity 可以做三维建模 vr 效果吗 tag:www.v2ex.com,2025-07-30:/t/1148782 2025-07-30T07:29:21Z 2025-07-30T08:41:28Z qwqqwq member/qwqqwq 把实验室场地的 cad 图纸导入草图大师建好模然后导入 unity,导入之后不知道怎么做了,之前没学过 unity ,有没有简单的办法在 unity 中渲染然后实现链家的看家 vr 效果,后期可能要实现数字孪生(这个得之后再说了

]]>
gaia pro 问题请教 tag:www.v2ex.com,2025-06-09:/t/1137315 2025-06-09T03:07:31Z 2025-06-09T03:06:31Z kevin1ee member/kevin1ee 基于 unity 想创建一个尽可能真实的地形 gaia pro ,heightmap ,还有地形纹理,植被等,想请问下,相关数据资源从哪里可以免费/付费获取到呢? 谢谢!

]]>
Cursor 等一众 AI 编程工具目前和 Unity 游戏开发的结合怎么样了? Unity 游戏开发想用 AI 提高生产率目前都有什么比较好的方案或者工具? tag:www.v2ex.com,2025-02-19:/t/1112656 2025-02-19T07:14:41Z 2025-03-05T16:06:34Z leenjewel member/leenjewel Unity 股价只剩三年前的 10%了(200USD=>20USD)会不会被哪个大公司收购? tag:www.v2ex.com,2025-02-16:/t/1111826 2025-02-16T12:34:02Z 2025-02-16T13:32:02Z drymonfidelia member/drymonfidelia Unity 6 现在国区不让下载了 tag:www.v2ex.com,2025-02-16:/t/1111772 2025-02-16T06:59:02Z 2025-02-16T17:27:48Z nathandoge member/nathandoge 估计是为了推国内特供版 团结引擎,真没见过这么恶心的公司

]]>
Unity 答疑,任何问题都可以 tag:www.v2ex.com,2024-09-19:/t/1074178 2024-09-19T12:51:52Z 2024-12-06T10:02:55Z zeroonetwo member/zeroonetwo 自己是游戏多年从业者,各阶段的公司都待过,游戏和引擎相关的各种问题,都可随意聊聊。
长期有效。 ]]>
当我用 Unity 开发游戏的时候有什么场景下要使用“栈”操作? tag:www.v2ex.com,2024-07-06:/t/1055293 2024-07-06T06:42:17Z 2024-07-05T14:41:17Z huzhikuizainali member/huzhikuizainali 背景资料,我与 ChatGpt 的两轮问答:

我问:“当我用 unity+C#开发一款游戏,在什么场景下需要我“显式”的使用“栈”?”

ChatGpt: Unity 游戏开发中的使用场景 1 、深度优先搜索( DFS )和迷宫生成:在需要遍历图形结构、树结构或生成迷宫时,深度优先搜索算法可以使用栈来管理节点的访问顺序。

2 、回溯算法:在处理需要回溯的算法,如求解谜题、路径查找等,可以使用栈来保存状态信息。

3 、撤销操作:在游戏中实现撤销/重做功能,可以使用栈来保存状态快照。

我问:你提到游戏开发中涉及“深度优先搜索( DFS )和迷宫生成”。这些问题难道 Unity 还没有解决么?还需要 C#程序员自己用栈来实现么?

ChatGpt:Unity 提供了强大的工具和组件来简化游戏开发,但有些特定的算法和逻辑需要开发者自己实现,尤其是当涉及到游戏的核心玩法设计和特定的算法需求时。深度优先搜索( DFS )和迷宫生成就是这样的例子。尽管 Unity 提供了一些基础功能,但具体的算法实现通常需要开发者根据需求自行编写。以下是一些需要开发者使用栈来实现的场景:

深度优先搜索( DFS ) DFS 是一种常用的图遍历算法,特别适用于以下场景:

1 、路径查找:虽然 Unity 提供了 NavMesh 进行路径查找,但在某些自定义图形结构(如自定义的迷宫或节点网格)中,开发者可能需要自行实现路径查找算法。

2 、游戏逻辑:在实现复杂的游戏逻辑时,如 AI 决策树、任务树等,DFS 可以帮助遍历和处理这些树状结构。

迷宫生成 迷宫生成是一个常见的游戏功能,特别是在随机生成游戏世界或关卡时。以下是一些迷宫生成算法:

1 、深度优先搜索生成迷宫:这种算法可以生成完全随机的迷宫。通过使用栈来跟踪路径,可以实现迷宫的生成。

2 、Prim's 算法:这是一种基于最小生成树的迷宫生成算法,也可以通过栈来实现。


我的问题:

1 、请各位老师看看 Chatgpt 第二轮回答是对的?还是在进行无意义的重复?像迷宫和路径查找这样的游戏中典型场景真的还需要 C#程序员用算法手动实现么? Unity 真的还没有做好让 C#程序员直接调用的方法或函数?

2 、游戏我玩过,但是 Gpt 提到的“求解谜题” “撤销操作” 分别对应什么游戏场景?能举个例子么?为什么这些场景需要用到“栈”?

]]>
为什么 Unity 自带的资源加密功能是中国版特供的? tag:www.v2ex.com,2024-06-12:/t/1049079 2024-06-12T20:39:45Z 2024-06-20T07:34:07Z drymonfidelia member/drymonfidelia Unity 远程开发岗位,感兴趣小伙伴看过来 tag:www.v2ex.com,2024-05-22:/t/1043030 2024-05-22T09:58:07Z 2024-05-30T08:57:46Z Brand001 member/Brand001 职位职责:
1 、使用 Unity3D 进行游戏客户端核心模块开发,负责游戏核心架构的搭建以及技术攻关;
2 、负责与策划、服务端沟通确定开发需求,协调服务端、策划、美术之间的工作;
3 、熟悉 Unity3D 引擎架构设计,熟悉 Unity3d 常用插件,至少掌握一种 UI 工具,精通 3D 图形学原理,了解 Unity3D 的 shaderlab 语言和物理引擎;
4 、熟悉 3d 引擎中的动画原理,熟悉 unity 中的动画解决方案;
5 、负责程序开发工作确保项目程序进度;
6 、配合项目负责人完成项目制作;
7 、能够提出各种客户端疑难问题的解决方案。
职位要求:
1 、了解 Unity3D 引擎架构设计,熟悉 C#语言(同时熟悉 c/c++优先考虑)及 lua 语言;
2 、有严谨的逻辑思维能力,善于阅读代码,自学能力强;
3 、有分析与解决问题能力;
4 、至少经历过一款游戏的完整开发期并担任重要职位,有完整参与上线项目的经验者优先。
5 、有 AVG 品类游戏开发经验优先。
工作方式及语言:
全职远程工中作
语言:中文
远程工作时间:北京时间 10:00 - 20:00 ,午休 2H ,周末双休+法定节假日
简历投递邮箱: zygs2151@gmail.com ]]>
unity 2D 像素精灵的生成有推荐的方法吗? tag:www.v2ex.com,2024-05-03:/t/1037612 2024-05-03T16:58:05Z 2024-05-03T17:56:05Z Fffys member/Fffys
  • 看到官方有推出付费版的 Muse ,不知道效果怎么样,价格每月 30 刀感觉有点小贵
  • Midjourney 也是收费的,还没有尝试,而且需要制作动画,看到有人说 MidJourney 制作动画不如 Stable Diffusion ;
  • Stable Diffusion 有尝试了,但是感觉不是很好调整,每生成一个精灵都要花很长的时间,动画目前还在摸索,刚接触 SD 没几天目前还没有成功制作动画。
  • 还有其它的方法推荐吗?

    或者,unity 之外的选择,只要能够用来做 2D 像素游戏,并且 2D 精灵的生成使用 AI 比较方便的?

    ]]>
    远程 Unity 开发工程师(英国) tag:www.v2ex.com,2024-04-19:/t/1033837 2024-04-19T02:21:35Z 2024-04-21T11:39:13Z Brand001 member/Brand001 职位职责:
    1 、使用 Unity3D 进行游戏客户端核心模块开发,负责游戏核心架构的搭建以及技术攻关;
    2 、负责与策划、服务端沟通确定开发需求,协调服务端、策划、美术之间的工作;
    3 、熟悉 Unity3D 引擎架构设计,熟悉 Unity3d 常用插件,至少掌握一种 UI 工具,精通 3D 图形学原理,了解 Unity3D 的 shaderlab 语言和物理引擎;
    4 、熟悉 3d 引擎中的动画原理,熟悉 unity 中的动画解决方案;
    5 、负责程序开发工作确保项目程序进度;
    6 、配合项目负责人完成项目制作;
    7 、能够提出各种客户端疑难问题的解决方案。
    职位要求:
    1 、了解 Unity3D 引擎架构设计,熟悉 C#语言(同时熟悉 c/c++优先考虑)及 lua 语言;
    2 、有严谨的逻辑思维能力,善于阅读代码,自学能力强;
    3 、有分析与解决问题能力;
    4 、至少经历过一款游戏的完整开发期并担任重要职位,有完整参与上线项目的经验者优先。
    5 、有 AVG 品类游戏开发经验优先。
    工作方式及语言:
    全职远程工中作
    语言:中文
    远程工作时间:北京时间 10:00 - 20:00 ,午休 2H ,周末双休+法定节假日
    简历投递邮箱: zygs2151@gmail.com ]]>
    求推荐点 Unity 入门教程 tag:www.v2ex.com,2024-02-18:/t/1016296 2024-02-18T08:54:03Z 2024-02-18T09:54:03Z manasheep member/manasheep 请教个语音听写的问题 tag:www.v2ex.com,2023-12-12:/t/999583 2023-12-12T01:51:18Z 2023-12-11T01:51:18Z JackMaMa member/JackMaMa 自己用 unity 开发了数字人,支持两个主要功能都使用了 Windows 自带的语音听写和唤醒能力:

    • 语音唤醒(关键词)
    • 语音交互

    现在碰到两个问题

    1. 语音唤醒时,麦克风会占用,这时候去交互时,偶尔会没有响应,可能是没有释放
    2. 最小化后再返回,肯定无法唤醒

    有搞过类似的大佬给点思路?

    ]]>
    Unity 打包后出现 "trial version" tag:www.v2ex.com,2023-11-02:/t/988000 2023-11-02T11:48:14Z 2023-11-02T13:51:50Z sunny1688 member/sunny1688 是这样的,项目 A 是用 Unity Hub 创建的一个 3D 项目,项目 A 的开发、打包都没有问题,一切都是正常的,

    假设项目 A 存放的路径是:/project/A

    由于最近要开发新项目,可以基于项目 A 去做开发,但是要隔离项目 A ,所以我复制项目 A 到一个新的文件夹,当成新的项目去做开发,这里叫做项目 B 。

    cp -r /project/A/* /project/B 

    在项目 B 上已经开发了一些功能,Unity 编辑器也可以正常跑起来、没有报错、能 build 项目成功(iOS)。

    用 xcode 打包到手机上,打开 App ,右下角出现 "trial version" 标识,Unity 许可证没有过期(个人版)。

    项目 A 并没有"trial version" 标识,是不能这样复制项目吗?

    请问这是什么原因?有遇到过的吗?有解决方案吗?求救,谢谢大佬们

    ]]>
    有没老哥用 m2 开发 unity 的 tag:www.v2ex.com,2023-10-26:/t/985724 2023-10-26T09:13:13Z 2023-10-26T12:18:44Z ouou0701 member/ouou0701 家里只有台老 pc 性能不好,自己有台 m2 mac,在用来开发 unity2d 游戏,用起来是不卡的,性能也够用。 就是不知道怎么回事,unity 特别容易崩,最近一天崩五六次,内存也够用。直接给整自闭了 用 Macbook 开发是因为续航和性能都很好,就不想去搞之前的老 pc 除了 unity 老崩溃,没其他毛病了 有老哥知道这是怎么回事吗,还有的救吗

    ]]>
    如何看待 Unity 新出的每次安装收费? tag:www.v2ex.com,2023-09-15:/t/974113 2023-09-15T06:55:05Z 2023-09-15T14:48:29Z famon member/famon

    中文主题网站 https://blog.unity.com/cn/news/plan-pricing-and-packaging-updates ]]> 为什么 Unity 的定价策略增加了对安装量的限制? tag:www.v2ex.com,2023-09-14:/t/973768 2023-09-14T07:16:33Z 2023-09-14T08:13:33Z woodfizky member/woodfizky https://blog.unity.com/news/plan-pricing-and-packaging-updates

    不能理解是什么支撑 Unity 做出这样的决定。

    用户可以在多设备上安装同一个游戏,甚至因为存储空间问题在一个设备上反复安装卸载同一个游戏,但是同一个游戏(买断制),用户通常只会买一次。



    后续又看到有说法,unity 表示只会计算同一个平台的同一个游戏的初始下载,那么问题来了,这个怎么统计?涉及收集用户数据了吧? ]]>
    unity 将于 2024 年 1 月调整收费模式 tag:www.v2ex.com,2023-09-13:/t/973463 2023-09-13T09:33:43Z 2023-09-13T09:57:01Z CloveAndCurrant member/CloveAndCurrant unity 将于 2024 年 1 月调整收费模式,按安装次数计费,每安装一次都要掏钱。 具体可以查看 unity X: https://twitter.com/unity/status/1701650081403842851 对于大公司(买断制),影响不大,对于小企业以及独立开发者是巨大打击。

    ]]>
    [Unity 离奇 Bug] 游戏在编辑器内运行 ok,构建出的 exe 却不能在本机正常运行 tag:www.v2ex.com,2022-12-26:/t/904814 2022-12-26T11:34:56Z 2023-03-27T01:21:28Z Back2life member/Back2life 目前在尝试独立开发游戏,现学 C#编程,一路上困难重重,但今天遇到了一个最致命的。

    我每次在 Unity 编辑器内运行,或者 [文件] -[构建并运行],游戏都能正常运转,但是,当我在编辑器外,双击打开构建出的 exe 文件来运行游戏,却发现游戏内容变得残缺。具体表现是,除了带有贴图的基本物理对象正常显示之外,其他的内容都没有被运行出来……

    不知道论坛有没有碰巧会 Unity 的大佬,能够指点迷津,或者大家觉得 Godot 这个引擎怎么样…… (இдஇ; )

    补充: 游戏的资源例如贴图和预制件都放在 Resources 文件夹; 数据库存放在 StreamingAssets 文件夹,sqlite3.dll ( SQLite4Unity3d 插件); 游戏存在大量的 标签赋予 和 查找带标签的对象 的语句

    ]]>
    3 维空间中,求过原点的直线的方向向量? tag:www.v2ex.com,2022-02-23:/t/836040 2022-02-23T14:38:43Z 2022-02-23T20:06:34Z zeronofreya member/zeronofreya 标准 XYZ 空间,只知道直线的旋转角度,怎么求方向向量?

    比如旋转角度为:

    (30, 30, 30) 

    工作几年后这些东西全忘了,也不知道该发哪,作业帮么……

    有会的朋友指点一下吧,谢谢

    ]]>
    求推荐资源 - 俯瞰视角场景设计相关 tag:www.v2ex.com,2021-04-13:/t/770382 2021-04-13T09:05:21Z 2021-05-21T17:44:05Z Zhuzhuchenyan member/Zhuzhuchenyan 各位好,

    小弟最近在做一款俯瞰视角的基于网络对战小游戏,(不知道俯瞰视角这个词汇用的对不对,各位可以脑补下王者荣耀这种 MOBA 类游戏的视角,有不对的话还请指出)

    目前游戏原型的开发正在有条不紊的进行,之前专注于游戏系统的设计,美术资源一切从简,场景里基本都是条条框框的简约线条。Alpha 测试版本发布之后,测试玩家吐槽很大的一点就是极为单调的场景,所以目前想在这方面做点改进。

    毕竟是小团队,没有这个能力和资本去维护一套自己的美术风格,所以希望能找个大而全的素材包将整个战斗场景的需求包进去。正好 Unity 资源商店春季打折了,所以想来这里问一下各位有什么和场景设计素材相关的 3D 资源推荐的嘛?

    不在 Unity 商店的资源也可以,毕竟海外商店的资源基本没有国风资源可以参考, 这里拜谢,

    Zhuzhu

    ]]>
    震惊,有个框架竟然能快速让 Unity 新手制作可热更游戏!竟然可以让老项目快速对接具备热更性质! tag:www.v2ex.com,2020-12-06:/t/732682 2020-12-06T10:11:29Z 2020-12-06T10:08:29Z JasonXuDeveloper member/JasonXuDeveloper 在这里安利一个自己的框架,JEngine,该 repo 建立了接近 5 个月,已经接近 400star,多个公司项目已完成对接,框架质量就不用多说了

    JEngine 是针对 Unity 开发者设计的开箱即用的框架,封装了强大的功能,小白也能快速上手,轻松制作可以热更新的游戏 链接:https://github.com/JasonXuDeveloper/JEngine

    JEngine 有大量视频教程及文档,并且可以实现资源热更+C#代码热更,再也不需要写 Lua 了!

    与此同时 JEngine 还支持将热更类拖拽赋值到 GameObject 上,对小白可谓是无敌的友好;

    JEngine 还有很多强大的功能等你发现!

    ]]>
    大佬们 学习 Unity 遇到的两个问题求助 tag:www.v2ex.com,2020-11-23:/t/728440 2020-11-23T10:46:20Z 2020-11-23T11:42:13Z rizon member/rizon 背景:我是用 mac +rider 开发

    问题 1: 如图,这里的生成 csproj 的两个选项,不知道该不该勾选,csproj 的概念我理解就是让编辑器更好的工作的,可是我勾选和不勾选在 rider 中实在看不出区别。 所以麻烦帮忙解释下这个选了对 rider 来说有什么好处?该不该勾选?? 以及勾选这两个选项分别的作用,每个能带来什么好处。

    问题 2: rider 安装 mono 和.net core 应该装哪个,这两个的概念我查资料理解了,目前也是装了 mono,但是还是好奇 用.net core 可以开发吗?具体有啥区别?

    ]]>
    有人对 unity 的 photon 组件熟悉的嘛,想有偿咨询几个问题 tag:www.v2ex.com,2020-10-23:/t/717992 2020-10-23T11:46:00Z 2020-10-23T12:16:44Z aichipipizhu member/aichipipizhu 参加了个 jam 比赛。弄联机弄的焦头烂额

    ]]>
    用 Unity3d 来做跨平台开发是不是很好? tag:www.v2ex.com,2020-09-30:/t/711988 2020-09-30T12:32:01Z 2020-10-01T23:09:26Z TangMonk member/TangMonk Unity 的 UIWidgets 看起来还不错,别说 Windows, Mac,估计甚至连 ps,Xbox 都可以跨平台,而且加上一点 3d, 粒子特效,比一般的 UI 都要酷炫。

    有人用过吗?移动开发和 Flutter 比比怎么样?桌面开发和 QT 相比又如何?

    ]]>
    对于 U3D Stopwatch ElapsedTick 的疑惑 tag:www.v2ex.com,2020-06-11:/t/680811 2020-06-11T13:06:44Z 2020-06-11T13:45:23Z bakatori member/bakatori 记录方法

     static public void time (string key) { if (timeKeyHub.ContainsKey (key)) { Debug.LogError ("MLOG.cs time key already exist"); } else { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch (); timeKeyHub.Add (key, sw); sw.Start (); } } static public void timeEnd (string key) { if (timeKeyHub.ContainsKey (key)) { System.Diagnostics.Stopwatch sw; timeKeyHub.TryGetValue (key, out sw); sw.Stop(); Debug.Log (string.Format ("<color=blue>{0}</color> {1} ms {2} ticks", key, sw.ElapsedMilliseconds, sw.ElapsedTicks)); // BIRDTODO:用池子 sw = null; timeKeyHub.Remove (key); } else { Debug.LogError ("MLOG.cs timeend key not exist"); } } 

    A*算法的部分代码

     MLOG.time("newCostDealer"); MLOG.time("newCostDealer - newCost"); float newCost = cost_so_far[cur]+ heuristic(next.Pos, end.Pos); MLOG.timeEnd("newCostDealer - newCost"); MLOG.time("newCostDealer - 0"); bool tmp2 = !cost_so_far.ContainsKey(next); MLOG.timeEnd("newCostDealer - 0"); MLOG.time("newCostDealer - temp3"); bool tmp3 = false; if (!tmp2) { tmp3 = newCost < cost_so_far[next]; } MLOG.timeEnd("newCostDealer - temp3"); if (tmp2 || tmp3) { MLOG.time("newCostDealer - 1"); frontier.put(next, newCost); MLOG.timeEnd("newCostDealer - 1"); MLOG.time("newCostDealer - 2"); cost_so_far[next] = newCost; // MLOG.info("came_from[next] = cur," + next.ToString() + " " + cur.ToString()); came_from[next] = cur; MLOG.timeEnd("newCostDealer - 2"); } MLOG.timeEnd("newCostDealer"); 

    运行结果:

    tIfWA1.png

    求教,为啥 newCostDealer ticks 和其它记录加起来差那么多丫?!

    ]]>
    有偿提供 unity 虚拟面试~ tag:www.v2ex.com,2020-05-11:/t/670495 2020-05-11T05:12:09Z 2020-08-30T07:05:05Z omxian member/omxian 大家觉得有的搞吗?有这个市场吗? 作为 unity 多年的从业者,有偿提供 unity 中低级的虚拟面试,给新人一些发展建议! 有兴趣的人可以留言。

    ]]>
    问一下各位大哥关于买网络授课是否有必要的问题 tag:www.v2ex.com,2020-05-01:/t/667919 2020-05-01T08:50:03Z 2020-05-01T08:47:03Z BottleOpener member/BottleOpener https://edu.manew.com/course/471 感觉好像挺实用的,我基础一般,c#略精通,目前项目用的也是 c#。

    想问一下各位,有必要买这个课学习嘛,毕竟有点小贵,还挺纠结的。

    工作之前从来没用过 uniy,工作以后也没有系统的学过 unity 或者热更新,也不了解服务器的知识,虽然平时工作没问题,但心里总是没底,总觉得少学了点东西。 ]]>
    请问通过 unity WebGL 能播放 rtmp,可以实现吗? tag:www.v2ex.com,2020-04-16:/t/663211 2020-04-16T13:17:53Z 2020-04-16T13:16:53Z MuscleOf2016 member/MuscleOf2016 [UnityTips] 一种仿 Minecraft 物品合成的思路 tag:www.v2ex.com,2020-03-04:/t/649703 2020-03-04T03:34:10Z 2020-03-04T04:29:57Z unn member/unn 本文转自 Unity Connect 博主 超级汽水

    最近在做一个小游戏,遇到了合成问题这里分享一下制作思路~ 📕

    本篇只讲解一下大概思路提供部分代码,完整的代码应该会在以后和项目一同提供 ~ 💡💡💡 Minecraft 中的合成概述

    image

    在 Minecraft 中,最基本的合成台是一个 2 * 2 的合成,我这里以 A、B、C、D 来代表它的四个格子。 合成分为俩种合成 :(在 Minecraft 中不单单只有这俩种,我这里为了方便介绍简化了)

    1. 有序合成

    2. 无序合成

    有序合成是指所有的物品必须按照指定的位置摆放,位置摆放不正确是无法合成的比如斧头、锄头、盔甲等,这也是本文中着重讲解的内容,因为无序合成情况比较复杂我会在日后专门提出来尝试说明。

    image

    仿制 2 * 2 合成思路 : 对合成有了大致概念以后我来说说我对这种对位置有要求的合成的仿制方案:

    1. 对游戏中所有的物品设置唯一物品 ID,用来代表这个物品可以用数值或者字符串。

    2. 对游戏中所有物品设置唯一合成 ID,用来代表这个物品的合成公式,这里用字符串来代表。

    3. 对合成台的每个格子设置唯一标识 ,如上文中的 ABCD。

    4. 合成时将格子的标识与格子中物品的 ID 组合为一个字符串,空格用 * 来表示得出合成公式。

    5. 与已有的合成公式做比对,找到结果。( 搜索可以优化的地方很多,能力有限就不写出来误人子弟了 )

    也就是说一个物品它拥有俩种身份标识,一种是唯一的 ID 给程序进行判断它是什么物品;另外一个是它的合成配方,表示它的合成方法,这个可以有多种。

    物品具有俩种标识

    接下来我们尝试表示合成 💕 的过程 : 假设我们要使用 🍗 (炸鸡) + 🍺 (啤酒) 合成 💕 (快乐)。

    提前将 💕 的公式写好,然后将它保存好。如果游戏中有大量的公式需要保存也可以试试 SQLite 数据库,不过通常情况下只需要保存为 Json 或是 YAML 格式即可。 根据 💕 合成公式 A001BCD002,我使用图来表示的话是这样 :

    image

    这样一个简单的合成系统设计就完成了,但是这个系统并不完美还需要后期优化许多我这里也只是起到一个抛砖引玉的作用。 🤗 代码设计

    using System.Collections.Generic; using UnityEngine; public class CraftingTable : MonoBehaviour { // 合称表 private Dictionary<string, int> _itemMakeTables = new Dictionary<string, int>(); // 物品表 private Dictionary<int, string> _itemNameTables = new Dictionary<int, string>(); private void Start() { //添加一个合成配方合成产物为 ID 是 10 的物品 _itemMakeTables.Add("A001B*C*D002",10); //添加一个 ID 为 10 的物品 _itemNameTables.Add(10, "幸福星星"); //模拟合成 Debug.Log(Make("A001B*C*D002")); } // 物品合成函数,接受合成公式返回合成出的物品名 private string Make(string formula) { string itemName = null; //判断公式是否正确 if (_itemMakeTables.TryGetValue(formula,out int itemID)) { //根据物品 id 查询物品名字 itemName = _itemNameTables[itemID]; return itemName; } else { return itemName; } } } 

    结果

    原文链接:https://connect.unity.com/p/unitytips-yi-chong-fang-minecraft-wu-pin-he-cheng-de-si-lu?app=true

    欢迎戳上方原文链接,下载 Unity 官方技术社区 app,在线技术答疑,发现更多资源干货!

    ]]>
    ShaderLab 入门实践 tag:www.v2ex.com,2020-02-27:/t/647960 2020-02-27T03:01:28Z 2020-02-27T02:58:28Z unn member/unn 本文转自 Unity Connect 博主 汪汪

    从实践来认识 shaderlab 编程

    写这篇文章呢,主要是为了让想学习 shaderlab 编程的小伙伴们迅速了解 shaderlab 的的基本语法,和运用基本语句来实现一些简单的材质效果和动画。由于是实践入门,嘛,理论就会很少,偏向于出效果,更多的是从例子来一步一步的解析,理解,直到融会贯通。如果想深入学习的小伙伴,需要多多自己钻研,查阅资料哦。

    虽然本文不偏向于理论,但是基本的常识性问题希望大家了解。

    编写 shader 目前主流的三种高级语言有:HLSL、GLSL、CG

    HLSL 由微软推出,基于 DirectX,全名 High Level Shading Language

    GLSL 则基于 OpenGL,全名 OpenGL Shading Language

    CG 由 Nvidia 推出,全名 C for Graphic

    Shader 是 Material 的一部分,Material 是 Shader,纹理,颜色等的集合

    Shader 在 GPU 中运行

    ShaderLab 是由 Unity 封装,兼容 Unity 的着色器语言

    目前 Unity 下 Shader 的分类有三种,surface shaders,vertex and fragment shaders、fixed function shaders。

    本汪将侧重,或者只使用 vertex and fragment shaders 来进行案例的讲解

    Hello Shader!

    就像我们学习第一门编程一样,我们应该友好的对 Shaderlab 说一句:Hello!

    我们从 Unity 中创建一个 Unlit (不受光)模板看一下

    image

    “ ??汪汪你什么意思,一来就给我看这么多代码,不是说给想了解 shaderlab 的同学看吗?这么多我看得懂啊??”

    这。。。别急,别急,看本汪对上面的代码进行瘦身!

    image

    我们逐一来分析:

    首先是

    Shader"WangWang/HelloShader"

    上面我已经注释了,是对 shader 进行分组和取名,如果按照上图中的设定,那么在 Unity 里就会有如下效果哦:

    image

    在材质的 shader 选项里,出现了一个 Wangwang 的分组,接下来是 HelloShader 的材质名字

    Properties{}

    我们可以在函数体中,声明我们向外暴露的一些参数,比如贴图啊,颜色啊,数值啊等。

    下面我们在函数体中添加如下代码:


    Properties

    {

    //在此声明属性

    _BaseTex("MainTextrue",2D)="black"{}

    }


    编译成功后,我们查看一下结果:

    image

    出现了一张自定义贴图选项栏!!

    名字是:MainTextrue

    我们可以这样理解下面的代码

    _BaseTex("MainTextrue",2D)="black"{}

    **声明了一个叫做_BaseTex 的脚本内变量,并向外暴露,暴露出来的是一个“2D”类型的变量类型,名字叫做“MainTextrue”,如果没有给接口赋予任何贴图,则默认为黑色。

    SubShader{}

    上文中我们提到,编写 shader 可以有多种语言,然而这些语言其实是有局限性的。

    HLSL 只能运行在支持 DirectX 的的平台

    GLSL 只能运行在支持 OpenGL 的平台

    嘛,就是说他们两个谁都不喜欢谁,都不想和对方说话,所以无法沟通呢。≡(▔﹏▔)≡

    在这里如果是针对多个平台,多个硬件来编写的 shader,就很有可能需要多个 SubShader 哦。

    如果显卡 1 不支持第一个 SubShader 中的代码,那么就会跳到第二个 Subshader 中执行哦。就这样跳跳跳跳~

    万一都不支持怎么办呢?

    Fallback“Shader 名”

    这就是我们最后登场的角色,如果没有任何一个 Subshader 是支持这个硬件的,那就会调用 Fallback 后面的 shader。通俗的说呢,就是!!

    备胎吧。

    最后一个 Pass 因为是重点内容,这里一篇文章讲不完,会在后续详细介绍。

    好啦,本期 shaderlab 实践就到这里了,汪汪本人也是属于学习阶段,如果不足,错误之处,希望大家海涵。

    原文链接:https://connect.unity.com/p/shaderlab-ru-men-shi-jian?app=true

    欢迎戳上方原文链接,下载 Unity 官方技术社区 app,在线技术答疑,发现更多资源干货!

    ]]>
    Unity 热更新 之 如何使用 AST 转换 C# -> Lua tag:www.v2ex.com,2020-02-24:/t/647052 2020-02-24T04:15:52Z 2020-02-24T07:24:36Z unn member/unn 本文转自 Unity Connect 博主 郡墙

    本篇主要论述 如何将 C# 代码自动转换为 Lua 代码的解决方案

    方案流程

    1. 利用 Mono ceil 库分析程序集中的类、字段、方法签名,然后将其翻译成对应的 Lua 模块所模拟的类型结构

    2. 通过 ILSpy 工具分析 IL 指令集,重建由语句表达式组成的 AST (抽象语法树),并翻译成对应的 Lua 方法体

    3. 把 Lua 类型与 Lua 方法体合并成完整的 Lua 代码

    按照同样的原理可以翻译成其他的语言 其中 Mono ceil 负责从程序集中提取类、字段、方法; ILSpy (基于 Mono ceil 开发的工具) 则负责分析方法体指令序列。 架构设想

    翻译流程思路分享

    1. 类型结构翻译,通过 Mono.ceil 分析程序集中包含的所有类,以及类中定义的字段和方法,收集到这些信息后,就可以生成 Lua 对应的类型和结构及方法定义(无方法体)

    2. 方法体翻译,利用 ILSpy 将方法体中的 IL 指令序列重建成 AST,翻译工具将 AST 转换成 Lua 语句和表达式,形成 Lua 方法体

    3. 整合 1,2 步骤

    因为源码在编译后,将会对字符串、常量、枚举、计算等进行一系列优化,比如删除无效的无用代码,预处理各种字符串、减少运行时开销等 。对翻译后的 Lua 代码逻辑也是编译器优化后的。 翻译细节分析 类关系

    类成员

    方法体

    原文链接:https://connect.unity.com/p/unity-re-geng-xin-zhi-ru-he-shi-yong-astzhuan-huan-c-lua?app=true

    欢迎戳上方原文链接,下载 Unity 官方技术社区 app,在线技术答疑,发现更多资源干货!

    ]]>
    按需渲染如何改善移动端性能? tag:www.v2ex.com,2020-02-20:/t/646035 2020-02-20T02:36:00Z 2020-02-20T02:33:00Z unn member/unn 大多数时候,项目并不需要一直保持最高帧率渲染,尤其是在移动平台上。过去,Unity 开发者们常使用 Application targetFrameRate 或 Vsync (垂直同步)来限制引擎的渲染速度。这种方法在影响渲染的同时,还会对其他部分的运行速率造成一些影响。而新的按需渲染 API 允许开发者将渲染频率从整体中独立出来调整。

    什么是按需渲染? 按需渲染可以在降低渲染帧数的同时,让其他部分系统依旧以高频率运行。它在移动端的用处最大。省去部分渲染可以大幅提升性能、降低能耗,同时应用依旧可以对触控事件作出及时响应。 按需渲染的使用场景? 在以下几个情景中,降低帧率通常是更好的选择:

    1. 菜单(即应用入口或暂停菜单):菜单通常是一个较为简单的场景,不需要高频率的渲染。即使将菜单渲染速率降低,应用依旧可以在未渲染的帧上接受到指令输入。降低渲染速率后,我们就能降低能耗,通过限制 CPU 频率来防止设备温度过高,UI 互动也能依旧平滑。

    2. 回合制游戏(如象棋):在回合制游戏中,玩家需要思考下一步行动,等待对手的行动。这段时间游戏内的活动较少,我们可以降低帧率来减少不必要的能耗、延长电池寿命。

    3. 静态内容:在应用中,在大部分时间都处于静态的内容上,比如车内用户界面,可以适当地降低帧率。

    4. 性能控制:如果想要控制能耗和设备温度,在延长电池寿命的同时又不限制 CPU 性能,尤其是使用了Adaptive Performance包的情况下,可以通过调整渲染速率来实现目标。

    5. 机器学习和 AI:减少 CPU 在渲染上的工作量,可以让应用的机器学习和 AI 处理有些许的性能提升。

    按需渲染支持哪些平台? 所有平台! Unity 2019.3 的按需渲染功能支持所有平台(详情请见系统要求部分)和渲染 API (包括内置渲染管线 Built-in Render Pipeline、通用渲染管线 URP 和高清渲染管线 HDRP )。 如何使用按需渲染呢? 按需渲染的 API 包含在 UnityEngine.Rendering 命名空间中,由三种属性组成。OnDemandRendering.renderFrameInterval 该 API 是最重要的部分。我们可以用它来取得或设置渲染帧间隔,确定帧率。 渲染帧间隔是确定 Application.targetFrameRate 及 QualitySettings.vSyncCount 间隔的要素。 打个比方,我们将 Application.targetFrameRate 设置为 60,将 OnDemandRendering.renderFrameInterval 设置为 2,则系统会每隔一帧渲染一次,最终帧率便为 30fps。 ☞OnDemandRendering.effectiveFrameRate 该属性会给出应用渲染帧率的估算值。 估算值根据 OnDemandRendering.renderFrameInterval、Application.targetFrameRate 和 QualitySetting.vSyncCount 的数值,以及显示器刷新频率计算得出。请注意这只是个估算值,并不一定是实际表现;当 CPU 同时在处理其他脚本、物理模拟、网络传输等等进程时,效率难免会降低,应用的渲染速度也可能变慢。 ☞OnDemandRendering.willThisFrameRender 该 API 会告知当前帧是否会被渲染到屏幕上。我们可以在不渲染的帧上执行一些吃 CPU 性能的进程,比如复杂的数学运算、加载资源或生成预制件。 注意事项

    示例 在下方例子中,菜单上使用了按需渲染将帧率限制在了 20fps,直到有指令输入。

    using UnityEngine; using UnityEngine.Rendering; public class Menu : MonoBehaviour { // Start is called before the first frame update void Start() { QualitySettings.vSyncCount = 0; Application.targetFrameRate = 60; // When the Menu starts, set the rendering to target 20fps OnDemandRendering.renderFrameInterval = 3; } // Update is called once per frame void Update() { if (Input.GetMouseButton(0) || (Input.touchCount > 0)) { // If the mouse button or touch detected render at 60 FPS (every frame). OnDemandRendering.renderFrameInterval = 1; } else { // If there is no mouse and no touch input then we can go back to 20 FPS (every 3 frames). OnDemandRendering.renderFrameInterval = 3; } } } 

    image

    百讲不如一练,在文章的最后向各位提供一个展示按需渲染不同使用情景的示例项目

    原文链接:https://connect.unity.com/p/an-xu-xuan-ran-ru-he-gai-shan-yi-dong-duan-xing-neng?app=true

    欢迎戳上方原文链接,下载 Unity 官方技术社区 app,在线技术答疑,发现更多资源干货!

    ]]>
    怎么通过 Unity 使用 Vulkan API & 移动端 Vulkan GPU 大 PK tag:www.v2ex.com,2020-02-17:/t/645196 2020-02-17T03:59:52Z 2020-02-17T06:45:25Z unn member/unn 本文转自 Unity Connect 博主 梦想在路上的 LPP

    逢人皆言 Vulkan 好,到底好在哪里?如何在 Unity 工程中使用 Vulkan API ?支持 Vukan 的 GPU 都有哪些?我的手机支持 Vulkan 吗?为什么感觉游戏厂家都“不积极”呢,适配 Vulkan 的游戏不多呢?.......在这篇文章中一起找答案吧。

    正如并行性和多线程编程为多核 CPU 的性能提升铺平了道路一样,Vulkan 也着手打造一个多线程、跨平台的 GPU 编程和高性能渲染的未来。自面世以来 Vulkan 因为高性能和低开销而广受关注,目前已有不少支持 Vulkan 的游戏与应用如《 Doom 》《 Dota2 》、《王者荣耀》、《使命召唤》、《天涯明月刀》等。

    下面以王者荣耀为例说明 Vulkan 的高性能和低开销优势。Talk is Cheap ,Shown me the data .

    image

    1.特效全开,画面精美,人物动作行云流水,玩游戏也是一场视觉享受(貂蝉连续放大招帧率稳定在 60FPS )

    image

    2.持续高帧,打团要稳,胜利在望

    image

    3.更低功耗,更低发热,延长手机续航时间

    image

    Unity 作为一个移动平台最为成功的游戏开发平台,从 2016 年 12 月,Unity Technologies 表示 Unity 游戏引擎 5.6 版将支持 Vulkan API ;到 2017 年 3 月,Unity 5.6 提供正式支持 Vulkan API,再到 Unity 2019.2 增加了许多 Vulkan 补丁,Unity 一直在提高游戏画质与提升游戏性能的道路上奋力前行。

    如何在项目中使用 Vulkan API 进行构建

    1.从 Unity 文件菜单中选择“Build Settings”

    image

    2.从构建设置面板中选择“Player Settings…”

    image

    3.单击展开“Other Settings”面板。

    image

    4.取消选中“Windows 的自动图形 API”复选框

    image

    5.单击“+”号,选择 Vulkan 将其添加到活动 API 列表中

    image

    6.使用“-”号从活动列表中删除其他 API

    image

    该项目现在将使用 Vulkan API 进行构建。

    移动端 Vulkan GPU 大 PK

    由于 Vulkan 诞生的时间不长,因此当下市面上 GPU 并不能很好的支持,为了使大家更好的了解这方面的情况,我简要介绍移动端目前能够很好的支持 Vulkan 的 GPU。

    Android 平台下,由于没有统一的硬件标准,导致各个厂家各个型号智能手机、平板电脑的硬件配置大相径庭。目前应用在 Android 移动平台的 GPU 主要由 4 家公司提供,分别为 Imagination、ARM、高通、NVIDIA。我将对这四家公司提供的支持 Vulkan 的 GPU 进行简要介绍。如下:

    PowerVR Rogue

    PowerVR Rogue 是由 Imagination 于 2010 年发布的 PowerVR 架构,支持 Vulkan 需要的 PowerVR 6 以及更新的系列,具体情况如下。

    PowerVR Series7XT 系列是中高端图形处理器,主要型号有 PowerVR GT7200、PowerVR GT7400、PowerVR GT7600、PowerVR GT7800、PowerVR GT7900 等。Series7XT 支持 HDR 渲染、4K 纹理、物理着色等。Android 设备中使用 PowerVR 架构的 GPU 的代表性产品是魅族的 PRO7 Plus,这款手机搭载了 Helio X30 处理器,集成了 PowerVR 7XTP GPU。

    Mail 系列

    Mail 系列 GPU 是 ARM 设计出品的,其中 Midgard1-4 可以全平台支持 Vulkan API。目前主要型号为 Mail-G71、Mail-G72、Mail-T760、Mail-T820、Mail-T830、Mail-T860、Mail-T880 等

    Mail-G71 基于 Bifrost 架构,拥有非常强大的移动图像处理能力,大大降低了设计功率,以提高同等核心面积下的图形处理性。三星 Galaxy S8 系列手机采用的就是 Mail-G71 图形处理器而搭载麒麟 970 处理器的华为 P20,GPU 使用的则是 Mail-G72。

    image

    Adreno 系列

    Adreno 系列由高通推出,被广泛用于高通的 Snapdragon 平台上。其中,高通 Adreno 400 和 Adreno500 系列全平台支持 Vulkan API。目前应用较为广泛 3 款 Adreno 系列 GPU 分别是 Adreno 430、Adreno 530、Adreno 540。

    Adreno 540 与 Adreno 530 相比,将频率提高了 710MHz,提升近 14%的性能。再加上其他方面的各种优化,Adreno 540 的性能比 Adreno 530 提升达 25%,小米 6 搭载的骁龙 835 处理器就集成了 Adreno 540 的图形处理器。

    image

    GeForce ULV 系列

    GeForce ULV 系列由 NVIDIA 推出,被广泛应用于 Tegra 平台上。目前支持 Vulkan API 的型号为 Tegra x1 等。从性能上来看,NVIDIA 的 GeForce 系列芯片在整体上非常的优秀,特别在高清视频录制和播放方面以及大型 3D 游戏方面有着不错的表现。

    NVIDIA 推出的 Shield 系列游戏机以及平板都是搭载 Tegra x1,而大名鼎鼎的任天堂 Switch 也是采用的 Tegra x1 改进版。

    image

    写到这里相信已经有很多的小伙伴有个疑问:Vulkan 可以大幅度提高渲染性能,提高游戏体验,那为什么感觉游戏厂家都“不积极”呢,适配 Vulkan 的游戏不多呢???

    一句话概括就是,时间就是金钱,效率就是生命。

    尤其在游戏行业。Vulkan 是个新东西,开发团队学习它需要时间,而且即使使用了 Vulkan API,但是不花精力优化的话,效果可能比之前更差(王者荣耀 Vulkan 版也于今年已经不维护了),游戏需按照机型进行适配。而这些需要平台,手机厂家和游戏厂商一起参与优化,而且要都有意愿,缺一不可。

    原文链接:https://connect.unity.com/p/zen-yao-tong-guo-unityshi-yong-vulkan-api-vulkan-gpu-da-pk?app=true

    ]]>
    Unity3D 实用技巧 - 快速学会模型合理导入 Unity3D 引擎 tag:www.v2ex.com,2020-02-14:/t/644467 2020-02-14T02:43:17Z 2020-02-14T02:40:17Z unn member/unn 本文转自 Unity Connect 博主 北京琳云信息科技有限责任公司

    学习模型合理导入 Unity3D 引擎

     相信在游戏中,模型相当于游戏必要的元素,例如游戏的一个人物角色,我们可以称它为一个模型,今天和小编一起学习一下,导入 Unity 3d 模型,我们应该注意哪些细节!当然我们知道常用的模型都可以通过 Maya/3D Max 等三维动画软件制作的。 

    官方学习文档: https://docs.unity3d.com/Manual/FBXImporter-Model.html

    1、模型自身规范:

     1.0 所有的模型在原点创建,没有具体的特定要求下,需要以物体对象中心为轴心; 1.1 模型面数:移动端( 300-1500 个多边形达到比较好的效果) PC 端( 1500-4000 比较适宜)正常的单个物体最好控制在 1000 面以下,所有的物体面数不超过 20000 个三角面; 1.2 模型大小:模型文件做到最大的优化,对于看不到的面,我们可以采用直接删除,合并断开的点,移除孤立的点; 1.3 模型命名:命名最好做到规范化,模型在给绑定之前必须做一次重置变换; 1.4 模型复制:如果物体可以复制的话,就尽量复制;例如一个 500 面的物体,烘培好之后复制出去 50 个,那么他所消耗资源基本和一个物体消耗的资源基本是一样; 1.5 模型单位:在建模型之前设定好单位,在 Unity3d 中用到的单位设置必须要一样,模型和模型之间的比例要正确,和程序导入的单位要一致,如果到程序中单位需要缩放也尽可能统一调整缩放比例,统一单位为米; 

    2、模型导入中的规范:

     2.0 模型使用的材质,首先在 3d Max 和 Maya 中,不是所有的材质在 Unity3d 中都可以支持使用,只有 Standard (标准材质)和 Multi/Sub-Object (多维 /子物体材质)被 Unity3d 软件所支持。 2.1 模型贴图格式和尺寸:常用的图片格式是 .tga 和 .png (带透明通道) 、不带通道为 .jpg ,贴图的尺寸最好为 2 的 N 次方,最大的贴图尺寸不能超过 1024X1024,当然特定的要求,尺寸可以根据具体的需求而定。 2.2 模型贴图其他注意点: 2.2.0 贴图命名不要有中文字命名,并且不能有重名; 2.2.1 材质命名和物体名称最好保持一致,易于整理,材质球的父子层级的命名尽可能一致; 2.2.2 同种贴图可以使用同一个材质球; 2.2.3 带 Alpha 通道贴图图片格式最好为 tga 和 png 格式,在命名可以加 _al 加以区分; 2.2.4 模型需要通过通道处理时,在制作通道纹理,最好将透明部分改成模型的主色,这样渲染可以使有效边缘部分大颜色正确,通道纹理在程序渲染占用的资源同尺寸大普通纹理要多,通道命名也最好以 _al 结尾; 2.3 Unity 资源导入: 对于游戏中资源导入可以导入图片,网格等,Project 面板右键( Import New Asset )。 2.3.0 图片设置: TextureType:贴图类型。 AlphafromGrayscal :使用灰度创建 Alpha,当需要 unity 为贴图创建一条 alpha 通道,使用它转换得到的灰度值来渲染透明度,可以开启它。 WrapMode:所谓的包裹模式,决定贴图会无限次平铺还是只绘制一次。Repeat 就是重复平铺。Clamp 会让贴图的边缘拉升并在改变大小时候填满任何缝隙。 FilterMode:所谓的图像过滤模式,在拉伸时候对贴图过滤,比如 [Point] 点过滤,在很近看贴图时候,可能感觉不均。Bilinear 它会时近看贴图显得模糊。Trilinear 它会在不同 mipmap 层次之间模糊。 AnisoLevel:设置各项异性过滤器的级别,同属的可以理解为当你从一个比较陡的角度来看,贴图现实的可以有多细腻,渲染的等级越高,越占内存。想要确定贴图页面会占用多大内存,可以长 X 宽,再乘以他的位深度 [bpp,每像素位数] ,如果贴图包含 mipmap,还需要扩大 1.33 倍。 2.3.1 网格设置: 网格就是那些 Maya,C4D,3DSMAX,C3D,LW 等软件导出的文件。比如导入一个 .fbx 文件。选中后,在 Inspector 面板中,我们能看到下面的属性: ScaleFactor:缩放系数,使用这个属性在导入资源后设置其大小,调整资源在场景现实过大,或者过小的问题。 MeshCompression:网格压缩的比例( Off,Low,Medium,High )不可否认,压缩时可以节约内存的。 OptimizeMesh:为提高 GPU 性能记录网格顶点和索引。 GenerateColliders:是否在场景中让网格对象具有碰状体表现,通俗的说就是实心的,不可以穿体而过。 SwapUVs:交换 UV,当你导入一个网格对象时候,着色器可能会使用错误的 UV 通道,如果发现资源异常(比如带光照图时候),可以选中这个复选框。 Generate Lightmap UVs:为 UV2 生成光照贴图。 Normals&Tangents:该项设置计算法线切线 Normals:计算法线,import 表示使用导入的网格法线,Calculate 表示从新计算网格法线。 Tangents:计算切线,import 表示使用导入的网格切线,Calculate 表示从新计算切线。 Smoothing angle:法线平滑角度,告诉引擎在角度值为多少时候把一个边缘视为硬边缘。 Split Tangents:是否分离切线,如果模型在场景渲染后现实 UV 缝隙,要开启它。 Materials: Import Materials:是否导入材质,默认选中。 Material Naming:材质名字生成模式。 Material Search:材质搜索方案。 

    3、模型在制作软件中具体设置:

     3.0 贴图烘培设置:在进行 Completemap 烘培方式,贴图通道和物体 UV 坐标通道必须为 1 通道。 3.1 lightingMap 烘培设置时,和 completemap 设置有些不同,贴图通道和物体 UV 坐标必须为 3 通道,烘培时灯光的阴影方式为 adv.raytraced 高级光线跟踪阴影,背景色要改白色,可以避免黑边的情况,主要物件的贴图 UV 必须手动展开; 3.2 模型绑定及动画 3.2.0 骨骼必须为 IK、CAT、BIP 三类,Unity 不认虚拟动画,单个物体骨骼数量不超过 60 个; 3.2.1 动画帧率、帧数的控制,一般情况下每秒 10 帧,一个动画尽量控制 1 秒内完成; 3.3 模型导出 3.3.0 将烘培材质改为标准材质球,通道为 1,自发光 100% ; 3.3.1 合并顶点,删除多余的材质球(不重要贴图缩小); 3.3.2 物体名称、材质球名、贴图名保持一致; 3.3.3 按要求导出的 .fbx (检查是否要按组导出,导出 fbx 后,再重新导入 3D Max / Maya 中动画是否正确) ..... 上面虽然没有图片的展示,但是属于实践中的文字总结,希望能帮助到小伙伴们。 

    原文链接: https://connect.unity.com/p/unity3d-shi-yong-ji-qiao-kuai-su-xue-hui-mo-xing-he-li-dao-ru-unity3d-yin-qing?app=true 欢迎大家戳上方原文链接,下载 Unity 官方技术社区 app,在线技术答疑,发现更多干货~

    ]]>
    Unity3D 实用技巧 - 从实践中总结经验(1) tag:www.v2ex.com,2020-02-10:/t/643390 2020-02-10T02:54:08Z 2020-02-10T02:51:08Z unn member/unn 本文转自 Unity Connect 博主 北京琳云信息科技有限责任公司 巧用 Unity3D 引擎编辑器

    作为一名游戏开发者,学会巧用引擎编辑器,可以让我们创造出更多优秀的游戏作品。今天小编与大家共同浅析这些常见的实用技巧。

    活用 Unity3D 引擎编辑器技巧

    1、通过 Assets 分支保证正确的流程

    所有的 Asset 都应该只有一个唯一的版本。如果当我们需要一个分支版本的 Prefab、Scene 或是 Mesh,那么我们需要制定一个非常清晰的流程,来确定哪个是正确的版本。错误的分支应该起一个特别的名字,例如双下划线前缀:__MainScene_Backup。Prefab 版本分支需要一个特别的流程来保证安全 。

    2、Unity3D 遇到机器突然死机未保存场景的情况

    当你在 Unity3D 中编辑场景,突然死机时,可以在项目文件目录中找到 Temp 文件夹,双击文件夹,找到_Backupscenes 文件夹,把后缀为 .backup 的文件后缀改为 .unity,然后拖进 Unity3D 的 Project 界面里面,这样就可以还原死机前场景最后情况。

    3、考虑编写通用的自定义 Inspector 代码

    作为 Unity3D 的检视面板, 编辑 Inspecto r 属性时,实现自己定义的 Inspector 是非常直截了当的。而编辑器有很多类:编辑器类。例如:EditorGUI (编辑器界面), EditorGUILayout (编辑器界面布局), EditorGUIUtility (编辑器界面工具)等等这些,都是编辑器对界面进行编辑,我们都可以进行学习简单套用。

    4、学会场景对象的灵活设置

    一般我们把控制对象和场景文件夹(空 Game Objec )放在原点( 0,0,0 ),假设位置对于这个对象不重要,那么就把它放到原点。这样你就不会遇到处理 Local Space 和 World Space 的麻烦,代码也会更简洁。

    例如:

    把世界的地面放在 Y=0,这样能够更方便的把对象放到地面上。而且在游戏逻辑中,能够把世界作为 2D 空间来处理(假设合适的话),比如 AI 和物理模拟。

    把角色和地面物体的中心点( Pivot )放在底部。不要放在中间这能够使你方便的把角色或者其它对象精确的放到地板上。假设合适的话,它也可能使得游戏逻辑、AI、甚至是物理使用 2D 逻辑来表现 3D。

    4、学会正确设定对象的 Scale,应该与美术商定把所有导入的缩放系数设置为 1,并且把他们的 Transform 的 Scale 设置为 1,1,1。可以使用一个参考对象(一个 Unity 的 Cube )来做缩放比较。为你的游戏选择一个世界的单位系数,然后坚持使用它。

    5、不要让游戏运行时生成的对象搞乱场景层次结构

    在游戏运行时,为动态生成的对象设置好它们的父对象,可以让你更方便的查找。你可以使用一个空的对象,或者一个没有行为的单件来简化代码中的访问。可以给这个对象命名为“DynamicObjects”。

    单件可以作为一些管理器,例如 ParticleManager 或者 AudioManager 亦或者 GUIManager。 对于那些非唯一的 Prefab 实例使用单件管理器(例如 Player )。不要为了坚持这条原则把类的层次关系复杂化,宁愿在你的 GameManager (或其他合适的管理器中)中持有一个它们的引用。 对于外部经常使用的共有变量和方法定义为 Static,这样你可以这样简便的书写 “GameManager.Player” ,而不用写成 “GameManager.Instance.player” 。

    6、学会活用编辑器的快捷键

    按键盘键 Q、W、E、R、T 可以依次切换界面上的小工具。除此之外,按数字键 2 或 3 还可以切换场景为 2D 模式或 3D 模式。如果觉得在场景视图中调整对象视角太不方便,可以按住右键结合键盘上的 A、S、W、D、Q、E 键像走路一样调整到合适的视角,然后点击 GameObject 菜单下的 Align With View 即可将游戏视图的视角与场景视图同步。

    7、如何应对脚本的运行出错

    如果脚本出现错误,Unity 编辑器会因为检查到出错而无法进入运行模式,这时可以在项目视图中新建文件夹 WebplayerTemplates,然后将出错的脚本拖入此文件夹下,所有位于该文件夹下的文件都会被识别为一般文件从而不会当作脚本被编译,这样就可以运行游戏了。

    8、掌握 Unity3D 引擎编辑器中可用的调试工具

    Unity3D 中的调试工具有许多功能,包括可以有效帮助用户调试游戏的功能,例如 Debug.Break,Debug.Log ,Debug.DrawRay 和 Debug.DrawLine 等函数。前两个函数用于理解游戏状态,而后两个函数可帮助用户直观地调试游戏。用户还可以使用调试可视检查器来查找运行时的私有字段,调试可视化检查用户需要以不同方式来调试可视化检查。Visual 调试器可以通过生成图表来帮助用户实现这一点,用户可以使用 Monitor Components 执行此操作。

    9、掌握 Unity3D 引擎编辑器中可用的测试工具

    Unity3D 发布了许多测试工具,这增加了用户的调试改进方法,Unity3D 的测试工具不仅适合正式测试,而且还可以便于进行可以在编辑器中运行,同时无需场景运行的 Scratch-pad 测试。

    活用游戏项目工作流程技巧

    工作流程改进技巧旨在帮助用户改进游戏开发过程。使用户的项目完成得更快,更正确。

    1、使用源代码控制,获得工作最大效率

    正确使用源代码控制来改善用户的工作流程。这将确保用户不会丢失任何工作,还可以让用户检查更改的内容。用户可以序列化资源,使用分支策略来最大限度地控制生产,并使用子模块来最大化源代码的有效控制。

    2、确认项目中所需资源的大小

    这一决定取决于用户的项目类型以及游戏运行的分辨率。

    3、始终用自动化构建过程,节省时间。

    自动化构建过程还将确保用户可以同时处理不同的游戏版本,并且帮助用户进行小的更改,无需在每次更改后完成整个构建过程。

    4、随时保存文档

    养成随手保存好习惯能避免各种问题。此外,文档可以让项目组其他成员更好地了解、协助完成本项目。

    5、将测试场景与代码分开

    测试场景可能是项目中一个庞大的部分,在项目完成后这些测试场景是没有用的。要确保项目文件不会太占空间,请将测试场景与代码分开,并在项目完成后将其删除。

    原文链接: https://connect.unity.com/p/unity3d-shi-yong-ji-qiao-cong-shi-jian-zhong-zong-jie-jing-yan-1?app=true 更多 Unity 精彩内容,请戳上方链接,下载 Unity 官方 app,在线讨论,技术答疑,干货满满!

    ]]>
    [HDRP] PBR Lit shader(一.基础用法篇) tag:www.v2ex.com,2020-01-23:/t/639988 2020-01-23T03:15:01Z 2020-01-23T03:12:01Z unn member/unn 本文转自 Unity Connect 博主 樱花兔

    借助这默认的 HDRP Shader,小伙伴们可以轻松地在 HDRP 中创建逼真的物理材质。它包括一些效果选项,例如次表面散射,虹彩,顶点或像素位移等各种表现效果。

    image

    今天呢,主要分享一下 HDRP 自带的三种 Shader Types:Shader types (着色器类型)

    要创建新的 Lit Material,在引擎的导航栏中找到 Asset 窗口,右键单击该窗口,然后选择 Create> Material。这会将新的材质添加到 Unity 项目的 Asset 文件夹中。当你在 HDRP 中创建新材质时,默认情况下它们将使用 Lit Shader。想要换成其他的呢,它们呢,都在“ 着色器”下拉菜单中“ HDRP/ ”下。 对了,有别的问题的话,可以看下我之前写的文章哦~ HDRP 的贴图通道细节可以看这里:厕所工程 HDRP 后期 Volume 介绍可以看这里:Volume Surface types (表面类型)

    image

    Tyes 的 HDRP 手册文档:Types

    Material types (材质类型)

    image

    Types 的 HDRP 手册文档:Types

    在制作之前呢,我们先来了解一下菲涅尔效应( Fresnel effect ),因为它是作为基于物理的渲染理念中的核心理念之一。 菲涅尔效应 Fresnel Effect 基于物理的渲染的核心理念之一是采用基于真实世界测量的材质光学参数。 由于光由电磁波组成,因此,物质的光学特性与其导电特性密切相关。我们通常把导电性较差的材质,如煤、人工晶体、琥珀、陶瓷等称为绝缘体。而把导电性比较好的金属如金、银、铜、铁、锡、铝等称为导体。以及将导电性质介于导体和绝缘体之间的材质称为半导体。 即根据导电特性,可将现实生活中的物质分为三个主要光学类别:

    菲涅尔效应,表示的是看到的光线的反射率与视角相关的现象,由法国物理学家奥古斯丁.菲涅尔率先发现。其具体表现是在掠射角(与法线呈接近 90 度)下光的反射率会增加。而上述的反射率,便被称为菲涅尔反射率。如下图。

    image

    image

    需要注意的是,我们在宏观层面看到的菲涅尔效应实际上是微观层面微平面菲涅尔效应的平均值。 也就是说,影响菲涅尔效应的关键参数在于每个微平面的法向量和入射光线的角度,而不是宏观平面的法向量和入射光线的角度。即:

    不同材质的菲涅尔效应的强弱是不同的,导体(如金属)的菲涅尔效应一般很弱,主要是因为导体本身的反射率就已经很强。就拿铝来说,其反射率在所有角度下几乎都保持 86%以上,随角度变化很小,而绝缘体材质的菲涅尔效应就很明显,比如折射率为 1.5 的玻璃,在表面法向量方向的反射率仅为 4%,但当视线与表面法向量夹角很大的时候,反射率可以接近 100%,这一现象也导致了金属与非金属外观上的不同。 当光线垂直(以 0 度角)撞击表面时,该光线被反射( Reflected )为镜面反射光的比率被称为 F0。即 F0 为 0 度角入射时的菲涅尔反射率。而折射( refracted )到表面中的光量则为为 1-F0。如下图。

    image

    对于光滑的电介质表面,在 0 度角入射( F0 )将反射 2-5 %的光,在掠射角入射下将反射 100 %的光。 大多数常见电介质的 F0 范围为 0.02-0.05 (线性值)。对于导体,F0 范围为 0.5-1.0。 为了方便创作呢,可参考如下的 PBR 材质 F0 反射率速查图表,其中分别对常见材质的线性值,sRGB 值和参考颜色进行了列举。

    image

    好了,现在呢,我们了解了基础的表面类型和材质类型,以及物理的材质解析之后呢,就可以开始制作了,这里呢,我做了几个材质的示例,供大家参考一下~

    — 木板 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“Lit” 电介质具有相当低的 F0 值 - 通常为 0.06 或更低。在垂直入射时的这种低反射率使得菲涅尔效应对于电介质尤其明显。电介质的光学性质在可见光谱上很少变化很大,导致无色反射率值。 这个木头材质的属性如下:首先木头的 Surface types 是 Opaque 的,然后呢,它的 Material types 就选默认的 Standard。

    image

    — 金属 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LitTessellation” 金属具有高的 F0 值 - 几乎总是 0.5 或更高。一些金属具有在可见光谱范围内变化的光学性质,导致了这些金属有色的菲涅尔反射率。 这个铁球为了将它表面细节体现的更加可信,我将它设为了曲面细分的 Lit,这个金属材质的属性如下:首先它的 Surface types 是 Opaque 的,然后呢,它的 Material types 就选默认的 Standard,如果选择了曲面细分的 shader 的话,会在 Shader 面板中多出一条 Tessellation Options,来设置曲面细分的细节,这里就先不多做介绍了。 然后在 Surface Inputs 会多一项 Height Map 的接口,以及细分高度参数的设置。

    image

    — 抛光金属 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“Lit” 这个抛光材质的属性如下:首先它的 Surface types 是 Opaque 的,然后呢,它的 Material types 选择到 Specular Color。 为了呈现出镜面抛光的感觉呢,我将 Smoothness 值调整到了 1,如果是 Specular Color 的材质类型的话,会在 shader 的 Surface Inputs 中多出一行 Specular 的贴图设置以及颜色设置。 抛光金属会立即吸收任何透射光,因此它们不会出现任何次表面散射或透明感。

    image

    — 皮肤 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LitTessellation” 这个皮肤材质的属性如下:首先它的 Surface types 是 Opaque 的,然后呢,它的 Material types 选择到 Subsurface Scattering。 之后会在 Surface Inputs 会多一项 Diffusion Profile (扩散曲线)的配置文件调用接口,如果你想创建 Diffusion Profile,可以在 Assets > Create > Rendering > Diffusion Profile,需要使用的话,可以添加到 HDRP 配置文件中的 Diffusion Profile List 里面。 然后还有 Subsurface Mask 贴图以及参数接口,以及 Thickness (厚度)参数接口。

    image

    — 透明塑料 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“Lit” 这个塑料材质的属性如下:首先它的 Surface types 是 Transparent 的,然后呢,它的 Material types 选择到 Translucent,Translucent 下的 Lit Shader 会生成一组 Refraction Inputs 设置,并将它之下的 Refraction Model 开启,并将模型设置成为 Sphere (球体),当开启 Refraction 之后,Smoothness 贴图就将能控制这个透明材质的可见度。

    image

    — 泥土沙石 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LitTessellation” 这个石头材质的属性如下:首先石头的 Surface types 是 Opaque 的,然后呢,它的 Material types 就选默认的 Standard,石头的设置和木材是一样的,不过为了获得更加丰富的细节,用 Hight Map 将埋在土里的小石子逼真的刻画出来。

    image

    — 电镀 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LitTessellation” 这个电镀材质的属性如下:首先它的 Surface types 是 Transparent 的,然后呢,它的 Material types 选择到 Iridescence,Iridescence 下的 Lit Shader 会生成一组 Iridescence 设置,彩虹色的遮罩贴图和参数调节,还有就是彩虹色的遮罩厚度和参数调节。

    image

    — 布料 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“Lit” 这个布料材质的属性如下:首先它的 Surface types 是 Opaque 的,然后呢,它的 Material types 选择到 Anisotropy,Anisotropy 下的 Lit Shader 会在 Surface Inputs 下生成一个 Tangent Map (切线贴图)的接口以及 Anisotropy 参数设置条以及 Anisotropy 贴图接口。

    image

    — 绒毛 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LitTessellation” 这个绒毛材质的属性如下:首先它的 Surface types 是 Opaque 的,然后呢,它的 Material types 选择到 Anisotropy,然后通过 Hight Map 的曲面细分,让材质有绒毛的感觉~。

    image

    — 分层材质 —

    image

    视频封面

    上传视频封面

    好的标题可以获得更多的推荐及关注者

    在“ 着色器”下拉菜单中,选择“ HDRP”>“LayeredLit” 在使用 LayeredLit 之后,会比 Lit 多一行 Layer 相关的设置,可以在 Layer Mask 上添加一张有 RGBA 通道的 Mask 贴图,将 Layer Count 设置成四个通道,这样,Layer List 就会出现 4 个材质的接口,你可以直接拖入已经设置好的材质(修改这些材质的参数并不会影响原有的这些材质),或者直接在下面四个 Layer 里面设置也可以的。

    image

    这是我在使用的这张 MASK 贴图,显示在 PS 中的通道视图,这样看就很清晰明白了,白色代表每一个通道的显示区域,把所有的白色部分组合在一起时候,就能成为一张完整的贴图。HDRP 在 Main Layer 之上按顺序渲染第 1 层,第 2 层和第 3 层,通过 Layering Options 里的 Opacity 也可以让层级之间的材质相互混合。

    image

    — 分层 Planar— 这是 Layered Lit 的拓展功能,将 Base UV Mapping 设置为 Planar,将贴图的 World Scale 设置保持和地形或者其他 Object 的贴图一致,这样的话,比如,在做室外场景的时候,有树根或者石块什么的,插在地形上面的情况时,就能和地形 Terrain 完美的结合了。

    image

    好了,这次的 Lit Shader 的基本功能介绍就到这里啦~

    原文链接:https://connect.unity.com/p/hdrp-pbr-lit-shader-yi-ji-chu-yong-fa-pian?app=true

    发现更多干货资源,欢迎戳上方链接,下载 Unity 官方 app!

    ]]>
    如何将 Unity 以库形式集成到原生 iOS 和 Android 应用 tag:www.v2ex.com,2020-01-22:/t/639847 2020-01-22T05:53:56Z 2020-01-22T05:50:56Z unn member/unn 本文转自 Unity Connect

    从 Unity 2019.3.a2 开始,我们通过把 Unity 运行时组件和内容集成到原生平台项目,在原生应用中将 Unity 作为库使用。

    本文将分享如何将 Unity 以库形式集成到原生 iOS 和 Android 应用。了解更多信息,请阅读:《在原生 iOS 或 Android 应用中将 Unity 作为库使用》。

    请访问云盘下载示例项目

    下载链接: https://pan.baidu.com/s/1JFWjbrh2x-qAsCzMwLywLA

    提取码: 9a71

    iOS 请 iOS 开发者使用 Unity 2019.3.a2 及更高版本,以及 Xcode 9.4 及更高版本。

    下载 Unity 2019.3.a2:https://unity3d.com/alpha/2019.3

    1、下载和解压项目

    我们会把该 Unity 项目加入到原生 iOS 应用,Assets/Plugins/iOS 文件夹中的文件用于使 Unity 播放器和原生应用进行通信。

    我们提供了一个简单的 Xcode 单视图应用,该项目拥有 UI,可以使用 UnityFrameworkLoad()加载播放器。

    为了方便起见,我们把二个项目解压到相同文件夹下。

    image

    2、生成 iOS 的 Xcode 项目

    我们按正常操作生成 Xcode 项目即可。

    首先在 Unity 编辑器打开 UnityProject 项目,选择 Menu -> Window -> Package Manager,因为 2.0.8 版本不兼容使用 Unity 作为库,所以要移除 Ads 资源包,或更新 Ads 资源包到 v 3.*版本。

    选择 Menu -> Edit -> Player Settings -> Player -> iOS 设置标签页 -> Identification Section,设置有效的 Bundle Identification 和 Signing Team ID,以避免后续步骤出现 Xcode 签名问题。

    打开 Menu -> File -> Builds Settings,在此选择并切换平台为 iOS。将 UnityProject 项目构建到 iosBuild 文件夹。

    image

    3、设置 Xcode 工作空间

    Xcode 工作空间允许同时处理多个项目,并结合它们的结果。 我们在 Xcode 打开 NativeiOSApp.xcodeproj。选择 File -> New -> Workspace,创建工作空间,把它保存在 UaaLExample/both.xcworkspace。

    image

    关闭 NativeiOSApp.xcodeproj 项目,此后的步骤会在刚创建的工作空间项目完成。

    选择 File -> Add Files to “both”,把 NativeiOSApp.xcodeproj 和第 2 步生成的 Unity-iPhone.xcodeproj 添加到相同等级的工作空间。

    image

    4、添加 UnityFramework.framework

    我们以框架的形式添加 Unity 播放器到 NativeiOSApp,它目前不会改变 NativeiOSApp 的行为。

    首先从 NativeiOSApp 项目选择 NativeiOSApp 目标。在 General -> Embedded Binaries 中按下+按钮。选择 Unity-iPhone/Products/UnityFramework.framework。

    我们从 Linked Frameworks and Libraries 移除 UnityFramework.framework,选中该条目并按下-按钮即可。

    image

    5、公开 NativeCallProxy.h

    原生应用会实现下面文件定义的 NativeCallsProtocol。

    首先找到并选择 Unity-iPhone / Libraries / Plugins / iOS / NativeCallProxy.h。

    然后在 Target Membership 中勾选 UnityFramework,通过 UnityFramework 右侧的下拉图标,把标头可见性设为 Public。

    image

    6、使 Data 文件夹成为 UnityFramework 的一部分

    默认情况下,Data 文件夹是 Unity-iPhone 目标的一部分,我们会通过修改它,使所有内容封装到单个框架文件中。

    我们把 Data 文件夹的 Target Membership 改为 UnityFramework。

    image

    在之前的改动后,为了使 Unity-iPhone 项目继续工作,Unity 播放器需要通过从 Unity-iPhone/MainApp/main.mm 进行调用,指向 Data 文件夹所处的新位置。

    [ufw setDataBundleId: "com.unity3d.framework"];

    在这种情况下不支持按需使用资源。为了使它们正常工作,我们不会使用前面的调用,因为默认情况下 Data 文件夹位于 mainBundle,所以我们要复制 Data 文件夹到原生应用,并跳过前面的调用。

    image

    运行 所有内容已经准备好针对二个项目进行构建,运行和调试,即:Unity-iPhone 项目和 NativeiOSApp 项目,选择 Scheme 为 NativeiOSApp 或 Unity-iPhone。

    image

    如果一切顺利,我们可以运行 NativeiOSApp。

    image

    完整项目说明 在集成步骤中遇到问题,你可以尝试已经准备好构建和部署的完整项目,只需要修复 Xcode 的签名即可。

    请下载并解压完整项目文件,在 Xcode 打开 both.xcworkspace,运行 NativeiOSApp Scheme。

    关键要点

    Unity 播放器由 UnityFramework 对象控制。为了获取该对象,我们要调用 UnityFrameworkLoad,它会加载 UnityFramework.framework,然后返回单个实例到 UnityFramework 类,请观察 Unity-iPhone/UnityFramework/UnityFramework.h,了解它的 API。

    请在 NativeiOSApp/NativeiOSApp/MainViewController.mm 文件中或 Unity-iPhone/MainApp/main.mm 文件中观察 UnityFrameworkLoad。

     #include <UnityFramework/UnityFramework.h> UnityFramework* UnityFrameworkLoad() { NSString* bundlePath = nil; bundlePath = [[NSBundle mainBundle] bundlePath]; bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"]; NSBundle* bundle = [NSBundle bundleWithPath: bundlePath]; if ([bundle isLoaded] == false) [bundle load]; UnityFramework* ufw = [bundle.principalClass getInstance]; if (![ufw appController]) { // 首次初始化 Unity [ufw setExecuteHeader: &_mh_execute_header]; // 同步 Data 文件夹的 Target Membership 设置 [ufw setDataBundleId: "com.unity3d.framework"] } return ufw; } 

    Android

    请 Android 开发者使用 Unity 2019.3.a2 及更高版本,以及 Android Studio 3.3.2 及更高版本。

    1、下载和解压项目

    请下载 Unity 项目,该项目是要加入原生 Android 应用的示例 Assets/Plugins/Android 文件夹中有扩展 UnityPlayerActivity 的 OverrideUnityActivity 文件。

    我们提供的原生 Android 项目是来自 Android Studio 模板的 Basic Activity 应用程序,我们会把前面的 Unity 项目加入到这里。该项目有 UI 和 MainUnityActivity 来扩展 OverrideUnityActivity,可以使用 Intent 启动 MainUnityActivity。

    为了方便起见,我们把二个项目解压到相同文件夹下。

    image

    2、生成 Android 平台的 Gradle 项目

    我们在 Unity 编辑器打开 UnityProject 项目,点击 Menu -> File -> Build Settings 打开 Build Settings。

    选择并切换为 Android Platform,勾选 Export Project 选项。

    image

    我们把 UnityProject 导出到 androidBuild 文件夹,该文件夹结构如下图所示。

    image

    3、把 Unity Library 模块添加到 NativeAndroidApp

    现在把 androidBuild/unityLibrary 模块添加给 Android Studio 中的 NativeAndroidApp Gradle 项目:

    我们在 Android Studio 打开 NativeAndroidApp,然后打开 settings.gradle 文件。在该文件末尾,添加指向 unityLibrary 的新项目。

     include ':unityLibrary' project(':unityLibrary').projectDir=new File('..\\UnityProject\\androidBuild\\unityLibrary') 

    image

    打开 build.gradle 文件,把下面代码添加到 dependencies 代码块。

    implementation project(':unityLibrary') 

    image

    打开 build.gradle 文件,把下面代码添加到 allprojects{repositories{代码块。

    flatDir { dirs "${project(':unityLibrary').projectDir}/libs" } 

    image

    Gradle 文件已经修改,单击 Sync Now。

    image

    如果一切顺利,我们会看到 unityLibrary 模块添加到 Android 视图中。

    image

    运行 现在已经准备好进行构建,运行和调试。

    image

    如果一切顺利,我们应该可以运行 NativeAndroidApp。

    image

    请注意:在应用程序安装到设备后,会出现二个启动应用程序的图标,请把<intent-filter>...</intent-filter>从 unityLibrary 的 AndroidManifest.xml 去掉,仅保留集成版本。

    完整项目说明

    如果在集成步骤中遇到问题,你可以尝试已经准备好构建和部署的完整项目。请下载并解压完整项目文件,在 Android Studio 打开 NativeAndroidApp。

    小结

    如何将 Unity 以库形式集成到原生 iOS 和 Android 应用为大家介绍到这里,请下载示例项目动手操作起来吧。

    原文链接:https://connect.unity.com/p/ru-he-jiang-unityyi-ku-xing-shi-ji-cheng-dao-yuan-sheng-ioshe-androidying-yong?app=true 更多 Unity 干货资源,请戳上方链接下载 Unity 官方 app。

    ]]>
    如何在 Timeline 中创建自定义轨道? tag:www.v2ex.com,2020-01-21:/t/639657 2020-01-21T06:01:28Z 2020-01-21T05:58:28Z unn member/unn 本文转自 Unity Connect 博主 大智

    用过一段时间 Timeline 后,我问大智:“Timeline 中只有这么几个轨道么?我发现有的需求这些轨道根本没办法满足,使用之前学过的 Playable Track 也很麻烦,还有其他办法么?”

    用过一段时间 Timeline 后,我问大智:“Timeline 中只有这么几个轨道么?我发现有的需求这些轨道根本没办法满足,使用之前学过的 Playable Track 也很麻烦,还有其他办法么?” 大智:“你遇到了什么问题呢?” 小新:“之前咱们学的那个 Playable 轨道其中一个问题是:每次添加自定义 clip 时,都需要给它指定 Light 组件,如果 Clip 很多,会非常麻烦。” 大智:“这个还好,不算个大问题,多选就能一次搞定。不过你可能更需要的是自定义轨道。使用自定义轨道有几个好处:

    可以发挥你的脑洞,用代码实现出无限的可能 可以解决轨道的绑定(如下图中的 Bound object )问题,只需要绑定一次,轨道上的所有 Clip 都可以访问。比如 Animation Track、Activation Track 都是这样的。 Playable Track 上如果混用很多类型的 Clip 时,最后会很混乱,也不直观。自定义轨道可以直观地将不同用途的轨道区分开。

    轨道的结构图 那我们来看看如何把之前学习 Playable 轨道时的自定义 Light Clip 改成自定义轨道,然后绑定 Light 组件。 注意下面是基于之前的 Playable 轨道这一节学习的,如果还没学习,建议先去学习 Playable 轨道。

    自定义轨道

    首先需要创建一个自定义轨道的脚本,继承 TrackAsset: using UnityEngine; using UnityEngine.Timeline;

    [TrackClipType(typeof(LightControlAsset))] [TrackBindingType(typeof(Light))] public class LightControlTrack : TrackAsset {} 这段代码用了两个 Attribute: TrackClipType 指定了轨道接收的 PlayableAsset 类型; TrackBindingType 指定了轨道要求绑定的对象类型。 现在还需要修改 PlayableAsset 和 PlayableBehaviour,也就是之前的 LightControlAsset 和 LightControlBehaviour。 LightControlBehaviour 的代码如下: using UnityEngine; using UnityEngine.Playables;

    public class LightControlBehaviour : PlayableBehaviour { //public Light light = null; 不再需要它了 public Color color = Color.white; public float intensity = 1f;

    public override void ProcessFrame(Playable playable, FrameData info, object playerData) { Light light = playerData as Light; // 这个地方有变化

     if (light != null) { light.color = color; light.intensity = intensity; } 

    } } PlayableBehaviour 中就不再需要 Light 成员变量了,因为可以从方法的 playerData 中获取到,转换成对应的类型即可。 using UnityEngine; using UnityEngine.Playables;

    public class LightControlAsset : PlayableAsset { //public ExposedReference<light> light; 不需要它了 public Color color = Color.white; public float intensity = 1f;</light>

    public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) { var playable = ScriptPlayable<lightcontrolbehaviour>.Create(graph);</lightcontrolbehaviour>

     var lightCOntrolBehaviour= playable.GetBehaviour(); //lightControlBehaviour.light = light.Resolve(graph.GetResolver()); lightControlBehaviour.color = color; lightControlBehaviour.intensity = intensity; return playable; 

    } } PlayableAsset 中也不需要再保留 ExposedReference<light>了,轨道会直接提供这个 Light 对象。 在 Timeline 中添加 现在就可以在 Timeline 中直接添加 Light Control Track 了。</light>

    添加 Track 后,需要设置绑定的对象。这样每个 Clip 都可以获取到这个绑定的对象。 就可以像内置的 Track 一样使用啦! 总结 自定义轨道可以支持你任意发挥,甚至可以让 Timeline 变成一个非线性运行,可交互的序列动画,是不是又勾起了你的兴趣?那就快来跟我小新一起来吧。 [扩展学习] 对 Timeline&Cinemachine 感兴趣的童鞋:更多教程在洪流学堂公众号回复 timeline 获取,免费获取图文手册,更有视频教程。

    原文链接: https://connect.unity.com/p/ru-he-zai-timelinezhong-chuang-jian-zi-ding-yi-gui-dao?app=true 更多 Unity 精彩内容,请戳上方链接,下载 Unity 官方 app,在线讨论,技术答疑,干货满满!

    ]]>
    写给 Unity 开发者的 iOS 内存调试指南 tag:www.v2ex.com,2020-01-20:/t/639426 2020-01-20T05:29:54Z 2020-01-20T05:26:54Z unn member/unn 聊聊 Unity 程序员可能会遇到的 iOS 内存问题。iOS 的内存管理机制是什么?怎么查看某一块很大的内存分配的堆栈调用?托管内存是如何分配的?戳。 0x00 前言

    工作的过程中,常常会发现有小伙伴对 Unity 的 Profiler 提供的内存数据与某些原生平台 Profiler 工具,例如 iOS 系统和 Xcode,所提供的内存数据有差异而感到好奇。而且大家对如何解读原生平台工具的数据更加感兴趣,同样例如 iOS 系统和 Xcode。最近正好看了一个来自 Unite Copenhagen 题为 Developing and optimizing a procedural game | The Elder Scrolls Blades - Unite Copenhagen 的演讲,其中就涉及到了一些关于 iOS 内存的话题(虽然并不是很详细)。正好也结合工作中的一些经验,写一篇文章来讨论一下一个 Unity 开发者如何处理和 iOS 内存有关的问题。主要内容包括解析 iOS 系统的内存管理,使用 Instrument 查看 Unity 游戏的内存状况,使用命令行工具深入挖掘 Unity 游戏的内存问题以及文末小彩蛋。 0x01 iOS 的内存管理 - Unity Profiler 统计错了吗?

    首先,我想强调的一点是,Profiler 工具所提供的内存数据只是一个(组)数字,而且不同的工具存在有不同统计内存的策略。因此,一个重要的问题是我们看到的数据究竟是如何获取的? 而根据所使用的工具不同,该工具用于查找数据的策略以及开发人员实际要查找的内容,最后的结果也有可能是不一样的。因此,如果要寻找一个数字来汇总某个应用或者游戏的所有内存信息,那么可能是把问题想简单了,或者说忽略了系统的复杂性。例如,不同版本的 iOS 其对内存开销的统计都是有区别的——在 iOS12 上运行的 metal app 的内存在 Xcode memory gauge 的统计是高于 iOS11 的,这同样是由于苹果改变了对内存的统计策略,很多之前没有被统计的内存如今也被计算到了内存开销中。而同样都是 iOS,Xcode memory gauge 的统计和 Instrument 中的统计也有可能不完全一致,而早期 Instrument 的 Allocation 则主要用来统计 heap 内存,只能说根据各自工具的统计规则,大家都是正确的。因此,把时间浪费在对比不同工具的数据上还不如以一个工具作为标尺来衡量内存开销或者是判断内存的优化是否有效。 The accounting for purgeable, nonvolatile memory changed beginning in iOS 12 and tvOS 12. In iOS 11 and tvOS 11, allocations with this memory storage mode—commonly used by Metal apps to store buffers, textures, and state objects—weren’t counted toward an app’s memory limit and weren’t presented in tools like Xcode memory gauge. 所以,了解操作系统是如何管理内存就变得十分重要,对于如何解读 Profiler 工具提供的数据也很有帮助。接下来我们先来讨论一下 iOS 系统对内存的管理机制,之后再来分别看看 Xcode 抓取的内存数据和 Unity 抓取的内存数据。 首先,每一个进程都会有一个地址空间。其范围由指针 size 支持,比如 32bit 或 64bit。并且地址空间首先会分为多个区域( regions ),然后将这些区域细分为 4KB (早期版本)或 16KB ( A7 之后)为单位的 page,这些 page 继承了该 region 的各种属性,例如是否是只读、可读写等等。当然,有些 page 可能存放的数据比这个 page 的尺寸要小,有的数据可能需要好几 page 才能存放,但是系统的内存单位是 16kb 的 page,所以系统统计的内存开销约等于 page 的数量 x page 的大小。 当然,系统还有真实的物理内存。 Virtual memory vs Resident memory

    ref: WWDC 2013 通过虚拟内存使我们能够建立从该地址空间到真实物理内存的映射,这点我想这些大家应该都知道。而映射其实是一个很有趣的事情。因为从每一个 app 进程的角度来看,它拥有所有的内存,即虚拟内存,但事实上只有一部分虚拟内存被映射到了真实的物理内存上,这部分被映射到物理内存的部分就是所谓的 Resident memory。 就像上面这个图中描述的一样,一个 app 分配了内存,可以看到在虚拟内存上分配了 4 个 region,其中第 3 个 region 包括了 13 个 page。 但此时,真正映射到物理内存上的只有 6 个 page。而虚拟内存到真实物理内存的映射发生在对内存的第一次使用时,比如从内存中读取数据或是向内存中写数据。Resident memory 同样也是 Virtual memory,只不过这部分 Virtual memory 已经映射到了真实的物理内存。 我想大家可能都通过 XCode 或者 Instrument 的统计看到过类似的数据,例如 Instrument 的 VM Tracker 中就分别列出了 Resident 和 Virtual Size。 Dirty memory vs Clean memory

    page 有可能是 dirty 的也有可能是 clean 的。要如何区分 dirty 和 clean 呢?简单的说,dirty 的页就是我们的 app 或者游戏对这个 page 的内容进行了修改即分配了内存同时也修改了内存的内容,常见的就是 malloc 在 heap 上分配的内存。这部分内存是不能被回收的,因为这些数据显然需要被保存在内存中以保证程序正常的运行。 而 clean 的页则是没有对其内容进行修改,可以被系统收回和重新创建的。例如内存映射文件( Memory-mapped file ),如果操作系统需要更多的内存,那么就可以将其丢弃。因为系统总是可以从磁盘中重新加载它,创建内存空间和磁盘上文件的映射关系。clean 的内存是可以被释放和重新创建的。但是可以看到,虽然 Memory-mapped file 并没有消耗真实的物理内存,但是它消耗了进程的虚拟内存。 除此之外还有可执行文件的__TEXT 段以及一些 framework 的 DATA CONST 段,也会归为 clean memory。 在 WWDC2018 上,iOS 的开发人员举了一个很形象的例子。即分配 20,000 个 integers 组成的 array,此时会有 page 被创建,如果只对第一个元素和最后一个元素赋值,则第一个 page 和最后一个 page——即首尾元素所在的 page——会变成 dirty,但是首尾之间的 page 仍然是 clean,即只分配了内存而没有修改或写数据。

    ref: WWDC 2018 Compressed memory

    当内存吃紧时,会回收 clean page。而 dirty page 是不能被回收的,那么如果 dirty memory 过多会如何呢?在 iOS7 之前,如果进程的 dirty memory 过高则系统会直接终止进程。iOS7 之后,引入了 Compressed Memory 的机制。由于 iOS 没有传统意义上的 disk swap 机制( mac OS 有),因此我们在苹果的 Profiler 工具中看到的 Swapped Size 指的其实就是 Compressed Memory。 iOS7 之后,操作系统可以通过内存压缩器来对 dirty 内存进行压缩。首先,针对那些有一段时间没有被访问的 dirty pages (多个 page ),内存压缩器会对其进行压缩。但是,在这块内存再次被访问时,内存压缩器会对它解压以正确的访问。举个例子,某个 Dictionary 使用了 3 个 page 的内存,如果一段时间没有被访问同时内存吃紧,则系统会尝试对它进行压缩从 3 个 page 压缩为 1 个 page 从而释放出 2 个 page 的内存。但是如果之后需要对它进行访问,则它占用的 page 又会变为 3 个。 Unity Profiler 错了吗?

    可以看到,从操作系统内存管理的角度来看,一个进程的内存其实是十分复杂的。而 Unity 记录的内存数据,以“Reserved Total - Unity”为例,则主要来自引擎内 MemoryManager 的记录。MemoryManager 会根据不同的情况调用对应的 Allocator 来进行引擎的内存分配。

    例如我们可以以 Unity 3D Game Kit 这个免费项目为例,使用 Instrument 来查看一下它的内存分配。

    可以看到 MemoryManager 调用了 UnityDefaultAllocator。 而下图的这个分配则使用了 IphoneNewLabelAllocator 来分配内存。

    也就是说 Unity 的代码分配的内存,Unity 是会进行记录的。但是我们可以看到除了 Unity 的代码本身分配的内存,还有很多 framework 或者第三方 library 也会分配内存。但是这部分内存,Unity 的 Profiler 是不会记录的。 0x02 使用 Instrument 调试 Unity 游戏的内存

    这部分我推荐 Valentin Simonov 的这篇文章 Understanding iOS Memory (WiP),对使用 Instrument 调试内存介绍的十分清晰。 0x03 使用命令行工具深入挖掘内存问题

    除了使用 Instrument 来调查内存问题之外,我们还可以通过很棒的 Xcode memory debugger 工具来查找内存问题。尤其是将 Memgraph 导出后,还可以借助各种命令行工具来辅助调查以获取更多信息。

    而且有时大家也会抱怨说在 Xcode 的 Memory Report 页面看到的内存数据有时候不仅和 Unity Profiler 不一样,有时甚至和 Instrument 等苹果自己的性能工具数值也不一样。上文已经说过了,不同的工具有不同的数据是正常的。但是我们同样可以通过 Memgraph 和命令行工具来查看一下,Memory Report 的数据侧重什么内容。 还是以 Unity 3D Kit 这个工程作为演示,测试设备为 iPhone X,不过在开始之前我们首先需要开启 Scheme -> Run -> Diagnostics -> Malloc Stack 选项。

    运行游戏后从主菜单点击开始游戏加载第一个场景,我们可以在 Memory Report 中看到此时的内存已经达到了 1.48G 。但是 Memory Report 中它的内存刻度仍然在绿色部分,所以实事求是的讲 Memory Report 的刻度并不是一个好的优化建议,因为这个内存开销在 iphone7 上就直接会导致游戏被系统中止。

    Animation Leak?

    我们直接进入到 Xcode memory debugger,如果想要在这里检查是否有内存 leak 的问题,可以点击 Filter 中的选项。这里有一个常见的假“leak”情况。

    如果我们看一下它的堆栈信息的话,大多是和 Animation 有关的。这里我咨询了一下这个功能的开发者,确认这是一个苹果的误报,Unity 还是会正常释放这部分内存的。当然如果大家遇到其他奇怪的和引擎有关的 leak,可以按照这篇文章的介绍给 Unity 提交 Bug Report。 https://blogs.unity3d.com/2016/08/24/attaching-your-project-to-a-bug-report/ 之后我们可以将此时的数据导出为.memgraph 文件。接下来就可以使用一些命令行工具来处理这些数据了。

    VMMAP Summary

    第一个命令行工具是 vmmap,使用它我们可以查看当前的虚拟内存的数据。 首先拿到一个 memgraph 文件时,我们可以考虑使用这个指令同时加上--summary 标记来输出当前虚拟内存的一个总览。 vmmap --summary Unity3DKit_ipx.memgraph 终端的输出如下图所示:

    我们可以发现一些有趣的地方。首先有前 4 列是我们之前讨论过的内容:VIRTUAL SIZE、RESIDENT SIZE、DIRTY SIZE、SWAPPED SIZE。分别表示虚拟内存的大小,映射到物理内存的大小,Dirty 内存的大小以及 Compressed 内存的大小。 我们可以看到 TOTAL 的部分,这个游戏进程分配了 2.7G 的虚拟内存其中有 1.6G 映射到了物理内存上,而 DIRTY SIZE 的值是 1.4G——这个值很接近 Memory Report 中的数值,而 SWAPPED SIZE 的数值为 52mb,根据苹果工程师在 WWDC2018 上的演讲,这个值是压缩前的内存而不是压缩后的内存。因此我们主要来关注 DIRTY SIZE 这一项。 IOKit

    其次我们可以看到 IOKit 的开销最大,它的虚拟内存不仅达到了 832.5mb ,而且实际映射到物理内存上的空间也达到了 750.4mb 。这部分主要是一些和 GPU 相关的一些内存,例如 render targets, textures, meshes, compiled shaders 等等。而这个测试项目也的确是 mesh、texture 的内存占用很大。 MALLOC 和 Heap

    再次,我们可以看到 MALLOC_**分配了很多内存。这部分内存主要是调用 Malloc 进行分配的,其中即包括 Unity 的原生也就是 C++代码的分配也包括第三方库和系统使用 Malloc 分配的内存,这部分内存在所谓的 Heap 上,在这几行的后面可以看到“see MALLOC ZONE table below”,也就是可以在下面看到各个 heap zone 的一个归类。在这里我们可以利用第二个命令行工具 heap 来检查一下 Heap 内存的内容。 heap --sortBySize Unity3DKit_ipx.memgraph 使用 heap 指令,我们还可以添加--sortBySize 标志来对数据进行排序(默认按照类型实例的数量进行排序)。

    可以看到 Heap 的绝大部分内存都被 non-object 占用了,达到了近 700mb,而实际的 object 的内存分配其实都是很小的,比如类 GpuProgramMetal 的实例有 573 个,但是内存其实只占用了 223kb。此时大家一定对 non-object 的内容很感兴趣,不过在这个页面里似乎也看不到太多的内容。所以接下来我们可以添加--showSize 标志,将合并在一起的数据按照 size 进行分组。 heap --showSize --sortBySize Unity3DKit_ipx.memgraph 这样就清晰多了。

    可以看到 non-object 这一类中,排名最高的几块内存分配的尺寸分别是 1 个 31mb、3 个 10mb 以及 1 个 8.4mb ,这样我们就确定了这个时候的调查方向。 当然,heap 指令还提供了更多的功能,比如那些有 Class Name 的对象分配,我们可以通过 ClassName 匹配的方式获取每一个该类型实例的内存地址。此时需要-addresses 标签即可。比如我们输出 Unity 的 GpuProgramMetal 类的所有实例的地址信息,可以看到其实这个类的实例本身并不大,但是它引用的真正的 shader 资源则可能是内存开销的大户之一。 heap -addresses GpuProgramMetal Unity3DKit_ipx.memgraph

    同时,有了各个对象所在的内存地址,我们就可以通过下面要提到的 malloc_history 命令来查找它们是怎么来的。但是现在我们还是把目光转向内存分配比较大的目标吧。 此时返回终端,继续输出虚拟内存的信息,不过这次我们只关注 MALLOC_LARGE 的分配,所以我们可以借助 grep 来过滤出我们的目标。 vmmap -verbose Unity3DKit_ipx.memgraph | grep "MALLOC_LARGE" 这次输出了 MALLOC_LARGE 类型下的内存信息,包括它的地址、尺寸以及所在 Heap Zone 等等信息。我们可以在这里找到我们的目标,一个 30mb、3 个 10mb 以及一个 8mb 的内存分配。

    接下来我们就来看一下分配它们的堆栈调用吧。这里我们会使用 malloc_history 命令,同时加上--fullStacks 标志来输出堆栈信息。 malloc_history Unity3DKit_ipx.memgraph --fullStacks 0x0000000127c60000 可以看到这 30mb 的分配是为了给 FMOD 分配内存池。

    另外 3 个 10mb 的分配,同样也是做类似的事情。可见这个项目使用的声音资源很多。最后我们来看一下这个 8mb 的分配是从哪里来的。 malloc_history Unity3DKit_ipx.memgraph --fullStacks 0x0000000113400000

    可以看到是开启多线程渲染时,Unity 创建 CommandQueue 时分配的内存。 VM_ALLOC == Mono Size?

    接下来,我们可以看到 vmmap –summary 输出的结果中,有一项叫做 VM_ALLOC。根据 Valentin Simonov 的说法,VM_ALLOC 对应的是 Mono 内存也就是托管内存的大小。究竟是否如此呢?我们同样可以通过上面的方式,来查看一下 VM_ALLOC 部分的内存分配堆栈。 首先我们还是通过 vmmap 和 grep 来过滤出 VM_ALLOC 部分的内存信息。 vmmap -verbose Unity3DKit_ipx.memgraph | grep "VM_ALLOC" 可以看到这部的内存分配并不多,我们同样选择 2 块分配最大的内存下手。

    我们首先使用 malloc_history 来查看一下 3m 部分的调用堆栈。 malloc_history Unity3DKit_ipx.memgraph --fullStacks 0x0000000152bd4000

    我们可以看到这 3m 的内存是 C#脚本中调用了 SimplFXSynth 的 RenderAudio 方法而触发了 GC 分配,托管堆进行了扩容。针对脚本中的方法定位,我们可以通过 RuntimeInvoker 这个符号来定位它在堆栈中的位置。

    有趣,接下来我们再来看看 1mb 的这块内存是怎么分配的。 malloc_history Unity3DKit_ipx.memgraph --fullStacks 0x0000000150084000 这次是由于 Unity 的 ScriptingGCHandle::Acquire 方法在托管堆上进行了内存分配。

    可见,VM_ALLOC 这部分内存主要对应了 Unity 的 Mono 托管堆的内存而且这个项目的 Mono 内存并不大。而具体是哪个函数触发了 GC 分配,则可以通过 malloc_history 来查看。 Command Summary

    至此,使用命令行调试和查找 iOS 平台上内存问题就介绍完了。简单来个小结,拿到一个 Unity 游戏的内存.Memgraph 文件之后,可以先通过 vmmap --summary 来查看一下内存的全景图。对于 heap 也就是 malloc 分配的内存,可以进一步通过 heap 指令来进一步分析。 而一旦获取了目标对象的内存地址之后,就可以使用 malloc_history 指令来获取分配这块内存的堆栈信息了。当然前提是要开启 Malloc Stack 的选项。之后,可以做一个自动化的分析工具,对数据进行处理和输出来定位内存问题。

    0x04 小彩蛋

    Unity 3D Game Kit 是一个很棒的 Unity 的学习工程。它的教学页面可以查看这里: https://learn.unity.com/tutorial/3d-game-kit-reference-guide#5c7f8528edbc2a002053b73f

    iOS13 之后提供了一个新的 API-os_proc_available_memory,利用这个 API 我们可以获取当前这个进程还能获取多少内存的预估值。嗯,怪不得我的 iphone7 跑不动这个项目。

    原文链接: https://connect.unity.com/p/xie-gei-unitykai-fa-zhe-de-iosnei-cun-diao-shi-zhi-nan?app=true 更多干货资源学习,戳上方链接下载 Unity 官方 app,在线技术互动答疑,结识更多 Unity 小伙伴,交友学习两不误!

    ]]>
    Unity3D 常用插件汇总 tag:www.v2ex.com,2020-01-19:/t/639131 2020-01-19T05:20:28Z 2020-01-19T05:17:28Z unn member/unn 本文转自 Unity Connect 博主 AnimeKing

    Unity Asset Store 上有大量的优质的艺术家与开发者提供的资源。我们也许需要择木而栖,好的资源经得起时间的沉沦。下面我们一起来看看这些优秀的之物。 一、功能性插件 1、Obfuscator 可以保护游戏代码和游戏资源,防止被逆向工程破解。 https://assetstore.unity.com/packages/tools/utilities/obfuscator-48919?aid=1101l4bPZ&utm_source=aff

    2、UFPS 是制作第一人称设计游戏的必备插件,开发者无需重造轮子,上手很快。 https://assetstore.unity.com/packages/templates/systems/ufps-ultimate-fps-106748?aid=1101l4bPZ&utm_source=aff

    3、Lip Sync Pro 是一款制作唇语同步&面部动画的插件。 https://assetstore.unity.com/packages/tools/animation/lipsync-pro-32117?aid=1101l4bPZ&utm_source=aff

    4、Edy's Vehicle Physics 这款插件可以为赛车类游戏提供各种逼真的真实物理效果,制作赛车类的游戏可以考虑一下这款插件。 https://assetstore.unity.com/packages/tools/physics/edy-s-vehicle-physics-403?aid=1101l4bPZ&utm_source=aff

    5、Editor Console Pro 是一款可以取代 Unity 原生控制台编辑器的插件,生产力提升必备插件。 https://assetstore.unity.com/packages/tools/utilities/editor-console-pro-11889?aid=1101l4bPZ&utm_source=aff

    6、A* Pathfinding Project Pro 是一款 AI 寻路插件,开发者可以自定义 AI 的寻路行为。 https://assetstore.unity.com/packages/tools/ai/a-pathfinding-project-pro-87744?aid=1101l4bPZ&utm_source=aff

    7、Stan's Assets 所有资源链接 https://blog.csdn.net/game_builder/article/details/93143186 Stan’s Assets 提供了和 iOS、安卓等平台交互的插件,还有使用 Admob 的插件。有了 Stan's Assets 的插件,你完全不需要了解任何 iOS 或者安卓开发,就可以使用 iOS 和安卓的各种特性。开发者必备插件,而且几年来他们一直在维护更新。强烈推荐。 A ) Ultimate Mobile Pro (包含与 iOS 和安卓交互的插件,同时包含 Admob 插件,随意切换 iOS 和安卓部署,不需要更改任何代码) https://assetstore.unity.com/packages/tools/integration/ultimate-mobile-pro-130345?aid=1101l4bPZ&utm_source=aff

    B ) iOS Native Pro(Ultimate Mobile Pro 的子集,包含和 iOS 交互的插件 ) https://assetstore.unity.com/packages/tools/integration/ios-native-pro-119175?aid=1101l4bPZ&utm_source=aff

    C ) Android Native Pro((Ultimate Mobile Pro 的子集,包含和安卓交互的插件,含安卓版使用 Admob 的插件 ) https://assetstore.unity.com/packages/tools/integration/android-native-pro-125691?aid=1101l4bPZ&utm_source=aff

    D ) Google Mobile Ads SDK ( Ultimate Mobile Pro 的子集,提供 iOS,wp8 和安卓使用 Admob 的插件 ) [已下架] 二、音频插件 1、Koreographer Professional Edition - Asset Store:是制作音乐节拍类游戏的必备神器! https://assetstore.unity.com/packages/tools/audio/koreographer-professional-edition-44270?aid=1101l4bPZ&utm_source=aff

    13:Master Audio: AAA Sound - Asset Store:是 Unity 游戏开发的终极音频解决方案。 https://assetstore.unity.com/packages/tools/audio/master-audio-aaa-sound-5607?aid=1101l4bPZ&utm_source=aff

    三、虚拟现实插件 1、Final IK - Asset Store:是一个反向动力控制插件。相比较 unity 的自带 IK 系统,Final IK 设置更加方便,用途更广。有很多情景化的应用,如针对和物体交互的动作系统。如果能使用好 Final IK 插件,就可以使用少量的固定动画,在此基础上融合 IK 动作,做出千变万化的交互动作。 https://assetstore.unity.com/packages/tools/animation/final-ik-14290?aid=1101l4bPZ&utm_source=aff

    2、PuppetMaster - Asset Store https://assetstore.unity.com/packages/tools/physics/puppetmaster-48977?aid=1101l4bPZ&utm_source=aff

    3、VR Panorama 360 PRO Renderer - Asset Store:可以帮助开发者在 Unity 内部渲染 4K360°全景视频。 https://assetstore.unity.com/packages/tools/video/vr-panorama-360-pro-renderer-35102?aid=1101l4bPZ&utm_source=aff

    4、Curved UI - VR Ready Solution To Bend / Warp Your Canvas! - Asset Store 是一款制作 VR 游戏的 UI 的插件。 https://assetstore.unity.com/packages/tools/gui/curved-ui-vr-ready-solution-to-bend-warp-your-canvas-53258?aid=1101l4bPZ&utm_source=aff

    四、素材、动画设计类插件 1、Amplify Shader Editor - Asset Store:是一款基于节点的可视化 shader 制作插件。 https://assetstore.unity.com/packages/tools/visual-scripting/amplify-shader-editor-68570?aid=1101l4bPZ&utm_source=aff

    2、Colorful FX - Asset Store:包含了大量的可定制的 FX 效果和全屏的 camera 渲染效果。对色彩纠正和图像后期处理有很大作用。 https://assetstore.unity.com/packages/vfx/shaders/fullscreen-camera-effects/colorful-fx-44845?aid=1101l4bPZ&utm_source=aff

    3、Mega-Fiers - Asset Store:是一个可以让 Mesh 发生的形变的插件。 https://assetstore.unity.com/packages/tools/modeling/mega-fiers-644?aid=1101l4bPZ&utm_source=aff

    4、Surforge - Asset Store:能够帮你在 Unity 内部快速制作 3D 模型的 texture。 https://assetstore.unity.com/packages/tools/utilities/surforge-79171

    5、NGUI: Next-Gen UI - Asset Store:Unity 中制作 UI 的最强插件,当然现在 Unity 有自己的 UGUI 了。 https://assetstore.unity.com/packages/tools/gui/ngui-next-gen-ui-2413?aid=1101l4bPZ&utm_source=aff

    6、Octave3D-Level Design - Asset Store:帮助 Unity 开发者制作关卡。 https://assetstore.unity.com/packages/tools/level-design/octave3d-level-design-45021?aid=1101l4bPZ&utm_source=aff

    7、UBER - Standard Shader Ultra - Asset Stor:可以帮助开发者制作 3A 级别的 shader。 https://assetstore.unity.com/packages/vfx/shaders/uber-standard-shader-ultra-39959?aid=1101l4bPZ&utm_source=aff

    8、UMotion Pro - Animation Editor - Asset Store :是 Unity 中一款非常优秀的动画制作插件。 https://assetstore.unity.com/packages/tools/animation/umotion-pro-animation-editor-95991?aid=1101l4bPZ&utm_source=aff

    9、Behavior Designer - Behavior Trees for Everyone - Asset Store:可以帮助 Unity 开发者制作 AI 的行为树。 https://assetstore.unity.com/packages/tools/visual-scripting/behavior-designer-behavior-trees-for-everyone-15277?aid=1101l4bPZ&utm_source=aff

    11、Mesh Baker - Asset Store:可以用来合并 Mesh 和 Materials,这样可以降低游戏对内存和显卡的消耗。 https://assetstore.unity.com/packages/tools/modeling/mesh-baker-5017?aid=1101l4bPZ&utm_source=aff

    五、制作 2D 游戏的插件 1、Rex Engine: Classic 2D Platformer Engine - Asset Store:是一款在 Unity 内部专门制作 2D 平台游戏的游戏引擎。 https://assetstore.unity.com/packages/templates/systems/rex-engine-advanced-2d-game-engine-92333

    2、Platformer PRO 2 - Asset Store:是一款制作 2D/2.5D 的平台游戏引擎,内部集成了大量功能,包括跳跃等运动系统。 https://assetstore.unity.com/packages/templates/systems/platformer-pro-2-140510?aid=1101l4bPZ&utm_source=aff

    3、Corgi Engine - 2D + 2.5D Platformer - Asset Store:是一款制作 2D/2.5D 的平台游戏引擎,内部集成了若干有趣的游戏,Corgi Engine 弥补了 Unity 制作 2D 游戏的短板。 https://assetstore.unity.com/packages/templates/systems/corgi-engine-2d-2-5d-platformer-26617?aid=1101l4bPZ&utm_source=aff

    4、TopDown Engine - Asset Store:TopDown Engine 和 Corgi Engine 来自同一开发者,是制作 2D/3D 动作类游戏的最佳引擎。 https://assetstore.unity.com/packages/templates/systems/topdown-engine-89636?aid=1101l4bPZ&utm_source=aff

    6、DOTween Pro - Asset Store:DOTween Pro 是一款制作动画和渐变状态的插件。 https://assetstore.unity.com/packages/tools/visual-scripting/dotween-pro-32416?aid=1101l4bPZ&utm_source=aff

    7、Fingers - Touch Gestures for Unity - Asset Store:Fingers Touch Gestures 是一款处理处理手势输入的插件,对手机游戏制作很有必要。 https://assetstore.unity.com/packages/tools/input-management/fingers-touch-gestures-for-unity-41076?aid=1101l4bPZ&utm_source=aff

    六、环境、地形&世界构建类插件 1、World Creator Professional - Asset Store:World Creator 是一款制作 3D 真实地形的的专业插件。 https://assetstore.unity.com/packages/tools/terrain/world-creator-professional-55073?aid=1101l4bPZ&utm_source=aff

    2、TerrainComposer 2 - Asset Store:TerrainComposer 2 是一款利用节点制作地形的插件。开发者用它可以很容易的制作惊艳的地形。 https://assetstore.unity.com/packages/tools/terrain/terrain-composer-2-65563

    3、Fantasy Environment - Asset Store:Fantasy Environment 包含了大量的幻想风格的游戏素材。 https://assetstore.unity.com/packages/3d/environments/polyquest-worlds-full-pack-vol-1-74631?aid=1101l4bPZ&utm_source=aff

    4、Polyquest Worlds Full Pack Vol.1 - Asset Store:Polyquest Worlds 包含了超多、超高质量的 Low Poly 风格素材,手机游戏和 VR 游戏都能很好的运行该插件里的素材。 https://assetstore.unity.com/packages/3d/environments/polyquest-worlds-full-pack-vol-1-74631?aid=1101l4bPZ&utm_source=aff

    5、MapMagic World Generator - Asset Store:MapMagic World Generator 也是一款基于节点制作地形的插件。 https://assetstore.unity.com/packages/tools/terrain/mapmagic-world-generator-56762?aid=1101l4bPZ&utm_source=aff

    6、Gaia - Asset Store:Gaia 是一款一站式地形制作插件。 https://assetstore.unity.com/packages/tools/terrain/gaia-terrain-scene-generator-42618

    原文链接: https://connect.unity.com/p/unity3d-chang-yong-cha-jian-hui-zong-1?app=true 更多干货资源,请戳上方链接,下载 Unity 官方 app !

    ]]>
    在 Unity 实现游戏命令模式 tag:www.v2ex.com,2020-01-17:/t/638732 2020-01-17T06:20:45Z 2020-01-17T06:17:45Z unn member/unn 本文介绍如何在 Unity 通过使用命令模式实现回放功能,撤销功能和重做功能,你可以使用该方法来强化自己的策略类游戏。

    原博地址:https://www.raywenderlich.com/3067863-implementing-the-command-pattern-in-unity

    原文链接:https://connect.unity.com/p/zai-unityshi-xian-you-xi-ming-ling-mo-shi?app=true

    作者:Najmm Shora 预计阅读时间:20 分钟

    Unity 版本:Unity 2019.1

    你是否想知道《超级食肉男孩》(Super Meat Boy)等游戏是如何实现回放功能的?其中一种方法是执行和玩家完全相同的输入操作,这样意味着输入需要保存起来。命令模式可以实现该功能,以及更多其它功能。

    如果希望在策略游戏里实现撤销和重做功能,命令模式也非常实用。

    在本教程中,我们会使用 C#实现命令模式,然后使用命令模式来遍历 3D 迷宫中的机器人( bot, 文中 bot,机器人交替出现,请整理一下)角色。在这个过程中,我们会学习到以下内容:

    备注:阅读本文需要熟悉 Unity 的使用,并且拥有对 C#有一定的了解。本教程使用 Unity 2019.1 和 C# 7。

    准备过程

    跟随本教程进行学习时,请下载文末链接的项目素材文件。解压文件,在 Unity 中打开 Starter 项目。

    打开 RW/Scenes 文件夹,打开 Main 场景。我们会注意到,场景中有一个迷宫和机器人,旁边有终端 UI 显示指令。地面上有网格,当玩家在迷宫中移动机器人时,这些网格有助于玩家进行观察。

    单击 Play 按钮后,我们发现指令不会进行工作,这是因为我们还没添加该功能,我们将在教程中添加功能。 场景中最有趣的部分是 Bot 游戏对象,在层级窗口单击选中该对象。

    在检视窗口查看该对象,我们看到它带有 Bot 组件。我们会在发出输入命令时使用该组件。

    了解 Bot 对象的逻辑

    打开 RW/Scripts 文件夹,在代码编辑器打开 Bot 脚本。我们不必了解 Bot 脚本会做什么,但要注意其中的 Move 方法和 Shoot 方法。我们也不用知道二个方法中的代码作用,但需要了解如何使用二个方法。

    我们发现,Move 方法会接收一个类型为 CardinalDirection 的输入参数。CardinalDirection 是一个枚举,类型为 CardinalDirection 的枚举对象可以为 Up,Down,Right 或 Left。根据所选的 CardinalDirection 不同,机器人会在网格上朝着对应方向移动一个网格。

    Shoot 方法可以让机器人发射炮弹,摧毁黄色的墙体,但对其它墙体毫无作用。

    现在查看 ResetToLastCheckpoint 方法,为了了解它的功能,我们要观察迷宫。在迷宫中,有一些点被称为检查点。为了通过迷宫,机器人应该到达绿色检查点。

    在机器人穿过新检查点时,该点会成为机器人的最后检查点。ResetToLastCheckpoint 方法会重置机器人的位置到最后检查点。

    我们目前无法使用这些方法,但我们很快就会用到了。首先,我们要介绍命令设计模式。

    命令设计模式介绍

    命令模式是 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 编写的《设计模式:可复用面向对象软件的基础》( Design Patterns: Elements of Reusable Object-Oriented Software )

    书中介绍的 23 种设计模式之一。 书中写道:命令模式把请求封装为对象,从而允许我们使用不同的请求,队列或日志请求,来参数化处理其它对象,并支持可撤销的操作。

    这个定义确实对读者来说并不友好,我们下面详细讲解一下。 封装是指方法调用封装为对象的过程。

    参数化其它对象指的是:封装的方法可以根据输入参数来处理多个对象。 请求的队列指的是:得到的“命令”可以在执行前和其它命令一起存储。

    “undoable”(可撤销)在此不是指无法实现的东西,而是指可以通过撤销功能恢复的操作。

    那么这些内容怎么用代码表示呢?

    简单来说,Command 类会有 Execute 方法,该方法可以接收命令处理的对象,该对象叫 Receiver,用作输入参数。因此,Execute 方法会由 Command 类进行封装。

    最后,为了执行命令,Execute 方法需要进行调用。触发执行过程的类叫作 Invoker。

    现在,该项目包含名叫 BotCommand 的空类。在下个部分,我们会完成要求,实现之前的功能,让 Bot 对象可以使用命令模式执行动作。

    移动 Bot 对象

    实现命令模式

    在这部分,我们会实现命令模式。实现该模式有多种方法。本教程会介绍其中一种方法。

    首先打开 RW/Scripts 文件夹,在编辑器打开 BotCommand 脚本。BotCommand 类此时应该是空白的,我们会给它加入代码。

    在该类中粘贴下列代码:

    //1 private readonly string commandName; //2 public BotCommand(ExecuteCallback executeMethod, string name) { Execute = executeMethod; commandName = name; } //3 public delegate void ExecuteCallback(Bot bot); //4 public ExecuteCallback Execute { get; private set; } //5 public override string ToString() { return commandName; } 

    下面讲解这些代码。

    1. commandName 变量用于存储用户可以理解的命令名称。它对于该模式并不重要,但是我们会在后面需要到它。

    2. BotCommand 构造函数会接收一个函数和一个字符串。它会帮助我们设置 Command 对象的 Execute 方法和名称。

    3. ExecuteCallback 委托会定义封装方法的类型。封装方法会返回 void 类型,接收类型为 Bot (即带有 Bot 组件)的对象作为输入参数。

    4. Execute 属性会引用封装方法。我们要使用它来调用封装方法。

    5. ToString 方法会被重写,返回 commandName 字符串,该方法主要在 UI 中使用。

    保存改动,现在我们已经实现了命令模式。

    接下来要使用命令模式。

    创建命令

    从 RW/Scripts 文件夹打开 BotInputHandler 脚本。

    我们会在此创建 BotCommand 的五个实例。这些实例会分别封装方法,从而让 Bot 对象向上,下,左,右移动,还可以让机器人发射炮弹。

    复制粘贴下列代码到 BotCommand 类中:

    //1 private static readonly BotCommand MoveUp = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Up); }, "moveUp"); //2 private static readonly BotCommand MoveDown = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Down); }, "moveDown"); //3 private static readonly BotCommand MoveLeft = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Left); }, "moveLeft"); //4 private static readonly BotCommand MoveRight = new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Right); }, "moveRight"); //5 private static readonly BotCommand Shoot = new BotCommand(delegate (Bot bot) { bot.Shoot(); }, "shoot"); 

    在每个实例中,都有一个匿名方法传到构造函数。该匿名方法会封装在相应命令对象之中。我们发现,每个匿名方法的签名都符合 ExecuteCallback 委托设置的要求。

    此外,构造函数的第二个参数是一个字符串,表示用于指代命令的名称。该名称会通过命令实例的 ToString 方法返回,它会在后面为 UI 使用。

    在前四个实例中,匿名方法会在 Bot 对象上调用 Move 方法。该方法有多种参数。

    对于 MoveUp、MoveDown、MoveLeft 和 MoveRight 命令,传入 Move 方法的参数分别是 CardinalDirection.Up ,CardinalDirection.Down,CardinalDirection.Left 和 CardinalDirection.Right。

    这些参数对应着 Bot 对象的不同移动方向,这在命令设计模式部分介绍部分中提到过。

    最后在第五个实例上,匿名方法在 Bot 对象调用 Shoot 方法。这会在执行该命令时,让机器人发射炮弹。

    现在我们创建了命令,这些命令需要在用户发出输入时进行访问。

    为此,我们要把下列代码复制粘贴到 BotInputHandler 中,它的位置在命令实例下方:

    public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; } 

    HandleInput 方法会根据用户的按键,返回单个命令实例。继续下一步前,保存改动内容。

    使用命令

    现在我们要使用创建好的命令。打开 RW/Scripts 文件夹,在代码编辑器打开 SceneManager 脚本。在该类中,我们会发现有 UIManager 类型的 uiManager 变量的引用。

    UIManager 类为场景中的终端 UI 提供了实用的功能性方法。在 UIManager 类的方法使用时,我们会介绍方法的用途,但在本文中,我们不必知道它内部的工作方式。

    此外,bot 变量引用了附加到 Bot 对象的 Bot 组件。

    现在把下列代码添加给 SceneManager 类,替换代码注释 //1 的已有代码:

    //1 private List<BotCommand> botCommands = new List<BotCommand>(); private Coroutine executeRoutine; //2 private void Update() { if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else { CheckForBotCommands(); } } //3 private void CheckForBotCommands() { var botCommand = BotInputHandler.HandleInput(); if (botCommand != null && executeRoutine == null) { AddToCommands(botCommand); } } //4 private void AddToCommands(BotCommand botCommand) { botCommands.Add(botCommand); //5 uiManager.InsertNewText(botCommand.ToString()); } //6 private void ExecuteCommands() { if (executeRoutine != null) { return; } executeRoutine = StartCoroutine(ExecuteCommandsRoutine()); } private IEnumerator ExecuteCommandsRoutine() { Debug.Log("Executing..."); //7 uiManager.ResetScrollToTop(); //8 for (int i = 0, count = botCommands.Count; i < count; i++) { var command = botCommands[i]; command.Execute(bot); //9 uiManager.RemoveFirstTextLine(); yield return new WaitForSeconds(CommandPauseTime); } //10 botCommands.Clear(); bot.ResetToLastCheckpoint(); executeRoutine = null; } 

    这里的代码很多,通过使用这些代码,我们可以在游戏视图正常运行项目。

    之后我们会讲解这些代码,现在先保存改动。

    运行游戏,测试命令模式

    现在要构建所有内容,在 Unity 编辑器按下 Play 按钮。

    我们可以使用 WASD 按键输入方向命令。输入射击模式时,使用 F 键。最后,按下回车键执行命令。

    备注:在执行过程结束前,我们无法输入更多命令。

    现在观察代码添加到终端 UI 的方式。命令会通过它们在 UI 中的名称表示,该效果通过 commandName 变量实现。

    我们还会注意到,在执行前,UI 会滚动到顶部,执行后的代码行会被移除。

    详细讲解命令

    现在我们讲解在使用命令部分添加的代码:

    1. botCommands 列表存储了 BotCommand 实例的引用。考虑到内存,我们只可以创建五个命令实例,但有多个引用指向相同的命令。此外,executeCoroutine 变量引用了 ExecuteCommandsRoutine,后者会处理命令的执行过程。

    2. 如果用户按下回车键,更新检查结果,此时它会调用 ExecuteCommands,否则会调用 CheckForBotCommands。

    3. CheckForBotCommands 使用来自 BotInputHandler 的 HandleInput 静态方法,检查用户是否发出输入信息,此时会返回命令。返回的命令会传递到 AddToCommands。然而,如果命令被执行的话,即如果 executeRoutine 不是空的话,它会直接返回,不把任何内容传递给 AddToCommands。因此,用户必须等待执行过程完成。

    4. AddToCommands 给返回的命令实例添加了新引用,返回到 botCommands。

    5. UIManager 类的 InsertNewText 方法会给终端 UI 添加新一行文字。该行文字是作为输入参数传给方法的字符串。我们会在此给它传入 commandName。

    6. ExecuteCommands 方法会启动 ExecuteCommandsRoutine。

    7. UIManager 类的 ResetScrollToTop 会向上滚动终端 UI。它会在执行过程开始前完成。

    8. ExecuteCommandsRoutine 拥有 for 循环,它会迭代 botCommands 列表内的命令,通过把 Bot 对象传给 Execute 属性返回的方法,逐个执行这些命令。在每次执行后,我们会添加 CommandPauseTimeseconds 时长的暂停。

    9. UIManager 类的 RemoveFirstTextLine 方法会移除终端 UI 里的第一行文字,只要那里仍有文字。因此,每个命令执行后,它的相应名称会从终端 UI 移除。

    10. 执行所有命令后,botCommands 会清空,机器人会使用 ResetToLastCheckpoint,重置到最后检查点。接着,executeRoutine 会设为 null,用户可以继续发出更多输入信息。

    实现撤销和重做功能

    再运行一次场景,尝试到达绿色检查点。

    我们会注意到,我们现在无法撤销输入的命令,这意味着如果犯了错,我们无法后退,除非执行完所有命令。我们可以通过添加撤销功能和重做功能来解决该问题。

    回到 SceneManager.cs 脚本,在 botCommands 的 List 声明后添加以下变量声明:

    private Stack<BotCommand> undoStack = new Stack<BotCommand>(); 

    undoStack 变量属于来自 Collections 命名空间的Stack类,它会存储撤销的命令引用。

    现在,我们要分别为撤销和重做添加 UndoCommandEntry 和 RedoCommandEntry 二个方法。在 SceneManager 类中,复制粘贴下列代码到 ExecuteCommandsRoutine 之后:

    private void UndoCommandEntry() { //1 if (executeRoutine != null || botCommands.Count == 0) { return; } undoStack.Push(botCommands[botCommands.Count - 1]); botCommands.RemoveAt(botCommands.Count - 1); //2 uiManager.RemoveLastTextLine(); } private void RedoCommandEntry() { //3 if (undoStack.Count == 0) { return; } var botCommand = undoStack.Pop(); AddToCommands(botCommand); } 

    现在讲解这部分代码:

    1. 如果命令正在执行,或 botCommands 列表是空的,UndoCommandEntry 方法不执行任何操作。否则,它会把最后输入的命令引用推送到 undoStack 上。这部分代码也会从 botCommands 列表移除命令引用。

    2. UIManager 类的 RemoveLastTextLine 方法会移除终端 UI 的最后一行文字,这样在发生撤销时,终端 UI 内容符合 botCommands 的内容。

    3. 如果 undoStack 为空,RedoCommandEntry 不执行任何操作。否则,它会把最后的命令从 undoStack 移出,然后通过 AddToCommands 把命令添加到 botCommands 列表。

    现在我们添加键盘输入来使用这些方法。在 SceneManager 类中,把 Update 方法的主体替换为下列代码:

    if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U)) //1 { UndoCommandEntry(); } else if (Input.GetKeyDown(KeyCode.R)) //2 { RedoCommandEntry(); } else { CheckForBotCommands(); } 
    1. 按下 U 键会调用 UndoCommandEntry 方法。

    2. 按下 R 键会调用 RedoCommandEntry 方法。

    处理边缘情况

    现在我们快要完成该教程了,在完成前,我们要确定二件事:

    1. 输入新命令时,undoStack 应该被清空。

    2. 执行命令前,undoStack 应该被清空。

    为此,我们首先给 SceneManager 添加新的方法。复制粘贴下面的方法到 CheckForBotCommands 之后:

    private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); } 

    该方法会清空 undoStack,然后调用 AddToCommands 方法。 现在把 CheckForBotCommands 内的 AddToCommands 调用替换为下列代码:

     AddNewCommand(botCommand); 

    最后,复制粘贴下列代码到 ExecuteCommands 方法内的 if 语句中,从而在执行前清空 undoStack:

     undoStack.Clear(); 

    现在项目终于完成了!

    保存项目。构建项目,然后在 Unity 编辑器单击 Play 按钮。输入命令,按下 U 键撤销命令,按下 R 键恢复被撤销的命令。

    尝试让机器人到达绿色检查点。

    后续学习

    如果想要了解更多游戏编程中的设计模式,建议查看 Robert Nystrom 的游戏编程模式网站。

    如果想了解更多高级 C#方法,可以查看C# Collections,Lambdas,and LINQ课程。

    挑战

    小挑战:尝试达到迷宫终点的绿色检查点。如果遇到困难,我在下面提供了解决方法,这是多个解决方法之一。

    解决方法:

    本文到此结束,感谢阅读。希望你喜欢这篇教程,如果有问题或评论,请在评论区讨论。 特别感谢艺术家 Lee Barkovich、Jesús Lastra 和 sunburn 提供本项目的资源。

    原文链接: https://connect.unity.com/p/zai-unityshi-xian-you-xi-ming-ling-mo-shi?app=true

    欢迎大家戳上方链接,下载 Unity 官方 app,技术社区互动答疑,干货资源学不停!

    ]]>
    Unity ECS(一)了解 ECS 与 DOTS tag:www.v2ex.com,2020-01-16:/t/638437 2020-01-16T06:08:51Z 2020-01-16T06:05:51Z unn member/unn 本文转自 Unity Connect 博主 EntherVarope

    ECS 是什么?可以做什么?为什么 ECS 成为我的选择?

    什么是 ECS (实体组件系统)?

    其实 ECS 的概念早就诞生了,但是它是因为守望先锋才逐渐被人所知。(暴雪的守望先锋是基于 ECS 模式设计的,用于改善在大场景下多角色运算的效率)

    ECS 是一种软件架构模式,由三个元素组成:实体(Entity),组件(Component)和系统(System)(看起来和 MVC 很相似)。游戏程序分为这三个主要元素,并且通过定义每个系统的责任和关系来管理游戏。

    实体代表游戏世界中的事物。实体本身没有特定功能,它们将会被组件填充来成为一个实体。

    组件是附加到事物的数据。重点不是对象,而是数据,没有办法操纵它。比如操作游戏的角色时,位置,速度和体力等每个状态都将成为一个组成部分,并与称为“角色”实体相关联。另外,实体中的字段信息也被表示为组件部分。

    系统是游戏世界的法则。给定与实体关联的某些组件作为数据输入源,或者更新某些组件的值(可能与输入的组件相同)。随着整个系统更新每一帧,游戏世界也在不断进行。我认为最容易想象的是物理定律。例如,想象一个刚体实体。它的运动基于利用位置和速度两个分量的系统来更新坐标。

    粗略地说,系统负责处理,组件负责数据,而实体是一组组的组件,用于过滤系统正在处理的内容。由于系统和数据是完全分开的,因此它与面向对象不兼容。

    Unity 也具有类似于 ECS 的架构(自 Unity2018.2 起已提供 ECS )。实体是已经削减到极限的游戏对象功能,组件是组件的序列化数据部分,其余部分是系统。Unity ECS 是对纯 ECS 的改进。

    ECS 可以做什么?

    UnityECS 正在围绕这一设计模式开发新的“面向数据技术堆栈”(DOTS),如果经常关注 Unity 的发布会,就或多或少对它有所了解。但是事先写在前边,ECS 并不能加速任何类型的游戏 /程序。

    ECS 有望加速游戏的类型

    弹幕游戏 有海量单位的大型 RTS 开放世界 /沙盒 集群模拟(例如新出的“动物星球”) 此类具有大量“遵循同一运算规则”对象的程序,便可以利用 ECS 来加速。

    为什么 ECS 成为我的选择?

    答案很简单:因为它十分的快!效率非常之高,比起传统的面向对象编程,它对内存的利用率是成几何倍数的增长,还有一些其他的优点:

    轻松并行化--------清晰的系统输入输出和小粒度 良好的缓存效率------通过顺序访问组件数组获得空间局部性(动态场景读取十分方便) 耦合度低---------因为系统仅交换数据 易于测试---------因为系统没有状态

    当然 ECS 也有它自身的一些缺点:

    对于习惯了面向对象编程的人,需要改变编程思维模式 在 Unity 推出完善的工具集前,管理内存是一件困难的事 至于为什么 ECS 如此之快,内容比较多,感兴趣的可以继续往下看,跳过也无妨。

    在传统的流程中,我们制作物体,为其添加脚本。这种做法有一些固有的缺点和性能缺陷。首先,数据和处理它们的方法是紧密耦合的,代码重用率较低。此外,系统非常依赖引用类型,引用错误屡见不鲜。

    最重要的是,在上图的示例中:Gun 与 Player 所引用的 Transform、Rigidbody、Collider 等这些关键脚本被分散在堆内存中,数据将不会转换成可由更快的 SIMD (单指令多数据流)矢量单元进行操作的形态。

    上图显示了这种数据存储方法的随机偶发性质。每一个单引用,在使用时都有可能会将其所有的成员变量从系统内存中全部拉出,举个例子,当我命令 Gun 进行开火,子弹飞出,对子弹的坐标进行运算让它飞行,表面上看起来仅仅是对子弹这个对象的 Transform 中的 position 进行了操作,但实际上,子弹的 rotation,gameobject 属性,还有等等等等其他成员也一并拉出来操作了。

    绿色块表示开发想象中认为操作引用的成员,而实际上,硬件听从脚本的命令从内存中获取数据时,缓存中会填充许多无用的数据(红色的块),如果将为要移动的 GameObject 设置成一个独立的只有位置与旋转成员的矩阵,那么系统就能够在很短的时间内执行操作。

    在 ECS 中,

    只需要考虑每一种 GameObject 所包含的数据实体,而不用考虑自己的组件集合(抛弃了 Transfrom,Rigidbody 等),将处理与各个对象类型完全分离。实体仅仅是一个句柄(或者说是一个标识符)永远索引它表示的不同数据类型的集合(ComponentDataGroups)系统可以通过这些句柄来对所有组件进行过滤和操作,而不需要将系统与实体类型明确结合。这种工作机制有很大优势,它不仅能提高缓存效率,缩短访问时间,它还支持现代 CPU 中的使用数据对齐的先进技术(自动矢量化 即:SIMD ),这种技术带来的效率提升是极为可观的。在 Unity 的 DOTS 中,还可以使用 Brust Complier 跳过中间语言的编译,使得性能进一步的提升。

    关于数据对齐与 SIMD

    实际编程中,对内存的管理总是不可能达到完美利用,总会有或多或少的内存块处于闲置状态,闲置内存不仅没有任何用处,系统仍然要访问它们增大访问开销(浪费时间)。传统的面向对象下虽然降低抽象门槛了,但是对内存来说实际上是很不友好的(构建对象时,数据引用总是杂乱无序的)。面向数据编程将数据提取出来,不用关心他们实际上的联系,仅仅是由实体这个索引来引用,这就代表,对于同类型的数据,可以将他们放在同一个内存块里,无论是对内存的利用或者是系统的访问都是十分便利的。

    ( PS:就算是数据对齐了,也不能彻底消除闲置内存,但是比起传统的内存结构来说,闲置内存的数量会大为减小)。

    SMID:

    SIMD 全称 Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在寄存器的一组指令集。

    传统的 CPU 使用 SISD 来完成逻辑运算,过程可以笼统的概括为一个执行单元先访问内存,根据命令找到第一个操作数,再一次访问内存,找到第二个操作数,才能根据命令进行逻辑运算(在查找操作数的过程中,由于数据的引用是杂乱无序的,执行单元只能遍历每一个内存块,这就造成了性能瓶颈)。

    新世代的 CPU 所采用的 SIMD 则是由数个执行单元同时访问内存,一次性找到所需的操作数进行运算,事实上,由于进行了数据对齐,每一个执行单元查找内存的效率比 SISD 的查找要高出不少,这就好比将无数的快递按照地区分门别类,一旦 CPU 发出“拿出上海地区的快递“指令,每一个快递员(执行单元)都会直奔存放上海快递的货架而不用一个货架一个货架的搜索。这样的特性非常适合大数据运算。

    以上内容都是关于 ECS 本身的优势,接下来将阐述 Unity 基于 ECS 进一步开发的面向数据技术栈工具 DOTS。

    UnityDOTS ( Data-Oriented Technology Stack )

    Burst Complier

    爆发式编译器(?)

    爆发编译器是 UnityECS 为了更高效地组织数据所产生的后台性能增益开发的。从本质上讲,突发编译器将根据玩家设备上的处理器功能优化代码操作。例如,您可以通过填充未使用的寄存器来执行 16、32 或 64 次浮点预算,而不是一次只进行 1 次浮点运算。

    新的编译器技术基于 Unity 的新数学命名空间( Unity.mathematics )和 C# 作业系统(JobSystem)以及改进过的高性能 C#(HPC),基于系统知道数据已经通过实体组件系统正确设置的事实。英特尔 CPU 的当前版本支持英特尔® SIMD 流指令扩展 4 (英特尔® SSE4 )、英特尔® 高级矢量扩展指令集 2 (英特尔® AVX2 )以及用于浮点和整数的英特尔® 高级矢量扩展指令集 512 (英特尔® AVX-512 ),AMD 的支持 3D Now 的 CPU 等都是能支持爆发式编译器的(除非是在十分老旧的电脑上运行,否则不需要考虑兼容性问题,因为自动矢量化技术已经成为现在与未来的 CPU 主流标准)。该系统还支持在每种方法中使用不同的精确度,以过渡方式应用。例如,如果您在低精度的顶级方法内使用余弦函数,则整个方法也将使用余弦的低精度版本。该系统还根据当前运行游戏的处理器的功能支持,通过动态选择适当的优化功能为 AOT (前期)编译做准备。

    这种编译器的另一个优势是确保游戏的未来适用性。如果一款全新的处理器产品线上市,其中包含一些令人惊叹的新功能,Unity 可以在后台为您完成所有费力工作。只需对编译器进行升级,以获取优势。编译器是基于软件包的,无需 Unity 编辑器更新即可升级。该爆发式编译器软件包将以自己的节奏进行更新,因此您将能够利用最新的硬件架构改进和功能,而无需等待代码升级到下一个编辑器版本。

    C#JobSystem

    C#作业系统

    大多数使用多线程代码和通用任务系统的人都知道编写线程安全代码很难。线程争用情况虽然很罕见,但仍然可能会发生。如果编程员没有想到这个问题,可能会导致潜在的程序严重错误。除此之外,上下文切换的成本,Debug 的成本很高,因此学习如何平衡工作负载以尽可能高效地跨核心运行是很困难的。最后,编写 SIMD 优化代码或 SIMD 内联函数是一种深奥的技能,有时最好交给编译器去完成。新的 Unity C# 作业系统为您解决所有这些难题,以便您可以在现代 CPU 中放心地使用所有可用的内核和 SIMD 矢量化。

    总而言之,C#的 JobSystem 提供一系列的多线程解决方案,让编写多线程程序更为安全方便。

    常规的 Unity 如果要开发多线程,不仅要引入外部的实现方式,Debug 过程也是繁琐不可视的,而引入 C#JobSystem,可以让系统智能化管理安排多线程任务,使用 Unity 自身封装的多线程安全集合(例如:NativeArray<>)可以有效防止线程冲突的问题。

    在接下来的文章中,我会告诉你如何利用 DOTS 来编写一个 ECS 程序。

    原文链接: https://connect.unity.com/p/unityecs-yi?app=true

    戳上方链接下载官方 app 即可提前了解接下来的文章,还有技术社区在线答疑,更多学习资源等你来发现

    ]]>
    使用 Unity 制作游戏 AI tag:www.v2ex.com,2020-01-15:/t/638180 2020-01-15T08:21:14Z 2020-01-15T08:18:14Z unn member/unn 本文转自 Unity Connect 官方文章

    简介

    本教程主要介绍游戏 AI 的概念和开发方法。虽然实现过程是面向 Unity 的,但整个理论方法可以应用于任何其它游戏引擎。

    本文介绍的所有概念都是我们团队在开发《 Radiant Blade 》游戏的原型阶段学习到的,目前该游戏已经到达成品阶段。

    使用游戏 AI 的原因

    开始介绍技术内容前,我们首先要思考为什么要为游戏添加 AI。

    很长一段时间以来,我都在幻想着为游戏开发令人惊奇的 AI,让 AI 给玩家带来印象深刻的体验。这种 AI 可以预料到玩家的每一个操作,几乎无法被打败。但说实话,这种 AI 毫无对抗的乐趣。

    值得玩家去玩的游戏应该是玩家可以获得乐趣的游戏。因此我们的 AI 必须可以和玩家旗鼓相当。AI 可以作为伙伴,让玩家通过特别的方法进行交互。

    显然,只有乐趣的游戏不会是优秀的游戏。游戏也必须有炫酷的机制,深刻的含义,以及精美的外观。但对我们的 AI 而言,我们希望 AI 具有娱乐性,因此我们要进一步缩小这个概念。

    游戏设计

    什么是娱乐性?更具体来说,游戏中的娱乐性是什么?

    开发团队花了一些时间思考这个问题,我们的结论可以总结为一个词:学习。具备娱乐性的游戏是玩家可以从中学习和利用知识的游戏。

    娱乐性源于小小的好奇心,在玩家看到新事物时,好奇心会占据玩家的头脑,并会不断增长,直到玩家完全理解这项新事物。

    也就是说,具有娱乐性的 AI 必须是可以被玩家学习的。

    这个简单的概念形成了所有游戏中 AI 的广泛理解,包括:《超级玛丽》,《毁灭战士》,《魔兽世界》和《以撒的结合》。

    如果分析这些游戏的 AI,我们会发现它们都是可以预测的。由于加入了一些随机元素,这些游戏 AI 不是完全固定不变的,但仍有预测的可能。

    这样又出现了另一个问题:如何制作出可预测的游戏 AI ?

    答案很简单:使用状态机。

    状态机

    状态机是包含状态和过渡的数学工具。

    基本的状态机

    在确定性状态机中,我们会处于一个特定状态,在移动时,我们会随着其中一个可用过渡转变到新状态。过渡可能会受到条件限制,例如:只有在拥有特定法术时,AI 才可以到达指定状态。

    状态机的优点是:它们具有表现力和可预测性。例如,假设状态包括“攻击”,“受击”,“奔跑至目标”和“逃跑”,我们可以使用一些过渡,创建出模拟 AI 基本行为的状态机。

    简单的 AI 示例

    我们制作的 AI 可以用下面三句话描述:

    生命值在 10%以下时,AI 会逃跑。

    AI 可以受到攻击。

    玩家处在 AI 范围内时,AI 会向玩家跑去,然后攻击玩家。

    这意味着 AI 很简单。简单是件好事情。如果我们无法简单地描述自己的 AI,那么我们可能需要对 AI 做进一步思考。

    状态机和 Unity

    我们知道状态机很厉害,那么我们是否可以在 Unity 使用状态机?

    当然可以。

    大致的方法有三种:

    自己开发;

    使用 Animator 实现;

    从 Asset Store 资源商店获取相应资源。

    由于状态机是游戏中很常见的工具,我不建议开发者自己开发状态机,因为已经有很多人实现过状态机,除非开发者希望学习怎么通过代码实现状态机,否则我们可以直接获取可以使用的状态机。

    第二种方法是使用 Unity 的内置 Animator 功能。虽然这个名称不太好理解,但它其实是一种可以播放动画的状态机。但在 Animator 中,我们不一定要使用动画,如果不使用动画的话,它的工作方式和状态机一样。

    Animator 使用起来快捷而直观。

    《 Radiant Blade 》中使用 Unity Animator 实现的弓箭手 AI

    第三种方法是从 Asset Store 资源商店获取相关资源。我们没有试过这个方法,但我们相信应该不少资源有和 Animator 一样不错的效果。

    如果你使用过比 Animator 更好的资源,请来告诉我们。

    Animator

    或许你使用过 Animator 在 Unity 中实现标准动画,但我们在此会根据需求调整一些方法。

    下面开始吧。

    状态

    通常,Animator 的状态包含动画。我们没有这样使用,而是把状态关联到描述行为的代码。

    为了演示这一点,我们现在查看定义弓箭手的游戏对象。

    Behaviours 对象的子对象是 AI 行为。它们其实是小型控制器,在对应状态激活时,它们会控制弓箭手。

    在 Shoot 状态激活时,会在弓箭手上使用 Shoot Behaviour 脚本

    这是基于状态的对象。在完成行为后,Shoot Behaviour 会通知 Animator。Animator 内置的蓝色进度条可能会让人迷惑,但它只在外观上起到作用。

    变量

    这里的 AI 设计是响应式系统,它会随条件而变化,那么条件是什么呢?当然是玩家和环境。

    Animator 的变量用于描述游戏的状态,以及做出已知决策。

    上图是弓箭手使用的变量,它们描述了形成 AI 的所有要素

    这是一项重要的概念。在以传统方法使用 Animator 时,大多数状态过渡会随着关联动画结束而结束。对于 AI 来说,状态就是行为,它会在未定义的时间内保存游戏逻辑。

    我们使用了两个变量,它们的作用是通知状态的结束,即 behaviour_ended 和 behaviour_error。它们是状态的输出结果,表示状态成功结束,或是出现错误。

    过渡

    过渡定义了 AI 行为的改变过程,表示:当 AI 完成向目标行走的过程后,它应该要做什么。

    示例过渡:如果目标在近战范围内,AI 会进行攻击

    对 Unity 的 Animator,有些开发者可能不知道的是:过渡是有先后顺序的。特定过渡会被首先评估,仅在它的相关条件为假时,第二个过渡才会进行评估。

    选中 Neutral 状态时,我们可以查看过渡的优先级

    这项功能很不错,因为它允许我们把 AI 设计为中心大脑,根据优先级来做出合适的选择。

    是否还记得我们之前展示的弓箭手 AI ?请注意 AI 的顺序和中心部分。Neutral 节点是决策中心,它的主要工作过程如下:

    如果没有玩家的话,AI 停止战斗;

    如果玩家距离较远,AI 向玩家移动,进入射击范围;

    如果玩家不在 AI 的视线方向,AI 向玩家移动,从而能够进行射击;

    如果处于近战范围,则进行近战攻击;

    如果玩家过于接近 AI,AI 可能会向后退;

    AI 有可能随机改变和玩家的方向;

    AI 会向玩家射击。

    该功能的好处在于,每个单独的过渡都非常简单:过渡会总结为一次测试,或甚至没有测试。使用后续过渡的前提是之前的过渡条件必须为假。

    实现方法

    从这部分开始,我们应该会开始了解具体操作。你是否注意到,到现在我还未提供过任何相关代码。

    这个状态不错,因为这意味着我们的框架有足够高的抽象级,不必处理任何技术细节,就可以很好进行解释。在代码部分完成后,设计 AI 的过程非常直观。

    我们需要什么

    下面是实现 AI 的任务:

    编写 AI 行为;

    把 Animator 和可用行为关联;

    为 Animator 更新游戏相关变量的列表。

    行为

    开始处理前,首先回顾行为的功能。

    行为会和游戏的角色控制器一起工作;

    行为可以被识别;

    行为可以被启用;

    行为可以成功完成;

    行为也可以出现错误;

    行为可以被中断;

    大概就是这样。

    public abstract class AbstractAIBehaviour : MonoBehaviour {

    // 角色由行为控制 [SerializeField] protected CharController charController; // 必须返回对应行为的 Animator 状态的短哈希值。 abstract public int GetBehaviourHash(); // 在行为成功结束时调用的事件。 public event Action OnBehaviourEnded; // 在行为失败时,要调用的事件 public event Action OnBehaviourError; // OnEnable() // OnDisable() // enable = true/false; 

    }

    对于启用和禁用部分,我们会利用 Unity 的内置方法,这里不必自己编写方法。

    我们会使用简洁的 API。

    对于识别符,我创建了带有特殊名称的方法:GetBehaviourHash。因为 Animator 状态的识别方式是:状态的识别符是其名称的哈希值。https://docs.unity3d.com/ScriptReference/Animator.StringToHash.html

    因此对于 Shoot 状态,对应的识别符是 Animator.StringToHash(“Shoot”)。

    为了弄清楚对象,避免再次计算相同的哈希值,我们可以把它们保存为静态变量:

    /**

    */

    public class BehaviourHashes {

    // 我们会使用行为,让角色向目标移动。 static public readonly int OBJ_MOVETO_STATE = Animator.StringToHash("Obj MoveTo"); // 我们会使用行为,让角色什么都不做。 static public readonly int IDLE_STATE = Animator.StringToHash("Idle"); // 此时角色会漫无目的地四处移动。 static public readonly int ROAM_STATE = Animator.StringToHash("Roam") // ... 

    }

    考虑到这点,AbstractAIBehaviour 的实现代码如下:

    // 必须返回对应行为的 Animator 状态的短哈希值。

    public override int GetBehaviourHash() { // Animator 中的状态名称为 Idle。 return BehaviourHashes.IDLE_STATE; } 

    我们会把每个哈希值存到对应的脚本中,因此 ROAM_STATE 可以保存在 RoamBehaviour 类中。

    唯一的问题在于:由于我们暗中把每个行为关联到名称,因此可能很难打开每个行为类,从而收集 Animator 状态的授权名称。

    从此开始,我们的工作是为真实行为编写实际的代码,但这取决于开发者,因为这要根据自己的游戏来实现。我们需要做的是实现 AbstractAIBehaviour 的子类。

    关联行为和 Animator

    我们的 AI 的行为可以被识别,监听,启用和禁用。现在我们要利用行为。

    我们要从控制器开始,由于我们有多个互相独立的实体,我们需要同步它们,实现流畅的工作效果。

    该控制器的目的是确保每次只启用一个行为,并提供修改当前行为的切入点。

    一些开发者可能不知道应该何时给游戏添加新控制器的类。好的习惯是把控制器看作用来同步多个较小功能的代码。

    /**

    */

    public class AIBehaviourController

    /** * Contains the available Behaviours. * 包含可用行为 * * The key of a Behaviour is the value returned by its GetBehaviourHash method. * 行为的关键是 GetBehaviourHash 方法返回的数值 */ protected Dictionary<int, AbstractAIBehaviour> behaviours = new Dictionary<int, AbstractAIBehaviour>(); // AI 的 Animator private Animator stateMachine; // 正在执行的行为 private AbstractAIBehaviour currentBehaviour; // 必须存在 AI Animator 中的触发器 public static readonly int BEHAVIOUR_ENDED = Animator.StringToHash("behaviour_ended"); public static readonly int BEHAVIOUR_ERROR = Animator.StringToHash("behaviour_error"); /** * 强制某个行为中断正在执行的行为 */ public void SetBehaviour(int behaviorHash) { // 安全地禁用当前行为 if (currentBehaviour) currentBehaviour.enabled = false; try { // 开始新的行为 currentBehaviour = behaviours[behaviorHash]; currentBehaviour.enabled = true; } catch (KeyNotFoundException) { currentBehaviour = null; } } void Awake() { stateMachine = GetComponent<Animator>(); // 对于每个子对象 foreach (AbstractAIBehaviour behaviour in GetComponentsInChildren<AbstractAIBehaviour>()) { // 注册行为 behaviours.Add(behaviour.GetBehaviourHash(), behaviour); // 监听行为 behaviour.OnBehaviourEnded += OnBehaviourEnded; behaviour.OnBehaviourError += OnBehaviourError; } } /** * 在行为结束时,通知 AI 的 Animator */ private void OnBehaviourEnded() { stateMachine.SetTrigger(BEHAVIOUR_ENDED); } /** * 在行为失败时,通知 AI 的 Animator */ private void OnBehaviourError() { stateMachine.SetTrigger(BEHAVIOUR_ERROR); } 

    }

    这个类比较长,但是代码其实很简单:

    字典包含我们已知的行为;

    方法可以激活特定行为;

    两个事件用于在行为结束时通知 Animator。

    有了切入点,我们可以把它和 Animator 连接起来。怎么连接呢?我们会使用一个不常用的功能:StateMachineBehaviour。

    选中 Animator 时,如果在空白处单击左键,我们会聚焦 Animator 本身,并显示 Animator 的隐藏检视窗口

    StateMachineBehaviour 的功能是什么?它允许我们向 Animator 插入自定义代码。我们要怎么使用它呢?

    我们会在 Animator 的状态变化时,调用我们的 AIBehaviourController。

    /**

    */

    public class AIStateController : StateMachineBehaviour {

    /** * 在 Animator 进入新状态时,通知 AI 控制器。 */ override public void OnStateEnter(Animator animator, AnimatorStateInfo info, int layerIndex) { if (!animator.GetComponent<AIBehaviourController>().SetBehaviour(animatorStateInfo.shortNameHash)) { // 如果状态不存在,那么把它设为决策中心。 // 强制 Animator 直接评估该状态。 animator.Update(0f); } } 

    }

    这些代码非常直观,它会处理 Unity 的一个特别之处:Animator 无法在每帧处理多个状态,因此在我们遍历决策中心时,会造成短暂的延迟。

    幸运的是,解决方法很简单,我们可以强行执行 Update 方法,强制 Animator 处理状态。

    通过使用我们的新类,我们可以把功能结合起来,只要把该脚本添加到 AI 的 Animator 即可。

    现在进入新状态时,我们的 AI Animator 会调用 AIBehaviourController

    最后,我们有框架的三个类部分,子类,以及角色控制器,它们包含着实际的游戏逻辑。

    组合成 AI 框架的小型类图示

    包含游戏逻辑

    总而言之,技术方面的解决方法可以总结为三个类,每个类都非常简洁。

    我们还需要什么呢?当然是游戏本身了。但这个部分必须由开发者自己制作。

    总之,实现自己的 AI 需要的内容如下:

    一个角色控制器,负责角色和其渲染的实际逻辑;

    变量,以及让变量与 Animator 保持同步的代码;

    自定义行为,例如:攻击,移动。

    此时我们要处理的都是常见的 Unity 标准代码。

    变量,Animator 和行为都协同工作

    原文链接: https://connect.unity.com/p/creating-an-a-i-with-unity-shi-yong-unityzhi-zuo-you-xi-ai?app=true

    各位在开发过程中遇到问题?欢迎大家戳上方链接,下载官方 app,可在线答疑哦,还有更多学习资源等你来发现~

    ]]>
    零基础入门 Unity Shader(二) tag:www.v2ex.com,2020-01-14:/t/637791 2020-01-14T06:08:48Z 2020-01-14T06:05:48Z unn member/unn 本文转自 Unity Connect 博主 taecg

    前言

    让我们先抛开什么鬼图形学、鬼数学、乱七八糟的鬼东西统统不管,直接在 Unity 中干上我们的 Shader,一步一步去学习了解并深入征服它! Shader 应该由谁来做? 这是个很有趣的话题,Shader 到底应该由谁来做呢?通常情况下有此技能的人员有: 图形程序员 一小部分程序 一小小部分美术 技术美术

    一般的项目团队,如果不是自研引擎的话,很少会配置图形程序员,而且图形程序员这么高大上的人做 Shader 似乎又有种浪费的感觉,所以我们先忽略他。

    程序呢,每个团队中总有那么一两个有两把刷子的,但是苦于自身美感问题,最后在不断与美术沟通交流的过程中放弃自我。

    美术呢,难得碰到个会做 Shader 的,效果也是华丽丽的,但是你敢用吗? 这时我们的主角,技术美术登场了,及程序与美术于一身的美男子。效果华丽丽的,性能也是妥妥的! 所以说如果你是技术美术的话,一定要让美术对效果满意,程序对性能放心,在这其中找到效果与性能的最佳平衡点,这才是我们制作 Shader 的优势,而不仅仅是我们能做!

    Shader 模版 现在让我们开始创建我们的第一个 Shader,打开 Unity,然后在 Project 面板点击右键,依次从中选择 Create/Shader/...

    然后你会发现几个选项: Standard Surface Shader 标准表面着色器,是一种基于物理的着色系统(使用了 Physically Based Rendering (简称 PBR )技术,即基于物理的渲染技术),以模拟现实真实的方式来模拟材质与灯光之间的关系,可以很轻易的表现出各种金属反光效果,同时此种 Shader 的书写逻辑也更符合人类的思维模式。

    Unlit Shader Vertex/Fragment Shader,也就是最基本的顶点片断着色器,不受光照影响的 Shader,多用于特效、UI 上的效果制作。

    Image Effect Shader 也是顶点片断着色器,只不过是针对后处理而定制的模版,后处理是什么呢? Bloom (也有人叫 Glow/泛光 /辉光等说法)、调色、景深、模糊等,这些基于最终整个屏幕画面而进行再处理的 Shader 就是后处理。

    Compute Shader Compute Shader 是运行在图形显卡上的一段程序,独立于常规渲染管线之外的,它可以直接将 GPU 作为并行处理器加以利用,从而使 GPU 不仅具有 3D 渲染能力,还具有其他的运算能力。

    Shader Variant Collection Shader 变体收集器,在上面创建的时候,你会发现 Shader Variant Collection 与以上四个是被隔开的,就是因为这个与它们不一样,它不是制作 Shader 的模版,而只是对 Shader 变体进行打包用的容器。 注:以上的 Standard Surface Shader、Unlit Shader、Image Effect Shader 仅仅只是 Unity 为了方便我们书写而内置的几个模版,你完全可以建一个 Unlit Shader,然后将其改成 Surface Shader,同样也可以将一个 Standard Surface Shader 改成顶点片断着色器,所以这一点一定要明白,它们只是内容格式不一样的模版本而已,我们完全可以自由修改成任意我们想要的一种着色器类型,当然我们也可以通过一些手段来定制出我们自己的模版,这在后续章节中我们再进行详细介绍。

    所以呢,我们接下来从最简单也是最基础的 Unlit Shader 开始我们的学习。 材质与 Shader 的关系 OK,继续上面,从 Shader 子菜单中选择"Unlit Shader"。创建完成后提示让我们输入新的名称,先不管它,默认即可。

    由于在 Unity 中 Shader 就是运行在图形显卡上的一段包含指令的代码,所以我们需要再创建一个材质来关联它,这样才能把材质赋给场景中的物体来实现我们想要的效果。 再创建一个材质( Material 或者经常被叫做材质球),由于材质也是一个资源,所以我们还是在 Project 面板中点击右键,从中选择 Create/Material,如下图所示:

    然后现在我们有了一个 Shader 和一个材质球,此时如果选中材质球在 Inspector 面板中就可以看到它的相关参数,比如这个材质所引用的是哪个 Shader,以及具体暴露出来的参数是哪些等等。

    我们先重点关注下其中的 Shader 这个参数。 它代表的是当前这个材质球是与哪个 Shader 绑定关联的,我们可以点击它的下拉列表框从中进行选择,前提是我们知道我们想关联的那个 Shader 的名字路径是哪个,这一点我们在后面会具体说明。

    现在我们采用拖动的方式进行关联,在 Project 面板下直接拖动 Shader 到材质球上,然后你会发现材质球的 Inspector 面板中的 Shader 参数就已经被指定为刚才那个 Shader 了。

    小技巧:在创建材质球的时候,如果我们选中某个 Shader,然后在它上面点击右键来创建材质的话,那么这个材质会自动与此 Shader 进行关联,无需再单独指定。

    让我们来总结一下 Shader 与材质的关系:

    一个 Shader 可以与无数个材质关联。 一个材质同一时刻只能关联于一个 Shader。(为什么说是同一时刻,因为我们可以通过代码去动态改变材质所关联的 Shader )

    材质可以赋与模型,但是 Shader 不行。 材质就像是 Shader 的实例,每个材质都可以参数不一样呈现不同的效果,但是当 Shader 改变时,关联它的所有材质都会相应的改变。

    举个例子:比如游戏中的怪物,我们只需做一个 Shader,然后每个怪物给它一个材质球,材质球上赋与不同的贴图与参数来表现不同的怪物。

    自学 Shader 步骤建议 给想自学 Shader 的同学先列出个学习步骤,可以按如下的顺序进行循序渐进的学习: Unlit Shader,这是最基本也是最简单的模版,通过学习它了解顶点片断着色器的基本构成,以及对 Shader 有初步的认识。

    利用顶点片断着色器做一些简单案例,比如一些游戏内常用的效果,与此同时深入学习 Shader 语法,以及用到的相关数学运算符。

    尝试优化你的 Shader,提升美术效果,减少变体数,减少运算量。 读一读渲染管线的书,加深对 Shader 理解的宽度与高度。 开始接触光照模型,了解 Surface Shader。 各种光照算法研究一通。。。。顺便复习下数学。。。 屏幕后处理 Shader Compute Shader 各种实例制作,不断强化美感、图形学以及数学。

    原文链接: https://connect.unity.com/p/ling-ji-chu-ru-men-unity-shader-er?app=true 更多干货,戳上方链接下载 Unity 官方 app,在线互动答疑技术社区,学习交友两不误!

    ]]>
    如何实现存储和加载游戏数据 tag:www.v2ex.com,2020-01-13:/t/637466 2020-01-13T05:38:24Z 2020-01-13T05:35:24Z unn member/unn 本文转自 Unity Connect 博主 Michael Wang

    特别针对 ScriptableObject 的特殊性来实现存储和加载

    提到存储,目前 Unity 中常用的存储有:XML、Json、PlayerPrefs。

    今天我们就用 Binary 二进制和 Json 结合我们之前的背包系统来真是存储我们的游戏数据(背包数据)有关背包系统的内容可以参考另外一篇文章:

    https://connect.unity.com/p/kuai-su-zuo-yi-ge-jian-dan-de-bei-bao-xi-tong?app=true

    要存储文件就需要得到游戏文件的路径从而创建文件夹和存储的文件

    我们使用 Unity 提供的 Application.persistentDataPath 来获得不同平台的游戏目录。

    public void SaveGame()

    { Debug.Log(Application.persistentDataPath);//找不到的小伙伴就 Debug 一下程序路径喽~ if (!Directory.Exists(Application.persistentDataPath + "/game_SaveData"))//判断是否有存储文件夹 { Directory.CreateDirectory(Application.persistentDataPath + "/game_SaveData");//创建文件夹 } } 

    接下来我们就来创建一个用来存储数据的文件了:

    public void SaveGame()

    { Debug.Log(Application.persistentDataPath);//程序路径 if (!Directory.Exists(Application.persistentDataPath + "/game_SaveData"))//判断是否有存储文件夹 { Directory.CreateDirectory(Application.persistentDataPath + "/game_SaveData");//创建文件夹 } BinaryFormatter formatter = new BinaryFormatter();//二进制转化 FileStream file = File.Create(Application.persistentDataPath + "/game_SaveData/inventory.txt");//创建存储文件,扩展名随便 var json = JsonUtility.ToJson(myInventory);//转换成 Json 格式 formatter.Serialize(file, json);//序列化将 json 变量的 string 内容保存在 file 里面 file.Close(); } 

    完成了以上保存的代码,我们做两个 UI - Button 来执行 SaveGame() 和 LoadGame() 并点击 Save 保存一下测试。

    我们找到对应平台的文件夹下的文件:

    想要打开并读取这里的数据,我们需要一些能读取二进制的软件,这里我用 iHex 打开它:

    我们发现右侧能明显看出来我的 itemList 里面的结构,instanceID:0 代表没有数据 这样我们就成功的保存了 ScriptableObject 了。结下的 Load 方法也类似:

    public void LoadGame()

    { BinaryFormatter bf = new BinaryFormatter(); if (File.Exists(Application.persistentDataPath + "/game_SaveData/inventory.txt")) { FileStream file = File.Open(Application.persistentDataPath + "/game_SaveData/inventory.txt", FileMode.Open); JsonUtility.FromJsonOverwrite((string)bf.Deserialize(file), myInventory); file.Close(); } } 

    原文链接: https://connect.unity.com/p/ru-he-shi-xian-cun-chu-he-jia-zai-you-xi-shu-ju?app=true 更多干货,欢迎戳上方链接下载 Unity Connect 官方 app,在线技术答疑,交友学习两不误!

    ]]>
    Unity3D 实用技巧 - 基础数学库函数学习 tag:www.v2ex.com,2020-01-10:/t/636804 2020-01-10T05:44:17Z 2020-01-10T05:41:17Z unn member/unn 本文转自 Unity Connect 博主 北京琳云信息科技有限责任公司

    Mathf 数学函数库浅析 1、Mathf.Abs 绝对值

    计算并返回指定参数 f 绝对值。

    2、Mathf.Acos 反余弦

    static function Acos (f : float) : float

    以弧度为单位计算并返回参数 f 中指定的数字的反余弦值。

    3、Mathf.Approximately 近似

    static function Approximately (a : float, b: float) : bool

    比较两个浮点数值,看它们是否非常接近, 由于浮点数值不精确,不建议使用等于来比较它们。例如,1.0==10.0/10.0 也许不会返回 true。

    public class example : MonoBehaviour { public void Awake() { if(Mathf.Approximately(5.0F, 50.0F / 50.0F)) print("近似"); } } 4、Mathf.Asin 反正弦

    static function Asin (f : float) : float

    以弧度为单位计算并返回参数 f 中指定的数字的反正弦值。

    5、Mathf.Atan2 反正切

    static function Atan2 (y : float, x :float) : float

    以弧度为单位计算并返回 y/x 的反正切值。返回值表示相对直角三角形对角的角,其中 x 是临边边长,而 y 是对边边长。

    返回值是在 x 轴和一个二维向量开始于 0 个结束在 (x,y) 处之间的角。

    public class example : MonoBehaviour { public Transform target; void Update() { Vector3 relative = transform.InverseTransformPoint(target.position); float angle = Mathf.Atan2(relative.x, relative.z) * Mathf.Rad2Deg; transform.Rotate(0,angle, 0); } } 6、Mathf.Atan 反正切

    static function Atan (f : float) :float

    计算并返回参数 f 中指定的数字的反正切值。返回值介于负二分之 pi 与正二分之 pi 之间。

    7、Mathf.CeilToInt 最小整数

    static function CeilToInt (f : float) : int

    返回最小的整数大于或等于 f。

    8、Mathf.Ceil 上限值

    static function Ceil (f : float) : float

    返回 f 指定数字或表达式的上限值。数字的上限值是大于等于该数字的最接近的整数。

    9、Mathf.Clamp01 限制 0~1

    static function Clamp01 (value : float) :float

    限制 value 在 0,1 之间并返回 value。如果 value 小于 0,返回 0。如果 value 大于 1 ,返回 1,否则返回 value。

    10、Mathf.Clamp 限制

    static function Clamp (value : float, min :float, max : float) : float

    限制 value 的值在 min 和 max 之间, 如果 value 小于 min,返回 min。 如果 value 大于 max,返回 max,否则返回 value。

    static function Clamp (value : int, min :int, max : int) : int

    限制 value 的值在 min 和 max 之间,并返回 value。

    11、Mathf.ClosestPowerOfTwo 最近的二次方

    static function ClosestPowerOfTwo (value :int) : int

    返回距离 value 最近的 2 的次方数。

    12、Mathf.Cos 余弦

    static function Cos (f : float) : float

    返回由参数 f 指定的角的余弦值(介于 -1.0 与 1.0 之间的值)。

    13、Mathf.Deg2Rad 度转弧度

    static var Deg2Rad : float

    度到弧度的转化常量。(只读)

    这等于(PI * 2) / 360。

    14、Mathf.Mathf.Rad2Deg 弧度转度

    static var Rad2Deg : float

    弧度到度的转化常量。(只读)

    这等于 360 / (PI * 2)。

    15、Mathf.DeltaAngle 增量角

    static function DeltaAngle (current :float, target : float) : float

    计算给定的两个角之间最短的差异。

    // Prints 90 Debug.Log(Mathf.DeltaAngle(1080,90)); 16、Mathf.Epsilon 小正数

    static var Epsilon : float

    一个很小的浮点数值。(只读)

    最小的浮点值,不同于 0。

    以下规则:

    一个在任意数和 Epsilon 的之间值将导致在任意数发生截断误差。

    public class example : MonoBehaviour { bool isEqual(float a, float b) { if(a >= b - Mathf.Epsilon && a <= b + Mathf.Epsilon) return true; else return false; } }
    17、Mathf.Exp 指数

    static function Exp (power : float) : float

    返回 e 的 power 次方的值。

    18、Mathf.FloorToInt 最大整数

    static function FloorToInt (f : float) :int

    返回最大的整数,小于或等于 f。

    19、Mathf.Floor 下限值

    static function Floor (f : float) : float

    返回参数 f 中指定的数字或表达式的下限值。下限值是小于等于指定数字或表达式的最接近的整数。

    20、Mathf.Infinity 正无穷

    static var Infinity : float

    表示正无穷,也就是无穷大,∞ (只读)

    21、Mathf.InverseLerp 反插值

    计算两个值之间的 Lerp 参数。也就是 value 在 from 和 to 之间的比例值。

    //现在参数是 3/5 float parameter =Mathf.InverseLerp(walkSpeed, runSpeed, speed); 22、Mathf.IsPowerOfTwo 是否 2 的幂

    static function IsPowerOfTwo (value : int): bool

    如果该值是 2 的幂,返回 true。

    // prints false Debug.Log(Mathf.IsPowerOfTwo(7)); // prints true
    Debug.Log(Mathf.IsPowerOfTwo(32)); 23、Mathf.LerpAngle 插值角度

    static function LerpAngle (a : float, b :float, t : float) : float

    和 Lerp 的原理一样,当他们环绕 360 度确保插值正确。

    a 和 b 是代表度数。

    public class example : MonoBehaviour { public float minAngle = 0.0F; public float maxAngle = 90.0F; void Update() { float angle = Mathf.LerpAngle(minAngle, maxAngle, Time.time); transform.eulerAngles= new Vector3(0, angle, 0); } } } 24、Mathf.Lerp 插值

    static function Lerp (from : float, to :float, t : float) : float

    基于浮点数 t 返回 a 到 b 之间的插值,t 限制在 0 ~ 1 之间。

    当 t = 0 返回 from,当 t = 1 返回 to。当 t = 0.5 返回 from 和 to 的平均值。

    25、Mathf.Log10 基数 10 的对数

    static function Log10 (f : float) : float

    返回 f 的对数,基数为 10。

    26、Mathf.Log 对数

    static function Log (f : float, p : float): float

    返回参数 f 的对数。

    // logarithm of 6 in base 2 //以 2 为底 6 的对数 // prints 2.584963 print(Mathf.Log(6, 2)); 27、Mathf.Max 最大值

    static function Max (a : float, b : float): float

    static function Max (params values :float[]) : float

    返回两个或更多值中最大的值。

    28、Mathf.Min 最小值

    static function Min (a : float, b : float): float

    static function Min (params values :float[]) : float

    返回两个或更多值中最小的值。

    29、Mathf.MoveTowardsAngle 移动角

    static function MoveTowardsAngle (current :float, target : float, maxDelta : float) : float

    像 MoveTowards,但是当它们环绕 360 度确保插值正确。

    变量 current 和 target 是作为度数。为优化原因,maxDelta 负值的不被支持,可能引起振荡。从 target 角推开 current,添加 180 度角代替。

    30、Mathf.MoveTowards 移向

    static function MoveTowards (current :float, target : float, maxDelta : float) : float

    改变一个当前值向目标值靠近。

    这实际上和 Mathf.Lerp 相同,而是该函数将确保我们的速度不会超过 maxDelta。maxDelta 为负值将目标从推离。

    31、Mathf.NegativeInfinity 负无穷

    static var NegativeInfinity : float

    表示负无穷,也就是无穷小,-∞(只读)

    32、Mathf.NextPowerOfTwo 下个 2 的幂

    //Prints 8 to the console Debug.Log(Mathf.NextPowerOfTwo(7)); 33、Mathf.PingPong 乒乓

    static function PingPong (t : float, length: float) : float

    0 到 length 之间往返。t 值永远不会大于 length 的值,也永远不会小于 0。

    The returned value will move back and forthbetween 0 and length.

    返回值将在 0 和 length 之间来回移动。

    34、Mathf.PI 圆周率

    static var PI : float

    PI (读 pai )的值,也就是圆周率(π)的值 3.14159265358979323846...(只读)

    35、Mathf.Pow 次方

    static function Pow (f : float, p : float): float

    计算并返回 f 的 p 次方。

    36、Mathf.Repeat 重复

    static function Repeat (t : float, length :float) : float

    循环数值 t,0 到 length 之间。t 值永远不会大于 length 的值,也永远不会小于 0。

    这是类似于模运算符,但可以使用浮点数。

    public class example : MonoBehaviour { void Update() { transform.position= new Vector3(Mathf.Repeat(Time.time, 3), transform.position.y,transform.position.z); } } 37、Mathf.RoundToInt 四舍五入到整数

    static function RoundToInt (f : float) :int

    返回 f 指定的值四舍五入到最近的整数。

    如果数字末尾 0.5,因此它是在两个整数中间,不管是偶数或是奇数,将返回偶数。

    38、Mathf.Round 四舍五入

    static function Round (f : float) : float

    返回浮点数 f 进行四舍五入最接近的整数。

    如果数字末尾是 0.5,因此它是在两个整数中间,不管是偶数或是奇数,将返回偶数。

    39、Mathf.Sign 符号

    static function Sign (f : float) : float

    返回 f 的符号。

    当 f 为正或为 0 返回 1,为负返回-1。

    40、Mathf.Sin 正弦

    static function Sin (f : float) : float

    计算并返回以弧度为单位指定的角 f 的正弦值。

    41、Mathf.SmoothDampAngle 平滑阻尼角度

    static function SmoothDampAngle (current :float, target : float, ref currentVelocity : float, smoothTime : float,maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float

    参数

    current:当前的位置。

    target:我们试图达到的位置。

    currentVelocity:当前速度,这个值在你访问这个函数的时候会被随时修改。

    smoothTime ( the target faster ) :要到达目标位置的近似时间,实际到达目标时要快一些。

    maxSpeed:可选参数,允许你限制的最大速度。

    deltaTime:上次调用该函数到现在的时间。缺省为 Time.deltaTime。

    随着时间的推移逐渐改变一个给定的角度到期望的角度。 这个值通过一些弹簧减震器类似的功能被平滑。这个函数可以用来平滑任何一种值,位置,颜色,标量。最常见的是平滑一个跟随摄像机。

    //一个简单的平滑跟随摄像机 //跟随目标的朝向 public class example : MonoBehaviour { public Transform target; public float smooth = 0.3F; public float distance = 5.0F; private float yVelocity = 0.0F; void Update() { //从目前的 y 角度变换到目标 y 角度
    float yAngle = Mathf.SmoothDampAngle(transform.eulerAngles.y, target.eulerAngles.y,ref yVelocity, smooth); //target 的位置
    Vector3 position = target.position; //然后,新角度之后的距离偏移
    position += Quaternion.Euler(0, yAngle, 0) * new Vector3(0, 0, -distance); //应用位置
    transform.position= position; //看向目标
    transform.LookAt(target); } }
    42、Mathf.SmoothDamp 平滑阻尼

    static function SmoothDamp (current :float, target : float, ref currentVelocity : float, smoothTime : float,maxSpeed : float = Mathf.Infinity, deltaTime : float = Time.deltaTime) : float

    参数

    current:当前的位置。

    target:我们试图达到的位置。

    currentVelocity:当前速度,这个值在你访问这个函数的时候会被随时修改。

    smoothTime:要到达目标位置的近似时间,实际到达目标时要快一些。

    maxSpeed:可选参数,允许你限制的最大速度。

    deltaTime:上次调用该函数到现在的时间。缺省为 Time.deltaTime。

    描述

    随着时间的推移逐渐改变一个值到期望值。

    这个值就像被一个不会崩溃的弹簧减振器一样被平滑。这个函数可以用来平滑任何类型的值,位置,颜色,标量。

    public class example : MonoBehaviour { public Transform target; public float smoothTime = 0.3F; private float yVelocity = 0.0F; void Update() { float newPosition = Mathf.SmoothDamp(transform.position.y, target.position.y, refyVelocity, smoothTime); transform.position= new Vector3(transform.position.x, newPosition, transform.position.z); } } 43、Mathf.SmoothStep 平滑插值

    static function SmoothStep (from : float,to : float, t : float) : float

    和 lerp 类似,在最小和最大值之间的插值,并在限制处渐入渐出。

    public class example : MonoBehaviour { public float minimum = 10.0F; public float maximum = 20.0F; void Update() { transform.position= new Vector3(Mathf.SmoothStep(minimum, maximum, Time.time), 0, 0); } } 44、Mathf.Sqrt 平方根

    static function Sqrt (f : float) : float

    计算并返回 f 的平方根。

    45、Mathf.Tan 正切

    static function Tan (f : float) : float 计算并返回以弧度为单位 f 指定角度的正切值。

    Unity3D 官方数学库学习教程:

    https://docs.unity3d.com/ScriptReference/Mathf.html

    原文链接:https://connect.unity.com/p/unity3d-shi-yong-ji-qiao-ji-chu-shu-xue-ku-han-shu-xue-xi

    欢迎大家戳上方链接,下载 Unity 官方 app,和博主一起探讨交流,在这里可以认识很多有趣的小伙伴,还有在线技术答疑,更多实用干货等你来发现。

    ]]>
    AStar-寻路原理 tag:www.v2ex.com,2020-01-09:/t/636473 2020-01-09T05:45:04Z 2020-01-09T05:42:04Z unn member/unn 本文转自 Unity Connect 官方博主 majorWu

    起源

     由于一次面试被问起 AStar 算法原理,我当场面红耳赤,不知怎么开口,这个耳熟能详的寻路算法,我对它的原理却浑然不知,一直都有听大家说到这个算法,也有调用过相关接口,然自己却那么陌生,真想一头钻到地底。于是就有这边篇记录 AStar 算法原理的学习文章。 AStar 算法是一种静态路网中求解最短路径最有效的直接搜索方法。在包含各种障碍物的地图中,为游戏角色的移动,寻找一条到目标地点最短路径。 

    简介

     AStar (又称 A*),它结合了 Dijkstra 算法的节点信息(倾向于距离起点较近的节点)和贪心算法的最好优先搜索算法信息(倾向于距离目标较近的节点)。可以像 Dijkstra 算法一样保证找到最短路径,同时也像贪心最好优先搜索算法一样使用启发值对算法进行引导。 简单点说,AStar 的核心在于将游戏背景分为一个又一个格子,每个格子有自己的靠谱值,然后通过遍历起点的格子去找到周围靠谱的格子,接着继续遍历周围…… 最终找到终点。 

    AStar 算法原理

     在游戏的地图中,AStar 会被预先烘焙成相应的格子记录相关信息。 

    经典原图 上图 A 表示起点,B 表示终点,蓝色方块表示障碍物,我们需要找出一条路,从 A 点开始,绕过蓝色方块,到达 B 点。

    首先说一下用到的名词:

    开放列表:一个记录所有被考虑来寻找最短路径的网格集合

    关闭列表:一个记录下不会被考虑的网格集合

    G:表示从起点方格移动到网格上指定方格的移动耗费 (可沿斜方向移动).

    H:表示从指定的方格移动到终点方格的预计耗费 (H 启发函数).

    这就是 Astar 核心部分:路径代价

     权重值:F=G+H,G 和 H 前面我们有介绍过,我们将根据这个权重值引导每一步的走向,直至目标点 那么 G 和 H 我们应该怎么计算,下面给出了三种基本的估价算法(也称估价公式) 

    分别为:曼哈顿估价法、几何估价法、对角线估价法

    我们以曼哈顿估价法为例:起点 A(X1,Y1),终点 B(X2,B2),那么距离可根据上图第一幅图可得出:D=X2-X1+Y2-Y1

    现在我们知道了估价法,接下来我们继续下一步方块的确定。

    通过 F 值比较,我们找出 F 值最小的方块,也就是当前方块右侧的那块,标记为 C ( F 值为 40 ),我们把当前方块放入关闭列表,周围的方块放到开启列表中,然后同上反复操作,找到 C 周围相邻的方块(过滤掉蓝色方块的障碍区域及已被添加到关闭列表的),估价计算出相应的 F 值,上下 F 值一样,然后随机找到了 D,把当前 C 放入关闭列表,周围的方块放到开启列表中(已存在的跳过)

    实现步骤:

    1.把起始格添加到开启列表。

    2.重复如下的工作:

     a) 寻找开启列表中估量代价 F 值最低的格子。我们称它为当前格。 b) 把它切换到关闭列表。 c) 对相邻的 8 格中的每一个进行如下操作 * 如果它不可通过或者已经在关闭列表中,略过它。反之如下。 * 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录这一格的 F,G,和 H 值。 * 如果它已经在开启列表中,用 G 值为参考检查新的路径是否更好。更低的 G 值意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的 G 和 F 值。如果你保持你的开启列表按 F 值排序,改变之后你可能需要重新对开启列表排序。 d) 停止,当你 * 把目标格添加进了关闭列表(注解),这时候路径被找到,或者 * 没有找到目标格,开启列表已经空了。这时候,路径不存在。 

    3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。这就是你的路径。

    最后本文是按自己对 AStar 的理解,按照自己的思路写出来,作为总结和备忘。有不对或理解错误的地方,烦请指正。由于无法上传图片,详细内容请看原文。

    原文链接: https://connect.unity.com/p/astar-xun-lu-yuan-li?app=true 欢迎大家戳上方链接,下载 Unity 官方 app,和博主一起探讨交流,在这里可以认识很多有趣的小伙伴,还有在线答疑,更多实用干货。

    ]]>
    如何编写更契合 Unity 的高效代码? tag:www.v2ex.com,2020-01-08:/t/636132 2020-01-08T06:01:53Z 2020-01-08T05:58:53Z unn member/unn 本文转自 Unity Connect 官方博主 郡墙

    在 Unity 中的 Effectie C# 思想分享

    尽可能使用属性(property),而不是直接访问数据成员 属性允许将数据成员作为共有接口的一部分暴露出去,同时仍旧提供面向对象环境下所需的封装。属性这个语言元素可以让你像访问数据成员一样使用,但其底层依旧是使用方法实现的

    可以非常轻松的在 get 和 set 代码段中加入检查机制

    因为属性是用方法实现的,所以它拥有方法所拥有的一切语言特性

    属性增加多线程的支持是非常方便的。你可以加强 get 和 set 访问器( accessors )的实现来提供数据访问的同步

    属性可以被定义为 virtual。

    可以把属性扩展为 abstract。

    可以使用泛型版本的属性类型。

    属性也可以定义为接口。

    因为实现实现访问的方法 get 与 set 是独立的两个方法,在 C# 2.0 之后,你可以给它们定义不同的访问权限,来更好的控制类成员的可见性

    而为了和多维数组保持一致,我们可以创建多维索引器,在不同的维度上使用相同或不同类型。

    需要在类型的公有或保护接口中暴露数据

    偏向于使用运行时常量(readonly),而不是编译时常量(const) const

    应仅仅在那些性能异常敏感,且常量的值在各个版本之间绝对不会变化时,再使用编译时常量。

    编译时常量( const )的值会被目标代码中的值直接取代

    仅能用于数值和字符串

    readonly

    在运行时进行求值。引用运行时生成的 IL 将引用到 readonly 变量,而不是变量的值。

    可以为任意类型。运行时常量必须在构造函数或初始化器中初始化,因为在构造函数执行后不能再被修改

    可以用 readonly 值保存实例常量,为类的每个实例存放不同的值。而编译时常量就是静态的常量。

    有时候你需要让某个值在编译时才确定,就最好是使用运行时常量

    标记版本号的值就应该使用运行时常量,因为它的值会随着每个不同版本的发布而改变。

    const 优于 readonly 的地方仅仅是性能,使用已知的常量值要比访问 readonly 值略高一点,不过这其中的效率提升,可以说是微乎其微的。

    结论 综上,在编译器必须得到确定数值时,一定要使用 const。例如特性( attribute )的参数和枚举的定义,还有那些在各个版本发布之间不会变化的值。除此之外的所有情况,都应尽量选择更加灵活的 readonly 常量。

    推荐使用 is 或 as 操作符而不是强制类型转换 as 和 is 操作符都不会执行任何用户自定义的转换,它们仅当运行时类型符合目标类型时才能转换成功,也不会在转换时创建新的对象

    is

    检查一个对象是否兼容于其他指定的类型,并返回一个 Bool 值,永远不会抛出异常

    as 运算符对值类型是无效,此时可以使用 is,配合强制类型转换进行转换。

    仅当不能使用 as 进行转换时,才应该使用 is 操作符。否则 is 就是多余的

    as

    作用与强制类型转换是一样,但是永远不会抛出异常,即如果转换不成功,会返回 null。

    相对于强制类型转换来说,as 更加安全,也更加高效

    转换失败时会返回 null,在转换对象是 null 时也会返回 null,所以使用 as 进行转换时,只需检查返回的引用是否为 null 即可

    使用条件属性(Conditional attribute)而不是 #if 条件编译 由于 #if/#endif 很容易被滥用,使得编写的代码难于理解且更难于调试

    使用条件特性可以将函数拆分出来,让其只有在定义了某些环境变量或设置了某个值之后才能编译并成为类的一部分。Conditional 特性最常用的地方就是将一段代码变成调试语句。

    Conditional 特性只可应用在整个方法上

    任何一个使用 Conditional 特性的方法都只能返回 void 类型。

    不能在方法内的代码块上应用 Conditional 特性。

    不可以在有返回值的方法上应用 Conditional 特性

    Conditional 特性的方法可以接受任意数目的引用类型参数

    Conditional 特性生成的 IL 要比使用#if/#Eendif 时更有效率

    将其限制在函数层面上可以更加清晰地将条件性的代码分离出来,以便进一步保证代码的良好结构

    理解几个等同性判断之间的关系 值类型

    覆写 Object.Equals()实例方法和 operatior==(),以便为其提供效率更高的等同性判断

    引用类型

    认为相等的含义并非是对象标识相等时,才需要覆写 Object.Equals( )实例方法。在覆写 Equals( )时也要实现 IEquatable<t>。</t>

    引用相等

    如果两个引用类型的变量指向的是同一个对象

    值相等

    两个值类型的变量类型相同,而且包含同样的内容

    public static bool ReferenceEquals (object left, object right)

    判断两个不同变量的对象标识( object identity )是否相等。无论比较的是引用类型还是值类型,该方法判断的依据都是对象标识,而不是对象内容。

    public static bool Equals (object left, object right)

    用于判断两个变量的运行时类型是否相等。

    public virtual bool Equals(object right)

    用于重载

    public static bool operator ==(MyClass left, MyClass right)

    用于重载

    注意

    不应该覆写 Object.referenceEquals()静态方法和 Object.Equals()静态方法

    了解 GetHashCode() 的一些坑 基于散列(hash)的集合定义键的散列值时,此类集合包括 HashSet<t>和 Dictionary<K,V>容器等</t>

    对引用类型来讲,索然可以正常工作,但是效率很低。对值类型来讲,基类中的实现有时甚至不正确。

    在.NET 中,每个对象都有一个散列码,其值由 System.Object.GetHashCode()决定

    实现自己的 GetHashCode( )

    如果两个对象相等(由 operation==定义),那么他们必须生成相同的散列码。否则,这样的散列码将无法用来查找容器中的对象。

    对于任何一个对象 A,A.GetHashCode()必须保持不变。

    对于所有的输入,散列函数应该在所有整数中按随机分别生成散列码。这样散列容器才能得到足够的效率提升

    理解短小方法的优势 C#代码翻译成可执行的机器码的步骤

    C#编译器将生成 IL,并放在程序集中

    JIT 将根据需要逐一为方法(或是一组方法,如果涉及内联)生成机器码

    短小的方法让 JIT 编译器能够更好地平摊编译的代价。

    短小的方法也更适合内联

    简化控制流程

    控制分支越少,JIT 编译器也会越容易地找到最适合放在寄存器中的变量

    不仅体现在代码的可读性上,还关系到程序运行时的效率

    选择变量初始化而不是赋值语句 初始化器将在所有构造函数执行之前执行

    成员初始化器是保证类型中成员均被初始化的最简单的方法

    若是所有的构造函数都要将某个成员变量初始化成同一个值,那么应该使用初始化器。

    正确地初始化静态成员变量 静态初始化器

    特殊的函数 (在其他所有方法执行之前以及变量或属性被第一次访问之前执行)

    只是需要为某个静态成员分配空间

    静态构造函数

    复杂一些的逻辑来初始化静态成员变量

    可捕获异常

    使用构造函数链(减少重复初始化逻辑) 示例说明

    class Foo

    {

    private int id; private string name; public Foo() : this(0, "") { } public Foo(int id, string name) { this.id = id; this.name = name; } public Foo(int id) : this(id, "") { } public Foo(string name) : this(0, name) { } 

    }

    Foo a = new Foo(), b = new Foo(456,"def"), c = new Foo(123), d = new Foo("abc");

    SomeType x = new SomeType(), y = new SomeType { Key = "abc" },z = new SomeType { DoB = DateTime.Today };

    [SeeAlso]

    实现标准的销毁模式 若某个引用类型(值类型无所谓)的局部变量用于被频繁调用的例程中,那么应该将其提升为成员变量

    为常用的类型实例提供静态对象

    创建不可变类型的最终值(比如 string 类的+=操作符会创建一个新的字符串对象并返回,多次使用会产生大量垃圾,不推荐使用。对于简单的字符串操作,推荐使用 string.Format。对于复杂的字符串操作,推荐使用 StringBuilder 类)

    区分值类型和引用类型 引用类型

    class

    用来定义应用程序的行为

    对类型未来的用图不确定

    值类型

    struct

    无法实现多态

    最佳用途就是存放数据

    主要职责在于数据存储

    类型的公有接口都是由访问其数据成员的属性定义

    该类型绝不会有派生类型

    该类型永远都不需要多态支持

    保证 0 为值类型的有效状态 自定义枚举值时

    确保 0 是一个有效的选项

    自定义标志(flag)时

    0 定义为没有选中任何状态的标志(比如 None )

    作为标记使用的枚举值(即添加了 Flags 特性)应该总是将 None 设置为 0

    保证值类型的常量性和原子性 不要盲目地为类型中的每一个属性都创建 get 和 set 访问器

    限制类型可见性 emm.... 这个意会意会

    通过定义并实现接口替代继承 抽象基类( abstract class )

    为一组相关的类型提供了一个共同的抽象

    描述了对象是什么

    可以为派生类提供一些具体的实现

    接口( interface )

    一种契约式的设计方式

    一个实现某个接口的类型,必须实现接口中约定的方法

    描述了对象将如何表现其行为

    不能包含实现

    不能包含任何具体的数据成员

    理解接口方法和虚方法的区别 接口中声明的成员方法默认情况下并非虚方法,所以,派生类不能覆写基类中实现的非虚接口成员。若要覆写的话,将接口方法声明为 virtual 即可

    基类可以为接口中的方法提供默认的实现,随后,派生类也可以声明其实现了该接口,并从基类中继承该实现。

    实现接口拥有的选择要比创建和覆写虚方法多。我们可以为类层次创建密封( sealed )的实现,虚实现或者抽象的契约。还可以创建密封的实现,并在实现接口的方法中提供虚方法进行调用

    用委托实现回调 委托为我们提供了类型安全的回调定义。虽然大多数常见的委托应用都和事件有关,但这并不是 C#委托应用的全部场合。当类之间有通信的需要,并且我们期望一种比接口所提供的更为松散的耦合机制时,委托便是最佳的选择

    委托允许我们在运行时配置目标并通知多个客户对象。委托对象中包含一个方法的应用,该方法可以是静态方法,也可以是实例方法。也就是说,使用委托,我们可以和一个或多个在运行时联系起来的客户对象进行通信

    由于回调和委托在 C#中非常常用,以至于 C#特地以 lambda 表达式的形式为其提供了精简语法。

    由于一些历史原因,.NET 中的委托都是多播委托( multicast delegate )。多播委托调用过程中,每个目标会被依次调用。委托对象本身不会捕捉任何异常。因此,任何目标抛出的异常都会结束委托链的调用。

    用事件模式实现通知 事件提供了一种标准的机制来通知监听者,而 C#中的事件其实就是观察者模式的一个语法上的快捷实现。

    事件是一种内建的委托,用来为事件处理函数提供类型安全的方法签名。任意数量的客户对象都可以将自己的处理函数注册到事件上,然后处理这些事件,这些客户对象无需在编译器就给出,事件也不必非要有订阅者才能正常工作。

    在 C#中使用事件可以降低发送者和可能的通知接受者之间的耦合,发送者可以完全独立于接受者进行开发

    避免放回对内部类对象的引用 值类型。当客户代码通过属性来访问值类型成员时,实际返回的是值类型的对象副本。

    常量类型。如 System.String。

    定义接口。将客户对内部数据成员的访问限制在一部分功能中。

    包装器( wrapper )。提供一个包装器,仅暴露该包装器,从而限制对其中对象的访问。

    若将引用类型通过公有接口暴露给外界,那么对象的使用者即可绕过我们定义的方法和属性来更改对象的内部结构,这会导致常见的错误。

    仅用 new 修饰符处理基类更新 使用 new 操作符修饰类成员可以重新定义继承自基类的非虚成员

    new 修饰符只是用来解决升级基类所造成的基类方法和派生类方法冲突的问题

    new 操作符必须小心使用。若随心所欲的滥用,会造成对象调用方法的二义性。

    原文链接: https://connect.unity.com/p/efficient-c-with-unity?app=true

    戳上方链接,下载 Unity 官方 app,话题群里,在线技术答疑,还有更多干货等你来发现~

    ]]>
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86