当前位置:科技动态 > vs2010 c 课程设计_《C语言课程设计与游戏开发实践教程》个人学习

vs2010 c 课程设计_《C语言课程设计与游戏开发实践教程》个人学习

  • 发布:2023-09-25 14:13

当前位置:我的异常网? C语言???求组,VS2010写C的课程设计,程序说类型不兼容

求组,VS2010写C的课程设计,程序说类型不兼容

www.sychzs.cn??网友分享于:2013-06-15??浏览:98次

求助求组,VS2010写C的课程设计,程序说类型不兼容

求组啊各位大神 - - 这个到底是什么原因啊

超市管理.c(160): warning C4133: “=”: 从“ware *”到“sp *”的类型不兼容

超市管理.c(160): warning C4133: “

超市管理.c(160): error C2036: “sp *”: 未知的大小

超市管理.c(162): error C2037: “num”的左侧部分指定未定义的结构/联合“sp”

//超市商品排序系统14.2的设计

//录入,输出,保存(至文件),查看(从文件中);

//按类别排序输出,按数量排序输出,按单价排序输出;

//超市商品管理系统(商品编号,商品名称,单价,数量,类别)

#include

#include

#include

int count;

struct ware

{int sn; //编号

char name[8]; //名称

float jg; //价格

int num; //数量

int sort; //类别

}sp[30]={0};

struct sp *p;

void input()

{

printf("商品编号: ");

scanf("%d",&sp[count].sn);

printf("商品名称(4字以内): ");

scanf("%s",&sp[count].name);

printf("商品价格: ");

scanf("%f",&sp[count].jg);

printf("商品数量: ");

scanf("%d",&sp[count].num);

printf("商品类别(1~8): ");

scanf("%d",&sp[count].sort);

count++;

printf("按任意键返回");

getchar(); //任意键返回

}

void output()

{

int i;

system("cls");

printf("\n商品编号 商品名称 价格 数量 类别\n");

for(i=0;i

{printf("\n%d %s %d %d %d\n",sp[i].sn,sp[i].name,sp[i].jg,sp[i].num,sp[i].sort);}

printf("按任意键返回");

getchar();

}

void del()

{int m,k,n,s=0;

printf("请输入商品编号: \n");

scanf("%d",&m);

for(k=0;k<30;k++)

{if(sp[k].sn==m)

{for(n=k;n<29;n++)

{sp[n].sn=sp[n+1].sn;

strcpy(sp[n].name,sp[n+1].name);

sp[n].jg,sp[n+1].jg;

sp[n].num,sp[n+1].num;

sp[n].sort,sp[n+1].sort;

}count--;s=1;};}

if(s==0)

printf("无该商品信息.\n");

getchar();

}

void coma() //根据数量排序

{int k,t,i;

float o;

char n[20];

for(k=0;k<30;k++)

for(i=0;i<30-k;i++)

if(sp[i].num>sp[i+1].num&&sp[i].num!=0&&sp[i+1].num!=0)

{t=sp[i].num;

sp[i].num=sp[i+1].num;

sp[i+1].num=t;

o=sp[i].jg;

sp[i].jg=sp[i+1].jg;

sp[i+1].jg=o;

t=sp[i].sn;

sp[i].sn=sp[i+1].sn;

sp[i+1].sn=t;

t=sp[i].sort;

sp[i].sort=sp[i+1].sort;

sp[i+1].sort=t;

strcpy(n,sp[i].name);

strcpy(sp[i].name,sp[i+1].name);

strcpy(sp[i+1].name,n);

printf("排序结束!\n");

output();

}}

void comb() //根据价格排序

{int k,t,i;

float o;

char n[20];

for(k=0;k<30;k++)

for(i=0;i<30-k;i++)

if(sp[i].jg>sp[i+1].jg&&sp[i].jg!=0&&sp[i+1].jg!=0)

{t=sp[i].num;

sp[i].num=sp[i+1].num;

sp[i+1].num=t;

o=sp[i].jg;

sp[i].jg=sp[i+1].jg;

sp[i+1].jg=o;

t=sp[i].sn;

sp[i].sn=sp[i+1].sn;

sp[i+1].sn=t;

t=sp[i].sort;

sp[i].sort=sp[i+1].sort;

sp[i+1].sort=t;

strcpy(n,sp[i].name);

strcpy(sp[i].name,sp[i+1].name);

strcpy(sp[i+1].name,n);

printf("排序结束!\n");

output();

}}

void comc() //按类别排序

{int k,t,i;

float o;

char n[20];

for(k=0;k<30;k++)

for(i=0;i<30-k;i++)

if(sp[i].sort>sp[i+1].sort&&sp[i].sort!=0&&sp[i+1].sort!=0)

{t=sp[i].num;

sp[i].num=sp[i+1].num;

sp[i+1].num=t;

o=sp[i].jg;

sp[i].jg=sp[i+1].jg;

sp[i+1].jg=o;

t=sp[i].sn;

sp[i].sn=sp[i+1].sn;

sp[i+1].sn=t;

t=sp[i].sort;

sp[i].sort=sp[i+1].sort;

sp[i+1].sort=t;

strcpy(n,sp[i].name);

strcpy(sp[i].name,sp[i+1].name);

strcpy(sp[i+1].name,n);

printf("排序结束!\n");

output();

}}

void save()

{FILE *fp;

fp=fopen("cs.out","wb");

for(count=0;count<30;count++)

文章评论

因为课程设计不得不用C++来写,而且底层函数字符串返回的都是unsigned char* 的,而VS2010中窗口控件中显示的字符串都是String^ ,所以整个过程基本上就是在各个类型之间进行转换,第一次用VS写程序,也是第一次接触.NET,刚开始真是被各个类型转换搞得头疼,下面总结一下用到的几个关于String^ 转换,希望以后用到的时候可以方便,也希望可以方便他人。

? ? ?1、char* 转换为String^

? ? ? ? ? char* ? ? c = "123";

? ? ? ? ? ?String^ ?str = gcnew String(c);

? ? ?2、String^ 转换为 char*?

? ? ? ? ? ?首先使用命名空间 ??using namespace System::Runtime::InteropServices;

? ? ? ? ? ?String^ ?str="123";

? ? ? ? ? ?char* c =(char*)(void*)Marshal::StringToHGlobalAnsi(str);

? ? ?3、String^ 转换为double?

? ? ? ? ? ? 这里使用Convert 类型,它是System下面的一个类型,刚开使在使用的时候以为可以跟Java里面一样,直接通过类名调用静态函数,但一直出错,后来先定义一个对象后,才能调用到方法,如果可以像Java那样调用,希望大家不吝赐教啊,

? ? ? ? ? ?Convert^ ?con;

? ? ? ? ? ?String^ ?str = "123";

? ? ? ? ? ? double d = con->ToDouble(str);

? ? ? ? ? ? 补充的是,Convert里面还有很多类型的转换方法,非常丰富。

? ? ?4、double 转到String^ 也是使用Convert 类,在此不再赘述。

? ? ? ? ? 最后想说的是,关于^符号,本人一直不太懂,但根据报错信息,每次定义对象的时候都使用^符号,并使用gcnew,就不报错了,个人的理解可能是自动回收内存的一个机制。

? ? ?5、unsigned char* 转String^ 类型

? ? ? ??

private: String^ BYTEtoString(unsigned char* BYTEContent)

{

string str = "";

stringstream ss; //需要添加 #include

for(int i = 0;i < sizeof(BYTEContent)/sizeof(BYTEContent[0]);i++) //遍历

{

if(int(BYTEContent[i]) < 16) //若该值小于16,则在前面补0

{

ss << "0";

}

?ss<

}

str = ss.str();

String^ s =gcnew String(str.c_str());

return s;

}

C语言课程设计与游戏开发实践教程

第一章 C游戏开发快速入门1.1弹跳球小球思考题

1.2 简单的飞机游戏思考题

总结

第二章 函数封装的游戏开发2.1 飞机游戏思考题:

2.2 用函数实现反弹球消砖块思考题

2.3 flappy bird思考题

总结

第三章 应用数组的游戏开发3.1 生命游戏思考题

3.2 用数组实现反弹球小砖块思考题

3.3 空战游戏思考题

3.4 贪吃蛇思考题

总结

第4章 简单绘图游戏开发4.1 EasyX快速入门4.1.2 围棋棋盘

4.2 多球反弹思考题

4.3 实时时钟思考题

4.4 结合游戏开发框架和EayX绘图实现反弹球消砖块思考题

4.5 鼠标交互思考题

总结

第五章应用图片与声音素材的游戏开发5.1 使用图片与声音思考题

第一章 C游戏开发快速入门

学习本章前需要掌握的语法知识:标识符、变量、常量、运算符与表达式,以及printf、scanf、if-else、while、for语句的用法原书中环境VS2010随书资源[密:cq2g]

1.1弹跳球小球

#include

#include

#include

int main(void)

{

int i, j;

int x = 0;

int y = 5;

int height = 20;

int velocity_x = 1;

int velocity_y = 1;

int left = 0;

int right = 20;

int top = 0;

int bottom = 10;

while ( 1 )

{

x += velocity_x;

y += velocity_y;

system("cls"); // 清屏函数

// 输出小球上面的空行

for ( i = 0; i < x; i++ )

printf("\n");

// 输出小球左边的空行

for ( j = 0; j < y; j++ )

printf(" ");

printf("o"); // 输出小球o

printf("\n");

Sleep(50); // 在输出图形后等待50ms

if ( x == top || x == bottom )

velocity_x = -velocity_x;

if ( y == left || y == right )

velocity_y = -velocity_y;

}

return 0;

}

思考题

1.如果不用Sleep函数,能否利用循环语句实现速度变慢的效果?

2.尝试利用printf("\a")实现小球碰撞到边界时响铃的效果。

3.尝试为反弹球游戏绘制边框。

1.2 简单的飞机游戏

#include

#include

#include // 输入函数:getch()、kbhit()

int main(void)

{

int i, j;

int x = 5;

int y = 10;

char input;

int isFire = 0; // 1显示飞机在正上方输出激光竖线

int ny = 5; // 第一个靶子,放在第一行的ny列上

int isKilled = 0;

// 0显示靶子,1靶子被击中则不显示

while ( 1 )

{

system("cls"); // 清屏函数

if ( !isKilled ) // 输出靶子

{

for ( j = 0; j < ny; j++ )

printf(" ");

printf("+ \n");

}

if ( isFire == 0 ) // 输出飞机上面的空行

{

for ( i = 0; i < x; i++ )

printf("\n");

}else

{

for ( i = 0; i < x; i++ ) // 输出飞机上面的激光光线

{

for ( j = 0; j < y; j++ )

printf(" ");

printf(" |\n");

}

if ( ny == y + 2 ) // +2 是因为激光在飞机的正中间,距离左边的而坐标

isKilled = 1; // 击中靶子

isFire = 0;

}

// 下面输出一个复杂的飞机图案

for ( j = 0; j < y; j++ )

printf(" ");

printf(" * \n"); // 输出飞机

for ( j = 0; j < y; j++ )

printf(" ");

printf("*****\n");

for ( j = 0; j < y; j++ )

printf(" ");

printf(" * * \n");

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' )

y--; // 位置左移

if ( input == 'd' )

y++; // 位置右移

if ( input == 'w' )

x--; // 位置上移

if ( input == 's' )

x++; // 位置下移

if ( input == ' ' )

isFire = 1;

}

}

return 0;

}

思考题

1.如何让靶子移动起来。 2.如何统计和显示击中得分。

总结

1、大框架:建立(x,y)坐标【行、列】建立循环体。 2、循环体作用域中使用if else语句创建图案。 3、对于不同的图案可使用标记变量进行操作判断。 (如果这个点需要移动则放在大框内,否则放在框架外)

//以下函数只能在windows os使用

#include // 提供system("cls");清屏函数

#include // 提供Sleep(50);睡眠50ms

#include

/*

提供: 输入函数:getch(),不需要回车就可以得到输入行的字符。

kbhit()函数在用户有键盘输入时,返回1,否则返回0;

*/

第二章 函数封装的游戏开发

2.1 飞机游戏

#include

#include // 提供 system("cls"); 清屏函数

#include // 提供 getch()、kbhit() 函数

#include // 提供 void gotoxy(int x, int y); 函数

// 全局变量

int position_x, position_y; // 飞机位置

int bullet_x, bullet_y; // 子弹位置

int enemy_x, enemy_y; // 敌机位置

int high, width; // 游戏画面尺寸

int score; // 得分

void startup() // 数据初始化

{

score = 0;

high = 20;

width = 30;

position_x = high/2;

position_y = width/2;

bullet_x = -1;

bullet_y = position_y;

enemy_x = 0;

enemy_y = position_y;

}

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}

