我正在为用 JavaScript/TypeScript 编写的游戏客户端开发软件操纵杆实现。我需要检测圆半径内发生 4 向移动(上、下、左、右)事件的不同区域。目前我的映射如下所示(因为我只知道如何使用四边多边形):
但是我想要实现的是这样的:
紫色代表横向移动的区域。绿色为垂直。灰色和黑色是死区(游戏),不应进行定向移动。
我正在使用的圆的半径是 75 像素。内部灰色死区半径为 24 像素。
以下代码是我当前如何检测方向的示例(不准确,因为我只包含与此问题相关的内容):
/**
* Retrieves the interpreted direction of joystick.
*
* Returned directions are represented using the following integer values:
* - 0: no movement (stop)
* - 1: up
* - 2: right
* - 3: down
* - 4: left
*
* @param relX {number}
* Point of event on X axis relative to joystick center.
* @param relY {number}
* Point of event on Y axis relative to joystick center.
* @return {number}
* Facing direction the joystick's state represents.
*/
private getPressedDirection(relX: number, relY: number): number {
let dir = 0;
const playThreshold = 24;
if (relX < -playThreshold) {
dir = 4;
} else if (relX > playThreshold) {
dir = 2;
} else if (relY < -playThreshold) {
dir = 1;
} else if (relY > playThreshold) {
dir = 3;
}
return dir;
}
正如您可能看到的,它存在多个问题。检测区域的大小不相等。死区区域是正方形而不是圆形。并且它允许在主圆半径之外检测方向。
如果我理解正确,我需要使用毕达哥拉斯定理来检测圆半径的边界,如https://stackoverflow.com/a/7227057/4677917以及我发现的许多其他答案中的建议,执行如下操作:
private getPressedDirection(relX: number, relY: number): number {
let dir = 0;
const radius = 75;
const playThreshold = 24;
const eventResult = Math.pow(relX, 2) + Math.pow(relY, 2);
const hit = eventResult < Math.pow(radius, 2) && eventResult > Math.pow(playThreshold, 2);
...
如果这是正确的,它会告诉我事件是否发生在操纵杆圆圈的边界内。但我仍然不确定如何检测事件发生的区域。
注意:https://www.w3resource.com/javascript-exercises/javascript-math-exercise-35.php建议使用毕达哥拉斯定理作为
Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2))
。但据我了解,平方根是为了求三角形第三条边的长度。从其他答案来看,我似乎想简单地使用Math.pow(relX, 2) + Math.pow(relY, 2)
。
感谢@CBroe指出我需要寻找什么。
我可以使用如下函数找到相对于操纵杆中心的角度:
private pointToDeg(relX: number, relY: number): number {
// convert coordinates to radians
const rad = Math.atan2(relY, relX);
// convert radians to angle of degrees
return rad * 180 / Math.PI;
}
我将角度区域映射到操纵杆:
// format: [direction, angleMin, angleMax]
private readonly sectors: number[][] = [
// NOTE: 0 degrees represents right on joystick plane
[1, 225, 314.9],
[2, 315, 44.9],
[3, 45, 134.9],
[4, 135, 224.9]
];
然后我可以迭代映射:
private getPressedDirection(relX: number, relY: number): number {
const angle = this.pointToDeg(relX, relY);
for (const s of this.sectors) {
if (this.sectorContains(s[1], s[2], angle)) {
return s[0];
}
}
return 0;
}
/**
* Checks if an angle is within a specified sector region.
*/
private sectorContains(min: number, max: number, angle: number): boolean {
// ensure values are within boundaries of 360 degrees
min %= 360;
max %= 360;
angle %= 360;
// normalize to positive degrees values
min = min < 0 ? min + 360 : min;
max = max < 0 ? max + 360 : max;
angle = angle < 0 ? angle + 360 : angle;
// check if within range
if (min > max) {
return angle >= min || angle <= max;
}
return angle >= min && angle <= max;
}
检查点是否在非死区区域内的第二个问题可以通过如下函数解决:
private isHit(relX: number, relY: number): boolean {
const dist = Math.pow(Math.abs(relX), 2) + Math.pow(Math.abs(relY), 2);
return dist < Math.pow(this.radius, 2) && dist > Math.pow(this.playThreshold, 2);
}
private getPressedDirection(relX: number, relY: number): number {
if (!this.isHit(relX, relY)) {
return 0;
}
const angle = this.pointToDeg(relX, relY);
for (const s of this.sectors) {
if (this.sectorContains(s[1], s[2], angle)) {
return s[0];
}
}
return 0;
}
如果我犯了任何错误,我会尽力改正。它不是完全从实际代码复制的。