Source SDK

Source SDK

Not enough ratings
Creación de una casilla Declaración (Motor Source 2013)
By Oitnemood
   
Award
Favorite
Favorited
Unfavorite
Declaración
Las ranuras de escuadrón son útiles para asignar NPCs para realizar acciones específicas en un escuadrón.

Para dar a un PNJ la capacidad de estar en un escuadrón, debes añadir lo siguiente a la función Spawn:
CapabilitiesAdd( bits_CAP_SQUAD);
Este código asegurará que los compañeros de escuadrón no intenten disparar a través de los demás para atacar a los enemigos.
CapabilitiesAdd( bits_CAP_NO_HIT_SQUADMATES );
El primer paso para crear una ranura de escuadrón para tu NPC es crear un nombre para ella y añadirlo al enum SquadSlot_T. Aquí hay un ejemplo de nombre para un squadslot: SQUAD_SLOT_GRENADE1.

Una vez que la actividad es añadida al enum, debes usar la macro DECLARE_SQUADSLOT en la sección AI_BEGIN_CUSTOM_NPC.

Aquí hay un ejemplo del código DECLARE_SQUADSLOT:
DECLARE_SQUADSLOT( SQUAD_SLOT_EXAMPLE )
Definir el NPC
Configuración

Todas las versiones del SDK (incluyendo Alien Swarm) proporcionan un archivo de plantilla NPC en server/hl2/monster_dummy.cpp. Puedes copiar este archivo a una nueva ubicación y empezar a trabajar en él.

Cambia la ruta del modelo en Precache() y Spawn() a un archivo de modelo válido (elige algo humanoide por ahora) y podrás engendrar un NPC que se gire para enfrentarse a los sonidos que escuche cerca.

Relationships

Alien Swarm Esta sección se centra en la base de código de Alien Swarm.
Las relaciones de un NPC definen cómo reacciona a otros NPCs. Utiliza la función FindEntityRelationship(CBaseEntity *pTarget) de cada NPC para comprobar el estado de las relaciones.

Disposiciones

Hay cuatro "disposiciones" incorporadas:

D_HATE
D_FEAR
D_LIKE
D_NEUTRAL
Cada relación tiene una prioridad. La relación con mayor prioridad gana si hay un conflicto.

Objetivos
Una relación puede tener como objetivo una de estas tres cosas:

Facción
Un NPC puede pertenecer a una facción, y esa facción puede tener disposiciones hacia otras facciones. Las facciones se definen en game/shared/shareddefs.h (por defecto no hay ninguna).

AñadirRelaciónDeFacción()
Añade una relación de facción sólo para este NPC.
CBaseCombatCharacter::SetDefaultFactionRelationship()
Añade una relación de facción estática que es compartida por todos los NPCs.
CambiarFacción()
Establece la facción a la que pertenece el NPC.

Clase
Un NPC puede clasificarse a sí mismo. Esta clase en particular es el valor de retorno de la función Classify(), y no está relacionada con las clases C++ o Hammer.

AñadirRelaciónDeClases()
Añade una relación de clase sólo para este NPC.
CBaseCombatCharacter::SetDefaultRelationship()
Añade una relación de clase estática que es compartida por todos los NPCs.
Clase_T Clasificar()
Devuelve la clase de este NPC.
Para añadir una nueva clase de NPC, vaya a basentity.h, línea 95 y añada su clase.

Entidad
Por último, se pueden especificar relaciones puntuales entre dos NPCs cualesquiera.

AñadirRelaciónEntidad()
Añade una relación entre este NPC y otro.
Crear una condición
Una condición es una bandera que un NPC utiliza para registrar algo sobre el estado del mundo. Las condiciones se usan principalmente para seleccionar o interrumpir programas, y se refrescan cada vez que se ejecuta NPCThink().

Algunos ejemplos de condiciones son

"Puedo ver un enemigo"
"He recibido algo de daño"
"El cargador de mi arma está vacío"
El conjunto de condiciones compartidas por el motor suele complementarse con otras específicas de un PNJ. Por ejemplo, los hormigueros se ahogan cuando están en el agua, por lo que tienen una condición propia que les dice "estoy bajo el agua".

