10.Unity2D 横版 简单AI 之 敌人随机移动+自动巡逻+障碍物跳跃+悬崖处转身+射线检测

总目录

9.Unity2D 简单AI 之 敌人跳跃条件优化+自动范围内检测敌人发起攻击(索敌)+对象池优化+主角受伤死亡_ζั͡ ั͡雾 ั͡狼 ั͡✾的博客-CSDN博客Unity2D 简单AI 之 敌人跳跃条件优化+自动范围内检测敌人攻击+敌人二连击。在敌人预制体下,创建空物体EnemyCanAttack,改成不受攻击检测标签,加上触发器,加上触发器脚本。 在主角到该范围内,敌人开始攻击,加入脚本EnemyAttackBox。在1中已经增加了主角受伤动画,现在优化攻击判定的代码,使得敌人也可以使用脚本。。。......https://blog.csdn.net/qq_54263076/article/details/125713329?spm=1001.2014.3001.5501

上一节课我们专门做了进入敌人前方范围,敌人自动攻击,所以这节课我们专门做AI寻路功能,将敌人动起来,既要随机一点,也要智能一些,接上一节的自动攻击AI。这样敌人就可以随机移动而攻击了,这个功能困难的一点是如何让敌人跳跃上方头顶地面和跳跃下方地面。我们先从 简单的做起。

1.优化CharacterPanel(角色面板)脚本

首先我们对角色PlayerControl脚本(主角控制)中提取主角和敌人共有的变量和函数(像跳跃,移动之类)到CharacterPanel(角色面板)脚本中,这样写敌人行为脚本的时候,好调用且好共同调整修改。

playercontrol脚本(只有主角有)

现阶段主角物体组件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class playerControl : MonoBehaviour
{
  
    private Rigidbody2D rig;//2D刚体
    private CharacterPanel characterPanel;//获取角色面板
    private Animator ani;//动画控制器
    private AnimatorStateInfo state;//动画状态
                         // Start is called before the first frame update
    void Start()
    {
        rig = GetComponent<Rigidbody2D>();//获取刚体
        ani = GetComponent<Animator>();//获取动画控制器
        characterPanel = GetComponent<CharacterPanel>();//获取角色面板


    }

    // Update is called once per frame
    void Update()
    {
        move();//移动函数
        attack();//攻击函数-四连击

    }
    private void move()
    {    //水平,垂直俩个轴系
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float dir = Input.GetAxisRaw("Horizontal");//方向-1 0 1
        //跳跃
        if (v > 0 &&transform.GetComponent<CharacterPanel>().Iscanjump == true)
        {
            characterPanel.jump();
        }
        //长按高跳
        if (rig.velocity.y > 0 && Input.GetKey(KeyCode.W)|| Input.GetKey(KeyCode.UpArrow) && transform.GetComponent<CharacterPanel>().Iscanjump == false)
        {
            rig.velocity += Vector2.up * 0.2f;//长按高跳额外得到向上速度
        }

        //方向改变
        if (dir != 0)
        {
            transform.localScale = new Vector3(dir* Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z);//通过改变scale改变方向
        }
        //按键左右移动
        Vector3 vt = new Vector3(h, 0, 0).normalized;//vt为俩个轴系合成的方向向量,normalized单位化
            //移动动画
        if (dir != 0)
        {
            ani.SetBool("Ismove", true);
        }
        else { ani.SetBool("Ismove", false); }
   
        //空中左右移动,为地面jumpcharacterPanel.speedVertiacal倍
        if (h != 0 && transform.GetComponent<CharacterPanel>().Iscanjump == false)
        {
            gameObject.transform.Translate(vt * characterPanel.speed * characterPanel.jumpspeedVertiacal * Time.deltaTime);//通过这个函数来使用vt使得左右移动
        }
        //地面左右移动
        else { gameObject.transform.Translate(vt * characterPanel.speed * Time.deltaTime); }
    }
    private void attack() {
        

        state = ani.GetCurrentAnimatorStateInfo(0);
        //判断播放完
        if ((state.IsName("attack1") || state.IsName("attack2") || state.IsName("attack3") || state.IsName("attack4")) && state.normalizedTime >= 1.0f)
        {
            
            ani.SetInteger("attack", 0);

        }

        if (Input.GetKey(KeyCode.J))
        {
    
            if (state.IsName("idle")||  state.IsName("move") && ani.GetInteger("attack")==0 )
            {
            
                ani.SetInteger("attack", 1);
            } else if (state.IsName("attack1")&& ani.GetInteger("attack") == 1 )
            {
                ani.SetInteger("attack", 2);
            }
            else if (state.IsName("attack2") && ani.GetInteger("attack") == 2)
            {
                ani.SetInteger("attack", 3);
            }
            else if (state.IsName("attack3") && ani.GetInteger("attack") == 3)
            {
                ani.SetInteger("attack", 4);
            }
        }
        if (state.normalizedTime >=1.0f) 
        {
            ani.SetFloat("normalizedTime", state.normalizedTime);
        }


        }
}