void show() /* 展示画面 */

{

//system("cls"); // 清屏

gotoxy(0,0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i < high; i++ )

{

for ( j = 0; j < width; j++ )

{

if ( (i == position_x) && (j == position_y) )

printf("*"); // 输出飞机*

else if ( (i == enemy_x) && (j == enemy_y) )

printf("@"); // 输出敌机@

else if( (i == bullet_x) && (j == bullet_y) )

printf("|"); // 输出子弹|

else

printf(" "); // 输出空格

}

printf("\n");

}

printf("得分:%d\n", score);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

if ( bullet_x > -1 )

bullet_x--;

// 用来控制敌机向下移动的速度,每隔几次循环才移动一次敌机

// 这样修改,虽然用户按键的交互速度还是很快,但 NPC 的移动显示可以降速

static int speed = 0;

if ( speed < 10 )

{

speed++;

}

if ( speed == 10 )

{

enemy_x++;

speed = 0;

}

if ( (bullet_x == enemy_x) && (bullet_y == enemy_y) ) //子弹击中敌机

{

score++;

enemy_x = -1; // 产生新的飞机

enemy_y = rand() % width;

bullet_x = -2; // 子弹无效(防止穿射)【等于0以下均可,这里只是用-2标识被击落】

}

if ( enemy_x > high ) // 敌机抛出显示屏幕

{

enemy_x = -1; // 产生新的飞机

enemy_y = rand() % width;

}

}

void updateWithInput() /* 与用户输入有关的更新 */

{

char input;

if (kbhit()) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' )

position_y--; // 位置左移

if ( input == 'd' )

position_y++; // 位置右移

if ( input == 'w' )

position_x--; // 位置上移

if ( input == 's' )

position_x++; // 位置下移

if ( input == ' ' )

{

bullet_x = position_x - 1; // 发射子弹的初始位置在飞机的正上方

bullet_y = position_y;

}

}

}

int main()

