3D沙盒游戏开发日志7——系统化的人物功能模块(2)

日志

此篇主要是将ActionController应用到之前已经写过的LocomotionController、ConstructController并做适当修改,本篇代码部分将只记录
与之前不同的修改

LocomotionController

实际上LocomotionController是所有Action中最特别的一个,回顾之前写的内容,主要有两类移动一类是鼠标点击后自动进行寻路的协程移动,这类移动和其它行为一样只需要一次或者很少次数的输入并且可以被打断。另一类是通过键盘的输入,显然这类输入是频繁并且持续的,不可能对每一帧输入都调用DoAction或者像其它动作一样只在第一次按下时调用后续不再监听输入,我们需要对它进行一些处理使它也可以应用到我们的ActionController体系中。此外LocomotionController也是唯一不需要ActionTrigger监听动画播放的Action

public class LocomotionController : PlayerAction
{
	public override string actionName
	{
	    get => "Locomotion";
	}
	public override int priority
	{
	    get => 1;
	}
    public override void Begin(params object[] target)
    {
        finish = false;
        //如果没有指定目的地,即开始键盘的自由移动
        if(target.Length == 0) return;
        //if(!(target is Vector3)) return;
        else currentMove = StartCoroutine(MoveToPoint((Vector3)target[0]));
    }
    public override void Interrupted()
    {
        if(currentMove != null)
        {
            StopCoroutine(currentMove);
        }
    }
}

第一步是继承抽象类并实现抽象成员函数
currentMove是协程移动,MoveToPoint和之前完全相同,由鼠标点击调用
InputController

else if(raycastHit.collider.CompareTag("Ground"))
{
    if(Input.GetMouseButtonDown(0))
    {
        actionController.DoAction<LocomotionController>(true, raycastHit.point);
    }
}

这里解决键盘自由输入的方法是在检测到第一次移动输入后调用DoAction,然后LocomotionController就会被Enable,什么都不需要做直接像之前一样在FixedUpdate中检测输入并移动,当没有任何移动键被按下时表示移动结束就退出(置finish),然后LocomotionController就会被ActionController Disable,也就是说第一次按下和没有任何键被按下将作为Action的开始和结束标志。
InputController

if(Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.W) ||
Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.D)) actionController.DoAction<LocomotionController>(false);

FixedUpdate

public class LocomotionController : PlayerAction
{
	void FixedUpdate()
	{
		if(moveDir != Vector3.zero)
		{
		    animator.SetInteger("MoveState", 1);
		    //正处于协程移动时进行自由移动,终止协程移动
		    if(currentMove != null)
		    {
		        StopCoroutine(currentMove);
		        currentMove = null;
		    }
		}
		//没有任何移动键被按下,也不处于协程移动,退出
		else if(currentMove == null)
		{
		    animator.SetInteger("MoveState", 0);
		    finish = true;
		}
	}
}

这里可以注意到我们还处理了一件事情,就是我们尝试用键盘移动打断协程移动,因为默认状态下我们在ActionController的DoAction中设定为同类型Action不能相互打断,所以我们需要在Locomotion的内部处理这个转换,当有键盘输入并且处于协程移动时终止协程移动切换到自由移动状态。但同时对于鼠标点击的情况,我们手动设定为可以打断同类动作,即再一次点击可以打断上一次的协程

ConstructController

ConstructController也稍有些特别,它除了挂载在角色身上的脚本外还需要一个额外的脚本控制建筑物(Constructable),并且因为需要融入新的ActionController系统,两个脚本都进行了一些改动

public class ConstructionController : PlayerAction
{
	public override string actionName
	{
	    get => "Construct";
	}
	public override int priority
	{
	    get => 1;
	}
	void Awake()
	{
	    Init();
	    RegisterTrigger("constructfinish");
	}
    public override void Begin(params object[] target)
    {
        finish = false;
        preBuildSuccess = false;
        current = StartCoroutine(TryConstruct(target[0] as BuildingStats));
    }

    public override void Interrupted()
    {
        StopCoroutine(current);
        animator.SetTrigger("StopConstruct");
        //处在prebuild阶段
        if(currentBuilding)
        {
            Destroy(currentBuilding);
        }
        else ResetActionTrigger();
    }
}

整个建筑放置的流程是这样的:
进入Construct Action -> 生成target并生成建筑物预制件作为子物体 -> 为target添加Constructable并配置初始参数(BuildingStat) -> 进入Constructable控制prebuild阶段(ConstructController等待结果回调) -> Constructable收到放置指令,回调结果给ConstructController(是否可以放置)并停止更新建筑物位置和状态(结束prebuild状态) -> 不可放置的话动作退出(置finish)/ 可放置则进入下一阶段building -> 人物移动到放置点并开始播放动画 -> 等待放置时间过后调用Constructable实际放置building(放置过后Constructable自我销毁)-> 开始播放结束动画 -> 等待动画播放结束(actiontrigger)后退出action
Constructable的作用
1.检测是否可以放置,这个功能必须要Constructable完成因为只有挂载在物体上的脚本才能收到碰撞体的消息(OnTriggerEnter等),直到玩家click后返回能否放置的结果给ConstructController
2.更新prebuild状态下building的shader等外观以及跟随鼠标移动位置
3.建筑动画播放完后由ContructController调用进行实际的放置建筑物(Detach建筑物子物体,替换回原本的shader,Destroy自身)

public class ConstructionController : PlayerAction
{
	IEnumerator TryConstruct(BuildingStats bs)
	{
	    currentBuilding = new GameObject("CurrentConstruct");
	    //加载模型
	    GameObject buildingPrefab = ABManager.Instance.LoadAsset<GameObject>("BuildingPrefab", bs.path);
	    if(buildingPrefab == null)
	    {
	        Debug.LogError("No Building Asset named " + bs.name);
	        finish = true;
	        yield break;
	    }
	    //生成模型
	    GameObject realBuilding = Instantiate(buildingPrefab, currentBuilding.transform);
	    realBuilding.transform.localPosition = Vector3.zero;
	    realBuilding.transform.rotation = Quaternion.identity;
	    //配置Constructable
	    Constructable target = currentBuilding.AddComponent<Constructable>();
	    target.viewCam = viewController.CurrentViewCam;
	    target.building = realBuilding;
	    target.info = new BuildingInfo();
	    target.info.stats = bs;
	    target.preBuildFinishCallback += OnConstructionFinish;
	    //等待Constructable的成功回调
	    while(!preBuildSuccess) yield return null;
	    Vector3 dst = target.building.GetComponent<Collider>().ClosestPointOnBounds(transform.position);
	    yield return locomotionController.MoveToPoint(dst);
	    animator.SetTrigger("Construct");
	    yield return new WaitForSeconds(Constants.normal_construct_time);
	    //调用Constructable完成最后的放置
	    target.PlaceBuilding();
	    //TODO: 扣除资源
	    animator.SetTrigger("ConstructEnd");
	    while(!triggers["constructfinish"]) yield return null;
	    finish = true;
	    ResetActionTrigger();
	}
	//由Constructable回调
	void OnConstructionFinish(bool result)
	{
	    if(result) preBuildSuccess = true;
	    else finish = true;
	}
}

Constructable基本没有变化,增加了一个inPrebuild来表示是否处在preBuild状态,在成功回调后就结束preBuild状态不再进行FixedUpdate,OnTriggerEnter等消息的监听
动作的调用由ConstructionPanel部分的UI发起

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值