Unity 锁定相机第二弹

前段时间 写的战斗相机一旦切换目标过快 相机就太晃了 晃的想吐
然后参考了某个游戏的战斗相机后 做了以下逻辑改动

大概是这样的

1.当每次攻击时锁定最近的目标 找到目标之后 如果目标在一区内则相机不需要转动
2.如果目标在二区内 相机转动到目标出现出现在1区内后停止转动
3.如果目标在屏幕外 相机转动到目标出现在中上区后停止转动
4.停止转动后 如果仅角色移动 相机只跟随角色移动

根据以上需求 优化了部分逻辑

1.添加了一个屏幕位置枚举

private enum TARGETPOSTYPE {
        /// <summary>
        /// 屏幕中上部分
        /// </summary>
        MIDDLESCREENTOP,
        /// <summary>
        /// 屏幕中部(0.45-0.55)
        /// </summary>
        MIDDLESCREEN,
        /// <summary>
        /// 屏幕中部(0.25-0.75)
        /// </summary>
        ONESCREEN,
        /// <summary>
        /// 屏幕左部(0-0.25)
        /// </summary>
        TWOSCREENLEFT,
        /// <summary>
        /// 屏幕右部(0.75-1)
        /// </summary>
        TWOSCREENRIGHT,
        /// <summary>
        /// 屏幕外
        /// </summary>
        OUTSIDESCREEN,
        NONE,
    }

2.获取目标在屏幕上的位置(0-1)

/// <summary>
    /// 获取目标的屏幕位置
    /// </summary>
    /// <param name="camera"></param>
    /// <param name="targetPos"></param>
    /// <returns></returns>
    public Vector2 GetPosInScreenPos(Camera camera, Vector3 targetPos) {
        Vector3 viewPos = camera.WorldToViewportPoint(targetPos);
        Vector3 dir = (targetPos - camera.transform.position).normalized;
        float dot = Vector3.Dot(camera.transform.forward, dir);

        if(dot > 0)
            return viewPos;
        else
            return new Vector2(-1, -1);
    }

3.每次攻击确定目标的屏幕位置枚举

/// <summary>
    /// 保存每次锁定时锁定目标在屏幕位置
    /// </summary>
    private void SaveLockTargetScreenType() {
        targetShouldType = TARGETPOSTYPE.NONE;
        TARGETPOSTYPE targetPosType = GetTargetInScreenType();

        switch(targetPosType) {
            case TARGETPOSTYPE.TWOSCREENLEFT:
            case TARGETPOSTYPE.TWOSCREENRIGHT:
                targetShouldType = TARGETPOSTYPE.ONESCREEN;
                break;
            case TARGETPOSTYPE.OUTSIDESCREEN:
                targetShouldType = TARGETPOSTYPE.MIDDLESCREENTOP;
                break;
        }
    }

4.监测目标当前屏幕位置枚举 与 需要到达的屏幕位置枚举

/// <summary>
    /// 获取目标的屏幕位置枚举
    /// </summary>
    /// <param name="target2"></param>
    /// <returns></returns>
    private TARGETPOSTYPE GetTargetInScreenType() {
        Vector2 screenPos = GetPosInScreenPos(camera, target2.position);
        Vector2 selfScreenPos = GetPosInScreenPos(camera, target.position);
        if(screenPos.x >= 0 && screenPos.x < 0.25f)
            return TARGETPOSTYPE.TWOSCREENLEFT;
        else if(screenPos.x >= 0.25 && screenPos.x <= 0.75) {
            if(screenPos.x >= 0.45 && screenPos.x <= 0.55) {
                if(screenPos.y >= selfScreenPos.y)
                    return TARGETPOSTYPE.MIDDLESCREENTOP;
                return TARGETPOSTYPE.MIDDLESCREEN;
            } else
                return TARGETPOSTYPE.ONESCREEN;
        } else if(screenPos.x > 0.75f && screenPos.x <= 1)
            return TARGETPOSTYPE.TWOSCREENRIGHT;
        else
            return TARGETPOSTYPE.OUTSIDESCREEN;
    }


/// <summary>
    /// 是否停止相机转动
    /// </summary>
    /// <param name="LockType"></param>
    /// <param name="target2"></param>
    /// <returns></returns>
    private bool StopCameraRot() {
        TARGETPOSTYPE targetPosType = GetTargetInScreenType();
        if(targetPosType <= targetShouldType) return true;
        return false;
    }

5.到达位置后改变相机转动逻辑 从战斗相机改成自由相机

if(beginCalcStop && !StopCameraRot()) {
                cameraMoveWay1 = false;
            } else if(!cameraMoveWay1) {
                cameraMoveWay1 = true;
                beginCalcStop = false;
            }

            Vector3 pos = camera.transform.position;

            if(cameraMoveWay1) {
                CameraMoveWay1(target,  pos);
            } else
                CameraMoveWay2(target,  pos);

 全部的代码是下面这样的:

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

public class test2 : MonoBehaviour {


