天天看點

遊戲程式設計精粹學習 - 計算到區域内部的距離

在《遊戲程式設計精粹1》的4.7中,原文主要解決賽車遊戲的路程确定問題和光照插值問題。

但原文中沒有提及如何判斷四邊形區域是否包含的問題,隻有提到point-in-sector這個函數名稱

實作是T和L兩部分做向量投影,但是不乘以最終方向矢量,而是以兩邊的點乘結果求得比例。

那麼我對其做了一些修改,改成了橫向和縱向二維的機關距離計算,這樣可以順手解決是否包含的判斷問題。

遊戲程式設計精粹學習 - 計算到區域内部的距離

支援兩個軸向之後可以對其做是否包含的判斷,還可以通過組合做一些複雜區域的判斷檢測。

例如運用在遊戲BOSS戰,或RailCamera中。

遊戲程式設計精粹學習 - 計算到區域内部的距離
代碼如下:
遊戲程式設計精粹學習 - 計算到區域内部的距離
遊戲程式設計精粹學習 - 計算到區域内部的距離

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

namespace Hont
{
    public class QuadSector : MonoBehaviour
    {
        public struct QuadSector_VecXZ
        {
            public float X { get; set; }
            public float Z { get; set; }


            public QuadSector_VecXZ(float x, float z)
            {
                X = x;
                Z = z;
            }
        }

        public Transform p0;
        public Transform p1;
        public Transform p2;
        public Transform p3;
        public bool isRealtimeUpdate;

        QuadSector_VecXZ mHPointLeading;
        QuadSector_VecXZ mHPointTrailing;
        QuadSector_VecXZ mHUnitNormalLeading;
        QuadSector_VecXZ mHUnitNormalTrailing;

        QuadSector_VecXZ mVPointLeading;
        QuadSector_VecXZ mVPointTrailing;
        QuadSector_VecXZ mVUnitNormalLeading;
        QuadSector_VecXZ mVUnitNormalTrailing;

        public QuadSector_VecXZ HPointLeading { get { return mHPointLeading; } set { mHPointLeading = value; } }
        public QuadSector_VecXZ HPointTrailing { get { return mHPointTrailing; } set { mHPointTrailing = value; } }
        public QuadSector_VecXZ HUnitNormalLeading { get { return mHUnitNormalLeading; } set { mHUnitNormalLeading = value; } }
        public QuadSector_VecXZ HUnitNormalTrailing { get { return mHUnitNormalTrailing; } set { mHUnitNormalTrailing = value; } }

        public QuadSector_VecXZ VPointLeading { get { return mVPointLeading; } set { mVPointLeading = value; } }
        public QuadSector_VecXZ VPointTrailing { get { return mVPointTrailing; } set { mVPointTrailing = value; } }
        public QuadSector_VecXZ VUnitNormalLeading { get { return mVUnitNormalLeading; } set { mVUnitNormalLeading = value; } }
        public QuadSector_VecXZ VUnitNormalTrailing { get { return mVUnitNormalTrailing; } set { mVUnitNormalTrailing = value; } }


        public void UpdateHorizontalQuadSectorInfo()
        {
            var pl = (p1.position + p2.position) * 0.5f;
            mHPointLeading = new QuadSector_VecXZ(pl.x, pl.z);

            var pt = (p0.position + p3.position) * 0.5f;
            mHPointTrailing = new QuadSector_VecXZ(pt.x, pt.z);

            var nl = CalcLineNormal(p1.position, p2.position, Vector3.up, p3.position);
            mHUnitNormalLeading = new QuadSector_VecXZ(nl.x, nl.z);

            var nt = CalcLineNormal(p0.position, p3.position, Vector3.up, p1.position);
            mHUnitNormalTrailing = new QuadSector_VecXZ(nt.x, nt.z);
        }

        public void UpdateVerticalQuadSectorInfo()
        {
            var pl = (p1.position + p0.position) * 0.5f;
            mVPointLeading = new QuadSector_VecXZ(pl.x, pl.z);

            var pt = (p2.position + p3.position) * 0.5f;
            mVPointTrailing = new QuadSector_VecXZ(pt.x, pt.z);

            var nl = CalcLineNormal(p1.position, p0.position, Vector3.up, p3.position);
            mVUnitNormalLeading = new QuadSector_VecXZ(nl.x, nl.z);

            var nt = CalcLineNormal(p2.position, p3.position, Vector3.up, p0.position);
            mVUnitNormalTrailing = new QuadSector_VecXZ(nt.x, nt.z);
        }

