游戏编程(C 语言)- 初学者






4.78/5 (33投票s)
使用C/C++进行游戏编程的简要介绍。
游戏编程
在我们真正开始游戏编程之前,我们需要了解一种称为事件驱动编程的编程方式。事件驱动编程是指这样一种编程风格:应用程序的用户可以从多个选项中自由选择,而不是被限制在预设的交互序列中。游戏编程是事件驱动编程的一个常见例子。游戏是一个封闭的、即完整的、自给自足的正式系统,它代表了现实世界的一个子集。游戏是动作-反应或事件-响应的完美结合,每一次响应都基于最近发生的事件。
游戏编程的要素
一般来说,电脑游戏有五个要素
- 图形
- 声音
- 接口
- 游戏玩法
- 故事
图形
图形包括显示的任何图像以及对图像执行的任何效果。这包括3D对象、纹理、2D图块、2D全屏截图、全动态视频(FMV)以及玩家将看到的其他任何东西。
声音
声音包括游戏中播放的任何音乐或音效。这包括开始音乐、CD音乐、MIDI或MOD音轨、环境音效(环境声音)和音效。
接口
声音包括游戏中播放的任何音乐或音效。这包括开始音乐、CD音乐、MIDI或MOD音轨、环境音效(环境声音)和音效。
游戏玩法
它涵盖了游戏的趣味性、庞大程度以及可玩性时长。
故事
游戏的故事包括游戏开始前的任何背景信息,玩家在游戏过程中或获胜时获得的所有信息,以及玩家从游戏中了解到的关于角色的一切。故事是游戏的一个要素。故事和游戏之间的区别在于,故事以不变(即固定)的序列代表事实,而游戏代表一个分支的序列树,并允许玩家通过在每个分支点做出选择来创造自己的故事。
虽然图形在游戏编程中扮演着重要的角色,但在这篇文章中,我们不会强调游戏的图形和声音元素。我们将专注于通过基于文本界面的基础游戏编程。
游戏设计流程
由于游戏设计需要探索个人的艺术能力,因此无法以一步一步的流程来制定。然而,在某些技术步骤上,一个人或多或少需要遵循。
这些是:
- 确定初始需求。
- 开发界面。
- 开发界面。
- 开发得分逻辑。
我们将详细探讨其中每一个。
界面是游戏编程的另一个非常重要的方面。界面是计算机和玩家之间的沟通方式。就像任何人类语言一样,它是程序员必须将他/她试图与玩家分享的雪崩般想法、构思和情感过滤进去的渠道。界面将决定什么可以做,什么不能做。界面由输入和输出组成。在开发界面时,程序员应该开发静态显示屏幕和动态显示屏幕。静态显示是指不受玩家操作(即玩家的输入)影响的屏幕。而动态显示则是由玩家操作(即玩家的输入)控制的屏幕。
一些静态显示屏的例子是
游戏选择屏幕
游戏启动时玩家有哪些可用的选项?这描述了菜单上有哪些选项,它们如何以及在哪里出现在哪个屏幕上,玩家如何进入,以及如何退出。
游戏开始屏幕
游戏开始时屏幕是什么样的,启动参数是什么,角色在哪里等等?屏幕上有什么消息,在哪里?开场音乐?等等。
由于动态屏幕会根据玩家的输入而变化,它们的描述太多,无法在此一一列出。一些例子
消息屏幕
在开发界面时,您还需要处理响应玩家合法操作的屏幕,以告知他/她正在正确的轨道上。此外,在玩家犯下非法移动或操作时,您还需要处理用于警告玩家的屏幕。
游戏结束消息
这些屏幕包括消息和对诸如:玩家输了会怎样?玩家赢了会怎样?玩家获得高分会怎样?游戏结束后玩家去哪里?他/她如何开始新游戏?等问题的回答。
这一步涉及开发一个合适的玩法逻辑。这要求游戏程序员以程序代码的形式回答许多问题。这些问题包括:游戏如何玩?控制是什么?游戏目标是什么?玩家将如何实现游戏目标?等等。换句话说,我们必须说,由于游戏代表了一种事件驱动的情况,游戏程序员,也就是你,必须指定或编程包括
- 确定初始需求
在编写游戏程序时,在选择游戏目标后,需要确定其初始需求。例如,要编写一个猜数字的游戏程序,你需要决定如何生成数字,涉及的玩家数量,允许玩家的猜测次数,评分方法等。在这里,我们的目标不是让你成为一名专业游戏程序员,而是更侧重于让你了解编写简单或基础游戏程序的想法。
游戏总体描述: 游戏的总体描述包括游戏的概览、它的工作原理、每个级别的流程等。它从玩家的角度描述了游戏的所有部分
- 他/她开始玩之前应该知道什么。
- 他/她看到了什么。
- 他/她做了什么。
- 他/她对所见所为的预期反应。
- 开发界面
- 开发游戏玩法逻辑
- 对用户/玩家操作的响应。
- 对系统事件的响应。
- 游戏规则。
- 如果是双人游戏(电脑作为玩家),则电脑的移动和操作。
- 开发计分逻辑
开发计分逻辑是开发游戏玩法逻辑的一个子集。为此,您必须首先确定您将在游戏中遵循的得分策略。您将决定允许的最大猜测次数,计分机制,是否与时间挂钩等。在此阶段,将处理里程碑事件,并相应地进行(正面或负面)得分。
游戏中的里程碑事件
玩家每隔一段时间都需要因为到达游戏中的某个点而得到奖励(或惩罚)。每个发生特殊情况的地方都称为里程碑事件。有一个仪表可以告知玩家他/她是否走在正确的(或错误的)方向上,并鼓励(或不鼓励)他/她继续前进。
现在我们已经讨论了游戏开发的这些不同阶段,让我们来开发一个简单的井字棋游戏。
游戏总体描述
- 这是一个双人游戏,所以程序会获取两个玩家的名字,并为他们分配O和X。
- 玩家轮流将他们的移动输入到他们选择的格子里。
- 程序需要确保没有格子被覆盖。
- 如果玩家试图输入他/她的移动到另一个玩家已经占用的格子里,那么机会就传给了另一个玩家。
- 程序需要一直运行,直到一个玩家获胜、玩家想退出游戏,或者没有剩余的移动。
- 玩家获胜后,程序会显示消息,并询问玩家是否想再次玩。
现在让我们来分析一下我们即将制作的游戏设计中的不同元素。
这是一个双人游戏,所以我们需要两个变量来存储他们的名字,并运行一个循环来依次要求玩家输入他们的移动。所以我们需要另一个变量来存储回合,以确定哪个玩家应该输入移动。以下是变量
char name[2][30]; //double dimensional array to store names of the player
int chance; //to store the chance, to track which player is to enter the move
我们需要一个函数来处理当玩家按下箭头键并在按下Enter键进入格子时导航到格子。我们还需要另一个变量来跟踪玩家当前所在的格子。一个数组来存储玩家输入的数值。所以以下是变量
int box; //to track the current box the player is on at the moment
char a[3][3]; //array to hold the actual values that player enter while playing
int navigate(char a[3][3], int box, int player, int key);
// to handle key presses and update the current box the player is on
// and to enter the move in to the box when player presses Enter.
在这个函数中,`char a[3][3]`是存储移动的数组。`box`是玩家所在的格子,`key`是按下的键。
还需要另一个变量来计算回合数。总共有九个格子,但回合数可能超过九个,因为如果玩家试图将他的移动输入到一个已被占用的格子里,机会就会传给另一个玩家。
int turns; // to count the number of chances
我们需要一个函数来将移动放入玩家选择的格子里,并且我们需要确保我们不会覆盖格子中的值
void putintobox(char a[3][3], char ch, int box);
这里 `a[3][3]` 用于表示格子,`ch` 是字符 ‘O' 或 ‘X',`box` 是要输入值的格子。那么我们怎么知道要将什么字符放入格子里呢?嗯,这个函数是由上面提到的 `navigate` 函数调用的。所以如果 `navigate` 函数像这样调用:`box = navigate(a[3][3],3,0,ENTER);`,那么它意味着玩家1(这里玩家1-0,玩家2用2表示)需要进入格子3。`putintobox` 函数检查格子是否已被占用,并将值输入到表示格子的数组(`a[3][3]`)中,并调用另一个函数 `showbox(char ch, int box)` 将字符显示在指定格子中的屏幕上。
`checkforwin` 检查玩家是否赢了游戏,而 `boxesleft` 将检查是否所有格子都已填满。我们需要另一个变量来检查玩家是否想退出游戏——`int quit;`。
为了与用户交互,会显示许多消息。玩家还会被告知他是否赢了游戏,或者是否是平局。程序还会询问玩家是否想再次玩。所以,在我们的程序中,消息将是
这个程序的逻辑是运行一个while循环,直到一个玩家获胜,或者所有格子都填满了但没有人赢了游戏,或者用户想退出。当循环运行时,跟踪谁有权输入移动的变量`chance`会被更新。一个函数将检查用户按下了什么键(用户只能输入上、下、左、右或回车键),并将光标移动到指定的格子,并将分配给玩家的字符输入到数组中,并将其显示在屏幕上。它还确保没有格子被覆盖。如果用户试图覆盖格子,机会将作为对输入错误移动的玩家的惩罚传递给另一位玩家。在程序结束时,会询问用户是否想再次玩游戏。
- 初始要求
- 开发界面
- 询问玩家姓名。
- 显示轮到谁输入移动。
- 显示玩家获胜或平局。
- 当玩家想退出时显示消息。
- 显示一条消息询问玩家是否想再次玩。
- 开发游戏玩法逻辑
以下是我们需要的函数列表
void showframe(int posx, int posy)
- 此函数将在指定位置显示井字棋的框架。void showbox(int ch, int box)
- 此函数将在指定格子里显示指定的字符。void putintobox(char a[3][3], char ch, int box)
- 此函数用于将字符写入数组,并将调用 `showbox(ch,box)`。void gotobox(int box)
- 此函数会将光标移动到指定的格子里。int navigate(char a[3][3], int box, int player, int key)
- 此函数用于跟踪用户在按下键盘上的箭头键时所处的格子编号。它还将获取用户想要输入为其分配的字符的格子编号。int checkforwin(char a[3][3])
- 检查玩家是否获胜。int boxesleft(char a[3][3])
- 检查剩余的格子数量。
函数细节:void showframe(int posx, int posy)
//Function to show the Tic Tac Toe Frame
void showframe(int posx, int posy)
{
int hr=196, vr=179; // These are ascii character which display the lines
int crossbr=197; // Another ascii character
int x=posx, y=posy;
int i,j;
gotoxy(35,4); cprintf("TIC TAC TOE");
gotoxy(35,5); for(i=0;i<11;i++) cprintf("%c",223);
for(i=0;i<2;i++)
{
for(j=1;j<=11;j++)
{
gotoxy(x,y);
printf("%c",hr);
x++;p; x++;
}
x=posx; y+=2;
}
x=posx+3; y=posy-1;
for(i=0;i<2;i++)
{
for(j=1;j<=5;j++)
{
gotoxy(x,y);
printf("%c",vr);
y++;
}
x+=4;y=posy-1;
}
x=posx+3; y=posy;
gotoxy(x,y);
printf("%c",crossbr);
x=posx+7; y=posy;
gotoxy(x,y);
printf("%c",crossbr);
x=posx+3; y=posy+2;
gotoxy(x,y);
printf("%c",crossbr);
x=posx+7; y=posy+2;
gotoxy(x,y);
printf("%c",crossbr);
}
void showbox(char ch, int box)
//Function to show the character in the specified box
void showbox(char ch, int box)
{
switch(box)
{
case 1 : gotoxy(_x+1,_y-1); printf("%c",ch); break; //1st box
case 2 : gotoxy(_x+5,_y-1); printf("%c",ch); break; //2nd box
case 3 : gotoxy(_x+9,_y-1); printf("%c",ch); break; //3rd box
case 4 : gotoxy(_x+1,_y+1); printf("%c",ch); break; //4th box
case 5 : gotoxy(_x+5,_y+1); printf("%c",ch); break; //5th box
case 6 : gotoxy(_x+9,_y+1); printf("%c",ch); break; //6th box
case 7 : gotoxy(_x+1,_y+3); printf("%c",ch); break; //7th box
case 8 : gotoxy(_x+5,_y+3); printf("%c",ch); break; //8th box
case 9 : gotoxy(_x+9,_y+3); printf("%c",ch); break; //9th box
}
}
void putintobox(char a[3][3], char ch, int box)
//Function to insert the specified character into the array
void putintobox(char arr[3][3], char ch, int box)
{
switch(box)
{
case 1 : if(arr[0][0] != 'X' && arr[0][0]!= 'O')
{ arr[0][0] = ch;
showbox(ch,1);
}
break;
case 2 : if(arr[0][1] != 'X' && arr[0][1]!= 'O')
{ arr[0][1] = ch;
showbox(ch,2);
}
break;
case 3 : if(arr[0][2] != 'X' && arr[0][2]!= 'O')
{ arr[0][2] = ch;
showbox(ch,3);
}
break;
case 4 : if(arr[1][0] != 'X' && arr[1][0]!= 'O')
{ arr[1][0] = ch;
showbox(ch,4);
}
break;
case 5 : if(arr[1][1] != 'X' && arr[1][1]!= 'O')
{ arr[1][1] = ch;
showbox(ch,5);
}
break;
case 6 : if(arr[1][2] != 'X' && arr[1][2]!= 'O')
{ arr[1][2] = ch;
showbox(ch,6);
}
break;
case 7 : if(arr[2][0] != 'X' && arr[2][0]!= 'O')
{ arr[2][0] = ch;
showbox(ch,7);
}
break;
case 8 : if(arr[2][1] != 'X' && arr[2][1]!= 'O')
{ arr[2][1] = ch;
showbox(ch,8);
}
break;
case 9 : if(arr[2][2] != 'X' && arr[2][2]!= 'O')
{ arr[2][2] = ch;
showbox(ch,9);
}
break;
}//end of switch
}
void gotobox(int box)
//Function to show the curson on the box specified
//uses the position to check the coordinates
void gotobox(int box)
{
switch(box)
{
case 1 : gotoxy(_x+1,_y-1); break;
case 2 : gotoxy(_x+5,_y-1); break;
case 3 : gotoxy(_x+9,_y-1); break;
case 4 : gotoxy(_x+1,_y+1); break;
case 5 : gotoxy(_x+5,_y+1); break; //5th box
case 6 : gotoxy(_x+9,_y+1); break; //6th box
case 7 : gotoxy(_x+1,_y+3); break; //7th box
case 8 : gotoxy(_x+5,_y+3); break; //8th box
case 9 : gotoxy(_x+9,_y+3); break;
}
}
int navigate(char a[3][3], int box, int player, int key)
//Function to handle the navigation
int navigate(char arr[3][3], int box, int player, int key)
{
switch(key)
{
case UPARROW : if( (box!=1) || (box!=2) || (box!=3) )
{ box-=3; if(box<1) box = 1; gotobox(box); }
break;
case DOWNARROW : if( (box!=7) || (box!=8) || (box!=9) )
{ box+=3; if(box>9) box = 9; gotobox(box); }
break;
case LEFTARROW : if( (box!=1) || (box!=4) || (box!=7) )
{ box--; if(box<1) box = 1; gotobox(box); }
break;
case RIGHTARROW : if( (box!=3) || (box!=6) || (box!=9) )
{ box++; if(box>9) box = 9; gotobox(box); }
break;
case ENTER : if(player == 0)
putintobox(arr,'O',box);
else if(player == 1)
putintobox(arr,'X',box);
break;
}//end of switch(key)
return box;
}
int checkforwin(char a[3][3])
int checkforwin(char arr[3][3])
{
int w=0;
/* 0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2
*/
//rows
if((arr[0][0] == arr[0][1]) && (arr[0][1] == arr[0][2])) w = 1;
else if((arr[1][0] == arr[1][1]) && (arr[1][1] == arr[1][2])) w = 1;
else if((arr[2][0] == arr[2][1]) && (arr[2][1] == arr[2][2])) w = 1;
//coloums
else if((arr[0][0] == arr[1][0]) && (arr[1][0] == arr[2][0])) w = 1;
else if((arr[0][1] == arr[1][1]) && (arr[1][1] == arr[2][1])) w = 1;
else if((arr[0][2] == arr[1][2]) && (arr[1][2] == arr[2][2])) w = 1;
//diagonal
else if((arr[0][0] == arr[1][1]) && (arr[1][1] == arr[2][2])) w = 1;
else if((arr[0][2] == arr[1][1]) && (arr[1][1] == arr[2][0])) w = 1;
return w;
}
int boxesleft(char a[3][3])
int boxesleft(char a[3][3])
{
int i,j,boxesleft=9;
for(i=0;i<3;i++)
{ for(j=0;j<3;j++)
{ if((a[i][j] == 'X') ||(a[i][j] == 'O'))
boxesleft--;
}
}
return boxesleft;
}
现在我们有了所有的函数,我们继续第三步,讨论演示。
我们其实已经在上面写的一些函数中处理了演示,不是吗?
此程序中用于显示垂直线的ASCII字符是179,用于显示水平线的ASCII字符是196。用于十字的是197。有关扩展ASCII字符的更多信息,请访问此网站:http://www.asciitable.com/。
您甚至可以使用C程序打印出您的ASCII表。这里有一个例子
#include <stdio.h>
int main()
{
FILE *fh;
int ch;
fh = fopen("ascii.txt","r");
for(i=0;i<256;i++)
fprint(fh,"\n%d - %c",i,i);
fclose(fh);
return 0;
}
现在我们将讨论异常处理。在这个程序中,我们将其保持简单,因此没有什么太多需要担心的,但是,我们确实会从用户那里获取姓名。我们用于存储姓名的数组的大小为30。如果用户输入的字符串长度超过30怎么办?这会导致缓冲区溢出。这可能会立即导致程序崩溃或产生意外结果。为了避免这种情况,我们可以编写一个函数,它一次从stdin
读取一个字符串,并在按下Enter键或字符串长度超过30时停止,或者我们可以使用名为fgets
的内置函数。
最后,将所有内容整合在一起
#include <stdio.h>
#include <conio.h>
int main()
{
/*
Declaration of variables used
*/
showframe(12,25);
printf("\nPlayer 1, enter your name:"); fgets(name[0], 30, stdin);
printf("\nPlayer 2, enter your name:"); fgets(name[1], 30, stdin);
printf("\n%s, you take 0",name[0]);
printf("\n%s, you take X",name[1]); getch();
clrscr();
do
{
while(!enter)
{
if(khbit())
ch = getch();
switch(ch)
{
case UPARROW : box = navigate(a[3][3], box, player, UPARROW);
.
.
.
}
}
if(quit) break;
//check if the player wins
win = checkforwin(a);
}while(!win)
if(win)
{ .
.
}
else if(quit)
{ .
.
}
return 0;
}
查看完整的源代码和可执行文件,了解程序的工作原理。
以下是井字棋工作可执行程序的一些截图
本文不是一篇完整、成熟的游戏编程文章,但我希望您能从中有所收获。如果您有任何问题,请随时通过shine_hack@yahoo.com 发送电子邮件给我。
本程序的源代码可在以下网址获取:http://techstuff.x10.mx/articles/。
编程愉快!
Shine Jacob (Enot): shine_hack@yahoo.com