可可西

UE3中的时间

为了管理时间,Unreal将游戏运行时间片分隔为"Ticks"。一个Tick是关卡中所有Actors更新的最小时间单位。一个tick一般是10ms-100ms(CPU性能越好,游戏逻辑越简单,tick的时间越短)

UE3使用的是游戏循环模型为:可变FPS决定游戏速度  详见:游戏主循环(Game Loop)

注1:能够tick的对象类型有 -- 从FTickableObject派生的Object、Actor、UActorComponent等

注2:当前Tick使用的DeltaTime为上一个Tick执行花费的逻辑时间

注3:DeltaTime=GDeltaTime*Info->TimeDilation(在UWorld::Tick中进行计算,见下面游戏循环示意代码)

         可通过slomo 5命令将WorldInfo的TimeDilation变量设置成5,那么游戏将以5倍的速率运行逻辑

注4:TimeSeconds为游戏逻辑世界的运行时间,RealTimeSeconds为真实世界的运行时间,也在UWorld::Tick中进行计算

         每次LoadMap加载地图关卡时,会在UWorld::BeginPlay中将TimeSeconds、RealTimeSeconds重置为0

注5:绝大部分函数是同步的,不会跨越tick来执行;UE3提供了"latent functions(延迟函数)",在State code中需要跨越多个tick才能完成

         这种类似于协程的机制,非常适合开发者使用线性代码来完成需要异步的逻辑

注6:UE3中使用appSeconds()【通过调用QueryPerformanceFrequency/QueryPerformanceCounter实现】获取当前系统时间

         CPU上也有一个计数器,以机器的clock为单位,可以通过rdtsc读取,而不用中断,精度为微妙级

常见的laten函数:

// Actor类
final latent function Sleep( float Seconds );
final latent function FinishAnim( AnimNodeSequence SeqNode );
// Controller类
final latent function MoveTo(vector NewDestination, optional Actor ViewFocus, optional float DestinationOffset, optional bool bShouldWalk = (Pawn != None) ? Pawn.bIsWalking : false);
final latent function MoveToDirectNonPathPos(vector NewDestination, optional Actor ViewFocus, optional float DestinationOffset, optional bool bShouldWalk = (Pawn != None) ? Pawn.bIsWalking : false);
final latent function MoveToward(Actor NewTarget, optional Actor ViewFocus, optional float DestinationOffset, optional bool bUseStrafing, optional bool bShouldWalk = (Pawn != None) ? Pawn.bIsWalking : false);
final latent function FinishRotation();
final latent function WaitForLanding(optional float waitDuration);
// UDKBot类
final latent function WaitToSeeEnemy(); 
final latent function LatentWhatToDoNext();

 

UE3游戏循环示意代码:

