Ykuee的博客 https://www.ykuee.link 欢迎来我的博客逛逛 Thu, 19 Jan 2023 05:03:28 +0000 zh-CN hourly 1 https://www.ykuee.link/wp-content/uploads/2020/12/cropped-logo-32x32.png Ykuee的博客 https://www.ykuee.link 32 32 DDD让我充血了 https://www.ykuee.link/archives/607 https://www.ykuee.link/archives/607#comments Thu, 19 Jan 2023 03:40:51 +0000 https://www.ykuee.link/?p=607 服务到底怎么划分?服务的边界到底在哪里?适合的怎么才算适合?到底怎么设计?真让人摸不到头脑。

第一次上手搞这些东西令我犹犹豫豫,不敢下手。

领域模型和数据模型

领域模型一般是贴合业务的,数据模型一般是注重技术实现上的。

领域模型关注的是领域知识,是业务领域的核心实体,体现了问题域里面的关键概念,以及概念之间的联系。领域模型建模的关键是看模型能否显性化、清晰的表达业务语义,扩展性是其次。
数据模型关注的是数据存储,所有的业务都离不开数据,都离不开对数据的 CRUD,数据模型建模的决策因素主要是扩展性、性能等非功能属性,无需过分考虑业务语义的表征能力

通常我们的定义的实体中,一般不会带有行为,只是属性,一堆get/set,没有增删改查等操作,这些操作一般在service中。
领域模型中的实体是会有的,把领域对象看成一个实体,这个实体在各种状态变换后仍是保持一致的,而不是属性的变换。对这些对象而言,重要的不是其属性,而是其延续性和标识。

实体和值对象

定义:
实体:许多对象不是由它们的属性来定义,而是通过一系列的连续性(continuity)和标识(identity)来从根本上定义的。只要一个对象在生命周期中能够保持连续性,并且独立于它的属性(即使这些属性对系统用户非常重要),那它就是一个实体。
值对象:当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它相应的行为。我们需要将值对象看成不变对象,不要给它任何身份标识,还应该尽量避免像实体对象一样的复杂性。

1674100798423.png

当然如何划分实体与值对象是根据业务来的。

实体人员,原包括:姓名、年龄、性别及所在省、市、县和街道等属性。这样显示地址相关属性就很零碎。
就可将 “省、市、县和街道等属性” 拿出来构成一个 “地址属性集合”,该集合就是值对象。领域模型会更加贴合面对对象编程,对象之间有明确的上下文关系。
在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。在数据模型设计时,地址值对象可以作为一个属性集整体嵌入人员实体中,组合形成上图这样的数据模型。

 

贫血模型

在贫血模型中领域模型一般都是贫血模型。平常用的mvc架构基本上都是如此。

举个例子来讲,User、UserDAO 作为数据访问层,UserBO、UserService 作为业务逻辑层,UserVO、UserController 作为接口层;

其中 UserBO 只作为纯粹的数据结构,没有业务处理,业务逻辑集中在 Service 中。

像 UserBO 这样的纯数据结构的就可以称之为贫血模型,同样的还有 User 和 UserVO,这样的设计破坏了 Java 面向对象设计的封装特性,属于面向过程的编程风格。

充血模型

基于充血模型的 DDD 开发模式,与贫血模型相反的是,充血模型将数据和业务放在一个类里面。DDD 领域驱动设计,DDD 核心是为了根据业务对系统的服务进行拆分。领域驱动设计的核心还是基于对业务的理解,不能一味追求这样的概念。

对于充血模型的开发的 MVC 架构,其核心区别在于 Service 层:包含 Domain 类和 Service 类。Domain 对于 BO 而言,添加了一定的业务逻辑,降低 Service 中的业务逻辑量。那么充血模型对于贫血模型好在哪里呢?

对于贫血模型而言,由于数据和业务的分离,数据在脱离业务的情况下可以被任务程序修改,数据操作将不受限制等。

为什么贫血模型这么盛行?一是对于大部分业务而言都比较简单,基本上都是围绕 SQL 的 CRUD 操作,仅仅通过贫血模型设计就可以完成业务。而是充血模型的设计难度较大。

 

DDD分层架构

领域模型关注的是领域知识,是业务领域的核心实体,体现了问题域里面的关键概念,以及概念之间的联系。领域模型建模的关键是看模型能否显性化、清晰的表达业务语义,扩展性是其次。

数据模型关注的是数据存储,所有的业务都离不开数据,都离不开对数据的 CRUD,数据模型建模的决策因素主要是扩展性、性能等非功能属性,无需过分考虑业务语义的表征能力
1674099232815.png
1.用户接口层 UI,负责界面展示。
2.应用层Application Layer,负责业务流程
3.领域层Domain,负责领域逻辑。
4.基建层Infrastructure Layer,负责提供基建。
分类的依据是:越往上,预期变动越频繁;越往下,预期变动越少。