{

startup(); // 数据的初始化

while ( 1 ) // 游戏循环执行

{

show(); // 显示画面

updateWithoutInput(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

思考题:

1.参考1.2.3节中的方法,尝试实现复杂的飞机图形。 2.随着积分的增加加快敌机的下落速度。 3.防止玩家操控飞机飞出边界。 4.增加按Esc键后暂停的功能。

2.2 用函数实现反弹球消砖块

#include

#include

#include

#include

// 全局变量

int high, width; // 游戏画面大小

int ball_x, ball_y; // 小球的坐标

int ball_vx, ball_vy; // 小球的速度

int position_x, position_y; // 挡板的中心坐标

int ridus; // 挡板的半径大小

int left, right; // 挡板的左右位置

int ball_number; // 反弹小球的次数

int block_x, block_y; // 砖块的位置

int score; // 消除砖块的个数

void gotoxy(int x, int y) /* 将光标移动到(x, y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}

void startup() /* 数据初始化 */

{

high = 15;

width = 20;

ball_x = 0;

ball_y = width / 2;

ball_vx = 1;

ball_vy = 1;

ridus = 5;

position_x = high;

position_y = width / 2;

left = position_y - ridus;

right = position_y + ridus;

ball_number = 0;

block_x = 0;

block_y = width / 2 + 1;

score = 0;

}

void show() /* 显示画面 */

{

gotoxy(0, 0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i <= high + 1; i++ )

{

for ( j = 0; j <= width; j++ )

{

if ( (i == ball_x) && (j == ball_y) )

printf("0"); // 输出小球

else if ( j == width )

printf("|"); // 输出右边框

else if ( i == high + 1 )

printf("-"); // 输出下边框

else if ( (i == position_x) && (j >= left) && (j <= right) )

printf("*"); // 输出挡板

else if ( (i == block_x) && (j == block_y) )

printf("B");

else

printf(" "); // 输出空格

}

printf("\n");

}

printf("反弹小球数:%d\n", ball_number);

printf("消除的砖块数:%d\n", score);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

if ( ball_x == high - 1 )

{

if ( (ball_y >= left) && (ball_y <= right) ) // 被挡板挡住

{

ball_number++;

printf("\a");

}else

{

printf("游戏失败\n");

system("pause");

exit(EXIT_FAILURE);

}

}

if ( (ball_x == block_x) && (ball_y == block_y) )

{

score++; // 分数加1

block_y = rand() % width; // 产生新的砖块

}

ball_x = ball_x + ball_vx;

ball_y = ball_y + ball_vy;

if ( (ball_x == 0) || (ball_x == high - 1) )

ball_vx = -ball_vx;

if ( (ball_y == 0) || (ball_y == width - 1) )

ball_vy = -ball_vy;

Sleep(80);

}

void updateWithInput() /* 与用户输入有关的更新 */

{

char input;

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' )

{

position_y--; // 位置左移

left = position_y - ridus;

right = position_y + ridus;

}

if ( input == 'd' )

{

position_y++; // 位置右移

left = position_y - ridus;

right = position_y + ridus;

}

}

}

int main(void)

{

startup(); // 游戏的初始化

while ( 1 )

{

show(); // 显示画面

updateWithoutInput(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

思考题

1、增加砖块,使得集中概率增大。 2、实现对小球更多的操控,从而可以调整击中的砖块。

2.3 flappy bird

#include

#include

#include

#include

// 全局变量

int high, width; // 游戏画面大小

int bird_x, bird_y; // 小鸟的坐标

int bar1_y, bar1_xDown, bar1_xTop; // 障碍物

int score; // 得分,经过障碍物的个数

void gotoxy(int x, int y) /* 将光标移动到(x, y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}

void startup() /* 数据的初始化 */

{

high = 20;

width = 59;

bird_x = 0;

bird_y = width / 3;

bar1_y = width / 2;

bar1_xDown = high / 3;

bar1_xTop = high / 2;

score = 0;

}

void show() /* 显示画面 */

{

gotoxy(0,0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i < high; i++ )

{

for ( j = 0; j < width; j ++ )

{

if ( (i == bird_x) && (j == bird_y) )

printf("@"); // 输出小鸟

else if ( (j == bar1_y) && ((i < bar1_xDown) || (i > bar1_xTop)) )

printf("*");

else

printf(" "); // 输出空格

}

printf("\n");

}

printf("得分:%d\n", score);

}

void updateWithoutInut() /* 与用户无关的更新 */

{

bird_x++;

bar1_y--;

if ( (bird_y == bar1_y) )

{

if ( (bird_x >= bar1_xDown) && (bird_x <= bar1_xTop) )

score++;

else

{

printf("游戏失败\n");

system("pause");

exit(EXIT_FAILURE);

}

}

if ( bar1_y <= 0 ) //重新生成新的障碍物

{

bar1_y = width;

int temp = rand() % (int)(high * 0.8);

bar1_xDown = temp - high / 10;

bar1_xTop = temp + high / 10;

}

Sleep(150);

}

void updateWithInput()

{

char input;

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == ' ' )

{

bird_x = bird_x - 2;

}

}

}

int main(void)

{

startup();

while ( 1 )

{

show(); // 显示画面

updateWithoutInut(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

思考题

1、实现小鸟受重力影响下落的效果,即加速度 2、模拟原版flappy bird游戏,在游戏画面中同时显示多个柱子。

总结

#include // 提供 void gotoxy(int x, int y); 函数

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}// 替代system("cls")清屏函数

// 固定句型

int main(void)

{

startup();

while ( 1 )

{

show(); // 显示画面

updateWithoutInut(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

// 结束程序语句

{

printf("游戏失败\n");

system("pause");

exit(EXIT_FAILURE);

}

第三章 应用数组的游戏开发

3.1 生命游戏

#include

#include

#include

#include

#include

#define HIGH 25 // 游戏画面尺寸

#define WIDTH 50

//全局变量

int cells[HIGH][WIDTH]; // 所有位置细胞生1或死0

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}// 替代system("cls")清屏函数

void startup()

{

int i, j;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

cells[i][j] = 1;

}

}

}

void show() /* 显示画面 */

{

gotoxy(0,0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

if ( cells[i][j] == 1 )

printf("*"); // 输出活的细胞

else

printf(" "); // 输出空格

}

printf("\n");

}

Sleep(50);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

int NewCells[HIGH][WIDTH]; // 下一帧的细胞情况

int NeibourNumber; // 统计邻居的个数

int i, j;

for ( i = 1; i <= HIGH - 1; i++ )

{

for ( j = 1; j <= WIDTH - 1; j++ )

{

NeibourNumber = cells[i - 1][j - 1] + cells[i - 1][j] + cells[i - 1][j + 1]

+cells[i][j - 1] + cells[i][j + 1] + cells[i + 1][j - 1] + cells[i + 1][j]

+ cells[i + 1][j + 1];

if ( NeibourNumber == 3 )

NewCells[i][j] = 1;

else if ( NeibourNumber == 2 )

NewCells[i][j] == cells[i][j];

else

NewCells[i][j] = 0;

}

}

for ( i = 1; i <= HIGH - 1; i++ )

{

for ( j = 1; j <= WIDTH - 1; j++ )

cells[i][j] = NewCells[i][j];

}

}

void updateWithInput()

{

}

int main(void)

{

startup();

while ( 1 )

{

show();

updateWithoutInput();

updateWithInput();

}

return 0;

}

思考题

1、让某块区域有水源,即在某块区域声明更容易生存、繁殖 2、实现按+键生命游戏加速演化显示,-键减速,Esc键暂停、R键重新开始 3、实现捕食者、猎物组成的生命游戏,分别用不同的字符显示。

3.2 用数组实现反弹球小砖块

#include

#include

#include

#include

#define HIGH 15 // 游戏画面尺寸

#define WIDTH 20

// 全局变量

int ball_x, ball_y; // 小球的坐标

int ball_vx, ball_vy; // 小球的速度

int position_x, position_y; // 挡板的中心坐标

int radius; // 挡板的半径大小

int left, right; // 挡板的左右位置

int canvas[HIGH][WIDTH] = { 0 }; // 二维数组存储游戏画布中对应的元素

// 0为空格,1为小球o,2为挡板,3为砖块#

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}// 替代system("cls")清屏函数

void startup()

{

ball_x = 0;

ball_y = WIDTH / 2;

ball_vx = 1;

ball_vy = 1;

canvas[ball_x][ball_y] = 1;

radius = 5;

position_x = HIGH - 1;

position_y = WIDTH / 2;

left = position_y - radius;

right = position_y + radius;

int k, i;

for ( k = left; k <= right; k++ )

canvas[position_x][k] = 2;

for ( k = 0; k < WIDTH; k++ )

{

for ( i = 0; i < HIGH / 4; i++ )

canvas[i][k] = 3;

}

}

void show()

{

gotoxy(0,0);

int i, j;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

if ( canvas[i][j] == 0 )

printf(" "); // 输出空格

else if ( canvas[i][j] == 1 )

printf("o"); // 输出小球o

else if ( canvas[i][j] == 2 )

printf("*"); // 输出挡板

else if ( canvas[i][j] == 3 )

printf("#"); // 输出砖块#

}

printf("|\n"); // 显示右边界

}

for( j = 0; j < WIDTH; j++ )

printf("-"); // 显示下边界

printf("\n");

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

if ( ball_x == HIGH - 2 )

{

if ( (ball_y >= left) && (ball_y <= right) )

printf("\a"); // 被挡板挡住响铃

else // 没有被挡板挡住

{

printf("游戏失败\n");

system("pause");

exit(EXIT_FAILURE);

}

}

static int speed = 0;

if ( speed < 7 )

speed++;

if ( speed == 7 )

{

speed = 0;

canvas[ball_x][ball_y] = 0;

// 更新小球的坐标

ball_x = ball_x + ball_vx;

ball_y = ball_y + ball_vy;

canvas[ball_x][ball_y] = 1;

// 碰到边界后反弹

if ( (ball_x == 0) || (ball_x == HIGH - 2) )

ball_vx = -ball_vx;

if ( (ball_y == 0) || (ball_y == WIDTH - 1) )

ball_vy = -ball_vy;

// 碰到砖块后反弹

if ( canvas[ball_x - 1][ball_y] == 3 )

{

ball_vx = -ball_vx;

canvas[ball_x - 1][ball_y] = 0;

printf("\a");

}

}

}

void updateWithInput() /* 与用户输入无关的更新 */

{

char input;

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' && left > 0)

{

canvas[position_x][right] = 0;

position_y--; // 位置左移

left = position_y - radius;

right = position_y + radius;

canvas[position_x][left] = 2;

}

if ( input == 'd' && right < WIDTH - 1 )

{

canvas[position_x][left] = 0;

position_y++; // 位置右移

left = position_y - radius;

right = position_y + radius;

canvas[position_x][right] = 2;

}

}

}

