c语言大作业_2018 C语言大作业--21_Ekko制作教程

524f4eb0713492943ea16d44cb2f85db.png

同学们实现的效果:

de1cbcb552e120afe9965623e140caff.png
https://www.zhihu.com/video/1066249425780809728

以下是开发同学的相关文档:

《Ekko》设计报告

本组设计并编写的游戏《Ekko》,是一款引用了当下红火的网络游戏《英雄联盟》中的游戏角色Ekko为主角,由本组三名成员使用C语言编写的一款横屏动作闯关游戏。

一 设计思路

在选取游戏题材作为最终的课程设计目标时,一如许多组的同学一样,我们曾考虑过将一个现成的游戏拿来改编,但最后我们决定自己做一款游戏。而我们要做的第一步,则是明确我们要做一款什么样的游戏。受一款动作游戏《Super Hot》灵感,本组成员想要做一款主角拥有控制时间能力的动作游戏。

在《Super Hot》中,玩家操控的角色拥有一种令人着迷的能力:放慢时间,让原本正常的世界的时间流动速度变慢,从而拥有更强、更从容的反应能力来应对敌人。我们认为这是一个非常酷炫的游戏概念,并想要做一款能体现这一游戏概念的游戏。

受限于我们的知识储备,我们只能使用C语言来编写游戏,而与此同时,我们也只能用EasyX来进行绘图。因此,我们最终决定设计一款2D的动作游戏,而这款游戏能展现出令人着迷的控制时间的能力。

而在设计我们的主角的时候,我们亦曾想过做一个“能控制时间的忍者”,而之后我们注意到了在现今的网络游戏中大红大紫的竞技游戏《英雄联盟》之中,就有一个类似的能控制时间的角色:艾克(Ekko)。艾克是一名生长于名为祖安的城市的少年,拥有控制时间的能力,并利用这个能力惩奸除恶,践行自己的价值观,守护着祖安城。艾克的人设展现出令人钦佩的人性光彩,和他桀骜不羁的个性。我们几乎一拍即合,决定采用Ekko作为我们游戏的主角。

而在游戏构架的构思中,我们为了最大程度体现“控制时间”这一能力,决定设计一种关卡难度极大的游戏——需要玩家细腻的操作,并且正确地使用“时间暂停”这一能力。

谈及动作游戏的难度,最容易想到的有两点:1、地图的复杂性;2、敌人攻击的危险性。因此,在最初的构思中,我们决定给予主角仅仅一条生命——如同《超级马里奥》一般,被敌人的攻击命中,便会死亡。同时,我们会设计复杂而多变的地形,以及大量的敌人、更大量的弹幕。

而我们不能只关注游戏难度是否够高,而应同时关注玩家的游戏体验。所以我们必须将主角的操作细节设计得足够细腻——以回应玩家细腻的操作。

综上,我们的游戏的基本雏形便构建完毕——在祖安城内,一位名为Ekko的少年,在大量的敌人面前把玩着自己的控制时间的能力,灵巧地躲避敌人的各种攻击并前进,这就是我们想做出来的一款原创跑酷游戏。

二 功能描述

在我们组的《Ekko》的设计中,由我来担任Ekko的运动的编写,以及Ekko与地图中各种各样的互动。

以下为艾克——这名酷炫的时间刺客,需要具有什么样的基础功能,以及什么样的“画龙点睛”,才能给玩家一个不错的游戏体验。

1:显示素材。不可否认的是,对于计科专业的同学来说,期末课设最为直接的素材收集方式为搜索互联网。但是这对于我们组而言不可行——因为我们要做的游戏中,需要的元素大部分在网上找不到,原因则为我们游戏虽题材非原创,但游戏方式与界面却与原素材的相去甚远(比如英雄联盟中的3D建模下的艾克是无法应用在我们的游戏中的)。因此,我们组只能自己去绘制我们的素材。

此外,绘制好的素材需要放在特定文件夹内,以便编程时用代码读取这些素材;此外,我们组还需要学会如何利用EasyX,在游戏中显示透明的图层。

2:人物移动。在课程中,我们学习了最基本的人物移动方法:利用getchar函数和几个简单的判断,来实现人物坐标的改变。但这对于我们的Ekko是不够的——这个函数的读取模式存在着“第一次和第二次之间存在停顿”的问题。因此,我需要寻求更加优秀的算法来实现人物的移动。

3:与场景的碰撞。在许多简单的游戏中,与场景的碰撞可以简单的归结为“和一两个物体之间的碰撞判定”,但这个判定方式在一个更加复杂的地图中时不成立的——我们需要编写大量碰撞判断,这让代码变得冗杂,难以阅读和编写,容易出错,且不易调试。因此,我需要写一套“碰撞法则”,以方便后期的地图编写。如此,只需要一套法则,就可以应对各种各样的地图而不易出现问题。

4:时间暂停。这个功能是我们的游戏中的亮点。Ekko正是因为能够控制自己的时间,才能成为“撕裂时空的少年”。这一功能的实现方法很多。