用户接口层

面向前端用户提供服务和数据适配。这一层聚集了对外接口和数据适配相关的功能。用户接口层在前后端分离设计时,主要完成后端微服务与前端不同用户的接口和数据适配。

用户接口层主要有Facade接口和DTO以及DO数据的组装和转换等代码逻辑。

应用层

应用层是用来连接用户接口和领域层的,很薄的一层,主要只能是协调领域层多个聚合完成服务的组合和编排,调度用的,不体现业务逻辑。

应用层之上是用户接口层,在应用层完成领域层服务组合和编排后,应用服务被用户接口层Facade服务封装,完成接口和数据适配后,以粗粒度的服务通过API网关面向前端应用发布。

此外,应用层也是微服务之间服务调用的通道,微服务在应用层可以调用其他微服务的应用服务,完成微服务之间的服务组合和编排。

在应用层主要有应用服务、事件订阅和发布等相关代码逻辑。

其中,应用服务主要负责服务的组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等。

注意:在微服务设计和开发时,应用层主要职能是服务的组合和编排,切记不要将本该在领域层的核心领域逻辑在应用层实现。这会使得领域模型失焦,时间一长应用层和领域层的边界就会变得混乱,边界清晰的四层架构慢慢可能就演变成了业务逻辑混杂的三层架构了。

领域层

领域层位于应用层之下,是领域模型的核心,主要实现领域模型的核心业务逻辑,体现领域模型的业务能力。用来表达业务概念、业务状态和业务规则,可以通过各种业务规则校验手段保证业务的正确性。

在设计时,领域层主要关注实现领域对象或者聚合自身的原子业务逻辑,不太关注外部用户操作或者流程等方面的业务逻辑。所以在领域层主要体现的是领域模型的能力。外部易变的如流程、业务组合和编排的需求由应用层完成。这样设计可以保证领域模型不易受外部需求的变化而受影响,从而保证领域模型的稳定。

领域建模时提取的大部分领域对象都放在领域层。微服务的领域层可能会有多个聚合,聚合内部一般都有聚合根、实体、值对象和领域服务等领域对象。它们组合在一起协同实现领域模型的核心业务能力。

注意:在选择用实体方法或者领域服务实现业务逻辑时,请记住不要滥用领域服务。如果将单一实体自身的业务行为也用领域服务来实现,这样就很容易变成贫血模型。

基础层

基础层贯穿了DDD所有层,它的主要职能就是为其他各层提供通用的技术和基础服务,包括如第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。我们常见的功能是完成实体的数据库持久化。

基础层主要有仓储服务代码逻辑。仓储采用依赖倒置设计,封装基础资源逻辑的服务实现,实现应用层、领域层与基础层的解耦,降低外部资源变化对领域逻辑的影响。

 

微服务代码目录结构
1674099896087.png

服务视图

1674102825374.png

基础层

基础层的服务形态主要是仓储服务。仓储服务包括仓储接口和仓储实现两部分。
仓储接口服务可以供应用层或者领域层服务或方法调用。
仓储实现服务完成领域对象的持久化或提供数据初始化所需要的PO数据。

领域层

领域层实现核心业务逻辑,负责表达领域模型业务概念、业务状态和业务规则。
领域层主要服务的形态有实体方法和领域服务。
实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,具体实现形式是实体类中的方法。实体是微服务内的原子业务对象,在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力,这是一种面向对象的编程方法。
实体方法不过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。
DDD提倡富领域模型,尽量将业务逻辑归属到实体对象上,实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排,实现跨多个实体的复杂核心业务逻辑。
你也可以认为领域服务是介于实体和应用服务之间的薄薄的一层。它的主要职能是实现领域层复杂核心领域逻辑的组合和封装。
采用严格分层架构时,实体方法如果需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务。

应用层

应用层主要面向前端应用和用户,根据前端用例和流程要求,通过服务组合和编排实现粗粒度的业务行为。
应用层主要服务形态有:应用服务和事件订阅服务。
应用服务负责服务的组合、编排和转发,负责处理业务用例的执行顺序和结果的拼装,负责不同聚合之间的服务和数据协调,负责微服务之间的事件发布和订阅。
通过应用服务对外暴露微服务的内部核心领域功能,这样可以隐藏领域层核心业务逻辑的复杂性和内部的实现机制。
应用服务用于组合和编排的服务,主要来源于领域服务,也可以是外部微服务的应用服务。
除了完成服务的组合和编排外,应用服务内还可以完成安全认证、权限校验、初步的数据校验和分布式事务控制等功能。
提示:为了微服务内聚合的解耦,聚合之间的服务调用和数据交互,可通过应用服务完成。原则上我们应该尽量避免聚合之间的领域服务直接调用和聚合之间的数据库表关联。