int main(void)

{

startup(); // 数据的初始化

while ( 1 ) // 游戏循环执行

{

show(); // 显示画面

updateWithoutInput(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

思考题

1、按空格键发射新的小球。 2、尝试接金币的小游戏。

3.3 空战游戏

#include

#include

#include

#include

#define HIGH 25 // 游戏画面尺寸

#define WIDTH 50

#define EnemyNum 5

// 全局变量

int position_x, position_y; // 飞机的位置

int enemy_x[EnemyNum], enemy_y[EnemyNum]; // 敌机的位置

int canvas[HIGH][WIDTH] = {0}; // 二位数组存储游戏画布中对应的元素

// 0为空格,1为飞机, 2为子弹|, 3为敌机@

int score;

int BulletWidth; // 子弹的宽度

int EnemyMoveSpeed; // 敌机的移动速度

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}// 替代system("cls")清屏函数

void startup() /* 数据的初始化 */

{

position_x = HIGH / 2;

position_y = WIDTH / 2;

canvas[position_x][position_y] = 1;

int k;

for ( k = 0; k < EnemyNum; k++ )

{

enemy_x[k] = rand() % 2;

enemy_y[k] = rand() % WIDTH;

canvas[enemy_x[k]][enemy_y[k]] = 3;

}

score = 0;

BulletWidth = 0;

EnemyMoveSpeed = 20;

}

void show() // 显示画面

{

gotoxy(0,0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

if ( canvas[i][j] == 0 )

printf(" "); // 输出空格

else if ( canvas[i][j] == 1 )

printf("*"); // 输出飞机*

else if ( canvas[i][j] == 2 )

printf("|"); // 输出子弹|

else if ( canvas[i][j] == 3 )

printf("@"); // 输出敌机@

}

printf("\n");

}

printf("得分:%3d\n", score);

//Sleep(20);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

int i, j, k;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

if ( canvas[i][j] == 2 ) // 子弹向上移动

{

for ( k = 0; k < EnemyNum; k++ )

{

if ( (i == enemy_x[k]) && (j == enemy_y[k]) ) // 子弹击中敌机

{

score++; // 分数加1

if ( score % 5 == 0 && EnemyMoveSpeed > 3 ) // 达到移动积分后敌机变快

EnemyMoveSpeed--;

if ( score % 5 == 0 ) // 达到一定积分后子弹变厉害

BulletWidth++;

canvas[enemy_x[k]][enemy_y[k]] = 0;

enemy_x[k] = rand() % 2; // 产生新的飞机

enemy_y[k] = rand() % WIDTH;

canvas[enemy_x[k]][enemy_y[k]] = 3;

canvas[i][j] = 0; // 子弹消失

}

}

// 子弹向上移动

canvas[i][j] = 0;

if ( i > 0 )

canvas[i - 1][j] = 2;

}

}

}

static int speed = 0;

if ( speed < EnemyMoveSpeed )

speed++;

for ( k = 0; k < EnemyNum; k++ )

{

if ( (position_x == enemy_x[k]) && (position_y == enemy_y[k]) ) // 敌机撞到我机

{

printf("失败!\n");

Sleep(3000);

system("pause");

exit(EXIT_FAILURE);

}

if ( enemy_x[k] > HIGH ) // 敌机抛出显示屏幕

{

canvas[enemy_x[k]][enemy_y[k]] = 0;

enemy_x[k] = rand() % 2;

enemy_y[k] = rand() % WIDTH;

canvas[enemy_x[k]][enemy_y[k]] = 3;

score--; // 减分

}

if ( speed == EnemyMoveSpeed )

{

// 敌机下落

for ( k = 0; k < EnemyNum; k++ ) // 这里最好再定义一个变量,为了方便就不定义了

{

canvas[enemy_x[k]][enemy_y[k]] = 0;

enemy_x[k]++;

canvas[enemy_x[k]][enemy_y[k]] = 3;

speed = 0;

}

}

}

}

void updateWithInput() /* 与用户输入有关的更新 */

{

int k;

char input;

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' )

{

canvas[position_x][position_y] = 0;

position_y--; // 位置左移

canvas[position_x][position_y] = 1;

}else if ( input == 'd' )

{

canvas[position_x][position_y] = 0;

position_y++; // 位置右移

canvas[position_x][position_y] = 1;

}else if ( input == 'w' )

{

canvas[position_x][position_y] = 0;

position_x--; // 位置上移

canvas[position_x][position_y] = 1;

}else if ( input == 's' )

{

canvas[position_x][position_y] = 0;

position_x++; // 位置下移

canvas[position_x][position_y] = 1;

}else if ( input == ' ' )

{

int left = position_y - BulletWidth;

int right = position_y + BulletWidth;

if ( left < 0 )

left = 0;

if ( right > WIDTH - 1)

right = WIDTH - 1;

for ( k = left; k <= right; k++ ) // 发射子弹

canvas[position_x - 1][k] = 2; // 发射子弹的初始化位置再飞机的正上方

}

}

}

int main(void)

