Unity3D TPS Demo开发笔记

2020-05-24  本文已影响0人  漫游之光

游戏管理者

通常,游戏需要一个游戏管理者,游戏管理者知道游戏所有的状态,其他的游戏组件可以通过游戏管理者知道其他组件的情况。

public class GameManager {
    //这种写法,相当声明了一个接口,然后再创建了一个该接口的数组
    public event System.Action<Player> onLocalPlayerJoined;

    [HideInInspector]
    public GameObject gameObject;
    private static GameManager mInstance;

    public static string rolename = "rolename";
    public static int level = 1;
    public static int health = 100;
    public static int ammo = 120;

    public static bool isPause = false;

    public static GameManager Instance {
        get {
            if (mInstance == null) {
                mInstance = new GameManager();
                mInstance.gameObject = new GameObject("GameManager");

            }
            return mInstance;
        }
    }

    //这种写法,在java中相当于一个private变量的set和get方法
    private Player mLocalPlayer;
    public Player LocalPlayer {
        get {
            return mLocalPlayer;
        }
        set {
            mLocalPlayer = value;
            if (onLocalPlayerJoined != null) {
                onLocalPlayerJoined(mLocalPlayer); //调用注册在上面的回调函数

            }

        }
    }
}

人物移动、旋转和跳跃

//人物的移动和跳跃
if (characterController.isGrounded) { //在地面的时候才能进行移动,空中不能
    moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
    moveDirection = transform.TransformDirection(moveDirection); //相当于以自身的坐标轴前进
    moveDirection *= speed;
    if (Input.GetButton("Jump")) {
        moveDirection.y = jumpSpeed; //跳跃相当于加了一个向上的速度
    }
}
moveDirection.y -= gravity * Time.deltaTime; //向上的速度受到重力的影响,每次会下降
characterController.Move(moveDirection * Time.deltaTime);

//人物绕y轴旋转
mouseInput.x = Mathf.Lerp(mouseInput.x, playerInput.MouseInput.x, 1 / MouseControl.Damping.x);
transform.Rotate(Vector3.up * mouseInput.x * MouseControl.Sensitivity.x);

第三人称相机

相机紧跟着玩家,但和玩家保持一定的距离。

瞄准

准心是固定在屏幕某个位置的UI,当射击的时候,用光线检测从相机的中心看出去有没有发现物体,如果发现了,射击的目标就是该物体,如果没有,射击的目标是距离相机位置的最大射程处。射击的时候,初始化子弹,子弹的方式是炮口的位置,指向要射击的位置。

//计算准星的位置
Vector3 rayOrigin = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0));
Vector3 targetPosition = Camera.main.transform.position + Camera.main.transform.forward * 500;
RaycastHit hit;
if (Physics.Raycast(rayOrigin, Camera.main.transform.forward, out hit, 500)) {
    targetPosition = hit.point;
}

子弹

子弹初始化的时候,有方向和速度,然后按照一定的速度进行前进,前进中进行检测,检测是否碰到了可以造成伤害的物体,有就造成伤害。当一定时间后,子弹会自动消失,即达到了最大的射程。

void Start() {
    Destroy(gameObject, timeToLive);
}

void Update() {
    //由于速度太快,碰撞检测可能出错
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
    RaycastHit hit;
    if(Physics.Raycast(transform.position,transform.forward,out hit, 5.0f)) {
        CheckDestructable(hit.transform);
    }
}

void CheckDestructable(Transform other) {
    var destructable = other.GetComponent<Destructable>();
    if (destructable == null) {
        return;
    }
    destructable.TakeDamage(transform.position,Random.Range(minDamage,maxDamage));
    Destroy(gameObject);
}

换弹夹

子弹打完了就需要换弹夹,换弹夹期间不能射击,使用延时操作来完成。

敌人AI

敌人比较简单,没有发现玩家的时候,会进行巡逻。巡逻的逻辑就是走到某个点,到了之后又走下一个点,走到最后一个点时,又回到第一个点。发现玩家的逻辑是,用一个球碰撞体进行检测,任何进入到球里面的玩家,检测视线,如果在视线范围内,就发现了玩家,然后进行射击。扫描玩家的代码如下。

public class Scanner : MonoBehaviour {
    [SerializeField] float scanSpeed;
    [SerializeField] float fieldOfView;

    SphereCollider rangeTrigger;
    List<Player> targets;

    Player mSelectedTarget;
    Player selectedTarget {
        get {
            return mSelectedTarget;
        }
        set {
            mSelectedTarget = value;
            if (mSelectedTarget == null) {
                return;
            }
            if (OnTargetSelected != null) {
                OnTargetSelected(mSelectedTarget.transform.position);
            }
        }
    }

    [SerializeField] LayerMask mask;

    public event System.Action<Vector3> OnTargetSelected;


    void Start() {
        rangeTrigger = GetComponent<SphereCollider>();
        targets = new List<Player>();
    }


    Vector3 GetViewAngle(float angle) {
        float radian = (angle + transform.eulerAngles.y) * Mathf.Deg2Rad;
        return new Vector3(Mathf.Sin(radian), 0, Mathf.Cos(radian));
    }

    public Player ScanForTarget() {
        selectedTarget = null;
        targets.Clear();
        Collider[] results = Physics.OverlapSphere(transform.position, rangeTrigger.radius);
        for (int i = 0; i < results.Length; i++) {
            Player player = results[i].GetComponent<Player>();
            if (player == null || !player.gameObject.activeSelf) {
                continue;
            }
            if (!IsInLineOfSight(Vector3.up, player.transform.position)) {
                continue;
            }
            targets.Add(player);
        }
        if (targets.Count >= 1) {
            float closestTarget = rangeTrigger.radius;
            foreach (var possibleTarget in targets) {
                if (Vector3.Distance(transform.position, possibleTarget.transform.position) <= closestTarget) {
                    selectedTarget = possibleTarget;
                }
            }
        }
        return selectedTarget;
    }

    bool IsInLineOfSight(Vector3 eyeHeight, Vector3 targetPosition) {
        Vector3 direction = targetPosition - transform.position;
        if (Vector3.Angle(transform.forward, direction.normalized) < fieldOfView / 2) {
            float distanceToTarget = Vector3.Distance(transform.position, targetPosition);
            //有遮挡物挡住了
            if (Physics.Raycast(transform.position + eyeHeight, direction.normalized, distanceToTarget, mask)) {
                Debug.Log("hide");
                return false;
            }
            return true;
        }
        return false;
    }
}

总结

这个Demo一共花了10天左右,主要是看了一个视频教程。做完这个Demo,最大的感受是,游戏开发就是一个导演,主要要做的工作就是同步。

上一篇下一篇

猜你喜欢

热点阅读