因为已经写了食物的实现,所以我不知道到底是该先写世界的实现还是蛇的实现。因为世界就是一个窗口,可以立刻在世界中看到食物的样子,对于大多数人来说,如果写完代码立刻就能看到效果,那就再好不过了。可是,我最后还是选择了先写蛇的实现这篇笔记。如果先写世界的实现,我就无法按照现在的思路完完整整的写下去,因为没有蛇,世界部分的代码就不完整,看完食物的效果后,我还是得写蛇的实现,然后又得修改世界部分的代码,来查看蛇的效果。反反复复,实在折腾不起。所以我打算把食物和蛇的实现都写完,最后统一看运行效果。

蛇和食物一样,得在世界中创建,所以代码基本差不多。

Snake * SNK_CreateSnake(World *world, int size, int x, int y)
{
Snake *snake; if (world == ) return ; if ((snake = (Snake *)SDL_malloc(sizeof(Snake))) == ) return ; INIT_SNAKE(world, size, x, y);
SNK_GrowSnake(snake); return snake;
}

宏INIT_SNAKE用于初始化Snake结构体。

SNK_GrowSnake函数用于将蛇的长度加一,因为蛇创建出来后只有蛇头,我必须再次给它加个蛇尾。如果只有蛇头,当然也能运行,这只是模拟,不是真正的生命体。不过这是畸形蛇,不好看。我还是让它正常一点,符合常规思维。

蛇的身体一节一节的,所以可以看到我在头文件中用了一个单向链表表示蛇的身体。所以销毁蛇时,我要遍历整个链表才行,然后依次释放每个身体节点。

void SNK_DestroySnake(Snake *snake)
{
struct Body *body; if (snake != )
{
if ((body = snake->body)) REMOVE_BODY(body); SDL_free(snake);
snake = ;
}
}

宏REMOVE_BODY就是用来遍历链表并释放身体节点的,我把它定义为一个宏,这显得有点多次一举。这么做主要是因为我不得不定义一个APPEND_BODY宏来增加蛇的身体节点,所以为了和增加节点相对应,我定义了移除节点这个宏。

移动蛇的位置分为两部,移动蛇头和移动蛇的身体。主要是由于定义的时候我没有把蛇头当作身体的一部分,因为身体可以增长,而蛇头不能增长,所以只能这样了。

void SNK_MoveSnake(Snake *snake)
{
struct Body *body; if (snake != )
{
MOVE_SNAKE(snake); for (body = snake->body; body; body = body->next)
{
MOVE_SNAKE(body);
body->direction = (body->next != ) ? body->next->direction : snake->direction;
}
}
}

snake表示蛇头,MOVE_SNAKE(snake)表示移动蛇头的位置,MOVE_SNAKE(body)表示移动身体的位置。

移动身体需要遍历链表,不过这里设置身体方向不知道是否有人看懂? 当前身体节点的方向等于下一个身体节点的方向,仔细想想,这是什么意思?

我对蛇的分析是,蛇只会在尾部追加节点,如果snake->body指向第一个节点first, first指向第二个节点second, 那么我追加第三个节点就要从snake->body开始遍历两次,追加第四个节点就要从snake->body开始遍历三次。所以我改变了这个没有效率的行为,我让snake->body始终指向最后一个身体节点,因此当追加新的身体节点时,直接追加即可,而不用遍历链表。

所以这个for循环其实是从蛇尾向蛇头方向遍历的,当蛇头方向改变时,身体跟着蛇头变化,蛇尾跟着身体变化。这是蛇能随意转弯的关键所在。

接下来就是画出蛇的样子了,和画食物一样,我用一连串的矩形表示蛇。

void SNK_DrawSnake(Snake *snake)
{
SDL_Rect rect;
struct Body *body; if (snake != )
{
rect.x = snake->x;
rect.y = snake->y;
rect.w = rect.h = snake->size; if (((snake->world != ) ? (snake->world->render != ) : ))
{
SDL_SetRenderDrawColor(snake->world->render,
snake->color.r, snake->color.g,
snake->color.b, snake->color.a);
SDL_RenderDrawRect(snake->world->render, &rect); for (body = snake->body; body; body = body->next)
{
rect.x = body->x;
rect.y = body->y;
SDL_RenderDrawRect(snake->world->render, &rect);
}
}
}
}

