YIXUNFE / blog

文章区
151 stars 25 forks source link

用 JS 制作随机地图(三) #49

Open ajccom opened 8 years ago

ajccom commented 8 years ago

用 JS 制作随机地图(三)

上一节我们确定了地图中各个区域的海拔值并以此为依据划分了陆地与海洋。这节我们将会给地图区域增添湖泊和河流,并以此确定各区域的湿度。


湖泊

被陆地包围的海洋区域可以被认为是湖泊,所以我们只需要遍历海洋区域,确定外圈相邻区域都是陆地即可。

在遍历海洋区域之前,为了方便我们递归出一片区域的海洋或者湖泊,可以为区域对象添加一个 neighbor 属性,用以存放该区域的相邻区域。

//初始化
polygon.neighbor = []

相邻区域可以在设置线段对象的 owner 属性时一并设置,因为一个线段的所属区域必然只有一个或两个。

//将线段的拥有者互设为相邻区域
if (edge.owner.length > 0) {
  p = edge.owner[0]
  polygon.neighbor.push(p)
  if (p.neighbor.indexOf(polygon) === -1) {
    p.neighbor.push(polygon)
  }
}

接下来就可以递归出一片 N 个海洋类型区域的集合,然后判断是否有区域在地图边界上,没有在边界的就可以认为是湖泊。

function _setLakeInfo (polygon) {
  var arr = [], isOcean = 0

  // isCheckedLake 标记已经被识别过的区域
  if (polygon.land === 1 || polygon.isCheckedLake) {return}

  //递归出海洋/湖泊区域集合
  function checkGroup (polygon) {
    if (polygon.land === 0 && !polygon.isCheckedLake) {
      arr.push(polygon)
      polygon.isCheckedLake = 1

      if (isOcean === 0) {
        isOcean = polygon.isSide === 1 ? 1 : 0
      }

      polygon.neighbor.map(function (p) {
        checkGroup(p)
      })
    }
  }

  checkGroup(polygon)

  //如果是湖泊,这次递归出的区域设置为湖泊
  if (isOcean === 0) {
    arr.map(function (p) {
      p.land = 2
    })
  }
}

效果如下:

000

被陆地包围的湖泊


河流

河流的一般流向都是从高到低,所以我选取了一个阈值,海拔高于这个阈值的区域就可能产生河流。

//海拔值高于 100,就有小几率产生河流
if (polygon.elevation > 100 && Math.random() > 0.98) {
  ...
}

要确定河流的路径,可以先选取区域的角对象中海拔值最高的一个角,然后选取该角对象的相邻角对象中海拔值最低的一个角以确定一个线段对象,这个线段对象就是河流的一段路径,重复这个过程直到河流流向了海洋或者湖泊,就可以得到一条河流的完整路径。

这里继续利用递归确定出一条河流的完整路径。

function _setRiverInfo (polygon) {
  var temp = 0, c = null
  var checked = {}

  //选取海拔值最低的邻居顶点,获得河流路径(边对象)
  function _getRiverEdge (c, min) {
    var min = min || 0, d = null, temp = null, corner = null
    if (!c || c.land === 0 || c.land === 2 || c.isSide === 1) {return}
    checked[c.id] = 1
    c.neighbor.map(function (corner) {
      if ((min >= corner.elevation && !checked[corner.id]) || 0 === min) {
        min = corner.elevation
        d = corner
      }
      checked[corner.id] = 1
    })

    if (d) {
      temp = Edge.get(d.point, c.point)
      if (temp) {
        temp.isRiver = 1
        _getRiverEdge(d, min)
      }
    } else {//有时候河流的路径还没进入湖泊或者海洋就到断了,可以设置路径终点区域为湖泊
      c.owner.map(function (polygon) {
        polygon.land = 2
      })
    }
  }

  //如果海拔高于一定高度,则可能产生河流,流至海洋
  if (polygon.elevation > 100 && Math.random() > 0.98) {
    //选取一个海拔值最高的角
    temp = polygon.corners[0].elevation
    c = polygon.corners[0]
    polygon.corners.map(function (corner) {
      if (temp > corner.elevation) {
        temp = corner.elevation
        c = corner
      }
    })

    _getRiverEdge(c)
  }
}