while( !GIsRequestingExit )
{
  EngineTick()
  {
    GEngineLoop.Tick();
    {
       void FEngineLoop::Tick()
       {
          // 计算GDeltaTime,对循环时间进行控制
          appUpdateTimeAndHandleMaxTickRate();
          {
            static DOUBLE LastTime = appSeconds() - 0.0001;
            
            GLastTime = GCurrentTime;
            
            if( GIsBenchmarking || GUseFixedTimeStep ) // UDK.exe CAT2-City_20_Main.udk  -benmark -dumpmovie
            {
                GDeltaTime        = GFixedDeltaTime;
                LastTime        = GCurrentTime;
                GCurrentTime    += GDeltaTime;
            }
            else
            {
                GCurrentTime = appSeconds();
                
                FLOAT DeltaTime = GCurrentTime - LastTime;
                const FLOAT MaxTickRate    = GEngine->GetMaxTickRate( DeltaTime ); // 获取配置的限制帧数
                FLOAT WaitTime        = 0;
                // Convert from max FPS to wait time.
                if( MaxTickRate > 0 )
                {
                    WaitTime = Max( 1.f / MaxTickRate - DeltaTime, 0.f );
                }

                if( WaitTime > 0 )
                {
                    // Give up timeslice for remainder of wait time.
                    const DOUBLE WaitStartTime = GCurrentTime;
                    while( GCurrentTime - WaitStartTime < WaitTime )
                    {
                        GCurrentTime = appSeconds();
                        appSleep( 0 );
                    }
                }
                
                GDeltaTime        = GCurrentTime - LastTime;
                LastTime        = GCurrentTime;
            }
          }
          GEngine->Tick( GDeltaTime );
          {
            Client->Tick( DeltaSeconds );
            {
              void UWindowsClient::Tick( FLOAT DeltaTime )
              {
                 ProcessDeferredMessages(); //处理Windows延迟消息
                 // 更新所有的viewports
                 for( … ) Viewports(ViewportIndex)->Tick(DeltaTime);
                 //使用dxInput8读取玩家鼠标键盘输入缓冲区到UInput成员变量中,
                 //通过Binds配置信息查找对应的响应函数并执行
                 ProcessInput( DeltaTime );
              }
            }

            GWorld->Tick( LEVELTICK_All, DeltaSeconds );
            {
              AWorldInfo* Info = GetWorldInfo();
              
              InTick=1; // 是否在Tick过程中
              
              // 网络复制(变量同步,远程函数调用)
              NetDriver->TickDispatch( DeltaSeconds );          
                 
              // Update time.
              Info->RealTimeSeconds += DeltaSeconds;

              // Audio always plays at real-time regardless of time dilation, but only when NOT paused
              if( !IsPaused() )
              {
                  Info->AudioTimeSeconds += DeltaSeconds;
              }
              
              // apply time multipliers
              DeltaSeconds *= Info->TimeDilation;
              // Clamp time between 2000 fps and 2.5 fps.
              DeltaSeconds = Clamp(DeltaSeconds,0.0005f,0.40f);
              Info->DeltaSeconds = DeltaSeconds;

              if (!IsPaused())
              {
                  Info->TimeSeconds += DeltaSeconds;
              }

              // 执行Kismet逻辑
              for( … ) CurrentLevel->GameSequences(SeqIdx)->UpdateOp( DeltaSeconds );
              
              // Tick Actors(Actor.TickGroup = TG_PreAsyncWork)即:物理仿真前类型Actor
              TickGroup = TG_PreAsyncWork;
              // 这一步APlayerController::Tick会被执行,然后会调用其成员变量数组Interactions(InteractionIndex)->Tick(DeltaSeconds)
              // 从而会调用UInput::Tick,当DeltaSeconds!=-1.0f时,该函数会处理按下ASDW持续移动逻辑;当DeltaSeconds==-1.0f时,
              // 该函数会首次将镜头参数绑定到AxisArray数组中,方便后续每帧清理镜头参数(将AxisArray 各元素置0即可)
              TickActors<FDeferredTickList::FGlobalActorIterator>(this,DeltaSeconds,TickType,GDeferredList);
              {
                World->NewlySpawned.Reset();
                for (ITER It(DeferredList); It; ++It)
                {
                    AActor* Actor = *It;
                    Actor->Tick(DeltaSeconds*Actor->CustomTimeDilation,TickType);
                    {
                      UBOOL AActor::Tick( FLOAT DeltaSeconds, ELevelTick TickType )
                      {
                         // 调用uc脚本中Tick方法
                         eventTick(DeltaSeconds);
                         // 执行state code
                         ProcessState( DeltaSeconds );
                         // 执行Timers
                         UpdateTimers( DeltaSeconds );
                      }
                    }
                    // 更新Actor的组件(骨骼、粒子特效、光照等)
                    TickActorComponents(Actor,DeltaSeconds,TickType,&DeferredList);
                }
                // If an actor was spawned during the async work, tick it in the post
                // async work, so that it doesn't try to interact with the async threads
                if (World->TickGroup == TG_DuringAsyncWork)
                {
                    DeferNewlySpawned(World,DeferredList);
                }
                else
                {
                    TickNewlySpawned(World,DeltaSeconds,TickType);
                }
              }
              TickAsyncWork(Info->bPlayersOnly == FALSE ? DeltaSeconds : 0.f); // 同步数据到物理仿真线程
              // Tick Actors(Actor.TickGroup = TG_DuringAsyncWork)即:物理仿真过程中类型Actor
              TickGroup = TG_DuringAsyncWork;
              TickActors<FDeferredTickList::FActorDuringAsyncWorkIterator>(this,DeltaSeconds,TickType,GDeferredList);
              {
                ... ...
              }
              TickDeferredComponents<FDeferredTickList::FComponentDuringAsyncWorkIterator>(DeltaSeconds,GDeferredList);
              WaitForAsyncWork(); // 等待物理仿真线程结束
              // Tick Actors(Actor.TickGroup = TG_PostAsyncWork)即:物理仿真后类型Actor
              TickGroup = TG_PostAsyncWork;
              DispatchRBCollisionNotifies(RBPhysScene);
              TickActors<FDeferredTickList::FActorPostAsyncWorkIterator>(this,DeltaSeconds,TickType,GDeferredList);
              {
                ... ...
              }
              TickDeferredComponents<FDeferredTickList::FComponentPostAsyncWorkIterator>(DeltaSeconds,GDeferredList);
              
              // Tick all objects inheriting from FTickableObjects.
              for( INT i=0; i<FTickableObject::TickableObjects.Num(); i++ )
              {
                FTickableObject* TickableObject = FTickableObject::TickableObjects(i);
                if( TickableObject->IsTickable() )
                {
                    TickableObject->Tick(DeltaSeconds);
                }
              }
              
              // Update cameras last. This needs to be done before NetUpdates, and after all actors have been ticked.
              for( AController *C = this->GetFirstController(); C != NULL; C = C->NextController)
              {
                APlayerController* PC = C->GetAPlayerController();

                // if it is a player, update the camra.
                if( PC && PC->PlayerCamera )
                {
                    PC->PlayerCamera->eventUpdateCamera(DeltaSeconds);

                }
              }
              
              InTick = 0; // Tick过程结束
              
              // 执行垃圾回收(GC)
              if(…) PerformGarbageCollection(); // GC Mark Time
              else IncrementalPurgeGarbage( TRUE ); // GC Sweep Time

            }
            
            //按照UUTConsole、UGFxInteraction、UTGGameInteraction、UPlayerManagerInteraction顺序响应玩家输入
            GameViewport->Tick(DeltaSeconds);
            // 渲染上屏
            RedrawViewports();
            // 播放声音
            Client->GetAudioDevice()->Update( !GWorld->IsPaused() );
          }
          // 处理Windows消息循环
          appWinPumpMessages();
       }
    }
  }
}

 

