среда, 18 ноября 2009 г.

NavMesh'им ботов

Что такое Navmesh

Navmesh это замена стандартному способу поиска путей по вейпойнтам.
Плюсы новой системы:
  • Более быстрый поиск пути.
  • Более точный поиск пути.
  • Более быстрые проверки по ObstacleMesh и WalkableMesh.
  • Простота создания NavMesh с точки зрения дизайнера уровней.
Итак начнем.

 

Что нужно, чтобы NavMesh заработал?

  1. Наличие NavMesh в уровне. (см. Туториал по NavMesh для дизайнеров by redbox )
  2. Реализация передвижения по NavMesh в коде.

 

Добавляем ботам возможность искать путь по NavMesh

Итак, добавляем две функции в контроллер ботов которые определены в базовом классе GameAIController.
event bool GeneratePathToActor( Actor Goal, optional float WithinDistance, optional bool bAllowPartialPath )
{
  // Clear cache and constraints (ignore recycling for the moment)
  NavigationHandle.PathConstraintList = none;
  NavigationHandle.PathGoalList = none;

  // Create constraints
  class'NavMeshPath_Toward'.static.TowardGoal( NavigationHandle, Goal );
  class'NavMeshGoal_At'.static.AtActor( NavigationHandle, Goal );

  // Find path
  return NavigationHandle.FindPath();
}

event bool GeneratePathToLocation( Vector Goal, optional float WithinDistance, optional bool bAllowPartialPath )
{
  // Clear cache and constraints (ignore recycling for the moment)
  NavigationHandle.PathConstraintList = none;
  NavigationHandle.PathGoalList = none;

  // Create constraints
  class'NavMeshPath_Toward'.static.TowardPoint( NavigationHandle, Goal );
  class'NavMeshGoal_At'.static.AtLocation( NavigationHandle, Goal );

  // Find path
  return NavigationHandle.FindPath();
}
Поиском пути по NavMesh управляет NavigationHandle который находится в контроллере. Для того чтобы NavigationHandle мог найти путь ему нужну указать как его искать. В данном случае мы используем два ограничителя (NavMeshPath_Toward, NavMeshGoal_At). Для правильной работы NavigationHandle обязательно должен иметь ограничители типов NavMeshPathGoalEvaluator и NavMeshPathConstraint. Вы можете написать новые классы ограничителей сами, чтобы получить нестандартный поиск пути.

 

Поехали

Путь мы нашли и теперь нам нужно сказать боту, что ему пора бы пойти... по этому пути. =)
Я покажу передвижение ботов по NavMesh на примере замены стандартного состояния AIController ScriptedMove на ScriptedMove с использованием NavMesh.
var() Vector TempDest;

...

