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

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

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. ocos 信号量

    信号量分为  :声明信号量.互斥信号量 转: ucos-ii学习笔记——信号量的原理   ucos-ii学习笔记——信号量的原理及使用 #include "INCLUDES.h" ...

  2. Nexus Root Toolkit教程—— 解锁与Root

    Nexus Root Toolkit是Nexus系列手机的专属root工具,本工具可为Nexus系列设备提供解锁.root和反解锁,还可以支持重要数据的备份和恢复.本教程以nexus 7二代和Nexu ...

  3. eucMenu

  4. [转] ubuntu 一些常用软件的安装

    首先说明一下 ubuntu 的软件安装大概有几种方式: 1. deb 包的安装方式deb 是 debian 系 Linux 的包管理方式, ubuntu 是属于 debian 系的 Linux 发行版 ...

  5. 并发情况下synchronized死锁

    存在缺陷的代码: public class DataPropertyIdAndNameRepositoryImpl{ /** 发布标志 */ private volatile boolean publ ...

  6. altium designer 原理图复制出错

    复制原理图的时候最后弹出这种错误 InvalidParameter at 2510219C. AdvSch.dll, Base Address: 24C80000. Exception Occurre ...

  7. 并行计算基础&amp;编程模型与工具

    在当前计算机应用中,对快速并行计算的需求是广泛的,归纳起来,主要有三种类型的应用需求: 计算密集(Computer-Intensive)型应用,如大型科学project计算与数值模拟: 数据密集(Da ...

  8. javaweb中去除某个get方式的参数,并且返回路径

    String requestURL = request.getRequestURL() + ""; // String queryString = request.getQuery ...

  9. sybase从表A创建表B

    sybase从表A创建表B 例如:需要创建一张表B,表的内容及结构跟表A完全一样,类似于SQL SERVER中的CREATE TABLE A AS SELECT * FROM B; 在sybase中只 ...

  10. 一张图看懂dex

    最近在看dex文件格式,看的是飞虫大大的android软件安全与逆向分析一书,写的条理很清晰.相对于elf文件来说,dex文件格式简单了很多,但是提到某个字段,仍然难以想象其所处位置.所以一直想用思维 ...