上面的代码里有个修正,在河流的处理过程中,可能出现路径没有到达湖泊或者海洋就无法找到更低海拔的角对象的情况,这时候可以设置终点周围的区域为湖泊。

带有河流的效果图:

111

从高处流向地处的河流


湿度

确定了湖泊、河流,再加上之前的海洋与陆地,我们就可以为地图的各个陆地区域设定湿度值了。湿度值可以用于划分生物群落,比如高海拔高湿度的是雪山,低海拔高湿度的是雨林等等。

湿度的划分可以通过陆地区域距离淡水资源(湖泊、河流)的距离确定,距离越远,湿度值越低。

为了方便计算距离,我对区域的相邻区域的集合做递归。比如第一次查找自身区域的相邻区域,第二次查找所有相邻区域的相邻区域集合,这种方式可以更加高效的得到最近距离。

//设置湿度值
function _setMoistureInfo (polygon) {
  var checked = {}
  if (polygon.land !== 1) {return}

  if (polygon.hasRiverEdge) {
    polygon.moisture = 0.8
    return
  }

  //获取最近湖泊、河流距离
  function getDest (polygon, dest, neighborArray) {
    var next = [], result = false
    if (polygon.moisture) {return}
    dest++

    neighborArray.map(function (p) {
      if (checked[p.id]) {return}
      //作一个所有相邻区域的集合用作下一次检测
      next = next.concat(p.neighbor)
      checked[p.id] = 1
      //如果这次集合中包含淡水资源区域,即检测成功
      if (p.hasRiverEdge || p.land === 2) {result = true}
    })

    if (result) {
      polygon.moisture = Math.pow(0.8, dest)
    } else {
      getDest(polygon, dest, next)
    }
  }

  getDest(polygon, 0, polygon.neighbor)
}

地图的湿度分布情况:

m

湿度图


划分生态群落

有了湿度值,再加上海拔值,就可以确定一个区域是属于哪一个生态系统。

var biomesCategory = {
  '0': {name: '热带雨林', color: '#9cbba9'},
  '1': {name: '温带雨林', color: '#a4c4a8'},
  '2': {name: '泰加林', color: '#ccd4bb'},
  '3': {name: '雪山', color: '#ffffff'},
  '4': {name: '热带季风雨林', color: '#a9cca4'},
  '5': {name: '温带落叶林', color: '#b4c9a9'},
  '6': {name: '灌木林', color: '#c4ccbb'},
  '7': {name: '苔原', color: '#ddddbb'},
  '8': {name: '草原', color: '#c4d4aa'},
  '9': {name: '裸土', color: '#bbbbbb'},
  '10': {name: '焦土', color: '#999999'},
  '11': {name: '温带沙漠', color: '#e4e8ca'},
  '12': {name: '亚热带沙漠', color: '#e9ddc7'}
}

var biomes = [
  ['0', '1', '2', '3'],
  ['0', '5', '2', '3'],
  ['4', '5', '6', '3'],
  ['4', '8', '6', '7'],
  ['8', '8', '11', '9'],
  ['12', '11', '11', '10']
]

/**
 * _getBiomes 获取生态信息
 * @param {Number} elevation 海拔值
 * @param {Number} moisture 湿度值
 * @return {String} 生态名称
 * 需要折算海拔值和湿度值
 */
function _getBiomes (elevation, moisture) {
  var lv = Math.floor((elevation - oceanElevation) / 20)
  lv = Math.min(3, lv)
  moisture = Math.min(5, Math.floor((moisture - 1) / Math.ceil(polygonNum / 1000)))
  return biomesCategory[biomes[moisture][lv]]
}

获取到生态后,我们就可以绘制地图的生态图了。

m

生态图


总结

这一节我们一步一步地获取湖泊、河流到最后的生态属性,已经将地图的主要元素全部呈现在大家眼前。下一节我们将学习如何将地图变的更加美观。

本节的 DEMO 在这里


参考文章

http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/


Thanks