untiy 2D 曲面地图动态生成

      前面说要做个2D曲面,因为一直在找工作没时间,如果大家有机会可以帮小弟推荐推荐,小弟不胜感激。好言归正传了,开始讲今天的东西了,先截个图看看效果。图如下:



第一张是在scence视图下的图,可以很清楚的看到这个地形的分布,第二张则是一个球在场景中的运行图,也就是我们的主角了。如果我们需要制作出一个曲面,毫无疑问的是我们得动态构造网格,然后给我们的网格贴uv。一个曲面是有很多个三角形绘制,所以我们知道如何绘制出一个三角形的话,然后间接的就可以绘制出一个四边形,乃至整个曲面网格了。如果不会绘制一个三角形的可以参考前面的博客了。如果我们先绘制出一个长方形的网格,然后通过变幻把直的变成弯的(如果我们能变魔术就好了,把直的变成弯的)。

我们先把直的绘制出来,上面就是一个方的地形了(逗比刘真是菜,一个方的地形还这样,逗比刘就只会装b了 哈哈)我们发现方的地形我们用了很多点来绘制其实是有必要的,这里便于我们理解,我们发现如果把上面的地形用sin曲线或者cos曲线做的话,那不是就是变成曲线了么。好我们先绘制出直的来,然后演变成曲的。

那么首先需要建一个类了。我们定义其为BKCurveEntity,

public class BKCurveEntity
{

}

我们先写个初始化方法,用来给定我们用什么材质,方形网格的高度,整个网物体的其实位置。

    public Vector3 P1;
    public Vector3 P2;
    public Vector3 P3;
    public Vector3 P4;

    public Vector3 OriginVector3;

    private Material _bodyMeshMaterial;
    private Material _terrianMaterial;
    private float _factor;

    public void IntilizedBkEntity(List<Vector3> keyPoints, Material tm, Material bm, float factor)
    {
        P1 = keyPoints[0]; P2 = keyPoints[1];
        P3 = keyPoints[2]; P4 = keyPoints[3];
        _bodyMeshMaterial = bm;
        _terrianMaterial = tm;
        _factor = factor;
    }
p2,p3表示我们绘制网格的左上坐标和右上坐标。这里p1,p4的用处我们先不讲,tm代表网格黑色山体的材质,而bm代表的网格灰色山体上面草的材质了。后面的参数先不讲了。接下来我们添加一个主方法。GameObject GenerateMeshGameObject(Vector3 originPos, float startuv)用来生成一个gameobject。

  public GameObject GenerateMeshGameObject(Vector3 originPos, float startuv)
    {     
        var bk = new GameObject("BK");
        bk.transform.position = originPos;
        OriginVector3 = originPos;   
       
       
        return bk;
    }

我们还是先对一些参数进行赋值,比如生成网格他的初始位置,起始uv等信息。然后就是我们需要添加网格的点了,这里我们网格左下到右下的点我们可以根据左上到右上的点来进行计算,所以我们需要先得到左上到右上这之间的点了。

<span style="background-color: rgb(255, 153, 0);">_tempVector3S = new List<Vector3>();</span>
var bk = new GameObject("BK");


        OriginVector3 = originPos;      

       <span style="background-color: rgb(255, 153, 0);"> float right = P3.x;
        float width = 8f;
        _tempVector3S.Add(P2);
        for (float i = P2.x; i < right; )
        {
            i += 0.5f;
            if (i > right)
                i = right;
            var leftTopPoint = P2+new Vector3(i,0,0);
            _tempVector3S.Add(leftTopPoint);
        }</span>
这个地方我们采用的是线型采样点。把它存在一个临时数组中。接下来我们就生成山体和山体上面的草了。同时我们同样需要得到山体网格的顶点信息和顶点绘制顺序及顶点的uv。

       OriginVector3 = originPos;

<span style="background-color: rgb(255, 153, 0);">        _terrainBodyV = new List<Vector3>();
        _terrainBodyT = new List<int>();
        _terrainBodyU = new List<Vector2>();

        _terrainSurfaceV = new List<Vector3>();
        _terrainSurfaceT = new List<int>();
        _terrainSurfaceU = new List<Vector2>();
</span>
        float right = P3.x;
