Unity 学习笔记 版本 4.6.8

文章目录

  • 前言
    • Hot Keys
  • 实例1
    • 实例1.1:Hello Cube
      • 发射频率控制:(写在update函数里)
      • Summary
    • 实例1.2:创建小岛
      • 创建地形对象
      • Summary
    • 实例1.3:岗哨
      • 使用“碰撞器”来实现角色与门的互动
      • 使用“光线投射”来实现角色与门的交互
      • 使用“触发器”来实现角色与门的互动
      • Summary
    • 实例1.4:收集、物品栏和 HUD
      • 子弹PowerCell的制作及其收集
      • Hud的制作
      • 红灯转绿灯
      • 字体
      • Summary
    • 实例1.5:实例化和刚体
      • 实例化Coconut
      • 装着标靶的屋子CoconutShy
      • 标靶
      • CrossHair瞄准
      • Summary
    • 实例1.6:粒子系统
      • Campfire
      • FireSystem
      • Smoke System
      • Matches.cs
      • Summary
    • 实例1.7:制作菜单
      • Summary
    • 实例1.8:动画基础
      • Summary
  • 实例2
    • 实例2.1
    • 实例2.2
      • Summary

前言

安装4.6.8教程

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 游戏对象消失 enable,destroy与active的区别

  • Unity中Update和FixedUpdate的区别

  • Unity协程(一):彻底了解yield return null 和 yield return new WaitForSeconds

  • Update和LateUpdate的区别

  • Unity 4种光源:平行光、点光源、聚光灯、区域光

  • 物体移动的方式:Unity3d 控制物体transform移动的几种方法、Unity中的物体移动-Transform.Translate

  • 碰撞体、刚体:传送门1、传送门2;预制体

  • GetButton 和 GetKey:GetKey会使用KeyCode明确指示按键名称

    • 没有按下按键时:GetButtonDownFalseGetButtonFalseGetButtonUpFalse

    • 按下按键时,第一帧返回true:GetButtonDownTrueGetButtonTrueGetButtonUpFalse

    • 按住(hold down)按键时:GetButtonDownFalseGetButtonTrueGetButtonUpFalse

    • 松开按键时:GetButtonDownFalseGetButtonFalseGetButtonUpTrue

      总结: GetKey的行为与其完全相同,只是代码写法略有不同。

    bool down = Input.GetButtonDown("Jump");	
    bool down = Input.GetKeyDown(KeyCode.Space);
    

    打开Edit - > Project Setting - > Input,即可查看Positive Button(激活按钮)

  • Instantiate vs Instantiate 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

  • OnTriggerEnter 和 OnCollisionEnter

使用“碰撞器”来实现角色与门的互动

  • 三秒后,自动关门
	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的制作及其收集

  • PowerCellPowerCell.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)。传送门
      它是相对运动。
  • 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 (!chargeHudGUI.enabled) {
    			chargeHudGUI.enabled=true;
    		}
    	}
    }
    
    • AudioSource & AudioClip:通过类调用,audioSource:通过对象下的组件调用。AudioClip是一个声音片段,如:按钮点击,相当于是一首音乐;AudioSource是一个源,包含AudioClip,还有其它一些属性,如:是否静音等,相当于是一个音乐播放器。传送门
  • PlayClipAtPoint

    Description:Plays an AudioClip at a given position in world space.This function creates an audio source but automatically disposes of it once the clip has finished playing.
    原型:public static void PlayClipAtPoint(AudioClip clip, Vector3 position, float volume = 1.0F);

    • Legacy GUI Scripting Guide:(已被弃用)

    The legacy GUI system has been replaced with the new UI System. The legacy GUI is still functional but is not recommended to use in your game or application. The legacy GUI system is still used in Unity’s own interface and can be used to create custom editor GUI.

    The legacy GUI allows you to create a wide variety of functional GUIs using code. Rather than creating a GUI object, manually positioning it, and then writing a script that handles its functionality, you can do everything at once with just a few lines of code. The code produces GUI controls that are instantiated, positioned and handled with a single function call. This section explains how to use GUI both in your game and in extensions to the Unity editor.

  • GUITexture:

  • generator:展开 Assets-Book Assets-Models,点击模型 generator,在 Inspector 视图中,修改属性 Scale Factor=1.6,勾选选中 Generate Colliders 和 Generate Lightmap UVs,点击 Apply;点击对象 generator-generator,注意,现在选中的是 generator 对象的子对象 generator。在 Inspector 视图中,点击 Add ComponentPhysics-Box Collider,并删除原有组件 Mesh Collider;选中 First Person Controller 对象,找到属性 Inventory-Meter Charge,展开它,修改 Size=5,然后在 Project 视 图中,展开 Assets-Book Assets-Textures,然后将meter_charge0-meter_charge4 共 5 个纹理文件分别拖入 Element 0-Element 4 对应的元素中;继续将子对象 generator-chargeMeter 拖入 Meter 属性中;

红灯转绿灯

  • 点击 Create-Light-Point Light,重命名为 Door Light,在 Inspector 视图中,修改属性 Light-Range=1.2,点击 Color 右边的颜色框,并 选取红色,修改 Intensity=3。然后,移动该对象到 outPost 的门上方;选中 outPost 对象,找到属性 Trigger Zone-Door Light, 将对象 Door Light 拖给它; 运行程序,可以看到,当角色收集满 4 个能量块并进入 outPost 之后,PowerGUI 消失,outPost 的门灯也变成了绿色;Inspector 视图中,Reset 它的 Transform 组件,修改属性 Transform-Position: X=0.5,Y=0.5。点击 Add Component-Rendering-GUIText,将目录 Assets-Fonts 中的字体文件 Sugo 拖入属性 GUIText-Font 中。修改属性 GUIText-Anchor: Middle center,GUIText-Alignment:center,GUIText-Font Size=25;

    // TriggerZone.cs
    using UnityEngine;
    using System.Collections;
    
    public class TriggerZone : MonoBehaviour {
    	
    	public AudioClip lockedSound;
    	public Light doorLight;
    	public GUIText textHints;
    	// Use this for initialization
    	void Start () {
    		
    	}
    	
    	// Update is called once per frame
    	void Update () {
    		
    	}
    	
    	void OnTriggerEnter(Collider col){
    		if (col.gameObject.tag == "Player") {
    			if (Inventory.charge == 4) {
    				transform.FindChild ("door").SendMessage ("DoorCheck");
    				if (GameObject.Find ("PowerGUI")) {
    					Destroy (GameObject.Find ("PowerGUI"));
    					doorLight.color = Color.green;
    				}
    			}else if (Inventory.charge > 0 && Inventory.charge < 4) {
    				textHints.SendMessage ("ShowHint", "This door won't budge.." + "guess it needs fully charging - " + "maybe more power cells will help...");
    				transform.FindChild ("door").audio.PlayOneShot (lockedSound);
    			}else {
    				transform.FindChild ("door").audio.PlayOneShot (lockedSound);
    				col.gameObject.SendMessage ("HUDon");
    				textHints.SendMessage ("ShowHint","This door seems locked.. " + "maybe that generator needs power...");
    			}
    			
    		}
    	}
    }
    

字体

  • 在 Assets 目录下新建一个目录 Fonts,将文件 Sugo.otf 拖入;点击 Create-Create Empty,重命名为 TextHintGUI,在Inspector 视图中,Reset 它的 Transform 组件,修改属性 Transform-Position: X=0.5,Y=0.5。点击 Add Component-Rendering-GUIText,将目录 Assets-Fonts 中的字体文件 Sugo 拖入属性 GUIText-Font 中。修改属性 GUIText-Anchor: Middle centerGUIText-Alignment:centerGUIText-Font Size=25;选中 outPost 对象,找到属性 Trigger Zone-Text Hints, 将对象 TextHintGUI 拖给它;回到 Unity,将脚本 TextHints 拖给对象 TextHintGUI;运行游戏,当角色在没有获得充足的能量块而欲进入 outPost 时,屏幕将会显 示一串字符串进行提示

    // TextHints.cs
    using UnityEngine;
    using System.Collections;
    
    public class Texthints : MonoBehaviour {
    	
    	float timer=0.0f;
    	
    	void Update () {
    		if (guiText.enabled) {
    			timer+=Time.deltaTime;
    			if(timer>=4){
    				guiText.enabled=false;
    				timer=0.0f;
    			}
    		}
    	}
    	
    	void ShowHint(string message){
    		guiText.text = message;
    		if (!guiText.enabled) {
    			guiText.enabled=true;
    		}
    	}
    }
    

Summary

