Unity3D自带案例AngryBots分析(三)——怪物激活、攻击、动作逻辑控制分析,第一个怪物KamikazeBuzzer的攻击特效的实现原理

本文分析Unity3D案例AngryBots中的怪物逻辑,包括怪物激活通过EnemyArea.js脚本实现,怪物攻击依赖人物位置,当在威胁范围内时发动电弧攻击,同时运用LineRenderer创建闪电特效。怪物动作逻辑类似人物,但通过rigidbody.AddForce进行考虑质量的持续力控制,物理模拟在FixedUpdate中确保精确。
摘要由CSDN通过智能技术生成

       从Hierarchy视图中可以看见,Enemies对象下面挂有很多子对象,很多都是Prefab。而点击这些子对象,其实发现它们的很多地方有很大的相同之处,就拿SimpleBuzzers来看,里面的怪物KamikazeBuzzer都是相同的怪物Prefab,随便点击一个,都可以看见包含KamikazeMovementMotor.js脚本,BuzzerKamikazeControllerAndAi.js脚本,Health.js脚本,和DestroyObject.js脚本,Sphere Collider,Rigidbody,Audio Source等。以下分析就以SimpleBuzzers来进行的:


怪物的激活:

       每个SimpleBuzzer对象点击后,可以在Inspector中看到Transform、EnemyArea.js脚本及Box Collider。而EnemyArea.js就是用于激活怪物的脚本。

        在编辑器模式下,当我们点击SimpleBuzzer*时,可以看见Scene视图中会有相应的长方体框显示,这个框就是Box Collider的边界,标记了盒状碰撞器的范围,实际上也是怪物活动范围(或者说看守范围)。

#pragma strict
#pragma downcast
import System.Collections.Generic;

public var affected : List.<GameObject> = new List.<GameObject> ();//主要记录受影响的对象,即在此范围有事件发生时会采取动作的对象的集合

ActivateAffected (false);//初始化时将所有对象标记为未激活状态

function OnTriggerEnter (other : Collider) {//当有其它碰撞器碰撞到该触发器上时
	if (other.tag == "Player")
		ActivateAffected (true);//如果碰撞器是人物所带碰撞器时,将affected集合中所有的对象激活(即人物入侵,怪物激活采取动作)
}

function OnTriggerExit (other : Collider) {//当有其它碰撞器离开该触发器时
	if (other.tag == "Player")
		ActivateAffected (false);//如果碰撞器是人物所带碰撞器时,将affected集合中所有的对象设为非激活状态(即人物离开防范领地,不需要警戒状态,该干嘛干嘛去)
}

function ActivateAffected (state : boolean) {
	for (var go : GameObject in affected) {//将affected集合中的所有对象设置好状态
		if (go == null)
			continue;
		go.SetActive (state);//设置状态
		yield;
	}
	for (var tr : Transform in transform) {
		tr.gameObject.SetActive (state);
		yield;
	}
}
          当物理引擎在每固定时间帧去检测游戏中所有碰撞器、触发器等是否发生碰撞,如果发生碰撞就会将相应的Trigger消息或Collision消息发送给受到碰撞的两个物体,那么这两个物体上挂载的脚本会处理相应的消息事件。就像上面脚本中写的,是消息处理事件,是触发之后我们决定采取什么行动都写在消息事件处理函数中。人物入侵,所有怪物处于激活状态,怪物身上挂着的脚本就会开始执行了。


怪物的攻击:

       会根据人物的位置,设置怪物的移动目标movementTarget始终为人物所在位置;并根据与人物之间的距离判断是否在威胁范围内;电弧定时器到时并在威胁范围内并追到人物则发动攻击,对目标生命值造成伤害;施放电弧展示及音效;然后随机重置电弧发射的定时器。

#pragma strict

public var motor : MovementMotor;      //MovementMotor对象,保存移动方向、朝向、移动目标
public var electricArc : LineRenderer; //线渲染器,用于怪物发射电弧的绘制
public var zapSound : AudioClip;       //声频剪辑,用于怪物攻击产生电弧时伴随的声音
public var damageAmount : float = 5.0f;//受伤害的大小

