概述
第十二章:怪物系统
怪物功能是交互的重要部分,涉及到任务系统、人物状态系统等等,设计起来也较为复杂。
首先将Model中的小狼拖入场景,命名为WolfBaby,并添加动画信息
12.1 小狼的状态切换和移动
为其添加一个脚本WolfBaby以控制其行为
using UnityEngine;
using System.Collections;
public enum WolfBabyState{
//几种枚举状态,对应怪物的不同形态
Idle,
Walk,
Attack,
Death
}
public class WolfBaby : MonoBehaviour {
public WolfBabyState state = WolfBabyState.Idle;
//默认动画为站立
public string aniName_death;
//对应不同的动画
public string aniName_walk;
public string aniName_idle;
public string currentAniName;
//当前播放的动画
public float changeTime = 3;
//动画改变的时间间隔
public float timer = 0;
//定时器
void Awake()
{
currentAniName = aniName_idle;
}
void Update()
{
if (state == WolfBabyState.Death)
//根据state判断当前的状态,并做出相应操作
{
animation.CrossFade(aniName_death);
}
else if(state == WolfBabyState.Attack)
//为攻击状态
{
//todo,对应下文的AutoAttack
}
else
//巡逻状态
{
animation.CrossFade(currentAniName);
timer += Time.deltaTime;
定时器开始工作
if(timer >= changeTime)
//当定时器大于3秒时
{
timer = 0;
RandomState();
//随机生成一种动画
animation.CrossFade(currentAniName);
}
}
}
void RandomState()
{
int value = Random.Range (0, 2);
if (value == 0)
{
currentAniName = aniName_idle;
} else
{
currentAniName = aniName_walk;
}
}
}
可以看到小狼有两种不同的动画,每隔3秒改变一次,但在Walk状态下的小狼无法移动,需要改进
因此添加一个Character Controller,用以控制小狼的移动,并在state == WolfBabyState.Walk的时候调用SimpleMove()控制移动。
private CharacterController cc;
public float speed = 0.5f;
void Awake()
{
cc = this.GetComponent<CharacterController> ();
}
if(currentAniName == aniName_walk)
{
cc.SimpleMove(transform.forward * speed);
}
此时的小狼只会朝一个方向进行移动,因此我们修改RandomState()中的设置,在切换到Walk状态时给小狼一个随机的方向。
void RandomState()
{
int value = Random.Range (0, 2);
if (value == 0)
{
currentAniName = aniName_idle;
} else
{
if(currentAniName != aniName_walk)
//当value为1时,即下一个播放状态为Walk,但当前状态仍为Idle的时候,改变小狼朝向的角度
{
transform.Rotate(transform.up * Random.Range(0,361));
//随机一个角度,改变朝向
}
currentAniName = aniName_walk;
}
}
12.2 小狼遭受攻击
处理完移动行为后,接下来还有自动攻击,被攻击等功能,先处理被攻击功能。为小狼创建一个血量。
public int hp = 100;
//怪物血量
public float missRate = 0.2;
//怪物闪避率
public void BeDamaged(int attackValue)
{
int value = Random.Range (0f, 1f);
//生成一个0~1之间的随机数,与missRate比较,若小于,则产生miss
if (value > this.missRate)
{
this.hp -= attackValue;
//扣除血量
if(hp <= 0)
{
state = WolfBabyState.Death;
//播放死亡动画,2秒后销毁
Destroy(this.gameObject,2);
}
}
}
这样达到了扣血的目的,但被攻击时的效果需要直观地显示出来,以更好地提示用户,因此我们通过Skinned Mesh Renderer组建控制怪物颜色的改变,在WolfBaby中定义两种颜色,对应普通状态和受击时的颜色。
我们定义一般状态的颜色normalColor,并通过协程控制颜色的改变,起到伤害的颜色效果
private Color normalColor; //存储原始的颜色,当被击效果结束后可以返回原样
private GameObject wolfBody;
void Awake()
{
wolfBody = transform.Find ("Wolf_Baby").gameObject;
//访问到控制颜色的子物体Wolf_Baby
normalColor = wolfBody.renderer.material.color;
}
IEnumerator ShowWolfRed()
//通过协程代替计时器,更加简单,协程的概念见https://blog.csdn.net/jasonwang18/article/details/55519165
{
wolfBody.renderer.material.color = Color.red;
yield return new WaitForSeconds (1f);
wolfBody.renderer.material.color = normalColor;
}
并在受到伤害时调用StartCoroutine(ShowWolfRed())即可
12.3 MISS效果
攻击怪物时加入一个闪避效果可以提高游戏体验,因此我们为Miss效果添加一个AudioClip,实现提示效果,在if (value > this.missRate)时,添加
AudioSource.PlayClipAtPoint(missSound,transform.position);
导入HUD Text创建Miss效果,将HUD Text放到UI root下,
它包含的UIFollow Target用以跟随主角或怪物,文本HUD Text用以显示Miss或扣血效果。在UI root下创建一个Invisible Widget,命名为HUDTextParent,并为其创建一个脚本HUDTextParent,并设置为单例模式。在每个物体创建时新增一个HUD Text
之后在WolfBaby中添加一个Empty物体,用以存放HUD Text,命名为WolfHUDtext,并将WolfBaby做成一个Prefab
private GameObject wolfHUDTextGO;
//WolfBaby下的HUD Text
private GameObject HUDTextGO;
//UI root下的HUDTextParent下的HUD Text
public GameObject HUDTextPrefab;
//HUD Text的prefab,直接导入即可
private HUDText showText;
//HUDTextGO下的Text信息,控制显示
private UIFollowTarget followTarget;
//HUDTextGO下的位置信息,控制位置
void Awake()
{
wolfHUDTextGO = transform.Find ("WolfHUDText").gameObject;
}
void Start()
{
HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;
//HUDTextParent下的HUD Text由Prefab得到
HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;
//并将这一Prefab作为HUDTextParent的子类
showText = HUDTextGO.GetComponent<HUDText> ();
//取得文本和位置信息
followTarget = HUDTextGO.GetComponent<UIFollowTarget> ();
followTarget.target = wolfHUDTextGO.transform;
//followTarget中的位置跟随小狼的移动
followTarget.gameCamera = Camera.main;
//followTarget中的Camera为main Camera
followTarget.uiCamera = UICamera.current.GetComponent<Camera> ();
//followTarget中的UICamera为current Camera
}
初始化完成后,在Miss时进行测试
public void BeDamaged(int attackValue)
{
if (value > this.missRate)
{
}
else
{
AudioSource.PlayClipAtPoint(missSound,transform.position);
showText.Add("Miss",Color.gray,1);
//添加显示的文本、颜色和时间
}
}
我们添加一个方法,作为模拟攻击测试。在Update()中,通过按下“A”键起到模拟的作用
if (Input.GetKeyDown (KeyCode.A))
{
BeDamaged(1);
}
被攻击时,小狼会变为红色,但miss文字的显示有问题(上图灰色部分所示,字体过于巨大)
问题出现在
HUDTextGO = GameObject.Instantiate (HUDTextPrefab, Vector3.zero, Quaternion.identity) as GameObject;
HUDTextGO.transform.parent = HUDTextParent._instance.gameObject.transform;
即创建HUDText物体时的问题,我们用NGUITool创建可以避免这一情况。
即
HUDTextGO = NGUITools.AddChild (HUDTextParent._instance.gameObject, HUDTextPrefab);
结果如下。
在怪物被杀死时,我们要销毁WolfBaby,并且销毁HUDText,在WolfBaby脚本中添加
if (hp <= 0)
{
state = WolfBabyState.Death;
Destroy (this.gameObject, 2);
GameObject.Destroy(HUDTextGO);
}
12.4 敌人的自动攻击部分
自动攻击的设计关系到AI的智商,这里只涉及基本的AI操作,一些高端的“拉怪”操作不在考虑范围内。。。
12.4.1 自动攻击逻辑
当state == WolfBabyState.Attack时,我们需要让小狼自动攻击。攻击包括下面几个属性
public int attackValue;
//攻击伤害
public string aniName_normalAttack;
//正常攻击
public string aniName_crazyAttack;
//疯狂攻击,提升attackRate,即攻击速率
public string aniName_nowAttack;
//当前攻击的种类
public float normalAttackTime;
//普通攻击消耗时间
public float crazyAttackTime;
//疯狂攻击消耗时间
public float crazyAttackRate;
//疯狂攻击触发的概率
public int attackRate = 1;
//攻击速率,默认为1秒1次
public float attackTimer = 0;
//攻击的计时器,决定attackRate
public Transform target;
//攻击目标,当触发BeDamage函数时获取目标
属性如下
自动攻击的逻辑为
- 当人物与小狼的距离小于可攻击距离时,进行攻击 (distance < acceptAttackDistance ,攻击)
- 当人物与小狼的距离大于可攻击距离并且小于最大攻击距离时,移动到最小距离之内,再攻击 (acceptAttackDistance < distance < maxAcceptAttackDistance,,移动再攻击)
- 当人物与小狼距离大于最大攻击距离时,返回巡逻状态 (distance > maxAcceptAttackDistance ,取消攻击状态)
因此对函数AutoAttack()地设置如下
public float minAttackDistance = 2f;
public float maxAttackDistance = 5f;
void AutoAttack()
{
if (target != null)
//取得目标
{
float distance = Vector3.Distance(target.position,transform.position);
//计算距离
if(distance > maxAttackDistance)
//大于最大攻击距离时,切换到巡逻状态
{
target = null;
state = WolfBabyState.Idle;
}
else if(distance <= minAttackDistance)
//小于最小攻击距离时,攻击
{
}
else
//介于最小与最大攻击距离时,移动到攻击距离内再攻击
{
transform.LookAt(target);
cc.SimpleMove(transform.forward * speed);
animation.CrossFade(aniName_walk);
}
}
else
{
state = WolfBabyState.Idle;
}
}
12.4.2 攻击行为的切换与播放
攻击行为可以拆分成两部分:攻击和距离下一次攻击开始的休息时间。因此我们将攻击状态分为3种:普通攻击、疯狂攻击以及攻击的休息间隔
先考虑distance <= minAttackDistance的情况
else if(distance <= minAttackDistance)
{
attackTimer += Time.deltaTime;
//计时器开启
animation.CrossFade(aniName_nowAttack);
//先播放当前攻击动画
if(aniName_nowAttack == aniName_normalAttack)
//判断当前攻击动画的种类
{
if(attackTimer >= normalAttackTime)
//大于播放时间后,造成伤害
{
//todo,造成伤害
animation.CrossFade(aniName_idle);
}
}
else if(aniName_nowAttack == aniName_crazyAttack)
{
if(attackTimer >= crazyAttackTime)
{
//todo
animation.CrossFade(aniName_idle);
}
}
if(attackTimer > (1f/attackRate))
//如果大于攻击休息间隔时,随机一种攻击动画并重置计时器,实现一个攻击的循环
{
RandomAttack();
attackTimer = 0;
}
}
void RandomAttack()
{
float value = Random.Range (0f, 1f);
if (value > crazyAttackRate)
{
aniName_nowAttack = aniName_normalAttack;
} else
{
aniName_nowAttack = aniName_crazyAttack;
}
}
这样就实现了攻击动画和攻击行为的循环,但暂时没有取得target目标,target要在角色对小狼造成伤害后将主角信息传递给小狼,之后进行补充。
ps:(5月3日补充)中型狼和Boss狼的创建。
中型狼和大型BOSS狼
中型狼和大型狼的素材都在RPG——>Model——>Model Enemy之中,拖入场景之中
与小狼类似,我们为其添加Animation动画和角色控制器,我们使用WolfBaby的脚本,稍作修改即可。
主要涉及修改部分:Animation动画、自身gameObject的指定、属性值以及攻击距离(大狼体积较大,攻击距离过小会导致无法正常攻击)
最后
以上就是坚强云朵为你收集整理的RPG游戏《黑暗之光》流程介绍与代码分析之(十二):怪物系统的实现的全部内容,希望文章能够帮你解决RPG游戏《黑暗之光》流程介绍与代码分析之(十二):怪物系统的实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复