characterpanel脚本(主角敌人共有)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CharacterPanel : MonoBehaviour
{
    public bool Iscanjump = false;//是否能跳跃,默认不能
    public float Hpmax = 100;//最大生命
    public float Hp = 100;//生命
    public float Atk = 10;//攻击力
    public float AtkRan = 1;//攻击浮动
    public float Def = 10;//防御力
    public float lookdir;//获取初始Scale.x,用于转向 
    public float dropConst = 15;//下坠常数
    public float speed = 10;//地面移动速度
    public float jumpspeedUp = 20;//上升速度
    public float jumpspeedVertiacal = 0.5f;//空中左右移动速度
    private Rigidbody2D rig;//2D刚体
    private Animator ani;
    private Transform Canvas;//获取角色个人UI面板
    // Start is called before the first frame update
    void Start()
    {
        Canvas = transform.Find("Canvas");
        ani = transform.GetComponent<Animator>();
      
        rig = GetComponent<Rigidbody2D>();//获取刚体
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        lookdir = transform.localScale.x;
        check();
        //跳跃优化手感
        Quickjump();

    }
    //标准化检查
    private void check()
    {
        if (Hp > Hpmax) Hp = Hpmax;//血量不超过上限
        if (Hp <= 0) { Hp = 0;death(); };//血量不超过下限,且死亡
    }
    //受伤,其他脚本调用
    public void hurt(float atk)
    {
       float hurtnum= (20 * atk / (20 + Def)) + Random.Range(-AtkRan, AtkRan);
        //受伤动画 为什么要将触发器中的受伤动画移动在角色面板的hurt函数下?因为受伤不仅有角色攻击受伤,还有陷阱,掉落等伤害,所以直接血量减少时播放受伤动画更加好
        transform.GetComponent<Animator>().SetBool("Ishurt", true);
        StartCoroutine(endHurt());//开启协程结束受伤动画
        //伤害数值显示
        Canvas.GetComponent<CharaCanvas>().ShowHurtText(hurtnum);
        Hp -= hurtnum;
    
    }
    IEnumerator endHurt()
    {
        yield return 0;//此处暂停,下一帧执行
        transform.GetComponent<Animator>().SetBool("Ishurt", false);
    }
    //死亡
    private void death()
    {
        ani.SetBool("Isdeath",true);
    }
    //跳跃
    public void jump()
    {
        if (Iscanjump == true)
        {
            rig.velocity = new Vector2(0, jumpspeedUp);//设置刚体速度,给予向量
        }
    }
    //优化跳跃手感,迅速下落,放入帧频率更新函数里面
    public void Quickjump()
    {
        float a = dropConst * 5 - Mathf.Abs(rig.velocity.y);//通过下坠常数,空中速度快为0时,下坠常数a越大,即越快速 度过这个状态
        rig.velocity -= Vector2.up * a * Time.deltaTime;
    }
    //以下是敌人的调运行为函数,主角有自己的控制脚本
    //转向
    public void Turndir()
    {
          transform.localScale = new Vector3(-lookdir, transform.localScale.y, transform.localScale.z);//通过改变scale改变方向
    }
    //移动
    public void move()
    {
        Vector3 vt = new Vector3(lookdir/Mathf.Abs(lookdir), 0, 0);
        //空中左右移动,为地面jumpcharacterPanel.speedVertiacal倍
        if (Iscanjump == false)
        {
            gameObject.transform.Translate(vt * speed * jumpspeedVertiacal * Time.deltaTime);//通过这个函数来使用vt使得左右移动
        }
        //地面左右移动
        else { gameObject.transform.Translate(vt * speed * Time.deltaTime); }
        ani.SetBool("Ismove", true);

    }
    //等待
    public void idle()
    {
        ani.SetBool("Ismove", false);
    }
}

2.敌人行为脚本EnemyBehavior,放在在敌人物体上

现阶段敌人物体组件