private var player : Transform;         //人物的Transform
private var character : Transform;      //怪物的Transform
private var spawnPos : Vector3;         //怪物的产生点
private var startTime : float;            //启动时间
private var threatRange : boolean = false;//怪物是否受到威胁,即人物是否在怪物的攻击范围内
private var direction : Vector3;         //存储从怪物到人物的距离向量
private var rechargeTimer : float = 1.0f;//电弧显示定时器
private var audioSource : AudioSource;   //声源
private var zapNoise : Vector3 = Vector3.zero;//用于设置怪物对人物伤害的小随机变量

function Awake () {
	character = motor.transform;     //怪物Transform赋值
	player = GameObject.FindWithTag ("Player").transform;//人物Transform赋值,通过FindWithTag来获取
	
	spawnPos = character.position;   //怪物的位置
	audioSource = GetComponent.<AudioSource> ();//声源赋值
}

function Start () {
	startTime = Time.time; 
	motor.movementTarget = spawnPos; //怪物的移动目标为怪物产生点
	threatRange = false;//攻击范围没有受到侵犯,即人物不在怪物的攻击范围内	
}

function Update () {	
	motor.movementTarget = player.position; //怪物的移动目标始终为人物的位置
	direction = (player.position - character.position);//从怪物到人物的距离向量
	
	threatRange = false;//未受到威胁
	if (direction.magnitude < 2.0f) {//假如怪物和人物离得太近了
		threatRange = true;//怪物受到威胁
		motor.movementTarget = Vector3.zero;//不用移动了,原地呆着
	} 
	
	rechargeTimer -= Time.deltaTime;//电弧发射定时器减去上一帧花的时间
	//假如电弧显示定时器到时了,并且怪物受到威胁,并且怪物的forward方向(前方)与怪物和人物的距离向量之间的角度比较小
	if (rechargeTimer < 0.0f && threatRange && Vector3.Dot (character.forward, direction) > 0.8f) {
		zapNoise = Vector3 (Random.Range (-1.0f, 1.0f), 0.0f, Random.Range(-1.0f, 1.0f)) * 0.5f;//使每次人物受到伤害有些小随机		
		var targetHealth : Health = player.GetComponent.<Health> ();//人物生命值
		if (targetHealth) {
			var playerDir : Vector3 = player.position - character.position;//怪物到人物的距离向量
			var playerDist : float = playerDir.magnitude;//怪物到人物的距离
			playerDir /= playerDist;//归一化攻击向量			
			targetHealth.OnDamage (damageAmount / (1.0f + zapNoise.magnitude), -playerDir);//人物受到伤害
		}		

		DoElectricArc(); //施放电弧显示	
		
		rechargeTimer = Random.Range (1.0f, 2.0f);//随机重置电弧发射的定时器
	}
}

function DoElectricArc () {	
	if (electricArc.enabled)
		return;
	//播放声音
	audioSource.clip = zapSound;
	audioSource.Play ();
	
	//设置怪物电弧为enabled
	electricArc.enabled = true;
	
	zapNoise = transform.rotation * zapNoise;//使每次电到人物的位置不同
	
	//显示电弧,并绘制纹理(绘制多条连续线段来产生电弧效果)
	var stopTime : float = Time.time + 0.2;//电弧从现在开始持续0.2s
	while (Time.time < stopTime) {//如果没有电弧显示结束时间
		electricArc.SetPosition (0, electricArc.transform.position);//设置电弧一个端点
		electricArc.SetPosition (1, player.position + zapNoise);//设置电弧的另一个端点
		electricArc.sharedMaterial.mainTextureOffset.x = Random.value;//共享纹理设置
		yield;
	}
	
	//隐藏电弧
	electricArc.enabled = false;
}

  • 攻击特效主要利用DoElectricArc函数来表达攻击方式,函数里播放了声音效果,而且通过LineRenderer构造多条连续线段,来制造闪电弧效果。


怪物的动作逻辑:

       怪物的动作和人物动作控制逻辑差不多,都是继承类MovementMotor,并通过一些参数及movementTarget 来改变怪物的运动的。

#pragma strict
class KamikazeMovementMotor extends MovementMotor {
	
