https://www.bilibili.com/video/BV1gQ4y1e7SS
本文基础部分参照此视频教程学习,这是作者写的一本书《新印象 Unity 2020游戏开发基础与实战 (飞羽(杜亚男)》可以作为参考,主要是整体的一个知识体系,至于具体的步骤等等将在进阶篇中和另一个视频Unity2024新手入门教程_超细节100集课程_哔哩哔哩_bilibili中学习,到时候看看有没有破解的教程。
作者要求先听喜马拉雅的影片,里面的劝告虽然我早就吃过亏,所以我将在文章中避免那些学习的大忌,这也是良好的学习习惯吧。
基础内容
什么是Unity?Unity是一套具有完善体系与编辑器的跨平台游戏开发工具,也可以称之为游戏引擎,游戏引擎是指一些编写好的可重复利用的代码与开发游戏所用的各功能编辑器。
Unity目前已超过50%的游戏引擎市场占有率。
Unity引擎优势
- 基于C#编程,拥有易上手、高安全性的特性。
- 独特的面向组件游戏开发思想让游戏开发更加简单易复用
- 拥有十分成熟的所见即所得开发编辑器
- 拥有良好生态圈,商城中包含大量成熟的功能脚本与资源
- 强大的跨平台特性,可以制作PC、主机、手机、AR、VR等多平台游戏
以下全是废话,建议直接跳到下载界面
VR(VirtualReality),即虚拟现实技术,具有沉浸性、交互性和构想性特征。VR技术集合了计算机图形学、仿真技术、多媒体技术、人工智能技术、计算机网络技术、并行处理技术和多传感器技术等多种技术,模拟人的视觉、听觉、触觉等感觉器官的功能,使人恍若身临其境,沉浸在计算机生成的虚拟世界中,并能通过语言、手势等进行实时交流,增强进入感和沉浸感。通过VR技术,让人在感受真实世界逼真的同时,还能突破时空等条件限制,感受到进入虚拟世界的奇妙体验。VR技术的应用十分广泛,如宇航员利用VR仿真技术进行训练;建筑师将图纸制作成三维虚拟建筑物,方便体验与修改;房地产商让客户能身临其境地参观房屋;娱乐业制作的虚拟舞台场景等等。
2012年,Oculus推出了OculusRift,开启了民用VR设备的热潮。2014到2016年,HTC、Sony、三星等公司紧跟Oculus均推出了自己的VR设备,奈何硬件开始占据市场,但软件略显疲惫,数量少、质量低,使得VR设备发展遇到阻碍。
2018年,出现了第一款现象级VR游戏一节奏光剑,同年销售破百万,成为了至今为止VR设备用户的标配游戏,使得VR再次进入了很多人的视野。
2019到2020年,VR开始设备换代,HTC、Oculus等公司都推出了自己的新款VR,在分辨率与便携性等方面都做了很大提升,比如堪称史上最强一体机的OculusQuest2。并且出现了第二款现象级游戏Half-LifeAlyx,也是第一款3A满分大作,极大的促进了VR的发展,加之同年疫情爆发,造成VR设备供应链断裂,如今,很多巨头公司看到了Half-LifeAlyx带来的效应,均开始回归VR市场并斥资开发自己的VR游戏。设备的进步也为VR虚拟仿真带来更多可能
越来越多的公司开始使用虚拟仿真,如汽车、房产、装修、医疗、工业、旅游等方面,导致即使疫情过后,整个就业环境疲惫,但是VR虚拟仿真的工作岗位依旧十分匮乏,可以明确的说,VR的未来一定是一个非常不错的新兴领域,现在国家也在大力扶持VR,发展空间十分大。
AR(AugmentedReality),即增强现实技术。这项技术是利用电子技术将虚拟的信息叠加到真实世界,通过手机、平板电脑等设备显示出来,被人们所感知,从而实现真实与虚拟的大融合,丰富现实世界。简而言之就是将本身平面的内容“活起来”,赋予实物更多的信息,增强立体感,加强视觉效果和互动体验感AR技术的常见应用,是利用手机摄像头,扫描现实世界的物体,通过图像识别技术在手机上显示相对应的图片、音视频、3D模型等。如一些软件的“AR红包"功能等。而更深层次的AR技术应用仍在探索中。这个qq在大概2015年有一个AR领取红包的活动技术。哎哎,越是学习,越是感觉天空之浩渺,压根学不完啊😅😭😭😭😭😭😭😭😭😭😭😭😭😭😭
MR全称MixedReality,即混合现实技术,是虚拟现实技术的进一步发展。它是通过在虚拟环境中
引入现实场景信息,将虚拟世界、现实世界和用户之间搭起一个交互反馈信息的桥梁,从而增强用户体验的真实感。MR技术的关键点就是与现实世界进行交互和信息的及时获取,也因此它的实现需要在一个能与现实世界各事物相互交互的环境中。如果环境都是虚拟的,那就是VR;如果展现出来的虚拟信息只是与虚拟事物的简单叠加,那则是AR。MR和AR的区别,简单而言:AR只管叠加虚拟环境却不需理会现实,但MR能通过一个摄像头让你看到裸眼都看不到的现实。
下载unity hub等
unity.cn直接下载unity hub,现在2024年底已经出来了unity6,当然也可以回退到经典的unity5甚至是4,登录后继续安装真正的unity,我安装的是Unity 6000.0.27f1c1,安装时提醒下载其他拓展功能是可以后期添加的。(说实在的,妈的非安装什么团结引擎,我也不做微信小游戏啊
“Unity个人版”许可证过期后只需要重新激活即可。“Unity个人版”包含了制作游戏所需的基本功能,对个人开发者而言已经足够:本书也会使用个人版进行讲解。“Unity加强版或专业版”包含制作一些大型商业游戏的高级功能,目前而言不会用到。
我们选择rider和.net来作为编译器开发,现在这个所有版本已经完全免费了
选择3D ,修改名字为FirstProject等待10秒(大量消耗CPU,记得开风扇,mac的文件复制机制是特殊的,相当于把加载时间摊到以后了
右上角可以选择2by3栏目,忘了搞中文可以在unity hub中添加中文模块,进入项目,选择Edit->Preferences有一个language选项
编辑这是工程设置projectsetting
我们打开preference,修改编译器为rider,当然是你要vs也行,这个无所谓,有.net即可
lauguage可以选择中文,英文也没问题,反正现在ai翻译多好啊,总之更改“语言”选项后要重启编辑器。
如图所示创建一个正方体,当然可以在右一栏里面 复制黏贴等等,也可以创建cube,capsule胶囊之类的,可以通过右侧inspector栏中scape调整他的长宽高等(缩放:键盘在英文模式下,按住X.Y.Z中任意一个,然后在数字区域左右拖动鼠标就行)按住滚轮滑动可以放大缩小
摄像机里看不到平面,但能看到四边形,通过这个可以看到表格和的区别,每个格子含有三角形
三角形越多,消耗的性能越大。(有限元分析Alt+鼠标左键,可以360度观察一个物体 ; 选择"手"可以任意拖动视角了,鼠标中键也任意拖动视角
unity使用左手坐标系,这和高中的立体几何不谋而合,即 左手定则
1.世界坐标系
在Unity的3D世界中有一个固定的左手坐标系,凡是添加到Unity中的物体都会有一个相对这个坐标系的位置。这个坐标系就是世界坐标系,通过世界坐标系就可以对每个物体进行定位。知道了每个物体在世界坐标系中的位置,也就能了解到这些物体在3D世界中的位置和关系。这个概念有点类似经纬度,在地球上,无论我们是哪一个国家和地区的人,所使用的都是同一个经纬度。只要你说出所在的经纬度,就能定位到你所处的位置。
2.本地坐标系
除了世界坐标系,每一个在Unity3D世界中的物体还拥有一个属于自己的左手坐标系,也叫作本地坐标系。在生活中,有时候遇到问路的人,你可能会说它就在我正前方200m的位置。这一描述并不是通过经纬度来指路的,而是以自己为坐标原点进行指路的,以自己为原点的坐标系其实就类似本地坐标系。
将一个3D模型拖到另一个模型中,会让他们有一个父子关系,拖动父亲会和儿子一样动.“此游戏对象相对于父级的局部位置” .任何物体都是世界位置的子物体
改变轴心会改变旋转的中心点 ,这样就可以让子物体的xyz轴回到物体里面,如图,选择pivot
对于global(全局)和轴心来获得是否
我们新建一个cube,位置全部归0,可以右上方三个小点,选择reset,默认就会放到000的位置上啊
最后一个小点是绿色边框,,边际碰撞器
实例,制作电脑桌

3d基础
如果要拖入什么文件,就拖到文件夹下面的assets里面 , 当我们创建或删除文件时,这些变化会直接反映在项目和本地文件系统中,二者保持同步。
对于外部资源的导入,如MP4、MP3、文本文档等,可以直接拖拽至assets文件夹下。特别是三维模型文件(例如FBX格式),它们可以直接拖入工程中,并可在Unity编辑器内被拖拽到场景视图中进行使用。这使得我们可以快速地将模型添加到场景中,丰富游戏内容。
当需要分享或转移多个资源时,Unity支持打包功能。通过选择要打包的资源,右键导出包,可以选择保存位置和包名。这个包可以被其他项目导入,从而简化了资源的共享流程。导入包时,可以选择性地导入所需资源,类似于解压过程。
关于模型显示,Unity中的每个模型都包含有材质信息,决定了其外观。材质球是控制模型表面属性的关键元素,如颜色、光滑度和金属感等。材质球通过应用不同的着色器(Shader)来实现多样化的视觉效果。默认情况下,Unity提供了一系列基本的材质球和着色器,但开发者也可以根据需求编写自定义的着色器以实现特定的效果。渲染=材质=色彩(图片)unity渲染管线
材质的使用方式非常简单,既可以将导入的材质直接拖曳到场景视图中的物体上进行应用,又可以在物体的MeshRenderer(网格渲染器)组件中进行材质的选择,如图2-39所示。
通过Unity提供的创建材质的文件,我们可以选择想要的颜色或贴图,再将材质应用到模型上,模型就会显示出对应的颜色或贴图。如果你导入一张图片,并希望将其作为模型贴图来显示,那么可以直接将图片拖曳到模型上,Unty会自动为模型创建材质。当然,材质的用法非常多,在以后的学习或工作中,我们还要持续地对材质部分进行学习。
此外,模型的颜色和纹理由材质球内的贴图决定,这直接影响了模型在场景中的表现形式。了解材质球和着色器的工作原理有助于更好地控制和优化游戏中的视觉呈现。
用于制作游戏的优质资源 | Unity Asset Store这是unity的资源网站库,从free和$99的大作都有(tmd,50000多资源里面只有3800多免费资源,这还不包括各大企业将自己的模型仅用于自家用户,比如【令和最新版】原神MMDをUnityで動かす話 ~URP対応を添えて~,哎哎,令人感慨,就好像某搜索引擎越来越垃圾一样.
选择一个模型,点击添加到unity中,如此所示:, 点击download
温馨提示:最新的商店里搜索后应该有两个start assets资源包,把那两个下下来,里面附带了这个资源包的所有功能,现在可以用Terrain Sample Asset Pack,不过说实在的,Terrain Sample Asset Pack这个包大小有点大,远观趋于写实的风格的后果如此
当然也可以和作者要package 夸克网盘分享 ,自行安装进去,我们直接把他拖到assets里面, 选择“更新全部的api”
选择“创建地形terrain”,兼容性难搞啊,还是踏踏实实用回Terrain Sample Asset Pack吧,编辑这样选择第5个,可以看出来长宽高1000,1000,600
编辑每一个小方格是1m*1m,
那么接下来,选择第一个按钮是创建相邻地形的意思, 第二个可以调整地形,如果地形下面只有四个选项,在第一个里面选raise or lower terrain,下方的size和opecity可以调整笔刷的大小和强度,还有hole挖山洞,set height设置固定高度的高原,stamp设置纹理,smooth平滑尖锐山峰,paint texture绘制地形中水,树,草
第三项,绘制树,
要在项目中免费试运行SpeedTree模型,您可以从Asset Store下载并安装免费的SpeedTrees包,然后使用Package Manager将这些资产直接导入项目中。 1.打开Web浏览器,在Unity Asset Store中搜索“Free SpeedTrees Package”,然后选择“Add to My Assets”按钮。 2.在Package Manage中,首先选择“Download”按钮,完成下载后再选择“Import”按钮。最后,在“Import Unity Packages”弹出窗口中再次选择“Import”按钮,将SpeedTrees Package保存到项目中。 在地形上绘制SpeedTrees之前,您首先需要重新生成SpeedTree材质,这样才能用于Universal Render Pipeline (URP)项目。只需快速调整即可轻松完成。
- 在“Project”窗口中导航至“Assets”文件夹内的“Free_SpeedTrees”文件夹。接下来,选择第一个名为“Broadleaf_desktop.spm”的树资产。 在Inspector中选择“Apply & Generate Materials”按钮。
- 重复上述步骤,为其余的以下名称的树重新生成材质:Broadleaf_Mobile、Conifer_Desktop和Palm_Desktop。现在可以开始在地形中应用这些树。 #使用Paint Trees工具 Paint Trees画笔工具的使用方式与Paint Terrain类似,但是您必须首先定义树网格才能使用画笔。
- 在“Hierarchy”窗口中选中“Terrain”,选择“Paint Trees”工具,即“Terrain”工具栏中的第三个按钮。选择“Edit Trees”按钮,然后从下拉菜单中选择“Add Tree”。
- 此时将显示“Add Tree”弹出窗口。选择“Tree Prefab”字段右侧的“Object Loader”圆圈按钮,选择树模型。 3.此时将显示“Select GameObject”弹出窗口。选择标记为“Broadleaf_Desktop”的树网格,然后在“Add Tree”窗口中选择“Add”按钮,将树模型加载到“Paint Trees”画笔工具中。 4.您现在可以在地形上绘制树了。调整“Brush Size”和“Tree Density”设置,确定树木放置的密度或稀疏程度。此外,勾选复选框以启用“Random Tree Height”,然后调整滑块范围以扩大或缩小随机性范围。始终勾选“Random Tree Rotation”,确保每棵树都按随机旋转角度放置,最后,调整滑块以提高或降低树的Color Variation。将光标放在“Scene”窗口的地形上,点击并拖动鼠标,从而有选择地将树纹理绘制到地形上。绘制时按住Shift键可删除树木。 5.通过调整“Brush Size”和“Tree Density”值,练习绘制小树林或大片森林。 #绘制细节 Paint Details画笔工具的使用方式与Paint Trees和Terrain画笔工具类似,但是您同样必须首先定义详细的网格才能使用画笔。在此例中,您将刻画草的细节。
编辑
- 在“Hierarchy”窗口中选中“Terrain”,选择“Paint Details”工具,即“Terrain”工具栏中的第四个按钮。选择“Edit Details”按钮,然后从下拉菜单中选择“Add Grass Texture”。
- 此时将显示“Add Grass Texture”弹出窗口。选择“Detail Texture”字段右侧的“Object Loader”圆圈按钮,选择草纹理。
- 此时将显示“Select Texture2D”弹出窗口。在搜索字段中输入“grass”,然后选择标记为“GrassFrond01AlbedoAlpha”的草网格。接下来,在“Add Grass Texture”窗口中选择“Add”按钮,从而将草纹理加载到“Paint Details”画笔工具中。
- 现在就可以在地形上绘制草纹理了。调整“Brush Size”和“Target Strength”设置,设置草纹理的大小和密度。将光标放在“Scene”窗口的地形上,点击并拖动鼠标,从而有选择地将草纹理绘制到地形上。绘制时按住Shift键可删除细节。 #地形设置工具 利用“Terrain Settings”工具,可以检查地形特征并进行调整。
- 在“Hierarchy”窗口中选中“Terrain”,选择“Terrain Settings”工具,即“Terrain”工具栏中的第五个,也是最后一个按钮。远了看不见tree是因为渲染距离
如果树是粉色的,可以看看这篇文章,这是渲染管线的问题,【unity小技巧】为啥我们的模型材质显示粉色?unity普通项目升级URP项目_unity模型粉色-CSDN博客
为什么这几个都变蓝?是没有更新的原因吗?都不行?为什么啊
第四个是草,第五个是花草树木的一些小细节
课后练习:
脚本基础
在Unity中,游戏物体是不具备任何功能的,如果想要为其添加功能,那么就需要为它添加该功能的组件,而每一个组件其实就是一个引擎内部的组件脚本或是由自己编写的组件脚本。也就是说,一个游戏物体(GameObject)会包含多个组件(Component),每一个组件又是一个组件脚本。本书选择使用C#语言来编写脚本。
技巧提示
组件是Unity中的重要组成部分,是Unity的灵魂所在,只有掌握了组件的使用和编程方法,才能更好地驾驭Unity。下面通过一个例子来说明怎样创建一个自己需要的游戏物体。假设在游戏场景中有一个立方体,它需要表示它在游戏世界中的位置、旋转和缩放信息,同时会不停地进行自转,并受到重力的影响而下落。此外当玩家按空格键时,它会立刻停止旋转和下落。当然,若要满足这些功能,就需要先创建一个立方体,创建完成后你会发现在立方体的“检查器”面板中已经包含了Transform(转换)组件、MeshFilter组件、MeshRenderer组件和BoxCollider(盒状碰撞器)组件等
Transform:描述了一个物体在3D世界中的位置、旋转和缩放信息。它是每一个游戏物体都具有的组件,也是不
能被删掉的必备组件。
MeshFilter:规定了该物体使用哪个网格显示。
MeshRenderer:包含了该物体与渲染相关的属性。
BoxCollider:设定了该物体在物理世界中的碰撞样式及大小。
我们现在已经知道,若要满足上述功能,需要给立方体添加自动旋转组件、物理组件和控制组件。Unity已经为我们提供了物理组件,并没有提供剩下的两个组件。那么接下来就需要我们来编写这两个功能的组件脚本,以丰富立方体的功能,编辑思路如图所示。
如果没有这个组件,比如是按wasd前后左右移动,unity没有这个组件,就自己写一个C#脚本。在Assets中右键创建一个脚本monobehave sprict,拖到cube的检查器中,即可。意思就是其实创建的是个空物体,让后给了个组件才有了实体的意思,他们都是Transform类里面的一个对象
添加组件的方式非常简单,有以下种。
第1种方式是添加自己编写的组件。在“项目”面板中执行“创建>C#脚本”命令创建一个脚本,创建的脚本就会在“项目”面板中显示出来。默认脚本的名称为NewBehabiourScript,可直接将其拖曳到游戏物体的“检查器”面板中;也可将其拖曳到“层级”面板中的对象上;还可将其拖曳到场景视图中的游戏物体上。这些操作都可以为游戏物体完成脚本组件的添加。第2种方式是添加Unity内置的组件。选择需要添加组件的游戏物体,然后单击“组件”菜单,接着选择需要添加的组件,如图3-7所示。
除此之外,选择需要添加组件的游戏物体,然后单击“检查器”面板下方的“添加组件”按钮添加件,在弹出的菜单中也可以选择需要添加的组件。当然,在搜索框中输入组件名称来搜索组件,这种方式操作起来更加方便,如图3-8所示。
注:其实这里的小对号选与不选只是不被渲染,不是禁用
还有复制组件,上下调整顺序,重置,粘贴组件值等等
按照同样的方式,在第2个立方体的BoxCollider组件中单击“更多”按钮并选择“粘贴为新组件”选项,即会发现系统自动将复制的第1个立方体的BoxCollider组件进行粘贴。也就是说,这个操作对第2个立方体的碰撞器没有产生任何影响,第2个立方体上拥有了两个碰撞器组件。
在第2个BoxCollider组件中单击“更多”按钮:并选择“粘贴组件值”选项,可以看到第2个立方体上并没有新增复制的组件,而是仅仅将复制的组件的数值粘贴到了该组件上。在以后的使用过程中,使用哪种粘贴方式要视情况而定。
双击move脚本自然跳转到rider中,我们可以看到一个类
脚本的生命周期
除了Start方法和Update方法,还有一些其他生命周期方法在游戏的开发过程中较为常用,接下来就来看一看这些生命周期方法的调用时间,如表所示。
方法名称 | 调用时间 |
Awake | 最早调用,所以一般可以在此实现单例模式 |
OnEnable | 组件激活后调用,在Awake后会调用一次 |
Start | 在Update之前调用一次,在OnEnable之后调用,可以在此设置一些初始值 |
FixedUpdate | 固定频率调用方法,每次调用与上次调用的时间间隔相同 |
Update | 帧率调用方法,每帧调用一次,每次调用与上次调用的时间间隔不相同 |
LateUpdate | 在Update每调用完一次后,紧跟着调用一次 |
OnDisable | 与OnEnable相反,组件未激活时调用 |
OnDestroy | 被销毁后调用一次 |
using System;
using UnityEngine;
public class move : MonoBehaviour
{
private void Awake()
{
Debug.Log("move");
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
比如说我们在unity中运行这个,控制台自然会打印move。Update和LateUpdate是帧刷新方法。
fixedUpdate则是固定的刷新,与电脑配置无关,这就是很多游戏帧率影响攻速之类的东西的原因啊
当一个游戏物体挂载了多个脚本时,每一个脚本都会有各自的生命周期方法,这时候就会产生一个问题。举例来说,现在有两个脚本,在第1个脚本的Start方法中设置了一个变量,在第2个脚本的Start方法中要输出这个变量。这时我们有可能会正确地看到变量输出,也可能看不到正确的变量输出,原因就是我们不知道这两个Start方法被调用的先后顺序。如果不希望在程序出现Bug后找不到问题所在,那么在遇到类似的情况后一定要注意顺序问题。
若出现顺序问题,接下来就需要解决顺序问题。执行“编辑>项目设置”菜单命令,打开ProjectSettings(项目设置)对话框,然后选择“脚本执行顺序”选项,在“脚本执行顺序”设置界面中单击“创建”按钮+添加需要调整顺序的脚本,然后通过拖曳来设定脚本的正确执行顺序。
从代码设计角度来讲 不推荐这么做 强烈不建议改,后面项目大了之后很麻烦的;
生命周期内的每个过程都会按照栈的顺序执行对应脚本 而不是一个脚本执行完在执行下一个
推荐的做法将在以后说;
- 使用事件和消息传递系统:可以考虑使用事件(Event)或消息(Message)传递系统,让脚本之间通过发布/订阅模式进行通信,而不是依赖执行顺序。
- 单例模式和依赖注入:利用单例模式或者依赖注入(Dependency Injection),可以在不关心脚本执行顺序的情况下访问所需的数据和服务。
- 初始化序列:为游戏对象或组件设计一个明确的初始化序列,确保所有需要先行执行的逻辑都在正确的时机被调用。
1.使用系统标签
标签的设置非常简单,选择需要添加标签的游戏物体,在“检查器”面板中展开“标签”的下拉列表,就可以看到一些系统设定好的标签的名称,选择其中一个进行标签的设置
标签起名,图层分组, 这个可以用于角色隐身效果
图层与标签不同,除了名字,图层还可以用序号索引来表示。由图3-26所示可知,图层最多只能有32个,所以不要添加过多的自定义图层。
我们可以把预制件理解为一个模具,当创建好一个预制件并设置好预制件的组件参数后,只需要把预制件实例化,就可以产生成游戏物体。如在场景中创建一个子弹物体,当设置好子弹的组件和参数后,将其制作为一个预制件;在之后的操作中,若需要子弹,只需要将该预制件实例化即可,而无须再关心子弹需要添加什么组件和参数。总而言之,预制件好比一个便捷的复制系统,可以帮助我们快速复制出多个相同的游戏物体,同时还可以 达到在一个游戏物体上修改后,在所有复制体上同步进行修改的目的。
1.生成预制件
预制件的创建非常简单,只需要将“层级”面板中的游戏物体拖曳到“项目”面板中,即可看到该游戏物体已经变成一个预制件并保存在“项目”面板的列表中了,如图所示。
预制件的图标为蓝色,当游戏物体成为预制件后,游戏物体也会自动变成该预制件的一个实例。
2.预制件的实例化
将预制件进行实例化的过程和生成预制件的过程十分类似,只需要将“项目”面板中的预制件拖曳到“层级”面板中即可,并可拖曳多次,生成多个实例物体。并且有的组件生成物体的,预制件是有一个加号,右侧三点里面有一个选择添加到
将预制件实例化后的物进行复制,复制体依然作为预制件的实例存在。删除预制件后,与其关联的实例物体均会由蓝色变为红色,此时把红色物体拖曳到“项目”面板中重新生成预制件即可修复关联。
Vector3结构体
由x、y、z这3个数值组成,表示了一向量;除了可以用来表示向量外,还可以用来表示位置、旋转和缩放等信息。所以在使用Vector3的时候一定要先确定这里的Vector3表示的是什么信息,再进行使用。使用以下方法可以创建一个Vector3结构体,即Vector3v:newVector3(1,1,1),Vector3结构体的部分常用属性方法如所示。
属性方法 | 详解 |
normalized | 返回一个规范化向量 |
magnitude | 返回向量的模 |
sqrMagnitude | 返回向量的模的平方 |
zero | 静态属性,返回Vector3(0.0.0) |
one | 静态属性,返回Vector3(1.1,1) |
forword | 静态属性,返回Vector3(0.0.1) |
back | 静态属性,返回Vector3((0,0,-1) |
left | 静态属性,返回Vector3(-1,0.0) |
right | 静态属性,返回Vector3(1.0,0) |
up | 静态属性,返回Vector3(0.1,0) |
down | 静态属性,返回Vector3(0,-1.0) |
Angle | 静态方法,返回两个向量的夹角 |
Distance | 静态方法,返回两个点间的距离 |
Lerp | 静态方法,插值运算 |
Dot | 静态方法,两个向量点乘 |
Cross | 静态方法,两个向量叉乘 |
using UnityEngine;
public class VectorTest : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
//向量,坐标,旋转,缩放
Vector3 v = new Vector3(1, 1, 0.5f);
v = Vector3.zero;
v = Vector3.right;
Vector3 v2 = Vector3.forward;
//计算两个向量夹角
Debug.Log(Vector3.Angle(v, v2));
//计算两点之间的距离
Debug.Log(Vector3.Distance(v, v2));
//点乘
Debug.Log(Vector3.Dot(v, v2));
//叉乘
Debug.Log(Vector3.Cross(v, v2));
//插值
Debug.Log(Vector3.Lerp(Vector3.zero, Vector3.one, 0.8f));
//向量的模
Debug.Log(v.magnitude);
//规范化向量
Debug.Log(v.normalized);
}
// Update is called once per frame
void Update()
{
}
}
Quaternion结构体
代表一个四元数,包含一个标量和一个三维向量,用于描述物体的旋转。四元数是一个四维空间的高阶复数,效率高于欧拉角,并且四元数不会造成万向节锁现象,所以游戏物体的旋转在Unity脚本中默认用四元数表示。但是因为四元数看起来并不直观,所以常常将欧拉角和四元数进行相互转换以便使用。Quaternion结构体的部分常用属性方法如表所示。
属性方法详解
identity | 单位旋转,相当于无旋转 |
eulerAngles | 转换为欧拉角 |
Quatermion | 使用x、y、z、w分量构造四元数 |
Angle | 返回两个旋转之间的角度 |
Euler | 通过欧拉角创建一个四元数 |
LookRotation | 返回forward向量与参数向量旋转相同的四元数,可以理解为看向某向量 |
w不是距离 四元数是一个抽象的概念 算是四维向量 w是和xyz一样的轴 是表示旋转的 不用管怎么实现的
using UnityEngine;
public class VectorTest : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
//Start is called before the first frame update
//旋转:欧拉角,四元数
Vector3 rotate = new Vector3(0, 30, 0);
Quaternion quaternion = Quaternion.identity;
//欧拉角转为四元数
quaternion = Quaternion.Euler(rotate);
//四元数转为欧拉角
rotate = quaternion.eulerAngles; 这里的eulerAngles应该是初始化后的属性吧;
//看向一个物体
quaternion = Quaternion.LookRotation(new Vector3(0, 0, 0));
}
// Update is called once per frame
void Update()
{
}
}
还能表示颜色呢Vector3(255,0,0);表示红色,说实话纯小白看这种视频很晕的,基础打牢再来吧
Debug常用属性方法
编程时调试是不可缺少的,Unity中用于调试的方法均在Debug类中。Debug类的部分常用方法如表所示。
方法详解
Log | 在“控制台”面板中输出一条消息 |
LogWaming | 在“控制台”面板中输出一条警告消息 |
LogError | 在“控制台”面板中输出一在指定的起始点和终点之间 |
DrawLine | 绘制一条直线条错误消息 |
DrawRay | 在指定的起始点向一个向量绘制一条直线 |
放在update里是因为每帧都要绘制,否则绘制一次直接没了
using UnityEngine;
public class VectorTest : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
Debug.Log("test");
Debug.LogWarning("test2");
Debug.LogError("test3");
}
void Update()
{
//绘制一条线起点,终点
Debug.DrawLine(new Vector3(1, 0, 0), new Vector3(1, 1, 0), Color.blue);
//绘制一条射线起点,射线
Debug.DrawRay(new Vector3(1, 0, 0), new Vector3(1, 1, 0), Color.red);
}
}
如下 , 这样可以通过一个脚本控制另外一个脚本了
void Start(){
//拿到当前脚本所挂载的游戏物体
//GameObject go=this.gameObject;
//名称
Debug.Log(gameObject.name);
//tag
Debug.Log(gameObject.tag);
//layer
Debug.Log(gameObject.layer);
//立方体的名称
Debug.Log(Cube.name);
}
在游戏场景中,每一个游戏物体都对应了一个GameObject类的对象,所以一个GameObject对象可以是一棵树、一个敌人、一颗子弹或一个灯光等。GameObject类的部分常用属性方法如表所示。
属性方法详解
activelnHierarchy | 游戏物体在场景中真正的激活状态 |
activeSelf | 游戏物体在场景中设置的激活状态 |
tag | 游戏物体的标签 |
layer | 游戏物体的图层 |
transform | 游戏物体的Transform组件 |
name | 游戏物体的名称,为继承属性 |
AddComponent | 添加组件 |
GetComponent | 得到某个组件 |
GetComponents | 得到多个组件 |
GetComponentlnChildren | 在子物体上得到组件 |
GetComponentlnParent | 在父物体上得到组件 |
SetActive | 设置物体的激活状态 |
Find | 静态方法,在场景中根据名称寻找游戏物体并返回 |
FindWithTag | 静态方法,在场景中根据标签寻找游戏物体并返回 ;多个相同标签则返回的是最后一个创建的物体 |
FindGameObjectsWithTag | 静态方法,在场景中根据标签寻找多个游戏物体并返回 |
Destroy | 静态方法,移除并销毁一个游戏物体,为继承方法 |
Instantiate | 静态方法,复制一个游戏物体,一般用此方法实例化预制件,为继承方法 |
DontDestroyOnLoad | 静态方法,设置此游戏物体在切换场景时不会销毁,为继承方法 |
技术专题:区分activelnHierarchy和activeSelf
activeInHierarchy表示游戏对象在场景中真正的激活状态,activeSelf仅代表游戏物体当前设置的激活状态。如果场景中有一个物体B为物体A的子物体,物体A设置为非激活状态,那么此时这两个物体均为非激活状态。即物体B的activeInHierarchy为false,activeSelf为true。
//当前真正的激活状态
Debug.Log(Cube.activelnHierarchy);
//当前自身激活状态
Debug.Log(Cube.activeSelf);
//获取Transform组件
//Transform trans = this.transform;
Debug.Log(transform.position);
//获取其他组件
BoxCollider bc= GetComponent<BoxCollider>0;
//获取当前物体的子物体身上的某个组件
//GetComponentlnChildren<CapsuleCollider>(bc);
//获取当前物体的父物体身上的某个组件
//GetComponentlnParent<BoxCollider>O;
//添加一个组件
Cube.AddComponent<AudioSource>0;
//通过游戏物体的名称来获取游戏物体
GameObject test = GameObject.Find("Test");
//通过游戏标签来获取游戏物体
test = GameObject.FindWithTag("Enemy");
test.SetActive(false);
Debug.Log(test.name);
//通过预设体来实例化一个游戏物体
GameObject go = Instantiate(Prefab, Vector3.zero, Quaternion.identity);
//销毁
Destroy(go);
}
时间
游戏的流程常常受到时间的影响,可能需要通过时间对游戏进行暂停、加速或减速等操作。Time类为Unity中的时间类,包含了游戏中的时间信息。其部分常用属性方法
deltaTime | 上一帧到这一帧所花的时间 |
fixedDeltaTime | 固定间隔时间 |
time | 游戏开始到现在所花的总时间 |
timeScale | 时间缩放值,默认为1.0 |
不论你每秒多少帧,你这一秒所有的帧所占的时间也是一秒,十秒的帧所占的时间也是十秒
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimeTest : MonoBehaviour
{
float timer = 0;
void Start()
{
//游戏开始到现在所花的时间
Debug.Log(Time.time);
//时间缩放值
Debug.Log(Time.timeScale);
//固定时间间隔
Debug.Log(Time.fixedDeltaTime);
}
//60帧 1/60 120 1/120
void Update()
{
timer += Time.deltaTime;
//上一帧到这一帧所用的游戏时间
//Debug.Log(Time.deltaTime);
//如果大于3秒
if (timer > 3)
{
Debug.Log("大于3秒了");
}
}
}
application
对编写好的程序进行控制和权限的管理,需要使用Unity提供的Application类。Application类的部分常用属性方法如表所示。
dataPath | 游戏数据文件夹路径 |
persistentDataPath | 持久化游戏数据文件夹路径 |
streamingAssetsPathStreamingAssets | 文件夹路径 |
temporaryCachePath | 临时文件夹路径 |
runinBackground | 控制在后台时是否运行 |
OpenURL | 打开一个URL |
Quit | 退出 |
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ApplicationTest : MonoBehaviour
{
//Startiscalledbeforethefirstframeupdate
void Start()
{
//游戏数据文件夹路径(只读,加密压缩)
Debug.Log(Application.dataPath + "/新建文本文档.txt");
//持久化文件夹路径
Debug.Log(Application.persistentDataPath);
//StreamingAssets文件夹路径(只读,配置文件)
Debug.Log(Application.streamingAssetsPath);
//临时文件夹 C盘霸主,这个才是缓存文件夹
Debug.Log(Application.temporaryCachePath);
//控制是否在后台运行
Debug.Log(Application.runInBackground);
//打开url
Application.OpenURL("https://space.bilibili.com/67744423");
//退出游戏
Application.Quit();
}
//Update iscalledonceperframe
}
scene
1个游戏通常由多个场景组成,所以场景的控制和切换在游戏的开发中也是一个很重要的课题。Unity提供了Scene结构体来代表一个场景,同时提供了SceneManager类来进行场景的管理。Scene结构体的部分常用属性方法如表所示
打开build setting就可以把生成的场景生成出来,拖过来生成索引排序,
属性方法详解
buildlndex | 返回场景在BuildSetings中的索引 |
isLoaded | 返回场景是否已经加载 |
name | 场景名称 |
path | 场景的相对路径 |
GetRootGameObject | 返回场景中的所有根游戏物体 |
SceneManager类的部分常用属性方法如下表所示。
sceneCount | 当前已加载场景的数量 |
CreateScene | 创建一个场景 |
GetActiveScene | 得到当前激活场景 |
GetSceneByName | 通过名称返回已加载场景 |
LoadScene | 加载场景 |
LoadSceneAsync | 异步加载场景 |
SetActiveScene | 激活场景 |
UnloadSceneAsync | 移除并且销毁场景 |
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//两个类,场景类,场景管理类
//场景跳转
//SceneManager.LoadScene("MyScene");
//获取当前场景
Scene scene = SceneManager.GetActiveScene();
//场景名称
Debug.Log(scene.name);
//场景是否已经加载
Debug.Log(scene.isLoaded);
//场景路径
Debug.Log(scene.path);
//场景索引
Debug.Log(scene.buildIndex);
GameObject[] gos = scene.GetRootGameObjects();
Debug.Log(gos.Length);
//场景管理类
//创建新场景
Scene newScene =SceneManager.CreateScene(newScene);
//已加载场景个数
Debug.Log(SceneManager.sceneCount);
//卸载场景
SceneManager.UnloadSceneAsync(newScene);
//加载场景
SceneManager.LoadScene("MyScene", LoadSceneMode.Additive);
SceneManager.lo
}
}
异步加载
首先解释同步与异步的区别:同步执行代码是从第一行开始逐行执行,而耗时操作(如数据加载、场景加载或网络读取)会导致程序在此期间暂停,直到这些操作完成;异步则允许将耗时操作分配给其他线程或协程处理,使得主程序可以继续运行而不被阻塞。
协程(Coroutine)是一种特殊的程序组件,它允许在函数或方法的执行过程中暂停,并在稍后恢复执行,而不需要依赖于操作系统的线程调度。与传统的线程相比,协程提供了更轻量级的并发控制机制,因为它们可以在单个线程内实现协作式的多任务处理。
对于Unity开发,可以通过多线程或协程实现异步操作。协程特别适用于处理需要长时间运行的任务,比如场景加载。创建一个协程方法用于异步加载场景,该方法返回类型为IEnumerator
,通过调用StartCoroutine
启动。加载场景的示例代码使用SceneManager.LoadSceneAsync
,它返回一个AsyncOperation
对象,可以用来检查加载进度。加载进度可以通过AsyncOperation.progress
属性获取,并可以在每帧更新中显示。
当然,以下是您提供的代码的文本内容:
csharp复制
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class AsyncTest : MonoBehaviour
{
AsyncOperation operation;
void Start()
{
StartCoroutine(loadScene());
}
// 协程方法用来异步加载场景
IEnumerator loadScene()
{
operation = SceneManager.LoadSceneAsync(1);
// 加载完场景不要自动跳转
operation.allowSceneActivation = false;
yield return operation;
}
float timer = 0;
void Update()
{
// 输出加载进度 0-0.9
Debug.Log(operation.progress);
timer += Time.deltaTime;
// 如果到达5秒,再跳转
if (timer > 5) {
operation.allowSceneActivation=true;
}
}
}
默认情况下,异步加载完成后会自动切换到新场景。如果希望手动控制何时切换,可以通过设置AsyncOperation.allowSceneActivation
属性为false
,并在适当的时候将其设置回true
来触发场景切换。这样可以实现例如“点击任意键继续”的功能。
Transform
Transform组件是每一个游戏物体都会包含的一个组件,管理着该游戏物体的位置、旋转、缩放信息和该物体与其他物体的父子关系。Transform组件在脚本中为Transform类,所以掌握Transform类是掌握Unity的第一步。
position | 在世界坐标系中的位置 | Rotate | 旋转物体 |
rotation | 在世界坐标系中的旋转 | RotateAround | 绕着某点旋转物体 |
eulerAngles | 在世界坐标系中的欧拉角旋转 | Translate | 在某一方向与距离上进行移动 |
localPosition | 相对于父物体的位置 | childCount | 当前Transform的子Transform个数 |
localRotation | 相对于父物体的旋转 | parent | 当前Transform的父Transform |
localScale | 相对于父物体的缩放 | DetachChildren | 解除所有子Transform的关联 |
localEulerAngles | 相对于父物体的欧拉角旋转 | Find | 查找对应子Transfom |
forward | 在世界坐标系中的自身坐标系的蓝色轴向量 | GetChild | 得到对应子Transform |
up | 在世界坐标系中的自身坐标系的绿色轴向量 | IsChildOf | 是否为子Transform |
right | 在世界坐标系中的自身坐标系的红色轴向量 | SetParent | 设置父Transfom |
LookAt | 旋转物体使forward向量指向目标点,也就是看向某个目标点 |
void Start()
{
/*
//获取位置
Debug.Log(transform.position);
Debug.Log(transform.localPosition);
//获取旋转
Debug.Log(transform.rotation);
Debug.Log(transform.localRotation);
Debug.Log(transform.eulerAngles);
Debug.Log(transform.localEulerAngles);
//获取缩放
Debug.Log(transform.localScale);
//向量
Debug.Log(transform.forward);
Debug.Log(transform.right);
Debug.Log(transform.up);
*/
//父子关系
//父子关系
//获取父物体
//transform.parent.gameObject
//子物体个数
//Debug.Log(transform.childCount);
//解除与子物体的父子关系
//transform.DetachChildren();
//获取子物体
Transform trans = transform.Find("Child");
trans = transform.GetChild(0);
//判断一个物体是不是另外一个物体的子物体
bool res = trans.IsChildOf(transform);
Debug.Log(res);
//设置为父物体
trans.SetParent(transform);
}
// Update is called once per frame
void Update()
{
//用时刻看向000点
//transform.LookAt(Vector3.zero);
//旋转
//transform.Rotate(Vector3.up, 1);
//绕某个物体旋转
transform.RotateAround(Vector3.zero, Vector3.up, 5);
//移动
transform.Translate(Vector3.forward*0.1f);
}
技术专题:区分Transform与Vector3中的向量
以up向量为例,Transform中的up向量永远为该物体的绿色轴指向的向量,因此会随着绿色轴的旋转而变化;而Vector3中的up只是一个固定值。
键盘和设备
属性方法详解
anyKey | 任何按键按下都返回true | anyKeyDown | 任何按键按下第一帧都返回true |
inputString | 该帧的键盘输入 | mousePosition | 鼠标指针当前的坐标位置 |
GetKey | 按下按键期间返回true | GetKeyDown | 按下按键瞬间返回一次true |
GetKeyUp | 松开按键瞬间返回一次true | GetMouseButton | 按下鼠标键返回true |
GetMouseButtonDown | 按下鼠标键瞬间返回一次true | GetMouseButtonUp | 松开鼠标键瞬间返回一次true |
在脚本中监听键盘和鼠标输入的方式非常简单,代码如下。
void Update()
{
// 这里的参数也可直接使用字符,如"A"
if (Input.GetKeyDown(KeyCode.A))
{
Debug.Log("按下了A键");
}
if (Input.GetKey(KeyCode.A))
{
Debug.Log("持续按下A键");
}
if (Input.GetKeyUp(KeyCode.A))
{
Debug.Log("松开了A键");
}
// 这里的参数,0为鼠标左键,1为鼠标右键,2为鼠标滚轮
if (Input.GetMouseButtonDown(0))
{
Debug.Log("按下鼠标左键");
}
if (Input.GetMouseButton(0))
{
Debug.Log("持续按下鼠标左键");
}
if (Input.GetMouseButtonUp(0))
{
Debug.Log("松开鼠标左键");
}
}
虚拟轴
那么虚拟轴到底是什么?简单来说,虚拟轴就是一个数值在-1~1内的数轴,这个数轴上重要的数值就是-1、0和1。当使用按键模拟一个完整的虚拟轴时需要用到两个按键,即将按键1设置为负轴按键,按键2设置为正轴按键。在没有按下任何按键的时候,虚拟轴的数值为0;在按下按键1的时候,虚拟轴的数值会从0~-1进行过渡;在按下按键2的时候,虚拟轴的数值会从0~1进行过渡,如图所示。
void Update()
{
/*
//获取水平轴
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Debug.Log(horizontal + " " + vertical);
*/
//虚拟按键
if (Input.GetButtonDown("Jump"))
{
Debug.Log("空格");
}
if (Input.GetButton("Jump"))
{
Debug.Log("空格");
}
if (Input.GetButtonUp("Jump"))
{
Debug.Log("空格");
}
}
touch
void Start()
{
//开启多点触摸
Input.multiTouchEnabled = true;
}
void Update()
{
//判断单点触摸
if (Input.touchCount == 1)
{
//触摸位置
Debug.Log(Input.touches[0].position);
//触摸阶段
switch (Input.touches[0].phase)
{
case TouchPhase.Began:
Debug.Log("开始触摸");
break;
case TouchPhase.Moved:
Debug.Log("触摸中并且在移动");
break;
case TouchPhase.Ended:
Debug.Log("触摸结束");
break;
case TouchPhase.Canceled:
Debug.Log("触摸取消");
break;
case TouchPhase.Stationary:
Debug.Log("触摸但未移动");
break;
//判断多点触摸,如两个点触摸
}
}
//判断多点触摸,如两个点触摸
if (Input.touchCount == 2)
{
//两个点触摸位置
Debug.Log(Input.touches[0].position);
Debug.Log(Input.touches[1].position);
}
}
Comments NOTHING