截图与代码均来自 cousera网络游戏设计与开发专项课程 如有侵权,联系本人立即删除
GameManager中添加了鼠标的显示与锁定功能,不太重要就没放。
1.对象池技术生成僵尸
using UnityEngine; using System.Collections; public class ZombieGenerator : MonoBehaviour { public Transform[] zombieSpawnTransform; //僵尸的生成地数组 public int maximumInstanceCount = 9; //场景中的最大僵尸数量 public float minGenerateTimeInterval = 5.0f; //生成僵尸的最小时间间隔 public float maxGenerateTimeInterval = 20.0f; //生成僵尸的最大时间间隔 public GameObject zombiePrefab; //僵尸预制件 private float nextGenerationTime = 0.0f; //下一次生成僵尸的时刻 private float timer = 0.0f; //计时器,用于计算生成僵尸的时间 private GameObject[] instances; //僵尸数组对象池 public static Vector3 defaultPosition = new Vector3(33, -6, -8); //僵尸的默认生成地点 void Start () { //生成僵尸对象池 instances = new GameObject[maximumInstanceCount]; //初始化僵尸对象池 for(int i = 0; i < maximumInstanceCount; i++) { //生成一个僵尸 GameObject zombie = Instantiate (zombiePrefab, defaultPosition, Quaternion.identity) as GameObject; //禁用僵尸 zombie.SetActive (false); //把僵尸放入僵尸对象池 instances [i] = zombie; } } //在僵尸对象池中,找一个处于禁用状态的僵尸对象 private GameObject GetNextAvailiableInstance () { for(var i = 0; i < maximumInstanceCount; i++) { if(!instances[i].activeSelf) { return instances[i]; } } return null; } //在Position参数指定的位置,生成一个僵尸 private bool generate(Vector3 position) { //从僵尸对象池中获得一个禁用状态的僵尸对象 GameObject zombie = GetNextAvailiableInstance (); if (zombie != null) { //启用僵尸 zombie.SetActive (true); //在指定位置初始化僵尸 zombie.GetComponent<ZombieAI> ().Born (position); return true; } return false; } void Update () { if (GameManager.gm.gameState != GameManager.GameState.Playing) return; //判断是否到达下一次生成僵尸的时间 if (timer > nextGenerationTime) { //选择一个出生地点 int i = Random.Range(0, zombieSpawnTransform.Length); //在选择的出生地生成一只僵尸 generate (zombieSpawnTransform [i].position); //计算下一次生成僵尸的时间 nextGenerationTime = Random.Range (minGenerateTimeInterval, maxGenerateTimeInterval); //清零timer timer = 0; } timer += Time.deltaTime; } }
2.僵尸AI
using UnityEngine; using System.Collections; public class ZombieAI : MonoBehaviour { public enum FSMState { Wander, //随机游荡状态 Seek, //搜索状态 Chase, //追踪状态 Attack, //攻击状态 Dead, //死亡状态 } public float wanderSpeed = 0.9f; // 僵尸游荡速度 public float runSpeed = 4.0f; // 僵尸奔跑速度 public float wanderScope = 15.0f; // 游荡状态下,随机选择目标位置的范围 public float seekDistance = 25.0f; // 僵尸中枪后的搜索距离 public float disappearTime = 3.0f; // 僵尸尸体消失前的停留时间 public float attackRange = 1.5f; // 僵尸攻击距离 public float attackFieldOfView = 60.0f; // 僵尸攻击夹角 public float attackInterval = 0.8f; // 僵尸攻击间隔 public int attackDamage = 10; // 僵尸攻击力 public AudioClip zombieAttackAudio; // 僵尸攻击音效 public FSMState currentState; //僵尸当前状态 public float currentSpeed = 0.0f; //僵尸当前速度 public bool autoInit = false; //是否自动初始化僵尸状态 private Vector3 previousPos = Vector3.zero; //僵尸上一次停留位置 private float stopTime = 0; //僵尸的停留时间 private float attackTimer = 0.0f; //僵尸攻击计时器 private float disappearTimer = 0.0f; //僵尸尸体消失计时器 private bool disappeared = false; //僵尸尸体是否已经消失 private NavMeshAgent agent; //导航代理组件 private Animator animator; //动画控制器组件 private Transform zombieTransform;//僵尸transform组件 private ZombieHealth zombieHealth; //僵尸生命值管理组件 private ZombieSensor zombieSensor; //僵尸感知器组件 private ZombieRender zombieRender; //僵尸渲染器控制器组件 private Transform targetPlayer; //僵尸感知范围内的玩家 private bool firstInDead = true; //僵尸是否首次进入死亡状态 void OnEnable() { //获取僵尸的各种组件 agent = GetComponent<NavMeshAgent>(); animator = GetComponent<Animator>(); zombieHealth = GetComponent<ZombieHealth> (); zombieSensor = GetComponentInChildren<ZombieSensor> (); zombieRender = GetComponent<ZombieRender> (); zombieTransform = transform; //把僵尸感知到的玩家字段设置为null targetPlayer = null; //初始状态为死亡状态 currentState = FSMState.Dead; //禁用导航代理组件 agent.enabled = false; //自动初始化僵尸 if (autoInit) Born (); } //在指定位置初始化僵尸 public void Born(Vector3 pos) { zombieTransform.position = pos; Born (); } // public void Born() { //把僵尸感知到的玩家字段设置为null血量 targetPlayer = null; //把僵尸的初始化状态设置为游荡状态, currentState = FSMState.Wander; //初始化僵尸生命值 zombieHealth.currentHP = zombieHealth.maxHP; //启用导航代理组件 agent.enabled = true; agent.ResetPath (); //启用动画控制器 animator.applyRootMotion = false; GetComponent<CapsuleCollider> ().enabled = true; animator.SetTrigger("toReborn"); disappearTimer = 0; disappeared = false; firstInDead = true; currentState = FSMState.Wander; } //禁用僵尸对象 void Disable() { zombieTransform.gameObject.SetActive (false); } //定期更新僵尸状态机的状态 void FixedUpdate() { FSMUpdate (); } //僵尸状态机更新函数 void FSMUpdate() { //根据僵尸当前的状态调用相应的状态处理函数 switch (currentState) { case FSMState.Wander: UpdateWanderState(); break; case FSMState.Seek: UpdateSeekState(); break; case FSMState.Chase: UpdateChaseState(); break; case FSMState.Attack: UpdateAttackState(); break; case FSMState.Dead: UpdateDeadState (); break; } //如果僵尸处于非死亡状态,但是生命值减为0,那么进入死亡状态 if (currentState != FSMState.Dead && !zombieHealth.IsAlive) { currentState = FSMState.Dead; } } //判断僵尸是否在一次导航中到达了目的地 protected bool AgentDone() { return !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance; } //限制僵尸的当前移动速度,更新动画状态机 private void setMaxAgentSpeed(float maxSpeed) { Vector3 targetVelocity = Vector3.zero; if (agent.desiredVelocity.magnitude > maxSpeed) { targetVelocity = agent.desiredVelocity.normalized * maxSpeed; } else { targetVelocity = agent.desiredVelocity; } agent.velocity = targetVelocity; currentSpeed = agent.velocity.magnitude; //设置动画状态 animator.SetFloat("Speed", currentSpeed); } //计算僵尸在某个位置附近的停留时间 private void caculateStopTime() { if (previousPos == Vector3.zero) { previousPos = zombieTransform.position; } else { Vector3 posDiff = zombieTransform.position - previousPos; if (posDiff.magnitude > 0.5) { previousPos = zombieTransform.position; stopTime = 0.0f; } else { stopTime += Time.deltaTime; } } } //游荡状态处理函数 void UpdateWanderState() { //感知到周围有活着的玩家,进入追踪状态 targetPlayer = zombieSensor.getNearbyPlayer (); if ( targetPlayer != null) { currentState = FSMState.Chase; agent.ResetPath (); return; } //如果受到伤害,那么进入搜索状态 if (zombieHealth.getDamaged) { currentState = FSMState.Seek; agent.ResetPath (); return; } //如果没有目标位置,那么随机选择一个目标位置 if (AgentDone () ) { Vector3 randomRange = new Vector3 ( (Random.value - 0.5f) * 2 * wanderScope, 0, (Random.value - 0.5f) * 2 * wanderScope); Vector3 nextDestination = zombieTransform.position + randomRange; agent.destination = nextDestination; } //限制游荡的速度 setMaxAgentSpeed(wanderSpeed); //统计僵尸在当前位置附近的停留时间 caculateStopTime(); // 如果在一个地方停留太久(各种原因导致僵尸卡住) // 那么选择僵尸背后的一个位置当做下一个目标 if(stopTime > 1.0f) { Vector3 nextDestination = zombieTransform.position - zombieTransform.forward * (Random.value) * wanderScope; agent.destination = nextDestination; } //进入普通状态 if(zombieRender!=null) zombieRender.SetNormal(); } //搜索状态处理函数 void UpdateSeekState() { //如果僵尸感知范围内有玩家,进入追踪状态 targetPlayer = zombieSensor.getNearbyPlayer (); if ( targetPlayer != null) { currentState = FSMState.Chase; agent.ResetPath (); return; } //如果僵尸受到攻击,那么向着玩家开枪时所在的方向进行搜索 if (zombieHealth.getDamaged) { Vector3 seekDirection = zombieHealth.damageDirection; agent.destination = zombieTransform.position + seekDirection * seekDistance; //将getDamaged设置为false,表示已经处理了这次攻击 zombieHealth.getDamaged = false; } //如果到达搜索目标,或者卡在某个地方无法到达目标位置,那么回到游荡状态 if (AgentDone () || stopTime > 1.0f ) { currentState = FSMState.Wander; agent.ResetPath (); return; } //减速度限制为奔跑速度 setMaxAgentSpeed(runSpeed); //进入狂暴状态 if(zombieRender!=null) zombieRender.SetCrazy(); //计算停留时间 caculateStopTime(); } //追踪状态处理函数 void UpdateChaseState() { //如果僵尸感知范围内没有玩家,进入游荡状态 targetPlayer = zombieSensor.getNearbyPlayer (); if (targetPlayer == null) { currentState = FSMState.Wander; agent.ResetPath (); return; } //如果玩家与僵尸的距离,小于僵尸的攻击距离,那么进入攻击状态 if (Vector3.Distance(targetPlayer.position, zombieTransform.position)<=attackRange) { currentState = FSMState.Attack; agent.ResetPath (); return; } //设置移动目标为玩家 agent.SetDestination (targetPlayer.position); //限制追踪的速度 setMaxAgentSpeed(runSpeed); //进入狂暴状态 if(zombieRender!=null) zombieRender.SetCrazy(); //计算停留时间 caculateStopTime(); } void UpdateAttackState() { //如果僵尸感知范围内没有玩家,进入游荡状态 targetPlayer = zombieSensor.getNearbyPlayer (); if (targetPlayer == null) { currentState = FSMState.Wander; agent.ResetPath (); animator.SetBool ("isAttack", false); return; } //如果玩家与僵尸的距离,大于僵尸的攻击距离,那么进入追踪状态 if (Vector3.Distance(targetPlayer.position, zombieTransform.position)>attackRange) { currentState = FSMState.Chase; agent.ResetPath (); animator.SetBool ("isAttack", false); return; } PlayerHealth ph = targetPlayer.GetComponent<PlayerHealth> (); if (ph != null) { //计算僵尸的正前方和玩家的夹角,只有玩家在僵尸前方才能攻击 Vector3 direction = targetPlayer.position - zombieTransform.position; float degree = Vector3.Angle (direction, zombieTransform.forward); if (degree < attackFieldOfView / 2 && degree > -attackFieldOfView / 2) { animator.SetBool ("isAttack", true); if (attackTimer > attackInterval) { attackTimer = 0; if (zombieAttackAudio != null) AudioSource.PlayClipAtPoint (zombieAttackAudio, zombieTransform.position); ph.TakeDamage (attackDamage); } attackTimer += Time.deltaTime; } else { //如果玩家不在僵尸前方,僵尸需要转向后才能攻击 animator.SetBool ("isAttack", false); zombieTransform.LookAt(targetPlayer); } } //攻击状态下的敌人应当连续追踪玩家 agent.SetDestination (targetPlayer.position); //进入狂暴状态 if(zombieRender!=null) zombieRender.SetCrazy(); //限制追踪的速度 setMaxAgentSpeed(runSpeed); } void UpdateDeadState() { //统计僵尸死亡后经过的时间,如果超过了尸体消失时间,那么禁用该僵尸对象 if (!disappeared) { if ( disappearTimer > disappearTime) { zombieTransform.gameObject.SetActive (false); disappeared = true; } disappearTimer += Time.deltaTime; } //如果僵尸初次进入死亡状态,那么需要禁用僵尸的一些组件 if (firstInDead) { firstInDead = false; agent.ResetPath (); agent.enabled = false; GetComponent<CapsuleCollider> ().enabled = false; animator.applyRootMotion = true; animator.SetTrigger ("toDie"); disappearTimer = 0; disappeared = false; //进入普通状态 if(zombieRender!=null) zombieRender.SetNormal(); animator.ResetTrigger("toReborn"); } } }
3.僵尸感知实现
using UnityEngine; using System.Collections; using System.Collections.Generic; public class ZombieSensor : MonoBehaviour { public float SoundRange = 15.0f; //僵尸听觉距离 public float SightRange = 25.0f; //僵尸视觉距离 public float SightAngle = 60; //僵尸的视觉夹角 public float SensorInterval = 0.5f; //僵尸的感知时间间隔 private float senseTimer = 0.0f; //统计僵尸的感知时间 private Transform zombieTransform; //僵尸的位置 private Transform nearbyPlayer; //僵尸周围玩家对象的缓存 private Transform zombieEye; //僵尸眼睛的位置 void Start() { //获取僵尸的transform组件 zombieTransform = transform; //获取僵尸眼睛的transform组件 zombieEye = transform.FindChild ("eye").transform; } void FixedUpdate() { //以一定的时间间隔,进行感知 if (senseTimer >= SensorInterval) { senseTimer = 0; SenseNearbyPlayer (); } senseTimer += Time.deltaTime; } void SenseNearbyPlayer() { nearbyPlayer = null; //获得玩家对象 GameObject player = GameObject.FindGameObjectWithTag ("Player"); if (player != null) { //获得玩家的生命值管理组件,判断玩家是否活着 PlayerHealth ph = player.GetComponent<PlayerHealth> (); if (ph != null && ph.isAlive) { //计算玩家与僵尸之间的距离 float dist = Vector3.Distance (player.transform.position, zombieTransform.position); //如果玩家与僵尸的距离小于僵尸的听觉距离 if (dist < SoundRange) { //缓存这个玩家 nearbyPlayer = player.transform; } //如果玩家与僵尸的距离小于僵尸的视觉距离 if (dist < SightRange) { //计算玩家是否在僵尸的视角内 Vector3 direction = player.transform.position - zombieTransform.position; float degree = Vector3.Angle (direction, zombieTransform.forward); if (degree < SightAngle / 2 && degree > -SightAngle / 2) { Ray ray = new Ray(); ray.origin = zombieEye.position; ray.direction = direction; RaycastHit hitInfo; //判断玩家和僵尸之间是否存在遮挡物 if (Physics.Raycast (ray, out hitInfo, SightRange)) { if (hitInfo.transform == player.transform) { //如果僵尸能够看到玩家就缓存这个玩家 nearbyPlayer = player.transform; } } } } } } } //获得当前缓存的附近玩家对象,如果附近没有玩家则返回null public Transform getNearbyPlayer() { return nearbyPlayer; } }
4.僵尸狂暴皮肤渲染
using UnityEngine; using System.Collections; using UnityStandardAssets.CrossPlatformInput; public class ZombieRender : MonoBehaviour { private Renderer[] rends; //僵尸皮肤渲染器数组 private int rendCnt = 0; //僵尸皮肤渲染器数组计数器 void Start() { //获取僵尸身体各部分的皮肤渲染器 rends = GetComponentsInChildren<SkinnedMeshRenderer>(); //获取皮肤渲染器的个数 rendCnt = rends.Length; } //进入狂暴模式 public void SetCrazy() { //把僵尸皮肤渲染器材质属性EnableRim,在着色器中名为_RimBool,设置为1.0开启狂暴效果。 for(int i=0;i<rendCnt;i++) rends [i].material.SetFloat ("_RimBool", 1.0f); } //进入正常(非狂暴)模式 public void SetNormal() { //把僵尸皮肤渲染器材质属性EnableRim,在着色器中名为_RimBool,设置为0.0关闭狂暴效果。 for(int i=0;i<rendCnt;i++) rends [i].material.SetFloat ("_RimBool", 0.0f); } public void SetCrazy2() { float rimBool = 1.0f; float rimPower = 3.0f; for(int i=0;i<rendCnt;i++) { Renderer rend = rends [i]; rend.material.SetFloat ("_RimBool", rimBool); rend.material.SetFloat ("_RimPower", rimPower); } } public void SetNormal2() { float rimBool = 0.0f; float rimPower = 3.0f; for(int i=0;i<rendCnt;i++) { Renderer rend = rends [i]; rend.material.SetFloat ("_RimBool", rimBool); rend.material.SetFloat ("_RimPower", rimPower); } } }
5.逆向动力学IKController
using UnityEngine; using System.Collections; public class IKController : MonoBehaviour { Animator animator; // 玩家动画控制器 public bool isActive = true; // 是否启用IK public Transform lookObj = null; // 玩家头部IK标记物 public Transform leftHandObj = null; // 玩家左手IK标记物 public Transform rightHandObj = null; // 玩家右手IK标记物 public Transform bodyObj = null; // 玩家身体IK标记物 void Start(){ // animator = GetComponent<Animator> (); } void OnAnimatorIK() { if (animator) { if (isActive) { if (lookObj != null) { //设置玩家的头部IK,使玩家的头部面向头部IK标记物所在的位置 animator.SetLookAtWeight (1.0f); animator.SetLookAtPosition (lookObj.position); } if (bodyObj != null) { //设置玩家躯干IK,使玩家躯干的旋转角与bodyObj对象的旋转角度相同 animator.bodyRotation = bodyObj.rotation; } if (leftHandObj != null) { //设置玩家左手的IK,使玩家左手的位置尽量靠近leftHandObj对象,左手的朝向与leftHandObj相同 animator.SetIKPositionWeight (AvatarIKGoal.LeftHand, 1.0f); animator.SetIKRotationWeight (AvatarIKGoal.LeftHand, 1.0f); animator.SetIKPosition (AvatarIKGoal.LeftHand, leftHandObj.position); animator.SetIKRotation (AvatarIKGoal.LeftHand, leftHandObj.rotation); } if (rightHandObj != null) { //设置玩家右手的IK,使玩家右手的位置尽量靠近RightHandObj对象,右手的朝向与RightHandObj相同 animator.SetIKPositionWeight (AvatarIKGoal.RightHand, 1.0f); animator.SetIKRotationWeight (AvatarIKGoal.RightHand, 1.0f); animator.SetIKPosition (AvatarIKGoal.RightHand, rightHandObj.position); animator.SetIKRotation (AvatarIKGoal.RightHand, rightHandObj.rotation); } } else { //取消玩家角色的IK,使玩家角色的头部,驱赶,左右手的位置和朝向受正向动力学控制 animator.SetLookAtWeight (0); animator.SetIKPositionWeight (AvatarIKGoal.LeftHand, 0); animator.SetIKRotationWeight (AvatarIKGoal.LeftHand, 0); animator.SetIKPositionWeight (AvatarIKGoal.RightHand, 0); animator.SetIKRotationWeight (AvatarIKGoal.RightHand, 0); } } } }
6.切换枪械
using UnityEngine; using System.Collections; using UnityStandardAssets.CrossPlatformInput; public class PlayerWeaponSwitcher : MonoBehaviour { public Transform[] weaponList; //玩家武器列表 private IKController ikController; //玩家IK控制器 private int currentIdx = 0; //当前使用的枪序号 private int weaponNum = 0; //武器列表中的枪总数 void Start () { //获取玩家IK控制器 ikController = transform.GetComponent<IKController> (); //获取玩家当前枪的数量 weaponNum = weaponList.Length; //设置当前枪序号为0; currentIdx = 0; //让玩家使用下一把抢 changeNextWeapon (); } void Update () { //玩家按下Fire2,执行换枪逻辑 if (CrossPlatformInputManager.GetButtonDown("Fire2")) { changeNextWeapon (); } } public void changeNextWeapon() { //只有武器列表中的枪支数量大于1,才执行换枪动作 if (weaponNum <= 1) return; //取出下一把枪的序号 int newIdx = (currentIdx + 1) % weaponNum; //使用下一把枪的IK标记物,设置玩家角色的IK,使玩家正确持枪 Transform newWeapon = weaponList [newIdx]; Transform rightHand = newWeapon.Find ("RightHandObj"); Transform leftHand = newWeapon.Find ("LeftHandObj"); Transform gunBarrelEnd = newWeapon.Find ("GunBarrelEnd"); ikController.leftHandObj = leftHand; ikController.rightHandObj = rightHand; ikController.lookObj = gunBarrelEnd; //激活新枪,禁用旧枪 newWeapon.gameObject.SetActive (true); weaponList [currentIdx].gameObject.SetActive (false); //更新当前使用的武器序号 currentIdx = newIdx; } }
7.手电筒
using UnityEngine; using System.Collections; using UnityStandardAssets.CrossPlatformInput; //玩家手电控制脚本 public class FlashLightController : MonoBehaviour { private Light mylight; void Start () { //获取玩家的聚光灯对象 mylight = GetComponent<Light> (); } void Update () { //根据玩家的输入,开启或关闭手电(调整聚光灯对象的亮度) if (CrossPlatformInputManager.GetButtonDown ("Fire3")) { if (mylight.intensity < 0.1) mylight.intensity = 5f; else mylight.intensity = 0.0f; } } }