做了很多注释,不懂的话就评论问我,会尽量回答的
这是一个控制window桌面图标,化作贪吃蛇和食物的游戏。学会了就发给女朋友玩吧。
b站视频
用桌面图标玩贪吃蛇【C++】
核心代码
1.开启独立线程, 获取桌面宽高,隐藏界面
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0,200,200, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
//ShowWindow(hWnd, nCmdShow);//不显示界面 就注释
//UpdateWindow(hWnd); //不显示界面 就注释
//随机值种子
srand(time(0));
//桌面宽高
nScreenWith = GetSystemMetrics(SM_CXSCREEN);
nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
thread gameLogicThread(OnGameLogicRun);
gameLogicThread.detach();
return TRUE;
}
二。
游戏核心逻辑
注意:每次移动和判断是否吃到食物都是以网格为基础的,宽高由nStep指定。
int nCurDir = 1; //当前方向 右1 下2 左3 上4
int nTargetDir = 1; //目标方向
int runInterval = 250; //移动间隔 毫秒
int nScore = 0; //得分
int nIconScore = 10; //图标得分
int nStep = 50; //每次移动的距离
int nFirstPosX = 10 * nStep; //蛇的初始位置x
int nFirstPosY = 10 * nStep; //蛇的初始位置y
int nFoodPosX = -1; //目标食物的位置x
int nFoodPosY = -1; //目标食物的位置y
int nScreenWith; //桌面宽
int nScreenHeight; //桌面高
enum eDir
{
DIR_RIGHT = 1,
DIR_DOWN,
DIR_LEFT,
DIR_UP,
};
//标记蛇节点的索引
int nSnakeNodeIndex = 0;
//蛇的每一个节点
class CSnakeNode
{
public:
CSnakeNode();
void SetPos(int x, int y)
{
m_posX = x;
m_posY = y;
}
void GetPos(int& x, int& y)
{
x = m_posX;
y = m_posY;
}
int m_index;
private:
int m_posX;
int m_posY;
};
CSnakeNode::CSnakeNode()
{
++nSnakeNodeIndex;
m_index = nSnakeNodeIndex;
}
//链表存整个蛇的身体
list<CSnakeNode> SnakeNodeList;
bool CreateFood(HWND handle)
{
if (SnakeNodeList.size() >= ListView_GetItemCount(handle))
{
//桌面图标被吃完了
return false;
}
//TODO 随机创建食物,窗口范围内且不能在蛇的身体上
int randNumX = rand() % 10000;
int randNumY = rand() % 10000;
randNumX = (int)((double)nScreenWith* (randNumX / 10000.0));
randNumY = (int)((double)nScreenHeight * (randNumY / 10000.0));
int gridX = ((double)randNumX / nStep); //处于第几个格子上
int gridY = ((double)randNumY / nStep); //处于第几个格子上
nFoodPosX = gridX * nStep; //格子的位置
nFoodPosY = gridY * nStep; //格子的位置
int foodIndex = SnakeNodeList.size();
::SendMessage(handle, LVM_SETITEMPOSITION, foodIndex, MAKELPARAM(nFoodPosX, nFoodPosY));
return true;
}
//更新蛇的位置
//param nFirstPosX nFirstPosY:蛇头的位置
//蛇身跟随蛇头,以此类推,后面的节点跟前面的走
void UpdateSnakePos(HWND handle,int nHeadPosX,int nHeadPosY)
{
if (SnakeNodeList.begin() == SnakeNodeList.end())
return;
int nLastPosX = 0;
int nLastPosY = 0;
CSnakeNode* head = &SnakeNodeList.front();
//记录蛇头上一个位置
head->GetPos(nLastPosX, nLastPosY);
//更新蛇头的位置
head->SetPos(nHeadPosX, nHeadPosY);
::SendMessage(handle, LVM_SETITEMPOSITION, 0, MAKELPARAM(nHeadPosX, nHeadPosY));
int index = 0;
for (auto iter = SnakeNodeList.begin(); iter != SnakeNodeList.end(); ++iter)
{
if (index == 0)
{
++index;
continue;//蛇头前面已经更新了
}
int nRecordX = 0, nRecordY = 0;
iter->GetPos(nRecordX, nRecordY);
iter->SetPos(nLastPosX, nLastPosY);
//记录节点的上一个位置
nLastPosX = nRecordX;
nLastPosY = nRecordY;
::SendMessage(handle, LVM_SETITEMPOSITION, index ++ , MAKELPARAM(nLastPosX, nLastPosY));
}
}
void OnGameLogicRun()
{
HWND hwndParent = ::FindWindow(_T("Progman"), _T("Program Manager"));
HWND hwndSHELLDLL_DefView = ::FindWindowEx(hwndParent, NULL, _T("SHELLDLL_DefView"), NULL);
HWND hwndSysListView32 = ::FindWindowEx(hwndSHELLDLL_DefView, NULL, _T("SysListView32"), _T("FolderView"));
/*某些系统可能获取不到句柄,需要用spy++再确定,有需要可以评论问我*/
//这里会将所有桌面图标放到0 0的位置,后面可以右键桌面设置对齐网格还原图标位置
int iconCount = ListView_GetItemCount(hwndSysListView32);
for (size_t i = 0; i < iconCount; i++)
{
::SendMessage(hwndSysListView32, LVM_SETITEMPOSITION, i, MAKELPARAM(-100, -100));
}
ListView_RedrawItems(hwndSysListView32, 0, iconCount - 1);
::UpdateWindow(hwndSysListView32);
//创建蛇头
CSnakeNode nSnakeHead;
nSnakeHead.SetPos(nFirstPosX, nFirstPosY);
SnakeNodeList.push_front(nSnakeHead);//蛇头加入列表
//加一个节点的蛇身
CSnakeNode nNewNode;
SnakeNodeList.push_back(nNewNode);
//创建第一个目标食物
CreateFood(hwndSysListView32);
DWORD nLastRunTick = 0;
int i = 0;
while (1)
{
//按esc退出游戏
if (GetKeyState(VK_ESCAPE) < 0)
{
DestroyWindow(hwndSysListView32);
break;
}
else if (GetKeyState(VK_RIGHT) < 0)
{
if(nCurDir != DIR_LEFT)//不能选择反方向
nTargetDir = DIR_RIGHT;
}
else if (GetKeyState(VK_DOWN) < 0)
{
if (nCurDir != DIR_UP)
nTargetDir = DIR_DOWN;
}
else if (GetKeyState(VK_LEFT) < 0)
{
if (nCurDir != DIR_RIGHT)
nTargetDir = DIR_LEFT;
}
else if (GetKeyState(VK_UP) < 0)
{
if (nCurDir != DIR_DOWN)
nTargetDir = DIR_UP;
}
if(GetTickCount() - nLastRunTick < runInterval)
continue;
nLastRunTick = GetTickCount();
//根据选择的方向,修改坐标
if (nTargetDir == DIR_RIGHT)
{
nCurDir = DIR_RIGHT;
nFirstPosX += nStep;
if (nFirstPosX > nScreenWith)
nFirstPosX = nStep;
}
else if (nTargetDir == DIR_DOWN)
{
nFirstPosY += nStep;
nCurDir = DIR_DOWN;
if (nFirstPosY > nScreenHeight)
nFirstPosY = nStep;
}
else if (nTargetDir == DIR_LEFT)
{
nFirstPosX -= nStep;
nCurDir = DIR_LEFT;
if (nFirstPosX < 0)
nFirstPosX = (int)((double)nScreenWith / nStep) * nStep;
}
else if (nTargetDir == DIR_UP)
{
nCurDir = DIR_UP;
nFirstPosY -= nStep;
if (nFirstPosY < 0)
nFirstPosY = (int)((double)nScreenHeight / nStep) * nStep;
}
UpdateSnakePos(hwndSysListView32, nFirstPosX, nFirstPosY);
if (nFoodPosX == nFirstPosX && nFoodPosY == nFirstPosY)
{
//吃的食物
CSnakeNode nNewNode;
SnakeNodeList.push_back(nNewNode);
//吃一个给一个新的
CreateFood(hwndSysListView32);
//提高游戏难度
runInterval -= 50;
if (runInterval < 200)
runInterval = 200;
}
//刷新蛇身的图标位置
ListView_RedrawItems(hwndSysListView32, 0, SnakeNodeList.size() -1 );
::UpdateWindow(hwndSysListView32);
}
}
最后记得去掉桌面的这2个勾选:自动排列图标、将图标与网格对齐。
按ESC可以退出游戏。
注意:游戏开始的时候会将所有图标挪到看不见的地方,游戏结束了再手动设置对齐网格就行了。
当然如果你的图标太少就玩不了。
收藏比赞多,证明写的东西还算有点用,顺手点个赞呗,赠人玫瑰手留余香,先行谢过!
有需要的话,下面是完整的工程压缩包,用vs打开。需要花点小钱(1.9元),毕竟要恰饭。
2020.12.23更新:重新上传了包含可以直接运行的release64位版本的可执行文件exe,需要的朋友自取。(经测试,win7,win10都可以玩)
https://download.csdn.net/download/Yang9325/13762577
(桌面图标系列之二 【俄罗斯方块】https://blog.csdn.net/Yang9325/article/details/111355836)
(桌面图标系列之三 【时钟】https://blog.csdn.net/Yang9325/article/details/112056356)