总目录https://blog.csdn.net/qq_54263076/category_11900070.html?spm=1001.2014.3001.5482
1.敌人背后受击转身+背部攻击伤害翻倍
回顾上节课,我们已经完成了范围内检索敌人自动攻击,随机移动功能。简易的AI已经完成了,淡是敌人还是有些呆,例如从背后偷袭,敌人好像没有感觉似的并不会转过身来,说道背后偷袭,顺便可以完成背后伤害按倍增长功能。由于并不需要频繁检测,只需要在攻击的一瞬间完成,所以这个代码并不需要写在敌人行为脚本enemybehavior,只需要写在攻击碰撞盒脚本attacktrigger
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class attackTrigger : MonoBehaviour
{
private Animator ani;//代码优化 获取自己动画器
private AnimatorStateInfo state;//动画状态
public float atkItemsBack = 1;
public float atkItemsUp = 1;
public float playerSpeedInfectBack = 1;
public float backAtkTime = 2;//背后攻击伤害倍率
// Start is called before the first frame update
void Start()
{
ani = transform.parent.GetComponent<Animator>();
}
public void OnTriggerEnter2D(Collider2D collision)
{
//攻击到了物品或敌人产生击退
if (collision.tag != transform.parent.tag && collision.tag!="ground" && collision.tag!="notAtkCheckCollision")//由于ground和bottom有碰撞盒但不需要攻击检测,需要剔除,给bottom标签notAtkCheckCollision
{
//获取人物与物品位置向量
Vector3 v = collision.transform.position-transform.parent.position ;
//冻结z轴
v.z = 0;
//获取横轴,速度影响击退距离
float h = Input.GetAxis("Horizontal");
//角色标签额外有挑飞效果 //如果处于动画2,4时额外实施向上的力、速度
if (transform.parent.tag == "Player")
{
//挑飞
state = ani.GetCurrentAnimatorStateInfo(0);
if (state.IsName("attack2") || state.IsName("attack4"))
{
v.y += (atkItemsBack *12* atkItemsUp);
}
}
collision.GetComponent<Rigidbody2D>().velocity = v * atkItemsBack + Vector3.right * h * playerSpeedInfectBack * 5;
}
//攻击到了敌人 代码更新判断条件为碰撞体的标签跟自己的标签不一样,这样敌人也可以对主角产生效果。
if (collision.tag == "Player" || collision.tag == "enemy")
{
if (collision.tag != transform.parent.tag)
{
//敌人受伤
//找到被攻击到的敌人
GameObject enemyGo = collision.gameObject;
//背后攻击
if (collision.gameObject.GetComponent<CharacterPanel>().lookdir * (collision.gameObject.transform.position.x - transform.parent.position.x) > 0)
{
enemyGo.GetComponent<CharacterPanel>().hurt(transform.parent.GetComponent<CharacterPanel>().Atk * backAtkTime);
//如果被攻击的是敌人,转身
if (collision.tag == "enemy")
{
collision.GetComponent<CharacterPanel>().Turndir();
collision.GetComponent<EnemyBehavior>().moveTime += 1;//移动时间加1s
}
}
else {
enemyGo.GetComponent<CharacterPanel>().hurt(transform.parent.GetComponent<CharacterPanel>().Atk);
}
//伤害计算 调用角色面板脚本的hurt函数
}
}
}
}
2.寻路跟随敌人
随机移动的坏处就是看到敌人(主角)会略过去,按照自己的行为行走,所以我们要再写一个行为脚本,用来看到敌人后的行为,当然如果没有看到敌人还是随机移动的函数
其中黄线为视线线,变红色为找到主角,并记录此时主角的位置
嗯,主要思路就是设置一个追寻时间变量,如果这段时间变量不为零的话,就射线检测,直接记录上一次看到敌人的位置然后尽量移动到这个点上,如果没有检测到,然后每过一秒追寻时间减少1;检测到话就重置追寻时间为常数。如果变量为零的话,就执行随机移动。
在enemybehavior脚本下,当然脚本的Debug函数可以不写,只是为了结果可视化我写上去划线的
using System.Collections;
using UnityEngine;
public class EnemyBehavior : MonoBehaviour
{
private CharacterPanel characterPanel;
public float moveTime = 3;//移动时间
private float _moveTime;//定值
public float waitTime = 3;//等待时间
private float _waitTime;//定值
private bool OneIsMustJump = true;//一次头顶检测,跳跃判断
private int OneTurn = 1;//一次转向,防止抽搐
private Vector2 playerPoi;
public float findDistance = 1;//寻找距离
public float findAngle = 1;//寻找角度
public float followTime = 3;//跟随时间
private float _followTime;//定值
// Start is called before the first frame update
void Start()
{
characterPanel = transform.GetComponent<CharacterPanel>();
_moveTime = moveTime;
_waitTime = waitTime;
_followTime = followTime;
followTime = 0;//默认起初不跟随
}
// Update is called once per frame
void Update()
{
JumpObstacle();
FindPlayer();
//如果有跟随时间的话,就只请跟随函数,如果没有跟随时间就实行随机移动按数。
if (followTime <= 0)
{ RandomMove(); }
else
{
FollowPlayer();
followTime -= Time.deltaTime;
}
CliffTurn();
}
//随机横向移动
private void RandomMove()
{
if (moveTime > 0)//如果处于移动时间
{
characterPanel.move();
moveTime -= Time.deltaTime;//移动时间减少一秒
if (moveTime < 0)
{
waitTime = _waitTime;//初始化等待时间
}
}
else
{
characterPanel.idle();
if (waitTime > 0)//如果处于等待时间
{
waitTime -= Time.deltaTime;//等待时间减少一秒
}
else
{
moveTime = _moveTime;//初始化移动时间
//等待结束,随机转向
bool Isturn = (Random.value > 0.5f);
if (Isturn)
{
characterPanel.Turndir();//转向
}
}
}
}
//跳跃障碍物
private void JumpObstacle()
{
for (int i = -1; i <= 1; i++)//i取-1,0,1,使得y轴变正负,三条射线检测,形成一个小扇角
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, characterPanel.lookdir * new Vector3(0.25f, -0.15f * i, 0),
2, LayerMask.GetMask("tilemap", "items"));//检测这俩种图层
if (hit)//检测到了有碰赚点
{
if (Vector2.Distance(hit.point, transform.position) < Vector2.Distance(playerPoi, transform.position))//如果碰撞点的距离大于与敌人(主角)的距离就不进行跳跃,防治这张卡墙角。避免攻击。
{
Debug.DrawLine(transform.position, hit.point);
if (hit.point.y <= transform.position.y)//脚底前方或前方检测到
{
characterPanel.jump();
}
else
{ //头顶前方检测到 下面代码含义为,头顶检测到首先判断要不要跳,还是钻过去,
//如果钻过去下面,就要使得3秒内OneIsMustJump为flase,走过去,三秒后如果头顶前方有东西,再进行一次判定再
bool mustJump = false;
if (OneIsMustJump)
{
mustJump = (Random.value > 0.5f);//进行一次判断是否跳跃
StartCoroutine(reMustJump()); //开启协程恢复OneIsMustJump为true
OneIsMustJump = false;
}
if (mustJump)
{
if (moveTime < _moveTime)
{
moveTime += Time.deltaTime;//移动时间加一秒确定能让他跳过去;
}
characterPanel.jump();
}
}
}
}
else
{
Debug.DrawRay(transform.position, characterPanel.lookdir * new Vector3(0.25f, -0.15f * i, 0));
}
}
}
IEnumerator reMustJump()
{
yield return new WaitForSeconds(3);
moveTime += Time.deltaTime;
OneIsMustJump = true;
}
//悬崖勒马,不让角色跳崖
private void CliffTurn()
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, new Vector2(0, -1),
Mathf.Infinity, LayerMask.GetMask("tilemap"));
if (!hit)//检测没有有碰撞点
{
if (OneTurn >= 1)
{
StartCoroutine(turnDir());//协程转向,转向一次后延迟0.5秒才能下一次转向,防止过短时间内频繁转向,抽搐
OneTurn--;
}
moveTime += 0.5f;
}
Debug.DrawRay(transform.position, new Vector2(0, -1), Color.blue);
}
IEnumerator turnDir()
{
characterPanel.Turndir();//转向
yield return new WaitForSeconds(0.5f);
OneTurn = 1;
}
//寻找敌人
private void FindPlayer()
{
for (int i = -2; i <= 2; i++)//使得y轴变正负,五条条射线检测,形成一个小扇角
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, characterPanel.lookdir * new Vector3(0.25f, -0.10f * i * findAngle, 0),
10 * findDistance, LayerMask.GetMask("tilemap", "items", "player"));//检测这三种图层
if (hit)//检测到了有碰赚点
{
if (hit.collider.tag == "Player")
{
followTime = _followTime;//找到主角,不执行随机移动
playerPoi = hit.point;//存储敌人找到的主角位置 更新找到的主角位置
Debug.DrawLine(transform.position, hit.point, Color.red);
}
else
{
Debug.DrawLine(transform.position, hit.point, Color.yellow);
}
}
else
{
Debug.DrawRay(transform.position, (characterPanel.lookdir * new Vector3(0.25f, -0.15f * i * findAngle, 0)).normalized * 10 * findDistance, Color.yellow);
}
}
}
//跟随移动函数
private void FollowPlayer()
{
Vector2 v = new Vector2(playerPoi.x - transform.position.x, playerPoi.y - transform.position.y);//获取上一次看到的主角与自己的位置向量
if (v.x * characterPanel.lookdir < 0)
{//如果位置向量与朝向相反,转向
if (OneTurn >= 1)
{
StartCoroutine(turnDir());//协程转向,转向一次后延迟0.5秒才能下一次转向,防止过短时间内频繁转向,抽搐
OneTurn--;
}
}
characterPanel.move();//向前移动
if (v.y > 2 && v.x < 3)
{
characterPanel.jump();
}
}
}
3.模块化思想
像上面咱们刚写敌人转身和自动追求敌人功能。如果你不想做一个非常让人玩不下去的游戏(都会追踪太困难了,不适合大部分玩家(除了高玩))的话,这应该是特殊体所具有的功能,你或者可以起我记得高级的行为,并把它模块化,一个一个分开,然后分别设置布尔变量当布尔之为真的时候,就会执行一些特殊行为。我们生成敌人的时候,我可以通过控制这些布尔值来生成各种行为组合的敌人,使得敌人不单一化,枯燥华。这就是模块化思想。
完成了!!!