(1)随机移动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyBehavior : MonoBehaviour
{
    private CharacterPanel characterPanel;
    public float moveTime=3;//移动时间
    private float _moveTime;//定值
    private float waitTime=3;//等待时间
    private float _waitTime;//定值
    // Start is called before the first frame update
    void Start()
    {
        characterPanel = transform.GetComponent<CharacterPanel>();
        _moveTime = moveTime;
        _waitTime = waitTime;
    }

    // Update is called once per frame
    void Update()
    {
        RandomMove();

    }
    //随机横向移动
    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();//转向
                }
                

            }
        }
    }

    
}

我们能够发现问题,随机移动会跳下悬崖,也会前面有遮挡,确一直往前走,不能自动跳跃。解决这个问题我们需要进行射线检测

教学链接

Unity 常用射线检测方法_昵称好难写的博客-CSDN博客_unity射线检测1.普通射线检测(一般用于检测某一个物体)Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); Debug.DrawRay(ray.origin ,ray.direction , Color.red); RaycastHit hit; if(Physics .Raycast (ray,https://blog.csdn.net/qq_36274965/article/details/79456449?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165759657616782425140924%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165759657616782425140924&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-79456449-null-null.142%5Ev32%5Edown_rank,185%5Ev2%5Etag_show&utm_term=%E5%B0%84%E7%BA%BF%E6%A3%80%E6%B5%8B&spm=1018.2226.3001.4187

(2)自动跳跃障碍物+悬崖勒马+射线检测

白线三条是检测跳跃视线,蓝线是检测转向线。

白线中下线检测到物体直接跳跃,上线检测,进行一次判定,是跳上去还是钻过去

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyBehavior : MonoBehaviour
{
    private CharacterPanel characterPanel;
    public float moveTime=3;//移动时间
    private float _moveTime;//定值
    private float waitTime=3;//等待时间
    private float _waitTime;//定值

    private bool OneIsMustJump=true;//一次头顶检测,跳跃判断
    private int OneTurn = 1;//一次转向,防止抽搐
    // Start is called before the first frame update
    void Start()
    {
        characterPanel = transform.GetComponent<CharacterPanel>();
        _moveTime = moveTime;
        _waitTime = waitTime;
    }

    // Update is called once per frame
    void Update()
    {
        RandomMove();
        JumpObstacle();
        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)//检测到了有碰赚点
            {
                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;
                        Debug.Log(mustJump);
                    }
                    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);
        waitTime += 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--;
            }
            characterPanel.move();
        }
        Debug.DrawRay(transform.position , new Vector2(0, -1),Color.blue);
    }
    IEnumerator turnDir()
    {
        characterPanel.Turndir();//转向
        yield return new WaitForSeconds(0.5f);
        OneTurn = 1;
    }
}

 如果不想敌人把敌人推下去,我们可以先给敌人预制体赋予enemy层(不是标签),然后在项目设置里关闭敌人层与敌人层的碰撞

结束了!!

其实学完射线检测我们可以做到很多东西,像简单的攻击范围判定,跳跃条件判定都可以通过射线检测来完成(我的程序是通过碰撞盒和触发器完成的)。

下一篇