用户接口层

用户接口层是前端应用和微服务之间服务访问和数据交换的桥梁。
用户接口层的主要服务形态是facade接口服务。
facade接口服务处理前端发送的Restful请求和解析用户输入的配置文件等,将数据传递给应用层。或者获取应用服务的数据后,进行数据组装,向前端提供数据服务。
facade接口服务分为接口和实现两个部分,完成服务定向。通过assembler组装器,完成DO与DTO数据的转换和组装,完成前端应用与应用层数据的转换和交换。
facade接口服务本质上就是端口适配器架构模型中的适配器,面向前端应用和用户提供主动适配。

数据视图

在DDD中有很多的实体和数据对象,这些对象分布在不同的层里。它们在不同的阶段有不同的形态,分别承担不同的职能。
数据持久化对象 (Persistent Object,PO),与数据库结构一一映射,它是数据持久化过程中的数据载体。
领域对象(Domain Object,DO),微服务运行时核心业务对象的载体,DO一般包括实体或值对象。
数据传输对象(Data Transfer Object,DTO),用于前端应用与微服务应用层或者微服务之间的数据组装和传输。是应用之间数据传输的载体。
视图对象(View Object,VO),用于封装展示层指定页面或组件的数据。
可以通过下图来具体了解微服务各层数据对象的职责和转换过程。
1674103399904.png

基础层

微服务基础层的主要数据对象是PO。在设计时,我们需要先建立DO和PO的映射关系。大多数情况下DO和PO是一一对应的。但也有DO和PO多对多的情况。在DO和PO数据转换时,需要进行数据重组。对于DO对象较多复杂的数据转换操作,你可以在聚合用工厂模式来实现。
当DO数据需要持久化时,先将DO转换为PO对象,由仓储实现服务完成数据库持久化操作。
当DO需要构建和数据初始化时,仓储实现服务先从数据库获取PO对象,将PO转换为DO后,完成DO数据构建和初始化。

领域层

领域层主要是DO对象。DO是实体和值对象的数据和业务行为载体,承载着基础的核心业务逻辑,多个依赖紧密的DO对象构成聚合。领域层DO对象在持久化时需要转换为PO对象。

应用层

应用层主要对象有DO对象,但也可能会有DTO对象。应用层在进行不同聚合的领域服务编排时,一般建议采用聚合根ID的引用方式,应尽量避免不同聚合之间的DO对象直接引用,避免聚合之间产生依赖。
在涉及跨微服务的应用服务调用时,在调用其他微服务的应用服务前,DO会被转换为DTO,完成跨微服务的DTO数据组装,因此会有DTO对象。
在前端调用后端应用服务时,用户接口层先完成DTO到DO的转换,然后DO作为应用服务的参数,传导到领域层完成业务逻辑处理。

用户接口层

用户接口层主要完成DO和DTO的互转,完成微服务与前端应用数据交互和转换。
facade接口服务在完成后端应用服务封装后,会对多个DO对象进行组装,转换为DTO对象,向前端应用完成数据转换和传输。
facade接口服务在接收到前端应用传入的DTO后,完成DTO向多个DO对象的转换,调用后端应用服务完成业务逻辑处理。

前端应用

前端应用主要是VO对象。展现层使用VO进行界面展示,通过用户接口层与应用层采用DTO对象进行数据交互。
提醒:数据转换主要目的是为了各层解耦,以保证领域模型的稳定,也是为了让微服务具有更强的扩展能力和适配能力。但每一次数据转换都是以性能作为代价,在设计时需要在性能和扩展能力之间找到平衡。

]]>
https://www.ykuee.link/archives/607/feed 6
森林 The Forest 欢乐野营 https://www.ykuee.link/archives/602 https://www.ykuee.link/archives/602#comments Sat, 07 Jan 2023 04:52:29 +0000 https://www.ykuee.link/?p=602 The Forest 2023-01-07 125140.jpg
最近和朋友趁着冬季特卖入手了森林,16块钱买不了吃亏,买不了上当。
一个人害怕野人,两个人应对野人,三个人化身野人,我们三个在森林里疯狂游荡,成为了野人的噩梦。
这游戏我记得在上大学的时候就已经有试玩版了,大概是15年? 正式发售在18年。
刚刚开荒的时候还是有点儿恐怖的气氛,到处采果子,烤鸟肉。
下了飞机不知道咋玩,在海边建了第一个小屋子当做存档点,野人也不多。
多亏这游戏的ai智商低,鸟就在身边乱飞,贴地飞行的那种,海龟也是乱爬,鹿只要被打中一下就不动了。靠着这些笨蛋的贡献,实现了食物自给自足。