{

startup(); // 游戏的初始化

while ( 1 ) // 游戏循环执行

{

show(); // 显示画面

updateWithoutInput(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

return 0;

}

思考题

1、增加敌机BOSS,其形状更大、血量更多。 2、尝试让游戏更有趣,敌机也发射子弹。

3.4 贪吃蛇

#include

#include

#include

#include

#define HIGH 20 // 游戏画面尺寸

#define WIDTH 30

// 全部变量

int moveDirection; // 小蛇移动的方向,上、下、左、右分别用1、2、3、4表示

int food_x,food_y; // 食物的位置

int canvas[HIGH][WIDTH] = { 0 }; // 二位数组存储游戏画布中对应的元素

// 0为空格,-1为边框#, 1为蛇头@, 大于1的正数为蛇身*

void gotoxy(int x, int y) /* 将光标移动到(x,y)位置 */

{

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

COORD pos;

pos.X = x;

pos.Y = y;

SetConsoleCursorPosition(handle, pos);

}// 替代system("cls")清屏函数

/*

·移动小蛇

·第一步扫描数组canvas的所有元素,找到正数元素都加1

·找到最大元素(即蛇尾巴),把其变为0

·找到等于2的元素(即蛇头),根据输出的上下左右方向把对应的另一个像素值设为1(新蛇头)

*/

void moveSnakeByDirection()

{

int i, j;

for ( i = 1; i < HIGH - 1; i++ )

for ( j = 1; j < WIDTH - 1; j++ )

if ( canvas[i][j] > 0 )

canvas[i][j]++;

int oldTail_i, oldTail_j, oldHead_i, oldHead_j;

int max = 0;

for ( i = 1; i < HIGH - 1; i++ )

for ( j = 1; j < WIDTH - 1; j++ )

if ( canvas[i][j] > 0 )

{

if ( max < canvas[i][j] )

{

max = canvas[i][j];

oldTail_i = i;

oldTail_j = j;

}

if ( canvas[i][j] == 2 )

{

oldHead_i = i;

oldHead_j = j;

}

}

int newHead_i, newHead_j;

if ( moveDirection == 1 ) // 向上移动

{

newHead_i = oldHead_i - 1;

newHead_j = oldHead_j;

}

if ( moveDirection == 2 ) // 向下移动

{

newHead_i = oldHead_i + 1;

newHead_j = oldHead_j;

}

if ( moveDirection == 3 ) // 向左移动

{

newHead_i = oldHead_i;

newHead_j = oldHead_j - 1;

}

if ( moveDirection == 4 ) // 向右移动

{

newHead_i = oldHead_i;

newHead_j = oldHead_j + 1;

}

// 如果新蛇头吃到食物

if ( canvas[newHead_i][newHead_j] == -2 )

{

canvas[food_x][food_y] = 0;

// 产生一个新的事物

food_x = rand() % ( HIGH - 5) + 2;

food_y = rand() % ( WIDTH - 5) + 2;

canvas[food_x][food_y] = -2;

// 原来的旧蛇尾留着,长度自动加1

}

else // 否则,原来的旧蛇尾减掉,保持当都不变

canvas[oldTail_i][oldTail_j] = 0;

// 小蛇是否和自身碰撞或者和边框撞,游戏失败

if ( canvas[newHead_i][newHead_j] > 0 || canvas[newHead_i][newHead_j] == -1 )

{

printf("游戏失败!\n");

exit(EXIT_FAILURE);

}else

canvas[newHead_i][newHead_j] = 1;

}

void startup() /* 数据的初始化 */

{

int i, j;

// 初始化边框

for ( i = 0; i < HIGH; i++ )

{

canvas[i][0] = -1;

canvas[i][WIDTH - 1] = -1;

}

for ( j = 0; j < WIDTH; j++ )

{

canvas[0][j] = -1;

canvas[HIGH - 1][j] = -1;

}

// 初始化蛇头位置

canvas[HIGH / 2][WIDTH / 2] = 1;

// 初始化蛇身,画布中的元素值分别为2、3、4、5等

for ( i = 1; i <= 4; i++ )

{

canvas[HIGH / 2][WIDTH / 2 - i] = i + 1;

}

// 初始化小蛇向右移动

moveDirection = 4;

food_x = rand() % ( HIGH - 5) + 2;

food_y = rand() % ( WIDTH - 5) + 2;

canvas[food_x][food_y] = -2;

}

void show() /* 显示画面 */

{

gotoxy(0,0); // 光标移动到原点位置,以下重画清屏

int i, j;

for ( i = 0; i < HIGH; i++ )

{

for ( j = 0; j < WIDTH; j++ )

{

if ( canvas[i][j] == 0 )

printf(" "); // 输出空格

else if ( canvas[i][j] == -1 )

printf("#"); // 输出边框#

else if ( canvas[i][j] == 1 )

printf("@"); // 输出蛇头@

else if ( canvas[i][j] > 1 )

printf("*"); // 输出蛇身*

else if ( canvas[i][j] == -2 )

printf("F"); // 输出食物F

}

printf("\n");

}

Sleep(100);

}

void updateWithoutInput() // 与用户输入无关的更新

{

moveSnakeByDirection();

}

void updateWithInput()

{

char input;

if ( kbhit() )

{

input = getch();

if ( input == 'a' )

{

moveDirection = 3;

moveSnakeByDirection();

}

else if ( input == 'd' )

{

moveDirection = 4;

moveSnakeByDirection();

}

else if ( input == 'w')

{

moveDirection = 1;

moveSnakeByDirection();

}

else if ( input == 's' )

{

moveDirection = 2;

moveSnakeByDirection();

}

}

}

int main(void)

{

startup();

while ( 1 )

{

show();

updateWithoutInput();

updateWithInput();

}

return 0;

}

思考题

1、增加道具,吃完可以加命或减速。 2、尝试实现双人版贪吃蛇游戏。

总结

1、开发流程:概要设计 - - - > 详细设计 2、合理分配,考虑耦合度问题 3、考虑添加新功能时,看看是否能与本身的功能进行关联。 4、应用数组可以更方便地记录复杂的数据,实现更复杂的显示、逻辑判断与控制。 5、利用“标记变量”对操作类进行控制。(分工明确)

第4章 简单绘图游戏开发

4.1 EasyX快速入门

EasyX是一套简单、易用的图形交互库,以教育为目的的应用可以免费试用

EasyX官网下载 EasyX在线教程

4.1.2 围棋棋盘

/*

【详细可参考EasyX帮助文档】

EasyX提供了很多绘图函数,例如:

line(x1, y1, x2, y2); // 画直线(x1,y1)、(x2, y2)为直线的两个端点坐标

circle(x, y, r); // 画圆,圆心为(x,y)半径为r

putpixel(x, y, c); // 画点(x,y),像素的颜色为c

solidrectangle(x1, y1, x2, y2); // 画填充矩形,(x1, y1)、(x2, y2)为左上角、右下角的坐标

EasyX设定绘制颜色,例如:

setlinecolor(c); // 设置线条颜色

setfillcolor(c); // 设置填充颜色

setbkcolor(c); // 设置背景颜色

setcolor(c); // 设置前景颜色

常用的颜色常量有BLACK、WHITE、BLUE、GREEN、RED、BROWN、YELLOW等

也可通过设置RGB三原色进行更多颜色的设定,形式为RGB(r, g, b) 范围0~255【r 红、g 绿、b 蓝】

如:浓度为200的直线 setlinecolor( RGB(200, 0, 0) );

line(0, 100, 640, 100);

*/

#include

#include

int main()

{

int step = 30; // 画线间隔使用step控制变量进行控制

initgraph(600, 600); /* 创建的绘图屏幕 640x480,表示横向有 640 个点,纵向有 480 个点。

左上角是原点 (0, 0),右下角坐标是 (639, 479),x 轴 向右为正,y 轴向下为正(和数学的 y 轴相反)。*/

setbkcolor(YELLOW); /* 设置背景色为黄色 */

cleardevice(); /* 用背景色清空屏幕 */

setlinestyle(PS_SOLID, 2); /* 画实线, 宽度为两个像素 */

setcolor( RGB(0,0,0) ); /* 设置为黑色 */

int i;

for ( i = 1; i <= 19; i++ ) // 画横线和竖线

{

line(i * step, 1 * step, i * step, 19 * step); // 竖线

line(1 * step, i * step, 19 * step, i * step); // 横线

}

getch(); // getch 实现按任意键功能,按任意键后,程序继续执行。否则,程序会立刻执行 closegraph 以至于看不到绘制的内容。

closegraph(); // 关闭图形界面

return 0;

}

4.2 多球反弹

/*

在具体实现时定义数组float minDistance2[BallNum][2],其中minDistances2[i][1]

记录距小球i最近的小球的下标、minDistance[i][0]记录小球i和其最近小球的平方(两点距离公式,不能用根号表示)。

所有小球两两遍历计算数组minDistances2,如果一个小球间的距离小于阈值,则认为发生碰撞,交换这两个小球的速度。

*/

/* 为解决出现明显的画面闪烁,需要借助绘图函数BeginBatchDraw()、FlushBatchDraw()、EndBatchDraw()

BeginBatchDraw(); // 开始批量绘图函数,执行后任何绘图操作都将暂时不输出到屏幕

// 直到执行FlushBatchDraw()或EndBatchDraw()才将之前绘图输出

FlushBatchDraw(); // 用于执行未完成的绘制任务,执行批量绘制

EndBatchDraw(); // 结束批量绘制,并执行未完成的绘制任务

*/

#include

#include

#define HIGH 480 // 游戏画面尺寸

#define WIDTH 640

#define BallNum 5 // 小球个数

int main()

{

float ball_x[BallNum], ball_y[BallNum]; // 小球的坐标

float ball_vx[BallNum], ball_vy[BallNum]; // 小球的速度

float radius; // 小球的半径

int i, j;

radius = 20;

for ( i = 0; i < BallNum; i++ )

{

ball_x[i] = ( i + 2 ) * radius * 3;

ball_y[i] = HIGH / 2;

ball_vx[i] = 1;

ball_vy[i] = 1;

}

initgraph(WIDTH, HIGH);

BeginBatchDraw();

while ( 1 )

{

// 绘制黑线、黑色填充的圆

setcolor(BLACK);

setfillcolor(BLACK);

for ( i = 0; i < BallNum; i++ )

fillcircle(ball_x[i], ball_y[i], radius);

// 更新小球坐标

for ( i = 0; i < BallNum; i++ )

{

ball_x[i] += ball_vx[i];

ball_y[i] += ball_vy[i];

}

// 判断是否和墙壁碰撞

for ( i = 0; i < BallNum; i++ )

{

if ( (ball_x[i] <= radius) || (ball_x[i] >= WIDTH - radius) )

ball_vx[i] = -ball_vx[i];

if ( (ball_y[i] <= radius) || (ball_y[i] >= HIGH - radius) )

ball_vy[i] = -ball_vy[i];

}

float minDistances2[BallNum][2]; // 记录某个小球和与它最近小球的距离,以及这个小球的下标

for ( i = 0; i < BallNum; i++ )

{

minDistances2[i][0] = 9999999;

minDistances2[i][1] = -1;

}

// 求所有小球两两之间的距离的平方

for ( i = 0; i < BallNum; i++ )

{

for ( j = 0; j < BallNum; j++ )

{

if ( i != j ) // 自己和自己不需要比

{

float dist2;

dist2 = ( ball_x[i] - ball_x[j]) * ( ball_x[i] - ball_x[j]) /* 两点距离公式 S^2 = (x2 - x1)^2 + (y2 - y1)^2 */

+ (ball_y[i] - ball_y[j]) * ( ball_y[i] - ball_y[j]);

if ( dist2 < minDistances2[i][0] )

{

minDistances2[i][0] = dist2;

minDistances2[i][1] = j;

}

}

}

}

// 判断球之间是否碰撞

for ( i = 0; i < BallNum; i++ )

{

if ( minDistances2[i][0] <= 4 * radius * radius ) // 若最小距离小于阈值,发生碰撞(距离的平方 小于 [2 * radius]^2 )

{

j = minDistances2[i][1];

// 交换速度

int temp;

temp = ball_vx[i]; ball_vx[i] = ball_vx[j]; ball_vx[j] = temp;

temp = ball_vy[i]; ball_vy[i] = ball_vy[j]; ball_vy[j] = temp;

minDistances2[j][0] = 9999999; // 避免两次速度,又回去了

minDistances2[j][1] = -1;

}

}

// 绘制黄线、绿色填充的圆

setcolor(YELLOW);

setfillcolor(GREEN);

for ( i = 0; i < BallNum; i++ )

fillcircle(ball_x[i], ball_y[i], radius);

FlushBatchDraw();

// 延时

Sleep(3);

}

EndBatchDraw();

closegraph();

return 0;

}

思考题

1、多个反弹球间有可能交叉重叠,试着改进。 2、实现每按一个空格键增加一个反弹球的效果。

4.3 实时时钟

/*

#include

系统变量 SYSTEMTIME ti,通过GetLocalTime(&ti)获取当前时间,秒针的角度由时间确定,即secondAngle = ti.wSecond * 2 * PI / 60;

ti.wSecond 秒

ti.wMinute 分

ti.wHour 时

setlinestyle() //这个函数用于设置当前设备画线样式。

如:setlinestyle(PS_SOLID,2); // 画实线,宽度为两个像素

#include

sin()\cos()以垂直Y轴形成的角度

*/

#include

#include

#include

#define HIGH 480 // 游戏画面尺寸

#define WIDTH 640

#define PI 3.14159

int main()

{

initgraph(WIDTH, HIGH); // 初始化640 X 480 的绘图窗口

int center_x, center_y; // 中心点的坐标,也是钟表的中心

center_x = WIDTH / 2;

center_y = HIGH / 2;

int secondLength = WIDTH / 7; // 秒针的长度

int minuteLength = WIDTH / 6; // 分针的长度

int hourLength = WIDTH / 5; // 时针的长度

int secondEnd_x, secondEnd_y; // 秒针的终点

int minuteEnd_x, minuteEnd_y; // 分针的终点

int hourEnd_x, hourEnd_y; // 时针的终点

float secondAngle = 0; // 秒针对应的角度

float minuteAngle = 0; // 分针对应的角度

float hourAngle = 0; // 时针对应的角度

SYSTEMTIME ti; // 定义变量保存当前时间

wchar_t s[] = L"我的手表"; // 输出字符串(Unicode 字符集)【outtextxy()字符串专用】

BeginBatchDraw();

while ( 1 )

{

// 制作一个简单的表盘

setlinestyle(PS_SOLID, 1);

setcolor(WHITE);

circle(center_x, center_y, WIDTH / 4);

// 画刻盘

int x, y, i;

for ( i = 0; i < 60; i++ )

{

x = center_x + int(WIDTH / 4.3 * sin(i * PI * 2 / 60));

y = center_y - int(WIDTH / 4.3 * cos(i * PI * 2 / 60));

if ( i % 15 == 0 )

bar(x - 5, y - 5, x + 5, y + 5); /* 这个函数用于画无边框填充矩形。*/

else if ( i % 5 == 0 )

circle(x, y, 3);

else

putpixel(x, y, WHITE);

}

outtextxy(center_x - 25, center_y + WIDTH / 6, s); /* 这个函数用于在指定位置输出字符串。 */

GetLocalTime(&ti); // 获取当前时间

// 秒针角度的变化

secondAngle = ti.wSecond * 2 * PI / 60; // 一圈一共2 * PI,一圈60秒,一秒钟秒钟走过的

// 角度为2 * PI / 60

// 分针角度的变化

minuteAngle = ti.wMinute * 2 * PI / 60; // 一圈一共2 * PI,一圈60分,一分钟秒钟走过的

// 角度为2 * PI / 60

// 时针角度的变化

hourAngle = ti.wHour * 2 * PI / 12; // 一圈一共2 * PI,一圈12小时,一小时时针走过的

// 角度为2 * PI / 12

// 由角度决定的秒针终点坐标【以Y轴形成角度】

secondEnd_x = center_x + secondLength * sin(secondAngle);

secondEnd_y = center_y - secondLength * cos(secondAngle);

// 由角度决定的分针终点坐标【以Y轴形成角度】

minuteEnd_x = center_x + minuteLength * sin(minuteAngle);

minuteEnd_y = center_y - minuteLength * cos(minuteAngle);

// 由角度决定的时针终点坐标【以Y轴形成角度】

hourEnd_x = center_x + hourLength * sin(hourAngle);

hourEnd_y = center_y - hourLength * cos(hourAngle);

setlinestyle(PS_SOLID,2); // 画实线,宽度为两个像素

setcolor(WHITE);

line(center_x, center_y, secondEnd_x, secondEnd_y); // 画秒针

setlinestyle(PS_SOLID, 4);

setcolor(BLUE);

line(center_x, center_y, minuteEnd_x, minuteEnd_y); // 画分针

setlinestyle(PS_SOLID, 6);

setcolor(RED);

line(center_x, center_y, hourEnd_x, hourEnd_y); // 画时针

FlushBatchDraw();

Sleep(10);

setcolor(BLACK);

setlinestyle(PS_SOLID, 2);

line(center_x, center_y, secondEnd_x, secondEnd_y); // 隐藏前一帧的秒针

setlinestyle(PS_SOLID, 4);

line(center_x, center_y, minuteEnd_x, minuteEnd_y); // 隐藏前一帧的分针

setlinestyle(PS_SOLID, 6);

line(center_x, center_y, hourEnd_x, hourEnd_y); // 隐藏前一帧的时针

}

EndBatchDraw();

getch(); // 按任意键继续

closegraph();

return 0;

}

思考题

1、显示一个倒计时码表 2、实现一个可视化的万年历

4.4 结合游戏开发框架和EayX绘图实现反弹球消砖块

#include

#include

#define HIGH 480 // 游戏画面尺寸

#define WIDTH 640

#define BRICK_NUM 10 // 砖块的个数

// 全局变量

int ball_x, ball_y; // 小球的坐标

int ball_vx, ball_vy; // 小球的速度

int radius; // 小球的半径

int bar_x, bar_y; // 挡板的中心坐标

int bar_high, bar_width; // 挡板的高度和宽度

int bar_left, bar_right, bar_top, bar_bottom; // 挡板的上下左右位置坐标

int isBrickExisted[BRICK_NUM]; // 每个砖块是否存在,1为存在,0为没有了

int brick_high, brick_width; // 每个砖块的高度和宽度

void startup() /* 数据初始化 */

{

ball_x = WIDTH / 2;

ball_y = HIGH / 2;

ball_vx = 1;

ball_vy = 1;

radius = 20;

bar_high = HIGH / 20;

bar_width = WIDTH / 2;

bar_x = WIDTH / 2;

bar_y = HIGH - bar_high / 2;

bar_left = bar_x - bar_width / 2;

bar_right = bar_x + bar_width / 2;

bar_top = bar_y - bar_high / 2;

bar_bottom = bar_y + bar_high / 2;

brick_width = WIDTH / BRICK_NUM;

brick_high = HIGH / BRICK_NUM;

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

isBrickExisted[i] = 1;

initgraph(WIDTH, HIGH);

BeginBatchDraw();

}

void clean() /* 显示画面 */

{

// 绘画黑线、黑色填充的圆

setcolor(BLACK);

setfillcolor(BLACK);

fillcircle(ball_x, ball_y, radius);

// 绘制黑线、黑色填充的挡板

bar(bar_left, bar_top, bar_right, bar_bottom);

int brick_left, brick_right, brick_top, brick_bottom;

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

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( !isBrickExisted[i] == 0 ) // 砖块没有了,绘制黑色

fillrectangle(brick_left, brick_top, brick_right, brick_bottom);

}

}

void show() /* 显示画面 */

{

// 绘制黄线、绿色填充的圆

setcolor(YELLOW);

setfillcolor(GREEN);

fillcircle(ball_x, ball_y, radius);

// 绘制黄线、绿色填充的挡板

bar(bar_left, bar_top, bar_right, bar_bottom);

int brick_left, brick_right, brick_top, brick_bottom;

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

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( isBrickExisted[i] )

{

setcolor(WHITE);

setfillcolor(RED);

fillrectangle(brick_left, brick_top, brick_right, brick_bottom);

}

}

FlushBatchDraw();

// 延时

Sleep(3);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

// 挡板和小球碰撞,小球反弹

if ( ( (ball_y + radius >= bar_top) && (ball_y + radius < bar_bottom - bar_high / 3) )

|| ( (ball_y - radius <= bar_bottom) && (ball_y - radius > bar_top - bar_high / 3) ) )

if ( (ball_x >= bar_left) && (ball_x <= bar_right) )

ball_vy = -ball_vy;

// 更新小圆的坐标

ball_x += ball_vx;

ball_y += ball_vy;

if ( (ball_x <= radius) || (ball_x >= WIDTH - radius) )

ball_vx = -ball_vx;

if ( (ball_y <= radius) || (ball_y >= HIGH - radius) )

ball_vy = -ball_vy;

int brick_left, brick_right, brick_top, brick_bottom;

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

{

if ( isBrickExisted[i] ) // 砖块存在才判断

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( (ball_y == brick_bottom + radius) && (ball_x >= brick_left) && (ball_x <= brick_right) )

{

isBrickExisted[i] = 0;

ball_vy = -ball_vy;

}

}

}

}

