僵尸围城小游戏:基于ARKit的Unity3d游戏开发--僵尸围城

基于ARKit的Unity3d游戏开发--僵尸围城
无可置疑的是,对绝大多数的中小游戏团队来说,目前和Unreal Engine4(虚幻4)已经成为3D游戏开发的首选商业引擎。因为Unity3d的简单易上手特性,强大的功能和丰富的游戏资源及扩展功能(通过Asset Store),很多初学者选择了从它开始进入游戏开发的世界。
然而遗憾的是,从2010年之后炙手可热的移动游戏(手游)开发如今已经成了超级红海,甚至是血海。如果大家只是纯粹的个人兴趣导向,那么还是可以学一学unity3d,然后试着做一两款自己喜欢的手游上架。但是如果从商业的角度考量,传统意义上的手游市场已经成了巨头的禁脔。作为小型团队或者独立游戏开发者,或许需要探索一条全新的道路。
苹果ARKit和Google ARCore的横空出世给我们照亮了前行的方向,虽然目前增强现实和虚拟现实技术都还相当不成熟,但在未知的蓝海中探索,总好过在红海中和一帮巨人搏命厮杀。
在本系列的教程中,我们将一起学习如何从零开始学习基于ARKit的Unity3d移动游戏开发。全系列的教程都是基于实战项目的,而且尽可能考虑到初学者可能会遇到的种种困难和障碍。
Part 1 开始前的准备
硬件设备:
首先要说明的是,既然本教程是基于ARKit的Unity3d移动游戏开发,那么有两大硬件设备是必不可少的。
1.苹果电脑或Wndows系统


2.iPhone或iPad pro
因为ARKit本身对运算性能的要求,因此需要A9或以上芯片,iPhone 6s之前的设备基本上都已经无法使用了。所以建议大家使用6s之后的iPhone,或者是iPad Pro。
下面列出了支持ARKit和Core ML的iOS设备清单:
        •        iPhone 6s and 6s Plus
        •        iPhone 7 and 7 Plus
        •        iPhone SE
        •        iPad Pro (9.7, 10.5 or 12.9) – 1代和2代
        •        iPad (2017)
        •        iPhone 8 and 8 Plus
        •        iPhone X

AR游戏开发和普通游戏开发最大的区别就是,模拟器对于AR游戏开发的作用微乎其微,绝大多数时候我们都需要随时通过iPhone或iPad设备进行测试,所以这个也是不能省的。
系统环境:
操作系统是macOS Sierra或High Sierra
特别说明一下,目前(2017年11月)有大量的第三方软件还不兼容High Sierra,因此个人目前使用的还是Sierra 10.12.6。建议大家如非必要,先不要着急升到High Sierra,可以等到明年初之后升级到新的操作系统。


开发工具:
Xcode
Xcode 9及以上版本是必不可少的了,本教程写作时所使用的版本是Xcode 9.1(9B55)
 

using System.Collections.Generic;
using UnityEngine;
//import namespace
using UnityEngine.UI;