在海边散步的三个野人:
20221228230310_1.jpg

在下了第一个野人洞之后,我觉得自己行了,用龟壳滑下大坑,不料没整好,摔死了。
为了拿回自己的尸体,接连死了四次,啥都没了,因为死的太快,连送的手斧都没了,陷入了死亡循环。我受不了了,我崩溃了。
在重新读档之后,游戏进度又回到了第一天玩的时候,这次不狂了,还是在海边建基地吧。

砍原木真是效率太低了,还得来回搬运。想上网查一下有没有加快效率的方法,发现了有小推车,又无意中发现可以利用bug刷资源,这下一发不可收拾,开始罪恶的卡bug。

此时在bug的帮助下,基地已经比较壮观了:
20221231230852_1.jpg

我直接化身为森林设计师,少一块平台的死亡楼梯,将老寒腿的扬哥摔死。恐怖围墙,将扬哥围困的动弹不得。
高速致命滑索会直接飞到海里,游上来直接获得debuff又湿又冷。
我感觉这游戏坑队友才是最快乐的。

变异野人拆家太快,大胖子一屁股就会给围墙干碎,队友打野人,我在后面苟着修围墙哈哈哈!
为了防御野人,在家门口设置陷阱,最主要的作用还是用来。。。坑队友。哈哈哈!
正门口全是陷阱,一不小心老寒腿发作,就要被打掉血。
大家出门都小心翼翼。
20230101232150_1.jpg

探索了全岛,该拿的武器道具也都拿到了。
满装备重新进发大坑:
20230102163856_1.jpg

要我说信号枪才是这个游戏的神器。趁队友不在电脑前,一发信号弹点燃队友,然后赶紧跑,装作无事发生。在角落里听队友的惨叫,宛如天籁一般!太美妙了!
20230102203147_1.jpg

深情对视的二人:
20230102000030_1.jpg

提米和他的三个老父亲:
20230102221415_1.jpg

最后也是选择了放弃拯救儿子,拯救了全世界。
20230102221835_1.jpg

用鞭炮后入:
20230102230754_1.jpg

最后再看一眼基地
20230102225715_1.jpg

]]>
https://www.ykuee.link/archives/602/feed 6
SMILEMO 为了难而难的游戏 https://www.ykuee.link/archives/598 https://www.ykuee.link/archives/598#comments Tue, 03 Jan 2023 03:16:54 +0000 https://www.ykuee.link/?p=598 1672715952082.png
最近游戏荒,看了这个游戏还挺吸引我,玩了一周目,总共4个小时。
SMILEMO是一款平台像素动作类游戏,是和JumpKing、掘地求生类似的那种,一碰到尖刺就会不停的后退,运气不好就会回家。
我的打法就是突出一个莽,所以经常回家。
1672717035839.png

我的评价是:不想再玩第二遍。
并不是说这个游戏特别难,而是玩起来确实不开心,玩的时候心里窝着火。

操作手感:作为动作游戏来讲,手感挺不错,有点儿惯性,但不是脚底抹油那种。需要小小适应一下。
游戏节奏:循序渐进,每关都会获得一个新能力。能力多了游戏内容更复杂,更难。
美术:像素,感觉颜色很扎眼。整体就是蓝绿红黑这几种颜色。主要满屏鲜红的刺儿,嗯,刺太多了!最后一关整个屏幕几乎都是红色,角色在跑动时,配合这个颜色令我的眼睛十分甚至九分的疲劳!狗看了摇头,我看了流泪。
音乐:忘记了,没留下什么印象。
核心玩法:平台跳跃类游戏,主打一个跳跃、避障、追逐、地形杀、时机。高惩罚性。

简评:
整体流程较短,是一款为了难而难的游戏。除了需要耐心,技术以外,还需要人品。
游戏后期刷怪的机制让人难受。小怪物会在地图上逛,还会随机加速,运气不好就会回家。
有时需要耐心等待机关。浮游炮攻击频率不固定,要耐心等怪物被打死再移动。
有的地方需要盲视野跳跃,试错成本比较高。
有时感觉是因为流程短做的故意拖延,堆怪。感觉只留下关键怪物刷新就好,不必一直刷新小怪。
后续可以多出一些关卡,地形杀是不错的,需要背板的地方多来点儿。因为我个人不太喜欢这种太拼运气的地方。随机要素让我比较心烦。
最终boss打起来也是看运气,boss房里会刷小怪。如果不刷追踪小怪的话,游戏会简单很多。