对于蛇的增长,有两个意思:没有尾巴时,增长的是尾巴。有尾巴时,增长的是身体。

void SNK_GrowSnake(Snake *snake)
{
struct Body *body; if (snake != )
{
if ((body = (struct Body *)SDL_malloc(sizeof(struct Body))) == ) return; if (snake->body == )
{
APPEND_BODY(snake, body);
}
else
{
APPEND_BODY(snake->body, body);
}
}
}

接下来是检查碰撞的函数,它主要有两个用途:1. 当参数rect是蛇头位置时,用来检测蛇头是否咬到自己的身体。2. 当参数rect是食物位置时,用来检测身体是否碰到食物。咬到自己或者碰到食物,返回1, 否则返回0。

int SNK_HasIntersection(Snake *snake, SDL_Rect rect)
{
SDL_Rect bodyrect;
struct Body *body; if (snake != )
{
bodyrect.w = bodyrect.h = snake->size; for (body = snake->body; body; body = body->next)
{
bodyrect.x = body->x;
bodyrect.y = body->y; if (SDL_HasIntersection(&bodyrect, &rect) != )
return ;
}
} return ;
}

在头文件中,我定义了蛇的两个状态:已死或者可以移动。这个函数便是用于检测蛇的状态的。返回SNAKE_DIED表示蛇死了;返回SNAKE_MOVABLE表示蛇处于正常状态,可以自由移动;返回0表示蛇碰到世界的边界,不可以移动。我没有实现蛇可以从一边回到另一边这种功能,也没有规定蛇碰到墙就死了。一切尽可能保持简单!

int SNK_GetSnakeStatus(Snake *snake)
{
SDL_Rect headrect; if (((snake != ) ? (snake->world != ) : ))
{
headrect.w = (snake->x > && snake->x < snake->world->w);
headrect.h = (snake->y > && snake->y < snake->world->h); if (headrect.w && headrect.h)
{
headrect.x = snake->x;
headrect.y = snake->y;
headrect.w = headrect.h = snake->size; if (SNK_HasIntersection(snake, headrect) != )
return SNAKE_DIED; return SNAKE_MOVABLE;
}
else
{
switch (snake->direction)
{
case SNAKE_UP:
headrect.x = (snake->y > );
break;
case SNAKE_DOWN:
headrect.x = ((snake->y + snake->size) < snake->world->h);
break;
case SNAKE_LEFT:
headrect.x = (snake->x > );
break;
case SNAKE_RIGHT:
headrect.x = ((snake->x + snake->size) < snake->world->w);
break;
} return ((headrect.x != ) ? SNAKE_MOVABLE : );
}
} return ;
}

这里switch语句只有当蛇碰到世界的边界时才会进入,这一段主要是为了实现一个功能:当蛇碰到世界的边界时,蛇无法再向前移动,但是蛇可以再次转弯。

未完,待续!