void updateWithInput() /* 与用户输入有关的更新 */

{

char input;

if ( kbhit() ) // 判断是否有输入

{

input = getch(); // 根据用户的不同输入来移动,不必输入回车

if ( input == 'a' && bar_left > 0 )

{

bar_x = bar_x - 15; // 位置左移

bar_left = bar_x - bar_width / 2;

bar_right = bar_x + bar_width / 2;

}

if ( input == 'd' && bar_right < WIDTH )

{

bar_x = bar_x + 15; // 位置右移

bar_left = bar_x - bar_width / 2;

bar_right = bar_x + bar_width / 2;

}

if ( input == 'w' && bar_top > 0 )

{

bar_y = bar_y - 15; // 位置上移

bar_top = bar_y - bar_high / 2;

bar_bottom = bar_y + bar_high / 2;

}

if ( input == 's' && bar_bottom < HIGH )

{

bar_y = bar_y + 15; // 位置下移

bar_top = bar_y - bar_high / 2;

bar_bottom = bar_y + bar_high / 2;

}

}

}

void gameover()

{

EndBatchDraw();

closegraph();

}

int main()

{

startup(); // 数据的初始化

while ( 1 ) // 游戏循环执行

{

clean(); // 把之前绘制的内容清除

updateWithoutInput(); // 与用于无关的更新

updateWithInput(); // 与用户有关的更新

show(); // 显示新画面

}

gameover(); // 游戏结束,进行后续处理

return 0;

}

