Unity实例.003官方示例Survival Shooter Tutorial核心代码学习

来源:互联网 发布:淘宝联盟提现手续费 编辑:程序博客网 时间:2024/05/18 01:17
【学习项目为Unity官方的Survival Shooter tutorial,可以在Asset Store下载】
【参考官方学习教程Unity.learn.Survival Shooter tutorial
Unity官方教学示例《噩梦射手》(Survival Shooter Tutorial)的一些核心代码的学习,包括如何实现角色的移动,摄像机的跟随,敌人的寻路,玩家的生命控制等等内容。

1.控制角色移动:脚本PlayerMovement

创建地板用于射线捕捉,把Layer设定为Floor
public float speed = 6f;// 定义角色的移动速度
Vector3 movement;// 角色的移动向量
Animator anim; // 角色动画组件
Rigidbody playerRigidbody; //角色刚体组件
int floorMask; // 地板遮罩层用于摄像捕捉
float camRayLength = 100f; //从摄像机到屏幕里的射线长度

void Awake ()
   
{
floorMask = LayerMask.GetMask ("Floor");// 创建一个地板的遮罩层
anim = GetComponent <Animator> ();//引用Player的组件Animator
playerRigidbody = GetComponent <Rigidbody> ();//引用Player的组件Rigidbody
    }
LayerMask 层蒙版
     struct in UnityEngine
LayerMask.GetMask
无论是内置的层名称集或是由用户在“标签和层管理器”中定义的层,返回与它们相等的层蒙版。

Component 组件
游戏对象的组件的基类
Component.GetComponent 获取组件
如果游戏对象有附加type类型的组件,则返回,如果没有则为空。



射线检测是否和地面碰撞,并且让角色旋转
void Turning ()
       
{
Ray camRay = Camera.main.ScreenPointToRay (Input.mousePosition);//获取摄像机和鼠标指针所在点连成的线
RaycastHit floorHit;//射线命中点
       if(Physics.Raycast (camRay, out floorHit, camRayLength, floorMask))//射线是否命中地板遮罩层
            {
Vector3 playerToMouse = floorHit.point - transform.position;//获取向量从角色位置指向射线命中地板的点
playerToMouse.y = 0f;//确保这个向量始终在地板平面上
Quaternion newRotatation = Quaternion.LookRotation (playerToMouse);//创建一个注视旋转,从垂直上方看着地板上向量playerToMouse的沿着Y轴的旋转轴角
playerRigidbody.MoveRotation (newRotatation);//使Player根据newRotatation这个轴角进行旋转
            }
        }
Camera.main
第一个启用的被标记为“MainCanmera”的相机(只读)。

Camera.ScreenPointToRay
返回一条射线从摄像机通过一个屏幕点。

Input.mousePosition
在屏幕坐标空间当前鼠标的位置(只读)。

RaycastHit
该结构用来获取射线投射返回的信息。
RaycastHit.point
在世界坐标空间,射线碰到碰撞器的接触点。

Physics.Raycast
在场景中投下可与所有碰撞器碰撞的一条光线。

Quaternion.LookRotation
创建一个旋转,沿着forward(z轴)并且头部沿着upwards(y轴)的约束注视。也就是建立一个旋转,使z轴朝向view y轴朝向up。
返回计算四元数。如果用于定向的变换,Z轴将会被对准前方并且如果这些向量正交,Y轴向前。如果forward方向是0,记录一个错误。

Rigidbody.MoveRotation
旋转刚体到新角度。


捕捉输入的h和v,移动角色
void Move (float h, float v)
     
{
movement.Set (h, 0f, v);//根据键盘的输入设置角色的移动向量
movement = movement.normalized * speed * Time.deltaTime;//对键盘的输入向量进行单位化,给它一个速度和帧时间来得到一帧移动的向量;
playerRigidbody.MovePosition (transform.position + movement);//用Player的当前坐标加上movement的坐标得到目标位置坐标,移动Player到目标位置
     }
Vector3.Set
设置现有的Vector3的x、y、z组件

Vector3.normalized
返回向量的长度为1(只读);
当归一化后,向量保持同样的方向,但是长度变为1.0;
注意,当前向量不能改变,而是返回一个新的归一化的向量。如果你想归一化当前向量,使用Normalize函数;
如果这个向量太小而不能被归一化,一个零向量将会被返回。

