每个需要移动的对象都有一个controller,controller通过心跳控制移动ai更新位置.
移动行为分类:
(1) 垂直向上/下移动,比如打飞机游戏的子弹移动路径.
(2)朝着固定点攻击,比如打飞机游戏吃到技能时,会攻击距离玩家最近的敌人攻击.
(3)子弹需要尾随敌人,比如导弹.
基于上面三种分类,controller 在onStart 和onUpdate 中需要传递两个参数,目标点和移动目标的transform.
对于分类(1), 移动ai只需要判断自己的类型,是敌人、技能、障碍物 就沿着屏幕垂直向下移动.
对于分类(2), 只需要在ai的onStart里面告诉ai,初始目标点和子弹出生点,子弹每次心跳都会沿着同一个方向移动.
对于分类(3),controller心跳不需要告诉ai目标点,只需要传递移动目标的transform,移动ai在心跳里面自己计算移动朝向.
具体代码:
以子弹移动为例子:
(1)controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/** controller 设计规则
* ctr的 onUpdate 接口负责更新ai心跳, 由BattleDatas脚本驱动.
* 所有ctr都遵从这个规则,包括玩家角色、敌人、技能、障碍物.
*
*
*/
public class CtrBase : MonoBehaviour
{
//内部属性
//--------------------------------------------------------------------------
// 战斗管理器
[SerializeField] protected BattleManager battle;
// 对象回收池
[SerializeField] protected BattlePool pool;
// 准备完毕标记
[SerializeField] protected bool isStart = false;
// 初始化时的攻击位置
[SerializeField] protected Vector3 orilShootPos;
// 初始化时的攻击位置
[SerializeField] protected Transform currentShootTrans;
// 移动脚本 todo:除了玩家,其他对象需要该组件,所有放在基础类里面
[SerializeField] protected MoveBaseAI moveAI;
//内部方法
//--------------------------------------------------------------------------
/** 根据移动ai类型,获取目标点
*
*/
protected Vector3 GetUpdateTargetPos()
{
Vector3 pos = Vector3.zero;
var t = this.MovingType;
switch (t)
{
case MoveType.None:
break;
case MoveType.Player:
break;
case MoveType.UpOrDown:
break;
case MoveType.StraightLine:
pos = this.orilShootPos;
break;
}
return Vector3.zero;
}
/** 初始化时,根据子弹移动规律,计算出初始 攻击目标点、攻击目标trans
*
*/
protected void InitTargetPos()
{
var t = this.MovingType;
switch (t)
{
case MoveType.None:
break;
case MoveType.Player:
break;
case MoveType.UpOrDown:
break;
case MoveType.StraightLine:
var targ = battle.battleDatas.GetNearestTarget(this);
Debug.Log("targ: " + targ);
if(targ != null){
Debug.Log("targ name : " + targ.gameObject.name);
this.orilShootPos = targ.transform.position;
this.currentShootTrans = targ.transform;
}
break;
}
}
//override方法
//--------------------------------------------------------------------------
/** 碰撞检测
*
*/
protected virtual void CollisionDetection() { }
/**
*
*/
public virtual void OnStart()
{
battle = GameMgr.instanse.BattleMgr();
pool = GameMgr.instanse.BattlePool();
InitTargetPos();
isStart = true;
}
/** 每帧更新
* BattleDatas 驱动此心跳
*
* 子弹移动
* 碰撞检测逻辑
*
*/
public virtual void OnUpdate(float deltaTime)
{
if (isStart == false) return;
this.CollisionDetection();
if(this.moveAI != null)
{
Vector3 targetPos = this.GetUpdateTargetPos();
Transform targetTrans = this.currentShootTrans;
object[] args = { targetPos, targetTrans };
this.moveAI.OnUpdate(deltaTime, args);
}
}
//公开接口
//--------------------------------------------------------------------------
// controller创建时,动态赋值
//--------------------------------------------------------------------------
/** ai移动类型
*
* 用来计算移动路径
*
*/
[SerializeField] private MoveType moveType = MoveType.None;
public MoveType MovingType
{
set { this.moveType = value; }
get { return this.moveType; }
}
/** 角色是否是敌人
*
*/
[SerializeField] private bool isEnemy = false;
public bool IsEnemy
{
set { this.isEnemy = value; }
get { return this.isEnemy; }
}
// 来自配置表
//--------------------------------------------------------------------------
/** 角色默认移动方向
*
*/
[SerializeField] private DefaultMoveDirection defaultMoveDirection;
public DefaultMoveDirection DefaultMoveDirection
{
set { this.defaultMoveDirection = value; }
get { return this.defaultMoveDirection; }
}
/** 重复移动
*
* 敌人、障碍物 出底线了,需要矫正位置后重新出现
* 子弹、技能不需要
*
*/
[SerializeField] private bool repeatMove;
public bool IsRepeatMove
{
set { this.repeatMove = value; }
get { return this.repeatMove; }
}
/** obj类型
* 用来计算移动路径
*
*/
[SerializeField] private RoleType roleType;
public RoleType RoleType
{
set { this.roleType = value; }
get { return this.roleType; }
}
//
//--------------------------------------------------------------------------
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/** 子弹控制器
* 更新子弹移动ai,碰撞检测
*
* 子弹攻击分三类:
* (1) 垂直方向无脑攻击(流弹型);
* (2) 往某个点攻击;
* (3) 往某个obj攻击,obj会移动(响尾蛇导弹型);
*
*/
public class BulletCtrBase : CtrBase
{
//内部属性
//--------------------------------------------------------------------------
[SerializeField] protected BulletConfig bulletConf; // 子弹配置
//内部方法
//--------------------------------------------------------------------------
/** 碰撞检测
* 子弹ctr 有个属性IsEnemy,标记子弹是玩家还是敌人发出的.
* 如果是玩家发出的,子弹会检测所有敌人.
* 如果是敌人发出的,子弹会检测所有玩家.
*
*
*/
protected override void CollisionDetection()
{
if (this.IsEnemy)
{
// 检测玩家
var player = battle.battleDatas.NearestPlayer(transform);
if (player == null)
{
Debug.LogError("没有检测到玩家");
return;
}
bool hitted = GlobalFunction.InFieldOfVision(player.transform.position, transform.position);
if (hitted)
{
Debug.LogError("击中玩家 " + player.gameObject.name);
battle.battleDatas.CutHp(player, 10);
if (player.GetData().Death())
{
Debug.Log("玩家 " + player.gameObject.name + " 死亡!");
battle.battleDatas.RoleDeath(player);
}
}
}
else
{
// 检测敌人
var enemy = battle.battleDatas.NearestEnemy(transform);
if (enemy == null)
{
//Debug.LogError("没有检测到敌人");
return;
}
bool hitted = GlobalFunction.InFieldOfVision(enemy.transform.position, transform.position);
if (hitted)
{
Debug.LogError("击中敌人 " + enemy.gameObject.name);
battle.battleDatas.CutHp(enemy, this.bulletConf.GetInt(this.bulletConf.attack));
if (enemy.GetData().Death())
{
Debug.Log("敌人 " + enemy.gameObject.name + " 死亡!");
battle.battleDatas.EnemyDeath(enemy);
}
}
}
}
//override方法
//--------------------------------------------------------------------------
/** BattleDatas 创建时,完成初始化(OnStart)
*
*
*
*/
public override void OnStart()
{
object[] args = { this.transform, this.bulletConf.moveSpeed, this};
this.moveAI.OnStart(args);
base.OnStart();
}
//公开接口
//--------------------------------------------------------------------------
/** 移动ai
*
*/
public void SetMoveAI(MoveBaseAI move)
{
this.moveAI = move;
}
/** 子弹配置
*
*/
public void SetConfig(BulletConfig conf)
{
this.bulletConf = conf;
}
/** 角色id
*
*/
public string Id()
{
return this.bulletConf.id;
}
}
(2) 通用的移动ai(子弹、敌人、技能对象、障碍物都适用)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//TODO:重构
/** 更新子弹移动ai
*
* controller 给移动ai必须的数据, 不同行为的移动ai负责计算,并执行具体移动行为.
*
*
*/
public class MoveBaseAI : BaseAI
{
//内部属性
//--------------------------------------------------------------------------
// 移动角色的controller
[SerializeField] protected CtrBase ctr;
// 移动角色 transform
[SerializeField] protected Transform roleTrans;
// 移动速度
[SerializeField] protected float moveSpeed;
[SerializeField] protected Vector3 moveDir;
//内部方法
//--------------------------------------------------------------------------
/** 移动朝向
*
*
*/
protected virtual Vector3 GetDir(params object[] args)
{
if (args == null || args.Length < 1)
{
Debug.LogError("移动的目标点必须传!");
}
return Vector3.zero;
}
//override方法
//--------------------------------------------------------------------------
/** 初始化ai
*
* 移动角色 transform
* 移动速度
* 移动角色 controller
*
*/
public override void OnStart(params object[] args)
{
if (args == null || args.Length < 3)
{
Debug.LogError("args 必须包含 移动transform 、移动速度、移动方向、目标transform、角色controller");
return;
}
this.roleTrans = (Transform)args[0];
this.moveSpeed = float.Parse(args[1].ToString());
this.ctr = (CtrBase)args[2];
base.OnStart(args);
}
/** 每帧更新(对应obj的controller)
*
* 参数1: deltaTime
* 参数2: args
*
*/
public virtual void OnUpdate(float deltaTime, params object[] args)
{
//Debug.Log("single forward deltaTime : " + deltaTime);
//Debug.Log("single forward isStart : " + this.isStart);
//Debug.Log("single forward roleTrans : " + roleTrans.name);
base.OnUpdate(deltaTime);
if (this.roleTrans == null)
{
//被消灭
return;
}
// 出底线了
if (battle.IsOutSide(this.roleTrans.position))
{
if (this.ctr.IsRepeatMove)
{
// 强制矫正
this.roleTrans.position = battle.CorrectOutSide(this.roleTrans.position);
}
else
{
// 回收
battle.battleDatas.ObjDeath(this.ctr);
}
} else {
this.moveDir = this.GetDir(args);
this.roleTrans.position += this.moveDir * deltaTime * this.moveSpeed;
}
}
//公开接口
//--------------------------------------------------------------------------
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/** 向屏幕上方/下方移动
*
*/
public class SingleForwardAI : MoveBaseAI
{
//内部属性
//--------------------------------------------------------------------------
//内部方法
//--------------------------------------------------------------------------
/** 移动朝向
*
* 只处理默认移动方向
*
*/
protected override Vector3 GetDir(params object[] args)
{
base.GetDir(args);
Vector3 dir;
if(this.ctr.DefaultMoveDirection == DefaultMoveDirection.VerticalDown)
{
dir = Vector3.down;
}
else
{
dir = Vector3.up;
}
return dir;
}
//override方法
//--------------------------------------------------------------------------
//公开接口
//--------------------------------------------------------------------------
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/** 向某个固定点移动
*
*/
public class StraightForwardAI : MoveBaseAI
{
//内部属性
//--------------------------------------------------------------------------
[SerializeField] protected Vector3 orilPos;
//内部方法
//--------------------------------------------------------------------------
/** 移动朝向
*
* 处理心跳传过来的目标点与子弹出生点之间的向量
*
*/
protected override Vector3 GetDir(params object[] args)
{
var target = (Vector3)args[0];
var dir = (target - this.orilPos).normalized;
return dir;
}
//override方法
//--------------------------------------------------------------------------
/** 初始化ai
*
* 移动角色 transform
* 移动速度
* 移动角色 controller
*
*/
public override void OnStart(params object[] args)
{
base.OnStart(args);
this.orilPos = this.roleTrans.position;
}
//公开接口
//--------------------------------------------------------------------------
}