1672719007150.png
1672719031497.png
1672719043789.png

]]>
https://www.ykuee.link/archives/598/feed 15
元旦快乐! https://www.ykuee.link/archives/596 https://www.ykuee.link/archives/596#comments Sun, 01 Jan 2023 04:54:35 +0000 https://www.ykuee.link/?p=596 一晃眼2022年就过完了。
可能打算做个总结,但感觉又没什么好总结的。。。

今天打算再种一点儿猫草,拿出来种子一看,里面已经生米虫了。
明明上周还没有来着,吃的种子上全是小洞。

这米虫叫“米象”,看起来是象鼻虫的一种吧。

想起来我在小时候还养过一些虫子,当时觉得好玩,抓起来放在一个磁带盒里,
每天投喂一点儿菜叶子。
不知道养了多久,最后都全军覆没了。
记得这里面有金龟子、象鼻虫、蜗牛、还有些不知道叫啥的虫子。
都是长得挺奇特的。

金龟子颜值是可以的,背甲很漂亮!捉了好几只关在里头。
2023-01-01 125253.jpg

前方米虫出没!
65a21e046e6be7a71d7cfbffd188605.jpg
e20d7e8331c918a8380477a58192869.jpg
b2be7532358404ea4013b356997eae5.jpg
d24e09834320c180711b3ba21d9f3b4.jpg
e5508dfc3ae499ad00a50d12b461856.jpg

]]>
https://www.ykuee.link/archives/596/feed 4
win下查看连过的WiFi密码 https://www.ykuee.link/archives/588 https://www.ykuee.link/archives/588#comments Wed, 07 Dec 2022 03:10:02 +0000 https://www.ykuee.link/?p=588 cmd命令

//1.查找wifi上所有连过的wifi
netsh wlan show profile

//2.显示wifi详细信息

netsh wlan show profile 无线名称 key=clear

安全设置-关键内容 会显示保存过的密码

]]>
https://www.ykuee.link/archives/588/feed 4
Unity3D一些简单的实现 https://www.ykuee.link/archives/582 https://www.ykuee.link/archives/582#comments Sun, 20 Nov 2022 05:09:14 +0000 https://www.ykuee.link/?p=582 刚刚开始学习unity,如果有更好的方法,请教教我!万分感谢!

 

摄像机固定到物体,跟随物体移动

简单的写法

public class CameraMove : MonoBehaviour
{
    // Start is called before the first frame update

    Vector3 Dir;
    //要跟随的物体
    public GameObject Player;
    // Use this for initialization
    void Start()
    {
        //获取到摄像机于要跟随物体之间的距离
        Dir = Player.transform.position - transform.position;
    }

    //LateUpdate是在所有Update函数调用后被调用
    void LateUpdate()
    {
        //摄像机的位置
        transform.position = Player.transform.position - Dir;
    }

}

 

将屏幕鼠标位置转换到世界位置

这里我只是想获取平面坐标x、z,忽略高度y。

// 相机是世界的,世界到屏幕
Vector3 camera = Camera.main.WorldToScreenPoint(firePoint.position);
Vector3 pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, camera.z);
Vector3 mousePoint = Camera.main.ScreenToWorldPoint(pos);
mousePoint.y = 0;

 

解决角色斜向移动速度变快

初中学过的知识已经完全不记得了。。。

        if (forword && left)
        {
            float z = Time.deltaTime * playerSpeed;
            float x = Time.deltaTime * -playerSpeed;
            movePoint.z = z * Mathf.Sqrt(1 - (x * x) / 2);
            movePoint.x = x * Mathf.Sqrt(1 - (z * z) / 2);
            this.transform.Translate(movePoint, Space.Self);
        }

 

使用slider制作一个简易血条

创建一个slider

2022-11-20 130718.jpg

将大小与位置调整一下,选择一个血条的颜色,将Image Type选择为Filled,滑动下方的fillAmount就可以看见滑块的效果。

1668921069573.png