Time.deltaTime
以秒计算,完成最后一帧的时间(只读);
使用这个函数使你的游戏帧速率独立;
如果你加或减一个每帧改变的值,你应该与Time.deltaTime相乘。当你乘以Time.deltaTime实际表示:每秒移动物体10米,而不是每帧10米;
当从MonoBehaviour的FixedUpdate里调用时,返回固定帧速率增量时间(fixedDeltaTime)。

Rigidbody.MovePosition
移动刚体到新位置;
使用Rigidbody.MovePosition来移动刚体,带有刚体插值设置;
如果刚体插值启用,调用Rigidbody.MovePosition导致在任意两帧之间平滑过渡。如果你想在每固定更新连续移动刚体使用这个;
如果你想把刚体从一个位置瞬移到另一个位置,中间不带过渡,使用Rigidbody.position替代。



如果角色移动就播放移动动画
void Animating (float h, float v)
       
{
bool walking = h != 0f || v != 0f;//创建一个布尔值,当键盘输入不为0时,即角色移动时,布尔值为真,否则为假;
anim.SetBool ("IsWalking", walking);//对Player的Animator组件中的条件字段赋值,进行真假判断,来执行角色移动动画或者放置动画;
        }
Animator 动画器
控制Mecanim动画系统的接口
Animator.SetBool
设置一个布尔参数的值


FiexdUpdate输入Input,并且统一调用以上几个方法
void FixedUpdate ()
   
{
float h =CrossPlatformInputManager.GetAxisRaw("Horizontal"); // 存储键盘输入的水平坐标
float v = CrossPlatformInputManager.GetAxisRaw("Vertical");// 存储键盘输入的垂直坐标
Move (h, v);//移动Player
Turning ();//使Player跟随鼠标指针转向
Animating (h, v);//Player的动画控制
    }
CrossPlatformInput 跨平台输入工具包

2.控制摄像机跟随玩家:脚本CameraFollow
public classCameraFollow: MonoBehaviour {
   public Transform target;    //用于编辑器中绑定玩家
    public float smoothing = 5f;   //用于计算顺滑度
    Vector3 offset;
    void Start() {
        //首先初始化的时候保存相机和玩家的相对位置
        offset = transform.position - target.position;
    }

//这里不要使用FixedUpdate, 移动端屏幕会有视觉卡顿
    void Update () {
        //计算出相机跟随的位置
        Vector3 targetCamPos = target.position + offset;
        //设置相机的位置,这里用到了Vector3.Lerp,是一个差值计算,使得移动更柔和.但是会略微消耗计算量
        //由于主摄像机只有1个,所以可以忽略这个计算量的消耗
        transform.position = Vector3.Lerp (transform.position, targetCamPos, smoothing * Time.deltaTime);
    }
}
Vector3.Lerp
两个向量之间的线性插值。
按照分数t在from到to之间插值。这是最常用的寻找一点沿一条线的两个端点之间一些分数的方式(例如,在那些点之间逐渐移动一个对象)。这分数是在范围[ 0…1]。t是夹在 [0…1]之间,当t = 0时,返回from,当t = 1时,返回to。当t = 0.5 返回from和to的中间点。

3.创建一个敌人,自动寻路跟随玩家:脚本EnemyMovement
public class EnemyMovement : MonoBehaviour
    {
        Transform player;               // 定义引用Player位置
        PlayerHealth playerHealth;      // 定义引用脚本PlayerHealth
        EnemyHealth enemyHealth;        // 定义引用脚本EnemyHealth
        UnityEngine.AI.NavMeshAgent nav;  // 定义引用NavMeshAgent


        void Awake ()
       
{
            player = GameObject.FindGameObjectWithTag ("Player").transform; //Player的位置坐标
            playerHealth = player.GetComponent <PlayerHealth> (); //获取脚本组件PlayerHealth
            enemyHealth = GetComponent <EnemyHealth> ();//获取脚本组件EnemyHealth
            nav = GetComponent <UnityEngine.AI.NavMeshAgent> ();//获取寻路导航
        }

       void Update ()
       
{
            if(enemyHealth.currentHealth > 0 && playerHealth.currentHealth > 0)//如果enemy和player的当前血量大于0
            {
                nav.SetDestination (player.position);//通过寻路导航设置目标点位玩家所在位置;
            }
            else
            {
                // ... disable the nav mesh agent.
                nav.enabled = false//如果enemy和player其中之一的血量小于等于0,则关闭寻路导航;
            }
        }
    }