思考题

尝试用游戏框架和EasyX绘图实现接金币的游戏。

4.5 鼠标交互

#include

#include

#define HIGH 480 // 游戏画面尺寸

#define WIDTH 640

#define BRICK_NUM 10 // 砖块的个数

// 全局变量

int ball_x, ball_y; // 小球的坐标

int ball_vx, ball_vy; // 小球的速度

int radius; // 小球的半径

int bar_x, bar_y; // 挡板的中心坐标

int bar_high, bar_width; // 挡板的高度和宽度

int bar_left, bar_right, bar_top, bar_bottom; // 挡板的上下左右位置坐标

int isBrickExisted[BRICK_NUM]; // 每个砖块是否存在,1为存在,0为没有了

int brick_high, brick_width; // 每个砖块的高度和宽度

void startup() /* 数据初始化 */

{

ball_x = WIDTH / 2;

ball_y = HIGH / 2;

ball_vx = 1;

ball_vy = 1;

radius = 20;

bar_high = HIGH / 20;

bar_width = WIDTH / 2;

bar_x = WIDTH / 2;

bar_y = HIGH - bar_high / 2;

bar_left = bar_x - bar_width / 2;

bar_right = bar_x + bar_width / 2;

bar_top = bar_y - bar_high / 2;

bar_bottom = bar_y + bar_high / 2;

brick_width = WIDTH / BRICK_NUM;

brick_high = HIGH / BRICK_NUM;

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

isBrickExisted[i] = 1;

initgraph(WIDTH, HIGH);

BeginBatchDraw();

}

void clean() /* 显示画面 */

{

// 绘画黑线、黑色填充的圆

setcolor(BLACK);

setfillcolor(BLACK);

fillcircle(ball_x, ball_y, radius);

// 绘制黑线、黑色填充的挡板

bar(bar_left, bar_top, bar_right, bar_bottom);

int brick_left, brick_right, brick_top, brick_bottom;

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

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( !isBrickExisted[i] == 0 ) // 砖块没有了,绘制黑色

fillrectangle(brick_left, brick_top, brick_right, brick_bottom);

}

}

void show() /* 显示画面 */

{

// 绘制黄线、绿色填充的圆

setcolor(YELLOW);

setfillcolor(GREEN);

fillcircle(ball_x, ball_y, radius);

// 绘制黄线、绿色填充的挡板

bar(bar_left, bar_top, bar_right, bar_bottom);

int brick_left, brick_right, brick_top, brick_bottom;

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

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( isBrickExisted[i] )

{

setcolor(WHITE);

setfillcolor(RED);

fillrectangle(brick_left, brick_top, brick_right, brick_bottom);

}

}

FlushBatchDraw();

// 延时

Sleep(3);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

// 挡板和小球碰撞,小球反弹

if ( ( (ball_y + radius >= bar_top) && (ball_y + radius < bar_bottom - bar_high / 3) )

|| ( (ball_y - radius <= bar_bottom) && (ball_y - radius > bar_top - bar_high / 3) ) )

if ( (ball_x >= bar_left) && (ball_x <= bar_right) )

ball_vy = -ball_vy;

// 更新小圆的坐标

ball_x += ball_vx;

ball_y += ball_vy;

if ( (ball_x <= radius) || (ball_x >= WIDTH - radius) )

ball_vx = -ball_vx;

if ( (ball_y <= radius) || (ball_y >= HIGH - radius) )

ball_vy = -ball_vy;

int brick_left, brick_right, brick_top, brick_bottom;

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

{

if ( isBrickExisted[i] ) // 砖块存在才判断

{

brick_left = i * brick_width;

brick_right = brick_left + brick_width;

brick_top = 0;

brick_bottom = brick_high;

if ( (ball_y == brick_bottom + radius) && (ball_x >= brick_left) && (ball_x <= brick_right) )

{

isBrickExisted[i] = 0;

ball_vy = -ball_vy;

}

}

}

}

void updateWithInput() /* 与用户输入有关的更新 */

{

MOUSEMSG m; // 定义鼠标消息

if ( MouseHit() ) // 这个函数用于检测当前是否有鼠标消息。

{

m = GetMouseMsg(); // 获取一条鼠标消息

if ( m.uMsg == WM_MOUSEMOVE )

{

// 挡板的位置等于鼠标所在的位置

bar_x = m.x;

bar_y = m.y;

bar_left = bar_x - bar_width / 2;

bar_right = bar_x + bar_width / 2;

bar_top = bar_y - bar_high / 2;

bar_bottom = bar_y + bar_high / 2;

}

else if ( m.uMsg == WM_LBUTTONDOWN )

{

// 按下鼠标左键,初始化小球的位置为挡板上面中心

ball_x = bar_x;

ball_y = bar_top - radius - 3;

}

}

}

void gameover()

{

EndBatchDraw();

closegraph();

}

int main()

{

startup(); // 数据的初始化

while ( 1 ) // 游戏循环执行

{

clean(); // 把之前绘制的内容清除

updateWithoutInput(); // 与用于无关的更新

updateWithInput(); // 与用户有关的更新

show(); // 显示新画面

}

gameover(); // 游戏结束,进行后续处理

return 0;

}

思考题

1、尝试实现鼠标移动时画出连续的曲线。 2、尝试实现按鼠标左键小鸟向上移动的Flappy bird游戏 3、尝试实现鼠标控制移动、按左键发射子弹的飞行游戏。

总结