11.Unity2D 横版 简单AI 之背后受击转身+寻路跟随敌人+模块化+射线检测_ζั͡ ั͡雾 ั͡狼 ั͡✾的博客-CSDN博客Unity2D 横版 简单AI 之背后受击转身+寻路跟随敌人+模块化+射线检测。回顾上节课,我们已经完成了范围内检索敌人自动攻击,随机移动功能。1.敌人背后受击转身+背部攻击伤害翻倍2.寻路跟随敌人随机移动的坏处就是看到敌人(主角)会略过去,按照自己的行为行走,所以我们要再写一个行为脚本,用来看到敌人后的行为,当然如果没有看到敌人还是随机移动的函数,其中黄线为视线线,变红色为找到主角,并记录此时主角的位置...https://blog.csdn.net/qq_54263076/article/details/125756448?spm=1001.2014.3001.5501

  • 8
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 你可以使用2D碰撞检测检测敌人是否与障碍物相交。在检测到碰撞时,你可以让敌人进行自动躲避。以下是一个简单的实现方式: 1. 给敌人障碍物添加刚体和碰撞器组件。 2. 在敌人的脚本中,获取玩家的位置,并计算出敌人需要移动的方向。 3. 使用Physics2D.Raycast()方法来检测前方是否有障碍物。如果有,就让敌人向其他方向移动。如果没有,就让敌人向玩家移动。 4. 如果敌人移动方向包含x和y轴的值,你需要将这些值归一化,以便敌人在x和y轴上移动相同的距离,而不是沿着斜线移动。 以下是一个示例代码: ``` public class EnemyController : MonoBehaviour { public Transform player; public float moveSpeed = 5f; public float rayDistance = 1f; Rigidbody2D rb; void Start() { rb = GetComponent<Rigidbody2D>(); } void Update() { Vector2 direction = player.position - transform.position; direction = direction.normalized; RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, rayDistance); if (hit.collider != null && hit.collider.CompareTag("Obstacle")) { //敌人需要躲避障碍物 Vector2 perpendicularDirection = new Vector2(-direction.y, direction.x); Vector2 leftDirection = transform.position - perpendicularDirection * rayDistance; Vector2 rightDirection = transform.position + perpendicularDirection * rayDistance; if (Physics2D.Raycast(leftDirection, direction, rayDistance).collider == null) { direction = leftDirection - (Vector2)transform.position; } else if (Physics2D.Raycast(rightDirection, direction, rayDistance).collider == null) { direction = rightDirection - (Vector2)transform.position; } else { direction = -direction; } } rb.velocity = direction * moveSpeed; } } ``` 在这个示例中,敌人检测前方是否有障碍物,如果有,就会尝试向左或向右移动,直到找到一条可行的路径为止。如果没有可行的路径,敌人就会掉头并朝相反的方向移动。 ### 回答2: 在Unity2D中,实现敌人在x轴和y轴移动追踪玩家且不可斜角追踪的方法如下: 首先,我们需要为敌人和玩家创建对应的游戏对象,并为其添加刚体组件。刚体组件可以控制物体的运动和碰撞。 接下来,编写一个敌人控制脚本,将其挂载在敌人游戏对象上。在脚本中,我们需要获取到玩家的位置,可以使用Transform类的position属性获取玩家的坐标。 然后,通过比较敌人和玩家的位置,可以计算出敌人需要移动的方向。根据x轴和y轴的差值确定敌人是否需要在对应轴上移动,并设置合适的移动速度。 为了实现不可斜角追踪,我们需要判断敌人和玩家的位置关系。可以借助Vector2.Distance方法计算敌人和玩家之间的距离,当距离大于一定值时,敌人只需在x轴或y轴上移动即可,而不需要斜着移动。 如果敌人需要避开障碍物,我们可以在脚本中添加对障碍物检测。可以使用Raycast方法检测敌人前方是否有障碍物,如果有,则根据需要选择其他方向移动,可以使用Mathf.RandomRange方法来随机选择移动的方向。 最后,在每一帧的Update函数中,根据以上的逻辑更新敌人的位置和移动方向即可。 以上就是一个简单的实现敌人在x轴和y轴移动追踪玩家但不可斜角追踪且自动躲避障碍物的方法。根据实际需要,你还可以进一步优化和扩展这些逻辑。 ### 回答3: 在Unity2D中实现敌人在x轴和y轴上移动并追踪玩家,并且不以斜角的方式进行追踪,并且还要自动躲避障碍物,需要进行以下步骤。 1. 创建敌人对象和玩家对象,并为它们添加刚体和碰撞器组件。确保敌人拥有脚本来管理移动和追踪逻辑。 2. 在敌人的脚本中,我们需要获取玩家的当前位置和敌人的当前位置。可以使用Transform组件或Vector3对象来获取它们的位置。 3. 计算玩家和敌人之间的距离。我们可以使用Vector3.Distance函数来计算它们之间的欧几里得距离。 4. 如果敌人距离玩家过远,可以考虑添加一个巡逻或寻找路径的行为。可以使用寻路算法(如A*算法)来计算最短路径。 5. 如果敌人和玩家之间的距离小于某个阈值,敌人应该开始追踪玩家。为了确保不以斜角方式追踪,我们可以根据玩家和敌人之间的水平和垂直距离来决定敌人移动方向。 6. 如果敌人前进的方向上有障碍物,则需要自动躲避。可以使用射线检测或者碰撞检测来判断敌人是否面临障碍物,并调整敌人移动方向以避免碰撞。 7. 在每帧更新中,应用计算得出的移动方向和速度来更新敌人的位置。可以使用Transform组件的Translate函数来实现这个移动操作。 通过以上步骤,就可以在Unity2D中实现敌人在x轴和y轴移动追踪玩家,并且不以斜角追踪,并且自动躲避障碍物的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ζั͡ ั͡雾 ั͡狼 ั͡✾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值