state ScriptedMove
{
  Begin:
    // while we have a valid pawn and move target, and
    // we haven't reached the target yet

    while( Pawn != None && ScriptedMoveTarget != None && !Pawn.ReachedDestination(ScriptedMoveTarget) )
    {
      if( GeneratePathToActor(ScriptedMoveTarget) )
      {
        //NavigationHandle.DrawPathCache(,TRUE);

        // check to see if it is directly reachable
        if( NavigationHandle.ActorReachable( ScriptedMoveTarget ) )
        {
          // then move directly to the actor
          MoveToward( ScriptedMoveTarget, ScriptedFocus );
        }
        else
        {
          // move to the first node on the path
          if( NavigationHandle.GetNextMoveLocation( TempDest, Pawn.GetCollisionRadius()/**0.8f*/) )
          {
            //DrawDebugCoordinateSystem(TempDest, rot(0,0,0),25.f,TRUE);
            MoveTo( TempDest, ScriptedFocus );
          }
          else
          {
            //give up because the nav mesh did not have anything for you in the path
            `warn("NavigationHandle.GetNextMoveLocation failed to find a destination for to"@ScriptedMoveTarget);
            ScriptedMoveTarget = None;
            break;
          }
        }
      }
      else
      {
        //give up because the nav mesh failed to find a path
        `warn("FindNavMeshPath failed to find a path to"@ScriptedMoveTarget);
        ScriptedMoveTarget = None;
      } 
    }  

    // return to the previous state
    PopState();
}
Все. Изучайте и экспериментируйте.

суббота, 7 ноября 2009 г.

Камера от третьего лица в UDK

В UDK уже есть отличная камера от третьего лица. Осталось только заставить ее работать.

Сначала создадим класс своей игры. Назовем ее TPSGame.
Укажем что мы будем использовать свой PlayerController класс, а также свой Pawn класс. Дефолтный UTPawn заточен под игру от первого лица. Ставим значение bDelayedStart в false, чтобы игрок не ждал начала матча. Это нужно для однопользовательских игр.
class TPSGame extends GameInfo;

defaultproperties
{
  bDelayedStart=false;
  PlayerControllerClass=class'TPSPlayerController'
  DefaultPawnClass=class'TPSPawn'
}
Внимание! после того как мы отнаследовались от GameInfo карты от UT3 скорее всего с нашей игрой работать не будут. Можете попробовать отнаследоваться от UTDeathmatch или UTGame.

Далее нужно изменить настройки в DefaultGame.ini.
[Engine.GameInfo]
DefaultGame=TPSGame
DefaultServerGame=TPSGame
PlayerControllerClassName=TPSPlayerController
Листинг файла TPSPawn.uc. Тут мы используем стандартные ассеты доступные в UDK для того, чтобы получить 3D модель робота.
class TPSPawn extends GamePawn
  config(Game);

simulated function name GetDefaultCameraMode( PlayerController RequestedBy )
{
  return 'ThirdPerson';
}

defaultproperties
{
  Components.Remove(Sprite)
  Begin Object Class=DynamicLightEnvironmentComponent Name=MyLightEnvironment
    ModShadowFadeoutTime=0.25
    MinTimeBetweenFullUpdates=0.25
    AmbientGlow=(R=.01,G=.01,B=.03,A=1)
    AmbientShadowColor=(R=0.10,G=0.10,B=0.10)
    LightShadowMode=LightShadow_ModulateBetter
    ShadowFilterQuality=SFQ_High
    bSynthesizeSHLight=TRUE
  End Object
  Components.Add(MyLightEnvironment)

  Begin Object Class=SkeletalMeshComponent Name=InitialSkeletalMesh
    CastShadow=true
    bCastDynamicShadow=true
    bOwnerNoSee=false
    LightEnvironment=MyLightEnvironment;
    BlockRigidBody=true;
    CollideActors=true;
    BlockZeroExtent=true;
    PhysicsAsset=PhysicsAsset'CH_AnimCorrupt.Mesh.SK_CH_Corrupt_Male_Physics'
    AnimSets(0)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_AimOffset'
    AnimSets(1)=AnimSet'CH_AnimHuman.Anims.K_AnimHuman_BaseMale'
    AnimTreeTemplate=AnimTree'CH_AnimHuman_Tree.AT_CH_Human'
    SkeletalMesh=SkeletalMesh'CH_LIAM_Cathode.Mesh.SK_CH_LIAM_Cathode'
  End Object

  Mesh=InitialSkeletalMesh;
  Components.Add(InitialSkeletalMesh);
}
Наконец файл TPSPlayerController.uc в котором мы указываем какой класс камеры использовать для камеры игрока.
class TPSPlayerController extends GamePlayerController
  config(Game);

defaultproperties
{
  CameraClass=class'TPSPlayerCamera'
}
Теперь собственно класс игровой камеры TPSPlayerCamera.uc. Метод FindBestCameraType возвращает камеру которую мы хотим использовать.
class TPSPlayerCamera extends GamePlayerCamera;

protected function GameCameraBase FindBestCameraType(Actor CameraTarget)
{
  return ThirdPersonCam;
}

defaultproperties
{
  ThirdPersonCameraClass=class'TPSThirdPersonCamera'
}
Листинг TPSThirdPersonCamera.uc. Очень веселый листинг. =)
class TPSThirdPersonCamera extends GameThirdPersonCamera;

defaultproperties
{
  ThirdPersonCamDefaultClass=class'TPSThirdPersonCameraMode_Default'
}
Ну и наконец последний файл TPSThirdPersonCameraMode_Default.uc.
class TPSThirdPersonCameraMode_Default extends GameThirdPersonCameraMode_Default;

var float FOV;

function float GetDesiredFOV( Pawn ViewedPawn )
{
  return FOV;
}

defaultproperties
{
  FOV=80.f;
  PawnRelativeOffset=(x=30,y=10,z=-40)
  BlendTime=0.3
}
Компилим, запускаем и радуемся.

Итак вкратце:
  • PlayerCamera это всего лишь обертка содержащая в себе все возможные типы камер которые у вас будут использоваться. В методе FindBestCameraType вы сможете выбирать тип камеры который нужно использовать в данный момент.
  • GameCameraBase (в нашем случае GameThirdPersonCamera) это и есть класс описывающий функциональность камеры. Каждый тип камеры может иметь несколько режимов.
  • GameThirdPersonCameraMode (в нашем случае GameThirdPersonCameraMode_Default) класс описывающий режим камеры.