5:正确的死亡。我需要编写艾克在合何时会死亡的代码。

三 分步骤实现方法

第一阶段:绘制素材,准备好人物图片与对应的遮罩图;

第二阶段:架构基本游戏框架,采用不同的源文件以封装不同的功能。利用掩码图将人物放入,调试直至能够正常的演示图片,并加入最简单的人物移动功能。为了使游戏运行更加顺畅,我花费了大量的时间来构建人物的移动,代码量多达一千行左右。以下为代码的一部分:

if ((GetAsyncKeyState(0x41) & 0x8000)) // a

{

if (Ekko_Speed_x >= -2.5)

{

Acceleration_x = -0.1;//获得加速度

Ekko_Speed_x += Acceleration_x;

}

else if (Ekko_Speed_x <= -3)

{

Acceleration_x = 0.8;

Ekko_Speed_x += Acceleration_x;

}

if (Crash_Wall())//如果要撞墙

{

Acceleration_x = 0;

Ekko_Speed_x = 0;

goto loop1;

}

if (Ekko_Face == 1)//变换方向

{

Ekko_Face = -1;

}

Ekko_x += Ekko_Speed_x;

if (DropOrNot == 0)

{

Status_check_i = 1;

}

else if (DropOrNot == 1)//检测空中是否按过AD

{

AD_in_Air = 1;

}

}

需要注意的是,单独的这一段代码并不能看出什么直接意义。这些变量的功能穿插于许许多多的函数中,牵一发而动全身。许多变量不仅用来记录数据,还用来记录状态。

第三阶段:优化人物移动,加入人物和环境的碰撞的法则并测试。这一阶段的完成,意味着地图编写工作可以正式开始;然而该阶段为开发过程最为艰难的部分之一。没有现成的物理引擎的支持,自己从头开始写,并非一件容易的事情,需要大量的耐心、细心,以及长时间的测试,同时这个阶段面临的BUG是最多的。

以下为碰撞代码的核心内容:

int ABS(float A, float B) //float型绝对值。单独定义并且用在下面的函数中

{

float C;

C = A - B;

if (C >= 0)

{

return C;

}

else

{

C = (-1)*C;

return C;

}

}

int Crash_Wall()//判断是否即将和地形碰撞。即将 碰撞 返还 1 ,否则返还 0

{

for (int i = 0; i < Block_Number; i++)

{

if (Ekko_Speed_x > 0)

{

if (

Ekko_x + Ekko_Width / 2 <= Land_Left[i] && Ekko_x + Ekko_Speed_x + Ekko_Width / 2 > Land_Left[i]

&&

Ekko_y + Ekko_High / 2 > Land_Top[i] && Ekko_y - Ekko_High / 2 < Land_Bottom[i]

)

{

LockedOne_x = i;

return 1;

//break;

}

else if (i == LockedOne_x && ABS(Land_Left[i], Ekko_x + Ekko_Width / 2) < 4)

{

return 1;

}

else

continue;

}

else if (Ekko_Speed_x < 0)

{

if (

Ekko_x - Ekko_Width / 2 >= Land_Right[i] && Ekko_x + Ekko_Speed_x - Ekko_Width / 2 < Land_Right[i]

&&

Ekko_y + Ekko_High / 2 > Land_Top[i] && Ekko_y - Ekko_High / 2 < Land_Bottom[i]

)

{

LockedOne_x = i;

return 1;

//break;

}

else if (i == LockedOne_x && ABS(Ekko_x - Ekko_Width / 2, Land_Right[i]) < 3)

{

return 1;

}

else

continue;

}

}

return 0;

}

int Crash_Ground()//判断和地面是否碰撞,即将碰撞返还1,否则返还0 加入了踩到地刺的判定

{

for (int i = 0; i < Block_Number; i++)

{

if (Ekko_Speed_y > 0)

{

if (

Ekko_y + Ekko_High / 2 <= Land_Top[i] && Ekko_y + Ekko_Speed_y + 0.06 + Ekko_High / 2 > Land_Top[i] //0.06是Y轴方向加速度

&&

Ekko_x + Ekko_Width / 2 > Land_Left[i] && Ekko_x - Ekko_Width / 2 < Land_Right[i]

)

{

LockedOne_y = i;

if (LockedOne_y == 6||LockedOne_y == 7 || LockedOne_y == 8 || LockedOne_y == 11 || LockedOne_y == 12 || LockedOne_y == 44 || LockedOne_y == 45 || LockedOne_y == 46 || LockedOne_y == 64 || LockedOne_y == 65 || LockedOne_y == 66)

DeadOrNot = 1;

return 1;

//break;

}

else

continue;

}

else if (DropOrNot == 1 && Ekko_Speed_y == 0)//悬空时速度为0

{

return 0;

}

else if (DropOrNot == 0 && Ekko_Speed_y == 0 && LockedOne_y == i && (Ekko_x + Ekko_Width / 2 < Land_Left[i] || Ekko_x - Ekko_Width / 2 > Land_Right[i]))//落地后速度为0但是踩空

{

return 1;

}

else if (DropOrNot == 0 && Ekko_Speed_y == 0 && LockedOne_y == i && (Ekko_x - Ekko_Width / 2 < Land_Right[i] && Ekko_x + Ekko_Width / 2 > Land_Left[i]))//落地后速度为0,踩实

{

return 0;

}

else if (Ekko_Speed_y < 0)

{

return 0;

}

}

return 0;

}

