GameFrameWork框架(Unity3D)使用笔记(七)游戏主流程ProcedureMain——虽然游戏本来没有怪物但是为了使用状态机模块写一个简单怪物AI吧(上)

目录

前言:

一、怪物需求定制

1、大致功能

2、具体设计

二、状态逻辑设计

1、有哪些状态

 2、状态的转换

(1)追逐状态

(2)攻击状态

  (3)懒散巡视状态

  (4)返回状态

3、画个状态转换图

        ​编辑

三、开始撸代码!

未完待续!


前言:

        状态机是游戏开发中很常用的东西,GF中也有对应的模块FSM(有限状态机)。但是呢,前面我用到的玩家预制体里的控制脚本是一把梭的。。。而且代码是很久以前写的可读性很差。。。(就是说不想再重构了呗),然后在这个游戏里目前并没有AI怪物的需求。。。

        所以为了强行练习使用GF的有限状态机模块,我准备写一个简单的怪物AI,使用FSM来完成。


一、怪物需求定制

1、大致功能

        怪物一开始在一个范围内来回巡视,当它发现玩家的时候,将会追赶和攻击玩家,但是当玩家移动到一定范围之外它就会放弃追逐。

2、具体设计

        如图所示,蓝色、红色范围是相对怪物的范围,绿色、紫色标识的范围是地图上的固定范围;一开始怪物在绿色范围内徘徊,当玩家进入它的蓝色范围的时候,怪物会追赶玩家;若玩家在追赶过程中进入了怪物的红色范围,则怪物进入攻击状态;若在追逐过程中怪物跑出了紫色范围,则怪物停止追逐并且返回绿色范围。


二、状态逻辑设计

        既然我们要用状态机实现,我们就得提前想好有哪些状态,状态转换的条件等。

1、有哪些状态

        由上面的具体设计不难写出一共有四个状态:

 2、状态的转换

        然后继续想想,在每个状态下遇到哪些条件会转换到哪些状态。。。(所以实际上程序员大多数时间是在撑着脑袋盯着屏幕发呆。。。)

(1)追逐状态

        在这个状态下,

        如果检测到玩家进入了攻击范围,则进入攻击状态;

        如果自己超过了追逐范围,则进入返回状态;

(2)攻击状态

        在这个状态下,

        如果检测到玩家跑出了攻击范围,则进入追逐状态;

  (3)懒散巡视状态

        在这个状态下,

        如果检测到玩家进入了发现范围,则进入追逐状态;

  (4)返回状态

        在这个状态下,

        如果检测到玩家进入了发现范围,则进入追逐状态;

        如果返回到了懒散巡视范围,则进入懒散巡视状态;

3、画个状态转换图

        虽然说做到上面的分析就完全可以开始写代码了,但是这里为了更直观展示这个状态机,我画个简陋的图:

        


三、开始撸代码!

        既然怪物的状态及其转换已经设计的差不多啦,那么下面就开始撸状态代码!

        啊慢着慢着,好像怪物实体还没有写出来呢。。。。。。

        所以先来写一下怪物实体!由于之前已经详细写过有关实体的内容,这里就很简略地飞快写一下!

         

         啊怪物预制体就做好了,怪物是这样一个圆形片片绝对不是因为我懒不想画不想花时间太懒了之类的,而是因为游戏设定中怪物就是这......编不下去了,就是我懒。

        然后就是写怪物实体数据类:

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

