关于第一人称射击的游戏,想起了我玩过的第一款第一人称射击网游,当时很火热的《反恐精英Online》,当时也是玩这个游戏的时候只能说是感觉好玩才去玩,现在再次玩起这种第一人称射击类游戏的时候,可以说多了一个目的,带着欣赏的目的去玩,或者说我更想知道其中的原理,也就是说这个动作或者这种效果是怎么实现的,做游戏开发的不仅要知其然还要知其所以然,我想带着这样一种的学习态度我们才能在学习的过程中获取更多的知识,这一点很重要。
在练习做这个第一人称设计游戏的过程中学到了很多的东西,其中也有自己不足的地方,下面将在做这个游戏的过程中遇到的问题和学到的东西记录下来。
(一)首先从玩家类来说
在这个练习中,玩家类是一个比较重要类,里面定义了玩家的一些基本的数据,注意这里定义变量的过程中如果那个变量或者方法在以后的开发中要调用的话,最好设置为共有的变量,这样就不会发生获取不到的情况了,记得在学习c++课程的时候,老师经常说要用面向对象的思想去编程,这里我想可以把用到的变量设置为属性,例如将playerLife玩家的生命值设置为私有的变量,可以通过方法的方式来初始化或者改变这个生命值,还有就是通过属性的方式,例如public static Life{get{return playerLife};set{playerLife=value} },这样的例子
using UnityEngine; using System.Collections; public class Player : MonoBehaviour { //玩家的transform public Transform playerTraform; //获取玩家身上的charactercontrol组件 CharacterController playerCC; //定义玩家的移动速度 public float playerSpeed=5f; //定义玩家的生命值 public float playerLife = 100f; //定义玩家的重力,当玩家通过控制器跳起来的时候 public float playerGravity = 2f; /// <summary> /// 用于获取摄像机的变量 /// /// </summary> //摄像机的tranform Transform camTransform; //摄像机的旋转 Vector3 camRot; //摄像机的高度 float camHeight = 0.8f; /// //射击部分变量 //枪口tranform Transform shootPoint; //射击射到的碰撞层 public LayerMask shootMask; //射击射中后的粒子效果 public Transform shootFX; //射击De 音效 AudioSource shootMusic; public AudioClip shootClip; //射击的间隔时间 float shootTime = 0f; //子弹伤害 public int shootPower = 1; void Start() { //获取组件 playerTraform = this.transform; playerCC = gameObject.GetComponent<CharacterController>(); //获取摄像机 camTransform = Camera.main.transform; Vector3 pos = playerTraform.position; pos.y += camHeight; camTransform.position = pos; //设置摄像机的旋转,与玩家的角度一致 camTransform.rotation = playerTraform.rotation; camRot = camTransform.eulerAngles; //锁定鼠标 Cursor.lockState = CursorLockMode.Locked; //获取音乐 shootMusic = this.gameObject.GetComponent<AudioSource>(); //获取枪口位置 shootPoint = Camera.main.transform.FindChild("M16/weapon/muzzlepoint").transform; //初始化玩家的 GameManager.Instance.SetLife((int)playerLife); } void Update() { //判断生命值是否为0 if(playerLife<=0) { return; } //执行Control; Control(); //射击部分的代码 // //更新射击的时间 shootTime -= Time.deltaTime; if(Input.GetMouseButton(0)&&shootTime<=0) { shootTime = 0.1f; //播放射击的音效 shootMusic.PlayOneShot(shootClip); //减少弹药数量 GameManager.Instance.SetAmout(1); //用Raycast用来做检测 RaycastHit hit; //从枪口的位置,point点 Ray ray=new Ray(shootPoint.position,camTransform.TransformDirection(Vector3.forward)); if(Physics.Raycast(ray,out hit,1000,shootMask)) { //如果射中 if(hit.transform.tag.CompareTo("Enemy")==0) { Enemy enemy = hit.transform.GetComponent<Enemy>(); //减少敌人的生命值 enemy.OnDamage(shootPower); } Instantiate(shootFX, hit.point, hit.transform.rotation); } } } /// <summary> /// 控制主角的移动 /// </summary> void Control() { //获取鼠标的移动来进行方向的切换 float h = Input.GetAxis("Mouse X"); float v = Input.GetAxis("Mouse Y"); //旋转摄像机设置 camRot.x -= v; camRot.y += h; camTransform.eulerAngles = camRot; //设置主角的朝向与摄像机一致 Vector3 playerRot = camTransform.eulerAngles; playerRot.x = 0; playerRot.z = 0; playerTraform.eulerAngles = playerRot; //主要玩家的移动控制 //定义三个变量 float x = 0f, y = 0f, z = 0f; //前后左右的移动 //前 if(Input.GetKey(KeyCode.W)) { z += playerSpeed * Time.deltaTime; } //后 if(Input.GetKey(KeyCode.S)) { z -= playerSpeed * Time.deltaTime; } //左 if(Input.GetKey(KeyCode.A)) { x -= playerSpeed * Time.deltaTime; } //右 if(Input.GetKey(KeyCode.D)) { x += playerSpeed * Time.deltaTime; } //当玩家跳起来的时候,角色控制器会自动检测玩家的跳起 y -= playerGravity * Time.deltaTime; //移动 playerCC.Move(playerTraform.TransformDirection(new Vector3(x,y,z))); //摄像机的位置和主角位置一致 Vector3 pos = playerTraform.position; pos.y += camHeight; camTransform.position = pos; } /// <summary> ///用来显示游戏中主角的位置 /// </summary> void OnDrawGizmos() { Gizmos.DrawIcon(this.transform.position,"Spawn.tif"); } //射击 public void OnDamage(int damage) { playerLife -= damage; //更新UI GameManager.Instance.SetLife((int)playerLife); //判断生命值是否为0 if (playerLife <= 0) { Cursor.lockState = CursorLockMode.Confined; } } }
在这个类中,比较重要的东西就是,在第一人称射击游戏中怎么通过鼠标的移动,转换成玩家视角的旋转,这个是这个类中比较重要的地方,下面我将这一部分的代码单独拿出来:
void Control() { //获取鼠标的移动来进行方向的切换 float h = Input.GetAxis("Mouse X"); float v = Input.GetAxis("Mouse Y"); //旋转摄像机设置 camRot.x -= v; camRot.y += h; camTransform.eulerAngles = camRot; //设置主角的朝向与摄像机一致 Vector3 playerRot = camTransform.eulerAngles; playerRot.x = 0; playerRot.z = 0; playerTraform.eulerAngles = playerRot; //主要玩家的移动控制 //定义三个变量 float x = 0f, y = 0f, z = 0f; //前后左右的移动 //前 if(Input.GetKey(KeyCode.W)) { z += playerSpeed * Time.deltaTime; } //后 if(Input.GetKey(KeyCode.S)) { z -= playerSpeed * Time.deltaTime; } //左 if(Input.GetKey(KeyCode.A)) { x -= playerSpeed * Time.deltaTime; } //右 if(Input.GetKey(KeyCode.D)) { x += playerSpeed * Time.deltaTime; } //当玩家跳起来的时候,角色控制器会自动检测玩家的跳起 y -= playerGravity * Time.deltaTime; //移动 playerCC.Move(playerTraform.TransformDirection(new Vector3(x,y,z))); //摄像机的位置和主角位置一致 Vector3 pos = playerTraform.position; pos.y += camHeight; camTransform.position = pos; }
(二)控制脚本,
这个脚本主要处理屏幕的UI方面,和游戏的一些逻辑处理
using UnityEngine; using System.Collections; using UnityEngine.UI; public class GameManager : MonoBehaviour { public static GameManager Instance = null; //游戏得分 int score = 0; //游戏最高分 static int highScore = 0; //弹药数量 int amout = 100; //游戏主角 Player player; Text txtAmout; Text txtHighScore; Text txtLife; Text txtScore; Transform canvasTranform; void Start() { Instance = this; player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>(); canvasTranform = GameObject.FindGameObjectWithTag("UI").GetComponent<Transform>(); //依次获取canvas 下面的UGUI的内容 foreach(Transform t in canvasTranform.GetComponentsInChildren<Transform>()) { if(t.name.CompareTo("boot")==0) { txtAmout= t.transform.GetComponentInChildren<Text>(); Debug.Log(txtAmout.text); } if(t.name.CompareTo("life")==0) { txtLife = t.transform.GetComponentInChildren<Text>(); } if(t.name.CompareTo("score")==0) { txtScore = t.GetComponent<Text>(); } if(t.name.CompareTo("highscore")==0) { txtHighScore = t.GetComponent<Text>(); } } //初始化分数为0 SetScore(0); } //更新分数 public void SetScore( int _score) { score += _score; // if(score>highScore) { highScore = score; } txtScore.text="Score <color=yellow>"+score+"</color>"; txtHighScore.text = "HighScore " + highScore; txtHighScore.color = Color.red; } //更新弹药 public void SetAmout(int _amout) { amout -= _amout; //如果弹药为负数,填充 if(amout<=0) { amout = 100; } txtAmout.text = amout.ToString() + "/100"; } //更新生命值 public void SetLife(int _life) { txtLife.color = Color.red; txtLife.text = _life.ToString(); } // void OnGUI() { if(player.playerLife<=0) { //居中显示 GUI.skin.label.alignment = TextAnchor.MiddleCenter; //改变字体大小 GUI.skin.label.fontSize = 40; //显示GameOver GUI.Label(new Rect(0, 0, Screen.width, Screen.height), "Game Over"); //设置按钮文字大小 GUI.skin.label.fontSize = 30; //显示重新开始 if(GUI.Button(new Rect(Screen.width*0.5f-150,Screen.height*0.75f,300,40),"再来一次")) { //重新读入 Application.LoadLevel(0); } } } }
(三)敌人的逻辑
主要完成敌人的一些寻路处理,这里一个重要的知识点就是寻路的设置,这在游戏中是非常重要的,需要编写敌人的Ai,这个类中实现了敌人每间隔一段的时间更新玩家的位置信息作为敌人的目标,然后通过动画的控制来达到行走,攻击等一些列的动作,主要是通过判断玩家和敌人之间的距离,其中动画部分的设计也是这个中的重点
using UnityEngine; using System.Collections; public class Enemy : MonoBehaviour { //生成敌人的脚本 protected EnemySpawn enemyspawn; Transform enemyTransform; //主角 Player player; //寻路组件 NavMeshAgent enemyAgent; //移动速度 float enemySpeed=1.0f; //敌人旋转速度 float enemyRotate = 30f; //计时器 float enemyTime = 2f; float enemyLife = 5f; //获取动画组件 Animator enemyAni; // void Start() { enemyTransform = this.transform; //获取主角 player = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>(); //获取寻路组件 enemyAgent = this.gameObject.GetComponent<NavMeshAgent>(); //改变敌人的移动速度 enemyAni = this.gameObject.GetComponent<Animator>(); enemyAgent.speed = enemySpeed; //设置寻路的目标 即敌人的目标玩家 enemyAgent.SetDestination(player.playerTraform.position); } //用于旋转敌人,使敌人面向玩家 void RotaeToPlayer() { //获取目标方向 Vector3 targetDir = player.playerTraform.position - enemyTransform.position; //计算出新的方向 Vector3 newDir = Vector3.RotateTowards(enemyTransform.forward, targetDir, enemyRotate * Time.deltaTime, 0); enemyTransform.rotation = Quaternion.LookRotation(newDir); } void Update() { //生命值为0 if(enemyLife<=0) { return; } //获取当前动画状态 AnimatorStateInfo stateInfo = enemyAni.GetCurrentAnimatorStateInfo(0); //如果处于待机状态//并且不是处于转换的时候 if(stateInfo.fullPathHash==Animator.StringToHash("First.idle")&&!enemyAni.IsInTransition(0)) { enemyAni.SetBool("idle",false); //待机一定时间 enemyTime -= Time.deltaTime; if(enemyTime<=0) { return; } //如果小于某个距离进行攻击 if(Vector3.Distance(enemyTransform.position,player.playerTraform.position)<1.5f) { enemyAni.SetBool("attack", true); } else { //重置定时器 enemyTime = 1f; //设置目标 enemyAgent.SetDestination(player.playerTraform.position); //进入跑步的动画 enemyAni.SetBool("run",true); } } //处于跑步的状态 if(stateInfo.fullPathHash==Animator.StringToHash("First.run")&&!enemyAni.IsInTransition(0)) { enemyAni.SetBool("run",false); //每隔一秒定位一次 enemyTime -= Time.deltaTime; if(enemyTime<=0) { //重新开始寻路 enemyAgent.Resume(); enemyAgent.SetDestination(player.playerTraform.position); enemyTime = 1; } //距离小于1.5 if(Vector3.Distance(enemyTransform.position,player.playerTraform.position)<1.5) { //停止寻路 enemyAgent.Stop(); enemyAni.SetBool("attack",true); } } //当处于攻击状态 if(stateInfo.fullPathHash==Animator.StringToHash("First.attack")&&!enemyAni.IsInTransition(0)) { //面向主角 enemyAni.SetBool("attack",false); //如果动画播放完,重新待机 if(stateInfo.normalizedTime>=1f) { enemyAni.SetBool("idle", true); //重置 enemyTime = 2; //更新主角的生命值 player.OnDamage(1); } } //// //// //// ////这里有一个Bug,不能进入death中,,,,,,, //// /// //// /// //当播放死亡的动画的时候 if(stateInfo.fullPathHash==Animator.StringToHash("First.death")) { Debug.Log("进入"); enemyTime-=Time.deltaTime; //播放完死亡的动画 if(enemyTime<=0) { //减少敌人的数量 enemyspawn.enemyCount--; //加分 GameManager.Instance.SetScore(100); //摧毁自身 Destroy(this.gameObject); Debug.Log("播放了死亡"); } } } //计算伤害的函数 public void OnDamage(int damage) { enemyLife -= damage; if(enemyLife<=0) { float time2 = Time.time; //停止寻路 enemyAgent.Stop(); //播放死亡动画 enemyAni.SetBool("death",true); } } public void Init(EnemySpawn scirpt) { enemyspawn = scirpt; //增加敌人数量 enemyspawn.enemyCount++; } }
(四)一些用到的类
1.自动销毁类
using UnityEngine; using System.Collections; public class AutoDestroy : MonoBehaviour { public float timer = 1.0f; void Start() { Destroy(this.gameObject, timer); } }
2.敌人生成
using UnityEngine; using System.Collections; public class EnemySpawn : MonoBehaviour { //定义变量 //敌人的prefab public GameObject enemyObj; //生成敌人的数量 public int enemyCount; //敌人生成的最大数量 public float enemyMaxCount; //敌人生成的时间间隔 public float enemyTimer; Transform thisTransform; void Start() { thisTransform = this.transform; } void Update() { //生成敌人的数量达到最大值时候,停止生成敌人 if(enemyCount>=enemyMaxCount) { return; } //每隔一段时间生成 enemyTimer -= Time.deltaTime; if(enemyTimer<=0) { //得到下一轮敌人的生成时间 enemyTimer = Random.Range(5, 15); GameObject tar = (GameObject)Instantiate(enemyObj, thisTransform.position, Quaternion.identity); Enemy enemyScript = tar.GetComponent<Enemy>(); enemyScript.Init(this); } } void OnDrawGizmos() { Gizmos.DrawIcon(transform.position,"item.png"); } }
3.小窗口的创建和显示,这里主要通过不同摄像机显示不同的layer来实现
using UnityEngine; using System.Collections; [AddComponentMenu("Game/MiniCamera")] public class MiniCamera : MonoBehaviour { // Use this for initialization void Start() { float ratio = (float)Screen.width / Screen.height; this.GetComponent<Camera>().rect = new Rect((1-0.2f),(1-0.2f*ratio),0.2f,0.2f*ratio); } }