unity Mirror使用心得二(服务器端的怪物创建,及其怪物AI的行为)

21 篇文章 1 订阅
17 篇文章 0 订阅

1.怪物必须由服务器创建:
创建代码:

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

public class MonsterCreater : NetworkBehaviour
{
    public Vector3 Pos;
    public GameObject projectilePrefab;
    private NetworkIdentity identity;
    // Start is called before the first frame update

    public override void OnStartServer()
    {
        var networkManager = GameObject.FindObjectOfType<NetworkManager>();
        identity = this.gameObject.GetComponent<NetworkIdentity>();
        CreaterMonster();
    }
    public void CreaterMonster()
    {
        GameObject projectile = Instantiate(projectilePrefab, Pos, transform.rotation);
        NetworkServer.Spawn(projectile);
    }
}

OnStartServer 是在服务器端运行,并且是在启动服务器的时候调用
2.怪物预制体创建:
在这里插入图片描述
networkTransform 和networkAnimator ClientAuthority 由于是在服务器端运行怪物的逻辑不需要勾选
3.怪物的脚本组件分享:
怪物的主体AI

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public enum AIType
{ 
	Monster = 1,//会攻击玩家的怪物
	Npc = 2,// 不会进行攻击的npc
}
public enum AIState
{ 
	Idle = 1,
	
	MoveToTarget  = 2,
	AttackTarget = 3,
	SkillTarget = 4,
	MoveBack = 5,
}
public enum PathType
{ 
	Once = 1,
	Loop = 2,
	PingPong = 3,

}
[System.Serializable]
public class PathPoint
{
	public Vector3 pos;
	public PathPoint NextPoint;
	public PathPoint PrePoint;
}

[System.Serializable]
public class AIPath
{
	public List<PathPoint> Paths = new List<PathPoint>();
	public PathType pathType = PathType.Loop;
	private PathPoint nextPoint;
	public void Init(){
		for (var i = 0; i < Paths.Count; i++)
		{
			var path = Paths[i];
			if (i == 0)
			{
				path.NextPoint = Paths[i + 1];
				if (pathType == PathType.Loop)
					path.PrePoint = Paths[Paths.Count - 1];
			}
			else if (i == Paths.Count - 1)
			{
				if (pathType == PathType.Loop)
					path.NextPoint = Paths[0];
				path.PrePoint = Paths[i - 1];
			}
			else
			{
				path.NextPoint = Paths[i + 1];
				path.PrePoint = Paths[i - 1];
			}
		}
		nextPoint = Paths[0];
	}
	public PathPoint GetNextPoint()
	{
		return nextPoint;
	}
	public bool IsReachNextPoint(Vector3 pos)
	{
		if (Vector3.Distance(nextPoint.pos, pos) <= 1)
			return true;
		return false;
	}
	private bool isForward = true;
	public void OnReachNextPoint()
	{
		if (pathType == PathType.Once)
		{
			nextPoint = nextPoint.NextPoint;
		}
		else if (pathType == PathType.Loop)
		{
			nextPoint = nextPoint.NextPoint;
		}
		else if (pathType == PathType.PingPong)
		{
			if (isForward)
			{
				if (nextPoint.NextPoint != null)
					nextPoint = nextPoint.NextPoint;

				else
				{
					nextPoint = nextPoint.PrePoint;
					isForward = false;
				}
			}
			else
			{
				if (nextPoint.PrePoint != null)
					nextPoint = nextPoint.PrePoint;
				else
				{
					nextPoint = nextPoint.NextPoint;
					isForward = true;
				}
			}
		}
	}
}
public class MonsterAI : NetworkBehaviour
{
	public Vector3 reBornPos;
	public float moveSpeed = 8f;

	public float atkDistance = 2f;//攻击范围
	public float warnDistance = 10f;//警戒范围
	public float followDistance = 12f;//追踪范围

	public float atk = 10f;
	public float atkOffset = 1f;
	public bool isFaceRight = true;
	public int MaxHp = 100;
	[SyncVar(hook = "HpChange")]
	public int curHp = 100;

	public AIPath path;

