Allied Modders en español

Versión completa: Vectores de Half-Life en AMXX - Parte 2: Vectores cartesianos y traslación
Actualmente estas viendo una versión simplificada de nuestro contenido. Ver la versión completa con el formato correcto.
Vectores de Half-Life en AMXX

Temas a tratar:
  • Parte 1: Introducción
    • Magnitudes escalares y vectoriales.
    • Los vectores.
    • Tipos de vectores en Half-Life.
    • Operaciones básicas con vectores.
    • Movimiento de una entidad.
    • La librería <xs>.
  • Parte 2: Vectores cartesianos y traslación
    • El sistema de coordenadas cartesianas.
    • La posición de una entidad.
    • La velocidad de una entidad.
    • Traslación de una entidad.
    • El tamaño de una entidad.
  • Parte 3: Vectores angulares y rotación
    • Vectores de representación angular.
    • La orientación de una entidad.
    • La velocidad angular de una entidad.
    • Rotación de una entidad.
  • Parte 4: Movimiento de traslación y rotación
    • Obtención de vectores angulares a partir de vectores cartesianos.
    • Obtención de vectores cartesianos a partir de vectores angulares.
    • Movimiento general de una entidad.

Parte 2 - Vectores cartesianos y traslación

El sistema de coordenadas cartesianas
  • Qué es el sistema cartesiano
    Nota: Esta explicación es matemática escolar, y puedes omitirla si ya conoces el tema.

    Es la forma como podemos ubicar puntos en un plano, matemáticamente. Se llama plano cartesiano al plano en el que contamos con los ejes horizontal y vertical: X e Y, y con los que podemos ubicar cualquier punto con solo conocer las distancias del punto con el eje X y el eje Y.

    [Imagen: attachment.php?aid=7629]

    En el plano de la imagen, se ven 3 puntos:
    • En morado, el punto (0, 0). A este punto se le llama origen de coordenadas. Es un punto que está ubicado en el centro del plano, lugar donde se intersectan los ejes X e Y. Diremos que, cualquier otro punto que ubiquemos en el plano, estará en referencia a este punto.
    • En celeste, el punto (2, 3). Este punto se ubica a una distancia de 2 en el eje X, y a una distancia de 3, en el eje Y; con respecto al origen de coordenadas. ¿Se nota que, si una persona se encuentra en el origen, deberá caminar 2 en el eje X, y 3 en el eje Y; para llegar al punto celeste? Es por esto que al punto (0, 0) se le denomina origen.
    • En naranja, el punto (-3, 1). Nótese el signo negativo de la distancia en X. Esto significa, que estando en el origen (0, 0), debemos de retroceder una distancia de 3 en el eje X, y luego avanzar una distancia de 1 en la dirección del eje Y.
    ¡Las coordenadas pueden tener componentes negativas! Aquí, el signo solo indica la dirección, pero un punto (-3, 0) está a la misma distancia del origen que el punto (3, 0).
  • El espacio cartesiano
    El plano cartesiano es muy útil para ubicar puntos en dos dimensiones. Perfecto para juegos en 2D como el clásico Mario Bros.™. En Half-Life, todo está en 3 dimensiones. Aumentaremos una dimensión al plano y obtendremos el espacio cartesiano.

    [Imagen: attachment.php?aid=7643]

    ¿Notas el eje Z? Es nuestro tercer eje. Ahora, con 3 posiciones, podremos ubicar un punto en el espacio.

    Código PHP:
    new Float:fOrigin = { 2.03.05.0 }; // Posición del punto P 

    En el gráfico se ubica el punto P:
    • A 2 de distancia en el eje X desde el origen.
    • A 3 de distancia en el eje Y desde el origen.
    • A 5 de distancia en el eje Z desde el origen.

    ¡Nótese que la componente Z de la posición representa la altura a la que se encuentra el punto!
    Esto es una convención, porque en algunos textos académicos se utiliza el eje Y para la altura. Pero en Half-Life, es el eje Z.

    Dato extra (No se requiere saber esto para el resto del tutorial, pero está relacionado): Notemos que, los ejes X, Y, Z apuntan a una dirección cada uno. De esta dirección depende si las componentes son positivas o negativas. ¿Cómo sabemos hacia donde? Esto es una convención matemática, y una forma fácil de recordarla es con tu mano derecha:

    [Imagen: attachment.php?aid=7640]

    Estira tus dedos índice, medio y pulgar, como en la imagen. A donde apunte cada uno de los dedos, representa en qué dirección deben apuntar los ejes de coordenadas. Recuerda, sólo funciona con la mano derecha.