Consejo.png Consejo: Puedes ver una lista de todas las condiciones buscando en la 'Vista de Clase' de Visual Studio.
Nota.png Nota: Por defecto hay un máximo de 256 condiciones en cualquier juego o mod. Puedes aumentar este número en ai_condition.h, pero al hacerlo se romperán las partidas guardadas más antiguas y aumentará el uso de memoria.

Condiciones de interrupción

Además de utilizarse para seleccionar un nuevo horario, las condiciones validan el actual actuando como "interrupciones". Cada horario tiene asociada una lista de condiciones que provocan su salida si se detectan. Cuando esto ocurre, se elige un nuevo horario.

Por ejemplo, un PNJ puede estar ejecutando un horario para "Perseguir a mi enemigo". Este tipo de programa normalmente especifica la condición "He elegido un nuevo enemigo" como una interrupción, porque el PNJ no debería seguir persiguiendo al viejo enemigo si ha encontrado uno más nuevo e importante.

Añadir nuevas condiciones

Las condiciones se enumeran normalmente dentro de la clase NPC. Por ejemplo, si nuestro nuevo NPC tiene una condición personalizada para reflejar "Tengo hambre", nuestro enum de condiciones debería ser algo así

enum { COND_MYNPC_HUNGRY = BaseClass::NEXT_CONDITION, NEXT_CONDITION };
Es una buena práctica incluir el enum NEXT_CONDITION, para que los NPCs derivados de nuestro NPC puedan usar BaseClass::NEXT_CONDITION (como hacemos nosotros, en la primera línea) para declarar sus condiciones personalizadas sin causar colisiones con las nuestras.

Advertencia: Si tu condición enum no está dentro de la definición de tu clase, entonces debes elegir un nombre diferente y único para tu ítem "próxima condición".
Las condiciones deben ser declaradas dentro del bloque AI_BEGIN_CUSTOM_NPC del NPC. Esto se hace a través de la macro DECLARE_CONDITION. Para el ejemplo anterior, usaríamos esta línea:

AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom ) DECLARE_CONDITION( COND_MYNPC_HUNGRY ) AI_END_CUSTOM_NPC()

Funciones de la condición

CAI_BaseNPC almacena las condiciones como banderas en m_Conditions, pero no se debe acceder a ella directamente. En su lugar, el manejo de las condiciones se hace con estas funciones:

GatherConditions()
El punto de entrada principal para la generación de condiciones, que es llamado cada vez que el NPC piensa.
Nota: ¡Asegúrate de llamar a BaseClass::GatherConditions() al final!
SetCondition( int iCondition )
ClearCondition( int iCondition )
Establece/borra una condición. En lugar de pasar un número entero, por supuesto hará uso de su enum.
bool TieneCondición( int iCondición )
Verdadero si la condición especificada está actualmente establecida. Recuerde que las condiciones se tiran y se vuelven a generar en cada pensamiento.
BuildScheduleTestBits()
Esta función le permite modificar dinámicamente las interrupciones de un programa en cualquier momento. Es útil para añadir sus condiciones NPC personalizadas a los horarios NPC base o compartidos. Vea abajo un ejemplo.


Ejemplos

GatherConditions()

Esta es una versión ligeramente recortada de GatherConditions() del Antlion. Los hormigueros saltan mucho, y a veces aterrizan sobre otros NPCs. Necesitan saber si han aterrizado sobre un PNJ a la hora de tomar decisiones, por lo que generan una condición cuando lo hacen. Los hormigueros también necesitan ahogarse si alguna vez se encuentran en el agua, por lo que establecen una condición cuando su nivel de agua llega a la altura de la cintura.

