Anteriormente, exploramos el enfoque del uso de regiones vectoriales para implementar el campo de visión de una torreta. Las tropas se acercaron a la torreta en campo abierto y no había obstáculos entre ellos. Ahora supongamos que hay un impedimento, digamos una pared, que oscurece la visibilidad de la tropa desde la torreta; ¿Cómo deberíamos hacer eso? Este tutorial sugiere un enfoque para abordar este problema..
Echemos un vistazo al resultado final en el que trabajaremos. Haga clic en la torreta en la parte inferior de la etapa para iniciar la simulación.
Así que esto es lo que intentamos lograr en este tutorial. Observa la imagen de arriba. La torreta puede ver la unidad del soldado si está dentro del campo de visión de la torreta (arriba). Una vez que colocamos un muro entre la torreta y el soldado, la visibilidad del soldado está protegida de la torre..
En primer lugar, hagamos una pequeña revisión. Digamos que el vector de la línea de visión de la torreta es P y el vector de la torreta al soldado es Q. El soldado es visible para la torreta si:
Arriba está el pseudocódigo para el enfoque que emprenderemos. Determinar si el soldado está dentro del campo de visión de la torreta (FOV) se explica en el Paso 2. Ahora vamos a determinar si el soldado está detrás de una pared.
Usaremos operaciones vectoriales para lograr esto. Estoy seguro de que por la mención de esto, el producto de punto y el producto cruzado vienen rápidamente a la mente. Haremos un pequeño desvío para revisar estas dos operaciones vectoriales solo para asegurarnos de que todos puedan seguir.
Revisemos las operaciones vectoriales: producto punto y producto cruzado. Esta no es una clase de matemáticas, y hemos cubierto esto con más detalle anteriormente, pero aún así, es bueno refrescar nuestra memoria en los trabajos, así que he incluido la imagen de arriba. El diagrama muestra la operación "B punto A" (esquina superior derecha) y la operación "B cruz A" (esquina inferior derecha).
Más importantes son las ecuaciones de estas operaciones. Hecha un vistazo a la imagen de abajo. | A |
y | B |
referirse a magnitud escalar de cada vector - la longitud de la flecha. Tenga en cuenta que el producto punto se relaciona con el coseno del ángulo entre los vectores, y el producto cruzado se relaciona con el seno del ángulo entre los vectores.
Al profundizar en el tema, la trigonometría viene a jugar: el seno y el coseno. Estoy seguro de que estos gráficos reavivan recuerdos (o agonías) cariñosos. Haga clic en los botones en la presentación de Flash a continuación para ver esos gráficos con diferentes unidades (grados o radianes).
Tenga en cuenta que estas formas de onda son continuas y repetitivas. Por ejemplo, puede cortar y pegar la onda sinusoidal en el rango negativo para obtener algo como abajo.
La licenciatura | Seno de grado | Coseno de grado |
-180 | 0 | -1 |
-90 | -1 | 0 |
0 | 0 | 1 |
90 | 1 | 0 |
180 | 0 | -1 |
La tabla anterior muestra los valores de coseno y seno correspondientes a grados específicos. Notará que el gráfico de seno positivo cubre un rango de 0 ° a 180 ° y el gráfico de coseno positivo cubre de -90 ° a 90 °. Relacionaremos estos valores con el producto punto y el producto cruzado más adelante..
Entonces, ¿cómo pueden todos estos ser útiles? Para cortar a la persecución, el producto punto es una medida de cómo paralela los vectores son mientras que el producto cruzado es una medida de cómo ortogonal los vectores son.
Vamos a tratar con el producto punto primero. Recuerde la fórmula para el producto puntual, como se mencionó en el Paso 4. Podemos determinar si el resultado es positivo o negativo simplemente observando el coseno del ángulo intercalado entre los dos vectores. ¿Por qué? Porque la magnitud de un vector es siempre positiva. El único parámetro que queda para dictar el signo del resultado es el coseno del ángulo.
De nuevo, recuerde que el gráfico de coseno positivo cubre -90 ° - 90 °, como en el Paso 6. Por lo tanto, el producto de puntos de A con cualquiera de los vecinos L, M, N, O anteriores producirá un valor positivo, porque el ángulo encajado ¡entre A y cualquiera de esos vectores está dentro de -90 ° y 90 °! (Para ser precisos, el rango positivo es más como -89 ° - 89 ° porque tanto -90 ° como 90 ° producen valores de coseno de 0, lo que nos lleva al siguiente punto). El producto puntual entre A y P (dado P es perpendicular a A) producirá 0. El resto creo que ya se puede adivinar: el producto de puntos de A con K, R o Q producirá un valor negativo.
Al utilizar el producto punto, podemos dividir el área en nuestro escenario en dos regiones. El producto punto del vector a continuación con cualquier punto que se encuentre dentro de la región marcada con "x" producirá un valor positivo, mientras que el producto punto con aquellos en la región marcada con "o" producirá valores negativos.
Vamos a pasar al producto cruzado. Recuerde que el producto cruzado se refiere a la seno De ángulo intercalado entre los dos vectores. El gráfico de seno positivo cubre un rango de 0 ° a 180 °; el rango negativo cubre 0 ° a -180 °. La imagen de abajo resume estos puntos..
Entonces, al observar nuevamente el diagrama del Paso 7, el producto cruzado entre A y K, L o M producirá valores positivos, mientras que el producto cruzado entre A y N, O, P o Q producirá valores negativos. El producto cruzado entre A y R producirá 0, ya que el seno de 180 ° es 0.
Para aclarar aún más, el producto cruzado del vector entre cualquier punto que se encuentre en la región marcada con "o" a continuación será positivo, mientras que los de la región marcada con "x" serán negativos.
Un punto a tener en cuenta es que, a diferencia del producto puntual, el producto cruzado es sensible a la secuencia. Esto significa resultados de AxB
y BxA
Será diferente en términos de dirección. Así que a medida que escribimos nuestro programa, debemos ser precisos al elegir qué vector comparar con.
(Nota: estos conceptos explicados se aplican al espacio cartesiano 2D).
Para reforzar su comprensión, he colocado aquí una pequeña aplicación para que pueda jugar. Haz clic en la bola azul en la parte superior del escenario y arrástrala. A medida que se mueve, el valor del cuadro de texto se actualizará dependiendo de la operación que haya elegido (punto o producto cruzado entre la flecha estática y la que usted controla).
Puede observar una rareza con la dirección invertida del producto cruzado. La región en la parte superior es negativa y la parte inferior es positiva, en contraste con nuestra explicación en el paso anterior. Bueno, esto se debe a que el eje y se invierte en el espacio de coordenadas Flash en comparación con el espacio de coordenadas cartesianas; apunta hacia abajo, mientras que tradicionalmente los matemáticos lo toman como apuntando hacia arriba.
Ahora que has entendido el concepto de regiones, hagamos un poco de práctica. Dividiremos nuestro espacio en cuatro cuadrantes: A1, A2, B1, B2.
He tabulado los resultados para verificar a continuación. "Vector" aquí se refiere a la flecha en la imagen de arriba. "Punto" se refiere a cualquier coordenada en la región especificada. El vector divide el escenario en cuatro áreas principales, donde los divisores (líneas de puntos) se extienden hasta el infinito.
Región | Vector sobre diagrama producto cruzado con punto | Vector en diagrama punto producto con punto |
A1 | (+), debido al espacio de coordenadas Flash | (+) |
A2 | (+) | (-) |
B1 | (-), debido al espacio de coordenadas Flash | (+) |
B2 | (-) | (-) |
Aquí está la presentación Flash mostrando las ideas como se explica en el paso 10. Haga clic derecho en el escenario para abrir el menú contextual y seleccione la región que desea ver resaltada.
Aquí está la implementación de ActionScript del concepto explicado en el Paso 10. No dude en ver todo el fragmento de código en la descarga de origen, como AppLine.as
.
// color de resaltado según la función de selección privada del usuario color (): void // cada bola en el escenario se compara con las condiciones para cada caso seleccionado (elemento var: bola en sp) var vec1: Vector2D = nuevo Vector2D (elemento. x - stage.stageWidth * 0.5, item.y - stage.stageHeight * 0.5); if (seleccione == 0) if (vec.vectorProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (seleccione == 1) if (vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (seleccione == 2) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1)> 0) item.col = 0xFF9933; else item.col = 0x334455; else if (seleccione == 3) if (vec.vectorProduct (vec1)> 0 && vec.dotProduct (vec1) <0) item.col = 0xFF9933; else item.col = 0x334455; else if (select == 4) if (vec.vectorProduct(vec1) < 0 &&vec.dotProduct(vec1) > 0) item.col = 0xFF9933; else item.col = 0x334455; else if (seleccione == 5) if (vec.vectorProduct (vec1) < 0 &&vec.dotProduct(vec1) < 0) item.col = 0xFF9933; else item.col = 0x334455; item.draw(); //swapping case according to user selction private function swap(e:ContextMenuEvent):void if (e.target.caption == "VectorProduct") select = 0; else if (e.target.caption == "DotProduct") select = 1; else if (e.target.caption == "RegionA1") select = 2; else if (e.target.caption == "RegionA2") select = 3; else if (e.target.caption == "RegionB1") select = 4; else if (e.target.caption == "RegionB2") select = 5;
Habiendo entendido las interpretaciones geométricas del producto puntual y el producto cruzado, lo aplicaremos a nuestro escenario. La presentación anterior de Flash muestra variaciones del mismo escenario y resume las condiciones aplicadas a un soldado protegido por una pared dentro del FOV de la torreta. Puedes desplazarte por los marcos con los botones de flecha.
Las siguientes explicaciones se basan en el espacio de coordenadas de Flash 2D. En el Marco 1, se coloca una pared entre la torreta y el soldado. Sean A y B los vectores de la torreta a la cola y la cabeza del vector de la pared, respectivamente. Sea C el vector de la pared y D el vector desde la cola de la pared hasta el soldado. Finalmente, sea Q el vector de la torreta al soldado..
He tabulado las condiciones resultantes abajo.
Ubicación | Producto cruzado |
La tropa esta frente a la pared | C x D> 0 |
La tropa esta detras de la pared | C x D |
Esta no es la única condición aplicable, porque también debemos restringir al soldado dentro de las líneas de puntos en ambos lados. Echa un vistazo a los marcos 2-4 para ver el siguiente conjunto de condiciones.
Ubicación | Producto cruzado |
La tropa está dentro de los lados del muro.. | Q x A 0 |
La tropa está a la izquierda del muro. | Q x A> 0, Q x B> 0 |
La tropa está a la derecha del muro. | Q x A |
Creo que mis lectores pueden ahora elegir las condiciones apropiadas para determinar si el soldado está oculto o no. Tenga en cuenta que este conjunto de condiciones se evalúa después de que encontramos que las tropas están dentro del FOV de la torreta (consulte el paso 3).
Aquí está la implementación de ActionScript de los conceptos explicados en el Paso 13. La imagen de arriba muestra el vector inicial del muro, C. Haga clic y arrastre el botón rojo a continuación y muévalo para ver el área protegida. Puede ver el código fuente completo en HiddenSector.as
.
Bien, espero que hayas experimentado con la bola roja, y si eres lo suficientemente observador, es posible que hayas notado un error. Tenga en cuenta que no hay área protegida cuando el botón rojo se desplaza a la izquierda del otro extremo de la pared, invirtiendo así el vector de pared para apuntar a la izquierda en lugar de a la derecha. La solución está en el siguiente paso..
Sin embargo, antes de eso veamos un fragmento de código ActionScript importante aquí en HiddenSector.as
:
función privada highlight (): void var lineOfSight: Vector2D = nuevo Vector2D (0, -50) var sector: Number = Math2.radianOf (30); para cada (var item: Ball in sp) var turret_sp: Vector2D = new Vector2D (item.x - turret.x, item.y - turret.y); // Q if (Math.abs (lineOfSight.angleBetween (turret_sp)) < sector) var wall:Vector2D = new Vector2D(wall2.x - wall1.x, wall2.y - wall1.y); //C var turret_wall1:Vector2D = new Vector2D(wall1.x - turret.x, wall1.y - turret.y); //A var turret_wall2:Vector2D = new Vector2D(wall2.x - turret.x, wall2.y - turret.y); //B var wall_sp:Vector2D = new Vector2D (item.x - wall1.x, item.y - wall1.y); //D if ( wall.vectorProduct (wall_sp) < 0 // C x D && turret_sp.vectorProduct(turret_wall1) < 0 // Q x A && turret_sp.vectorProduct(turret_wall2) > 0 // Q x B) item.col = 0xcccccc else item.col = 0; item.draw ();
Para resolver este problema, necesitamos saber si el vector de pared apunta hacia la izquierda o hacia la derecha. Digamos que tenemos un vector de referencia, R, que siempre apunta hacia la derecha.
Direccion de vector | Producto de punto |
La pared apunta hacia la derecha (el mismo lado que R) | w R> 0 |
El muro apunta hacia la izquierda (lado opuesto de R) | w R |
Por supuesto, hay otras formas de solucionar este problema, pero creo que es una oportunidad para utilizar los conceptos expresados en este tutorial, así que ya está..
A continuación se muestra una presentación en Flash que implementa la corrección explicada en el Paso 15. Después de jugar con ella, desplácese hacia abajo para verificar los ajustes de ActionScript..
Se resaltan los cambios de la implementación anterior. Además, los conjuntos de condiciones se redefinen de acuerdo con la dirección de la pared:
función privada highlight (): void var lineOfSight: Vector2D = nuevo Vector2D (0, -50); sector var: Número = Math2.radianOf (30); var pointToRight: Vector2D = nuevo Vector2D (10, 0); // agregado en la segunda versión para cada (var item: Ball in sp) var turret_sp: Vector2D = nuevo Vector2D (item.x - turret.x, item.y - turret.y); // Q if (Math.abs (lineOfSight.angleBetween (turret_sp)) < sector) var wall:Vector2D = new Vector2D(wall2.x - wall1.x, wall2.y - wall1.y); //C var turret_wall1:Vector2D = new Vector2D(wall1.x - turret.x, wall1.y - turret.y); //A var turret_wall2:Vector2D = new Vector2D(wall2.x - turret.x, wall2.y - turret.y); //B var wall_sp:Vector2D = new Vector2D (item.x - wall1.x, item.y - wall1.y); //D var sides: Boolean; //switches according to wall direction if (pointToRight.dotProduct(wall) > 0) sides = wall.vectorProduct (wall_sp) < 0 // C x D && turret_sp.vectorProduct(turret_wall1) < 0 // Q x A && turret_sp.vectorProduct(turret_wall2) > 0 // Q x B else sides = wall.vectorProduct (wall_sp)> 0 // C x D && turret_sp.vectorProduct (turret_wall1) 0 // Q x A && turret_sp.vectorProduct (turret_wall2) < 0 // Q x B if (sides) item.col = 0xcccccc else item.col = 0; item.draw();
Echa un vistazo a la fuente completa en HiddenSector2.as
.
Ahora vamos a parchar nuestro trabajo en Scene1.as
del tutorial anterior. Primero, levantaremos nuestro muro..
Iniciamos las variables.,
la clase pública Scene1_2 extiende Sprite private var river: Sprite; private var wall_origin: Vector2D, wall: Vector2D; // añadido en el segundo tutorial privado var tropas: Vector.; private var troopVelo: Vector. ;
… Luego dibuja la pared por primera vez,
función pública Scene1_2 () makeTroops (); makeRiver (); makeWall (); // añadido en el segundo tutorial makeTurret (); turret.addEventListener (MouseEvent.MOUSE_DOWN, inicio); function start (): void stage.addEventListener (Event.ENTER_FRAME, move);
función privada makeWall (): void wall_origin = new Vector2D (200, 260); wall = new Vector2D (80, -40); graphics.lineStyle (2, 0); graphics.moveTo (wall_origin.x, wall_origin.y); graphics.lineTo (wall_origin.x + wall.x, wall_origin.y + wall.y);
... y volver a dibujar en cada cuadro, porque el graphics.clear ()
llamada está en algún lugar en comportamientoToreta ()
:
// agregado en el segundo tutorial movimiento de función privada (e: evento): void behaviourTroops (); comportamentalTurret (); redrawWall ();
// añadido en la segunda función privada tutorial redrawWall (): void graphics.lineStyle (2, 0); graphics.moveTo (wall_origin.x, wall_origin.y); graphics.lineTo (wall_origin.x + wall.x, wall_origin.y + wall.y);
Las tropas también interactuarán con la pared. Cuando chocan con la pared, se deslizarán a lo largo de la pared. No intentaré entrar en detalles, ya que se ha documentado ampliamente en Reacción de colisión entre un círculo y un segmento de línea. Aliento a los lectores a que lo revisen para una explicación más detallada..
El siguiente fragmento de código vive en la función. ComportamientoTroops ()
.
// Versión 2 // si avanza por el río, disminuya la velocidad // si choca contra el muro, deslice el paso // else normal speed var collideWithRiver: Boolean = river.hitTestObject (tropas [i]) var wall_norm: Vector2D = wall.rotate ( Math2.radianOf (-90)); var wall12Troop: Vector2D = nuevo Vector2D (tropas [i] .x - wall_origin.x, tropas [i] .y - wall_origin.y); var collideWithWall: Boolean = tropas [i] .rad> Math.abs (wall12Troop.projectionOn (wall_norm)) && wall12Troop.getMagnitude () < wall.getMagnitude() && wall12Troop.dotProduct(wall) > 0; if (collideWithRiver) tropas [i] .y + = troopVelo [i] .y * 0.3; else if (collideWithWall) // reposicionar tropa var projOnNorm: Vector2D = wall_norm.normalise (); projOnNorm.scale (tropas [i] .rad -1); var projOnWall: Vector2D = wall.normalise (); projOnWall.scale (wall12Troop.projectionOn (wall)); var reposition: Vector2D = projOnNorm.add (projOnWall); tropas [i] .x = wall_origin.x + reposition.x; tropas [i] .y = wall_origin.y + reposition.y; // deslice a través del ajuste de var de la pared: Number = Math.abs (troopVelo [i] .projectionOn (wall_norm)); var slideVelo: Vector2D = wall_norm.normalise (); slideVelo.scale (ajuste); slideVelo = slideVelo.add (troopVelo [i]) tropas [i] .x + = slideVelo.x; tropas [i] .y + = slideVelo.y; else tropas [i] .y + = troopVelo [i] .y
Finalmente, llegamos a la carne de este tutorial: establecer la condición y verificar si los soldados están detrás de la pared y, por lo tanto, protegidos de la visibilidad de la torreta. He resaltado los importantes códigos de parche:
// comprueba si el enemigo está a la vista // 1. Dentro del sector de vista // 2. Dentro del rango de visión // 3. Más cercano que el enemigo más cercano, var c1: Boolean = Math.abs (lineOfSight.angleBetween (turret2Item)) < Math2.radianOf(sectorOfSight) ; var c2:Boolean = turret2Item.getMagnitude() < lineOfSight.getMagnitude(); var c3:Boolean = turret2Item.getMagnitude() < closestDistance; //Checking whether troop is shielded by wall var withinLeft:Boolean = turret2Item.vectorProduct(turret2wall1) < 0 var withinRight:Boolean = turret2Item.vectorProduct(turret2wall2) > 0 var behindWall: Boolean = wall.vectorProduct (wall12troop) < 0; var shielded:Boolean = withinLeft && withinRight && behindWall //if all conditions fulfilled, update closestEnemy if (c1 && c2&& c3 && !shielded) closestDistance = turret2Item.getMagnitude(); closestEnemy = item;
Echa un vistazo al código completo en Scene1_2.as
.
Finalmente, podemos sentarnos y ver el parche en acción. Presione Ctrl + Enter para ver los resultados de su trabajo. He incluido una copia de la presentación de Flash de trabajo a continuación. Haga clic en la torreta en la parte inferior de la etapa para iniciar la simulación.