上面分别定义2种网格所需要的基本信息:网格顶点信息,顶点绘制顺序,顶点uv信息。

      for (float i = P2.x; i < right; )
        {
            i += 0.5f;
            if (i > right)
                i = right;           
            var leftTopPoint = P2+new Vector3(i,0,0);
            _tempVector3S.Add(leftTopPoint);
        }
       <span style="background-color: rgb(255, 153, 0);"> for (int i = 0; i < _tempVector3S.Count - 1; i++)
        {
            _terrainBodyV.Add(_tempVector3S[i]);
            _terrainBodyV.Add(new Vector3(_tempVector3S[i].x, 0, 0));
            _terrainBodyV.Add(_tempVector3S[i + 1]);
            _terrainBodyV.Add(new Vector3(_tempVector3S[i + 1].x, 0, 0));

            _terrainSurfaceV.Add(_tempVector3S[i] + new Vector3(0, 0.5f, 0f));
            _terrainSurfaceV.Add(_tempVector3S[i] - new Vector3(0, 0.2f, 0f));
            _terrainSurfaceV.Add(_tempVector3S[i + 1] + new Vector3(0, 0.5f, 0f));
            _terrainSurfaceV.Add(_tempVector3S[i + 1] - new Vector3(0, 0.2f, 0f));

            _terrainBodyT.Add(0); _terrainBodyT.Add(3); _terrainBodyT.Add(1);
            _terrainBodyT.Add(3); _terrainBodyT.Add(0); _terrainBodyT.Add(2);

            _terrainSurfaceT.Add(0); _terrainSurfaceT.Add(3); _terrainSurfaceT.Add(1);
            _terrainSurfaceT.Add(3); _terrainSurfaceT.Add(0); _terrainSurfaceT.Add(2);        

            var terrainBodymesh = new Mesh();
            var terrainFaceMesh = new Mesh();

            terrainBodymesh.vertices = _terrainBodyV.ToArray();
            terrainBodymesh.uv = _terrainBodyU.ToArray();
            terrainBodymesh.triangles = _terrainBodyT.ToArray();

            terrainFaceMesh.vertices = _terrainSurfaceV.ToArray();
            terrainFaceMesh.uv = _terrainSurfaceU.ToArray();
            terrainFaceMesh.triangles = _terrainSurfaceT.ToArray();

            var ob = new GameObject("meshobject");
            var surface = new GameObject("Surface Gameobject");

            ob.transform.parent = bk.transform;
            ob.transform.localPosition = Vector3.zero;

            surface.transform.parent = bk.transform;
            surface.transform.localPosition = Vector3.zero;
                  
            _terrainBodyV.Clear();
            _terrainBodyT.Clear();
            _terrainBodyU.Clear();

            _terrainSurfaceV.Clear();
            _terrainSurfaceT.Clear();
            _terrainSurfaceU.Clear();
        }</span>
这里我们只是对2种网格的顶点信息和顶点绘制顺序进行了赋值。还没有对他们uv赋值了。这里我们画一张图给大家理解一下。



我们添加点的顺序依次是左上,左下,右上,右下,所以我们我们这个绘制顺序数组就可以很轻松得出来,分别0,3,1和0,2,3.最后就是添加uv和碰撞器及材质了。

           surface.transform.localPosition = Vector3.zero;

           <span style="background-color: rgb(255, 153, 0);"> AddColliderToChildMesh(_tempVector3S[i], _tempVector3S[i + 1], bk.transform);

            var ren = ob.AddComponent<MeshRenderer>();
            var filter = ob.AddComponent<MeshFilter>();
            ren.material = _bodyMeshMaterial;
            filter.mesh = terrainBodymesh;

            var ren1 = surface.AddComponent<MeshRenderer>();
            var filter1 = surface.AddComponent<MeshFilter>();
            ren1.material = _terrianMaterial;
            filter1.mesh = terrainFaceMesh;</span>

            _terrainBodyV.Clear();
这样一个方的网格就出来了。这里我们建一个类测试一下。
public class CreateTerrian : MonoBehaviour
{
}
先定义一个在控制面板上可以拖拽的2个材质。

    <span style="background-color: rgb(255, 153, 0);">private BKCurveEntity _bkCurveEntity;
    [SerializeField] private Material bm;
    [SerializeField] private Material tm;</span>
