文章目录
前言
Hot Keys
热键 | 作用 |
---|---|
ctrl+shift+n |
创建新物体 |
fn+f2 |
重命名物体 |
Q |
Pan 平移 |
W |
Move 移动 |
E |
Rotate 旋转 |
R |
Scale 缩放 |
F |
Focus 聚焦 |
Ctrl+p |
Play 运行游戏/退出游戏 |
Ctrl+d |
Duplicate 复制对象 |
delete |
删除 |
实例1
实例1.1:Hello Cube
-
Unity协程(一):彻底了解yield return null 和 yield return new WaitForSeconds
-
Unity 4种光源:平行光、点光源、聚光灯、区域光
-
物体移动的方式:Unity3d 控制物体transform移动的几种方法、Unity中的物体移动-Transform.Translate
-
GetButton 和 GetKey:
GetKey
会使用KeyCode
明确指示按键名称-
没有按下按键时:
GetButtonDown
:False;GetButton
:False;GetButtonUp
:False -
按下按键时,第一帧返回true:
GetButtonDown
:True;GetButton
:True;GetButtonUp
:False -
按住(hold down)按键时:
GetButtonDown
:False;GetButton
:True;GetButtonUp
:False -
松开按键时:
GetButtonDown
:False;GetButton
:False;GetButtonUp
:True总结:
GetKey
的行为与其完全相同,只是代码写法略有不同。
bool down = Input.GetButtonDown("Jump"); bool down = Input.GetKeyDown(KeyCode.Space);
打开
Edit
- >Project Setting
- >Input
,即可查看Positive Button
(激活按钮) -
-
Instantiate
vsInstantiate as RigidBody
:Instantiate返回的是一个Object,Instantiate as是将其转变为一个刚体再返回。
Instantiate returns an Object. That's not hugely useful. So the second example casts the result as a gameobject and then assigns it to the "projectile" variable so you can do other things with it.
-
transform.TransformDerection:从本地坐标转换为世界坐标
-
Object.Destroy:public static void Destroy(Object obj, float t = 0.0F); t是延迟销毁的时间
-
Edit
- >Project Setting
- >Input
- >Vertical
- >Type
:MouseMovement 鼠标移动;Key or Mouse Button 鼠标或按键 -
Time.time:自游戏开始以来的时间(以秒为单位)
发射频率控制:(写在update函数里)
解释:Update
函数(每帧调用一次更新)是每过一个Time.deltaTime
就运行一次的。假设Time.deltaTime
等于0.1秒,频率为每0.5秒/次(即firerate=2)。那么当Time.deltaTime=0.6s时,nextFireTime=0.5。假设Time.deltaTime
等于0.4秒时,此时nextfireTime
等于0s。
当按下开火键时,nextfireTime
等于0.5秒。此时子弹实例化,更新nextFireTime
为1.0秒,这个时候就起到了一个限制频率的作用。下一次开火的时间为前一次开火的时间加上限制子弹发射的时间间隔,如果下一次开火的时间大于当前的时间,则子弹无法实例化。那么下一次运行Update函数时,nextFireTime
为1.0秒,只有当Time.time
为1.6秒时才可以将nextFireTime
更新为1.5s。
总结: 将Time.time
想象成一个在时间轴上不停向前运动的物体A,它的位置不受任何东西影响。我们的频率限制是每t
秒只能开火一次,且A的位置要在B的位置之前才能开火。想象每t
秒有一个障碍物,每当B穿过障碍物且按下开火键的时候,就会触发发射子弹的功能。当A越过障碍物,B的位置改成前一帧A的位置。此时按下开火键,发射子弹,B的位置向前增加t
秒,此时,只有A追上B的时候,才能开火。当A追上B的时候,B的位置再次更改为A前一帧B的位置,循环往复。(注意,帧率是变动的,也就是说Time.deltaTime
是在动态变化的,这里为了方便解释,将其假设为定值。)
if (Time.time - 1f / fireRate > nextFireTime)
nextFireTime = Time.time - Time.deltaTime;
while (Input.GetButtonUp("Fire1") && (nextFireTime < Time.time)) {
nextFireTime += 1 / fireRate;
Rigidbody instance = Instantiate (bullet, this.transform.position, this.transform.rotation) as Rigidbody;
Vector3 forword = this.transform.TransformDirection (Vector3.forward);
instance.AddForce (forword * power);
Debug.Log ("Fire!");
}
Summary
公式 | 作用 |
---|---|
Input.GetAxis("Horizontal") |
获取物体在水平方向 向左还是右运动 |
Input.GetAxis("Vertical") |
获取纵轴在垂直方向 向上还是向下运动 |
Time.deltaTime |
获取提当前帧和上一帧之间的时间间隔 |
transform.Translate(h,0,v) |
设置下一步移动的矢量方向和大小进行移动 |
Input.GetButtonUp ("Fire1") |
按下fire1(左ctrl键) |
Instantiate (object,position,rotation) as Rigidbody |
实例化为刚体 |
this.transform.TransformDirection (Vector3.forward) |
从本地坐标转换为世界坐标 |
instance.AddForce (forward * power) |
给物体施加力 |
Destroy (this.gameObject,3f) |
3s后销毁该物体 |
实例1.2:创建小岛
创建地形对象
-
Create
- >3D Object
- >Terrain
-
在Inspector视图中,找到
Terrian(Script)
组件第一个图标:Raise/Lower Terrian 手动升高或降低地形,从调色板中选择一个画笔,然后单击鼠标并将其拖动到Terrain对象上以提高其高度。在按住Shift键的同时单击并拖动以降低地形高度。
第二个图标:Paint Height 第一个按钮从左侧激活“ *升高/降低高度”*工具。使用此工具绘画时,将鼠标移到地形上时高度会增加。如果将鼠标放在一个位置,高度会累积,类似于图像编辑器中喷枪工具的效果。如果按住Shift键,则高度会降低。不同的画笔可用于创建各种效果。例如,您可以通过使用软边刷子增加高度来创建起伏的丘陵,然后通过用硬边刷子降低高度来切割陡峭的悬崖和山谷。
第三个图标:Smooth Height 平滑地增加高度
第四个图标:Paint Texture 添加纹理
第五个图标:Place Trees 种树
第六个图标: Paint Details:Hold down shift to erase,hold down ctrl to erase the selected detail type
第七个图标:Terrian Settings
-
第二个图标,Height 设置为30。并按Flatten。
左边第二个工具是绘制高度 (paint height)工具,该工具使您能够指定目标高度,并将地形的任意部分移向该高度。一旦达到目标高度,地形便会停止移动并保持在此高度。
unity 还提供可使地形变平的简便方法。选择地形 (terrain) -> 变平… (flatten…)。该功能使您能够将地形变平至您在向导中指定的高度。地形高度调节、地形引擎
-
第一个图标,Brush Size=100,Opacity=75,刷出形状
-
第二个图标,Brush Size=75,Opac-ity=50,Height=100;鼠标点击数次,形成火山
-
第二个图标,Brush Size=30,Height=20;鼠标点击数次,形成火山口
-
选择
Import Package
- >Terrain Assets
-
选中Terrain对象,第四个图标
Paint Texture
,找到Textures
,点击Edit Textures
,在弹出的菜单中选择Add Texture
,将弹出一个对话框,点击Texture
- >Select
,选中Grass(Hill)
,点击Add
退出对话框,此时Terrain对象应该被Grass(Hill)纹理覆盖了 -
添加三种纹理,Grass & Rock、GoodDirt、Cliff(LayeredRock),并修改最后一种纹理的Tile Size的属性:x=70,y=70
-
点击GoodDirt,修改属性:BrushSize=60,Opacity=50,Target Strength=1,刷地形海岸部分
-
点击Grass&Rock,Brush Size=25,Opacity=30,Target Strength=0.5,对小岛的丘陵地带和火山的上半部分进行粉刷
-
点击 Cliff,Brush Size=20,Opacity=100,Target Strength=1,对火山外围的上半部分和整个内围进行粉刷
-
第五个图标,然后在下方点击EditTrees,选择Add Tree,在弹出的对话框中,点击Tree最右边的圆形选择按钮,这将打开对话框SelectGameObject,然后选择Palm,接着点击Add
-
修改属性:Brush Size=15,Tree Density=40,ColorVariation=0.4,Tree Height=50,Variation=30,Tree Width=50,Variation=30;
-
在小岛上的沿岸地带和内部随机放置一些Palm,要移除放置了的Palm,可以按住Shift键,再点击Palm
-
第六个图标,然后在下方点击EditDetails,选择Add Grass Texture,在弹出的对话框中,点击Detail Texture右端的圆形选择按钮,在弹出的Select Texture2D对话框中,选择Grass纹理后,确保Billboard被选中,点击Healthy Color最右端的笔形图标,然后在小岛内部的草地上点击一下,使两者颜色一致,最后点击Add;
-
第六个图标,修改属性:Brush Size=100,Opacity=0.1,TargetStrength=0.3,点击,粉刷
-
Import Package-Character Controller,在弹出的对话框中,点击Import,将导入角色控制包
-
Assets-Standard Assets-CharacterControllers,然后将右侧窗口中的First Person Controller预制体拖入场景中;
-
…
Summary
-
添加阳光: 在Project视图中,鼠标左键点击左侧的Assets,然后在右侧的Assets中,鼠标右键点击,然后选择Import Package-Light Flares,点击Import,导入镜头光晕包; 选中Directional light对象,在Inspector视图中,找到
Light-Flare
属性,单击最右端的圆形选择按钮,在弹出的对话框中,选择Sun,这将添加一个太阳光晕 -
添加天空盒: 鼠标左键点击左侧的Assets,然后在右侧的
Assets
中,鼠标右键点击,然后选择Import Package-Skyboxes
,点击Import,将导入天空包 -
添加水: 在Project视图中,鼠标左键点击左侧的Assets,然后在右侧的Assets中,鼠标右键点击,然后选择
Import Package-Water(Pro Only)
,点击Import,导入水资源包;将Daylight Water拖入场景中,并Reset它的Transform组件,并修改属性Transform-Scale:X=1600,Z=1600,然后在Scene视图中,点击该对象上的绿色坐标Y轴,上下提升,使得海水刚好淹没小岛的海岸线为止;
实例1.3:岗哨
-
在Inspector视图中,修改属性Meshes-Scale Factor=1.5,勾选选中属性Meshes-Generate Colliders,勾选选中属性Meshes-Generate Lightmap UVs,点击Apply;
-
确保 outPost-door 对象被选中,在 Inspector 视图中,点击 Add Component - Physics-Rigidbody,添加刚体,勾选去掉 Use Gravity 属性,勾选选中 Is Kinematic 属性,这样 Unity 引擎就不会对门使用物理效果了;
-
确保 outPost-door 对象被选中,在 Inspector 视图中,点击 Add Component - Audio-Audio Source,勾选去掉它的属性 Play On Awake;在 Hierarchy 视图中,点击 outPost 对象,在 Inspector 视图中,找到属性 Animation-Play Automatically,勾选去掉它,这样就不会自动播放动画了;
-
通过子对象获取父对象:
sonObject.transform.parent
-
OnControllerColliderHit:OnControllerColliderHit is called when the controller hits a collider while performing a Move.
This can be used to push objects when they collide with the character.
-
OnTrrigerEnter与OnCollisionEnter、OnControllerColliderHit、Rigidbody、CharacterController
使用“碰撞器”来实现角色与门的互动
- 三秒后,自动关门
void Update () {
if (doorIsOpen) {
doorTimer+=Time.deltaTime;
if(doorTimer>doorOpenTime){
Door(currentDoor);
doorTimer=0f;
}
}
}
- 碰撞到门,🚪就自动打开
void OnControllerColliderHit(ControllerColliderHit hit){
if (hit.gameObject.tag == "playerDoor" && doorIsOpen == false) {
currentDoor=hit.gameObject;
OpenDoor(hit.gameObject);
}
}
void OpenDoor(GameObject door){
doorIsOpen = true;
door.audio.PlayOneShot (doorOpenSound);
door.transform.parent.animation.Play ("dooropen");
}
void ShutDownDoor(GameObject door){
doorIsOpen = false;
door.audio.PlayOneShot (doorShutSound);
door.transform.parent.animation.Play("doorshut");
}
使用给物体加tag的方法来判断被撞物体是不是门。How:在 Inspector 视图中,点击 Tag 的下拉菜单,然后选择 Add Tag,在 Element 0 处输入 playerDoor
优化:
void Door(AudioClip aClip,bool OpenCheck,string animName,GameObject door){
doorIsOpen = OpenCheck;
door.audio.PlayOneShot (aClip);
door.transform.parent.animation.Play (animName);
}
// DoorManager.cs
using UnityEngine;
using System.Collections;
public class DoorManager : MonoBehaviour {
bool doorIsOpen=false;
float doorTime=0f;
float doorOpentime=3.0f;
AudioClip DoorShutSound;
AudioClip DoorOpenSound;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (doorIsOpen) {
doorTime+=Time.deltaTime;
if(doorTime>doorOpentime){
Door(DoorShutSound,false,"doorshut");
doorTime=0f;
}
}
}
void DoorCheck(){
if (!doorIsOpen) {
Door(DoorOpenSound,true,"dooropen");
}
}
void Door(AudioClip aClip,bool OpenCheck,string animName){
audio.PlayOneShot (aClip);
doorIsOpen = OpenCheck;
transform.parent.animation.Play (animName);
}
}
使用“光线投射”来实现角色与门的交互
- RayCastHit:射线是在三维世界中从一个点沿一个方向发射的一条无限长的线。在射线的轨迹上,一旦与添加了碰撞器的模型发生碰撞,将停止发射。我们可以利用射线实现子弹击中目标的检测,鼠标点击拾取物体等功能。
- SendMessage:脚本调用该方法进行发送消息,可以使自身所有脚本或者父物体 子物体身上的所有脚本进行接收,其接收的类型为Object
// PlayerCollisions.cs
void Update () {
RaycastHit hit;
if (Physics.Raycast (transform.position, transform.forward, out hit, 3)) {
if(hit.collider.gameObject.tag=="playerDoor"){
currentDoor=hit.collider.gameObject;
currentDoor.SendMessage("DoorCheck");
}
}
}
如果被撞到的物体是门,那么门发送消息,调用DoorCheck函数,如果此时门是开着的,看有没有超过3s,把门关上,没超过三秒,门继续开着。反之,如果门是关着的,调用Door函数,把门打开。
使用“触发器”来实现角色与门的互动
// TriggerZone.cs
void OnTriggerEnter(Collider col) {
if (col.gameObject.tag == "Player") {
transform.FindChild("door").SendMessage("DoorCheck");
}
}
速度比较快时: 触发碰撞:上一帧未发生碰撞,下一帧已经穿过去了; 光线碰撞:预先判断,光线碰撞是有方向的。
-
Unity3d vector3.forward和transform.forward的区别: transform.position是世界坐标。 transform.forward是物体当前面向对于世界坐标的值。 所以transform.position += transform.forward 会得到物体向着它的前方移动的结果。 但是transform.translate是相对移动。
-
sendMessage() 广播;不要求所有对象接收
-
调用OnTriggerEnter时,
is Trigger
属性需要被勾选 -
分割动画: 在 Project 视图中,展开目录 Book Assets-Models,点击模型 outPost;. 在 Inspector 视图中,修改属性 Meshes-Scale Factor=1.5,勾选选中属性 MeshesGenerate Colliders,勾选选中属性 Meshes-Generate Lightmap UVs(生成光线UV贴图),点击 Apply;在 Inspector 视图中,点击 Animations 标签,在下方找到 Clips-Default Take, 将名称 Default Take 修改为 idle,修改属性 Start=1,End=2;同样,在该标签页下,点击“+”按钮,将添加一个新的动画 Default Take,将 它的名称修改为 dooropen,修改属性 Start=1,End=15;同样的步骤添加动画 doorshut,Start=16,End=29;点击Apply
Summary
公式 | 作用 |
---|---|
Object.animation.Play (animName) |
播放动画 |
Physics-Rigidbody |
添加刚体 |
Audio-AudioSource |
添加声音 |
OnControllerColliderHit |
碰撞器 |
OnTriggerEnter |
触发器 |
if(Physics.Raycast (transform.position, transform.forward, out hit, distance)) |
光线投射 |
Object.SendMessage(messageName); |
发送消息 |
Object.audio.PlayOneShot (audioClip); |
播放声音 |
-
碰撞器(Collider)和触发器(Trigger)区别:
-
碰撞器调用OnCollisionEnter/Stay/Exit函数,触发器调用OnTriggerEnter/Stay/Exit函数.传送门
-
Rigidbody(刚体)
,作用是让游戏物体具有物体的属性,如重力,摩擦力、弹力等. 触发检测(没有物理效果),只需勾选Is Trigger
。Collider和Rigidbody结合:传送门
-
静态碰撞器:只有Collider没有Rigidbody,只能通过运动学(Transform)让它移动、旋转、缩放,与任何物体发生碰撞,只会产生穿透效果。
-
刚体碰撞器:同时添加的Collider和Rigidbody的游戏对象,受物理引擎的影响,同时也可以使用运动学去运动
-
运动学刚体碰撞器:指添加类刚体和碰撞器的游戏对象,但刚体的Iskinematic 属性勾选为 True. 这时,它不受力作用,只能使用运动学,就是控制Transform组件的position、rotation、scale属性,让它动起来。但它能进行碰撞检测和触发检测
-
还有一个特殊的:角色控制器,用于制作游戏的中第一/三人称角色,同样不受物理影响,比如没有重力,没有阻力,摩擦力,能快速运动立马停下来,沿着墙壁滑动、上下楼梯、斜坡等,但是不同运动刚体的是,它可以通过代码模拟重力,碰撞(需通过OnControllerColliderHit()脚本函数来实现)
-
-
触发检测和碰撞检测:传送门
-
发生碰撞的条件:主动方必须有Rigidbody,发生碰撞的两个游戏对象必须有Collider,被动方对于RigidBody可又不可无,参数是表示被动方 (主刚体,两个碰撞器)
-
发生触发的条件:发生碰撞的物体两者其中之一有Rigidbody即可,发生碰撞的两个游戏对象必须有Collider,其中一方勾选IsTrigger即可,参数是表示被动方 (任意一个刚体,两个碰撞器,一个
Is Trigger
)
-
-
实例1.4:收集、物品栏和 HUD
子弹PowerCell的制作及其收集
-
PowerCell:
PowerCell.cs
拖给PowerCell
。Scale Factor=1.6,勾选选中 Generate Colliders(勾选Generate Colliders选项,然后即会自动在预支体或游戏物体中生成一个Mesh Collider 组件,并添加了Mesh,若没有生成组件,可自行导入)和 Generate Lightmap UVs(生成光线uv图),点击 Apply;添加Tag:cell;点击 Add Component-Physics-Capsule Collider,勾选选中 Is Trigger 属性。点击 Direction,选中 X-Axis,修改 Radius=0.3,Height=1.15;在确保 powerCell 对象被选中的情况下,按 Ctrl+d 复制它 3 次,这样可以得 到总共 4 个对象,然后通过拖动它们的坐标轴,将它们分散放在不同的地方。// PowerCell.cs public float rotationSpeed=100f; void Update () { transform.Rotate (new Vector3(0,rotationSpeed*Time.deltaTime,0)); // 自旋 } void OnTriggerEnter(Collider col){ // Collider:是被撞的物体的Collider if (col.gameObject.tag == "Player") { col.gameObject.SendMessage("CellPickUp"); Destroy(gameObject); } }
- transform.rotate
原型:public void Rotate(float xAngle,float yAngle,float zAngle, Space relativeTo = Space.Self);
relativeTo: 确定是将GameObject局部旋转到GameObject还是相对于Scene世界空间旋转。
xAngle: 绕X轴旋转GameObject的度数。
辨析:Rotate()方法需要一个vector3三维向量,rotation是用四元素旋转(Quaternion)。传送门
它是相对运动。
- transform.rotate
-
Inventory.cs
拖给First Person Controller
。展开 Assets-Book Assets-Sound,将cell_collected 文件拖给属性 Inventory-Collect Sound;
Hud的制作
-
Hud:展开 Assets-Book Assets-Textures,点击文件hud_nocharge,修改 Texture Type 为
Editor GUI and Legacy GUI
,点击 Apply;点击 Create-Create Empty,重命名为PowerGUI
,在 Inspector 视图中,Reset 它的 Transform 组件。点击 Add Component-RenderingGUITexture
,将文件hud_nocharge 拖给属性 GUITexture-Texture,修改属 性 Transform-Scale:X=0.15,Y=0.15
,修改属性 Transform-Position:X=0.15, Y=0.1
。结果:GUI 显示于左下角;展开 Assets-Book Assets-Textures, 找到hud_charge1、hud_charge2、hud_charge3、hud_charge4,并选中它们,修改 Texture Type 为 Editor GUI and Legacy GUI, 最后点击 Apply;选中 First Person Controller,找到属性 Inventory-Hud Charge,修改Size=5
。使用鼠标拖放的方式对 Element 0-4 赋值
,它们分别是: hud_nocharge、hud_charge1、hud_charge2、hud_charge3、hud_charge4;找到属性 Inventory-Charge Hud GUI,将对象 PowerGUI 拖给它
;选中 PowerGUI,找到组件 GUITexture,勾选去掉它
,这 样,在 Game 视图的左下角,你将看不到 PowerGUI 图标;(结果:运行游戏:控制角色直接靠近门,不要捡起能量块,此时,左下角的 HUD 将 被激活,并且播放门锁着的声音。退出游戏,再次运行游戏:控制角色捡起能 量块,此时,左下角的 HUD 也将被激活。)// Inventory.cs using UnityEngine; using System.Collections; public class Inventory : MonoBehaviour { public static int charge=0; public AudioClip collectSound; public Texture2D[] hudCharge; public GUITexture chargeHudGUI; public Texture2D[]meterCharge; public Renderer meter; void Start () { charge = 0; } void Update () { } void CellPickUp(){ HUDon(); AudioSource.PlayClipAtPoint (collectSound,transform.position); charge++; chargeHudGUI.texture = hudCharge [charge]; meter.material.mainTexture=meterCharge[charge]; } void HUDon(){ if (!chargeHudGU