public class ShootEnemy : MonoBehaviour {
        //创建到Button对象的引用
        public Button shootBtn;
        //创建到主摄像机的引用
        public Camera fpsCam;
        //设置敌人每次受到伤害的数值
        public float damage = 10f;
        //敌人受伤的粒子特效
        public GameObject bloodEffect;
        //攻击的粒子特效
        public GameObject shootingEffect;
        //添加的攻击力度
        public int forceAdd = 300;
        //1.定义两个音源对象
        AudioSource shootSound;
        AudioSource reloadSound;
        // Use this for initialization
        void Start () {
                //Debug.Log ("Activated!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //添加按钮的响应事件
                shootBtn.onClick.AddListener (OnShoothttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>
                //2.获取音源组件
                AudioSource[] audios = GetComponents<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //3.设置音源
                shootSound = audios [0];
                reloadSound = audios [1];
        }
        public void OnShoot(){
                //4.播放音效
                shootSound.Play(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                Debug.Log ("shooting!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //定义一个RaycastHit类型变量,用于保存检测信息
                RaycastHit hit;
                //判断是否检测到命中敌人
                if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit)) {         
                //获取所受攻击的敌人
                        Enemy target = hit.transform.GetComponent<Enemy>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //destroy enemy
                        if (target != null) {
                                //instantiate blood effect                      
                                target.TakeDamage (damagehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //创建敌人受伤的粒子特效

                                GameObject bloodBurst = Instantiate (bloodEffect, hit.point, Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //0.2秒后销毁粒子特效
                                Destroy (bloodBurst, 0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        }  else {
                                //load shooting effect
                                //如果没有击中敌人,则创建攻击时的粒子特效
                                GameObject shootingGo =        Instantiate (shootingEffect, hit.point, Quaternion.LookRotation (hit.normal));                      
                                //0.2秒后销毁粒子特效
                                Destroy (shootingGo,0.2f);                        
                        }
                //攻击敌人时添加一个额外的冲击力
                        if (hit.rigidbody != null) {                      
                                hit.rigidbody.AddForce (-hit.normal * forceAddhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        
                        }
        
                //输出所命中的对象名称
                Debug.Log (hit.transform.namehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>
                }
        }
}
还是按照注释行的数字编号来解释一下:

1.定义了两个音源对象,分别用在攻击敌人和重新装弹上

2.使用数组来获取音源组件,注意这里用的是GetComponents,而不是GetComponent,因而获取的是一个数组,而非单一组件对象。

3.分别设置两个音源对象

4.在射击时播放对应的音效。

好了,攻击敌人的音效已经添加了,接下来我们还将给敌人本身添加点音效。

在Unity编辑器的Project视图中找到_Prefabs中的zombieEnemy预设体,在Inspector视图中点击Add Component,并添加一个新的Audio Source组件。

将AudioClip属性设置为Project视图中Assets/Sounds/BloodSFX/Splat中的bloodfx1音效(或者其它你个人喜好的),同时注意取消勾选Play On Awake。

然后打开Enemy.cs这个文件,并更改其中的代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {

        //设置敌人的生命值
        public float health = 30f;
        //1.定义敌人受伤的音效
        AudioSource bloodSound;
        // Use this for initialization
        void Start () {
                //2.获取音源
                AudioSource[] audios = GetComponents<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //3.设置音效
                bloodSound = audios[1];
        }
        //敌人受到伤害后的处理
        public void TakeDamage(float damage){
                //4.播放音效
                bloodSound.Play();   
                //敌人生命值减少特定的数值
                health -= damage;
                //输出敌人生命值
                print (healthhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //当敌人生命值变为0的时候,就死亡
                if (health <= 0) {
                
                //Enemy Die
                        Die (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }
        }
        //敌人死亡
        void Die(){
                //5.在1秒钟后销毁敌人对象
                Destroy (gameObject, 1fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }        
}

这里所添加的几行代码跟刚才的完全类似,其作用如下:

1.定义敌人受伤的音效

2.获取音源组件的数组

3.设置敌人受伤的音效

4.播放音效。

回到Unity编辑器,点击工具栏上的Play按钮,就可以预览游戏效果了。

可以看到从视觉上没有什么变化,只是增加了跟敌人相关的特定音效。

首先我们要对场景做一些小的调整。

在Unity编辑器的Hierarchy视图中找到HItCubeParent下的ruined_house子对象,然后在Inspector视图中右键单击Unity AR Hit Test Example(Script)组件,选择Edit Script,并对其中的代码进行编辑。

当前的代码有一个小小的问题,特别是在Update方法中。当前的脚本会检测用户在屏幕上的触碰操作,并将其转换成一个新的坐标。但问题在于,当触碰游戏的UI按钮,比如射击按钮时,也会执行类似的操作,并将整个地图移动到新的坐标。这显然不是我们希望看到的。因此我们将更改UnityARHitTestExample.cs的代码如下:

using System;
using System.Collections.Generic;
//1.导入UI事件系统
using UnityEngine.EventSystems;
namespace UnityEngine.XR.iOS
{
        public class UnityARHitTestExample : MonoBehaviour
        {
                public Transform m_HitTransform;

        bool HitTestWithResultType (ARPoint point, ARHitTestResultType resultTypes)
        {
            List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, resultTypeshttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>            if (hitResults.Count > 0) {
                foreach (var hitResult in hitResults) {
                    Debug.Log ("Got hit!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                    m_HitTransform.position = UnityARMatrixOps.GetPosition (hitResult.worldTransformhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                    m_HitTransform.rotation = UnityARMatrixOps.GetRotation (hitResult.worldTransformhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                    Debug.Log (string.Format ("x:{ 0:0.######} y:{ 1:0.######} z:{ 2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                    return true;
                }
            }
            return false;
        }
                // Update is called once per frame
                void Update () {
                        if (Input.touchCount > 0 && m_HitTransform != null)
                        {
                                var touch = Input.GetTouch(0https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //2.对此行代码进行调整,添加另外一个逻辑判断条件,也即UI系统没有进行交互
                                if ((touch.phase == TouchPhase.Began || touch.phase == TouchPhase.Moved) && !EventSystem.current.IsPointerOverGameObject(0))
                                {
                                        var screenPosition = Camera.main.ScreenToViewportPoint(touch.positionhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        ARPoint point = new ARPoint {
                                                x = screenPosition.x,
                                                y = screenPosition.y
                                        } ;
                    // prioritize reults types
                    ARHitTestResultType[] resultTypes = {
                        ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent, 
                        // if you want to use infinite planes use this:
                        //ARHitTestResultType.ARHitTestResultTypeExistingPlane,
                        ARHitTestResultType.ARHitTestResultTypeHorizontalPlane, 
                        ARHitTestResultType.ARHitTestResultTypeFeaturePoint
                    };                                         
                    foreach (ARHitTestResultType resultType in resultTypes)
                    {
                        if (HitTestWithResultType (point, resultType))
                        {
                            return;
                        }
                    }
                                }
                        }
                }        
        }
}

以上仅作了两处调整,按照数字编号解释一下:

1.导入了Unity的UI事件交互系统的

2.添加了一个判断条件,仅当UI事件交互系统没有响应时才会执行下面的操作。

通过这两处调整,就可以规避我们刚刚提到的问题。

回到Unity编辑器,接下来我们还希望实现当用户触碰Start Game按钮开启游戏后,可以删除UnityARHitTestExample脚本。因为当游戏正式开启后,我们不希望再实时更改ruined_house对象的位置,而希望它固定在某个位置。此外,在开启游戏后还需要做的就是禁用Start Game按钮,直到游戏重新开始。

为此,在Hierarchy视图中选择HItCubeParent下的ruined_house子对象,然后在Inspector视图中点击Add Component,创建一个新的脚本文件,将其命名为StartGame,并在MonoDevelop中将其打开。

更改其中的代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.导入UI相关的命名空间
using UnityEngine.UI;
//2.导入ARKit相关的命名空间
using UnityEngine.XR.iOS;
public class StartGame : MonoBehaviour {
        //3.开始按钮
        public Button startBtn;
        //4.创建到UnityARHitTestExample的引用
        private UnityARHitTestExample unityARHitTestExample;
        //5.crosshair
        public Image crosshair;
        // Use this for initialization
        void Start () {
                //6.添加开始游戏按钮的事件响应机制
                startBtn.onClick.AddListener (StartNewGamehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }        
        void StartNewGame(){
                //7.获取到UnityARHitTestExample的引用
                unityARHitTestExample = GetComponent<UnityARHitTestExample> (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //8.删除到UnityARHitTestExample的引用
                Destroy (unityARHitTestExamplehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //9.禁用开始游戏按钮
                startBtn.gameObject.SetActive (falsehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //10.启用辅助瞄准
                crosshair.gameObject.SetActive (truehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }
}

这里按照注释行的数字编号顺序来简单解释一下:

1.这里导入了和UI相关的命名空间

2.导入了和ARKit相关的命名空间

3.创建到开始按钮的引用

4.创建到UnityARHitTestExample的引用 5.创建到crosshair准星的引用

6.添加开始游戏按钮的事件响应机制 7.获取到UnityARHitTestExample的引用 8.删除到UnityARHitTestExample的引用 9.禁用开始游戏按钮 10.启用辅助瞄准

接下来回到Unity编辑器,做一些简单的设置。

首先在Hierarchy视图中的Canvas下找到Crosshair,并将其禁用,因为我们会在代码中将其启用。

接下来在Hierarchy视图中找到HitCubeParent下的ruined_house子对象,然后在Start Game(Script)组件处设置Start Btn和Crosshair如下。

点击Unity编辑器工具栏上的Play按钮,然后切换到Game视图。当我们按下Start Game按钮的时候,可以看到Inspector视图处的UnityARHitTestExample脚本对象已经没有了,Start Game按钮也消失了,而准星则出现在界面中间。如下图所示。

接下来我们要在iPhone设备上进行测试。

为此,从Unity菜单栏中选择F

ile-Build Settings,

然后点击Player Settings,

首先更改设备朝向,在Resolution and Presentation部分的Default Orientation属性处,从下拉列表中选择Landscape Left,如图所示。

然后点击Build and Run按钮编译运行。

此时可能会提示你之前有同名的项目,选择Replace即可。

此时系统会自动在Xcode中打开生成的项目,当然首先会看到几个错误提示。

接下来要在Xcode的General- Signing中更改Team信息。

之前提到过,你需要注册一个苹果开发者账号,才能顺利进行下面的工作。

如果还没有注册过,需要在苹果官网注册:https://developer.apple.com/

然后确保iPhone设备连接到电脑上,点击Xcode工具栏上的编译运行按钮(向右的三角)即可。

可以看到,在我们触碰Start Game之前,ruined_house模型在空间中的位置是会发生变化的。当触碰Start Game按钮后,房屋模型的空间位置就不变了,然后就会看到僵尸敌人从各个方向来袭。触碰屏幕右下角

可以开始攻击。我们可以借助准星的力量进行瞄准。当敌人受到攻击时,会受到一个向后的冲力,同时会播放音效和粒子特效。在遭到敌人攻击时,也会有对应的音效和屏幕特效。


 

需要特别说明的是,在手机上实际测试游戏的时候,最好是在户外。如果是在室内测试,那么建议将房屋模型和敌人的Transform比例进行相应的缩写。

实际测试之后,我们发现有一些小的地方可以继续优化。

首先,我们不太希望看到房屋底部的阴影,因此需要把HitCubeParent下的shadowPlanePrefab的位置稍微往上提一下。

此外,现在房屋的阴影有点太生硬了,因此需要更改阴影的力度。

在Hierarchy视图中选择Directional Light,然后在Inspector视图中将Light组建的Shadow Type下的Realtime Shadows的Strength属性调整到0.55左右。

最后在Hierarchy视图中找到CameraParent下的Main Camera的子对象weapon1,然后在Inspector视图中将Shoot Enemy(Script)组建中的Force Add属性值降低到150,从而让敌人受到的冲击力显得更为自然。

OK,现在我们的调整就基本到位了。

在本课的内容中,我们将继续对ShootEnemy.cs脚本中的代码进行一些优化和完善工作。首先给武器添加一些弹药。

在Unity编辑器的Hierarchy视图中找到Canvas对象,然后右键单击,选择UI-Image,添加一个新的

Image控件,将其命名为Weapon1。在Inspector视图中将其Source Image设置为UIMask。然后将其Rect Transform中的Width 和Height属性值更改为280和170,如图所示。

为了方便我们调整UI元素的视觉效果,接下来选择Canvas中的btn_StartGame控件,并将其先禁用。

然后回到刚才添加的Weapon1控件,在Hierarchy视图中选中该控件,右键单击添加一个Image子对象,将其命名为weapon1Image。设置其Source Image为LT Handgun1。然后将Rect Transform中的Width和Height更改为160和130(主观调整)。

然后回到刚才添加的Weapon1控件,在Hierarchy视图中选中该控件,右键单击添加一个Text子对象,将其命名为ammoText。在Inspector视图中将其默认的文本内容更改为20。

更改Font为Jupiter,调整字体大小到合适的程度,比如55.

更改Color的R,G,B,A为255,255,255,180。也就是不完全透明的白色。

此外还要调整Width和Height到160和100,并调整Pos X和Pos Y,使其处于手枪图片的右下。

调整完成后在Game视图中的预览效果如下。

然后在Hierarchy视图中右键单击ammoText,选择Duplicate命令从而复制一个新的文本对象,并将其更名为ammoText2。

在Inspector视图中将其Text属性设置为/100。

更改Color为纯白色,然后调整其位置和字体大小,直到在Game视图中看到类似下面的效果。

需要说明的是,对UI元素的视觉效果调整是非常主观的,因此最好有美术设计人员的协助,或是培养自己的审美。

设置完成后,在Hierarchy视图中选择Weapon1对象,然后在Rect Trans

form中点击Anchors上面的小图标,注意要按住Alt键,然后选择Top Right的标志,如下图所示。

适当调整Pos X和Pos Y的数值,使其在Game视图中的显示类似下图:

然后我们再来添加其它武器。

在Hierarchy视图中选中Canvas对象,右键单击,选择UI-Image,添加一个新的Image对象,并将其命名为Grenade,并将其Source Image属性设置为UIMask。

调整Rect Transform中的Width和Height属性为150和150.

接着右键单击Grenade,选择UI-Image,为其添加一个新的Image子对象,设置其Source Image属性为LT Handgrenade 8。

选中Grenade对象,在Inspector视图中的Rect Transform中点击Anchors上面的小图标,注意要按住Alt键,然后选择Top Right的标志。这个操作和刚才调整手枪的UI元素位置类似。

然后调整手榴弹的图片位置到合适的地方。

在Hierarchy视图中选择Grenade对象,右键单击,选择Duplicate,并将其命名为HealthKit。

并将其下面Image子对象的Source Image设置为LT Healthpack.

然后调整其位置,直到看到类似下图的效果。

此时Hierarchy视图中Canvas下面新添加的几个对象的层级关系类似下图:

最后重新启用btn_StartGame对象即可。

在上边的内容中,我添加了弹药的UI元素。在这一次的内容中,我将添加相应的脚本。
打开Unity编辑器,在Hierarchy视图中选中CameraParent下面Main Camera的子对象weapon1,然后从Inspector视图中打开ShootEnemy脚本文件。
在Start方法的前面添加以下代码:
        //创建到弹药UI元素的引用
        public Text ammo1Text;
        public Text ammo2Text;
        public int ammo1;
        public int ammo2;
以上我们创建了到弹药UI元素的引用,以及弹药的具体数量。
然后在Hierarchy视图中找到Canvas下的Weapon1的子对象ammoText和ammoText2,并将其分别拖动到Inspector视图中Shoot Enemy组件的Ammo1 Text和Ammo2 Text。如图所示:

 
接下来回到ShootEnemy脚本,在Start方法的最后添加以下代码:
                //设置弹药数量的初始值
                ammo1 = 20;
                ammo2 = 100;
然后在OnShoot方法的最开始添加以下代码:
                //弹药数量减少
                ammo1 -= 1;
                string ammo1String = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                ammo1Text.text = ammo1String;
                ammo2 -= 1;
                string ammo2String = (ammo2).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                ammo2Text.text = ammo2String;
回到Unity主编辑器,点击工具栏上的Play按钮预览游戏效果。

 
此时脚本起作用了,但是似乎少了点东西。是的,少了个/符号。
回到ShootEnemy.cs脚本文件,修改刚才的代码如下:
                //弹药数量减少
                ammo1 -= 1;
                string ammo1String = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                ammo1Text.text = ammo1String;
                ammo2 -= 1;
                string ammo2String = (ammo2).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                ammo2Text.text = "/"+ ammo2String;
接下来测试,发现弹药数量竟然可以减少到负数,显然是不科学的。

回到ShootEnemy.cs脚本,修改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//import namespace
using UnityEngine.UI;
public class ShootEnemy : MonoBehaviour {
        //创建到Button对象的引用
        public Button shootBtn;
        //创建到主摄像机的引用
        public Camera fpsCam;
        //设置敌人每次受到伤害的数值
        public float damage = 10f;
        //敌人受伤的粒子特效
        public GameObject bloodEffect;
        //攻击的粒子特效
        public GameObject shootingEffect;
        //添加的攻击力度
        public int forceAdd = 300;
        //定义两个音源对象
        AudioSource shootSound;
        AudioSource reloadSound;
        //创建到弹药UI元素的引用
        public Text ammo1Text;
        public Text ammo2Text;
        public int ammo1;
        public int ammo2;
        private bool ammoIsEmpty;
        // Use this for initialization
        void Start () {
                //Debug.Log ("Activated!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //添加按钮的响应事件
                shootBtn.onClick.AddListener (OnShoothttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //获取音源组件
                AudioSource[] audios = GetComponents<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //设置音源
                shootSound = audios [0];
                reloadSound = audios [1];
                //设置弹药数量的初始值
                ammo1 = 20;
                ammo2 = 100;
        }
        public void OnShoot(){

                //仅在ammoIsEmpty为真时才可执行逻辑判断中的操作
                if (!ammoIsEmpty) {
                
                        if (ammo1 == 1) {

                                ammo1 = 21;
                        }
                        //弹药数量减少
                        ammo1 -= 1;
                        string ammo1String = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo1Text.text = ammo1String;
                        ammo2 -= 1;
                        string ammo2String = (ammo2).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo2Text.text = "/"+ ammo2String;
                        //如果弹药总数量为0,则设置ammoIsEmpty为true
                        if (ammo2 == 0) {
                                ammoIsEmpty = true;
                                ammo1 = 0;
                                string ammoTempString = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                ammo1Text.text = ammoTempString;
                        }


                        //播放音效
                        shootSound.Play(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        Debug.Log ("shooting!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //定义一个RaycastHit类型变量,用于保存检测信息
                        RaycastHit hit;
                        //判断是否检测到命中敌人
                        if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit)) {
                                //获取所受攻击的敌人
                                Enemy target = hit.transform.GetComponent<Enemy>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //destroy enemy
                                if (target != null) {
                                        //instantiate blood effect
                                        target.TakeDamage (damagehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //创建敌人受伤的粒子特效
                                        GameObject bloodBurst = Instantiate (bloodEffect, hit.point, Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (bloodBurst, 0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }  else {
                                        //load shooting effect
                                        //如果没有击中敌人,则创建攻击时的粒子特效
                                        GameObject shootingGo =        Instantiate (shootingEffect, hit.point, Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (shootingGo,0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }
                                //攻击敌人时添加一个额外的冲击力
                                if (hit.rigidbody != null) {
                                        hit.rigidbody.AddForce (-hit.normal * forceAddhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }
                                //输出所命中的对象名称
                                Debug.Log (hit.transform.namehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        }
                }
        }
}
在以上的代码中,我们主要是添加了一个逻辑判断,使用布尔变量isAmmoEmpty来判断弹药总量是否没有减少到0.仅当有弹药时才会执行后面的操作。
修改完成后回到Unity主编辑器,点击Play按钮预览游戏效果,测试一下,直到弹药数量减少为0,看看是否一切正常。

 

我将在屏幕中添加武器。
为此准备了相关的资源,请从此处下载:
链接:https://pan.baidu.com/s/1c2ezd7a  密码:o2g6将下载的文件解压缩,并拖动到Unity编辑器的Project视图中的Assets文件夹中。在继续之前,要清理下我们的Assets文件组织,把之前创建的几个脚本文件全部拖动到_Scripts中。
打开WeaponPack文件夹,把其中的Pistol预设体拖动到Hierarchy视图中,使之成为CameraParent-Main Camera- weapon1的子对象,如图所示。

 

然后在Inspector视图中设置Pistol的Transform属性,如下图所示。

 

当然,以上的具体参数其实比较主观,只要最后在Game视图中显示的效果类似下图即可:

 
然后在Hierarchy视图中展开刚刚添加的Pistol预设体,直到找到最里层的polySurface8。

 
然后在Inspector视图中更改其材质属性,将Main Maps中的Albedo属性设置为hands

 
接下来在Hierarchy视图中继续在Pistol预设体的子对象中寻找到GunBody,如图

 
然后在Inspector视图中更改其材质属性,将Main Maps中的Albedo属性设置为m1911

 
此时Game视图的效果如图:

 
接下来我们要添加开火的效果。
在Hierarchy视图中,给Pistol对象添加一个子对象,并将其命名为MuzzleFlash。

 
然后从Project视图中找到Standard Assets-ParticleSystems-Prefabs,将Flare拖动为MuzzleFlash的子对象。
接下来记得在Inspector视图中重置MuzzleFlash和Flare的Transform位置信息。
在Hierarchy视图中,删除Flare对象的Sparks子对象。
此时在Scene视图中注意观察手枪的相关显示,为方便起见,甚至可以暂时关闭其它场景对象。
然后使用移动工具将MuzzleFlash调整到枪口附近的位置。

 

 
然后在Hierarchy视图中选中Flare,在Inspector视图中更改相关的属性:
1.将Duration调整为1.

 
注意,将其子对象Smoke的Duration也调整为1。
2.展开Emission属性
将Rate over Time更改为0,并增加一个Burst属性,设置如下图所示。

 
对Flare的子对象Smoke进行相似的设置。不同之处只是在Bursts处是从三个中删除下面的两个。
接下来让我们替换开火的效果。
在Project视图中从WeaponPack文件夹中找到muzzleFlash这个空的材质,将其纹理设置为MuzzleFlash,如图所示。

 
然后将muzzleFlash材质拖动到Hierarchy视图中的Flare对象上。
选中Flare的子对象Smoke,并在Transform中稍微调整下位置。并取消勾选Looping。


对Flare对象也要取消勾选Particle System中的Looping属性。

 
再次提醒,这里的很多参数设置是比较主观的,你可以调整到自己满意为止~

在上边的内容中,我添加了武器开火的粒子特效。这次的内容相对比较简单,我将给所添加的粒子特效设置对应的脚本。
打开Unity,在Project视图中找到\Assets\_Scripts\ShootEnemy.cs脚本,并在MonoDevelop中将其打开。
更改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//import namespace
using UnityEngine.UI;

public class ShootEnemy : MonoBehaviour {
        //创建到Button对象的引用
        public Button shootBtn;
        //创建到主摄像机的引用
        public Camera fpsCam;
        //设置敌人每次受到伤害的数值
        public float damage = 10f;
        //敌人受伤的粒子特效
        public GameObject bloodEffect;
        //攻击的粒子特效
        public GameObject shootingEffect;
        //添加的攻击力度
        public int forceAdd = 300;
        //定义两个音源对象
        AudioSource shootSound;
        AudioSource reloadSound;
        //创建到弹药UI元素的引用
        public Text ammo1Text;
        public Text ammo2Text;
        public int ammo1;
        public int ammo2;

        //判断弹药是否已空
        private bool ammoIsEmpty;
        //1.创建到开火粒子系统的引用
        public ParticleSystem muzzleFlash;
        // Use this for initialization
        void Start () {
                //Debug.Log ("Activated!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //添加按钮的响应事件
                shootBtn.onClick.AddListener (OnShoothttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //获取音源组件
                AudioSource[] audios = GetComponents<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //设置音源
                shootSound = audios [0];
                reloadSound = audios [1];
                //设置弹药数量的初始值
                ammo1 = 20;
                ammo2 = 100;
        }
        public void OnShoot(){
                //仅在ammoIsEmpty为真时才可执行逻辑判断中的操作
                if (!ammoIsEmpty) {
                
                        if (ammo1 == 1) {
                                ammo1 = 21;
                        }
                        //弹药数量减少
                        ammo1 -= 1;
                        string ammo1String = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo1Text.text = ammo1String;
                        ammo2 -= 1;
                        string ammo2String = (ammo2).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo2Text.text = "/"+ ammo2String;
                        //如果弹药总数量为0,则设置ammoIsEmpty为true
                        if (ammo2 == 0) {
                                ammoIsEmpty = true;
                                ammo1 = 0;
                                string ammoTempString = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                ammo1Text.text = ammoTempString;
                        }
                        //播放音效
                        shootSound.Play(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        Debug.Log ("shooting!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //定义一个RaycastHit类型变量,用于保存检测信息
                        RaycastHit hit;
                        //判断是否检测到命中敌人
                        if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit)) {
                                //获取所受攻击的敌人
                                Enemy target = hit.transform.GetComponent<Enemy>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //destroy enemy
                                if (target != null) {
                                        //instantiate blood effect
                                        target.TakeDamage (damagehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //创建敌人受伤的粒子特效
                                        GameObject bloodBurst = Instantiate (bloodEffect, hit.point, Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (bloodBurst, 0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }  else {
                                        //load shooting effect
                                        //如果没有击中敌人,则创建攻击时的粒子特效
                                        GameObject shootingGo =        Instantiate (shootingEffect, hit.point, Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (shootingGo,0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>
                                }
                                //攻击敌人时添加一个额外的冲击力
                                if (hit.rigidbody != null) {
                                        hit.rigidbody.AddForce (-hit.normal * forceAddhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }
                                //输出所命中的对象名称
                                Debug.Log (hit.transform.namehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        }
                        //2.播放开火的粒子特效
                        muzzleFlash.Play (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }
        }
}
以上我们只添加了两行代码,按照注释行的数字编号来解释一下:
1.创建了到开火粒子系统的引用
2.在满足逻辑条件的前提下播放开火的粒子特效。
接下来回到Unity编辑器,在Hierarchy视图中选中CameraParent下Main Camera的子对象weapon1,然后在Inspector视图中,将Shoot Enemy组件中的Muzzle Flash属性设置为Flare,如图所示。

 
点击工具栏上的Play按钮,预览一下游戏的运行效果。
感觉开火的颜色似乎有点不满意,让我们来调整一下。
在Project视图中找到WeaponPack文件夹中的muzzleFlash材质,然后在Inspector视图中点击Tint Color旁边的色彩拾取器,并适当调整一下其中的色彩。

 
大概调整到类似上图的程度就好了。当然,具体如何调整其实是比较主观的。调整完成后,点击Unity编辑器工具栏上的Play按钮,观察下效果。

 

我们将给武器添加idle、开火和装弹时的动画,让画面显得更为真实。
首先我们要手动创建一个idle动画。在Hierarchy视图中选中Pistol对象,

对于idle动画,我们只需要更改Transform中的Position Y即可。
 
在Unity编辑器的菜单栏中点击Window-,打开动画编辑器。

点击Create以创建一个新的Animation,并将其命名为idleAnimation。
 
需要注意的是,在点击Create之前,需要在Hierarchy视图中选中Pistol对象。

接下来我们只需要设置几个关键帧即可。
首先点击Animation面板左侧的Add Property按钮,并选择Position,如图所示。
 
添加完成后,展开Pistol:Position,如下图所示:
 
然后点击Animation面板左上的红色录制按钮开始录制动画。
在面板中间的时间轴的大概0.35秒处点击,然后点击左侧的Add Keyframe按钮添加一个关键帧,

然后将Position.y的数值更改为-0.176,如图所示。
 
最后将时间轴上另外一个关键帧拖动到1.10秒左右的位置,然后将Position.y的数值更改为-0.179,如图所示。
 
然后点击Animation面板工具栏上的播放按钮预览动画效果。

如果觉得还不错,那么可以点击红色录制按钮停止录制。
好了,这样我们的idleAnimation动画就录制完毕了。
接下来在Inspector视图中打开Pistol的Animator Controller,

此时在Animator视图中只有一个idleAnimation状态,如图。
 
此时点击工具栏上的Play按钮,可以看到动画在播放中。
但是如果触碰开火按钮,会发现仍然播放的是idle动画。
 
因此接下来我们将添加开火的动画状态和重新装弹的动画状态。
在Unity编辑器的Project视图中找到WeaponPack中的Pistol预设体并将其展开,

发现里面已经提供了Fire动画和Reload动画,如图所示。
 
将Fire动画和Reload动画拖动到Animator视图中。
右键单击Fire动画,选择Make Transition,并拖出一条线到idleAnimation.
 
接下来选中idleAnimation,使用同样的方式拖一条线到Reload动画,

不过此时我们还要从Reload动画托一条线到idleAnimation。
完成后的连线如下图所示。
 
好了,Animator的设置基本上完成了,待会儿我们会回来添加一些其它的设置。
接下来进入代码时间,在Project视图中的Assets-_Scripts中找到并打开ShootEnemy.cs脚本文件。
在Start方法的前面添加以下代码:
        //创建到手机对象的引用
        public GameObject pistol;
然后回到Unity编辑器,选中CameraParent-Main Camera- weapon1对象,

然后在Inspector视图中设置Shoot Enemy组件的Pistol属性为Pistol对象。
 
然后回到ShootEnemy.cs脚本,在OnShoot方法的最后,紧接着muzzleFlash.Play();添加一行代码如下:
        //播放开火动画
                        pistol.GetComponent<Animator>().Play("Fire"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>接下来回到Animator视图,选中从idleAnimation到Reload动画状态之间的连线,

然后取消勾选Has Exit Time,如图所示。
 
然后点击Unity编辑器工具栏上的Play按钮,来预览下游戏效果:
开火动画起作用了,但是似乎稍微有点慢。
回到Animator视图,选中Fire动画,将Inspector视图中的Speed更改为2.
再次预览,发现效果好一些了。
接下来让我们添加重新装弹的Reload动画。
首先回到Animator视图,在左侧切换到Parameters选项卡,点击+号,

选择Trigger类型,将其命名为reload。
然后选中从idleAnimation到Reload的连线,在Inspector视图中的Conditions处点击+号,

从而创建一个新的触发条件reload,如图所示。
 
Trigger类型的参数触发条件意味着,仅当reload条件满足时,才会播放相关的动画。
 
接下来回到ShootEnemy.cs,并更改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//import namespace
using UnityEngine.UI;
public class ShootEnemy : MonoBehaviour {

        //创建到Button对象的引用
        public Button shootBtn;
        //创建到主摄像机的引用
        public Camera fpsCam;
        //设置敌人每次受到伤害的数值
        public float damage = 10f;
        //敌人受伤的粒子特效
        public GameObject bloodEffect;
        //攻击的粒子特效
        public GameObject shootingEffect;
        //添加的攻击力度
        public int forceAdd = 300;
        //定义两个音源对象
        AudioSource shootSound;
        AudioSource reloadSound;
        //创建到弹药UI元素的引用
        public Text ammo1Text;
        public Text ammo2Text;
        public int ammo1;
        public int ammo2;
        //判断弹药是否已空
        private bool ammoIsEmpty;

        //创建到开火粒子系统的引用
        public ParticleSystem muzzleFlash;
        //创建到手机对象的引用
        public GameObject pistol;
        //1.重新装弹检查判断
        private bool reloadCheck;
        // Use this for initialization
        void Start () {
                //Debug.Log ("Activated!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //添加按钮的响应事件
                shootBtn.onClick.AddListener (OnShoothttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //获取音源组件
                AudioSource[] audios = GetComponents<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //设置音源
                shootSound = audios [0];
                reloadSound = audios [1];
                //设置弹药数量的初始值
                ammo1 = 20;
                ammo2 = 100;
                //2.设置重新装弹检查的默认值为true

                reloadCheck = true;
        }
        //3.Coroutine方法
        IEnumerator WaitForReload(){
                yield return new WaitForSeconds (3fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                reloadCheck = true;
        }
        public void OnShoot(){
                //4.仅在ammoIsEmpty和reloadCheck同时为真时才可执行逻辑判断中的操作
                if (!ammoIsEmpty && reloadCheck) {              
                        if (ammo1 == 1) {
                                ammo1 = 21;
                                //5.触发重新装弹动画
                                pistol.GetComponent<Animator> ().SetTrigger ("reload"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                reloadCheck = false;
                                //6.等待3秒
                                StartCoroutine (WaitForReload ()https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //7.播放重新装弹的音效
                                reloadSound.Play (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        }



                        //弹药数量减少
                        ammo1 -= 1;
                        string ammo1String = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo1Text.text = ammo1String;
                        ammo2 -= 1;
                        string ammo2String = (ammo2).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        ammo2Text.text = "/"+ ammo2String;
                        //如果弹药总数量为0,则设置ammoIsEmpty为true
                        if (ammo2 == 0) {
                                ammoIsEmpty = true;
                                ammo1 = 0;
                                string ammoTempString = (ammo1).ToString (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                ammo1Text.text = ammoTempString;
                        }
                        //播放音效
                        shootSound.Play(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        Debug.Log ("shooting!"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //定义一个RaycastHit类型变量,用于保存检测信息
                        RaycastHit hit;
                        //判断是否检测到命中敌人
                        if (Physics.Raycast (fpsCam.transform.position, fpsCam.transform.forward, out hit)) {
                                //获取所受攻击的敌人
                                Enemy target = hit.transform.GetComponent<Enemy>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                //destroy enemy
                                if (target != null) {
                                        //instantiate blood effect
                                        target.TakeDamage (damagehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //创建敌人受伤的粒子特效
                                        GameObject bloodBurst = Instantiate (bloodEffect, hit.point,

 Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (bloodBurst, 0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }  else {
                                        //load shooting effect
                                        //如果没有击中敌人,则创建攻击时的粒子特效
                                        GameObject shootingGo =        Instantiate (shootingEffect, hit.point,

Quaternion.LookRotation (hit.normal)https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                        //0.2秒后销毁粒子特效
                                        Destroy (shootingGo,0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }
                                //攻击敌人时添加一个额外的冲击力

                                if (hit.rigidbody != null) {
                                        hit.rigidbody.AddForce (-hit.normal * forceAddhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                                }
                                //输出所命中的对象名称
                                Debug.Log (hit.transform.namehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        }
                        //播放开火的粒子特效
                        muzzleFlash.Play (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //播放开火动画
                        pistol.GetComponent<Animator>().Play("Fire"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }
        }
}
所添加的新代码都已经添加了注释,这里就不再一一赘述了。
在最后测试之前,我们还有一处小小的修改,

在Hierarchy视图中找到CameraParent-Main Camera-weapon1-MuzzleFlash-Flare,

然后在Inspector视图中取消勾选Play On Awake.
 
全部完成后在编辑器上点击工具栏上的Play按钮,预览下游戏效果。
好了,本课的内容到此结束,我们下一课再见。

 

 

我们将让玩家可以在地面上捡起武器,然后开火。而不是一开始就已经一人一枪横扫千军了。
为此,首先我们要从Asset Store中下载一个武器资源。
在Asset Store中搜索pistol,然后选择FREE ONLY,从搜索结果中选择下图中的这个资源。
 
把下载后的资源拖入到Arts文件夹。打开[PBR]Makarov文件夹,然后将其中的预设体拖动到Hierarchy视图中,使其成为ruined_house的子对象,并更名为myWeapon。
接下来在Inspector视图中调整Transform中的相关属性如下(仅供参考):

 
接下来点击Inspector视图中的Add Component,给pistol对象添加一个Box Collider组件,然后设置碰撞体的大小,如图所示。
再次强调,这些数字是相对比较主观的,你觉得合适就行。
 
除了Box Collider,我们还需要继续给pickupWeapon对象添加一个Rigidbody组件,因为我们希望武器受到重力的影响。这样当游戏开始的时候,武器就会自动掉落到地面。
好了,接下来我们需要创建脚本,来处理拾取武器的操作。
在Hierarhcy视图中选中CameraParent-Main Camera,然后点击Inspector视图中的Add Component按钮,给它添加一个新的脚本组件,并将其命名为PickupWeapon。
在MonoDevelop中将其打开并更改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickupWeapon : MonoBehaviour {
        // Use this for initialization
        void Start () {
        }
        //1.开始碰撞
        void OnCollisionEnter(Collision col){
                if (col.gameObject.name == "myWeapon") {                
                        Debug.Log ("Enter test");              
                }
        }
        //2.碰撞结束
        void OnCollisionExit(Collision col){        
                if(col.gameObject.name == "myWeapon"{
                        Debug.Log("Exit test"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }
        }
}
这里我们只是添加了两个方法,而这两个方法在之前的课程中有提过,分别是用于处理碰撞开始和碰撞结束的事件。这里我们暂时只是在Console中输出相应的信息。

好了,接下来回到Unity编辑器,在Hierarchy视图中选中CameraParent-Main Camera,然后在Inspector视图中点击Add Component,添加一个Rigidbody组件,并取消勾选Use Gravity。此外在Rigidbody组件的Constraints属性处勾选所有的选项。
 
接下来可以测试一下,点击Unity编辑器上的Play按钮进行测试。
当我们在场景中拖动主摄像机到地面上的武器位置附近时,可以看到武器被撞飞了,同时在Console中输出了相应的提示。
 
不过看起来武器的重量太轻了点,让我们给它增加点重量。
在Hierarchy视图中选中HitCubeParent-ruined_house-myWeapon,然后在Inspector视图中将其Rigidbody组件的Mass属性设置为40.

 
再次运行测试,看起来差不多了~
然后从这里下载本章所需的资源素材:
链接:百度网盘-链接不存在  密码:s6pz
将其中的文件解压缩,并将hand-sprite.png资源文件拖动到Unity编辑器的Project视图的Arts文件夹中。
选中该文件,然后在Inspector视图中点击Texture Type旁的下拉列表,然后选择Sprite(2D and UI)

 
然后别忘了点击右下角的Apply按钮
接下来在Hierarchy视图中选中Canvas,添加一个新的Button UI元素,将其命名为btn_Pickup,删除该按钮的文本子对象。
然后在Inspector视图中将Image组建下的Source Image属性设置为hand-sprite。
更改Rect Transform属性中的Width 和Height为150和150.
最后在默认情况下对其禁用,如图所示。
 

好了,现在可以回到我们的PickupWeapon.cs脚本,修改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PickupWeapon : MonoBehaviour {
        //1.创建到拾取武器按钮的引用
        public GameObject pickupBtn;
        // Use this for initialization
        void Start () {
        }
        //1.开始碰撞
        void OnCollisionEnter(Collision col){
                if (col.gameObject.name == "myWeapon") {              
//                        Debug.Log ("Enter test"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //2.启用拾取武器按钮
                        pickupBtn.gameObject.SetActive (true);              
                }
        }
        //2.碰撞结束
        void OnCollisionExit(Collision col){
                if(col.gameObject.name == "myWeapon"){

//                        Debug.Log("Exit test"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //3.禁用拾取武器按钮
                        pickupBtn.gameObject.SetActive (falsehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }       
        }
}
在以上代码中,按照注释行的数字编号来简单解释一下:
1.创建了到拾取武器按钮的引用。
2.当碰撞发生时,启用拾取武器按钮
3.当碰撞结束时,禁用拾取武器按钮。
回到Unity编辑器,在Hierarchy视图中选中CameraParent-Main Camera,然后在Inspector视图中将Pickup Weapon组件的Pickup Btn属性更改为btn_Pickup,如图所示。
 
好了,接下来我们完成一个之前别遗忘的工作,给准星创建一个引用。
回到PickupWeapon.cs,更改代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PickupWeapon : MonoBehaviour {
        //创建到拾取武器按钮的引用
        public GameObject pickupBtn;
        //1.创建到准星的引用
        public GameObject crossHair;
        // Use this for initialization
        void Start () {
        }
        //开始碰撞
        void OnCollisionEnter(Collision col){
                if (col.gameObject.name == "myWeapon") {                
//                        Debug.Log ("Enter test"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //启用拾取武器按钮
                        pickupBtn.gameObject.SetActive (truehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //2.禁用准星
                        crossHair.gameObject.SetActive(false);                
                }
        }
        //碰撞结束
        void OnCollisionExit(Collision col){
        
                if(col.gameObject.name == "myWeapon"){
//                        Debug.Log("Exit test"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //禁用拾取武器按钮
                        pickupBtn.gameObject.SetActive (falsehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //3.启用准星
                        crossHair.gameObject.SetActive(truehttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                }        
        }
}
按照注释行数字编号简单解释下:
1.创建了到准星UI控件对象的引用
2.当碰撞发生时,禁用准星
3.当碰撞结束时,启用准星
然后回到Unity编辑器,在Hierarchy视图中选中CameraParent-Main Camera,然后在Inspector视图中将Pickup Weapon组件的Cross Hair属性设置为Crosshiar UI元素,如图所示。
 
接下来点击工具栏上的Play按钮,预览下游戏效果。
到了这一步的,基本的操作已经完成了,让我们再做一些完善工作。
在Hierarchy视图中选中Canvas,然后在Inspector视图中点击Add Component,给其添加一个新的脚本组件,将其命名为WeaponPickup。在MonoDevelop中将其打开,并更改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//1.导入UI相关的命名空间
using UnityEngine.UI;
public class WeaponPickup : MonoBehaviour {
        //2.创建到拾取武器按钮的引用
        public Button pickupBtn;
        //3.创建到武器的引用
        public GameObject weapon1;

        // Use this for initialization
        void Start () {
        }
        // Update is called once per frame
        void Update () {
        }
}
以上代码比较简单,这里就不再赘述了,大家直接看注释应该就可以明白。
回到Unity编辑器,首先在Hierarchy视图中找到CameraParent-Main Camera-weapon1,然后在默认状态下将其禁用。
然后在Hierarchy视图中选中Canvas,设置Weapon Pick Up组件的属性如图:
 
接下来回到WeaponPickup.cs脚本,更改其中的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//导入UI相关的命名空间
using UnityEngine.UI;
public class WeaponPickup : MonoBehaviour {
        //创建到拾取武器按钮的引用
        public Button pickupBtn;
        //创建到武器的引用
        public GameObject weapon1;
        // Use this for initialization
        void Start () {
                //1.启用武器
                pickupBtn.onClick.AddListener (EnableWeapon);              
        }
        void EnableWeapon(){
                weapon1.gameObject.SetActive (true);       
        }
      // Update is called once per frame
        void Update () {               
        }
}

以上代码中,我们只是在用户按下拾取武器的按钮时启用武器。好了,接下来可以点击工具栏上的Play按钮预览游戏效果。

为了让游戏的效果更加接近真实,我们希望当手机射击的时候可以弹出弹壳。
在本课的内容中,我们将主要实现这一功能。
打开Unity,切换到Asset Store视图,在搜索栏中输入shell,选择FREE ONLY,然后下载并导入下图中的资源。
 
下载完成后,把Grenades_Ammo_FPS文件夹拖动到Project视图的Assets/Arts中。
从Grenades_AMMO_FPS的Prefabs文件夹中找到9x18这个预设体,在Inspector视图中,右键单击Transform,选择reset。调整其Scale比例,并设置Rotation值。
将其更命名为shell,然后拖动到Project视图的Assets/_Prefabs文件夹中。

 
然后从Project视图中找到并打开ShootEnemy.cs。
在Start方法之前添加一行代码:
        //创建到弹壳的引用
        public GameObject shell;
这里我们创建了到弹壳对象的引用。
回到Unity编辑器,在Hierarchy视图中选中CameraParent-Main Camera-weapon1对象,然后在Inspector视图中将Shoot Enemy组件中的Shell属性设置为刚才创建的shell 预设体,如图所示。

 
然后在OnShoot方法中,紧接着播放开火动画的那行代码添加以下代码:
                        //loading shell
                        Vector3 position = GameObject.FindGameObjectWithTag ("positionPistol").transform.position;
                        Quaternion rotation = Quaternion.Euler (0, 0, 0https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        Instantiate(shell,position,rotationhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>这里我们获取了Tag标记为positionPistol的对象的位置,然后将rotation设置为0,并使用Instantiate方法生成弹壳对象。
接下来我们要设置这个Tag。在Hierarchy视图中选中CameraParent-Main Camera-weapon1-Pistol-All-GunAndRightArm-GunPosition,然后在Inspector视图中添加一个Tag。

 
创建完成后,将GunPostion的Tag设置为positionPistol。
 
好了,接下来点击Unity编辑器工具栏上的Play按钮预览游戏效果。
可以看到在Hierarchy视图中子弹壳已经生成了,但是在Game视图中还无法看到子弹壳的实体。这是因为它的位置在手枪的扳机处,而且保持不变。
为此,我们需要给弹壳添加物理机制。
从Project视图中的Assets/_Prefabs文件夹中找到shell这个预设体,然后在Inspector视图中做一些调整。
首先给它添加一个Rigidbody组件,然后将Mass设置为0.03,Drag设置为1,Angular Drag设置为1.

 
紧接着添加一个新的脚本组件,名为MoveShell.cs,在MonoDevelop中打开并编辑。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveShell : MonoBehaviour {
        //创建到Rigidbody的引用
        public Rigidbody rb;
        // Use this for initialization
        void Start () {
                //获取Rigidbody
                rb = GetComponent<Rigidbody> (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }
        // Update is called once per frame
        void Update () {
        
                //施加一个向右的力
                rb.AddForce (transform.right * 0.05fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //施加一个向上的力
                rb.AddForce (transform.up * 0.05fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }
}
回到Unity编辑器,在Inspector视图中将Move Shell脚本组件中的Rb属性设置为Rigidbody,同时禁用Use Gravity,如图。
 
点击Unity编辑器上的Play按钮预览游戏,弹壳出来了,但是弹出的方向始终在一个方向,显然不符合常识。我们希望子弹有个随机的掉落方向,接下来将实现这一点。继续编辑MoveShell.cs脚本如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveShell : MonoBehaviour {
        //创建到Rigidbody的引用
        public Rigidbody rb;
        // Use this for initialization
        void Start () {
                //获取Rigidbody
                rb = GetComponent<Rigidbody> (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //1.添加随机旋转的协程
                StartCoroutine("Rotate"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //2.添加恢复重力的协程
                StartCoroutine("RecoverGravity"https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }       
        // Update is called once per frame
        void Update () {
                //施加一个向右的力
                rb.AddForce (transform.right * 0.05fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //施加一个向上的力
                rb.AddForce (transform.up * 0.05fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>        }
        IEnumerator Rotate(){
                while (true) {
                
                        //3.等待0.1秒
                        yield return new WaitForSeconds (0.1fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                        //4.随机旋转
                        transform.eulerAngles += new Vector3 (Random.Range (-360f, 360f), Random.Range (-360f, 360f),Random.Range(-360f,360f));                
                }
        }
        IEnumerator RecoverGravity(){
                //5.等待0.2秒
                yield return new WaitForSeconds (0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //6.恢复重力的影响
                rb.useGravity = true;
        }
}
按照注释行的数字编号简单解释一下:
1.添加了一个随机旋转的协程
2.添加了恢复重力作用的协程
3.等待0.1秒
4.在0.1秒后开始随机旋转
5.等待0.2秒
6.在0.2秒后开始恢复重力的作用

回到Unity编辑器,点击Play,可以预览下游戏效果。
接下来还有一个事情需要完成,那就是销毁弹壳,不然游戏场景中的弹壳会无穷无尽了。
继续回到刚才的MoveShell.cs脚本,在Start方法的最后添加一行代码:
                //3.在2秒后销毁弹壳
                Destroy(gameObject,2.0fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>最后给弹壳也添加一个音效。
在Project视图中选中shell预设体,然后点击Inspector视图中的Add Component,添加一个Audio Source组件。禁用Play On Awake,将AudioClip属性设置为bulletshells01,如图所示。
 
最后回到MoveShell.cs,在RecoverGravity方法的最后添加以下代码:
                //等待0.2秒
                yield return new WaitForSeconds(0.2fhttps://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                //播放弹壳的音效
                AudioSource shell = GetComponent<AudioSource>(https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>                shell.Play (https://blog.csdn.net/Batman1208/article/details/);%0D%0A%3Cbr>代码的作用很直白,首先要再等待0.2秒,然后播放弹壳的音效。

在Unity编辑器中切换到Asset Store,在搜索栏中�输入mode在Unity编辑器中切换到Asset Store,在搜索栏中

ONLY,然后下载和导入下面的这个资源。

接着在�输入table,仍然选择FREE ONLY,然后下载和导入下图的资源。

接着在�输入campfire,仍然选择FREE ONLY,然后下载和导入下图的资源。

资源下载导入成功后,接下来让我们把这些资源添加到场景中。

首先要添加的就是野外的篝火。

在刚刚导入的Campfire Pack资源包中打开DemoScene,从Hierarchy视图中选中Torch并复制该对象。

再次打开UnityARKitScene,将复制的Torch对象粘贴到Hierarchy视图中

接下来在Project视图中找到Campfire Pack-Model-model中的modelBonfire,将其拖动到Hierarchy视图中,使其成为HitCubeParent-ruined_house的子对象。然后使用移动工具将其拖动到房子外面的空地上。

将刚刚添加的Torch对象拖动为modelBonfire的子对象,并重置其Transform信息如下。

在粒子特效的属性中将起始大小调的小一点,

当然,还是那句话,具体的数值大小是很主观的,大家没必要完全照搬,觉得满意就行~

接下来在Project视图中找到Campfire Pack-Model-model中

 

的modelTent,并将其拖动到Hierarchy视图,使其成为HitCubeParent-ruined_house的子对象。


在Inspector视图中调整其位置和大小,具体就不再赘述了,大家觉得合适就行。

接下来添加一个桌子,从刚刚下载的资源包中找到Wooden_table_and_chair-FBX下的table_and_chair预设体,把它拖动到Hierarchy视图,使其成为HitCubeParent-ruined_house的子对象

通过移动工具调整它的位置,放到合适的位置,主要是想让武器可以掉落在桌子上。

接下来选中table_and_chair的子对象chair,给其添加Rigidbody和Box Collider两个组件,对子对象table做同样的操作。

接下来适当调整桌椅的大小。

把武器拖动为table_and_chair的子对象,并调整其Box Collider的大小。

点击Play按钮,查看手枪是不是比较和谐的掉落在桌面上。如果不是,就需要我们稍微调整双方的Box

 

Collider的大小和位置。

调整到位后,在Project视图中找到Grenades_Ammo_FPS/Prefabs中的手榴弹预设体,并拖动到Hierarchy视图中,让其成为table_and_chair的子对象

在Inspector视图中重置其position,具体的操作就不再赘述了,大家应该已经非常熟悉此类操作了。

把手榴弹对象拖动到桌面上,给其添加box collider和rigidbody组件,并适当调整下比例大小。

然后从Project视图中把另外一个手榴弹预设体拖动到Hierarchy

 

 

 

 

 

 

相关推荐

相关文章