[C入门 - 游戏编程系列] 贪吃蛇篇(五) - 蛇实现的更多相关文章

  1. [C入门 - 游戏编程系列] 贪吃蛇篇(六) - 蛇实现

    这一篇是关于设置蛇的属性的,接上一篇(五). 设置蛇的速度,很简单,只要不是负数就行了. void SNK_SetSnakeSpeed(Snake *snake, int speed) { ) sna ...

  2. [C入门 - 游戏编程系列] 贪吃蛇篇(三) - 蛇定义

    蛇是这个游戏的主角,要实现的功能也是最复杂的一个.因为蛇不止有属性,还有行为.它会动,还会吃东西,还会长大!而且还会死!这是很要命的.我一向看不懂复杂的代码,也写不出复杂的代码.所以对于蛇,我很纠结, ...

  3. [C入门 - 游戏编程系列] 贪吃蛇篇(一) - 世界定义

    每个游戏都有一个很明确的目的或者说游戏主题,贪吃蛇的目的很明确:蛇找到并吃掉食物.只有目的是很无聊的,算不上一个好游戏.所以设计者增加了创意:1. 吃掉食物后蛇会增长:2. 吃掉食物后分数会增加.有些 ...

  4. [C入门 - 游戏编程系列] 贪吃蛇篇(四) - 食物实现

    由于食物是贪吃蛇游戏中最简单的一部分,而且和其他部分关联性不强,基本上是一个独立的部分,所以我打算先实现它. 我的想法是食物必须在世界中才能被创造出来,也就是说,先有世界再有食物,所以我得先判断世界是 ...

  5. [C入门 - 游戏编程系列] 贪吃蛇篇(二) - 食物定义

    游戏中的食物没有那么多复杂属性,特别是贪吃蛇游戏中,我把食物看待的很简单: 1. 它必须属于世界,才能出现在世界.不可能一个不属于世界的食物,出现在世界中:但是可能存在着一个食物,它属于世界,但是却没 ...

  6. [C入门 - 游戏编程系列] 序言篇

    记得学习C语言的时候,看着别人能写各种各样的小游戏和小软件,甚是羡慕.而自己,虽然说语法都会,但是真正上手写个几百行的代码,就显得力不从心.曾经一度很是郁闷,看过一些书,大都处于教语法的层面,有些涉及 ...

  7. [C入门 - 游戏编程系列] 环境篇

    这一系列笔记的代码都是在Ubuntu 14.04下编码并测试的,原因无他,因为我笔记本电脑只装了一个Ubuntu系统,其中唯一使用的第三方库SDL也是开源并且跨平台的.所以即使你用的是Windows或 ...

  8. 游戏编程系列[1]--游戏编程中RPC协议的使用[2]--Aop PostSharp篇

    上一篇我们使用了一个通用JSON协议约定来进行达到远程调用的目的.但是从实现上,我们需要不断的在所有的方法上添加拦截,并且判断拦截,然后执行,这就达到了一个比较繁琐的目的. 之前我们尝试过使用代码生成 ...

  9. 游戏编程系列[2]--游戏编程中RPC与OpLog协议的结合--序

    在系列[1]中,我们展示了RPC调用协议的定义以及演示,通过方法定义以及协议约定,进行了协议约定以及调用过程的约定.然而,实际上在游戏中,调用过程之后,需要传输相对多的数据给服务端. 常用场景,客户端 ...

随机推荐

  1. GCD 深入理解

    GCD 深入理解(一) 虽然 GCD 已经出现过一段时间了,但不是每个人都明了其主要内容.这是可以理解的:并发一直很棘手,而 GCD 是基于 C 的 API ,它们就像一组尖锐的棱角戳进 Object ...

  2. Caffe : Layer Catalogue(2)

    TanH / Hyperbolic Tangent 类型(type):TanH CPU 实现: ./src/caffe/layers/tanh_layer.cpp CUDA.GPU实现: ./src/ ...

  3. rtf表格的合并

    {\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil Calibri;}{\f1\fnil\fcharset134 \'cb\'ce\ ...

  4. ImageView显示不出来图片

    问题?? 用ImageView控件方法:public void setImageBitmap(Bitmap bm)来显示生成的bimap,结果在图片显示区域显示一片空白,而且其他控件颜色没变化,造成I ...

  5. BZOJ 1492 货币兑换Cash

    http://www.lydsy.com/JudgeOnline/problem.php?id=1492 思路: 问题转变为维护一个凸包,每次转移都找凸包上的点,并更新凸壳 可以用splay维护,或者 ...

  6. C# 代码转换到Python

    下载地址:http://pan.baidu.com/s/1dDAZJxv SharpDevelop 3.1 now supports converting C# and VB.NET code to ...

  7. 2015第19周三小众app

    今天搜集下个人知道好玩的小众app: 1.开眼每日推荐5个小视频,世界那么大,一起来看看吧. 2.懒人周末周末去哪了?简洁美观 3.LOFTER 网易良心出品. 4.玩儿去 发现很多很多好玩的去处 5 ...

  8. jtree(选择框)

    jtree一般的用法是: 1. 展示电脑中文件的层次结构,如图所示. 具体的代码: package jtree; import java.io.File; import javax.swing.JTr ...

  9. 《Unix环境高级编程》环境搭建

    用的是第三版的安装包:src.3e.tar.gz 地址:http://www.apuebook.com/ 1.解压:$ tar  -zxvf  *.tar.gz 2. $ cd apue.3e/ 3. ...

  10. pkill killall kill pidof

    http://blog.csdn.net/whycold/article/details/11771841  http://www.cnblogs.com/rsky/p/4886043.html ht ...