void CNPC_Antlion::GatherConditions() { BaseClass::GatherConditions(); // Ver si he aterrizado en otro NPC después de saltar. CBaseEntity *pGroundEnt = GetGroundEntity(); if ( pGroundEnt && pGroundEnt->GetSolidFlags() & FSOLID_NOT_STANDABLE && GetFlags() & FL_ONGROUND ) SetCondition( COND_ANTLION_ON_NPC ); else ClearCondition( COND_ANTLION_ON_NPC ); // See if I've landed in water if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 ) SetCondition( COND_ANTLION_IN_WATER ); }

La declaración else utiliza ClearCondition() aquí porque si la primera declaración if es verdadera, entonces el booleano COND_ANTLION_ON_NPC se pone a True. Cuando esto sucede, otra pieza de código más adelante se dispara, diciéndole al antlion que se baje de la cabeza del NPC, y con suerte, el antlion no salta sobre otro NPC. Una vez que ya no está sobre un NPC, la condición vuelve a ser falsa, por lo que se reanuda la actividad normal.

//¿En la cabeza de otro NPC? if( HasCondition( COND_ANTLION_ON_NPC ) ) { // Estás en la cabeza de un NPC. Bájate. return SCHED_ANTLION_DISMOUNT_NPC; }

BuildScheduleTestBits()

El comportamiento de Asalto permite a los mapeadores especificar si un NPC debe desviarse de su camino para luchar contra nuevos enemigos o seguir corriendo. Este tipo de especificación de interrupción dinámica no se puede hacer en las definiciones estáticas del horario, y es exactamente para lo que está diseñado BuildScheduleTestBits().

void CAI_AssaultBehavior::BuildScheduleTestBits() { BaseClass::BuildScheduleTestBits(); // Si se nos permite desviar, añadir las interrupciones apropiadas a nuestros horarios de movimiento if ( m_hAssaultPoint && m_hAssaultPoint->m_bAllowDiversion ) { if ( IsCurSchedule( SCHED_MOVE_TO_ASSAULT_POINT ) || IsCurSchedule( SCHED_MOVE_TO_RALLY_POINT ) || IsCurSchedule( SCHED_HOLD_RALLY_POINT ) ) { GetOuter()->SetCustomInterruptCondition( COND_NEW_ENEMY ); GetOuter()->SetCustomInterruptCondition( COND_SEE_ENEMY ); } } }
Creación de un calendario "Schedule"
Un horario es una lista de tareas que debe realizar un PNJ. Sólo se elige un nuevo horario cuando no hay ninguno activo; esto puede ser porque el NPC acaba de aparecer o porque el último horario acaba de terminar, ha fallado o ha encontrado una condición de interrupción.

Crear un nuevo horario

Enumeración

Los horarios se identifican con un número. Naturalmente, se emplean enums para facilitar la lectura.
enum { SCHED_DODGE_ENEMY_FIRE = LAST_SHARED_SCHEDULE, LAST_MY_NPC_SCHEDULE, };

Los valores de "último horario" se utilizan para evitar colisiones entre enums cuando se hereda de clases existentes. LAST_SHARED_SCHEDULE es el final de la lista estándar de horarios que son compartidos por todos los NPCs; si está heredando de algo distinto a CAI_BaseNPC probablemente querrá comprobar su clase base y obtener su propio valor de "último horario".

Definición

Una vez enumerado el horario podemos empezar a definirlo dentro del bloque AI_BEGIN_CUSTOM_NPC. Todos los horarios utilizan la misma estructura:

AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom ) DEFINE_SCHEDULE ( SCHED_DODGE_ENEMY_FIRE, " Tasks" " TASK_FIND_DODGE_DIRECTION 3" " TASK_JUMP 0" "" " Interrupts" " COND_LIGHT_DAMAGE" ) AI_END_CUSTOM_NPC()

Advertencia: El primer carácter de cada cadena (excepto el "hueco") debe ser un espacio o un tabulador. De lo contrario, su programa no será válido.
La definición tiene dos partes:

Tareas
La lista de tareas secuenciales que un PNJ debe realizar para completar el horario. Ver Tareas compartidas para el conjunto básico del motor.
Nota: Cada tarea requiere un argumento numérico. Si la tarea no hace uso de uno, simplemente pasa 0.
Interrupciones
Una lista de condiciones que harán que se abandone el horario, y se elija uno nuevo, si se detecta alguna.
En el ejemplo anterior, el PNJ intentará encontrar una dirección adecuada para esquivar (comprobando en un máximo de tres direcciones, a juzgar por el parámetro) antes de utilizar la salida de esa tarea, almacenada en algún lugar de la clase, para realizar el movimiento en sí. Pero si encuentra COND_LIGHT_DAMAGE (cualquier daño mayor que cero) durante el proceso, abandonará el programa y seleccionará otro.

El código de comportamiento que decide lo que el NPC realmente hace se define en las tareas del componente.

SelectSchedule()

int SelectSchedule(void) es llamado cada vez que un NPC se encuentra sin horario, y contiene la lógica que decide cuál debe ser seleccionado para llenar el vacío. El estado actual de la NPC y las condiciones normalmente juegan un papel importante en la decisión.

Se selecciona un horario si la función devuelve su nombre - por ejemplo, devolver SCHED_DODGE_ENEMY_FIRE;.

Consejo: Si espera escribir mucha lógica de selección de horarios, puede resultarle útil dividirla en subfunciones de su propia creación. CAI_BaseNPC, por ejemplo, tiene SelectIdleSchedule(), SelectCombatSchedule(), etc. que son llamadas dependiendo del estado del NPC.

Funciones útiles
TieneCondición(int condición)
Verdadero si la condición especificada se ha establecido para el pensamiento actual. Por supuesto, ¡usaría sus nombres enumerados en lugar de pasar un entero directamente!
ObtenerEstado()
Devuelve el estado del NPC. También puedes acceder a m_NPCState directamente, pero GetState() es de sólo lectura y por lo tanto más seguro.
return ClaseBase::SeleccionarHorario()
Si no se ha elegido ningún horario propio, hay muy pocas situaciones en las que no se quiera pasar a la clase base.

TranslateSchedule()

TranslateSchedule() se llama inmediatamente después de SelectSchedule(). Está diseñada para permitir que las clases hijas sustituyan los horarios de sus padres o progenitores por los suyos propios sin tener que duplicar la lógica de selección.

int CNPC_Custom::TranslateSchedule( int scheduleType ) { switch( scheduleType ) { case SCHED_IDLE_WALK: { return SCHED_CUSTOM_IDLE_WALK; break; } } return BaseClass::TranslateSchedule( scheduleType ); }
Crear una tarea
Una Tarea es una acción que un NPC puede realizar. Las programaciones ejecutarán una lista de tareas. Mantenga las tareas tan atómicas como sea posible; no exprima dos acciones distintas en la misma.

Crear una tarea

Las tareas personalizadas se añaden declarando primero un nuevo valor de enum.

enum { TASK_JUMP = LAST_SHARED_TASK, TASK_FIND_DODGE_DIRECTION, LAST_MY_NPC_TASK, };

Después, la tarea en sí debe declararse así:

AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom ) DECLARE_TASK( TASK_FIND_DODGE_DIRECTION ) AI_END_CUSTOM_NPC()
A continuación, añada las tareas en su programa personalizado en el orden en que desea que se ejecuten. Consulte la sección Programación para obtener más detalles sobre el siguiente código:
AI_BEGIN_CUSTOM_NPC( npc_custom, CNPC_Custom ) DEFINE_SCHEDULE ( SCHED_DODGE_ENEMY_FIRE, " Tasks" " TASK_FIND_DODGE_DIRECTION 3" " TASK_JUMP 0" "" " Interrupts" " COND_LIGHT_DAMAGE" ) AI_END_CUSTOM_NPC()
Lógica de tareas Ahora que todo está configurado es finalmente el momento de escribir algo de código real de la IA. Las tareas se inician desde StartTask(Task_t *pTask), que debe tomar la forma de una sentencia switch que evalúe pTask->iTask. Nota.png Nota: Recuerda tener un caso por defecto que recaiga en BaseClass::StartTask(). Cada tarea tiene un valor flotante asociado, pTask->flTaskData, que puede utilizarse para cambiar su resultado. Cuando la tarea actual esté completa, llame a TaskComplete(). El programa pasará a la siguiente tarea. Si la tarea actual ha fallado, llame a TaskFail() con un mensaje de error. Se seleccionará un nuevo horario. Ejemplo Este código hace que el NPC juegue la actividad ACT_MP_JUMP interminablemente.
void CNewNPC::StartTask( const Task_t *pTask ) { switch (pTask->iTask) { case TASK_MYCUSTOMTASK: if (FindGestureLayer(ACT_MP_JUMP) == -1) AddGesture(ACT_MP_JUMP); TaskComplete(); break; default: BaseClass::StartTask( pTask ); } }
Crear una actividad El primer paso para crear una actividad para tu NPC es crear un nombre para ella y añadirlo al enum. Aquí hay un ejemplo de nombre para una actividad: ACT_COMBINE_BUGBAIT. Una vez añadida la actividad, debes utilizar la macro DECLARE_ACTIVITY en la sección AI_BEGIN_CUSTOM_NPC. Aquí hay un ejemplo del código DECLARE_ACTIVITY:
DECLARE_ACTIVITY( ACT_NEWNPC_ACTIVITY )
Nota: No es necesario declarar las actividades compartidas. Aplicación Las actividades se utilizan en los horarios y utilizan una de estas 2 tareas:
  • TASK_PLAY_SEQUENCE
  • TASK_SET_ACTIVITY