	private bool isMove = false;
	private Vector3 moveDelta;
	private bool isAttack = false;
	private bool curIsFaceRight = true;
	private Vector3 oriScale;
	private NetworkIdentity identity;
	private CharacterAni characterAni;
	private CharacterHp characterHp;
	private bool isCanReborn = true;
	private CharacterInput curTarget;
	private Vector3 findTargetPos;
	private AIPolicy aiPolicy;

	void Awake() {
		oriScale = this.transform.localScale;
		identity = this.gameObject.GetComponent<NetworkIdentity>();
		characterAni = this.gameObject.GetComponent<CharacterAni>();
		characterHp = this.gameObject.GetComponent<CharacterHp>();
		characterHp.SetHpInfo(curHp, MaxHp);
		aiPolicy = new AIPolicy(this);
		path.Init();
	}
	private void Start()
	{
		InvokeRepeating("OnAiAction", 1f, 1f);
	}
	private void OnAiAction()
	{
		if (IsDead())
			return;
		allIdentitys = GameObject.FindObjectsOfType<CharacterInput>(false);
		aiPolicy.OnRun(allIdentitys);
	}
	public void HpChange(int pOld, int hp)
	{
		characterHp.SetHpInfo(hp, MaxHp);
		if (hp <= 0)
		{
			OnDead();
		}
	}
	private void OnDead()
	{
		if (isCanReborn)
		{
			Invoke("Reborn", 5f);
			isCanReborn = false;
		}
	}
	[Command]
	private void Reborn()
	{
		curHp = MaxHp;
		RpcReborn();
	}
	[ClientRpc]
	private void RpcReborn()
	{
		this.transform.localPosition = Vector3.zero;
		isCanReborn = true;
	}
	public bool IsDead()
	{
		return curHp <= 0;
	}
	public void StopMove() {
		isMove = false;
		isMoveToTargetPos = false;
	}
	public void FaceToCurTarget()
	{
		if (curTarget.transform.position.x > this.transform.position.x)
		{
			FaceRight(true);
		}
		else
		{
			FaceRight(false);
		}
	}
	public void StopAttack() {
		isAttack = false;
	}
	public void Attack(){
		isAttack = true;
	}

	private bool IsInRange(CharacterInput pTarget,float pRange)
	{
		var targetPos = pTarget.transform.position;
		var selfPos = this.transform.position;

		if (Vector3.Distance(targetPos,selfPos)<= pRange){
			return true;
		}
		return false;
	}

	private bool IsInAtkRange(CharacterInput pTarget)
	{
		var direction = curIsFaceRight ? Vector2.right * atkDistance : -Vector2.right * atkDistance;
		var targetPos = pTarget.transform.position;
		var selfPos = this.transform.position;
		if (Mathf.Abs(targetPos.y - selfPos.y) <= atkOffset && Mathf.Abs(targetPos.x - (selfPos.x + direction.x)) <= atkOffset)
		{
			return true;
		}
		return false;
	}
	private CharacterInput[] allIdentitys;
	public bool IsHaveTarget(){
		return curTarget != null && !curTarget.IsDead();
	}
	public CharacterInput GetOneCharacterInRange(float pRange)
	{
		allIdentitys = GameObject.FindObjectsOfType<CharacterInput>(false);
		for(var i = 0; i< allIdentitys.Length;i++)
		{
			if (!allIdentitys[i].IsDead() && IsInRange(allIdentitys[i], pRange))
				return allIdentitys[i];
		}
		return null;
	}
	public bool CurTargetInRange(float pRange) {
		if (curTarget != null && !curTarget.IsDead())
		{
			return IsInRange(curTarget, pRange);
		}
		return false;
	}
    public void OnLoopMove()
    {
		if (path.IsReachNextPoint(this.transform.position))
		{
			path.OnReachNextPoint();
		}
		var nextPos = path.GetNextPoint();
		if (nextPos != null)
			MoveToTargetPos(nextPos.pos);
		else
			Idle();
	}
	private bool isMoveToTargetPos = false;
	private Vector3 targetPos;
    public void MoveToTargetPos(Vector3 pTargetPos)
	{
		StopAttack();
		targetPos = pTargetPos;
		isMoveToTargetPos = true;
	}
	public void MoToCurTarget()
	{
		StopAttack();
		MoveToTargetPos(curTarget.transform.position);
	}
	public bool NeedSelectTarget()//是否需要重新选择追踪目标
	{
		if (curTarget != null && !curTarget.IsDead() && IsInRange(curTarget, followDistance))
			return false;
		var character = GetOneCharacterInRange(warnDistance);
		return character != null;
	}
	public void SelectAdjustTarget() {
		var character = GetOneCharacterInRange(warnDistance);
		SetCurTarget(character);
	}
	private bool IsNeedBackToHome = false;
	public void BackToHome()
	{
		var distance = Vector3.Distance(this.transform.position, findTargetPos);
		if (distance >= 1)
		{
			IsNeedBackToHome = true;
			SetCurTarget(null);
			MoveToTargetPos(findTargetPos);
		}
		else
		{
			IsNeedBackToHome = false;
		}	
	}
	public bool NeedBackToHome()
	{
		if (IsNeedBackToHome)
			return true;
		if (curTarget == null)
			return false;
		if (curTarget.IsDead())
			return true;
		var distance = Vector3.Distance(this.transform.position, findTargetPos);
		return distance >= followDistance;
	}
	