La posición de una entidad
  • El vector origin
    Código PHP:
    new Float:fOrigin[3];

    // Engine:
    entity_get_vector(iEntEV_VEC_originfOrigin);

    // Fakemeta:
    pev(iEntpev_originfOrigin);

    // Reapi:
    get_entvar(iEntvar_originfOrigin); 

    Este vector es la posición de una entidad, con respecto al origen de coordenadas.
    Nota: El origen de coordenadas está definido por el mapa.

    [Imagen: attachment.php?aid=7583]

    Este lugar es el origen de coordenadas en de_dust2. A partir de este punto, se definen las posiciones de todas las enidades.
    ¿Qué pasa si me ubico a 400 unidades del origen en el eje X?

    Código PHP:
    new Float:fNewOrigin[3] = { 400.00.00.0 };
    entity_set_vector(iIdEV_VEC_originfNewOrigin}; 

    [Imagen: attachment.php?aid=7584]

    Ahora sabemos en qué dirección está el eje X en de_dust2. Entonces, el eje Y estará en dirección a catwalk, y Z hacia arriba.

    [Imagen: attachment.php?aid=7585]

    Aplicación: Supongamos que necesito hacer que un jugador se eleve. ¿Recuerdan la suma de vectores? Aquí la usaremos.

    [Imagen: attachment.php?aid=7586]

    Código PHP:
    // Podemos obtener la posición actual del jugador
    new Float:fOldOrigin[3];
    entity_get_vector(iIdEV_VEC_originfOldOrigin);

    // Ahora, creamos un vector vertical, de 400.0 unidades de longitud
    new Float:fAdd[3] = { 0.00.0400.0 };

    // Finalmente, sumando los dos vectores, tendremos un vector desde el origen de coordenadas hasta la posición deseada
    new Float:fNewOrigin[3];
    // Suma de vectores
    xs_vec_add(fOldOriginfAddfNewOrigin);

    // Finalmente, establecemos la nueva posición al jugador
    entity_set_origin(iIdfNewOrigin); 

    Cita:Pero Mía, ¿por qué ese vector se llama origin en AMXX, si tú lo llamas posición?

    Resulta que, en muchas situaciones, es conveniente considerar como origen a la entidad misma, como veremos en la velocidad.
  • Los spawns de CSDM
    Una aplicación básica de la manipulación de posiciones es colocar a los jugadores en posiciones específicas del mapa. CSDM nos permite colocar las posiciones en un archivo ubicado en /configs/csdm.
    Ejemplo de un origin en de_dust2:

    Código:
    -2198 -538 179 8 -34 0 0 -3 -34 0

    Los 3 primeros números son la posición de un spawn. Por ahora, ignoremos los otros números. Supongamos que deseo crear mi propio punto de spawn. Utilizaré este código:

    Código PHP:
    #include <amxmodx>
    #include <engine>

    public plugin_init() {
        
    register_plugin("Origin test""0.1""Mia");
        
    register_clcmd("say origin""clcmd_origin");
    }

    public 
    clcmd_origin(iId) {
        new 
    Float:fOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOrigin);
        
    client_print(iIdprint_chat"Origin: %.0f %.0f %.0f"fOrigin[0], fOrigin[1], fOrigin[2]);


    [Imagen: attachment.php?aid=7587]

    Guardaré esta posición en el archivo de spawns /csdm/de_dust2.spawns.cfg
    Código:
    495 1424 132 0 0 0 0 0 0 0

    CSDM entonces posicionará a los jugadores en esta posición cuando aparezcan. Se puede hacer con un entity_set_origin en el call de Ham_Spawn post.
La velocidad de una entidad
  • Rapidez y velocidad
    • Se denomina rapidez (speed) a la distancia que recorre una entidad por unidad de tiempo, sin importar la dirección. Es un número (magnitud escalar).
      Una aplicación de rapidez es en EV_FL_maxspeed.
    • Se denomina velocidad al vector que describe el movimiento de la entidad en el instante, considerando rapidez y dirección. Teniendo el vector de velocidad, es posible obtener la rapidez calculando la longitud del vector (xs_vec_len).
  • El vector velocity
    Código PHP:
    new Float:fVelocity[3];

    // Engine:
    entity_get_vector(iEntEV_VEC_velocityfVelocity);

    // Fakemeta:
    pev(iEntpev_velocityfVelocity);

    // Reapi:
    get_entvar(iEntvar_velocityfVelocity); 

    Este vector representa la velocidad instantánea de una entidad. Se denomina instantánea porque esta velocidad puede cambiar en cualquier momento: cuando un jugador está en caída y toca el suelo, cuando un jugador cambia de arma corriendo, la pelota de SoccerJam se detiene sola por la fricción. La gravedad, la fricción y las colisiones son algunos modificadores de la velocidad, por lo que establecer la velocidad de una entidad no garantiza que esta se mueva por siempre.

    Recordemos que los vectores tienen una dirección y una longitud. Para los vectores de velocidad, la longitud del vector representa la rapidez con la que se mueve una entidad.

    El vector de velocidad es independiente del vector posición.

    [Imagen: attachment.php?aid=7588]

    En la imagen, hay un jugador corriendo en la dirección del eje X positivo. Sí, los ejes de coordenadas en los vectores de velocidad son los mismos que usamos en los vectores de posición. Esto nos facilita muchas acciones.