然后再start方法中我们生成4段方的网格。

  [SerializeField] private Material tm;

  <span style="background-color: rgb(255, 153, 0);">  private float _length;
    private float _originx;
    private float _x1, _x2, _x3, _x4;

    private List<float> _posFloats;
    private List<GameObject> _bkGameObjects; 
	void Start () 
    {
        _posFloats=new List<float>();
	    _bkCurveEntity=new BKCurveEntity();
        List<Vector3> points=new List<Vector3>();
        _bkGameObjects=new List<GameObject>();
	_originx = -10;//起始位置的x值为-10
	 _length = 0;
        _x1 = 0; _x2 = 2; _x3 = 6; _x4 = 5;
	    for (int i = 0; i < 3; i++)
	    {
            points.Add(new Vector3(-10, _x1, 0));
            points.Add(new Vector3(0, _x2, 0));
            points.Add(new Vector3(10, _x3, 0));
            points.Add(new Vector3(20, _x4, 0));


            _bkCurveEntity.IntilizedBkEntity(points, bm, tm, 0.5f);
            var ob=  _bkCurveEntity.GenerateMeshGameObject(new Vector3(_originx+_length, -5, 0), 0);
            _bkGameObjects.Add(ob);

            _length= _bkCurveEntity.GetBkLength();
            _originx=_bkCurveEntity.OriginVector3.x;
            _posFloats.Add(_originx);
           
            
            _x1 = _bkCurveEntity.P2.y; 
            _x2 = _bkCurveEntity.P3.y; 
            _x3 = _bkCurveEntity.P4.y;
            _x4 = GetRandomValue(2,8,_x3);
            points.Clear();
	    }
    }</span>
这里第二段网格的最后一个点的y值我们用随机函数生成。

   private float GetRandomValue(float min, float max, float referce)
    {
        while (true)
        {
            float endvalue = Random.Range(min, max);
            if (Mathf.Abs(endvalue - referce) > 2)
            {
                return endvalue;
            }
        }
    }
第二段网格的起始点就是第一段网格的终点。这样就轻松的搞定了一个方形的网格了,那么接下来我们想办法把一个方形的网格变成一个曲的网格了。这里我们可以用最常见的sin和cos函数来试试吧。

       for (float i = P2.x; i < right; )
        {
            i += 0.5f;
            if (i > right)
                i = right;        
       <span style="background-color: rgb(255, 153, 0);">     var leftTopPoint = P2+new Vector3(i,Mathf.Sin(i),0);</span><span style="background-color: rgb(255, 204, 102);">
</span>            _tempVector3S.Add(leftTopPoint);
        }




这就是效果,很显然我觉得很糟糕,因为2段之间没有连接起来,当然想让他们无缝连接起来也很简单。只需保证所有网格的起始点和终点他们的y保持一致就行了。这样虽然保证了网格是曲面,发现整个网格是有规则的没有随机性可言。所以我们只有想别的办法然他保证随机性和曲面了。(逗比刘这都不知道么,我来告诉你吧,用采样曲线啊,这你都不知道啊。)哦 我们可以用采样曲线,首先让我想到的就是贝塞尔曲线,那我们来看看如果想让2条贝塞尔曲线连接起来,2段曲线有什么关系呢?从下图来分析:

上图中有2段贝塞尔曲线分别为p0,c0,p1和p1,c2,p2。如果想让2段2次贝尔赛曲线连接起来,那么满足第一段贝尔赛曲线的终点和第二段杯赛曲线的起始点为同一个点,同样p1要在c0和c2线段上,我们在做完第一段贝塞尔曲线的时候,开始连接第二段贝塞尔曲线的时候,我们先通过p1c0向量求得c1控制点,然后在随机p2点就可以做出曲线了,思路已经很清晰了,我们可以试试,这里我就不试了,这样虽然可以做出曲线,但是对于y的取值是个费劲的问题,y的取值超出摄像机的视野范围,这对于我们来讲可以来说很简单同样也不简单。所以这里我们就不用这个曲线了,我们采用catmull rom曲线,不知道的朋友可以在wiki上查看https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline。接下来我们返回到BKCurveEntity这个类中,添加如下方法:

   private Vector3 CatmullRom(Vector3 P0, Vector3 P1, Vector3 P2, Vector3 P3, float t)
    {
        Vector3 c0 = P1;
        Vector3 c1 = (P2 - P0) * _factor;
        Vector3 c2 = (P2 - P1) * 3.0f - (P3 - P1) * _factor - (P2 - P0) * 2.0f * _factor;
        Vector3 c3 = (P2 - P1) * -2.0f + (P3 - P1) * _factor + (P2 - P0) * _factor;

        Vector3 curvePoint = c3 * t * t * t + c2 * t * t + c1 * t + c0;
        return curvePoint;
    }