UE3的UGameEngine::LoadMap示意代码:

UBOOL UGameEngine::LoadMap( const FURL& URL, UPendingLevel* Pending, FString& Error )
{
    // send a callback message
    GCallbackEvent->Send(CALLBACK_PreLoadMap);

    // 清理地图关卡
    CleanupPackagesToFullyLoad(FULLYLOAD_Map, GWorld->PersistentLevel->GetOutermost()->GetName());
    
    // cleanup the existing per-game pacakges
    // @todo: It should be possible to not unload/load packages if we are going from/to the same gametype.
    //        would have to save the game pathname here and pass it in to SetGameInfo below
    CleanupPackagesToFullyLoad(FULLYLOAD_Game_PreLoadClass, TEXT(""));
    CleanupPackagesToFullyLoad(FULLYLOAD_Game_PostLoadClass, TEXT(""));
    CleanupPackagesToFullyLoad(FULLYLOAD_Mutator, TEXT(""));

    // 关闭网络连接,回收老的游戏世界GWorld
    if( GWorld )
    {
        // close client connections
        {
            UNetDriver* NetDriver = GWorld->GetNetDriver();
            if (NetDriver != NULL && NetDriver->ServerConnection == NULL)
            {
                for (INT i = NetDriver->ClientConnections.Num() - 1; i >= 0; i--)
                {
                    if (NetDriver->ClientConnections(i)->Actor != NULL && NetDriver->ClientConnections(i)->Actor->Pawn != NULL)
                    {
                        GWorld->DestroyActor(NetDriver->ClientConnections(i)->Actor->Pawn, TRUE);
                    }
                    NetDriver->ClientConnections(i)->CleanUp();
                }
            }
        }

        // Clean up game state.
        GWorld->SetNetDriver(NULL);
        GWorld->FlushLevelStreaming( NULL, TRUE );
        GWorld->TermWorldRBPhys();
        GWorld->CleanupWorld();
        
        // send a message that all levels are going away (NULL means every sublevel is being removed
        // without a call to RemoveFromWorld for each)
        GCallbackEvent->Send(CALLBACK_LevelRemovedFromWorld, (UObject*)NULL);

        // Disassociate the players from their PlayerControllers.
        for(FLocalPlayerIterator It(this);It;++It)
        {
            if(It->Actor)
            {
                if(It->Actor->Pawn)
                {
                    GWorld->DestroyActor(It->Actor->Pawn, TRUE);
                }
                GWorld->DestroyActor(It->Actor, TRUE);
                It->Actor = NULL;
            }
        }

        GWorld->RemoveFromRoot();
        GWorld = NULL;
    }

    // Clean up the previous level out of memory.
    UObject::CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS, TRUE );

    // 加载新的地图
    UPackage* WorldPackage = LoadPackage(MapOuter, *URL.Map, LOAD_None);

    // 创建和初始化新的游戏世界GWorld
    GWorld = FindObjectChecked<UWorld>( WorldPackage, TEXT("TheWorld") );
    GWorld->AddToRoot();
    GWorld->Init();


    // 将GPendingLevel的网络连接移交给新的GWorld
    if( Pending )
    {
        check(Pending==GPendingLevel);

        // Hook network driver up to level.
        GWorld->SetNetDriver(Pending->NetDriver);
        if( GWorld->GetNetDriver() )
        {
            GWorld->GetNetDriver()->Notify = GWorld;
            UPackage::NetObjectNotifies.AddItem(GWorld->GetNetDriver());
        }

        // Setup level.
        GWorld->GetWorldInfo()->NetMode = NM_Client;
    }

    // 设置新的GWorld的GameInfo并初始化游戏  MaxPlayers,TimeLimit等重要参数在该函数中被赋值
    GWorld->SetGameInfo(URL);
    {
      AWorldInfo* Info = GetWorldInfo();

      if( IsServer() && !Info->Game )
      {
        Info->Game = (AGameInfo*)SpawnActor( GameClass );
      }
    }

    // Initialize gameplay for the level.
    GWorld->BeginPlay(URL);
    {
      AWorldInfo* Info = GetWorldInfo();

      // 重置TimeSeconds、RealTimeSeconds和AudioTimeSeconds为0
      if (bResetTime)
      {
        GetWorldInfo()->TimeSeconds = 0.0f;
        GetWorldInfo()->RealTimeSeconds = 0.0f;
        GetWorldInfo()->AudioTimeSeconds = 0.0f;
      }
      
      // Init level gameplay info.
      if( !HasBegunPlay() )
      {
        // Enable actor script calls.
        Info->bBegunPlay    = 1;
        Info->bStartup        = 1;

        // 调用GameInfo中的InitGame函数
        if (Info->Game != NULL && !Info->Game->bScriptInitialized)
        {
            Info->Game->eventInitGame( Options, Error );
        }
      }
    }

    // 创建LocalPlayer的本地Controller
    // 单机模式下使用该创建的Controller(GWorld->IsServer()为true)
    // 联网模式下Loading地图时使用该创建的Controller,但当玩家成功加入游戏后
    // 客户端从服务器同步自动创建新的Controller(UActorChannel::ReceivedBunch函数中),
    // 然后调用UNetConnection::HandleClientPlayer销毁此处为LocalPlayer创建的Controller,并将LocalPlayer设置给新的Controller
    for(FLocalPlayerIterator It(this);It;++It)
    {
        It->SpawnPlayActor(URL.String(1),Error2);
    }

    // send a callback message
    GCallbackEvent->Send(CALLBACK_PostLoadMap);

    return TRUE;
}

 

更多请参加:

udn游戏流程  中文   en

udn Actor tick 中文  en

udn ActorComponents 中文  en

 

posted on 2018-10-26 17:03  可可西  阅读(1036)  评论(0编辑  收藏  举报

导航