分享17级同学大一上学期用C语言实现的合金弹头。分步骤代码、图片音乐素材、可执行程序可从百度网盘下载:
链接:https://pan.baidu.com/s/1cm01uEDNodXH-eU5zfF3kw
提取码:toam
一、设计思路
1. 游戏主要基于Easyx提供的函数库,能够忽略WinAPI,减轻代码量。
2. 游戏基本设计思路:
为了方便总体控制和简化操作,选择将easyx底层抽象,可以直接加载相应动画图片到精灵(即游戏中的每个图像元素)结构,通过设置精灵结构中数据,来直接控制动画的播放和移动。
为了实现精灵的操作,选择定义游戏图片结构,封装原图和蒙版,实现基本的根据坐标将蒙版和原图运算到背景中。
为了方便精灵结构的管理,选择实现场景结构。场景主要负责根据Z轴顺序来绘制添加到该场景的所有精灵。另外可以将每个场景相应的逻辑函数,初始化函数,消息处理函数,关闭函数,储存在场景中方便调用。
为了方便场景的管理,选择实现引擎结构。将场景添加到其中,设置相应的游戏编号。就可以通过游戏编号的转换实现场景的切换。
游戏框架选择的基本循环框架。即包含游戏初始化,游戏主循环,消息处理,逻辑执行,碰撞检测,音乐控制。
所有的添加都通过指针连接,最基本的图像结构通过new分配内存,每个结构提供释放函数
3. 具体合金弹头的实现就可以通过,建立场景,建立精灵,将图片资源加载到精灵中。在需要的时候显示或者隐藏相应的动画。在逻辑中进行图片的移动判断,在相应的消息处理函数中,改变相应的变量。就可以完整的实现游戏的基本动画,和控制。具体细节,可以通过游戏的反馈进行调整.
4. 具体的游戏资源收集于各论坛。
5. 在最后对写好的游戏进行优化。
二、功能描述
1. 在主函数中依次调用,GameStart(),在主循环中调用MousEvent(),KeyEvent(),GameAction(),循环结束调用GameEnd();
2. 游戏主体框架及功能
typedef struct _EesyXGameEngine
{
std::vector<Scene*> scenes;
static int m_delay;//游戏帧频率
static int m_starttime;
static int m_sceneID;//当前场景编号
static int GetDelay();
static int GetStartTime();
static int GetScenID();
void AddScene(Scene* scene);
void Paint();//绘制当前场景的全部
void Pause();//暂停整个游戏
void Active();//从暂停中开始
void Init(int delay, int starttime);
Scene* GetCurrentScene();
void InitCurrentScene();
}EasyXGameEngine;
//用于控制场景,在这里设置游戏主循环延迟,窗口高度宽度. 可以通过其中的场景编号实现场景的切换(包含初始化,绘制,逻辑,碰撞检测,等)。
typedef struct _scene
{
std::vector<Sprite*> m_sprites;
std::vector<IMAGE*> m_bkimages;
int m_bkx;//第一张背景的坐标
int m_bky;
int m_maxz;//最大z坐标
int m_state;//场景当前的状态
SceneClose m_sceneclose;//场景的处理函数
SceneStart m_scenestart;
SceneMusic m_scenemusic;
CloseSceneMusic m_closescenemusic;
SceneAction m_sceneaction;
SceneKeyEvent m_scenekeyevent;
SceneMouseEvent m_scenemouseevent;
SceneCollision m_scenecollision;
void Add(Sprite* sprite);//添加精灵
void AddBk(IMAGE* bkimage);//添加背景
void DrawAll();//绘制所有可显示的精灵
void DeleteAll();
void Init(int bkx, int bky, int maxz, SceneClose sceneclose,//初始化函数
SceneStart scenestart,
SceneMusic scenemusic,
CloseSceneMusic closescenemusic,
SceneAction sceneaction,
SceneKeyEvent scenekeyevent,
SceneMouseEvent scenemouseevent,
SceneCollision scenecollision);
}Scene;
//场景结构,可以添加到引擎。用于控制游戏精灵(动画),在其中添加这个场景的逻辑,初始化和关闭函数的函数指针。方便进行控制
typedef struct _sprite
{
std::vector<ImageElement*> images;//使用了STL的向量容器,方便操作
bool m_visible; //控制是否显示动画化
int m_index; //动画总帧数
int m_tempindex; //当前帧数
ImageMode m_imagemode;//图片模式,是否相等
int m_x[50]; //动画坐标,分别对应每一帧
int m_y[50];
int m_z; //z坐标,即绘制的先后顺序
int m_delay; //动画帧数
long m_starttime; //用于控制帧延迟
SpriteMode m_mode;
SpriteMode m_oringmod;//用于记录暂停前的模式
int m_playdelay;
long m_playstarttime;
int m_pausedelay;
long m_pausedellaystarttime;
bool m_needRelease;
void addPauseDelay(int nDelay);//设置一个延迟之后暂停
void addPlayDelay(int nDleay, SpriteMode spritemode);//设置播放之前的延迟
void addXY(int nX, int nY);//增加x,y的值
void reduceXY(int nx, int ny);//减少x,y的值
void Pause(int index);//按照帧的索引暂停
void Active();
void DrawAll(); //根据延迟绘制当前帧
void Add(ImageElement* imageelement);//添加原图和蒙版
void DeleteAll();
void Init(int x, int y, int z, int delay, int starttime,int index,bool visible=true, SpriteMode nMode=SpriteMode::ALLTIME, ImageMode nImage=ImageMode::ALLSAME,bool NeedRelease=true);
} Sprite;
//精灵结构,可添加到场景。用于实现游戏中每一个图像的动画,包含动画延迟,播放模式,是否可见,Z轴等功能。也包含该场景的逻辑函数指针,初始化函数指针,关闭场景函数指针。其中也包含要加载的图片的方式,在同一个动画中有些图片大小不同,就可以通过设置相应的模式来手动设置坐标.另外关于枚举类型,这里使用的是C++语法。(VC6.0不支持)
typedef struct _ImageElement
{
IMAGE* image;
IMAGE* image_mask;
void Delete();
void Draw(int x,int y);
}ImageElement;
//具体图像结构,可添加到精灵中。包含两个图像,原图和蒙版.
bool GameStart();//游戏的初始化
void GamePaint();//游戏画面的绘制,根据场景编号进行绘制
void GameAction();//游戏逻辑,也就是与输入无关的更新,和判断
void GameCollision();//碰撞检测
void GameEnd();//可以在这里释放游戏资源
DWORD WINAPI GameMusic(void *flg);//游戏音乐,是一个线程函数,其中包含一个无限循环,用于检测音乐标志播放相应的音乐。这是为了处理mciSendString()加载资源过慢,造成的游戏卡顿
void KeyEvent();//按键处理
void MouseEvent(MOUSEMSG msg);//鼠标消息处理
ImageElement*LoadImageElement(char*imagepath,char* image_maskpath,int nHeight=0,int nWidth=0,bool resize=false);//用来加载原图和蒙版并分配空间,抽象底层直接返回ImageElemet的指针
3. 另外游戏通过在适当位置添加文件的读写操作(二进制),来实现基本的存档功能。
三、分步骤实现的方法
1. 主体框架的实现
主题框架实现的重点主要在游戏精灵结构的实现以及各种播放模式的实现,要保证不冲突。还有就是游戏资源的释放,由于前期的设计问题,涉及到的多个相同精灵的复用,精灵结构中储存的是资源的地址,所以复用精灵使用的数组,添加到场景中,无法再使用delete来释放资源。具体解决方法,为每一个精灵增加了一个bool用来控制是否释放资源。
2. 具体玩家人物的设计
由于图像资源太过分开(分为上半身和下半身),导致人物的动作设计过于复杂,且代码可读性降低。同时由于相同动画资源图片大小不同导致必须每一帧的坐标都必须手动设置。具体解决办法,牺牲代码的可读性。
3. 游戏音乐的实现
在游戏中使用mciSendString()函数实现音乐的播放,但由于函数加载音乐资源速度太慢,会对游戏造成明显的卡顿。所以选择单独开一个线程来播放音乐。由于mciSendString使用的是栈操作,所以选择在线程函数中建立一个无限循环,通过不断的检测音乐标志的变化,来实现播放音乐。另外再降低音乐质量。
四、体会与总结
1. 版本控制很重要
在游戏分步实现的过程中,随着编写的不断加深,代码量逐渐增大,每次修改都有可能会产生不可预料的结果。甚至有可能某个阶段功能的实现的失败。所以及时回溯,版本控制很重要。
2. 前期设计的重要性
最开始的设计关系到整个游戏最终是否能完成。不然就只能降低代码的可读性来实现。
3. 团队
一个人的精力是有限。在某些情况下需要多人合作。
4. 总结
这是我个人独立写过的代码最多行的程序(近4000行),代码写得越多对语言的设计理解的越深。这次的课设让我学到了很多。写代码的过程中遇到了很多的问题,设计不够完善,中途修改主体框架,找不到bug等等,但庆幸的是都一一解决掉(百度,参考手册,博客等).我以前也写过几个小程序,录频,合成图像,其中不乏一点原理也不懂的,但不懂的总是可以通过各种方法学会。所以不管写什么程序,不管有多复杂,只要坚持下去去学习研究尝试,总会写出来。另外整个框架的优势体现在有多个场景的游戏。