Traslación de una entidad
  • Traslación básica por impulsos
    Aplicación: Hagamos volar a un jugador.

    Código PHP:
    #include <amxmodx>
    #include <engine>

    public plugin_init() {
        
    register_plugin("Velocity test""0.1""Mia");
        
    register_clcmd("say fly""clcmd_fly");
    }

    public 
    clcmd_fly(iId) {
        
    // Usaremos una velocidad orientada hacia el eje Z positivo.
        
    new Float:fVelocity[3] = { 0.00.0500.0 }; // Velocidad de 500 hacia arriba
        
        
    entity_set_vector(iIdEV_VEC_velocityfVelocity);


    [Imagen: attachment.php?aid=7592]

    ¿Divertido, no?

    Cita:Mía, ¿por qué el jugador sube y baja con ese código, en lugar de quedarse volando?

    La gravedad nos ha controlado por completo. En realidad, ¡solo tuvimos una velocidad de 500.0 hacia arriba por un instante! (Exactamente: 1/fps segundos; 0.001 segundos en un servidor 1000 fps). En el siguiente frame, el motor de Half-Life calculó una nueva velocidad afectada por la gravedad. Veremos cómo es este cálculo más adelante. Si juegas con el valor de sv_gravity, podrás lograr que tu impulso se convierta en un movimiento perpetuo.

    Ha sido muy sencillo manipular la velocidad cuando esta se da en una sola dirección. Ahora veremos qué sucede si nos movemos en "diagonal".

    [Imagen: attachment.php?aid=7589]

    El jugador tiene una velocidad: (152, 198, 0)
    • La rapidez en el eje Y es mayor que la rapidez en el eje X. Esto significa que el jugador avanza más próximo al eje Y que al eje X.
    • La rapidez total se calcula obteniendo la longitud del vector.
      Código PHP:
      new Float:fVelocity[3] = { 152.0198.00.0 };
      // Pitágoras: raiz(x^2 + y^2 + z^2)
      new fSpeed xs_vec_len(fVelocity); // 249.62 

    Aplicación: Demos un impulso a este jugador. Hagamos que se mueva a una velocidad de 500.0 en la dirección hacia donde se está moviendo.

    Código PHP:
    // Obtener la velocidad actual
    new Float:fVelocity[3];
    entity_get_vector(iIdEV_VEC_velocityfVelocity);

    // La rapidez de este jugador era de 249.62. Hagamos que su rapidez sea 1.0 manteniendo la dirección. Esto se logra normalizando el vector.
    xs_vec_normalize(fVelocity); // { 0.6089, 0.7932, 0 }

    // Este nuevo vector normalizado mantiene la proporción entre las componentes. La componente en Y sigue siendo mayor que la componente en X.

    // Multiplicando este vector por la longitud deseada (la velocidad que queremos, 500.0), tendremos el vector que buscamos
    xs_vec_mul_scalar(fVelocity500.0fVelocity); // { 304.468, 396.610, 0.0 }

    // Aplicar la velocidad al jugador
    entity_set_vector(iIdEV_VEC_velocityfVelocity); 

    [Imagen: attachment.php?aid=7591]

    Aplicación: Hagamos que un jugador se dirija hacia otro.

    En la parte 1 del tutorial se explicó la resta vectorial. Veremos qué sucede si la usamos con las posiciones de dos jugadores.

    Código PHP:
    move_player(iIdiTargetId) {
        new 
    Float:fOrigin1[3], Float:fOrigin2[3];
        
        
    // Obtener las posiciones
        
    entity_get_vector(iTargetIdEV_VEC_originfOrigin1);
        
    entity_get_vector(iIdEV_VEC_originfOrigin2); 

    [Imagen: attachment.php?aid=7590]

    Código PHP:
    // Continuación del código anterior
        
    new Float:fSub[3];
        
    // Con esto tendremos un vector que apunte desde fOrigin2 hacia fOrigin1. La longitud de este vector será la distancia entre estos jugadores
        
    xs_vec_sub(fOrigin1fOrigin2fSub);

        
    // Normalicemos el vector, para quedarnos con la dirección, que es lo que nos interesa
        
    xs_vec_normalize(fSubfSub); // ¿Qué pasaría si omitimos este paso?

        // Multiplicando este vector por la rapidez deseada, tendremos el vector buscado
        
    xs_vec_mul_scalar(fSub500.0fSub);

        
    // Aplicar la velocidad a iId
        
    entity_set_vector(iIdEV_VEC_velocityfSub);


    Cita:Importante: Aunque utilizamos las posiciones de los dos jugadores con respecto al origen de coordenadas, el resultado no tiene ninguna relación con la distancia entre los jugadores y el origen del mapa. Esto es porque, al restar los vectores, obtuvimos un vector entre los dos jugadores, completamente independiente de las posiciones.

    Sugiero probar el código para un mejor entendimiento. A continuación, un plug-in completo, de prueba, que se puede usar con un solo jugador.
    Modo de uso: Escribir en el chat "make" para crear a un jugador falso. Luego, escribir "move" para movernos hacia aquel.

    Código PHP:
    #include <amxmodx>
    #include <engine>
    #include <xs>

    new iEnt;

    public 
    plugin_init() {
        
    register_plugin("Velocity test""0.1""Mia");
        
    register_clcmd("say make""clcmd_make")
        
    register_clcmd("say move""clcmd_move");
    }

    public 
    clcmd_make(iId) {
        if (
    iEnt == 0) {
            
    iEnt create_entity("info_target");
            
    entity_set_model(iEnt"models/player/sas/sas.mdl");
        }

        new 
    Float:fOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOrigin)
        
    entity_set_origin(iEntfOrigin);
        
        
    // Lo explicare en la tercera parte del tuto!
        
    new Float:fAngles[3];
        
    entity_get_vector(iIdEV_VEC_anglesfAngles)
        
    entity_set_vector(iEntEV_VEC_anglesfAngles)
    }

    public 
    clcmd_move(iId) {
        if (
    iEnt == 0) {
            
    client_print(iIdprint_chat"No has creado al jugador falso! Escribe 'make' para crearlo.");
            return 
    PLUGIN_CONTINUE;
        }
        
        
    move_player(iIdiEnt);
        return 
    PLUGIN_CONTINUE;
    }

    move_player(iIdiTargetId) {
        new 
    Float:fOrigin1[3], Float:fOrigin2[3];
        
        
    // Obtener las posiciones
        
    entity_get_vector(iTargetIdEV_VEC_originfOrigin1);
        
    entity_get_vector(iIdEV_VEC_originfOrigin2);

        new 
    Float:fSub[3];
        
    xs_vec_sub(fOrigin1fOrigin2fSub);

        
    // Ahora tenemos un vector que apunta desde iId hacia iTargetId. La longitud de este vector es la distancia entre estos jugadores

        // Normalicemos el vector, para quedarnos con la dirección, que es lo que nos interesa
        
    xs_vec_normalize(fSubfSub); // ¿Qué pasaría si omitimos este paso?

        // Multiplicando este vector por la rapidez deseada, tendremos el vector buscado
        
    xs_vec_mul_scalar(fSub500.0fSub);

        
    // Aplicar la velocidad a iId
        
    entity_set_vector(iIdEV_VEC_velocityfSub);


    Cita:Mía, ¿por qué el movimiento es muy corto?

    El impulso hacia arriba fue afectado por la gravedad. Del mismo modo, el impulso hacia una dirección fue afectado por la fricción con el piso, además de la gravedad si es que tu jugador falso estaba en el aire. Si juegas con el valor de sv_friction, o si tu entidad está en el aire con MOVETYPE_FLY, podrás lograr que tu impulso se convierta en un movimiento perpetuo.

    Cita:Ok Mía, me rindo. No logro entender qué pasa si borro la línea xs_vec_normalize.

    Si mantenemos la longitud del vector (la distancia entre los dos jugadores), haremos que la velocidad que vamos a establecer sea proporcional a esta distancia. Entonces, cuanto más separados estén los jugadores, más rápido será el movimiento. Me recuerda al seguimiento de los rehenes en CS. ¿Y a ti?
  • Seguimiento de entidades
    Vimos un ejemplo de seguimiento de una entidad con un impulso. Podemos repetir este impulso en el tiempo y tener un "seguimiento" de una entidad a otra.

    Aplicación: Mascota

    Hagamos una mascota voladora. Será una simple entidad no sólida, con MOVETYPE_FLY, que seguirá al jugador a una velocidad proporcional a la distancia que los separa.

    Código PHP:
    // Usamos la forward think para poder manipular la velocidad de la entidad varias veces en el tiempo
    public fw_NpcThink(iEnt) {
        
        
    // Obtener el index del jugador al que seguimos
        
    static iId;
        
    iId entity_get_edict(iEntEV_ENT_owner);
        
        
    // Calculemos un vector entre la posición de la mascota y la posición del jugador
        
    static Float:fDistVec[3], Float:fOwnerOrigin[3], Float:fEntOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOwnerOrigin);
        
    entity_get_vector(iEntEV_VEC_originfEntOrigin);
        
    // Si queremos un vector desde la mascota hasta el jugador, debemos restar la posición del jugador menos la posición de la mascota
        
    xs_vec_sub(fOwnerOriginfEntOriginfDistVec);
        
        
    // Hagamos que la mascota se mueva más rápido si está más lejos
        // ¿Recuerdas en el ejemplo anterior, qué pasa si no normalizamos el vector?
        
        // Ejemplo: Si la mascota está a 400 unidades de distancia, correrá a 800 u/s
        
    static Float:fVelocity[3];
        
    xs_vec_mul_scalar(fDistVec2.0fVelocity);
        
        
    // Establecer la nueva velocidad
        
    entity_set_vector(iEntEV_VEC_velocityfVelocity);
        
        
    // Como es una entidad no sólida y con MOVETYPE_FLY, si no modificamos la velocidad nosotros, esta seguirá moviéndose para siempre
        // Corregiremos la velocidad cada 0.2 segundos
        
    entity_set_float(iEntEV_FL_nextthinkhalflife_time() + 0.2);


    [Imagen: attachment.php?aid=7626]

    ¿No es una ternura? (◕ᴗ◕✿)

    Aquí el código completo, por si deseas probarlo.

    Código PHP:
    #include <amxmodx>
    #include <engine>
    #include <xs>

    // Classname para la entidad de la mascota
    new const CLASSNAME[] = "pet_test";

    // Un model de un zombie para una mascota amigable
    new const MODEL[] = "models/zombie.mdl";

    // Variables
    new g_model;

    public 
    plugin_precache() {
        
    g_model precache_model(MODEL);
    }

    public 
    plugin_init() {
        
    register_plugin("Pet test""0.1""Mia");
        
    register_think(CLASSNAME"fw_NpcThink");
        
    register_clcmd("say crear""clcmd_spawn");
    }

    public 
    clcmd_spawn(iId) {
        new 
    iEnt create_entity("info_target");
        
    entity_set_string(iEntEV_SZ_classnameCLASSNAME);
        
    entity_set_model(iEntMODEL);
        
    entity_set_int(iEntEV_INT_modelindexg_model);
        
    entity_set_int(iEntEV_INT_movetypeMOVETYPE_FLY);
        
    entity_set_edict(iEntEV_ENT_owneriId);
        
    entity_set_float(iEntEV_FL_nextthinkhalflife_time() + 0.2);
        new 
    Float:fOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOrigin);
        
    fOrigin[2] += 60.0;
        
    entity_set_origin(iEntfOrigin);
    }

    public 
    fw_NpcThink(iEnt) {
        
    // Obtener el index del jugador al que seguimos
        
    static iId;
        
    iId entity_get_edict(iEntEV_ENT_owner);
        
        
    // Calculemos un vector entre la posición de la mascota y la posición del jugador
        
    static Float:fDistVec[3], Float:fOwnerOrigin[3], Float:fEntOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOwnerOrigin);
        
    entity_get_vector(iEntEV_VEC_originfEntOrigin);
        
    // Si queremos un vector desde la mascota hasta el jugador, debemos restar la posición del jugador menos la posición de la mascota
        
    xs_vec_sub(fOwnerOriginfEntOriginfDistVec);
        
        
    // Hagamos que la mascota se mueva más rápido si está más lejos
        // ¿Recuerdas en el ejemplo anterior, qué pasa si no normalizamos el vector?
        
        // Ejemplo: Si la mascota está a 400 unidades de distancia, correrá a 800 u/s
        
    static Float:fVelocity[3];
        
    xs_vec_mul_scalar(fDistVec2.0fVelocity);
        
        
    // Establecer la nueva velocidad
        
    entity_set_vector(iEntEV_VEC_velocityfVelocity);
        
        
    // Como es una entidad no sólida y con MOVETYPE_FLY, si no modificamos la velocidad nosotros, esta seguirá moviéndose para siempre
        // Corregiremos la velocidad cada 0.2 segundos
        
    entity_set_float(iEntEV_FL_nextthinkhalflife_time() + 0.2);


    Aquí hay un ejemplo un poco más avanzado (un NPC zombie): https://amxmodx-es.com/showthread.php?ti...6#pid68026
    Con lo visto hasta ahora en el tutorial, ese código debería ser entendible.
  • Lanzamiento vertical de entidades
    En un ejemplo anterior, vimos cómo una velocidad hacia arriba en un instante, provocó que la entidad se impulse hacia arriba, se eleve, eventualmente se detenga en el aire y comience a caer de nuevo. La gravedad se encarga de modificar la velocidad en cada instante, siempre impulsándola hacia abajo (eje Z negativo).

    [Imagen: attachment.php?aid=7641]
    (Imagen tomada de StickMan Physics)

    La velocidad (lado izquierdo de la imagen) está siempre disminuyendo en el lanzamiento vertical, por acción de la gravedad. Esto hace que, la velocidad, que era hacia arriba al inicio, disminuya al punto de detenerse en un instante, y luego dirigirse hacia abajo.

    Cita:La ecuación que describe la velocidad en el tiempo es:

    [Imagen: attachment.php?aid=7630]

    Donde:
    • Vo es la velocidad inicial (la que establecimos en el impulso).
    • g es la gravedad (valor de sv_gravity multiplicado por EV_FL_gravity). Normalmente es 800.0.
    • t es el tiempo, en segundos.

    Con esta ecuación, podemos hacer algunos cálculos:
    • Calcular cuanto tiempo tarda en detenerse una entidad lanzada hacia arriba.
    • Calcular con qué velocidad debemos lanzar una entidad para que se detenga en "x" segundos.

    Aplicación: Usemos el código de ejemplo de impulso hacia arriba. ¿Cuánto tiempo permanece el jugador en el aire?

    Volvamos a la imagen gif de arriba. ¿Se nota que, cuando el objeto cae y vuelve a la posición de inicio, la velocidad es el negativo de la velocidad inicial? Esto es porque la gravedad ha hecho de las suyas, y al mismo ritmo en el que disminuyó la velocidad mientras el objeto se elevó, incrementó su velocidad negativamente hasta volver a la posición de inicio. Entonces, sabemos que la velocidad será el negativo de la velocidad inicial cuando la entidad haya regresado a la posición inicial.

    Cita:En la ecuación:
    • V = -500
    • Vo = 500
    • g = 800

    [Imagen: attachment.php?aid=7631]

    Podemos comprobar el resultado con este código:

    Código PHP:
    #include <amxmodx>
    #include <engine>
    #include <xs>

    new g_iId;
    new 
    Float:g_fTime;

    public 
    plugin_init() {
        
    register_plugin("Velocity test""0.1""Mia");
        
    register_clcmd("say fly""clcmd_fly");
    }

    public 
    clcmd_fly(iId) {
        
    // Usaremos una velocidad orientada hacia el eje Z positivo.
        
    new Float:fVelocity[3] = { 0.00.0500.0 }; // Velocidad de 500 hacia arriba
        
        
    entity_set_vector(iIdEV_VEC_velocityfVelocity);
        
        
    g_iId iId;
        
    g_fTime halflife_time();


    public 
    server_frame() {
        if (
    g_iId == 0)
            return;
        
        if (
    entity_get_int(g_iIdEV_INT_flags) & FL_ONGROUND) {
            
    client_print(g_iIdprint_chat"Tiempo transcurrido: %.2f segundos."halflife_time() - g_fTime);
            
    g_iId 0;
        }


    Cita:Mía, he probado el código y he visto que, a veces, el tiempo no es exactamente 1.25, aunque sí es un número muy cercano. ¿Qué sucede?

    Los cálculos de la física del juego están afectados por los FPS del servidor. Es muy difícil mantener un número alto de FPS perfectamente estable. Por esto, la física del juego no es muy precisa, y ocurren estos pequeños errores.

    Hemos visto la ecuación de la velocidad. Ahora pasemos a la ecuación de la posición.

    Cita:La ecuación que describe la posición de una entidad en el tiempo, respecto a la posición de inicio, en un lanzamiento vertical es:

    [Imagen: attachment.php?aid=7632]

    Donde:
    • Vo es la velocidad inicial (la que establecimos en el impulso).
    • g es la gravedad (valor de sv_gravity multiplicado por EV_FL_gravity). Normalmente es 800.0.
    • t es el tiempo, en segundos.

    La demostración de esta ecuación se puede encontrar en cualquier texto de física universitaria.

    Podemos usar esta ecuación para saber qué distancia se va a elevar una entidad a la que demos un impulso vertical.

    Aplicación: Usemos el código anterior. ¿Qué distancia se elevará el jugador?

    Con la primera ecuación, calculemos el tiempo que tarda la entidad en llegar al punto más alto. ¿Se nota en la animación arriba que, cuando el objeto lanzado está en el punto más alto, la velocidad es 0? Entonces, calculemos el tiempo que tarda la velocidad en volverse 0.

    Cita:
    • Vo = 500.0
    • g = 800.0
    • V = 0

    [Imagen: attachment.php?aid=7633]

    Utilicemos este valor de t en la segunda ecuación.

    [Imagen: attachment.php?aid=7634]

    Podemos comprobar el resultado con este código.

    Código PHP:
    #include <amxmodx>
    #include <engine>
    #include <xs>

    new g_iIdFloat:g_fPos;

    public 
    plugin_init() {
        
    register_plugin("Velocity test""0.1""Mia");
        
    register_clcmd("say fly""clcmd_fly");
    }

    public 
    clcmd_fly(iId) {
        
    // Usaremos una velocidad orientada hacia el eje Z positivo.
        
    new Float:fVelocity[3] = { 0.00.0500.0 }; // Velocidad de 500 hacia arriba
        
    entity_set_vector(iIdEV_VEC_velocityfVelocity);
        
        new 
    Float:fOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOrigin);
        
        
    g_iId iId;
        
    g_fPos fOrigin[2];


    public 
    server_frame() {
        if (
    g_iId == 0)
            return;
        
        static 
    Float:fDistFloat:fMaxDist 0.0;
        static 
    Float:fOrigin[3];
        
    entity_get_vector(g_iIdEV_VEC_originfOrigin);
        
    fDist fOrigin[2] - g_fPos;
        
        if (
    fDist fMaxDist)
            
    fMaxDist fDist;
        
        if (
    entity_get_int(g_iIdEV_INT_flags) & FL_ONGROUND) {
            
    g_iId 0;
            
    client_print(g_iIdprint_chat"Distancia maxima: %.2f"fMaxDist);
            
    fMaxDist 0.0;
        }


    Y poco a poco, esto pasó de ser un tutorial de vectores a un texto de mecánica clásica Excitedeyes

    Consideremos otro caso: Tenemos una altura. Queremos dar un impulso a un jugador para que salte lo suficiente para que alcance la altura que queremos. ¿Cómo calculamos la velocidad del impulso?
    Necesitaremos la ayuda de otra ecuación.

    Cita:Para un lanzamiento vertical, la velocidad inicial necesaria para alcanzar una altura h se calcula con la ecuación:

    [Imagen: attachment.php?aid=7635]

    Donde:
    • Vo es la velocidad inicial que buscamos.
    • g es la gravedad (valor de sv_gravity multiplicado por EV_FL_gravity). Normalmente es 800.0.
    • h es la altura deseada.

    Nota: Esta ecuación sólo sirve para un lanzamiento hacia arriba.

    Aplicación: Hagamos un salto que permita a un jugador llegar a las cajas grandes en de_dust2.

    Primero, debemos saber qué altura tienen las cajas. Utilizando un sencillo plug-in, puedo obtener la posición al pie de la caja, y la posición encima de la caja. La diferencia de ambas (en el eje Z) me dará la altura de la caja.

    [Imagen: attachment.php?aid=7623]

    Código PHP:
    #include <amxmodx>
    #include <engine>

    public plugin_init() {
        
    register_plugin("Origin test""0.1""Mia");
        
    register_clcmd("say origin""clcmd_origin");
    }

    public 
    clcmd_origin(iId) {
        new 
    Float:fOrigin[3];
        
    entity_get_vector(iIdEV_VEC_originfOrigin);
        
    client_print(iIdprint_chat"Altura actual: %.2f"fOrigin[2]);


    Diferencia de alturas: 292.03 - 164.03 = 128.0

    Cita:[Imagen: attachment.php?aid=7636]

    Modificando nuestro plug-in del impulso vertical, y colocando una velocidad en Z de 452.6, ¡tendremos un salto perfecto para posicionarnos sobre las cajas en de_dust2!

    Cita:Mía, ¿por qué redondeas la velocidad a 452.6?

    Porque es posible que, por la poca precisión del motor de Half-Life, si utilizamos la velocidad exacta no logremos llegar a la caja. Incrementando unas centésimas me aseguro de que lleguemos.
  • Lanzamiento parabólico de entidades
    El movimiento parabólico es la combinación de un movimiento de velocidad constante (horizontal) y un lanzamiento (vertical).

    [Imagen: attachment.php?aid=7627]

    En la imagen se muestra un lanzamiento de un objeto con una velocidad "inclinada", que tiene componentes horizontales (X e Y) y vertical (Z). En el aire, el objeto no se frena (como sí lo haría en el piso por la fricción). Entonces, el movimiento horizontal se mantiene mientras el objeto permanece en el aire. El movimiento vertical es afectado por la gravedad, ¡y se comporta igual que el lanzamiento vertical!

    [Imagen: attachment.php?aid=7642]

    Cita:El movimiento horizontal se describe con una sencilla ecuación:

    [Imagen: attachment.php?aid=7637]

    Donde:
    • d es la distancia.
    • V es la velocidad.
    • t es el tiempo, en segundos.

    Esta movimiento lleva el nombre de movimiento rectilíneo uniforme (MRU). La ecuación también se puede utilizar en su forma vectorial:

    [Imagen: attachment.php?aid=7638]

    Donde:
    • s es el vector del recorrido.
    • V es el vector velocidad.
    • t es el tiempo, en segundos.

    ¿Y la ecuación de la velocidad horizontal? ¡No requiere de ninguna ecuación! Es siempre igual en este tipo de movimiento.

    Aplicación: Hagamos un lanzamiento, que nos lleve desde la base CT hasta la ventana de B en de_dust2.

    El primer paso es obtener las posiciones: el punto de lanzamiento y el destino.

    [Imagen: attachment.php?aid=7624]

    Código PHP:
    new Float:fOrigin1[3] = { -240.02287.96, -91.96 // Posicion inicial
    new Float:fOrigin2[3] = { -1171.392587.98169.32 // Posicion final 

    Restando estos vectores tendremos el vector del recorrido desde el punto de inicio hasta el final.

    Código PHP:
    new Float:fDistVec[3];
    xs_vec_sub(fOrigin2fOrigin1fDistVec); // (-931.39, 300.02, 260.92) 

    [Imagen: attachment.php?aid=7625]

    Podemos separar este vector en sus componentes horizontales (X e Y) y la componente vertical (Z).

    Código PHP:
    new Float:fHDist[3], Float:fVDist[3];
    xs_vec_set(fHDistfDistVec[0], fDistVec[1], 0.0); // (-931.39, 300.02, 0.0)
    xs_vec_set(fVDist0.00.0fDistVec[2]); // (0.0, 0.0, 260.92) 

    Conocemos la altura que debemos elevar al jugador. Podemos usarla en la ecuación de velocidad inicial para lanzamiento vertical hacia arriba:

    Código PHP:
    // Vo = raiz(2*g*h)
    new Float:fVo_Z floatsqroot(2.0*800.0*fVDist[2]); 

    Con la velocidad inicial en vertical, podemos calcular el tiempo que tarda el jugador en elevarse al punto más alto

    Código PHP:
    // V = Vo - g*t , V = 0
    // Vo = g*t -> t = Vo/g
    new Float:fTime fVo_Z 800.0

    Ya tenemos el tiempo que tarda el jugador en elevarse. En el movimiento horizontal, el jugador debe de moverse hasta la posición deseada en el mismo tiempo. Utilicemos este tiempo en la ecuación vectorial del MRU para obtener la velocidad horizontal (ejes X, Y):

    Código PHP:
    // S = V*t -> V = S / t
    new Float:fVelocity[3];
    // xs_vec_div_scalar funciona igual que xs_vec_mul_scalar, pero divide en lugar de multiplicar
    xs_vec_div_scalar(fHDistfTimefVelocity); 

    Completemos la componente vertical (Z) a la velocidad:

    Código PHP:
    fVelocity[2] = fVo_Z;

    // Finalmente, establecer la velocidad al jugador
    entity_set_vector(iIdEV_VEC_velocityfVelocity); 

    [Imagen: attachment.php?aid=7628]

    Código completo:

    Código PHP:
    #include <amxmodx>
    #include <engine>
    #include <xs>

    public plugin_init() {
        
    register_plugin("Fly test""0.1""Mia");
        
    register_clcmd("say fly""clcmd_fly");
    }

    public 
    clcmd_fly(iId) {
        new 
    Float:fOrigin1[3] = { -240.02287.96, -91.96 // Posicion inicial
        
    new Float:fOrigin2[3] = { -1171.392587.98169.32 // Posicion final

        // Restando estos vectores tendremos el vector del recorrido desde el punto de inicio hasta el final.
        
    new Float:fDistVec[3];
        
    xs_vec_sub(fOrigin2fOrigin1fDistVec); // (-931.39, 300.02, 260.92)

        // Podemos separar este vector en sus componentes horizontales (X e Y) y la componente vertical (Z).
        
    new Float:fHDist[3], Float:fVDist[3];
        
    xs_vec_set(fHDistfDistVec[0], fDistVec[1], 0.0); // (-931.39, 300.02, 0.0)
        
    xs_vec_set(fVDist0.00.0fDistVec[2]); // (0.0, 0.0, 260.92)

        // Conocemos la altura que debemos elevar al jugador. Podemos usarla en la ecuación de velocidad inicial para lanzamiento vertical hacia arriba:
        // Vo = raiz(2*g*h)
        
    new Float:fVo_Z floatsqroot(2.0 800.0 fVDist[2]);

        
    // Con la velocidad inicial, podemos calcular el tiempo que tarda el jugador en elevarse al punto más alto
        // V = Vo - g*t , V = 0
        // Vo = g*t -> t = Vo/g
        
    new Float:fTime fVo_Z 800.0;

        
    // Ya tenemos el tiempo. Ahora usemos la ecuación vectorial del MRU:
        // S = V*t -> V = S / t
        
    new Float:fVelocity[3];
        
    xs_vec_div_scalar(fHDistfTimefVelocity);

        
    // Completemos la componente vertical (Z) a la velocidad:
        
    fVelocity[2] = fVo_Z;

        
    // Finalmente, establecer la velocidad al jugador
        
    entity_set_vector(iIdEV_VEC_velocityfVelocity);


    Cita:Mía, ¿existen otros tipos de movimientos de traslación en Half-Life?

    ¡Claro que sí! Por ejemplo: el movimiento afectado por la fricción, el tiro parabólico que cae por debajo del punto de inicio, los rebotes por colisión. Aunque estoy enamorada de la física, y me encanta explicarla; no puedo cubrir todos los movimientos posibles en mi tutorial. Puedes consultarme todas tus dudas en este tema. ¡Te responderé con mucho gusto!
El tamaño de una entidad
  • Posición relativa a la entidad
    Veamos el concepto de posición relativa.

    [Imagen: attachment.php?aid=7639]

    La posición del punto B es (-3, 3), respecto al origen de coordenadas. A esto se le denomina posición absoluta. ¿Pero qué sucede si nos situamos en el punto A? Una persona situada en ese punto, podría decir, el punto B se ubica a (-2, 6). Esto es la posición relativa de B respecto a A.

    Cita:Para calcular la posición relativa, basta con restar las posiciones absolutas.

    [Imagen: attachment.php?aid=7644]

    En esta ecuación, r representa una posición. La posición de B relativa a A es igual a la posición absoluta de B menos la posición absoluta de A.

    En Half-Life y sus mods, consideramos la posición absoluta a las posiciones con respecto al origen del mapa. Ahora veremos que es de utilidad considerar posiciones relativas con respecto a una entidad. Un vector de posición representa un punto en el espacio. ¡Pero las entidades pueden ser objetos mucho más grandes que un punto! Por ejemplo, en un jugador, la posición representa un punto en el medio. Los pies se encuentran a (0, 0, -36) de la posición del jugador, y la punta de la cabeza a (0, 0, 36).
  • Vectores mins y maxs
    Son los vectores que nos indican el tamaño de una entidad sólida. Se usa para las colisiones (touch).
    Para una entidad, consideremos que el origen de coordenadas se ubica en la posición de la entidad (origin).

    [Imagen: attachment.php?aid=7653]

    Luego, ubiquemos los puntos mins y maxs en este sistema. Para este ejemplo, consideramos:

    Código PHP:
    static const Float:fMins[3] = { -2.0, -2.0, -2.0 };
    static const 
    Float:fMaxs[3] = { 2.02.02.0 }; 

    [Imagen: attachment.php?aid=7654]

    Tomando la punta del vector mins, como la esquina inferior izquierda de una caja, y la punta del vector maxs como la punta superior derecha de esta; tenemos un sólido que representa nuestra entidad en el espacio.

    [Imagen: attachment.php?aid=7655]

    Aplicación: ¿Cuál es el tamaño de la entidad? Si tiene los vectores:

    Código PHP:
    static const Float:fMins[3] = { -20, -20};
    static const 
    Float:fMaxs[3] = { 202040 }; 

    Es una caja, cuya altura empieza desde el origen, de tamaño 40x40x40.

    Código PHP:
    // Para calcular el tamaño, basta con restar los vectores maxs y mins
    static Float:fSize[3];
    xs_vec_sub(fMaxsfMinsfSize);

    // Dimensiones: 40.0 (X), 40.0 (Y), 40.0 (Z).
    client_print(0print_chat"Dimensiones: %.1f (X), %.1f (Y), %.1f (Z)."fSize[0], fSize[1], fSize[2]); 

    Cita:Mía, ¿Qué diferencia hay entre poner mins negativos y no hacerlo?

    Los vectores de tamaño nos indican no solo las dimensiones de la entidad; también la ubicación del sólido respecto a la posición.
    Si tenemos una entidad con fMins[2] negativo, entonces significa que parte del sólido está por debajo de su "origin". Entonces, si ubicamos la entidad en el piso, ¡estará atorada con una parte bajo el suelo! Si fMins[2] es positivo, entonces al soltar la entidad en el piso, esta caerá, porque el sólido está por encima de la posición.

    Usualmente, el tamaño depende del model que usemos.

Pronto (algún día) la parte 3 Excitedeyes
buentuto.
Me aclaraste muchas dudas, gracias por la tutorial  Rainbow