使用脚本控制FillAmount的值,就可以达到效果。

    private void BarFiller()
    {
        //血条始终面向屏幕
        Camera camera = Camera.main;
        playerHeathBar.transform.LookAt(playerHeathBar.transform.position + (camera.transform.rotation * Vector3.back), camera.transform.rotation * Vector3.up);

        PlayerLogic logic = this.GetComponentInParent<PlayerLogic>();
        float maxHealth = logic.maxHealth;
        float health = logic.health;
        //使用MathF.Lerp平滑血条滑动 lerpSpeed滑动速度
        playerHeathBar.fillAmount = Mathf.Lerp(playerHeathBar.fillAmount, health / maxHealth, lerpSpeed * Time.deltaTime);
    }

 

]]>
https://www.ykuee.link/archives/582/feed 2
uniyt 定时,向量,预制体 – 笔记 https://www.ykuee.link/archives/577 https://www.ykuee.link/archives/577#respond Sat, 12 Nov 2022 15:19:33 +0000 https://www.ykuee.link/?p=577 调度

invoke(delay) 在delay之后执行一次

invokeRepeating(func,delay,interval) 在dealy之后以interval为间隔执行

unity核心是单线程的,所以不必担心。
获取当前线程
using System.Threading;
Thread.CurrentThread.ManagedThreadId

java中package与c#中的namespace

package都是同一级的,没有父子关系
namespace是可以有子namespace的,划分粒度更细。
引入父namespace就可以访问子namespace中的内容。

IsInvoking("methodName")判断函数是否在调度
CancelInvoke("methodName")取消函数所有调度

向量

获得向量的长度

flot m = v3.magnitude;

单位向量
标准化向量 按比例

Vector3 v = v3.normailzed;//把v3转化为标准向量

向量的运算

向量是支持加减法运算的

a + b xyz分别相加

向量的乘法

b = a * 2

向量测距

即物体轴心点之间的距离

使用减法
距离=a-b

静态方法
Vector3.Distance(a,b)

向量的使用

向量作为脚本的参数

预制体

预先制作好的模型,相当于类。
*.prefab

预制体的创建

先制作好一个样本节点。
直接拖到Assents窗口,就会自动生成一个预制体资源。
原始物体可以删除。
需要导出依赖文件。

预制体示例

perfabInstance
预制体实例的上下文工具不一样,窗口中标记为淡蓝色。
实体上右键unpack可以去掉关联预制体。

预制体的编辑

单体编辑。

双击预制体,进入单体编辑。
预制体被修改,实例也会被同步修改。

原位编辑

选择预制体实例,点击inspector中open按钮,仅编辑此实例。

覆盖编辑

选择预制体实例,点击inspector中overrides按钮,将实例的修改覆盖至预制体。
由于预制体被修改,其他实例也会被同步修改。

多级节点

多个预制体组合,合成一个具有父子关系的预制体。

api脚本生命周期流程图
monobehaviour_flowchart.jpg

]]>
https://www.ykuee.link/archives/577/feed 0
unity 输入与组件调用 – 笔记 https://www.ykuee.link/archives/573 https://www.ykuee.link/archives/573#respond Sun, 06 Nov 2022 12:49:35 +0000 https://www.ykuee.link/?p=573 15 输入

鼠标相关api
button values are 0 for left button, 1 for right button, 2 for the middle button. The return is true when the mouse button is pressed down, and false when released.
//按下事件
Input.GetMouseButtonDown(0);
//抬起事件
Input.GetMouseButtonUp(1);
//按住鼠标状态
Input.GetMouseButton(2);
//按下a键事件 小写
Input.GetKeyDown("a")
枚举 KeyCode.A

获取鼠标二维屏幕位置 摄像头中位置
Input.mousePosition
单位为像素,二维平面,左下角为原点(0,0)

Screen.width
Screen.height

获取实体在二维屏幕上的位置
Camera.main.WorldToScreenPoint(obj.transform.position);
可以判断实体是否在屏幕中,是否超出视野范围。

获取实体中的组件

AudioSource audioTest = this.GetComponent();
通过audioTest可以控制音频组件中的参数

引用其他物体组件
其组件必须为public

调用其他脚本方法
sendMessage利用反射,但是效率低下,所以基本很少看到有人用
第一种,被调用脚本函数为static类型,调用时直接用 脚本名.函数名()
//能调用public和private类型函数
第二种,GameObject.Find("脚本所在的物体的名字").SendMessage("函数名");
//可以添加参数
第三种,GameObject.Find("Main Camera").GetComponent().SendMessage("SetWipeOutNum",null);
//只能调用public类型函数
第四种,GameObject.Find("脚本所在的物体的名字").GetComponent<脚本名>().函数名();

]]>
https://www.ykuee.link/archives/573/feed 0
unity 脚本 – 笔记 https://www.ykuee.link/archives/571 https://www.ykuee.link/archives/571#respond Sun, 06 Nov 2022 12:47:26 +0000 https://www.ykuee.link/?p=571 --------脚本编程--------

9.1 脚本