int Crash_Top()

{

for (int i = 0; i < Block_Number; i++)

{

if (Ekko_Speed_y < 0)

{

if (

Ekko_y - Ekko_High / 2 >= Land_Bottom[i] && Ekko_y + Ekko_Speed_y - Ekko_High / 2 < Land_Bottom[i]

&&

Ekko_x + Ekko_Width / 2 > Land_Left[i] && Ekko_x - Ekko_Width / 2 < Land_Right[i]

)

{

LockedOne_y = i;

return 1;

//break;

}

}

else

break;

}

return 0;

}

简而言之,这些代码实现了主角和任意设置好的矩形都能发生正确的碰撞,使得地图的开发变得简单——只需要记录各个地图板块的坐标即可。

第四阶段:测试人物和地图的碰撞,并加入冲刺能力、时间减缓能力,并测试。

冲刺代码:

static float SIN, COS;//记录角度

AD_in_Air=0;

MOUSEMSG mouse;

mouse.uMsg = false;

if (MouseHit())

{

mouse = GetMouseMsg();

}

if (mouse.uMsg== WM_RBUTTONDOWN && Dash_Check == 0&&Dash_limit==0) //准备冲刺

{

Dash_Speed = 11;

COS = ((mouse.x - Screen_Center_x) / sqrt((mouse.y - Screen_Center_y)*(mouse.y - Screen_Center_y) + (mouse.x - Screen_Center_x)*(mouse.x - Screen_Center_x)));

SIN= ((mouse.y - Screen_Center_y) / sqrt((mouse.y - Screen_Center_y)*(mouse.y - Screen_Center_y) + (mouse.x - Screen_Center_x)*(mouse.x - Screen_Center_x)));

Ekko_Speed_x = Dash_Speed * COS;//鼠标位置决定冲刺方向

Ekko_Speed_y = Dash_Speed * SIN;

Dash_Check = 1;

Dash_limit = 1;

if (!Crash_Wall()&&!Crash_Top()&&!Crash_Ground())

{

Ekko_x += Ekko_Speed_x;

Ekko_y += Ekko_Speed_y;

}

else

{

Ekko_Speed_x = 0;

Ekko_Speed_y = 0;

Dash_Check = 0;

Dash_limit = 0;

}

if (Dash_Check==1)

{

Status_check_i = 3;//图形演示变为冲刺

if (Ekko_Speed_x > 0)

Ekko_Face = 1;

else

Ekko_Face = -1;

DropOrNot = 1;

w_check = 1;

JumpOrNot = 1;

jump_limit_check = 1;

}

}

else if (Dash_Check == 1) //冲刺过程不可控

{

if (Dash_Speed > 3)

{

Dash_Speed -= 0.2;

}

Ekko_Speed_x = Dash_Speed * COS;

Ekko_Speed_y = Dash_Speed * SIN;

if (Crash_Wall())

{

Ekko_Speed_x = 0;

Dash_Check = 0;

}

if (Crash_Top())

{

Ekko_Speed_y = 0;

Dash_Check = 0;

}

if (Crash_Ground())

{

Ekko_Speed_y = 0;

Dash_Check = 0;

Dash_limit = 0;

}

if(Dash_Speed<=5)

{

Dash_Check = 0;

}

Ekko_x += Ekko_Speed_x;

Ekko_y += Ekko_Speed_y;

}

else //非冲刺的情况

... ...(其他功能)

第五阶段:加入人物的死亡功能——被敌人的子弹击中时死亡,坠入地底时也会死亡,碰到地刺的时候亦会死亡。

第六阶段:优化、加入与Ekko语音的音乐素材。

四 体会与总结

在最后完成游戏的一瞬间,心中固然有着千般喜悦,但苦涩感依然无法散去。这一个月的开发过程并不总是一帆风顺,在面对众多的BUG和技术难关时,自己知识的匮乏彻彻底底地暴露出来了,这也迫使自己进一步去学习新的知识。

令我高兴的是,游戏的效果不错——拥有着相当流畅的游戏体验、自己制作的素材活灵活现地展现在游戏中、游戏本身有着诸多趣味等等,依然会让我会心一笑。在这样的过程中,我领悟到了编程带来的无穷乐趣,以及编程的深奥。这样宝贵的经历,必将促使我进行更加深入的学习,以做出让自己更为满意的作品!

分步骤代码、素材、开发难点讲解视频、报告文档,可以从百度网盘下载:

https://pan.baidu.com/s/15lDd1bAwBjsZm6oUi5NHhA​pan.baidu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值