GameObject.FindGameObjectWithTag
返回具体tag标签的激活的游戏对象列表,如果没有找到则为空。

GameObject.transform
获取附加于这个游戏对象上的transform,如果没有则为空。

GameObject.GetComponent
如果这个游戏对象附件了一个类型为type的组件,则返回该组件,否则为空;
GetComponent是访问别的组件的原始方法,脚本的类型就是项目视图里面看到的脚本名称。通过这个函数,你可以访问内置组件或脚本。

NavMeshAgent 导航网格代理
NavMeshAgent.SetDestination 设置目的地
设置或者更新目的地因此触发计算新的路径;
注意该路径可能不会变成可获取的直到一些帧之后。当路径被计算出,pathPending 将会是true。如果一个有效路径变得可获取,那么代理将会重新恢复运动。

NavMeshAgent继承自Behaviour
Behaviour.enabled 启用
启用行为将被Updated,禁用行为将不进行Updated;
行为在检视面板显示为小的复选框。

4.对玩家的生命进行控制:脚本PlayerHealth

public int startingHealth = 100;//Player的初始血量100
public int currentHealth; //定义Player的当前血量变量
public Slider healthSlider; //定义引用血条参数
public Image damageImage; //引用受伤时的图像                       
public AudioClip deathClip;//添加角色死亡的音效
public float flashSpeed = 5f;//设定damageImage的淡出屏幕的速度
public Color flashColour = new Color(1f, 0f, 0f, 0.1f); //设定damageImage的颜色
Animator anim;//引用动画组件参数
AudioSource playerAudio;//引用音效组件参数
PlayerMovement playerMovement;//引用脚本PlayerMovement
PlayerShooting playerShooting;//引用脚本PlayerShooting
bool isDead; //声明bool值判断角色是否死亡
bool damaged;//声明bool值判断角色是否受伤

       void Update ()
       
{
            if(damaged)
            {

                damageImage.color = flashColour;//角色受伤时,将flashColour赋值给damageImage的color;
            }
            else
            {
                damageImage.color = Color.Lerp (damageImage.color, Color.clear, flashSpeed * Time.deltaTime);//通过Lerp,将受伤后屏幕显示的颜色渐变消除
            }
            damaged = false;//重置角色的受伤状态为否;
        }