unity中使用vsCode编辑脚本
在unity中 菜单-edit-External Tools-选择用vsCode编辑
如果vsCode没有代码提示,需要使用vsCode打开整个unity项目,在控制台输出日志Omnisharp Log的输出中检查是否缺少对应版本的.net SDK,下载相应的sdk。在vscode中安装c#插件。

unity加载的脚本,文件名必须与类名一致,这点和java一样了。其实c#的话是允许不一致的,只不过unity要求一致。

9.2 当前物体

中文API https://docs.unity3d.com/cn/2021.2/ScriptReference/index.html

ToString("F3") flot格式化三位小数
update方法 帧更新
帧时间间隔Time.deltaTime

11.1 物体的移动

float dy = speed*Time.deltaTime;
this.transform.Translate(0,dy,0,Space.Self);

11.1.1 引用问题

Java的函数调用都是值传递 只不过有的对象、引用型变量是引用传递
C#既有值传递又有引用传递 分类型 值传递类型:结构,枚举,基础数据类型(排除string)

结构体和类的区别
结构体是值类型,类是引用类型结构体存在栈中,类存在堆中结构体成员不能使用protected访问修饰符,而类可以结构体成员变量申明不能指定初始值,而类可以结构体不能申明无参的构造函数,而类可以结构体申明有参构造函数后,无参构造不会被顶掉结构体不能申明析构函数,而类可以结构体不能被继承,而类可以。结构体需要在构造函数中初始化所有成员变量,而类随意结构体不能被静态static修饰(不存在静态结构体),而类可以

结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。

结构体和类最大的区别是在存储空间上的,因为结构体是值,类是引用,因此他们的存储位置一个在栈上,一个在堆上,通过之前知识点的学习,我相信你能够从此处看出他们在使用的区别——值和引用对象在赋值时的区别。

由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符。

如何选择结构体和类
要用到继承和多态时,直接淘汰结构体。比如玩家、怪物等等
对象是数据集合时,优先考虑结构体。比如位置、坐标等等从值类型和引用类型赋值区别上去考虑,比如时长需要被赋值被传递的对象,且改变赋值的对象,原对象不想跟着变时,就用结构体。比如坐标、向量、旋转等等

Unity 为什么不能直接修改vector3.x yz
原因就在于Vector3是一个结构体,而position是一个自动实现的属性。 public struct Vector3
在C#里,结构体是值类型的,值类型在通过方法传递的时候,所传递的只是值的副本。
C#的属性可以说是两个分别名为get和set的方法
而当我们写出形如 transform.position.x = 1 这样的代码时,是在通过get方法得到position,然后修改position的x字段的值。
但是,由于position是结构体类型的,get得到的也只是position的一个副本,而对这个副本所作出的任何修改,都对原positon没有任何影响,因此这样的修改是毫无意义的。编译器会禁止这样的修改操作,也就是可以理解的了。

获取实体

//用名称 很容易出错
GameObject qb = GameObject.Find("TestObj");
一般都定义一个public成员变量,然后在unity绑定此脚本的实体inspector里指定。

父物体
在Transform中维护的
this.transform.parent
顶级的话没有父物体,那就是返回null。
重设父物体
transform.SetParent(Transform p)

子物体
this.transform.GetChild(0);

foreach (Transform child in transform)
{
child.position += Vector3.up * 10.0f;
}
this.transform.Find("子名称/孙子名称");
添加子物体
this.transform.add

隐藏子物体
Transform child = this.transform.Find("子物体名称");
if(child.gameObject.activeSelf){
child.gameObject.SetActive(false);
}

相对运动的方法

Space枚举 World世界 Self自身
float speed = 1;
float distance = speed * Time.deltaTime;
this.transform.Translate(0,0,distance,Space.Self);

//转向物体
GameObject qb = GameObject.Find("TestObj");
this.transform.LookAt(qb.transform);

//指定向上方向的向量。
Vector3.magnitude

//自转
float jiao = 60;
this.transform.Rotate(0, jiao * Time.deltaTime, 0, Space.Self);

//公转
1.让父节点转动,子节点也会跟着转
this.transform.parent.Rotate(0, jiao * Time.deltaTime, 0, Space.Self);
2.围绕位置转动 RotateAround
this.transform.RotateAround(this.transform.parent.position,Vector3.up, jiao * Time.deltaTime);

常见回调函数
Awake 初始化 执行一次 总是执行
Start 初始化 执行一次 组件禁用不会执行
Update 帧更新
OnEnable 此实体或脚本组件启用时调用
OnDisable 此实体或脚本组件禁用时调用

13.3 脚本执行顺序

整体按阶段执行
执行所有awake -> 执行所有start -> ......
默认所有实体脚本谁先执行不确定,与层级管理中摆放顺序无关。可以指定优先级,01234 顺序执行