	public void SetCurTarget(CharacterInput pCharacterInput){
		curTarget = pCharacterInput;
		findTargetPos = this.transform.position;
	}
	public void CmdDoNormalAttack()
	{
		var direction = curIsFaceRight ? Vector2.right : -Vector2.right;
		allIdentitys = GameObject.FindObjectsOfType<CharacterInput>(false);
		for (var i = 0; i < allIdentitys.Length; i++)
		{
			if (allIdentitys[i] != this)
			{
				if (IsInAtkRange(allIdentitys[i]))
				{
					allIdentitys[i].OnDamage((int)atk);
				}
			}
		}
	}
	public void Idle()
	{
		SetCurTarget(null);
		StopAttack();
		StopMove();
		characterAni.PlayAni("idle", 1);
	}
	public void FaceRight(bool pIsFaceRight)
	{
		int xS = isFaceRight == pIsFaceRight ? 1 : -1;
		curIsFaceRight = xS == 1;
		this.transform.localScale = new Vector3(oriScale.x * xS, oriScale.y, oriScale.z);
	}
	public void OnDamage(int pDamage)
	{
		curHp -= pDamage;
		if (curHp > MaxHp)
			curHp = MaxHp;
		if (curHp < 0)
			curHp = 0;
		SetHpInfo();
	}
	public void SetHpInfo()
	{
		characterHp.SetHpInfo(curHp, MaxHp);
	}
	private bool isPlayMoveAni = false;
	private void FixedUpdate()
	{
		//if (identity.isClient)
		//	return;
		if (IsDead())
			return;
		if (isAttack)
		{
			characterAni.PlayAni("attack", 4, () => {
				isAttack = false;
				CmdDoNormalAttack();
			});
		}
		if (isMoveToTargetPos)
		{
			if (Vector3.Distance(targetPos, this.transform.position) <= 1)
			{
				isMoveToTargetPos = false;
			}
			else
			{
				if (targetPos.x > this.transform.position.x)
					moveDelta.x = moveSpeed;
				else
					moveDelta.x = -moveSpeed;
				this.transform.localPosition += moveDelta * Time.deltaTime;
				int xS = isFaceRight == moveDelta.x > 0 ? 1 : -1;
				curIsFaceRight = xS == 1;
				this.transform.localScale = new Vector3(oriScale.x * xS, oriScale.y, oriScale.z);
				characterAni.PlayAni("move", 3);
			}
		}
		else
		{
			if (isMove)
			{
				this.transform.localPosition += moveDelta * Time.deltaTime;
				int xS = isFaceRight == moveDelta.x > 0 ? 1 : -1;
				curIsFaceRight = xS == 1;
				this.transform.localScale = new Vector3(oriScale.x * xS, oriScale.y, oriScale.z);
				characterAni.PlayAni("move", 3);
			}
		}
	
		if (!isAttack && !isMove && !isMoveToTargetPos)
		{
			characterAni.PlayAni("idle", 1);
		}
	}

}

分享了怪物的类型方便扩展,本demo 没有扩展,寻路逻辑的设置和逻辑编写AIPath和PathPoint

4.重点来了,分享怪物的AI 行为决策:

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

