Mooc Hero 2 - SP(二)—— 脚本:僵尸AI、换枪逻辑

截图与代码均来自        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;
		}
			
	}
}

猜你喜欢

转载自blog.csdn.net/qq_27012963/article/details/80273657
今日推荐