公式 作用
Add Component - Capsule Collider 添加胶囊体碰撞器(Capsule Collider)
Add Component - RenderingGUITexture 添加 GUITexture
Add Component-Rendering - GUIText 添加 GUIText
位置:GUIText-Anchor: Middle center 修改 GUIText 字体
对齐:GUIText-Alignment: center
GUIText-Font Size=25 修改 GUIText 字体大小
col.gameObject.SendMessage ("CellPickup") 通过 SendMessage 函数直接调用另一个对象的方法
AudioSource.PlayClipAtPoint(collectSound, transform.position) 对象不显式添加 AudioSource 组件而直接播放音频
chargeHudGUI.texture = hudCharge [charge] 修改 GUITexture 的纹理
guiText.text = message 修改 GUIText 的文本
GameObject.Find ("PowerGUI") 找到场景中对象的方法
transform.FindChild("door").SendMessage("DoorCheck") 找到子对象的方法
chargeHudGUI.enabled = true/false 关闭和打开 GUITexture 的方法
guiText.enabled = true/false 关闭和打开 GUIText 的方法
meter.material.mainTexture = meterCharge [charge] 修改对象上纹理的方法
doorLight.color = Color.green 修改灯光颜色的方法
  • GUIText:

    • Anchor:The point at which the Text shares the position of the Transform.
    • Alignment:How multiple lines are aligned within the GUIText.
  • Texture2D修改纹理:GameObject.materal.maintexture

  • GUITexture修改纹理:GameObject.texture

实例1.5:实例化和刚体

实例化Coconut

  • Coconut:点击 Create-3D Object-Sphere,重命名为 Coconut,找到 组件 Transform,修改属性 Scale:X=0.5,Y=0.6,Z=0.5;在 Project 视图的 Assets 目录中,创建一个目录 Materials,回车进入该目录,鼠标右键点击 Create-Material,重命名为 Coconut Skin;展开 Assets-Book Assets-Textures,将纹理 coconutTexture 拖入 Inspector 视图的 Base(RGB) 的最右侧空白栏中,这样 就生成了一个椰子材质纹理;点击 Add Component-PhysicsRigidbody

  • 将TidyObject.cs拖给Coconut对象,功能:Coconut对象3s后消失。

    // TidyObject.cs
    	public float removeTime=3.0f;
    	void Update () {
    		Destroy (gameObject, removeTime);
    	}
    
  • 生成预制体:在 Project 视图的 Assets 目录中,新建一个目录 Prefabs,将对象 Coconut 拖 入该目录中,这样就生成了一个 Coconut 预制体,后面将利用脚本动态生成该对象;在 Hierarchy 视图中,删除对象 Coconut;

  • 发射:创建空物体Launcher,并挂载到对象 First Person Controller-Main Camera 上,并 Reset 对象 Launcher 的 Transform 组件;修改Launcher的属性 Position: X=1,Z=1,修改属性 Rotation:Y=-8

    // CoconutThrower.cs
    public AudioClip throwSound;
    public Rigidbody coconutPrefab;
    public float throwSpeed = 30.0f;
    public static bool canThrow = false;
        
    if (Input.GetButtonDown ("Fire1") && canThrow) {
    	audio.PlayOneShot(throwSound);
    	Rigidbody newCoconut = Instantiate(coconutPrefab,
    	transform.position, transform.rotation) as Rigidbody;
    	newCoconut.name = "coconut";
    	newCoconut.velocity = transform.forward * throwSpeed;
    	Physics.IgnoreCollision(transform.root.collider,
    	newCoconut.collider, true);
    }
    

    如果按下开火键,并且可以扔(只有站到触发区域才可以扔),那么播放扔的声音,在Launcher的位置上实例化coconuPrefab为刚体,并给该对象命名为coconut,给该对象施加力的作用。如果不小心Coconut砸到Launcher的父对象,忽略这个碰撞。因为如果不忽略的话,很有可能椰子被弹得乱飞。

  • 拖拽声音和预制体:选中对象 First Person Controller-Main Camera-Launcher,找到组件 Coconut Thrower,将 Project 视图中的 Assets-Book Assets-Sounds-coconut_throw 文件 拖入属性 Throw Sound 中,将预制体 Assets-Prefabs-Coconut 拖入属性 Coconut Prefab 中;

  • Rigidbody.velocity:在大多数情况下,你不应该直接修改速度,因为这可能会导致不真实的行为–使用AddForce代替。 不要在每一个物理步骤中都设置物体的速度,这将导致不真实的物理模拟。一个典型的用法是,当你在第一人称射击游戏中跳跃时,你会改变速度,因为你想立即改变速度。

  • Physics.IgnoreCollision(transform.root.collider, newCoconut.collider, true)
    原型:public static void IgnoreCollision (Collider collider1, Collider collider2,bool ignore = true);

    collider1 Any collider.
    collider2 Another collider you want to have collider1 to start or stop ignoring collisions with.
    ignore Whether or not the collisions between the two colliders should be ignored or not.
  • transform.root

    返回层次结构中最顶层的transform。它永远不会返回null,如果此Transform没有父对象,它将返回自身。

装着标靶的屋子CoconutShy

  • 创建:展开 Assets-Book Assets-Models,点击 模型 coconutShy; 修改属性 Scale Factor=1,勾选选中 Generate Colliders, 勾选选中 Generate Lightmap UVs,点击 Apply;在 Hierarchy 视图中,展开对象 coconutShy-mat,选中该子对象,在 Inspector 视图中,点击 Add Component-Physics-Box Collider;找到 Box Collider 组件,勾选选中 Is Trigger,点击 Edit Collider 左侧的图标, 然后在 Scene 视图中,鼠标左键点击并按住绿点,上移把盒碰撞器抬高

  • ThrowTrigger附加到mat上:如果站在了垫子上就可以扔(canThrow=true),如果离开了垫子,就不可以扔。

    //  ThrowTrigger.cs
    void OnTriggerEnter(Collider col){
        if (col.gameObject.tag == "Player") {
            CoconutThrower.canThrow=true;
        }
    }
    
    void OnTriggerExit(Collider col){
        if (col.gameObject.tag == "Player") {
            CoconutThrower.canThrow=false;
        }
    }
    
  • 动画:找到模型 Assets-Book Assets-Models-target,选中它,在
    Inspector 视图中,修改 Scale Factor=1,勾选选中 Generate Colliders,勾选选
    Generate Lightmap UVs,点击 Apply。点击 Animation 标签,使用动画列
    表右边的加号图标添加 3 个动画,分别填入 Name、Start 和 End(点击 Clamp
    Range 修改 Start 和 End)。修改完毕后,点击 Apply;在确保 target 被选中的情况下,Reset 它的 Transform 组件。修改属性 Rotation: Y=180,Position:Z=-2.4,勾选去掉 Play Automatically 。点击展开 coconutShy-target-target_pivot,同时选中它下面的三个子对象 target、target_support_1、target_support_2,然后在 Inspector 视图中,点 击 Add Component-Physics-Rigidbody勾选去掉 Use Gravity勾选选中 Is Kinematic

    Clips Start End
    idle 1.0 2.0
    down 3.0 13.0
    up 14.0 35.0

标靶

  • 将TargetCollision脚本附加给coconutShy - target - target_pivot - target。

    拖入声音:找到组件 Target Collision,展开 Project 视图中的 Assets-Book Assets-Sounds 目录,将文件target_hit 和target_reset 分别拖入 Hit Sound 和 Reset Sound 中;

    复制三个标靶:点击coconutShy-target,按 Ctrl+d 复制该子对象,然后,在 Inspector 视 图中,修改属性 Transform-Position:X=-1.6。再次复制一个,这次在 Inspector 视图中,修改属性 Transform-Position:X=1.6。这样,一共有 3 个 target 子对象;

    结果:控制角色走上 coconutShy 的靶标发射处,发射椰子,击倒靶标,3 秒钟后靶标还原

    // TargetCollision.cs
    public class TargetCollision : MonoBehaviour {
    
    	bool beenHit=false;
    	Animation targetRoot;
    	public AudioClip hitSound;
    	public AudioClip resetSound;
    	public float resetTime=3.0f;
    
    	void Start () {
    		targetRoot = transform.parent.transform.parent.animation;
    	}
        
    	void OnCollisionEnter(Collision col){
    		if (beenHit == false && col.gameObject.name == "coconut") {
    			StartCoroutine("targetHit");
    		}
    	}
    
    	IEnumerator targetHit(){
    		audio.PlayOneShot (hitSound);
    		targetRoot.Play ("down");
    		beenHit = true;
    		CoconutWin.targets++;
    
    		yield return new WaitForSeconds(resetTime);
    
    		audio.PlayOneShot (resetSound);
    		targetRoot.Play ("up");
    		beenHit = false;
    		CoconutWin.targets--;
    	}
    }
    

    初始化:targetRoot动画组件初始化为target的父组件(target_pivot) 的父组件(target)的动画组件。如果target与coconut发送碰撞,并且标靶没被撞击过,那么启动targetHit协程。此时,播放撞击的声音和动画,beenHit为true,被撞击的标靶数增加. 暂停3s,播放重置的声音和弹起的动画,beenHit为false,标靶数减一.

  • StartCoroutine: 启动协程。协程的执行可以在任何时候使用yield语句暂停。使用yield语句时,协程将暂停执行并在下一帧自动恢复

  • 将脚本 CoconutWin 拖给对象 coconutShy;将对象 powerCell 拖入 Project 视图中的 Assets-Prefabs 目录,生成一个 powerCell 预制体;选中对象 coconutShy,找到组件 Coconut Win,将预 制体 powerCell 拖入 Cell Prefab 中。将 Project 视图中的 Assets-Book AssetsSounds-win_cell 拖入 Win Sound 中;

    // CoconutWin.cs
    
    public class CoconutWin : MonoBehaviour {
    
    	public static int targets=0;
    	public static bool haveWon=false;
    	public AudioClip winSound;
    	public GameObject cellPrefab;
    
    	void Update () {
    		if (targets == 3 && haveWon == false) {
    			targets=0;
    			audio.PlayOneShot(winSound);
    			GameObject winCell=transform.Find("powerCell").gameObject;
    			winCell.transform.Translate(-1,0,0);
    			Instantiate(cellPrefab,winCell.transform.position,transform.rotation);
    			Destroy(winCell);
    			haveWon=true;
    		}
    	}
    }
    

    如果连击三下,并且haveWon=false(注意:haveWon是一次性的,一旦能量块被弹出,那么继续连击的话也无法再次获得能量块了),清空targets,播放winSound,找到能量块,将它向左平移.实例化带脚本的powerCell预制体,并且消灭winCell,haveWon改为true.

  • 将脚本 CoconutWin 拖给对象 coconutShy;将对象 powerCell 拖入 Project 视图中的 Assets-Prefabs 目录,生成一个 powerCell 预制体;选中对象 coconutShy,找到组件 Coconut Win,将预 制体 powerCell 拖入 Cell Prefab 中。将 Project 视图中的 Assets-Book AssetsSounds-win_cell 拖入 Win Sound 中;