	public var flyingSpeed : float = 5.0;//怪物向前飞的速度
	public var zigZagness : float = 3.0f;//怪物移动影响因子
	public var zigZagSpeed : float = 2.5f;//怪物之字形移动速度
	public var oriantationMultiplier : float = 2.5f;//怪物方向旋转影响因子
	public var backtrackIntensity : float = 0.5f;//怪物回溯强度大小
	
	private var smoothedDirection : Vector3 = Vector3.zero;//怪物转动方向平滑因子
			
	function FixedUpdate () {
		var dir : Vector3 = movementTarget - transform.position;//移动方向设置为从自身位置到目标位置
		var zigzag : Vector3 = transform.right * (Mathf.PingPong (Time.time * zigZagSpeed, 2.0) - 1.0) * zigZagness;//怪物之字形移动速度

		dir.Normalize ();//移动方向归一化
		
		smoothedDirection = Vector3.Slerp (smoothedDirection, dir, Time.deltaTime * 3.0f);//获取平滑的移动方向,防止变换突兀
		var orientationSpeed = 1.0f;//旋转速度设置
				
		var deltaVelocity : Vector3 = (smoothedDirection * flyingSpeed + zigzag) - rigidbody.velocity;//速度差值
		if (Vector3.Dot (dir, transform.forward) > 0.8f)//移动方向和怪物现在的正前方夹角比较小的情况(即怪物只需稍微移动即可)
			rigidbody.AddForce (deltaVelocity, ForceMode.Force);//对怪物身上刚体施加外力作用
		else {//否则让怪物向相反方向移动
			rigidbody.AddForce (-deltaVelocity * backtrackIntensity, ForceMode.Force);//反速度方向的力,游戏中可以观察到怪物有的时候前进攻击,有的时候旋转,有的时候会后退伴随旋转	
			orientationSpeed = oriantationMultiplier;
		}
		
		//使怪物旋转到目标方向
		var faceDir : Vector3 = smoothedDirection;
		if (faceDir == Vector3.zero) {
			rigidbody.angularVelocity = Vector3.zero;//不旋转的时候,设置刚体转动角速度为zero
		}
		else { 
			var rotationAngle : float = AngleAroundAxis (transform.forward, faceDir, Vector3.up);//世界坐标系中,将怪物的transform中存储的前方,旋转到要面朝方向所需转动的角度
			rigidbody.angularVelocity = (Vector3.up * rotationAngle * 0.2f * orientationSpeed);//设置刚体角速度让其转起来
		}		
	
	}
	
	//方向dirA绕轴axis旋转到方向dirB所需转动的角度
	static function AngleAroundAxis (dirA : Vector3, dirB : Vector3, axis : Vector3) {
	    //dirA和dirB在与轴垂直的平面上的投影,这样以便得到两者直接的角度 
	    dirA = dirA - Vector3.Project (dirA, axis);
	    dirB = dirB - Vector3.Project (dirB, axis);
	    //dirA和dirB之间角度的正值
	    var angle : float = Vector3.Angle (dirA, dirB);
	   
	    //根据dirA旋转到dirB叉乘正方向与axis方向,得出旋转角度的正负
	    return angle * (Vector3.Dot (axis, Vector3.Cross (dirA, dirB)) < 0 ? -1 : 1);
	}	
	
	function OnCollisionEnter (collisionInfo : Collision) {//产生碰撞无动作
	}
	
}

  • rigidbody.AddForce (deltaVelocity, ForceMode.Force);中为刚体施力的函数跟人物的不一样,人物利用加速模式,而对怪物利用的是考虑质量的持续的力,在每个FixedUpdate调用中持续一段时间。这种模式取决于刚体的质量,这样的话对于推或扭转更大质量的物体就需要更大的力。
  • 为什么物理因素作用下的运动变换都是在FixedUpdate函数中定义的,而没有在Update函数中定义?由于机器不同其帧速率不同,会使每秒调用Update函数次数也会不同,即使在同一台机器,不同秒帧速率也会因为场景需要渲染的三角面数量不同,而被调用次数不同,帧的间隔时间不一定。Update函数会使用该帧与上一帧的时间间隔,FixedUpdate函数会使用固定时间间隔,这样两者的时间差会导致每一帧出现误差,最后模拟出来的物理现象与理论不符合。物理引擎对刚体的各种模拟都是以FixedUpdate函数的时间间隔来计算的,使用Update函数会出错。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值