乐趣区

关于unity:黑魂复刻游戏的玩家控制器锁定状态Unity随手记

明天实现的内容:
新增锁定输出
为输出模块增加锁定键和锁定信号,更新信号。

    --- IPlayerInput
    public bool lockOn; // 锁定信号
    --- JoystickInput
    public MyButton buttonLockOn = new MyButton(); // 锁定键
    
    // Update is called once per frame
    void Update()
    {
        // 更新按键
        buttonLockOn.Tick(Input.GetButton(btnRS));
        // 锁定信号
        lockOn = buttonLockOn.onPressed;

锁定和解锁的代码逻辑和摄像机代码逻辑
首先,要锁定目标,先要确定要锁定的指标是什么。咱们应用 Physics.OverlapBox 来失去指定盒子区域内的碰撞体。

锁定须要始终将摄像机对准目标。所以咱们在 CameraController 中增加新办法 LockOn_or_Unlock 用来解决锁定解锁逻辑,而后在 PlayerController 中调用该办法。

    // 摄像机锁定 / 解除锁定
    public void LockOn_or_Unlock()
    {
        // 尝试去锁定一个
        Vector3 tmp_modelCenter = modelGO.transform.position + Vector3.up; // 取得模型的核心
        Vector3 tmp_boxCenter = tmp_modelCenter + modelGO.transform.forward * 5.0f; // 失去 OverlapBox 的核心
        Collider[] cols = Physics.OverlapBox(tmp_boxCenter,  // 通过 OverlapBox 尝试获取范畴内 Enemy 标签的碰撞体
            new Vector3(1.0f, 1.0f, 5.0f),
            modelGO.transform.rotation,
            LayerMask.GetMask("Enemy"));
        if (cols.Length != 0 && lockTarget == null)
        {
            // 如果失去碰撞体并且没有锁定目标 将第一个赋值给 lockTarget
            lockTarget = cols[0].gameObject;
            lockonIcon.enabled = true;
            // 设置 LockonIcon 的图片地位
            lockonIcon.rectTransform.position = Camera.main.WorldToScreenPoint(lockTarget.transform.position);
            isLockon = true;
        }
        else
        {
            // 如果没有检测到任何货色或者曾经锁定了指标 将 lockTarget 设置为 null
            lockTarget = null;
            lockonIcon.enabled = false;
            isLockon = false;
        }
    }

LockOn_or_Unlock 会应用 OverlapBox 查看给定范畴内是否有碰撞体并且是否曾经锁定目标,如果找到了碰撞体,将数组中的第一个给 lockTarget,如果没找到或曾经锁定了指标则设置为 null。
如果摄像机没有锁定,则在 FixedUpdate 中将 playerController 按输出设置旋转。如果锁定了,则摄像机要计算模型和锁定目标的方向向量,把这个方向向量交给摄像机,在咱们的架构中,间接用来设置 PlayerController 的 forward 就行,记得要将该向量的 y 轴设置为 0。最初,咱们会在锁定时让摄像机始终看向指标的“脚底”来让视角更贴近黑魂。

    void FixedUpdate()
    {if (lockTarget == null) // 如果没有锁定目标 按输出管制 PlayerController 旋转
        {
            // 失去摄像机旋转前的模型欧拉角
            Vector3 temp_modelEuler = modelGO.transform.eulerAngles;
            // 左右旋转时间接旋转 PlayerHandle 摄像机也会跟着
            playerController.transform.Rotate(Vector3.up, current_pi.cameraRight * horizontalSensitivity * Time.fixedDeltaTime);
            // 摄像机旋转后将模型原来的欧拉角再赋给模型 保障模型不动
            modelGO.transform.eulerAngles = temp_modelEuler;
            // 高低旋转时旋转 CameraHandle
            temp_eulerX -= current_pi.cameraUp * verticalSensitivity * Time.fixedDeltaTime;
            // 限度俯仰角
            temp_eulerX = Mathf.Clamp(temp_eulerX, -40, 30);
            // 赋值 localEulerAngles
            cameraHandle.transform.localEulerAngles = new Vector3(temp_eulerX, 0, 0);
        }
        else // 如果锁定了指标 计算从模型到锁定目标的方向向量 将 PlayerController 的 forward 设置为该向量
        {
            // 计算方向向量
            Vector3 temp_forward = lockTarget.transform.position - modelGO.transform.position;
            // 将向量的 y 轴设置为 0 PlayerController 的 Y 轴不须要旋转
            temp_forward.y = 0;
            // 设置 PlayerController 的 forward
            playerController.transform.forward = temp_forward;
            // 锁定摄像机看指标的“脚底”cameraHandle.transform.LookAt(lockTarget.transform.parent.position);
        }

        // 摄像机的地位通过 SmoothDamp 来实现一种提早挪动的成果
        cameraGO.transform.position = Vector3.SmoothDamp(cameraGO.transform.position, this.transform.position, ref temp_dampValue, 0.1f * current_pi.dirMag);
        // 让摄像机放弃看向一个地位 避免地位进行 SmoothDamp 时的抖动
        cameraGO.transform.LookAt(cameraHandle.transform);
    }

锁定的提醒 UI
为了在游戏中提醒玩家以后锁定了哪个指标,咱们要退出一个 UI。

须要在没有进行锁定时将其 enabled 设置为 false。只在锁定时将其设置为 true。最初将 UI 的地位放到锁定的对象身上。

    private void Update()
    {if(isLockon)
        {
            // 更新 LockonIcon 的图片地位
            lockonIcon.rectTransform.position = Camera.main.WorldToScreenPoint(lockTarget.transform.position);
        }
    }

锁定的控制器代码逻辑
锁定时控制器中的代码和摄像机相似,一个是设置摄像机对准目标,一个是设置模型对准目标。只有当模型对准目标,咱们能力引入锁定时相应的动画。同样重要的还有锁定状态下的挪动计算,因为锁定时模型和 PlayerController 的方向都已锁死,此时方向的判断不依据模型了,而是间接来自输出的产生方向,咱们在输出模块中用 dirVec 来示意。

    // -- PlayerController --

    // Update is called once per frame
    void Update()
    {
        // ...
        // 触发锁定 
        if (current_pi.lockOn)
        {camCon.LockOn_or_Unlock();
        }
        if (camCon.isLockon) // 摄像机已锁定 将模型旋转设定为朝向指标
        {
            // 因为曾经在 CameraController 设置了 PlayerController 对象的旋转所以间接给模型就行
            model.transform.forward = this.transform.forward;
            // 计算锁定时的挪动
            if (!lockPlanar)
            {m_planarVec = current_pi.dirVec  * walkSpeed * (current_pi.run ? runMultiplier : 1.0f);
            }
        }
        else // 摄像机没有锁定 依据输出管制模型旋转
        {
            // 只在有速度时可能旋转 避免原地旋转
            if (current_pi.dirMag > 0.1f)
            {
                // 使用旋转 应用 Slerp 进行成果优化   
                model.transform.forward = Vector3.Slerp(model.transform.forward, current_pi.dirVec, 0.3f);
            }
            // 计算没有锁定时的挪动量
            if (!lockPlanar)
            {m_planarVec = current_pi.dirMag * model.transform.forward * walkSpeed * (current_pi.run ? runMultiplier : 1.0f);
            }
        }
    }

总结一下,因为咱们程度旋转摄像机是靠旋转 PlayerController 游戏对象来实现,所以锁定目标将 PlayerController 游戏对象的 forward 指向指标就行。同样的,因为曾经设置了 PlayerController 在锁定时指向指标,要将模型指向指标只须要将 PlayerController 对象的 forward 给模型就行。挪动的方向判断须要通过输出产生的方向,也就是咱们之前做输出模块时写的 dirVec 来判断。

锁定的动画

之前说要将模型始终对准目标能力引入锁定对应的动画,锁定对应的动画就是始终朝向一个方向时的后退后退和左右侧步。要退出这些动画,咱们须要将 ground 混合树批改为 2D Freeform 类型。原来的挪动仍然只由 forward 参数操控,咱们要额定退出 right 参数,在锁定时,咱们将通过批改 right 参数操控左右侧步,以及调整 forward 为负值来操控后退。这样还不须要退出新混合树就能够实现原来的挪动和锁定时的挪动了。

至于动画参数的代码,构造和之前的逻辑相似,在锁定状态下要调整的是 forward 和 right,非锁定状态下只调整 forward 就行。具体代码如下:

    // Update is called once per frame
    void Update()
    {
        // --------------------- 动画参数 ---------------------
        if (camCon.isLockon)
        {Vector3 localDirVec = this.transform.InverseTransformDirection(current_pi.dirVec);
            anim.SetFloat("forward", localDirVec.z * ((current_pi.run) ? 2.0f : 1.0f));
            anim.SetFloat("right", localDirVec.x * ((current_pi.run) ? 2.0f : 1.0f));
        }
        else
        {anim.SetFloat("forward", current_pi.dirMag * Mathf.Lerp(anim.GetFloat("forward"), (current_pi.run) ? 2.0f : 1.0f, 0.5f));
            anim.SetFloat("right", 0);
        }
        
    // ...

    }

锁定状态下的翻滚和跳跃
因为锁定状态下模型方向判断和之前不一样了,所以咱们须要从新设计锁定状态下的翻滚和后跳。设计方案是,设置一个新的 bool 值 trackDirection,当该 bool 为 true 时,将追踪 m_planarVec,提供给翻滚后跳作为方向。当咱们开始跳跃或翻滚时,将 trackDirection 设置为 true。落地时设置为 false。

    // 进入 Base 层的动画节点 roll 时执行的办法
    // 通过 PlayerController 动画机中的 roll 节点上挂载的 FSMOnEnter 调用
    public void OnRollEnter()
    {
        // 敞开输出模块
        current_pi.inputEnabled = false;
        // 锁定平台挪动计算
        lockPlanar = true;
        // 使用翻滚冲量
        m_planarVec = m_planarVec.normalized * rollThrust;
        // 追踪 dirVec
        trackDirection = true;
    }

当锁定时,要旋转模型的时候,如果 trackDirection 为 true,则依照 m_planarVec 作为方向。

        // --------------------- 模型旋转和位移 ---------------------
        if (camCon.isLockon) // 摄像机已锁定 将模型旋转设定为朝向指标
        {if(trackDirection) // 以后是否追踪方向
            {model.transform.forward = m_planarVec.normalized;}
            else
            {
                // 因为曾经在 CameraController 设置了 PlayerController 对象的旋转所以间接给模型就行
                model.transform.forward = this.transform.forward;
            }

最初,将动画机调整一下,将 right 参数思考进翻滚和跳跃。

主动解除锁定
到目前为止,咱们的锁定性能根本刊用了,然而每当玩家想要解除锁定时必须再次按下锁定键,否则无论跑出多远都会锁定到指标上,从游戏设计的角度来讲,玩家跑出很远的间隔个别都是心愿勾销锁定的,而且如果在很远距离都能锁定敌人会导致一些追踪魔法能打很远。所以最初咱们再做一个主动解除锁定性能。
通过计算玩家和指标的间隔,如果间隔大于某个值,则主动执行解锁。

    private void Update()
    {
        // 如果锁定了指标
        if(isLockon)
        {
            // 更新 LockonIcon 的图片地位
            lockonIcon.rectTransform.position = Camera.main.WorldToScreenPoint(lockTarget.transform.position);
            // 超出某个间隔主动解除锁定
            if (Vector3.Distance(lockTarget.transform.position, modelGO.transform.position) > 10f)
            {
                lockTarget = null;
                lockonIcon.enabled = false;
                isLockon = false;
            }
        }
    }
退出移动版