Ejemplos
DEFINE_SCHEDULE ( SCHED_COMBINE_BUGBAIT_DISTRACTION, " Tasks" " TASK_STOP_MOVING 0" " TASK_RESET_ACTIVITY 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_COMBINE_BUGBAIT" "" " Interrupts" "" )
DEFINE_SCHEDULE ( SCHED_COMBINE_BURNING_STAND, " Tasks" " TASK_WAIT_RANDOM 0.3" " TASK_SET_ACTIVITY ACTIVITY:ACT_COMBINE_BUGBAIT" " TASK_WAIT 2" " TASK_WAIT_RANDOM 3" " TASK_COMBINE_DIE_INSTANTLY DMG_BURN" " TASK_WAIT 1.0" "" " Interrupts" "" )
SECUENCIA_DE_REPRODUCCIÓN_DE_TAREAS Esta tarea no restablece la actividad, pero sí que tiene en cuenta las capacidades del Movimiento Automático. TASK_SET_ACTIVITY Esta tarea restablece la actividad, pero no tiene en cuenta las capacidades del Movimiento Automático.
Creación de un animevent
Declaration

Los animevents son útiles para añadir funcionalidad a mitad de la animación.

El primer paso para crear un animevent para tu NPC es crear un nombre para él y añadirlo a la sección "Private animevents". Aquí hay un ejemplo de nombre para un animevent: COMBINE_AE_ALTFIRE.

