准备做个躲猫猫的demo,先实现视野有时间再做做别的
首先看预览,一个小人在城市里穿行
原理图
然后是代码
https://github.com/crytion111/visionAndMask
第一步很简单, 根据墙体的节点判断墙体的顶点位置, 理论支持各种凸多边体
// 传入矩形, 返回4个点
getRectFourPoint(node:cc.Node) : cc.Vec3[]
{
let nWidth = node.width / 2;
let nHeight = node.height / 2;
//坐标转化成世界坐标系, 因为墙体的节点和玩家不一定在同一根节点下
let p1 = node.convertToWorldSpaceAR(cc.v3(-nWidth, -nHeight)); //左下
let p2 = node.convertToWorldSpaceAR(cc.v3(+nWidth, -nHeight)); //右下
let p3 = node.convertToWorldSpaceAR(cc.v3(+nWidth, +nHeight)); //右上
let p4 = node.convertToWorldSpaceAR(cc.v3(-nWidth, +nHeight)); //左上
let posArr = [p1, p2, p3, p4]
return posArr;
}
第二步,计算出这个墙体的所有顶点和主角的向量, 然后算出哪两个顶点离的最远,也就是向量夹角最大,那么这两个顶点肯定就是视野边缘
// 检测矩形和某个点的边界. 找出夹角最大的两个角
/**@
* @param posArr 矩形的4个顶点
* @param posPlayer 玩家位置
* @param targetNode 玩家节点
*/
checkRectEdge(posArr:cc.Vec3[], posPlayer:cc.Vec3, targetNode:cc.Node): cc.Vec3[]
{
// 存放向量用于计算
let angleArr:cc.Vec3[] = [];
for (let tempWorldPos of posArr)
{
//坐标转化, 因为getRectFourPoint中转出的是世界坐标,要变成主角所在的坐标系
let tempPos = targetNode.parent.convertToNodeSpaceAR(tempWorldPos);
let deX = tempPos.x - posPlayer.x
let deY = tempPos.y - posPlayer.y
angleArr.push(cc.v3(deX, deY));
}
let nMaxAngle = 0;
let nMaxIndex = 0;
let nMinIndex = 0;
// 计算向量夹角,看看谁最大
for (let i = 0; i < angleArr.length; i++)
{
let pos1 = angleArr[i];
for (let j = i + 1; j < angleArr.length; j++)
{
let pos2 = angleArr[j];
// 向量夹角计算公式,算出两个向量夹角, 那么夹角最大的肯定是视野边缘
let cosO = ((pos2.x * pos1.x)+(pos2.y * pos1.y)) / (pos1.mag() * pos2.mag())
let nJiaJiao = Math.acos(cosO) * (180 / Math.PI)
//找出最大的夹角,然后记录下两个点的下标
if (nJiaJiao > nMaxAngle)
{
nMaxAngle = nJiaJiao;
nMaxIndex = i;
nMinIndex = j;
}
}
}
//返回这个墙体的两个视野边缘点
return [targetNode.parent.convertToNodeSpaceAR(posArr[nMaxIndex]),
targetNode.parent.convertToNodeSpaceAR(posArr[nMinIndex])];
}
第三步, 画出计算的点围成的区域, 一共4个点围成这4个区域, 就是两个墙体边缘点,和两个延长线的终点
showGraLines()
{
this.graphicsLine.clear();
let posPlayer = this.nodePlayer.position;
//遍历所有的墙体节点
this.nodeWallRootArr.forEach((nodeRoot, index) =>
{
let nodeWall = nodeRoot.getChildByName("nodeWall")
let posArr = this.getRectFourPoint(nodeWall);
//拿到墙的节点,
// let posArr = this.getRectFourPoint(nodeRoot);
let drawPosArr = this.checkRectEdge(posArr, posPlayer, this.nodePlayer);
/* 先画墙体边缘的点,
再画两个延长的阴影区域,
再往回画另一个墙体边缘的点
*/
this.graphicsLine.moveTo(drawPosArr[0].x, drawPosArr[0].y); //先画墙体边缘的点,
for (let i in drawPosArr)
{
let pos = drawPosArr[i];
//将阴影部分至少延长到屏幕外面,就是放大墙体边缘和主角的向量长度
let pos1111 = cc.v3((pos.x - posPlayer.x) * 9999, (pos.y - posPlayer.y) * 9999);
//再画两个延长的阴影区域,
this.graphicsLine.lineTo(posPlayer.x + pos1111.x, posPlayer.y + pos1111.y);
}
//再往回画另一个墙体边缘的点
this.graphicsLine.lineTo(drawPosArr[1].x, drawPosArr[1].y);
})
//填充所有这4个点围成的区域
this.graphicsLine.fill();
this.graphicsLine.stroke();
}
游戏初始化了500个墙体,
但是遍历时只校验显示范围内的,所以也不怎么消耗性能