为了管理时间,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 = ;
// Convert from max FPS to wait time.
if( MaxTickRate > )
{
WaitTime = Max( .f / MaxTickRate - DeltaTime, .f );
} if( WaitTime > )
{
// Give up timeslice for remainder of wait time.
const DOUBLE WaitStartTime = GCurrentTime;
while( GCurrentTime - WaitStartTime < WaitTime )
{
GCurrentTime = appSeconds();
appSleep( );
}
} 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=; // 是否在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 : .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=; 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 = ; // 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() - ; i >= ; 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 = ;
Info->bStartup = ; // 调用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(),Error2);
} // send a callback message
GCallbackEvent->Send(CALLBACK_PostLoadMap); return TRUE;
}

更多请参加:

udn游戏流程  中文   en

udn Actor tick 中文  en

udn ActorComponents 中文  en

UE3中的时间的更多相关文章

  1. 借助JavaScript中的时间函数改变Html中Table边框的颜色

    借助JavaScript中的时间函数改变Html中Table边框的颜色 <html> <head> <meta http-equiv="Content-Type ...

  2. javaScript系列:js中获取时间new Date()详细介绍

    var myDate = new Date();myDate.getYear(); //获取当前年份(2位)myDate.getFullYear(); //获取完整的年份(4位,1970-????)m ...

  3. MVC中的时间js格式化

    记录下我遇到的一个,MVC中post请求返回一个JSON字符串,其中包含数据库中的时间格式(如:/Date(10000000000)/),不知道怎么处理.百度的方法都不适用,经自己研究,做成了一个Jq ...

  4. C程序中对时间的处理——time库函数详解

    包含文件:<sys/time.h> <time.h> 一.在C语言中有time_t, tm, timeval等几种类型的时间 1.time_t time_t实际上是长整数类型, ...

  5. [转]JDBC中日期时间的处理技巧

    Java中用类java.util.Date对日期/时间做了封装,此类提供了对年.月.日.时.分.秒.毫秒以及时区的控制方法,同时也提供一些工具方法,比如日期/时间的比较,前后判断等. java.uti ...

  6. java8 中的时间和数据的变化

    java8除了lambda表达式之外还对时间和数组这两块常用API做想应调整, Stream 有几个常用函数: store 排序 (a,b)-> a.compareTo(b)  排出来的结果是正 ...

  7. Linux中表示“时间”的结构体和相关函数

    转载于:http://blog.chinaunix.net/uid-25909722-id-2827364.html      Linux中表示“时间”的结构体和相关函数 2011-09-13 17: ...

  8. ylb:SQL Server中的时间函数

    ylbtech-SQL Server:SQL Server-SQL Server中的时间函数 SQL Server中的时间函数. 1,SQL Server中的时间函数 返回顶部 1.   当前系统日期 ...

  9. mysql数据库中查询时间

    项目中要对数据按时间处理,在数据库中,时间处理的格式如 2014-12-09 06:30:17 时间查询出来如下所示: 现在要查询具体有哪天的数据,应用substring函数,SQL如下: ) as ...

随机推荐

  1. [Swift]LeetCode263. 丑数 | Ugly Number

    Write a program to check whether a given number is an ugly number. Ugly numbers are positive numbers ...

  2. HBase之CF持久化系列(续3——完结篇)

    相信大家在看了该系列的前两篇文章就已经对其中的持久化有比较深入的了解.相对而言,本节内容只是对前两节的一个巩固.与持久化相对应的是打开文件并将其内容读入到内存变量中.而在本节,我就来介绍这一点. 本节 ...

  3. MySQL如何系统学习

    MySQL是当下互联网最流行的开源数据库.不管你使用或者学习何种编程语言,都将会使用到数据库,而MySQL则是应用最为广泛的数据库,没有之一! 之前在我的博客上也发布过一些MySQL优化配置项,都收到 ...

  4. 【Spark篇】---Spark故障解决(troubleshooting)

    一.前述 本文总结了常用的Spark的troubleshooting. 二.具体 1.shuffle file cannot find:磁盘小文件找不到. 1) connection timeout ...

  5. “吃人”的那些Java名词:对象、引用、堆、栈

    记得中学的课本上,有一篇名为<狂人日记>课文:那时候根本理解不了鲁迅写这篇文章要表达的中心思想,只觉得满篇的“吃人”令人心情压抑:老师在讲台上慷慨激昂的讲,大多数的同学同我一样,在课本面前 ...

  6. 如何写好CSS系列之表单(form)

    表单模块可以分为两部分:一是表单的布局,也就是规范表单元素单元的排列位置:二是表单元素,如:输入框.单选.复选.列表组件.搜索组件等,由于列表组件.搜索组件不是单纯的css组件,所以暂且没有实现. 一 ...

  7. Android Native crash日志分析

    在Android应用crash的类型中,native类型crash应该是比较难的一种了,因为大家接触的少,然后相对也要多转几道工序,所有大部分对这个都比较生疏.虽然相关文章也有很多了,但是我在刚开始学 ...

  8. Lucene 05 - 使用Lucene的Java API实现分页查询

    目录 1 Lucene的分页查询 2 代码示例 3 分页查询结果 1 Lucene的分页查询 搜索内容过多时, 需要考虑分页显示, 像这样: 说明: Lucene的分页查询是在内存中实现的. 2 代码 ...

  9. 版本管理工具Git(三)Gitlab高可用

    高可用模式 企业版 社区版 我们这里说一下成本比较低的主备模式,它主要依赖的是DRBD方式进行数据同步,需要2台ALL IN ONE的GitLab服务器,也就是通过上面安装方式把所有组件都安装在一起的 ...

  10. leetcode — path-sum-ii

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Source : https://o ...