    #region 锁定相机新逻辑
    private enum TARGETPOSTYPE {
        /// <summary>
        /// 屏幕中上部分
        /// </summary>
        MIDDLESCREENTOP,
        /// <summary>
        /// 屏幕中部(0.45-0.55)
        /// </summary>
        MIDDLESCREEN,
        /// <summary>
        /// 屏幕中部(0.25-0.75)
        /// </summary>
        ONESCREEN,
        /// <summary>
        /// 屏幕左部(0-0.25)
        /// </summary>
        TWOSCREENLEFT,
        /// <summary>
        /// 屏幕右部(0.75-1)
        /// </summary>
        TWOSCREENRIGHT,
        /// <summary>
        /// 屏幕外
        /// </summary>
        OUTSIDESCREEN,
        NONE,
    }
    /// <summary>
    /// 每次刷新锁定时 确认目标应该去的位置
    /// </summary>
    private TARGETPOSTYPE targetShouldType;

    bool cameraMoveWay1 = false;
    bool beginCalcStop = false;
    #endregion

    /// <summary>
    /// 角色
    /// </summary>
    public Transform target;
    /// <summary>
    /// 目标
    /// </summary>
    public Transform target2;
    /// <summary>
    /// 相机
    /// </summary>
    public Camera camera;
    /// <summary>
    /// 相机距离角色水平距离
    /// </summary>
    public float _radius;
    /// <summary>
    /// 相机看向角色正前方多少米处
    /// </summary>
    public float cameraLookDis;
    /// <summary>
    /// 相机距离角色竖直距离
    /// </summary>
    public float _camHeight;
    /// <summary>
    /// 相机旋转角速度变化曲线
    /// </summary>
    public AnimationCurve _cameraAngleSpeedCurve;
    /// <summary>
    /// 相机角速度
    /// </summary>
    public float cameraSpeed;

    public bool reset;

    // Start is called before the first frame update
    void Start() {
    }

    // Update is called once per frame
    void Update() {
        if(reset) {
            if(beginCalcStop && !StopCameraRot()) {
                cameraMoveWay1 = false;
            } else if(!cameraMoveWay1) {
                cameraMoveWay1 = true;
                beginCalcStop = false;
            }

            Vector3 pos = camera.transform.position;

            if(cameraMoveWay1) {
                CameraMoveWay1(target,  pos);
            } else
                CameraMoveWay2(target,  pos);
        }
    }
    /// <summary>
    /// 获取当前角度标准距离后的位置
    /// </summary>
    /// <returns></returns>
    private Vector3 GetDisPos(Vector3 pos, Vector3 _lookat) {
        Vector3 view = _lookat - pos;
        view.y = 0.0f;
        view.Normalize();
        Vector3 endPos = _lookat - view * _radius;
        endPos.y = 0f;
        return endPos;
    }

    private Vector3 GetCurPos(Vector3 center, Vector3 curPos, float angle) {
        return RotateRound(curPos, center, Vector3.up, angle);
    }

    /// <summary>
    /// 围绕某点旋转指定角度
    /// </summary>
    /// <param name="position">自身坐标</param>
    /// <param name="center">旋转中心</param>
    /// <param name="axis">围绕旋转轴</param>
    /// <param name="angle">旋转角度</param>
    /// <returns></returns>
    private Vector3 RotateRound(Vector3 position, Vector3 center, Vector3 axis, float angle) {
        return Quaternion.AngleAxis(angle, axis) * (position - center) + center;
    }
    /// <summary>
    /// 求出两个向量之间的夹角+-180
    /// </summary>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <returns></returns>
    public float Angle_180(Vector3 from, Vector3 to) {
        from.y = 0; to.y = 0;
        Vector3 v3 = Vector3.Cross(from, to);
        if(v3.y > 0)
            return Vector3.Angle(from, to);
        else
            return -Vector3.Angle(from, to);
    }


    /// <summary>
    /// 攻击刷新镜头锁定
    /// </summary>
    /// <param name="target"></param>
    public void AttackUpdate() {
        SaveLockTargetScreenType();
        beginCalcStop = true;
        cameraMoveWay1 = false;
    }

    /// <summary>
    /// 获取当前角度标准距离后的位置
    /// </summary>
    /// <returns></returns>
    private Vector3 GetDisPos(Transform target, Vector3 pos, Vector3 _dir) {
        Vector3 _lookat = target.position - _dir.normalized * cameraLookDis;
        Vector3 view = _lookat - pos;
        view.y = 0.0f;
        view.Normalize();
        Vector3 endPos = _lookat - view * _radius;
        endPos.y = 0f;
        Debug.DrawLine(target.position, endPos, Color.yellow);
        return endPos;
    }

    #region 锁定相机新逻辑
    /// <summary>
    /// 转动方式1 保持主角在屏幕中间 不管锁定目标位置
    /// </summary>
    /// <param name="target"></param>
    /// <param name="dir"></param>
    /// <param name="pos"></param>
    private void CameraMoveWay1(Transform target, Vector3 pos) {
        Vector3 dir = target.position - target2.position;
        dir.y = 0.0f;
        Vector3 lookat = target.position - dir.normalized * cameraLookDis;
        //相机看向目标
        camera.transform.LookAt(lookat);
        Vector3 disEndPos = GetDisPos(target, pos, dir);
        Vector3 curPos = GetCurPos(target.position, disEndPos, 0);

        //相机位置赋值
        camera.transform.position = curPos + Vector3.up * (_camHeight + target.position.y);
    }