主控脚本

用来设置全局

脚本的参数

全局变量可以在unity里展示并编辑 必须声明为public。
这样不同的实体可以定义不同的变量,但是使用同一个脚本。
unity中参数赋值是先于脚本的awake执行的。

特性

[Tooltip("特性")]
public float speed = 1f;

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。 您可以通过使用特性向程序添加声明性信息。 一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

自定义结构体在Inspector上显示

1.在自定义结构体前加 [System.Serializable]
[System.Serializable]
public struct TestStruct
{
public float a;
public float b;
public float c;

}

2.在创建该结构体时在前面加 [SerializeField]
[SerializeField]
public TestStruct testStruct3;

在play状态下保存参数

可以在play下copy component
停止后在past component values
实现参数的拷贝

]]>
https://www.ykuee.link/archives/571/feed 0
unity基本操作 – 笔记 https://www.ykuee.link/archives/568 https://www.ykuee.link/archives/568#respond Sun, 06 Nov 2022 12:42:58 +0000 https://www.ykuee.link/?p=568 unity操作

scene 场景中可以开关默认的栅格、天空盒

视角与操作:

F 讲视角中心点移至选中物体
ALT+左键 以当前中心点旋转
ALT+中间 平移
ALT+右键 放大缩小视角

添加新物体位置 默认为当前视角的中心点

在导航器可以切换:
透视视图 perspective 近大远小
正交视图 orthographic 等距视图 isometric 可以用于对齐

场景摄像机 scene camera
修改摄像机广角,畸变 field of view

unity 尺寸单位为1米
unity是左手坐标系,旋转角度也是根据左手来的

q视角拖动
w移动
e旋转
r缩放
t
y

 

素材

 

网格 mesh
网格固定形状

材质 materials
定义粗糙度,光反射特性

纹理 贴图 texture

材质 Material包含贴图 Map,贴图包含纹理 Texture。纹理是最基本的数据输入单位,游戏领域基本上都用的是位图。此外还有程序化生成的纹理 Procedural Texture。贴图的英语 Map 其实包含了另一层含义就是“映射”。其功能就是把纹理通过 UV 坐标映射到3D 物体表面。贴图包含了除了纹理以外其他很多信息,比方说 UV 坐标、贴图输入输出控制等等。

材质是一个数据集,主要功能就是给渲染器提供数据和光照算法。贴图就是其中数据的一部分,根据用途不同,贴图也会被分成不同的类型,比方说 Diffuse Map,Specular Map,Normal Map 和 Gloss Map 等等。另外一个重要部分就是光照模型 Shader ,用以实现不同的渲染效果。

外部模型 FBX
一个FBX会包含
Mesh 网格
材质 material

模型细节
平面 plane
没有厚度,正面可见,背面透明。背面不会被渲染

FBX材质替换
1.重映射
2.使用外部材质

Assets 资源文件
常见类型
模型文件 Model *.fbx
图片文件 Texture *.jpg png psd tif
音频文件 AudioClip *.mp3 wav aiff
脚本文件 Script *.cs c#代码
材质文件 *.mat, 场景文件 *.unity

描述文件 *.meta

资源包 *.package
右键可以导出、导入

7.1 轴心

是指一个物体的基准点
在建模软件中指定轴心点,不一定在中心。 pivot

7.2 父子关系

子节点会随着父节点一并移动、旋转、删除。
子节点的坐标是相对坐标,相对于父节点轴心的坐标。
当然,旋转角度也一样

7.3 空物体

empty,没有网格数据,所以没有形状。
但是可以移动,旋转,操作。
用于节点的管理 作为父节点 一个容器,标记位置。

7.4 Global与Local

Global 全局坐标系
Local 本地坐标系 坐标轴变为物体轴心的坐标
方向:Y轴 up Z轴 forward X轴 right
一般模型的方向是与Z轴方向是一致的

 

7.5 Pivot与Center

Pivot 轴心点模式
Center 几何中心点模式
多选下
pivot旋转时会围绕各自的轴心点旋转
center旋转时会围绕多个模型计算后的中心点旋转

8.0 组件 Component

组件用来提供相应的功能
每加一个组件,就会多一个相应的功能
组件的排序没有先后,只做展示

Light 光源
Mash Filter 网格过滤器
Mash Render 网格渲染器

Mash Filter 负责加载网格
Mash Render 负责网格的渲染

Audio Source 音乐播放
Transfom 位置 缩放 旋转
Camera 摄像机
将摄像机拍摄角度移动到当前视角
GameObject-Align with view

]]>
https://www.ykuee.link/archives/568/feed 0