public class AIPolicy
{
    private List<AIAction> allActions = new List<AIAction>() { 
        new BackToHomeAction(),new AttackToTargetAction(),new MoveToTargetAction(),new SelectTargetAction(),
        new MoveAction(),new IdleAction()
    };
    private AIAction curAiAction;
    private MonsterAI bindAi;
    public AIPolicy(MonsterAI pMonsterAi) {
        bindAi = pMonsterAi;
    }
    public void OnRun(CharacterInput[] pAllCharacters)
    {
        for (var i = 0; i < allActions.Count; i++)
        {
            if (allActions[i].IsPass(bindAi, pAllCharacters))
            {
                curAiAction = allActions[i];
                break;
            }
        }
        curAiAction.OnAcion(bindAi);
    }
}
public class BackToHomeAction : AIAction
{
    public override AIConditioner Conditioner { get { if (conditioner == null) conditioner = new BackToHomeConditioner(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.BackToHome();
    }
}
public class AttackToTargetAction : AIAction
{
    public override AIConditioner Conditioner { get { if (conditioner == null) conditioner = new AttackToTargetConditioner(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.StopMove();
        pBindAi.FaceToCurTarget();
        pBindAi.Attack();
    }
}
public class MoveToTargetAction : AIAction
{
    public override AIConditioner Conditioner { get { if (conditioner == null) conditioner = new MoveToTargetConditioner(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.MoToCurTarget();
    }
}
public class SelectTargetAction : AIAction
{
    public override AIConditioner Conditioner { get { if (conditioner == null) conditioner = new ForcusTargetConditioner(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.SelectAdjustTarget();
    }
}
public class MoveAction : AIAction {
    public override AIConditioner Conditioner { get { if (conditioner == null) conditioner = new MoveConditioer(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.OnLoopMove();
    }
}
public class IdleAction : AIAction {
    public override AIConditioner Conditioner { get {if (conditioner == null) conditioner = new IdleConditoner(); return conditioner; } }
    public override void OnAcion(MonsterAI pBindAi)
    {
        pBindAi.Idle();
    }
}
public class AIAction
{
    protected AIConditioner conditioner;

    public virtual AIConditioner Conditioner { get { if (conditioner == null) conditioner = new AIConditioner(); return conditioner; } }
    public virtual void OnAcion(MonsterAI pBindAi) { }
    public bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters) {
        return Conditioner.IsPass(pBindAi, pAllCharacters);
    }
}

public class BackToHomeConditioner : AIConditioner//目标超出追踪范围,返回原地
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return pBindAi.NeedBackToHome();
    }
}
public class AttackToTargetConditioner : AIConditioner//攻击目标
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return pBindAi.CurTargetInRange(pBindAi.atkDistance);
    }
}
public class MoveToTargetConditioner : AIConditioner//追踪目标
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return pBindAi.CurTargetInRange(pBindAi.followDistance);
    }
}
public class ForcusTargetConditioner : AIConditioner//锁定目标
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return pBindAi.NeedSelectTarget();
    }
}
public class MoveConditioer : AIConditioner //巡逻
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return !pBindAi.IsHaveTarget();
    }
}
public class IdleConditoner: AIConditioner//待机
{
    public override bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return !pBindAi.IsDead();
    }
}
public class AIConditioner
{
    public virtual bool IsPass(MonsterAI pBindAi, CharacterInput[] pAllCharacters)
    {
        return true;
    }
}


简单介绍一下ai 的行为策略,
ai包含各种行为,行为必须有进入该行为的条件,已经进入该行为的运行方式。舍去了行为优先级的设计,先入行为队列的优先级就是最高的,如果条件通过了,其他行为不会执行。

 private List<AIAction> allActions = new List<AIAction>() { 
        new BackToHomeAction(),new AttackToTargetAction(),new MoveToTargetAction(),new SelectTargetAction(),
        new MoveAction(),new IdleAction()
    };

该段代码中的优先级行为为:
1.目标死亡或者超出追踪范围返回追踪该目标的起始点
2.目标在攻击范围时,进行攻击行为
3.目标在追踪范围时,进行追踪
4.存在目标在警戒范围内时,进行目标锁定
5.不存在锁定目标的时候,进行巡逻
6.待机行为

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值