    /// <summary>
    /// 转动方式2 保持主角和锁定角色同屏
    /// </summary>
    /// <param name="target"></param>
    /// <param name="dir"></param>
    /// <param name="pos"></param>
    /// <param name="lookat"></param>
    private void CameraMoveWay2(Transform target, Vector3 pos) {
        Vector3 dir = target.position - target2.position;
        dir.y = 0.0f;
        Vector3 lookat = target.position - dir.normalized * cameraLookDis;
        //相机看向目标
        camera.transform.LookAt(lookat);
        //角色到相机的水平方向上的向量
        Vector3 roted = dir.normalized * _radius;
        //相机应该移动到平面上的位置
        Vector3 endPos = lookat + roted;
        //当前相机在校准和角色的水平距离后在平面上的位置
        Vector3 disEndPos = GetDisPos(pos, lookat);
        Vector3 curPos = endPos;
        //当前位置和应到位置的的角度(+-180)
        float angle = Angle_180(disEndPos - lookat, endPos - lookat);
        //根据角度获取曲线上的速度倍率
        float curSpeedRate = _cameraAngleSpeedCurve.Evaluate(Mathf.Abs(angle));
        float curSpeed = cameraSpeed;
        //根据角度调整旋转方向
        if(angle > 0) curSpeed *= 1;
        else if(angle < 0) curSpeed *= -1;
        //根据角度调整位置
        if(Mathf.Abs(angle) > Mathf.Abs(curSpeed * Time.deltaTime * curSpeedRate)) {
            float rotAngle = curSpeed * Time.deltaTime * curSpeedRate;
            //根据圆心角度半径求出旋转角度后应到的位置
            curPos = GetCurPos(target.position, disEndPos, rotAngle);
        }
        //相机位置赋值
        camera.transform.position = curPos + Vector3.up * (_camHeight + target.position.y);
    }

    /// <summary>
    /// 保存每次锁定时锁定目标在屏幕位置
    /// </summary>
    private void SaveLockTargetScreenType() {
        targetShouldType = TARGETPOSTYPE.NONE;
        TARGETPOSTYPE targetPosType = GetTargetInScreenType();

        switch(targetPosType) {
            case TARGETPOSTYPE.TWOSCREENLEFT:
            case TARGETPOSTYPE.TWOSCREENRIGHT:
                targetShouldType = TARGETPOSTYPE.ONESCREEN;
                break;
            case TARGETPOSTYPE.OUTSIDESCREEN:
                targetShouldType = TARGETPOSTYPE.MIDDLESCREENTOP;
                break;
        }
    }

    /// <summary>
    /// 获取目标的屏幕位置枚举
    /// </summary>
    /// <param name="target2"></param>
    /// <returns></returns>
    private TARGETPOSTYPE GetTargetInScreenType() {
        Vector2 screenPos = GetPosInScreenPos(camera, target2.position);
        Vector2 selfScreenPos = GetPosInScreenPos(camera, target.position);
        if(screenPos.x >= 0 && screenPos.x < 0.25f)
            return TARGETPOSTYPE.TWOSCREENLEFT;
        else if(screenPos.x >= 0.25 && screenPos.x <= 0.75) {
            if(screenPos.x >= 0.45 && screenPos.x <= 0.55) {
                if(screenPos.y >= selfScreenPos.y)
                    return TARGETPOSTYPE.MIDDLESCREENTOP;
                return TARGETPOSTYPE.MIDDLESCREEN;
            } else
                return TARGETPOSTYPE.ONESCREEN;
        } else if(screenPos.x > 0.75f && screenPos.x <= 1)
            return TARGETPOSTYPE.TWOSCREENRIGHT;
        else
            return TARGETPOSTYPE.OUTSIDESCREEN;
    }

    /// <summary>
    /// 是否停止相机转动
    /// </summary>
    /// <param name="LockType"></param>
    /// <param name="target2"></param>
    /// <returns></returns>
    private bool StopCameraRot() {
        TARGETPOSTYPE targetPosType = GetTargetInScreenType();
        if(targetPosType <= targetShouldType) return true;
        return false;
    }

    /// <summary>
    /// 获取目标的屏幕位置
    /// </summary>
    /// <param name="camera"></param>
    /// <param name="targetPos"></param>
    /// <returns></returns>
    public Vector2 GetPosInScreenPos(Camera camera, Vector3 targetPos) {
        Vector3 viewPos = camera.WorldToViewportPoint(targetPos);
        Vector3 dir = (targetPos - camera.transform.position).normalized;
        float dot = Vector3.Dot(camera.transform.forward, dir);

        if(dot > 0)
            return viewPos;
        else
            return new Vector2(-1, -1);
    }
    #endregion
}

攻击的时候触发AttackUpdate方法就会按照规则执行

每个rpg游戏必然有着自己的战斗相机特殊要求 ,这个只是记录自己的经历,程序不易,且行且珍惜

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一梭键盘任平生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值