/*

【详细可参考EasyX帮助文档】

·EasyX提供了很多绘图函数,例如:

cleardevice(); // 用背景色清空屏幕

line(x1, y1, x2, y2); // 画直线(x1,y1)、(x2, y2)为直线的两个端点坐标

circle(x, y, r); // 画圆,圆心为(x,y)半径为r

putpixel(x, y, c); // 画点(x,y),像素的颜色为c

solidrectangle(x1, y1, x2, y2); // 画填充矩形,(x1, y1)、(x2, y2)为左上角、右下角的坐标

bar(x1, y1, x2, y2); // 这个函数用于画无边框填充矩形。

fillrectangle(x1,x2,y1,y2); // 这个函数用于画有边框的填充矩形。

rectangle(x1,x2,y1,y2); // 这个函数用于画无填充的矩形。

outtextxy(x, y, s); // 这个函数用于在指定位置输出字符串。

·EasyX设定绘制颜色/样式,例如:

setlinecolor(c); // 设置线条颜色

setfillcolor(c); // 设置填充颜色

setbkcolor(c); // 设置背景颜色

setcolor(c); // 设置前景颜色

setbkcolor(YELLOW); // 设置背景色为黄色

setlinestyle(PS_SOLID,2); // 这个函数用于设置当前设备画线样式(画实线,宽度为两个像素)

·EasyX鼠标相关函数

MOUSEMSG m; // 这个结构体用于保存鼠标消息

m.uMsg; // 当前鼠标消息 【相关鼠标类型参考MOUSEMSG中的帮助文档】

MouseHit(); // 这个函数用于检测当前是否有鼠标消息。

MOUSEMSG GetMouseMsg(); // 这个函数用于获取一个鼠标消息。如果当前鼠标消息队列中没有,就一直等待。

·EasyX系统\其他函数

GetLocalTime(&ti) // 系统变量(SYSTEMTIME ti)获取当前时间

// ti.wSecond 秒

// ti.wMinute 分

// ti.wHour 时

initgraph(); //创建的绘图屏幕 640x480,表示横向有 640 个点,纵向有 480 个点。

// 左上角是原点 (0, 0),右下角坐标是 (639, 479),x 轴 向右为正,y 轴向下为正(和数学的 y 轴相反)。

#include // sin()\cos()以垂直Y轴形成的角度,类型float

常用的颜色常量有BLACK、WHITE、BLUE、GREEN、RED、BROWN、YELLOW等

也可通过设置RGB三原色进行更多颜色的设定,形式为RGB(r, g, b) 范围0~255【r 红、g 绿、b 蓝】

如:浓度为200的直线 setlinecolor( RGB(200, 0, 0) );

line(0, 100, 640, 100);

*/

/* 为解决出现明显的画面闪烁,需要借助绘图函数BeginBatchDraw()、FlushBatchDraw()、EndBatchDraw()

BeginBatchDraw(); // 开始批量绘图函数,执行后任何绘图操作都将暂时不输出到屏幕

// 直到执行FlushBatchDraw()或EndBatchDraw()才将之前绘图输出

FlushBatchDraw(); // 用于执行未完成的绘制任务,执行批量绘制

EndBatchDraw(); // 结束批量绘制,并执行未完成的绘制任务

*/

绘图: 1、前景色:着色绘画部分。 2、填充色:着色,由绘画部分形成的图形,填充空位中的颜色(不包含绘画部分)。 3、需要再绘画图形前,提前将颜色设置好。

绘画思维: 1、把左上角位置当做原点,自原点向右为x轴,自原点向下为y轴。 2、每当需要再画布上添加图像时,首先计算位置(可具体,可相对)。 3、使用变量left 、 top描述左上角 4、使用变量right、bottom描述右下角 5、在图形与其他图形产生碰撞等操作时,需考虑其作用域的问题。

实际应用: 1、变化过程中产生操作的因素,可用数组(一维、二维)对一组记录进行存储。【后再判断】 2、使用数学思维解决问题。

第五章应用图片与声音素材的游戏开发

5.1 使用图片与声音

/*

目录用户可自定义,本段代码中导入的媒体均在:C:\\bird 下

*/

#include

#include

// 引用Windows Multimedia APT

# pragma comment(lib, "Winmm.lib") // 提供声音的导入与使用函数: mciSendString();

// 定义IMAGE对象

IMAGE img_bk, img_bd1, img_bd2, img_bar_up1, img_bar_up2, img_bar_down1, img_bar_down2;

int bird_x;

int bird_y;

void startup() /* 数据的初始化 */

{

initgraph(300, 600);

// 读取图片到IMAGE对象中

loadimage(&img_bk, _T("C:\\bird\\background.jpg"));

loadimage(&img_bd1, _T("C:\\bird\\bird1.jpg"));

loadimage(&img_bd2, _T("C:\\bird\\bird2.jpg"));

loadimage(&img_bar_up1, _T("C:\\bird\\bar_up1.gif"));

loadimage(&img_bar_up2, _T("C:\\bird\\bar_up2.gif"));

loadimage(&img_bar_down1, _T("C:\\bird\\bar_down1.gif"));

loadimage(&img_bar_down2, _T("C:\\bird\\bar_down2.gif"));

bird_x = 50;

bird_y = 200;

BeginBatchDraw();

// 循环播放背景音乐

mciSendString(_T("open C:\\bird\\www.sychzs.cn3 alias bkmusic"), NULL, 0, NULL); // 打开背景音乐

mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循环播放

}

void show() /* 显示画面 */

{

// 在坐标(x, y)位置显示IMAGE对象

putimage(0,0, &img_bk); // 显示背景

putimage(150, -300, &img_bar_up1, NOTSRCERASE); // 显示上面一般的障碍物

putimage(150, -300, &img_bar_up2, SRCINVERT);

putimage(150, 400, &img_bar_down1, NOTSRCERASE); // 显示下面一般的障碍物

putimage(150, 400, &img_bar_down2, SRCINVERT);

putimage(bird_x, bird_y, &img_bd1, NOTSRCERASE); // 显示小鸟

putimage(bird_x, bird_y, &img_bd2, SRCINVERT);

FlushBatchDraw();

Sleep(50);

}

void updateWithoutInput() /* 与用户输入无关的更新 */

{

if ( bird_y < 580 )

bird_y = bird_y + 3;

}

void updateWithInput() /* 与用户输入有关的更新 */

{

char input;

if ( kbhit() )

{

input = getch();

if ( input == ' '&& bird_y > 20 )

{

bird_y = bird_y - 60;

mciSendString(_T("close jpmusic"), NULL, 0, NULL); // 先把前面一次的音乐关闭

mciSendString(_T("open C:\\bird\\www.sychzs.cn3 alias jpmusic"), NULL, 0, NULL); // 打开音乐

mciSendString(_T("play jpmusic"), NULL, 0, NULL); // 仅播放一次

}

}

}

void gameover() /* 游戏结束 */

{

EndBatchDraw();

closegraph();

}

int main()

{

startup(); // 游戏的初始化

while ( 1 )

{

show(); // 显示画面

updateWithoutInput(); // 与用户输入无关的更新

updateWithInput(); // 与用户输入有关的更新

}

gameover(); // 游戏结束

return 0;

}

/*

总结:

·EasyX定义图像函数:

IMAGE img_bk; // 定义图像对象。【class IMAGE(int width = 0, int height = 0);】

loadimage(); // 读取图片到IMAGE对象中【从图片文件获取图像(bmp/gif/jpg/png/tif/emf/wmf/ico)】

// 【遮罩】bird1,jpg与bird2.jpg中的像素一一对应,bird1中的白色的区域将bird2中对应的像素显示,bird1中黑色的区域将bird2对应的像素隐藏

loadimage(&img_bd1, _T("D:\\bird1.jpg"),NOTSRCERASE)

loadimage(&img_bd1, _T("D:\\bird2.jpg"),SRCINVERT)

putimage(x, y, &img_bd); // 在坐标(0,0)位置显示IMAGE对象【这个函数的几个重载用于在当前设备上绘制指定图像。】

·EasyX声音的导入与使用

// 导包:#paragma comment(lib, "Winmm.lib")

// 循环播放背景音乐

mciSendString(_T("open C:\\bird\\www.sychzs.cn3 alias bkmusic"), NULL, 0, NULL); // 打开背景音乐

mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循环播放

// 播放一次音乐

mciSendString(_T("open C:\\bird\\www.sychzs.cn3 alias jpmusic"), NULL, 0, NULL); // 打开音乐

mciSendString(_T("play jpmusic"), NULL, 0, NULL); // 仅播放一次

// 多次播放某一段音乐,则需要先关闭再打开播放

mciSendString(_T("close jpmusic"), NULL, 0, NULL); // 先把前面一次的音乐关闭

mciSendString(_T("open C:\\bird\\www.sychzs.cn3 alias jpmusic"), NULL, 0, NULL); // 打开音乐

mciSendString(_T("play jpmusic"), NULL, 0, NULL); // 仅播放一次

*/

思考题

实现完整的flappy bird 游戏

相关文章