采样曲线前面2个点就相当于我们的控制点,factor为曲线平滑因子范围是[0,1]。  t的范围是[0,1]为0时 该曲线取得p1。为1时该曲线取得p2。这样我们想让我们2段catmull曲线连接起来只需要,第一段曲线的p1,p2,p3等于第二段曲线的p0,p1,p2即可。这样给我们的曲线构建带来很大的方便。这里我们就将他改为该曲线线,效果就如最上面的图了。

       for (float i = P2.x; i < right; )
        {
            i += 0.5f;
            if (i > right)
                i = right;
           <span style="background-color: rgb(255, 153, 0);"> var t = GetCatmullRomT(P1, P2, P3, P4, i);
            var leftTopPoint = CatmullRom(P1, P2, P3, P4, t);</span>
            _tempVector3S.Add(leftTopPoint);
        }
添加方法如下:

   private float GetCatmullRomT(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float cur)
    {
        return 2 * (cur - p2.x) / (p3.x - p1.x);
    }
</pre>最后给草地添加碰撞器。<pre name="code" class="plain"> private void AddColliderToChildMesh(Vector3 originVector3, Vector3 endVector3, Transform parent)
    {
        var ob = new GameObject("Collider");
        ob.tag = "Ground";
        ob.transform.parent = parent;

        var length = Vector3.Distance(originVector3, endVector3);
        var midX = (endVector3.x + originVector3.x) / 2f;
        var midY = (originVector3.y + endVector3.y) / 2f;

        var offset = (endVector3 - originVector3);
        var eulerZ = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
        ob.transform.localPosition = new Vector3(midX, midY, 0f);
        ob.transform.rotation = Quaternion.Euler(0, 0, eulerZ);

        var col = ob.AddComponent<BoxCollider2D>();
        col.offset = new Vector2(0, -0.03f);
        col.size = new Vector2(length, 0.06f);
    }
这里给点建议,如果我们有时不是很特别想突出表现我们的山体,我们可以给曲线的点让他们分散的稀疏点,给我们的草地上的点添加密一点。如果有纰漏的话,大家可以把这个资源文件下载下来,如果有什么不懂的 可以加qq:185076149,大家可以一起讨论。资源链接:http://pan.baidu.com/s/1mh66kDq







  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要爬取动态生成的网页内容,您可以使用 Selenium 和 Chrome WebDriver 结合进行操作。Selenium 可以模拟用户在浏览器中的行为,包括滚动页面、点击按钮等,从而触发异步加载并获取到完整的网页内容。 以下是一个示例代码,演示如何使用 Selenium 和 Chrome WebDriver 爬取动态生成的网页内容: ```python from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 设置 Chrome WebDriver 的路径 webdriver_path = 'path/to/chromedriver' # 设置 Chrome WebDriver 的选项 chrome_options = Options() chrome_options.add_argument('--headless') # 无头模式,不打开浏览器窗口 chrome_options.add_argument('--disable-gpu') # 禁用 GPU 加速 # 启动 Chrome WebDriver driver = webdriver.Chrome(executable_path=webdriver_path, options=chrome_options) # 打开目标网页 driver.get("https://www.example.com") # 等待页面加载完成 wait = WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.TAG_NAME, "body"))) # 模拟滚动页面,触发异步加载 driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 等待异步加载完成 wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".load-more-button"))) # 获取网页源代码 page_source = driver.page_source # 处理网页源代码,提取需要的内容 # 关闭 Chrome WebDriver driver.quit() ``` 在上述示例中,我们通过设置 Chrome WebDriver 的选项来启动 Chrome 浏览器,并使用 `webdriver.Chrome()` 方法指定 Chrome WebDriver 的路径和选项。 然后,我们使用 `get()` 方法打开目标网页,并使用显式等待等待页面加载完成。 接下来,我们使用 `execute_script()` 方法模拟滚动页面,以触发异步加载。然后,我们再次使用显式等待等待异步加载完成。 最后,我们使用 `page_source` 属性获取网页的源代码,并可以在此基础上进行处理,提取需要的内容。 请注意,这只是一个示例代码,具体的操作和等待条件可能需要根据目标网页的特点进行调整。另外,确保您已正确安装 Chrome WebDriver 并设置了正确的路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值