Una vez que la actividad es añadida al enum, debes usar la macro DECLARE_ANIMEVENT en la sección AI_BEGIN_CUSTOM_NPC.

Aquí hay un ejemplo del código DECLARE_ANIMEVENT:

DECLARE_ANIMEVENT( NEWNPC_AE_ANIMEVENT )

Aplicación

Todo el código de eventos se maneja en void CNPC_New::HandleAnimEvent( animevent_t *pEvent ). Si pEvent no contiene nada que deba ser capturado y tratado, se debe llamar a BaseClass::HandleAnimEvent.
Crear una interacción
Las interacciones son mensajes que se transmiten entre los NPCs, como permitir que un NPC esquive un ataque, o que vuele en una dirección específica y establezca un determinado estado al ser pateado.

Declaración
El primer paso para crear una interacción para su NPC es crear un nombre para ella y añadirla a la sección "Interacciones", así como añadirla a src\dlls\hl2_dll\AI_Interactions.h. Aquí hay un nombre de ejemplo para una actividad: g_interactionVortigauntKick.

Una vez añadida la actividad, debes utilizar la macro DECLARE_INTERACTION en la sección AI_BEGIN_CUSTOM_NPC.

Aquí hay un ejemplo del código DECLARE_INTERACTION:

DECLARE_INTERACTION( g_interactionExample )

Nota: Una interacción sólo debe declararse en la sección AI_BEGIN_CUSTOM_NPC de un NPC, independientemente de cuántos NPC la utilicen.

Implementación
Nota: Una interacción sólo debe declararse en la sección AI_BEGIN_CUSTOM_NPC de un NPC, independientemente de cuántos NPC la utilicen.