        public float CalcHorizontalUnitDistanceIntoSector(float pointX, float pointZ)
        {
            var lp = new QuadSector_VecXZ();
            var tp = new QuadSector_VecXZ();
            var dotL = 0f;
            var dotT = 0f;

            lp.X = pointX - HPointLeading.X;
            lp.Z = pointZ - HPointLeading.Z;

            tp.X = pointX - HPointTrailing.X;
            tp.Z = pointZ - HPointTrailing.Z;

            dotL = lp.X * mHUnitNormalLeading.X + lp.Z * mHUnitNormalLeading.Z;
            dotT = tp.X * mHUnitNormalTrailing.X + tp.Z * mHUnitNormalTrailing.Z;

            return dotL / (dotL + dotT);
        }

        public float CalcVerticalUnitDistanceIntoSector(float pointX, float pointZ)
        {
            var lp = new QuadSector_VecXZ();
            var tp = new QuadSector_VecXZ();
            var dotL = 0f;
            var dotT = 0f;

            lp.X = pointX - VPointLeading.X;
            lp.Z = pointZ - VPointLeading.Z;

            tp.X = pointX - VPointTrailing.X;
            tp.Z = pointZ - VPointTrailing.Z;

            dotL = lp.X * mVUnitNormalLeading.X + lp.Z * mVUnitNormalLeading.Z;
            dotT = tp.X * mVUnitNormalTrailing.X + tp.Z * mVUnitNormalTrailing.Z;

            return dotL / (dotL + dotT);
        }

        public bool IsContain(float pointX, float pointZ)
        {
            var x = CalcHorizontalUnitDistanceIntoSector(transform.position.x, transform.position.z);
            var z = CalcVerticalUnitDistanceIntoSector(transform.position.x, transform.position.z);

            return x > 0 && x < 1 && z > 0 && z < 1;
        }

        Vector3 CalcLineNormal(Vector3 p0, Vector3 p1, Vector3 upAxis, Vector3 comparePoint)
        {
            var dir = (p1 - p0).normalized;
            var normal1 = Vector3.Cross(dir, upAxis);
            var normal2 = Vector3.Cross(dir, -upAxis);

            if (Vector3.Dot(normal1, comparePoint - p0) > 0)
                return normal1;
            else
                return normal2;
        }

        void Awake()
        {
            UpdateHorizontalQuadSectorInfo();
            UpdateVerticalQuadSectorInfo();
        }

        void Update()
        {
            if (isRealtimeUpdate)
            {
                UpdateHorizontalQuadSectorInfo();
                UpdateVerticalQuadSectorInfo();
            }
        }

        void OnDrawGizmos()
        {
            if (p0 == null || p1 == null || p2 == null || p3 == null) return;

            if (!Application.isPlaying)
            {
                UpdateHorizontalQuadSectorInfo();
                UpdateVerticalQuadSectorInfo();
            }

            Gizmos.DrawLine(p0.position, p1.position);
            Gizmos.DrawLine(p1.position, p2.position);
            Gizmos.DrawLine(p2.position, p3.position);
            Gizmos.DrawLine(p3.position, p0.position);

            var cacheColor = Gizmos.color;
            Gizmos.color = Color.blue;

            var ori = new Vector3(HPointLeading.X, transform.position.y, HPointLeading.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(HUnitNormalLeading.X, transform.position.y, HUnitNormalLeading.Z));

            ori = new Vector3(VPointLeading.X, transform.position.y, VPointLeading.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(VUnitNormalLeading.X, transform.position.y, VUnitNormalLeading.Z));

            ori = new Vector3(HPointTrailing.X, transform.position.y, HPointTrailing.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(HUnitNormalTrailing.X, transform.position.y, HUnitNormalTrailing.Z));

            ori = new Vector3(VPointTrailing.X, transform.position.y, VPointTrailing.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(VUnitNormalTrailing.X, transform.position.y, VUnitNormalTrailing.Z));

            Gizmos.color = cacheColor;
        }
    }
}      

QuadSector

P0-P3四個點沿順時針方向排布,如果不勾選實時更新則法線和頂點都不會在運作時修改。

測試腳本:

public class TestPlayer : MonoBehaviour
{
    public QuadSector quadSector;


    void Update()
    {
        var x = quadSector.CalcHorizontalUnitDistanceIntoSector(transform.position.x, transform.position.z);
        var z = quadSector.CalcVerticalUnitDistanceIntoSector(transform.position.x, transform.position.z);
        Debug.Log("x: " + x + " z: " + z);
    }
}