本来这个文章周日准备更新的,但是有点别的事,就推迟了一天~

给文章下方增加了打赏功能,大家要是喜欢我们的文章的话可以打赏我们,也可以通过微信公众号或者本站留言,给我们建议和意见!3Q

通过上两次的文章,我们已经能把游戏框框和蛇给画出来了,现在我们要做的就是,移动我们的蛇,老规矩,我们先来整理下思路,我们蛇是存在链表里面的,链表的每个节点就是蛇的身体的每一节,所谓移动,就是算一下蛇头的位置,然后蛇身体往上一节点移动,第一节身体的值等于蛇头,第二节身体的值等于第一节身体…………然后再重绘下,看起来就像是再移动了,然后我们需要的是,没过一段时间,就要让蛇移动一下,这样就看起来像是在动,然后,键盘按W S A D分别上 下 左 右 移动,

我们来看下面这张图, 左边是我们认为的蛇头,右边是蛇尾,我们定义两个指针,分别指向蛇的最后一个元素,和倒数第二个元素,分别是BODY和HEAD,我们用Head的值来覆盖BODY的值,然后把body和head分别往前移动一个节点,之所以不从前往后覆盖是因为 当你第一个元素的值覆盖了第二个元素,再用第二个元素覆盖第三个元素的时候,其实是拿第一个元素的值去覆盖的~

上面两个图思路理清楚了,我们就可以开始写代码了~

我们先在头文件 定义一个枚举

enum MOVEENUM
{
	LEFT,
	RIGHT,
	UP,
	DOWN,
};

用来表示当前蛇朝那个方向移动,然后定义一个成员变量

MOVEENUM m_snakeMove;

在构造函数给他初始化成向左移动,代码如下

CSnakeDlg::CSnakeDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(IDD_SNAKE_DIALOG, pParent)
	, m_snakeMove(LEFT)//默认向左移动
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

我们只加了, m_snakeMove(LEFT)//默认向左移动这已经,好了 继续下一步

上面说了,需要蛇看起来像自己在动,那么,我们就需要一个定时器,不停的算蛇的位置,我们先定义一个定时器,加在InitGameData()里面,我们用SetTimer来设置一个定时器,这个API有3个参数

MSDN的解释如下

nIDEvent

指定一个非零计时器标识符。 如果计时器标识符是唯一的,则此相同的值将由 SetTimer 返回。 否则,
SetTimer 确定一个新的唯一值并返回它。
针对窗口计时器(具备 NULL 回调功能),该值必须仅对与当前窗口相关的窗口计时器是唯一的。 针对回调计时器,该值对于全部过程中的所有计时器必须是唯一的。 因此,创建回调计时器时,很有可能返回的值不同于您指定的值。

nElapse

指定超时值或间隔(以毫秒为单位)。

lpfnTimer

指定应用程序提供的 TimerProc 处理 WM_TIMER 消息的回调函数的地址。 如果此参数为 NULL, WM_TIMER
消息将置于应用程序的消息队列中,并由 CWnd
对象处理。

第一个参数是一个定时器ID,我们照样在头文件定义一个宏,方便以后使用和修改

#define BEGINGAME 1000 //定时器宏,控制蛇移动的

然后

SetTimer(BEGINGAME, 500, NULL);

这样调用这个API,我们希望500毫秒刷新一次,回调函数我们传的NULL,我们只要响应WM_TIME消息 就能处理定时器回调了,

我们切换到类视图,然后找到我们的对话框类,点右键->类向导

添加好WM_TIMER消息以后,我们代码里面会多一个OnTimer函数

我们把代码改成

void CSnakeDlg::OnTimer(UINT_PTR nIDEvent)
{
	if (nIDEvent == BEGINGAME)
	{
		list<SNAKE>::iterator itHead = m_listSnake.end();
		list<SNAKE>::iterator itBody = m_listSnake.end();
		itBody--;
		itHead--;

		while (itHead != m_listSnake.begin())
		{
			itHead--;
			itBody->nCol = itHead->nCol;
			itBody->nRow = itHead->nRow;
			itBody--;
		}
		itHead = m_listSnake.begin();
		switch (m_snakeMove)
		{
		case LEFT:
			itHead->nCol -= 1;
			break;
		case RIGHT:
			itHead->nCol += 1;
			break;
		case UP:
			itHead->nRow -= 1;
			break;
		case DOWN:
			itHead->nRow += 1;
			break;
		}

		Invalidate();
	}

	CDialogEx::OnTimer(nIDEvent);
}

Invalidate();是系统API 用来强制重绘当前窗口的,因为我们在OnPaint里面画的蛇,所以只有重绘,蛇就动了

上面的while循环,就是我们之前所说的从后往前遍历,赋值的循环

我们运行起来看看 蛇是不是会动了

蛇已经会动了,只会往左边走(因为我们初值是往左边走),再加个键盘控制移动,今天的任务就算完成了~

我们按刚才添加WM_TIMER消息的步骤来添加一个WM_KEYDOWN消息, 添加好了以后 会多一个void CSnakeDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数

我们在OnKeyDwon里面添加如下代码

void CSnakeDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch (nChar)
	{
	case 'W':
		if (m_snakeMove != DOWN)
		{
			m_snakeMove = UP;
		}
		break;
	case 'S':
		if (m_snakeMove != UP)
		{
			m_snakeMove = DOWN;
		}
		break;
	case 'A':
		if (m_snakeMove != RIGHT)
		{
			m_snakeMove = LEFT;
		}
		break;
	case 'D':
		if (m_snakeMove != LEFT)
		{
			m_snakeMove = RIGHT;
		}
		break;
	default:
		break;
	}

	CDialogEx::OnKeyDown(nChar, nRepCnt, nFlags);
}

nChar是表示哪个键被按下了,这里我们判断WSAD,如果按下了,W,我们就上移动,但是 上移动之前,我们要判断之前是不是下移动,之前已经在往下走,就不能往上走,要不然蛇头会画在蛇身上~

打赏