418 lines
8.3 KiB
C++
418 lines
8.3 KiB
C++
#include "..\easy2d.h"
|
|
#include "..\Win\winbase.h"
|
|
#include "..\EasyX\easyx.h"
|
|
#include <time.h>
|
|
#include <assert.h>
|
|
#include <imm.h>
|
|
#pragma comment(lib, "imm32.lib")
|
|
#include <stack>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
using namespace std::chrono;
|
|
|
|
// App 的唯一实例
|
|
static App * s_pInstance = nullptr;
|
|
// 场景栈
|
|
static std::stack<Scene*> s_SceneStack;
|
|
|
|
App::App() :
|
|
m_pCurrentScene(nullptr),
|
|
m_pNextScene(nullptr),
|
|
m_pLoadingScene(nullptr),
|
|
m_bRunning(false),
|
|
m_nWindowMode(0),
|
|
m_bSaveScene(true)
|
|
{
|
|
assert(!s_pInstance); // 不能同时存在两个 App 实例
|
|
s_pInstance = this; // 保存实例对象
|
|
}
|
|
|
|
App::~App()
|
|
{
|
|
}
|
|
|
|
App * App::get()
|
|
{
|
|
assert(s_pInstance); // 断言实例存在
|
|
return s_pInstance; // 获取 App 的唯一实例
|
|
}
|
|
|
|
int App::run()
|
|
{
|
|
// 开启批量绘图
|
|
BeginBatchDraw();
|
|
// 记录当前时间
|
|
steady_clock::time_point nLast = steady_clock::now();
|
|
// 帧间隔
|
|
LONGLONG nAnimationInterval = 17LL;
|
|
// 时间间隔
|
|
LONGLONG nInterval = 0LL;
|
|
// 挂起时长
|
|
LONGLONG nWaitMS = 0L;
|
|
|
|
// 将隐藏的窗口显示
|
|
ShowWindow(GetHWnd(), SW_NORMAL);
|
|
// 运行游戏
|
|
m_bRunning = true;
|
|
|
|
// 启动多线程
|
|
//std::thread t(std::bind(&App::_mainLoop, this));
|
|
//t.join();
|
|
|
|
// 进入主循环
|
|
while (m_bRunning)
|
|
{
|
|
// 刷新计时
|
|
::FlushSteadyClock();
|
|
// 计算时间间隔
|
|
nInterval = duration_cast<milliseconds>(GetNow() - nLast).count();
|
|
// 判断间隔时间是否足够
|
|
if (nInterval >= nAnimationInterval)
|
|
{
|
|
// 记录当前时间
|
|
nLast = GetNow();
|
|
// 刷新游戏画面
|
|
_draw();
|
|
}
|
|
else
|
|
{
|
|
// 计算挂起时长
|
|
nWaitMS = nAnimationInterval - nInterval - 1;
|
|
// 挂起线程,释放 CPU 占用
|
|
if (nWaitMS > 1LL)
|
|
{
|
|
std::this_thread::sleep_for(milliseconds(nWaitMS));
|
|
}
|
|
}
|
|
}
|
|
// 停止批量绘图
|
|
EndBatchDraw();
|
|
// 关闭窗口
|
|
close();
|
|
// 释放所有内存占用
|
|
free();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void App::_initGraph()
|
|
{
|
|
// 创建绘图环境
|
|
initgraph(m_Size.cx, m_Size.cy, m_nWindowMode);
|
|
// 隐藏当前窗口(防止在加载阶段显示黑窗口)
|
|
ShowWindow(GetHWnd(), SW_HIDE);
|
|
// 获取屏幕分辨率
|
|
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
|
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
|
// 获取窗口大小
|
|
CRect rcWindow;
|
|
GetWindowRect(GetHWnd(), &rcWindow);
|
|
// 设置窗口在屏幕居中
|
|
SetWindowPos(GetHWnd(), HWND_TOP,
|
|
(screenWidth - rcWindow.Size().cx) / 2,
|
|
(screenHeight - rcWindow.Size().cy) / 2,
|
|
rcWindow.Size().cx,
|
|
rcWindow.Size().cy,
|
|
SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOSIZE);
|
|
// 禁用输入法
|
|
ImmAssociateContext(GetHWnd(), NULL);
|
|
// 重置绘图环境
|
|
reset();
|
|
// 设置窗口标题
|
|
if (m_sTitle.empty())
|
|
{
|
|
// 保存当前标题
|
|
TCHAR title[31];
|
|
GetWindowText(GetHWnd(), title, 30);
|
|
m_sTitle = title;
|
|
if (m_sAppName.empty()) m_sAppName = title;
|
|
}
|
|
else
|
|
{
|
|
setWindowTitle(m_sTitle);
|
|
}
|
|
}
|
|
|
|
void App::_draw()
|
|
{
|
|
// 下一场景指针不为空时,切换场景
|
|
if (m_pNextScene)
|
|
{
|
|
// 进入下一场景
|
|
_enterNextScene();
|
|
}
|
|
// 断言当前场景非空
|
|
assert(m_pCurrentScene);
|
|
|
|
cleardevice(); // 清空画面
|
|
m_pCurrentScene->_onDraw(); // 绘制当前场景
|
|
FlushBatchDraw(); // 刷新画面
|
|
|
|
MouseMsg::__exec(); // 鼠标检测
|
|
KeyMsg::__exec(); // 键盘按键检测
|
|
Timer::__exec(); // 定时器执行程序
|
|
ActionManager::__exec(); // 动作管理器执行程序
|
|
FreePool::__flush(); // 刷新内存池
|
|
}
|
|
|
|
void App::_mainLoop()
|
|
{
|
|
while (true)
|
|
{
|
|
if (m_bRunning)
|
|
{
|
|
MouseMsg::__exec(); // 鼠标检测
|
|
KeyMsg::__exec(); // 键盘按键检测
|
|
Timer::__exec(); // 定时器执行程序
|
|
ActionManager::__exec(); // 动作管理器执行程序
|
|
FreePool::__flush(); // 刷新内存池
|
|
}
|
|
std::this_thread::sleep_for(milliseconds(10));
|
|
}
|
|
|
|
}
|
|
|
|
void App::createWindow(int width, int height, int mode)
|
|
{
|
|
// 保存窗口信息
|
|
m_Size.cx = width;
|
|
m_Size.cy = height;
|
|
m_nWindowMode = mode;
|
|
// 创建窗口
|
|
_initGraph();
|
|
}
|
|
|
|
void App::createWindow(CSize size, int mode)
|
|
{
|
|
createWindow(size.cx, size.cy, mode);
|
|
}
|
|
|
|
void App::createWindow(TString title, int width, int height, int mode)
|
|
{
|
|
// 保存窗口信息
|
|
m_Size.cx = width;
|
|
m_Size.cy = height;
|
|
m_nWindowMode = mode;
|
|
m_sTitle = title;
|
|
if (m_sAppName.empty()) m_sAppName = title;
|
|
// 创建窗口
|
|
_initGraph();
|
|
}
|
|
|
|
void App::createWindow(TString title, CSize size, int mode)
|
|
{
|
|
createWindow(title, size.cx, size.cy, mode);
|
|
}
|
|
|
|
void App::setWindowSize(int width, int height)
|
|
{
|
|
// 游戏正在运行时才允许修改窗口大小
|
|
assert(s_pInstance->m_bRunning);
|
|
|
|
// 获取屏幕分辨率
|
|
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
|
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
|
// 获取窗口大小(包含菜单栏)
|
|
CRect rcWindow;
|
|
GetWindowRect(GetHWnd(), &rcWindow);
|
|
// 获取客户区大小
|
|
CRect rcClient;
|
|
GetClientRect(GetHWnd(), &rcClient);
|
|
// 计算边框大小
|
|
width += (rcWindow.right - rcWindow.left) - (rcClient.right - rcClient.left);
|
|
height += (rcWindow.bottom - rcWindow.top) - (rcClient.bottom - rcClient.top);
|
|
// 销毁当前窗口
|
|
// DestroyWindow(GetHWnd());/* 无法操作多线程导致失效 */
|
|
// 修改窗口大小,并设置窗口在屏幕居中
|
|
SetWindowPos(GetHWnd(), HWND_TOP,
|
|
(screenWidth - width) / 2,
|
|
(screenHeight - height) / 2,
|
|
width,
|
|
height,
|
|
SWP_SHOWWINDOW);
|
|
// 重置窗口属性
|
|
reset();
|
|
}
|
|
|
|
void App::setWindowSize(CSize size)
|
|
{
|
|
setWindowSize(size.cx, size.cy);
|
|
}
|
|
|
|
void App::setWindowTitle(TString title)
|
|
{
|
|
// 设置窗口标题
|
|
SetWindowText(GetHWnd(), title.c_str());
|
|
// 保存当前标题,用于修改窗口大小时恢复标题
|
|
s_pInstance->m_sTitle = title;
|
|
}
|
|
|
|
TString App::getWindowTitle()
|
|
{
|
|
return s_pInstance->m_sTitle;
|
|
}
|
|
|
|
void App::close()
|
|
{
|
|
closegraph(); // 关闭绘图环境
|
|
}
|
|
|
|
void App::enterScene(Scene * scene, bool save)
|
|
{
|
|
// 保存下一场景的指针
|
|
s_pInstance->m_pNextScene = scene;
|
|
// 切换场景时,是否保存当前场景
|
|
s_pInstance->m_bSaveScene = save;
|
|
}
|
|
|
|
void App::backScene()
|
|
{
|
|
// 从栈顶取出场景指针,作为下一场景
|
|
s_pInstance->m_pNextScene = s_SceneStack.top();
|
|
// 不保存当前场景
|
|
s_pInstance->m_bSaveScene = false;
|
|
}
|
|
|
|
void App::clearScene()
|
|
{
|
|
// 清空场景栈
|
|
while (s_SceneStack.size())
|
|
{
|
|
auto temp = s_SceneStack.top();
|
|
SafeDelete(temp);
|
|
s_SceneStack.pop();
|
|
}
|
|
}
|
|
|
|
void App::setAppName(TString appname)
|
|
{
|
|
s_pInstance->m_sAppName = appname;
|
|
}
|
|
|
|
TString App::getAppName()
|
|
{
|
|
return s_pInstance->m_sAppName;
|
|
}
|
|
|
|
void App::setBkColor(COLORREF color)
|
|
{
|
|
setbkcolor(color);
|
|
}
|
|
|
|
void App::_enterNextScene()
|
|
{
|
|
bool bBackScene = false;
|
|
|
|
// 若下一场景处于栈顶,说明正在返回上一场景
|
|
if (s_SceneStack.size() && m_pNextScene == s_SceneStack.top())
|
|
{
|
|
bBackScene = true;
|
|
// 删除栈顶场景
|
|
s_SceneStack.pop();
|
|
}
|
|
|
|
// 执行当前场景的 onExit 函数
|
|
if (m_pCurrentScene)
|
|
{
|
|
m_pCurrentScene->onExit();
|
|
if (m_bSaveScene)
|
|
{
|
|
// 若要保存当前场景,把它放入栈中
|
|
s_SceneStack.push(m_pCurrentScene);
|
|
// 暂停当前场景上运行的所有定时器
|
|
Timer::waitAllSceneTimers(m_pCurrentScene);
|
|
MouseMsg::waitAllSceneListeners(m_pCurrentScene);
|
|
KeyMsg::waitAllSceneListeners(m_pCurrentScene);
|
|
ActionManager::waitAllSceneActions(m_pCurrentScene);
|
|
}
|
|
else
|
|
{
|
|
// 不保存场景时,停止当前场景上运行的所有定时器,并删除当前场景
|
|
Timer::clearAllSceneTimers(m_pCurrentScene);
|
|
MouseMsg::clearAllSceneListeners(m_pCurrentScene);
|
|
KeyMsg::clearAllSceneListeners(m_pCurrentScene);
|
|
ActionManager::stopAllSceneActions(m_pCurrentScene);
|
|
SafeDelete(m_pCurrentScene);
|
|
}
|
|
}
|
|
|
|
m_pCurrentScene = m_pNextScene; // 切换场景
|
|
m_pNextScene = nullptr; // 下一场景置空
|
|
|
|
if (bBackScene)
|
|
{
|
|
// 返回上一场景时,恢复场景上的定时器
|
|
Timer::notifyAllSceneTimers(m_pCurrentScene);
|
|
MouseMsg::notifyAllSceneListeners(m_pCurrentScene);
|
|
KeyMsg::notifyAllSceneListeners(m_pCurrentScene);
|
|
ActionManager::notifyAllSceneActions(m_pCurrentScene);
|
|
}
|
|
else
|
|
{
|
|
m_pCurrentScene->init(); // 进入一个新场景时,执行它的 init 函数
|
|
}
|
|
|
|
m_pCurrentScene->onEnter(); // 执行下一场景的 onEnter 函数
|
|
}
|
|
|
|
void App::quit()
|
|
{
|
|
s_pInstance->m_bRunning = false;
|
|
}
|
|
|
|
void App::end()
|
|
{
|
|
s_pInstance->m_bRunning = false;
|
|
}
|
|
|
|
void App::reset()
|
|
{
|
|
// 重置绘图环境
|
|
graphdefaults();
|
|
setbkmode(TRANSPARENT);
|
|
setbkcolor(Color::black);
|
|
}
|
|
|
|
Scene * App::getCurrentScene()
|
|
{
|
|
// 获取当前场景的指针
|
|
return s_pInstance->m_pCurrentScene;
|
|
}
|
|
|
|
Scene * App::getLoadingScene()
|
|
{
|
|
return s_pInstance->m_pLoadingScene;
|
|
}
|
|
|
|
int App::getWidth()
|
|
{
|
|
return s_pInstance->m_Size.cx;
|
|
}
|
|
|
|
int App::getHeight()
|
|
{
|
|
return s_pInstance->m_Size.cy;
|
|
}
|
|
|
|
void App::free()
|
|
{
|
|
// 释放场景内存
|
|
SafeDelete(m_pCurrentScene);
|
|
SafeDelete(m_pNextScene);
|
|
// 清空场景栈
|
|
while (s_SceneStack.size())
|
|
{
|
|
auto temp = s_SceneStack.top();
|
|
SafeDelete(temp);
|
|
s_SceneStack.pop();
|
|
}
|
|
// 删除所有定时器
|
|
Timer::clearAllTimers();
|
|
MouseMsg::clearAllListeners();
|
|
KeyMsg::clearAllListeners();
|
|
ActionManager::clearAllActions();
|
|
// 删除所有对象
|
|
FreePool::__clearAllObjects();
|
|
}
|