CrossHair瞄准

  • 点击 Crosshair 纹 理文件,在 Inspector 视图中,修改 Texture Type 为 Editor GUI and Legacy GUI,点击 Apply;点击 Create-Create Empty,修改对象名称为 Crosshair, 在 Inspector 视图中 Reset 其 Transform 组件,然后,修改属性 Position:X=0.5, Y=0.5,修改属性 Scale:X=0.05,Y=0.05。点击 Add Component-RenderingGUITexture,将纹理文件 Crosshair 拖入 Texture 属性中。勾选去掉 GUITexture 组件;

    // ThrowTrigger.cs
    
    public class ThrowTrigger : MonoBehaviour {
    	public GUITexture crossHair;
    	public GUIText textHints;
    
    
    	void OnTriggerEnter(Collider col){
    		if (col.gameObject.tag == "Player") {
    			crossHair.enabled=true;
    			if(!CoconutWin.haveWon){
    				textHints.SendMessage("ShowHint",
    				"\n\n\n\n\n There's a power cell attached to this game, \n" +
    				"maybe I'll win it if I can knock down all the targets...");
    			}
    
    			CoconutThrower.canThrow=true;
    		}
    	}
    	
    	void OnTriggerExit(Collider col){
    		if (col.gameObject.tag == "Player") {
    			crossHair.enabled = false;
    			CoconutThrower.canThrow=false;
    		}
    	}
    
  • 在 Hierarchy 视图中,展开 coconutShy-mat,点击 mat,将对象 Crosshair 拖入属性 Throw Trigger-Crosshair 中,将对象 TextHintGUI 拖入属性 Throw Trigger-Text Hints 中。运行游戏,控制角色到 coconutShy 的标靶发射处,此时,屏幕上会出现一个准心并显示提示信息,离开后,准心会消失。

Summary

公式 作用
Destroy (gameObject, removeTime); 定时删除对象
Rigidbody newCoconut = Instantiate(coconutPrefab, transform.position, transform.rotation) as Rigidbody; 预制体的实例化
newCoconut.name = "coconut"; 修改对象的名称
newCoconut.velocity = transform.forward * throwSpeed; 给刚体对象施加速度
Physics.IgnoreCollision(transform.root.collider, newCoconut.collider, true); 忽略对象间的碰撞
Physics.IgnoreCollision(transform.root.collider, newCoconut.collider, true); 忽略对象间的碰撞
StartCoroutine("targetHit"); 协同程序的调用
IEnumerator targetHit() { yield return new WaitForSeconds(resetTime); } 协同程序的调用、定义及让出 CPU

实例1.6:粒子系统

Campfire

  • 点击模型campfire,修改属性 Meshes-Scale Factor=0.5,勾选选中 Generate Colliders,勾选选中 Generate Lightmap UVs,点击 Apply;将模型 campfire 拖入场景中,放置在outPost 和 coconutShy 附近。,点击 Add Component-Physics-Capsule Collider。修改该组件的 Center 属性:Y=1,修改Radius=2,修改Height=5;

FireSystem

  • 点击 Create-Particle System,重命名为 FireSystem;选中 campfire,把鼠标移动到 Scene 视图中,然后按 下 f 键聚焦该对象。在 Hierarchy 视图中,选中 FireSystem,然后点击菜单 GameObject-Move to View。这样,这 2 个对象在地理位置上就重合了。

    • Move To View:将选中的gameObject移动到当前Scene视野3D空间的正中间位置。Move To View、Align With View、Align View to Selected 的作用
  • . 在确保 FireSystem 被选中的情况下,在 Inspector 视图中,修改下列属性: Start Size:Random Between Two Constants,它的 2 个数值分别为 0.5 和 2; Start Lifetime:Random Between Two Constants,它的 2 个数值分别为 1 和 1.5;Max Particles:80; 展开 Emission,修改 Rate 为 Curve,点击曲线图,按图1-2所示修改它的 曲线,并修改 Rate 的最高值为 80;Start Speed:Random Between Two Constants,它的 2 个数值分别为 0.1 和 1.5; 勾选选中 Velocity over Lifetime,并展开它,修改 Y=0.2,修改 Space 为 World; 勾选选中 Color over Lifetime,并展开它,点击颜色块,将弹出一个对话框 Gradient Editor,如图1-3所示。点击图中红色圆圈标示的图标,将 Alpha 设置为 0,双击图中蓝色圆圈标示的图标,将 Color 的三个分量 RGB 设置 成 255、255、255,注意,此时的 Location=0.0%。在红色圆圈标示的图标 水平向右处,用鼠标左键点击一下,将生成一个新的 Alpha 分量,设置它 的 Location 为 25%,修改 Alpha=42。在蓝色圆圈标示的图标水平向右处, 用鼠标左键点击一下,将生成一个新的 Color 分量,设置它的 Location 为 25%,然后双击该图标,修改它的 Color 分量 RGB 为 251、238、11。 重复上述步骤,依次生成三个 Alpha 和 Color 分量,它们的数据分别是: Location=50%,Alpha=181,Color RGB=255、171、11;Location=75%, Alpha=110,Color RGB=255、127、57;Location=100%,Alpha=11,Color RGB=255、100、40。结果如图1-4所示; Duration:1.50; 勾选选中 Size over Lifetime,修改 Size 为 Curve,点击曲线图,按图1-5所 示修改它的曲线(绿色),并修改 Size 的最高值为 1; 勾选选中 Force over Lifetime,展开,设置它为 Random Between Two. Constants,设置第 2 个向量为 X=1,Y=1,Z=1。勾选选中 Randomize; 勾选选中 Limit Velocity over Lifetime,展开,勾选选中 Separate Axis,修 改 Dampen=0.1; 展开 Renderer,勾选去掉 Cast Shadows 和 Receive Shadows;在 Project 视图的 Materials 目录中,鼠标右键点击 Create-Material,重命名为 Flame。在 Inspector 视图中,点击 Shader-Particles-Additive(Soft)。展开 Project 视图中的 Assets-Book Assets-Textures,将文件 flame 拖入 Particle Texture 上;将 Project 视图 Materials 目录中的 Flame 材质附加给 FireSystem 对象;在 Hierarchy 视图中,将 FireSystem 对象拖入 campfire 中,然后 Reset 前者的 Transform 组件。根据实际情况,适当地调整 FireSystem 的位置和绕 X 轴的 旋转角度。要观察 FireSystem 粒子发射器的形状(缺省是 Cone)和实际朝向, 可以点击 Shape 属性,然后根据实际朝向来调整绕 X 轴的旋转角度;

Smoke System

  • 点击 Create-Particle System,重命名为 SmokeSystem,将 SmokeSystem 拖入 campfire 对象中,并 Reset SmokeSystem 的 Transform 组 件。根据实际情况,适当地调整 SmokeSystem 的位置和绕 X 轴的旋转角度。要 观察 SmokeSystem 粒子发射器的形状(缺省是 Cone)和实际朝向,可以点击 Shape 属性,然后根据实际朝向来调整绕 X 轴的旋转角度;
  • 在确保 SmokeSystem 被选中的情况下,在 Inspector 视图中,修改下列属性:
    • Start Size:Random Between Two Constants,它的 2 个数值分别为 0.8 和 2.5;
    • Start Lifetime:Random Between Two Constants,它的 2 个数值分别为 8 和 10;
    • Max Particles:30;
    • 展开 Emission,修改 Rate 为 Curve,点击曲线图,按图1-6所示修改它的 曲线,并修改 Rate 的最高值为 15,最低值为 10(先点击水平线,修改最 高值,再拖动线的第 1 个端点到 10 附近);
    • Start Speed:1.5;
    • Simulation Space:World;
    • 勾选选中 Velocity over Lifetime,并展开它,修改为 Random Between Two Constants,设置第 2 个向量的 Y=0.2,修改 Space 为 World;
    • 勾选选中 Limit Velocity over Lifetime,展开,修改 Dampen=0.1;
    • 勾选选中 Color over Lifetime,并展开它,点击颜色块,将弹出一个对 话框 Gradient Editor。按照与 FireSystem 中一样的步骤,依次完成五个 Alpha 和 Color 分量的设置,它们的数据分别是:Location=0%,Alpha=6, Color RGB=16、16、16;Location=25%,Alpha=190,Color RGB=26、26、 26;Location=50%,Alpha=250,Color RGB=36、36、36;Location=75%, Alpha=190,Color RGB=46、46、46;Location=100%,Alpha=6,Color RGB=56、56、56。结果如图1-7所示;
    • 勾选选中 Size over Lifetime,修改 Size 为 Curve,点击曲线图,按图1-8所 示修改它的曲线(绿色),并修改 Size 的最高值为 2,最低值为 1;
    • 勾选选中 Force over Lifetime,展开,设置它为 Random Between Two Constants,设置第 2 个向量为 X=0.1,Y=0,Z=0.1。Space=World,勾 选选中 Randomize;
    • 展开 Renderer,勾选去掉 Cast Shadows 和 Receive Shadows,Max Particle Size=0.25;
  • 在 Project 视图的 Materials 目录中,鼠标右键点击 Create-Material,重命名为
    Smoke。在 Inspector 视图中,点击 Shader-Particles-Additive(Soft)。展开 Project
    视图中的 Assets-Book Assets-Textures,将文件 smoke 拖入 Particle Texture 上;
  • 将 Project 视图 Materials 目录中的 Smoke 材质附加给 SmokeSystem 对象;
  • 在 Hierarchy 视图中,选中 campfire 对象,在 Inspector 视图中,点击 Add Component-Audio-Audio Source,将 Project 视图 Assets-Book Assets-Sounds 目录中的文件fire_atmosphere 拖入属性 Audio Source-Audio Clip 中,勾选选 中 Loop;
  • 在确保 campfire 对象被选中的情况下,勾选去掉属性 Audio Source-Play On Awake。展开 campfire,选中 FireSystem 对象,勾选去掉属性 Play On Awake, 选中 SmokeSystem 对象,勾选去掉属性 Play On Awake;
  • 在 Project 视图中,将 Assets-Book Assets-Models 目录中的模型 matchbox 附 加到 outPost 对象上,然后 Reset matchbox 对象的 Transform 组件。修改属性 Transform-Position:Y=2.31,Z=-1.5,确保 matchbox 放在桌子上;
  • 在确保 matchbox 被选中的情况下,在 Inspector 视图中,点击 Add ComponentPhysics-Box Collider,勾选选中 Is Trigger,修改属性 Size:X=1,Y=1,Z=1;

Matches.cs

void OnTriggerEnter(Collider col) {
	if (col.gameObject.tag == "Player") {
		col.gameObject.SendMessage("MatchPickup");
		Destroy (gameObject);
    }
}

解释:如果火柴盒碰到了Player,那么Player发送消息给MatchPickUP函数,并且销毁火柴盒.

  • 回到 Unity,将脚本 Matches.cs 附加到子对象 outPost-matchbox 上;在 Project 视图中,选中目录 Assets-Book Assets-Textures 中的纹理文件 MatchGUI,在 Inspector 视图中,修改 Texture Type 为 Editor GUI and Legacy GUI,点击 Apply;

  • 在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 MatchGUI,Reset 它 的 Transform 组件,修改属性 Position:X=0.5,Y=0.5,修改属性 Scale:X=0.2, Y=0.2。在 Inspector 视图中,点击 Add Component-Rendering-GUITexture,将 目录 Assets-Book Assets-Textures 中的纹理文件 MatchGUI 拖入属性 Texture 中;

  • 将 MatchGUI 对象拖入 Project 视图的预制体目录 Prefabs 中,删除 Hierarchy 视图中的 MatchGUI 对象;

  • 编辑脚本 Inventory,找到语句: public Renderer meter; 在它的下一行输入:

    bool haveMatches = false;
    public GUITexture matchGUIprefab;
    GUITexture matchGUI;
    public GUIText textHints;
    bool fireIsLit = false;
    

    找到函数 HUDon,在它的函数体后面添加:

    void MatchPickup() {
    haveMatches = true;
    AudioSource.PlayClipAtPoint (collectSound, transform.position);
    GUITexture matchHUD = Instantiate (matchGUIprefab,
    new Vector3 (0.15f, 0.1f, 0), transform.rotation) as GUITexture;
    matchGUI = matchHUD;
    }
    
  • 选中 First Person Controller,找到组件 Inventory,
    将预制体目录 Prefabs 中的 MatchGUI 拖入属性 Match GUIprefab 中,将
    Hierarchy 视图中的 TextHintGUI 对象拖入 Text Hints 属性中;

  • 运行游戏,控制角色收集齐 4 个能量块,进入 outPost,收集到 matchBox 后,
    屏幕左下角将会出现 MatchGUI 图标;继续编辑 Inventory 脚本,在函数 MatchPickup 函数体的后面继续添加:

    void OnControllerColliderHit(ControllerColliderHit col) {
    	if (col.gameObject.name == "campfire") {
    		if (haveMatches && !fireIsLit) {
    			LightFire(col.gameObject);
    		} else if (!haveMatches && !fireIsLit) {
    		textHints.SendMessage("ShowHint","I could use this campfire to signal for help... " +
    		"if only I could light it...");
    		}
    	}
    }
    void LightFire(GameObject campfire) {
    	ParticleSystem[] pSystems;
    	pSystems = campfire.GetComponentsInChildren ();
    	foreach (ParticleSystem pSystem in pSystems) {
    	pSystem.Play();
    }
    
    campfire.audio.Play ();
    Destroy (matchGUI);
    haveMatches = false;
    fireIsLit = true;
    }
    

    运行游戏,控制角色靠近篝火,屏幕将会出现提示;控制角色获得 4 个能量块, 进入 outPost 获得 matchbox,再靠近篝火,点燃它,MatchGUI 将会消失;

Summary

属性名称 属性说明
Duration 发射粒子的持续时间(喷射周期)
Looping 是否循环发射
Prewarm 预热(Looping状态下预产生下一周期的粒子)
Start Delay 发射粒子之前的延迟(Perwarm状态下无法延迟)
Start Lifetime 开始的生命周期
Start Speed 发射时的速度(m/s)
3D Start Size 三维尺寸
Start Size 初始尺寸
3D Start Rotation 三维旋转
Start Rotation 开始旋转角
Randomize Rotation 随机数旋转
Start Color 发射时的颜色
Gravity Modifier 重力修改器(相当于物理管理器中重力加速度的重力密度)
Simulation Space 模拟空间
Simulation Speed 模拟速度
Delta Time 时间增量
Scaling Mode 扩展模式
Play On Awake 是否开始自动播放
Emitter Velocity 发射器速度
Max Particle 发射的最大数量(一个周期内的最大发射数量,超过则停止发射),手游建议不超过50
Auto Random Seed 随机种子

用Unity3D粒子系统做特效

实例1.7:制作菜单

  • 打开 SurvivalIsland 场景;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 Environment,并 Reset 它的 Transform 组件;将 Hierarchy 视图中的 Terrain、Daylight Water、Directional light 三个对象拖 入 Environment 对象中,成为它的子对象; 按 Ctrl+s 保存当前场景;在 Project 视图的 Scenes 目录中,选中 SurvivalIsland,按 Ctrl+d 复制它,重 命名为 Menu;双击 Menu 场景,打开它; 7. 在 Hierarchy 视图中,选中除 Environment 对象之外的其它所有对象,按 delete 键删除它们; 在 Hierarchy 视图中,点击 Create-Camera; 在 Scene 视图中,用鼠标调整视角,使得场景的视觉效果如图1-1所示;在 Hierarchy 视图中,选中 Camera,点击菜单GameObject-Align With View, 使得相机(Game 视图)与 Scene 视图中的视角对齐;

  • 在 Project 视图中,展开 Assets-Book Assets-Textures,同时选中文件menu_mainTitle、 menu_instructionsBtn、menu_instructionsBtnOver、menu_playBtn、menu_playBtnOver、 menu_quitBtn、menu_quitBtnOver 共 7 个文件,在 Inspector 视图中,修改属 性 Texture Type 为 Editor GUI and Legacy GUI,点击 Apply;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为menu_mainTitle,Reset 它的 Transform 组件。在 Inspector 视图中,点击 Add Component-RenderingGUITexture,将纹理文件menu_mainTitle 拖入属性 Texture 中。找到 Transform组件,修改属性 Position:X=0.2,Y=0.8,修改属性 Scale:X=0.2,Y=0.2; 13. 重复上面步骤,创建一个menu_playBtn 对象,使用的是menu_playBtn 纹理文 件,找到 Transform 组件,修改属性 Position:X=0.22,Y=0.6,修改属性 Scale: X=0.2,Y=0.1;在 Project 视图中,在目录 Scripts 下,新建一个 C# 脚本 MainMenuBtns,打开编辑:

    // MainMenuBtns.cs
    public class MainMenuBtns : MonoBehaviour {
    
    	public string levelToLoad;
    	public Texture2D normalTexture;
    	public Texture2D rollOverTexture;
    	public AudioClip beep;
    	public bool quitButton = false;
    	public bool instructionsButton = false;
    	public GUIText textHints;
    
    
    	void OnMouseEnter() {
    		guiTexture.texture = rollOverTexture;
    	}
    
    	void OnMouseExit() { 
    		guiTexture.texture = normalTexture;
    	}
    
    
    	IEnumerator OnMouseUp()
    	{
    		Debug.Log("Hey!");
    		audio.PlayOneShot(beep);
    		yield return new WaitForSeconds(0.35f);
    		if (quitButton)
    		{
    			Application.Quit();	// 退出应用程序
    		}
    		else if (instructionsButton)
    		{
    			textHints.SendMessage("ShowHint", "You awake on a mysterious island..." + "Find a way to signal for help or face certain doom!");
    		}
    		else
    		{
    			Application.LoadLevel(levelToLoad);     // 现在已过时。请改用SceneManager.LoadScene。加载场景
    		}
    	}
    
    }
    
  • 保存脚本,回到 Unity。将脚本 MainMenuBtns 附加到对象menu_playBtn 上;在 Project 视图中,选中文件 Assets-Book Assets-Sounds-menu_beep,在 Inspector视图中,勾选去掉 3D Sound,点击 Apply;Hierarchy 视图中,选中menu_playBtn 对象,在 Inspector 视图中,找到组件 Main Menu Btns,修改属性 Level To Load 为 SurvivalIsland,将纹理文件menu_playBtn 和menu_playBtnOver 分别拖入属性 Normal Texture 和 RollOver Texture 中,将音频文件menu_beep 拖入属性 Beep 中;在 Hierarchy 视图中,展开 Environment-Terrain,选中该子对象,在 Inspector视图中,勾选去掉 Terrain 的组件 Audio Source;点击菜单 File-Build Settings,将打开一个对话框。将 Project 视图 Scenes 目录中的 Menu 场景和 SurvivalIsland 场景相继拖入对话框的子窗口 Scenes In Build 中,关闭该对话框;运行游戏,观察鼠标移动到 Play 按钮上方和移开时的变化,点击 Play 按钮时,
    将会载入我们之前生成的场景 SurvivalIsland;退出游戏。保存当前场景,双击 Scenes 目录中的 SurvivalIsland 场景,调入该
    场景,按 Ctrl+c 复制对象 TextHintGUI,双击 Scenes 目录中的 Menu 场景,调入它,按 Ctrl+v 粘贴对象 TextHintGUI;选中 TextHintGUI 对象,在 Inspector 视图中,修改属性 Transform-Position:Y=0.6,修改属性 GUIText-Color 的颜色为红色;按照创建menu_playBtn 的方法,创建一个menu_instructionsBtn 对象,使用的是menu_instructionsBtn 纹理文件,找到 Transform 组件,修改属性 Position:X=0.22,Y=0.5,修改属性 Scale:X=0.2,Y=0.1;将脚本 MainMenuBtns 附加到对象menu_instructionsBtn 上。在 Inspector视 图 中, 找 到 组 件 Main Menu Btns, 将 纹 理 文 件menu_instructionsBtn和menu_instructionsBtnOver 分别拖入属性 Normal Texture 和 Roll Over Texture 中,将音频文件menu_beep 拖入属性 Beep 中。勾选选中 Instructions Button,将对象 TextHintGUI 拖入属性 Text Hints 中;运行游戏,观察鼠标移进和移开按钮时的变化,点击 Instructions 按钮,将显示游戏指令帮助;同样的方法,创建一个menu_quitBtn 对象,使用的是menu_quitBtn 纹理文件,
    找到 Transform 组件,修改属性 Position:X=0.22,Y=0.4,修改属性 Scale:X=0.2,Y=0.1;将脚本 MainMenuBtns 附加到对象menu_quitBtn 上。在 Inspector 视图中,找到组件 Main Menu Btns,将纹理文件menu_quitBtn 和menu_quitBtnOver 分别
    拖入属性 Normal Texture 和 Roll Over Texture 中,将音频文件menu_beep 拖入属性 Beep 中,勾选选中属性 Quit Button;运行游戏,点击 Quit 按钮,应该能够听到 beep 声音,但不会真正退出游戏,因为现在是在 Unity 中运行它。如果将游戏编译成可执行文件,那么点击 Quit按钮将会退出游戏。

Summary

  • 游戏载入:Application.LoadLevel (levelToLoad);
  • 游戏退出:Application.Quit();
  • 鼠标碰到这个组件:void OnMouseEnter();
  • 鼠标离开这个组件:void OnMouseExit();
  • 鼠标点击这个组件:IEnumerator OnMouseUp();

实例1.8:动画基础

  • 打开 SurvivalIsland 场景; 编辑脚本 Inventory,找到: bool fireIsLit = false; 在它的下一行,输入: public GameObject winObj; 在函数 LightFire 的函数体中,找到: fireIsLit = true;在它的下一行,输入: winObj.SendMessage ("GameOver");

  • 保存脚本,返回 Unity。在 Project 视图中,展开 Assets-Book Assets-Textures, 选中三个纹理文件win_message、win_survival、win_youWin,在 Inspector 视 图中,修改属性 Texture Type 为 Editor GUI and Legacy GUI,点击 Apply;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 WinMessage,在 Inspector 视图中,Reset 它的 Transform 组件,修改属性 Position:X=0.5,Y=0.4, 修改属性 Scale:X=0.3,Y=0.3。点击 Add Component-Rendering-GUITexture, 将纹理win_message 拖入属性 Texture 中;按照同样的方式,创建 WinSurvival,纹理文件使用win_survival,属性 Position: X=0.5,Y=0.8,属性 Scale:X=0.2,Y=0.2;按照同样的方式,创建 WinYouwin,纹理文件使用win_youWin,属性 Position: X=0.5,Y=0.65,属性 Scale:X=0.2,Y=0.2;

  • 在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 WinSequence,在 Inspector 视图中,Reset 它的 Transform 组件。将对象 WinMessage、WinSurvival、 WinYouwin 拖入 WinSequence 中,成为它的子对象;

    // IslandAnimator.cs
    
    public class IslandAnimator : MonoBehaviour {
    
    	public float xStartPosition=-1.0f;
    	public float xEndPosition=0.5f;
    	public float speed=1.0f;
    	float startTime;
    
    	void Start () {
    		startTime = Time.time;
    	}
    	
    	void Update () {
    		Vector3 pos = new Vector3 (Mathf.Lerp(xStartPosition,xEndPosition,(Time.time-startTime)*speed),transform.position.y,transform.position.z);
    		transform.position = pos;
    	}
    }
    

    xStartPosition和xEndPosition都是指的位置,Mathf.Lerp返回xStartPosition到xEndPosition的线性插值,由于是每一帧更新一次,所以在视觉上有一种动画效果。

  • 保存脚本,回到 Unity。把该脚本分别附加到 WinSequence 的子对象 WinMessage、WinSurvival、WinYouwin 上;选中子对象 WinMessage,修改属性 XStart Postion=1,选中子对象 WinSurvival,修改属性 XStart Postion=1,选中子对象 WinYouwin,修改属性 XStart
    Postion=-1;运行游戏,观察动画效果;在 Hierarchy 视图中,将对象 WinSequence 拖入 Prefabs 目录中。删除 Hierarchy
    视图中的对象 WinSequence;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 winObj,Reset 它的 Transform 组件;

    // WinGame.cs
    
    public class WinGame : MonoBehaviour {
    	public GameObject winSequence;
    	public GUITexture fader;
    	public AudioClip winClip;
    
    	IEnumerator GameOver()
        {
    		AudioSource.PlayClipAtPoint(winClip, transform.position);
    		Instantiate(winSequence);
    		yield return new WaitForSeconds(8.0f);
    		Instantiate(fader);
        }
    }
    

    当GameOver方法被调用的时候,播放结束的音效,实例化赢了的字幕(会呈现出动画效果)等待8秒,实例化fader(使屏幕黑掉,并且重新载入菜单)。

  • 保存脚本,回到 Unity;选中纹理文件 Assets-Book Assets-Textures-fade,在 Inspector 视图中,修改属 性 Texture Type 为 Editor GUI and Legacy GUI,点击 Apply;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 fadeGUI,Reset 它 的 Transform 组件,修改属性 Position:X=0.5,Y=0.5,修改属性 Scale:X=0.01, Y=0.01。在 Inspector 视图中,点击 Add Component-Rendering-GUITexture, 将纹理文件 fade 拖入属性 Texture 中,修改属性 Pixel Inset:X=-512,Y=-384, W=1024,H=768;

    // Fader.cs
    
    public class Fader : MonoBehaviour {
    	public GUITexture loadGUI;
    	void Start () {
    		Rect currentRes = new Rect(-Screen.width * 0.5f, -Screen.height * 0.5f, Screen.width, Screen.height);
    		guiTexture.pixelInset = currentRes;
    	}
    	void LoadAnim()
        {
    		Instantiate(loadGUI);
        }
    }
    

为了防止屏幕分辨率导致的屏幕没有全部黑掉,所以采用Scree.width和Scree.height; pixelInset是用来执行像素嵌入的功能。 关于pixelInset控制在屏幕位置注意:GUITexture.pixelInset = Rect (x, y, width, height); 此变量用于划定GUITexture的位置及宽高,Rect中x,y的值都为0的时候,所在的位置居于屏幕中心,即的坐标中心是以屏幕中心为(0,0)坐标,它的正负情况符合二维坐标系的四个象限。LoadAnim将loadGUI(也就是loading字样)实例化出来。

  • 保存脚本,回到 Unity。将脚本 Fader 附加到对象 fadeGUI 上; 运行游戏,可以观察到,不管是哪种分辨率,fadeGUI 都可以全部覆盖(黑屏)。 在 Game 视图的左上角可以改变分辨率; 在 Hierarchy 视图中,选中 fadeGUI 对象,在 Inspector 视图中,找到组件 GUITexture,点击属性 Color 右侧的颜色条,将打开一个 Color 对话框,将 A 分量修改为 0。修改后,Game 视图中的游戏场景应该是可见的; 点击 Add Component-Miscellaneous-Animation,为 fadeGUI 添加一个 Animation 组件; 在确保 fadeGUI 被选中的情况下,点击菜单 Window-Animation,将打开 Animation 对话框; 在 Animation 对话框中,点击 Sample 60 左侧的上三角下三角图标,选择 Create New Clip,Unity 要求你输入保存 Animation 的文件名称,输入 fadeOut.anim, 你可以将这个文件保存在目录 Assets-Animations 中; 在 Animation 对话框中,点击 Add Curve-GUITexture-Color,然后点击右侧的 “+”图标,然后展开 fadeGUI:GUITexture.Color;在 Animation 对话框中,选中 Color.a,它的右侧窗口上方显示了以秒为单位的 时间刻度,将该对话框向右扩大,直至能够看到刻度 2:00 为止,点击该刻度上 的垂直短线,然后在左侧窗口,修改 Color.a 为 1,点击对话框下方的 Curves 标签。如果没有看到曲线,可以把鼠标移到曲线窗口,然后 按下 f 键聚焦; 点击刻度 0:00 处短垂线,此时显示一条长垂直红线,鼠标右键点击,然后选择 Flat。在刻度 2:00 处按同样的方式操作,结果如图1-3所示; 在 Project 视图中,找到 fadeOut 动画,将它拖入 fadeGUI 对象的属性 Animation-Animation 中; 运行程序,可以看到整个场景逐渐变黑;

  • . 保存脚本,回到 Unity 中。再次选中对象 fadeGUI,然后打开它的动画编辑窗 口 Animation,展开左侧的属性 Color.a,选中它。然后,在右侧时间轴窗口中, 点击刻度 1:00 上的小垂线(选中该刻度),点击 Sample 60 上方如图1-4所示的 按钮,将弹出一个对话框 Edit Animation Event,为 Function 选择 LoadAnim, 关闭该窗口; 在 Project 视图中,点击 Assets-Book Assets-Textures,选中纹理文件 loadingGUI, 在 Inspector 视图中,修改属性 Texture Type 为 Editor GUI and Legacy GUI, 点击 Apply;在 Hierarchy 视图中,点击 Create-Create Empty,重命名为 loadingGUI,Reset 它的 Transform 组件。修改属性 Position:X=0.5,Y=-1,修改属性 Scale:X=0.2, Y=0.2。点击 Add Component-Rendering-GUITexture,将纹理文件 loadingGUI 拖入属性 Texture 中。点击 Add Component-Miscellaneous-Animation;

  • 点击菜单 Window-Animation,打开动画窗口。按前述方法,点击 Create New Clip,新建一个 clip,命名为 showHide.anim,保存到目录 Animations 中; 在 Animation 对话框中,点击 Add Curve-Transform-Position,点击添加按钮; 选中 Position.y,点击 Curves 标签,点击时间刻度 1:00 处的小垂线,修改 Position.y=0.5,重复该步骤,分别在时间刻度 4:00 处,修改 Position.y=0.5, 时间刻度 5:00 处,修改 Position.y=-1。再次确认,这几个时间刻度对应的 Position.y 的数值是否正确。设置完毕后,将鼠标移到 Curve 窗口,点击 f 键, 曲线显示应如图1-5所示; 在时间刻度 1:00 的下方,鼠标右键点击曲线上的菱形点,选择 Flat,同样,在刻 度 4:00 的下方,鼠标右键点击曲线上的菱形点,选择 Flat。这样,在 1:00-4:00 之间,曲线是平的; 38. 关闭动画窗口,将动画 showHide.anim 拖入对象 loadingGUI 的属性 Animation 中;

    // Reloader.cs
    
    public class Reloader : MonoBehaviour {
    
    	void Reload()
        {
    		Application.LoadLevel("Menu");
    		Debug.Log("Go to Menu!");
        }
    }
    
    
  • 保存脚本,回到 Unity。将脚本 Reloader 附加到 loadingGUI 对象上;在 Hierarchy 视图中,选中 loadingGUI 对象,打开动画窗口 Animation。选中 Position.y,点击 Curves,选中时间刻度 6:00,点击 Add Event 按钮,在出现的 Edit Animation Event 窗口中,选中 Reload 函数,关闭该窗口。将对象loadingGUI 拖入 Prefabs 中,然后删除 Hierarchy 视图中的 loadingGUI 对象;在 Hierarchy 视图中,选中 fadeGUI,找到属性 Fader-Load GUI,将预制体loadingGUI 拖入该属性中;将对象 fadeGUI 拖入 Prefabs 中,然后删除该对象;Hierarchy 视图中,选中对象 winObj,将脚本 WinGame 附加到该对象上,将预制体 WinSequence 拖入属性 Win Sequence 中,将预制体 fadeGUI 拖入属性 Fader 中,将音频文件win_clip 拖入属性 Win Clip 中;在 Hierarchy 视图中,选中对象 First Person Controller,将对象 winObj 拖入 属性 Win Obj 中; 46. 在 Project 视图中,展开预制体 WinSequence,修改三个子级物体 WinMessage、 WinSurvival、WinYouwin 的属性 Position:Z=-2。修改预制体 fadeGUI 的属 性 Position:Z=-1; 运行游戏,控制角色收集完所有四个能量块,进入 outPost 收集火柴,并点燃 篝火,观察效果。

Summary

  • 如何制作动画?

    1. 选中要添加该动画组件的对象,选择Window-Animation,最上面一栏倒数第二个上三角下三角图标,点击添加动画。

    2. 在 Animation 对话框中,点击 Add Curve-GUITexture-Color(改变颜色的动画),点击刻度x:y处短垂线,此时显示一条长垂直红线,(x:y是你选择的时刻);如若点击 Add Curve-Transform-Position,则是改变位置的动画。

    3. 右键点击曲线上的菱形点,或者选择 Flat,可以改变曲线的形状。

    4. 点击刻度x:y处短垂线,点击最上面一栏倒数第一个,可以添加事件响应函数。

  • 如何通过脚本实现动画效果?

    Vector3 pos = **new** Vector3 ( 
    Mathf.Lerp (xStartPosition,xEndPosition,(Time.time - startTime) * speed),transform.position.y,transform.position.z); 
    

    插值函数可以使物体动起来,进而在视觉上形成动画效果。

实例2


实例2.1

  • Unity3D中Rigidbody.velocity和Addforce的区别

  • transflorm.TransformDirection: 从局部坐标转换为世界坐标。 传送门1

    当自身Z轴方向跟世界X轴方向一致时,我的位置还是(0,0,0),我向前(相对自身)移动一个单位,我的位置变成(1,0,0),即为:transform.postion += transform.TransformDirection(vector3(0,0,1))的结果。

    public class MissileLauncher : MonoBehaviour {
    
    public Rigidbody projectile;
    public float speed = 30.0f;
    
    void Update () {
    		if (Input.GetButtonDown("Fire1")) {
    			Rigidbody instantiateProjectile = Instantiate(projectile, transform.position, transform.rotation) as Rigidbody;
    			instantiateProjectile.velocity = transform.TransformDirection(new Vector3(0, 0, speed));
    			Physics.IgnoreCollision(instantiateProjectile.collider, transform.root.collider);
    		}
    
    	}
    }
    
  • 导弹碰撞:hit.contact 物理引擎生成的接触点;Quaternion 公共静态四元数,FromToRotation 可以用来做旋转变换(根据两个向量计算出旋转量,计算出来的旋转量为从fromDirection旋转到toDirection的旋转量) contact.normal, 接触点的法线。

    unity3d 四元数和旋转矩阵

    public class Projectile : MonoBehaviour {
    
    	public GameObject explosion;
    
    	void OnCollisionEnter(Collision hit) {
    		ContactPoint contact = hit.contacts[0];
    		// 描述发生碰撞的接触点。
    		Quaternion hitRot = Quaternion.FromToRotation(Vector3.up, contact.normal);
    		Vector3 hitPos = contact.point;
    		GameObject instantiatedExplosion = Instantiate(explosion, hitPos, hitRot) as GameObject;
    		Destroy(this.gameObject);
    	}
    }
    
  • 导弹爆炸:Physics.OverlapSphere返回以参数1为原点和参数2为半径的球体内“满足一定条件”的碰撞体集合;对于获得的碰撞体集合,若每个碰撞体有刚体,则给刚体添加力;ParticleEmitter 粒子碰撞器,Emit 方法允许 ParticleEmitter 发射给定数量的粒子;如果有粒子碰撞器,发射粒子,等待0.5s,将控制发射粒子的布尔变量改为false。

    public class Explosion : MonoBehaviour {
    
    	public float explosionTime = 1.0f;
    	public float explosionRadius = 5.0f;
    	public float explosionPower = 2000.0f;
    
    	IEnumerator Start()
    	{
    		Destroy(this.gameObject, explosionTime);
    		Collider[] colliders = Physics.OverlapSphere(this.transform.position, explosionRadius);
    		foreach(Collider collider in colliders)
            {
    			if (collider.rigidbody) {
    				collider.rigidbody.AddExplosionForce(explosionPower, this.transform.position, explosionRadius);
    			}
            }
    
    		if (this.particleEmitter) {
    			this.particleEmitter.emit = true;
    			yield return new WaitForSeconds(0.5f);
    			this.particleEmitter.emit = false;
    		}
    	}
    }
    
  • 瞄准Crosshair:Rect (x, y, width, height) 适应各种分辨率,使其都在屏幕正中间.

    public class Crosshair : MonoBehaviour {
    
    	public Texture2D crosshairTexture;
    	private Rect position;
    
    	void Start () {
    		position = new Rect((Screen.width - crosshairTexture.width)/2,(Screen.height-crosshairTexture.height)/2,crosshairTexture.width,crosshairTexture.height) ;
    	}
    
    	void OnGUI() {
    		GUI.DrawTexture(position, crosshairTexture);
    	}
    	
    }
    

实例2.2

这作业贞多得离谱

  • PlayerWeapons.js
function Start () {
	SelectWeapon(0);
}

function Update () {
	if(Input.GetButton("Fire1"))
		BroadcastMessage("Fire");

	if(Input.GetKeyDown("1")){
		SelectWeapon(0);
	}
	else if(Input.GetKeyDown("2")){
		SelectWeapon(1);
	}
}

function SelectWeapon(index: int){
	for(var i=0;i<transform.childCount;i++)
	if(i==index)
		transform.GetChild(i).gameObject.SetActive(true);
	else 
		transform.GetChild(i).gameObject.SetActive(false);
}
  • SendMessageUpwards

    原型:public void SendMessageUpwards(string methodName, object value = null, SendMessageOptions options = SendMessageOptions.RequireReceiver);

    作用:它的作用和SendMessage类似,只不过它不仅会向当前对象推送一个消息,也会向这个对象的父对象推送这个消息(记住,是会遍历所有父对象)。

  • BroadcastMessage

    原型:public void BroadcastMessage(string methodName, object parameter = null, SendMessageOptions options = SendMessageOptions.RequireReceiver);

    作用:这个函数的作用和SendMessageUpwards的作用正好相反,它不是推送消息给父对象,而是推送消息给所有的子对象,当然,也是会遍历所有的子对象。传送门

该函数的功能是选择武器。该脚本挂载在Main Camera下的Weapons物体下。选择武器能够遍历Weapons下的所有物体(RocketLauncher和Machine Gun)如果按下1号键,那么将激活武器1,并且将武器2设为不可见;如果按下2号键,将激活武器2,并将武器1设为不可见。如果按下开火键,就推送开火消息给所有的子对象。

易错:GetButton的函数名容易敲成别的比如GetKeyDown或者GetButtonDown之类。

  • RocketLauncher.js
var projectile: Rigidbody;
var initialSpeed=20.0;
var reloadTime=0.5;
var ammoCount=20;
private var lastShot=-10.0;

function Fire(){
	if(Time.time>reloadTime+lastShot && ammoCount>0){
		var instantiatedProjectile: Rigidbody=Instantiate(projectile,transform.position,transform.rotation);
		instantiatedProjectile.velocity=transform.TransformDirection(Vector3(0,0,initialSpeed));
		Physics.IgnoreCollision(instantiatedProjectile.collider,transform.root.collider);
		lastShot = Time.time;
		ammoCount--;
	}
}

projectile:实例化的物体为刚体,initialSpeed:初始速度为20,reloadTime:重载时间,ammoCount:一共有20发子弹。发射条件:一共有20发子弹,并且每次发射子弹的间隔为0.5s。如果满足发射条件,就实例化子弹,给子弹添加速度,并且忽略子弹和First Person Player的碰撞(否则可能会乱飞),每次发射一次子弹,ammoCount都递减1。

  • Rocket.js
var explosion: GameObject;
var timeOut=3.0;

function Start () {
	Invoke("Kill", timeOut);
}

function OnCollisionEnter (collision : Collision) {
	var contact: ContactPoint=collision.contacts[0];
	var rotation=Quaternion.FromToRotation(Vector3.up,contact.normal);
	Instantiate(explosion,contact.point,rotation);
	Kill();
}

function Kill(){
	var emitter: ParticleEmitter=GetComponentInChildren(ParticleEmitter);
	if(emitter)
		emitter.emit=false;
	transform.DetachChildren();
	Destroy(gameObject);
}

@script RequireComponent (Rigidbody)
  • Invoke函数:延时调用函数。传送门
  • GetComponentInChildren:获取对象的子对象中指定类型的控件,注意的是假如父对象拥有该控件,首先获取的是父对象的控件(它寻找的对象也包括父对象的)传送门
  • ParticleEmitter:粒子发射器 传送门
  • DetachChildren:分离当前物体上所有子物体。用处:当想销毁物体,但需保留子物体时可以这样。 传送门

该脚本挂载在Rocket身上。在3s后调用kill函数。获取子弹Rocket中的粒子发射器组件并命名为emitter,如果该组件为false,就把它设置为true,发射粒子。发射后分离子物体,并销毁子弹。

  • Trail.js
public class Trail : MonoBehaviour {

	void Start () {
		Destroy(gameObject, 3);
	}
}

该脚本挂载在Rocket下的 Trail System(类型为粒子系统)下。粒子发射三秒后,自动销毁。

  • ExplosionSimple.js
var explosionRadius=5.0;
var explosionPower=500.0;
var explosionDamage=100.0;

var explosionTime=1.0;

function Start () {
	var explosionPosition=transform.position;
	var colliders:Collider[]=Physics.OverlapSphere(explosionPosition,explosionRadius);

	for(var hit in colliders){
		if(!hit)
			continue;

		if(hit.rigidbody){
			hit.rigidbody.AddExplosionForce(explosionPower,explosionPosition,explosionRadius,3.0);
			
			var closestPoint=hit.rigidbody.ClosestPointOnBounds(explosionPosition);
			var distance=Vector3.Distance(closestPoint,explosionPosition);
			var hitPoints=1.0-Mathf.Clamp01(distance/explosionRadius);
			hitPoints*=explosionDamage;
			hit.rigidbody.SendMessageUpwards("ApplyDamage",hitPoints,SendMessageOptions.DontRequireReceiver);
		}
	}
	if (particleEmitter) {
		particleEmitter.emit = true;
		yield WaitForSeconds(0.5);
		particleEmitter.emit = false;
	}

	Destroy (gameObject, explosionTime);
}
  • Physics.OverlapSphere(explosionPosition,explosionRadius):本函数一旦被调用,将会返回以参数1为原点和参数2为半径的球体内“满足一定条件”的碰撞体集合,此时我们把这个球体称为 3D相交球。
  • AddExplosionForce:应用一个力到刚体来模拟爆炸效果。爆炸力将随着到刚体的距离线形衰减。传送门
  • ClosestPointOnBounds:到碰撞器的框最近的点。当应用于爆炸伤害,这能用于计算伤害点数。
  • Mathf.Clamp01 限制0~1:static function Clamp01 (value : float) :float
    限制value在0,1之间并返回value。如果value小于0,返回0。如果value大于1,返回1,否则返回value 。传送门
  • SendMessageUpwards:它的作用和SendMessage类似,只不过它不仅会向当前对象推送一个消息,也会向这个对象的父对象推送这个消息(记住,是会遍历所有父对象)。传送门

该脚本挂载在 SimpleExplosion(类型为粒子系统)上。explosionRadius:爆破半径,explosionPower:爆破力,explosionTime:爆破时间。explosionPosition:爆破位置,即为该粒子所在位置;colliders是一个爆炸范围内的碰撞体集合。遍历这个集合里的所有的碰撞体,如果该碰撞体的刚体为可用时,就给该碰撞体添加力。计算到爆炸框最近的点到爆炸点的距离,将计算该距离和爆炸半径的比值,使其值在0-1内,将该比值乘以伤害值100。向当前对象及其父对象发送消息ApplyDamage。如果该物体有粒子发射器组件,发射粒子,等待0.5s,然后停止。1s后销毁该物体。

  • MachineGun.js
var range=100.0;
var fireRate=0.05;
var force=50.0;
var damage=5.0;
var bulletsPerClip = 40;
var clips=20;
var reloadTime=0.5;
private var hitParticles:ParticleEmitter;
var muzzleFlash:Renderer;

private var bulletsLeft : int = 0;
private var nextFireTime = 0.0;
private var m_LastFrameShot = -1;

function Start () {
	hitParticles = GetComponentInChildren(ParticleEmitter);
	if (hitParticles) 
		hitParticles.emit = false;
	bulletsLeft = bulletsPerClip;
}

function Update () {

}

function LateUpdate() {
	if (muzzleFlash) {
		if (m_LastFrameShot == Time.frameCount) {
			muzzleFlash.transform.localRotation = Quaternion.AngleAxis(Random.value * 360,Vector3.forward);
			muzzleFlash.enabled = true;
			if (audio) {
				if (!audio.isPlaying)
					audio.Play();
				audio.loop = true;
			}
		} 
		else {
			muzzleFlash.enabled = false;
			enabled = false;
			if (audio){
				audio.loop = false;
			}
		}
	}
}

function Fire () {
	if (bulletsLeft == 0)
		return;
	if (Time.time - fireRate > nextFireTime)
		nextFireTime = Time.time - Time.deltaTime;
	while( nextFireTime < Time.time && bulletsLeft != 0) {
		FireOneShot();
		nextFireTime += fireRate;
	}
}

function FireOneShot () {
	var direction = transform.TransformDirection(Vector3.forward);
	var hit : RaycastHit;

	if (Physics.Raycast (transform.position, direction, hit, range)) {
		if (hit.rigidbody)
			hit.rigidbody.AddForceAtPosition(force * direction, hit.point);
		if (hitParticles) {
			hitParticles.transform.position = hit.point;
			hitParticles.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
			hitParticles.Emit();
		}
		hit.collider.SendMessageUpwards("ApplyDamage",damage,SendMessageOptions.DontRequireReceiver);
	}
	bulletsLeft--;
	m_LastFrameShot = Time.frameCount;
	enabled = true;
	if (bulletsLeft == 0)
		Reload();
}

function Reload () {
	yield WaitForSeconds(reloadTime);
	if (clips > 0) {
		clips--;
		bulletsLeft = bulletsPerClip;
	}
}

function GetBulletsLeft () {
	return bulletsLeft;
}
  • LateUpdate:LateUpdate是在所有Update函数调用后被调用。这可用于调整脚本执行顺序。传送门

  • Time.frameCount:已经经过的总帧数。

    using UnityEngine;
    
    public class Example : MonoBehaviour
    {
        // 确保RecalculateValue每帧只执行一次某些操作,而不是更多。
        static private int lastRecalculation = -1;
    
        static void RecalculateValue()
        {
            if (lastRecalculation == Time.frameCount)
                return;
            //  ProcessData.AndDoSomeCalculations();
        }
    }
    
  • Transform.localRotation:变换的旋转相对于父对象的变换旋转。

  • SendMessageOptions.RequireReceiver:没有找到相应函数,也不会报错,自动忽略

初始化:获取粒子发射器组件,如果获取到了该组件,那么将bulletsLeft设为40。如果muzzleFlash状态为true。每帧执行:相对于父对象变换旋转,围绕z轴随机旋转一定度数,将muzzleFlash变为可用,如果有音乐组件,且音乐没在播放,就循环播放音乐。

开火:发射频率为每0.05秒一次,并且还有剩下的子弹时,执行发射子弹FireOneShot函数。

FireOneShot:从本地坐标转换为世界坐标。使用光线投射的方式检测碰撞,给小球在hit.point位置上施加一个force大小的力。如果hitParticles为可用,设置hitParticles的位置为hit的位置,旋转一定角度后发射粒子。发送ApplyDamage消息计算伤害点,没有找到相应函数,也不会报错,自动忽略。如果子弹发完了,执行Reload函数。

Reload:等待0.5s,子弹重新上膛20发。

  • DamageReceiver.js
#pragma strict

var hitPoints = 100.0;
var detonationDelay = 0.0;
var explosion : Transform;
var deadReplacement : Rigidbody;

function ApplyDamage (damage : float) {
	if (hitPoints <= 0.0)
		return;
	hitPoints -= damage;
	if (hitPoints <= 0.0) {
		var emitter : ParticleEmitter =
		GetComponentInChildren(ParticleEmitter);
		if (emitter)
			emitter.emit = true;
		Invoke("DelayedDetonate", detonationDelay);
	}
}

function DelayedDetonate () {
	BroadcastMessage ("Detonate");
}

function Detonate () {
	Destroy(gameObject);
	if (explosion)
		Instantiate (explosion, transform.position, transform.rotation);
	if (deadReplacement) {
		var dead : Rigidbody = Instantiate(deadReplacement,transform.position, transform.rotation);
		dead.rigidbody.velocity = rigidbody.velocity;
		dead.angularVelocity = rigidbody.angularVelocity;
	}
	var emitter : ParticleEmitter = GetComponentInChildren(ParticleEmitter); 
	if (emitter) {
		emitter.emit = false;
		emitter.transform.parent = null;
	}
}

@script RequireComponent (Rigidbody)

它们是其中的一部分,因此当它运行时,它们就会运行。可以取消它的子代(以防止死亡的导弹将其吸入坟墓),停止产生新的导弹并延迟死亡足够长的时间,以使它们全部消失。传送门

Transform PE = transform.Find("exhaustPE");
PE.particleSystem.Stop();
PE.gameObject.parent = null;
Destroy(PE.gameObject, 5.0); // if particles live for at most 5 secs

ApplyDamage:减去伤害点,获取粒子发射器组件,发射粒子,延时detonationDelay秒后,执行DelayedDetonate函数。

DelayedDetonate:广播执行Detonate函数

Detonate:销毁物体,实例化爆炸粒子效果Explosion,如果deadReplacement刚体存在,实例化出deadReplacement并命名为dead,给它添加速度,和角速度。获取粒子发射器组件,停止发射,emitter.transform.parent = null可以使得销毁粒子系统时,可以保留已经发出的粒子。

  • SentryGun.js
var attackRange = 30.0;
var shootAngleDistance = 10.0;
var target : Transform;

function Start () {
	if (target == null && GameObject.FindWithTag("Player"))
		target = GameObject.FindWithTag("Player").transform;
}

function Update () {
	if (target == null)
		return;
	if (!CanSeeTarget ())
		return;

	var targetPoint = target.position;
	var targetRotation = Quaternion.LookRotation (targetPoint - transform.position, Vector3.up);
	transform.rotation = Quaternion.Slerp(transform.rotation,targetRotation, Time.deltaTime * 2.0);
	var forward = transform.TransformDirection(Vector3.forward);
	var targetDir = target.position - transform.position;
	if (Vector3.Angle(forward, targetDir) < shootAngleDistance)
		SendMessage("Fire");
}

function CanSeeTarget () : boolean
{
	if (Vector3.Distance(transform.position, target.position) > attackRange)
		return false;
	var hit : RaycastHit;
	if (Physics.Linecast (transform.position, target.position, hit))
		return hit.transform == target;

	return false;
}
  • Linecast:从开始位置到结束位置做一个光线投射,如果与碰撞体交互,返回真

  • RaycastAll:投射一条光线并返回所有碰撞,也就是投射光线并返回一个RaycastHit[]结构体。

Physics.Linecast 投射的是一条线段,有开始值和结束值,返回值也为bool

Physics.RaycastAll 投射的是一条射线,但返回值为RancastHit的一个结构体

初始化:如果target为空,且找到了Player,就将Player的transform组件赋值给target。

CanSeeTarget:如果他们的距离大于伤害距离,返回False,即不能看见。

如果target有值且能看见Player,那么获取target的位置和旋转角度,转换为自身坐标系向前运动,如果他们之间的转角小于10,发送开火消息。

Summary

  • FPS 多武器切换:在MainCamera下添加多个空物体,在空物体下添加武器。使用SeleceWeapon(index)切换武器。用index来传入键盘输入。

  • 发射频率控制:

    if(Time.time>reloadTime+lastShot && ammoCount>0){
        ..........
    		lastShot = Time.time;
    		ammoCount--;
    	}
    
  • Rocket 对象上依附的粒子处理技巧:先关闭该粒子的继续生成功能,然后将它从父体上剥离,以防止粒子突然消失带来的唐突感,

    var emitter : ParticleEmitter =	GetComponentInChildren(ParticleEmitter);
    if (emitter)
    	emitter.emit = false;
    
  • 为爆炸增加破坏力:AddExplosionForce

  • LateUpdate 函数在每次 Update 函数被调用后得到执行,因此,枪口火焰 只能在当前帧有子弹发射后才能被显示出来。否则,就应该被关闭;

    if (m_lastFrameShot == Time.frameCount) 
    { ... muzzleFlash.enabled = true; }
    else 
    { ... muzzleFlash.enabled = false; }
    
  • 函数 Reload 模拟了弹匣装载的效果

  • 控制对象转向目标的方法:

    var targetPoint = target.position;
    var targetRotation = Quaternion.LookRotation( targetPoint - transform.position, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 2.0);
    
  • 对象视域角及对象可见性的处理

    if (Vector3.Distance(transform.position, target.position) > attackRange)
        return false;
    var hit : RaycastHit;
    if (Physics.Linecast (transform.position, target.position, hit))
        return hit.transform == target;
    
    return false;
    

你可能感兴趣的:(笔记,unity3d,unity,游戏开发)