Color.Lerp 线性插值
颜色a和颜色b之间的线性差值t;
t是夹在0到1之间,当t为0时返回a,当t为1时返回b。



        public void TakeDamage (int amount)        {            damaged = true;//改变角色受伤状态为真            currentHealth -= amount;//当前血量减少,减少值为受到的伤害值            healthSlider.value = currentHealth;//将当前血量赋值给血条            playerAudio.Play ();//播放角色受伤的音效,引用的是Player的音效组件中的Player Hurt            if(currentHealth <= 0 && !isDead)            {                Death ();//当Player的血量小于等于0,isDead为flase时;            }        }
AudioSource.Play
播放音频剪辑。


       void Death ()
       
{
            isDead = true;//Player判定为死亡
            playerShooting.DisableEffects ();//调用playerShooting脚本中的DisableEffects方法关闭射击特效
            anim.SetTrigger ("Die");//通过触发器播放Player的死亡动画
            playerAudio.clip = deathClip;//音效组件引用脚本中的音效deathClip,Player Hurt的音效会停止播放
            playerAudio.Play ();//播放音效组件中的deathClip音效

            playerMovement.enabled = false;//关闭脚本PlayerMovemonet
            playerShooting.enabled = false;//关闭脚本PlayerShooting
        }

        public void RestartLevel ()
       
{
            SceneManager.LoadScene (0);//重载当前的场景
        }
Animator.SetTrigger 设置触发器
设置一个要激活的触发器参数;
触发器是参数,大多数行为像布尔,但当它们被用在过渡时,重置为无效。

SceneManager 场景管理      class in UnityEngine.SceneManagement
运行时的场景管理方法。
SceneManager.LoadScene 加载场景
通过在Build Settings中它们的名称或索引加载场景;
指定场景名称可以是路径的最后部分不加.unity扩展名或者全部路径不加.unity扩展名。该路径在 Build Settings窗口中被精确的显示出来。如果场景名是指定的将会加载匹配到的首个场景。如果有多个名称相同但是路径不同的场景,你应该使用全部路径。

5.控制怪物进行攻击:脚本EnemyAttack

       public float timeBetweenAttacks = 0.5f;//攻击的间隔时间
        public int attackDamage = 10//攻击的伤害值

        Animator anim; //声明引用动画的参数
        GameObject player; //声明引用游戏对象的参数
        PlayerHealth playerHealth;//声明引用脚本PlayerHealth的参数
        EnemyHealth enemyHealth; //声明引用脚本EnemyHealth的参数
        bool playerInRange;//定义角色是否在被攻击范围的布尔值
        float timer;//定义下次攻击的计时器 
       void Awake ()
       
{
            // Setting up the references.
            player = GameObject.FindGameObjectWithTag ("Player");//通过标签设置引用的游戏对象为Player
            playerHealth = player.GetComponent <PlayerHealth> ();//引用脚本PlayerHealth
            enemyHealth = GetComponent<EnemyHealth>();//引用脚本EnemyHealth
            anim = GetComponent <Animator> ();//引用游戏动画组件
        }
6.控制玩家进行攻击:脚本PlayerShooting


   void Update ()
   
{
        timer += Time.deltaTime;//tiemr计时器

        if(Input.GetButton ("Fire1") && timer >= timeBetweenBullets && Time.timeScale != 0)
        {
            Shoot ();//输入开火键 且 计时器大于射击间隔时间 且 timeScale传递时间不为0时 进行射击
        }

        if(timer >= timeBetweenBullets * effectsDisplayTime) //计时器大于等于射击间隔时间*特效显示时间时
        {
            DisableEffects ();//关闭射击特效
        }
    }
void Shoot ()
   
{
        timer = 0f;//射击时计时器重置
        gunAudio.Play ();//播放设计音效
        gunLight.enabled = true;//光照特效开启
        gunParticles.Stop ();//粒子特效关闭
        gunParticles.Play ();//粒子特效开启
        gunLine.enabled = true;//线渲染器开启
        gunLine.SetPosition (0, transform.position);//设置线段起始点为枪所在位置

        shootRay.origin = transform.position;//射线起始点为枪的坐标
        shootRay.direction = transform.forward;//射线的方向枪的正前方

        if(Physics.Raycast (shootRay, out shootHit, range, shootableMask))//射线与射线层内的碰撞器有交集时为真
        {
            EnemyHealth enemyHealth = shootHit.collider.GetComponent <EnemyHealth> ();//获取射击点所在碰撞器物体的组件的血量脚本
            if(enemyHealth != null)//脚本存在时
            {
                enemyHealth.TakeDamage (damagePerShot, shootHit.point);//射击点所在的敌人执行受伤的方法
            }
            gunLine.SetPosition (1, shootHit.point);//脚本不存在时,设置线段目标点为射击位置
        }
        else
        {
            gunLine.SetPosition (1, shootRay.origin + shootRay.direction * range);//射线与可射击层无交集时,设置线段目标点为为枪所在位置加上射线的长度范围
        }
    }
Ray.origin 射线的原点
Ray.direction 射线方向,方向总是归一化的向量。如果你指定一个非单位长度的向量,它将被归一化。
LineRenderer 线性渲染
该线性渲染用于在3D空间中绘制独立线条;
该类是线性渲染器组件的脚本接口。
LineRenderer.SetPosition
在线条上设置线条的顶点位置。

7.添加怪物的生命组件:脚本EnemyHealth
using UnityEngine;

public class EnemyHealth : MonoBehaviour
{
    public int startingHealth = 100;//敌人初始血量
    public int currentHealth;//敌人当前血量
    public float sinkSpeed = 2.5f;//敌人死亡后下沉速度
    public int scoreValue = 10;//消灭敌人的分数值
    public AudioClip deathClip;//敌人死亡音效

    Animator anim;//引用动画组件参数
    AudioSource enemyAudio;//引用音效参数
    ParticleSystem hitParticles;//引用粒子特效系统参数
    CapsuleCollider capsuleCollider;//引用胶囊体参数
    bool isDead;//敌人是否死亡布尔值
    bool isSinking;//敌人是否在下沉布尔值

    void Awake ()
   
{
        anim = GetComponent <Animator> ();//获取动画组件
        enemyAudio = GetComponent <AudioSource> ();//获取音效组件
        hitParticles = GetComponentInChildren <ParticleSystem> ();//获取子对象粒子特效
        capsuleCollider = GetComponent <CapsuleCollider> ();//获取组件胶囊体

        currentHealth = startingHealth;//当前生命为初始生命
    }

    void Update ()
   
{
        if(isSinking)
        {
            transform.Translate (-Vector3.up * sinkSpeed * Time.deltaTime);//下沉为真值时,使敌人向下移动
        }
    }

    public void TakeDamage (int amount, Vector3 hitPoint)
   
{
        if(isDead)
            return;//死亡时,不再受伤
        enemyAudio.Play ();//播放受伤音效
        currentHealth -= amount;//当前血量减去受到的伤害值
        hitParticles.transform.position = hitPoint;//把射击点坐标赋值给粒子特效位置
        hitParticles.Play();//播放粒子特效
        if(currentHealth <= 0)
        {
            Death ();//敌人生命小于等于0时,敌人死亡
        }
    }

    void Death ()
   
{
        isDead = true;//死亡布尔值为真
        capsuleCollider.isTrigger = true;//使胶囊体触发器为真
        anim.SetTrigger ("Dead");//动画触发器“Dead”启动
        enemyAudio.clip = deathClip;//将deathClip赋值给声音源
        enemyAudio.Play ();//播放deathClip
    }


    public void StartSinking ()
   
{
        GetComponent <NavMeshAgent> ().enabled = false;//寻路导航关闭
        GetComponent <Rigidbody> ().isKinematic = true;//刚体受力影响为真
        isSinking = true;//下沉状态为真
        ScoreManager.score += scoreValue;//分数计数增加
        Destroy (gameObject, 2f);//2秒后摧毁当前游戏对象
    }
}
8.创建怪物生成器:脚本EnemyManager

using UnityEngine;
using System.Collections;

public class EnemyManagerMonoBehaviour {

   public PlayerHealth playerHealth;   //玩家的血量
   public GameObject enemy;    //要创建的怪物
    public float spawnTime = 3f;   //创建间隔时间
    public Transform[] spawnPoints; //创建位置

   void Start () {
        //循环调用("方法名", 初始等待时间, 循环间隔时间)
        InvokeRepeating ("Spawn", spawnTime * 0.7f, spawnTime);
    }

    void Spawn() {
        if(playerHealth.currentHealth <= 0) {
            return;
        }
        //随机得到数组范围内的一个整数
        int spawnPointIndex = Random.Range (0, spawnPoints.Length);
        //实例化一个物件(物件, 位置, 旋转角度);
        Instantiate (enemy, spawnPoints [spawnPointIndex].position, spawnPoints [spawnPointIndex].rotation);
    }
}
MonoBehaviour
MonoBehaviour是每个脚本的基类。
MonoBehaviour.InvokeRepeating 重复调用
在time秒调用methodName方法;
简单说,根据时间调用指定方法名的方法;
从第一次调用开始,每隔repeatRate时间调用一次。

Object.Instantiate
克隆原始物体并返回克隆物体。
克隆原始物体,位置设置在position,设置旋转在rotation,返回的是克隆后的物体。这实际上在Unity和使用复制(ctrl+D)命令是一样的,并移动到指定的位置。如果一个游戏物体,组件或脚本实例被传入,实例将克隆整个游戏物体层次,以及所有子对象也会被克隆。所有游戏物体被激活。
实例化更多通常用于实例投射物(如子弹、榴弹、破片、飞行的铁球等),AI敌人,粒子爆炸或破坏物体的替代品。
注意,Instantiate(实例化)能克隆Object(物体)任何类型,包含script(脚本)。
0 0
原创粉丝点击