namespace ShadowU
{
    public class EnemyData : EntityData
    {
        /// <summary>
        /// Idle的圆心
        /// </summary>
        public Vector3 Origin
        {
            get; 
            private set;
        }
        /// <summary>
        /// Idle的范围
        /// </summary>
        public float IdleRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 追逐的范围
        /// </summary>
        public float ChasingRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 发现玩家的范围
        /// </summary>
        public float AlarmRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 攻击范围
        /// </summary>
        public float AttackRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 移动速度
        /// </summary>
        public float Speed
        {
            get;
            private set;
        }
        public EnemyData(int entityId, int typeId) : base(entityId, typeId)
        {

        }
    }

}

        逻辑过会儿再写。

        因为怪物也是实体,所以在实体配置表里面加一个,用于加载预制体:

        ​​​​​​​​​​​​​​​​​​​​​

        然后EnemyData的属性也通过数据表进行配置:

        写数据表行类:

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace ShadowU
{
    public class DREnemy : DataRowBase
    {
        private int m_Id;
        public override int Id
        {
            get { return m_Id; }
        }
        /// <summary>
        /// Idle的圆心
        /// </summary>
        public Vector3 Origin
        {
            get;
            private set;
        }
        /// <summary>
        /// Idle的范围
        /// </summary>
        public float IdleRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 追逐的范围
        /// </summary>
        public float ChasingRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 发现玩家的范围
        /// </summary>
        public float AlarmRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 攻击范围
        /// </summary>
        public float AttackRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 移动速度
        /// </summary>
        public float Speed
        {
            get;
            private set;
        }
        public override bool ParseDataRow(string dataRowString, object userData)
        {
            string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
            for (int i = 0; i < columnStrings.Length; i++)
            {
                columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
                Log.Debug(columnStrings[i]);
                Log.Debug(columnStrings[i].Length);
            }
            int index = 0;
            index++;
            m_Id = int.Parse(columnStrings[index++]);
            index++;
            string[] rowSpawnPosition = columnStrings[index++].Split('|');
            Origin = new Vector3(float.Parse(rowSpawnPosition[0]), float.Parse(rowSpawnPosition[1]), float.Parse(rowSpawnPosition[2]));
            IdleRange = float.Parse(columnStrings[index++]);
            ChasingRange = float.Parse(columnStrings[index++]);
            AlarmRange = float.Parse(columnStrings[index++]);
            AttackRange = float.Parse(columnStrings[index++]);
            Speed = float.Parse(columnStrings[index++]);
            return true;
        }
    }
}

        加载数据表:

把读取数据表配置信息赋值给实体数据的逻辑放在EnemyData的构造函数里:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityGameFramework.Runtime;
using GameFramework.DataTable;
namespace ShadowU
{
    public class EnemyData : EntityData
    {
        /// <summary>
        /// Idle的圆心
        /// </summary>
        public Vector3 Origin
        {
            get; 
            private set;
        }
        /// <summary>
        /// Idle的范围
        /// </summary>
        public float IdleRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 追逐的范围
        /// </summary>
        public float ChasingRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 发现玩家的范围
        /// </summary>
        public float AlarmRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 攻击范围
        /// </summary>
        public float AttackRange
        {
            get;
            private set;
        }
        /// <summary>
        /// 移动速度
        /// </summary>
        public float Speed
        {
            get;
            private set;
        }
        public EnemyData(int entityId, int typeId) : base(entityId, typeId)
        {
            IDataTable<DREnemy> dtEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
            DREnemy drEnemy = dtEnemy.GetDataRow(typeId);
            Origin = drEnemy.Origin;
            IdleRange = drEnemy.IdleRange;
            ChasingRange = drEnemy.ChasingRange;
            AlarmRange = drEnemy.AlarmRange;
            AttackRange = drEnemy.AttackRange;
            Speed = drEnemy.Speed;
            
            Position = Origin;
        }
    }

}

然后写Enemy的实体逻辑类(状态机之后写所以这里只写了测试实体数据是否读取成功的逻辑):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace ShadowU
{
    public class Enemy : EntityLogic
    {
        protected override void OnInit(object userData)
        {
            base.OnInit(userData);
            CachedTransform.position = ((EnemyData)userData).Position;
        }

        protected override void OnShow(object userData)
        {
            base.OnShow(userData);
            EnemyData enemyData = (EnemyData)userData;
            Log.Debug("敌人加载成功");
            Log.Info(enemyData.Origin);
            Log.Info(enemyData.IdleRange);
            Log.Info(enemyData.ChasingRange);
            Log.Info(enemyData.AlarmRange);
            Log.Info(enemyData.AttackRange);
            Log.Info(enemyData.Speed);
        }

        protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(elapseSeconds, realElapseSeconds);

        }
    }
}


 然后在实体扩展中扩展一个加载敌人的函数

然后在主流程调用测试一下:

 

 运行!

 

成功! 

未完待续!

     

                

      

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咖啡咖_CoffCa

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

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

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

打赏作者

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

抵扣说明:

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

余额充值