zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    vue03
    vue02
    vue01
    VMware下Linux配置局域网和外网访问
    以太坊solidity编程常见错误(不定期更新)
    elasticsearch的探索之路
    一个rails项目连多个mongo数据库
    分析rails日志,计算响应率
    PostgreSQL新手上路PG::ConnectionBad (FATAL: Peer authentication failed
    cache目录没有权限
  • 原文地址:https://www.cnblogs.com/kekec/p/9835889.html
Copyright © 2011-2022 走看看