bada 2D游戏编程之六——一个基于线程的游戏循环
上篇文章中用定时器实现了一个“基于时间的固定间隔游戏循环”,这篇文章还是在上篇文章的基础上,用bada平台提供的线程机制来实现这么一个游戏循环。这中实现方式的主要特点是采用了多线程编程的机制,用单独的一个线程来实现游戏循环,在这个线程当中进行HandleEvent(),UpdateLogic()和Draw()的处理。
1, bada平台线程机制
bada平台的线程功能是由Osp::Base::Runtime::Thread类来提供的。在bada平台上存在2种类型的线程,
一种是事件驱动(Event-driven)线程,这种类型的线程运行起来之后,它的Run()函数一直运行着,直到停止线程运行才会从Run()函数返回退出,同时它在Run()函数中进行事件监听,当其它运行的线程通过调用Thread::SendUserEvent()来向它发送事件通知后,它在Thread:: OnUserEventReceivedN ()函数对接收到的事件进行处理。
还有一种是工作者(Worker)线程,这种线程需要和Osp::Base::Runtime::IRunnable结合使用,工作者线程在执行时会调用IRunnable::Run()函数,这样将需要运行的代码放到IRunnable::Run()函数中就可以了,在这里Run()函数中的代码是按照线性的方式被执行一次,执行完成后线程也就结束了。
在这里我们选择工作者线程来实现游戏循环,因为事件驱动线程与其它线程之间采用的是异步的通信方式,无法与基于定时器的实现接口保持一致;而采用工作者线程,可以在Run()函数中加入一个While()死循环,再加入线程的Sleep()功能就能够实现定时循环了,实现方式上可以和基于定时器的方式保持一致。
2, 线程的使用方法
在实现循环时用到的Osp::Base::Runtime::Thread的主要方法:
函数 | 功能描述 |
Thread(void) | 默认构造函数 |
Construct(IRunnable& targe, long stackSize, ThreadPriority priority) | 初始化函数,传入IRunnable |
Start(void) | 开始线程 |
Sleep(long milliSeconds) | 暂停线程执行 |
IRunnable提供的方法:
函数 | 功能描述 |
Run(void) | 通过被线程回调来执行函数中的代码 |
这样将需要被线程执行的代码块放到IRunable::Run()函数中,线程开始执行后就会调用IRunable::Run()方法,执行其中的代码,所以游戏循环的实现代码都是放在了IRunable::Run()函数中。
3,类图
下图是游戏循环涉及到的各类之间的关系。
4,相关的类和接口
下面对实现游戏循环的一些基本的接口和类进行介绍:
(1)TGThreadLoop类,这是游戏循环对应的一个具体的类,它继承了TGLoopBase,并使用Osp::Base::Runtime::Thread来实现循环功能。
classTGThreadLoop : publicTGLoopBase,
public Osp::Base::Runtime::IRunnable
{
public:
TGThreadLoop();
virtual ~TGThreadLoop();
result Construct(void);
public:
virtual Osp::Base::Object* Run(void);
public:
void Start();
void Pause();
void Stop();
private:
Osp::Base::Runtime::Thread* __pThread;
bool__isRunning;
bool__isPaused;
};
5,循环实现
游戏循环是在TGThreadLoop中实现的,主要在Run()函数中完成循环逻辑,其它的像Start(),Pause(),Stop()等函数对循环进行控制。
TGThreadLoop::TGThreadLoop() : __pThread(NULL),__isRunning(true),__isPaused(false)
{
}
TGThreadLoop::~TGThreadLoop()
{
if(__pThread)
{
delete__pThread;
__pThread = NULL;
}
}
result
TGThreadLoop::Construct(void)
{
result r = E_SUCCESS;
__pThread = newThread();
__pThread->Construct(*this,THREAD_PRIORITY_HIGH);
return r;
}
Osp::Base::Object*
TGThreadLoop::Run(void)
{
while(__isRunning)
{
longlong startTime = 0;
Osp::System::SystemTime::GetTicks(startTime);
if(__pStatusListener && !__isPaused)
{
__pStatusListener->HandleEvent();
__pStatusListener->UpdateLogic(__frameInterval);
__pStatusListener->Draw();
}
longlong endTime = 0;
Osp::System::SystemTime::GetTicks(endTime);
longlong deltaTime = endTime - startTime;
int leftTime = __frameInterval - deltaTime;
if(leftTime > 0)
{
__pThread->Sleep(leftTime);
}
else
{
//Do Nothing
}
}
return NULL;
}
void
TGThreadLoop::Start()
{
__isPaused = false;
__pThread->Start();
}
void
TGThreadLoop::Pause()
{
__isPaused = true;
}
void
TGThreadLoop::Stop()
{
__isRunning = false;
}
在TGThreadLoop类中通过引起了__isPaused和__isRunning这两个标志位来对线程的状态进行标识,从而实现对游戏循环的控制。
6,总结
下面前面的两种实现方式之间的类图关系,
可以看出这两种实现方式在接口上保持了完全一致,仅仅是实现的方式不一样,这样即使在开发过程中将这两种游戏循环进行相互替换也不需要改变任何接口,非常的方便。
下面是这两种实现方式之间的一个简单比较:
基于定时器的游戏循环,按照这种机制开发的游戏是运行在单线程环境之中,所有的任务都是交给程序的主线程来处理。这种方式的好处是实现简单,不需要考虑多线程编程;但也可能存在主线程处理任务过多而造成游戏阻塞的情况。
基于线程的游戏循环,采用的是多线程编程,主线程用于相应用户的输入事件,而循环线程则负责游戏的运行,可以避免主线程阻塞的情况;但是引入的这个线程一直运行着,非常的消耗系统资源,最直接的表现就是游戏比较耗电。
大家可以根据自己的需要来选择合适的实现方式。