65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (33投票s)

2012年8月28日

CPOL

12分钟阅读

viewsIcon

846235

downloadIcon

14020

使用C/C++进行游戏编程的简要介绍。

游戏编程

在我们真正开始游戏编程之前,我们需要了解一种称为事件驱动编程的编程方式。事件驱动编程是指这样一种编程风格:应用程序的用户可以从多个选项中自由选择,而不是被限制在预设的交互序列中。游戏编程是事件驱动编程的一个常见例子。游戏是一个封闭的、即完整的、自给自足的正式系统,它代表了现实世界的一个子集。游戏是动作-反应或事件-响应的完美结合,每一次响应都基于最近发生的事件。

游戏编程的要素

一般来说,电脑游戏有五个要素

  • 图形
  • 声音
  • 接口
  • 游戏玩法
  • 故事

图形

图形包括显示的任何图像以及对图像执行的任何效果。这包括3D对象、纹理、2D图块、2D全屏截图、全动态视频(FMV)以及玩家将看到的其他任何东西。

声音

声音包括游戏中播放的任何音乐或音效。这包括开始音乐、CD音乐、MIDI或MOD音轨、环境音效(环境声音)和音效。

接口

声音包括游戏中播放的任何音乐或音效。这包括开始音乐、CD音乐、MIDI或MOD音轨、环境音效(环境声音)和音效。

游戏玩法

它涵盖了游戏的趣味性、庞大程度以及可玩性时长。

故事

游戏的故事包括游戏开始前的任何背景信息,玩家在游戏过程中或获胜时获得的所有信息,以及玩家从游戏中了解到的关于角色的一切。故事是游戏的一个要素。故事和游戏之间的区别在于,故事以不变(即固定)的序列代表事实,而游戏代表一个分支的序列树,并允许玩家通过在每个分支点做出选择来创造自己的故事。

虽然图形在游戏编程中扮演着重要的角色,但在这篇文章中,我们不会强调游戏的图形和声音元素。我们将专注于通过基于文本界面的基础游戏编程。

游戏设计流程

由于游戏设计需要探索个人的艺术能力,因此无法以一步一步的流程来制定。然而,在某些技术步骤上,一个人或多或少需要遵循。
这些是:

  1. 确定初始需求。
  2. 开发界面。
  3. 开发界面。
  4. 开发得分逻辑。

我们将详细探讨其中每一个。

界面是游戏编程的另一个非常重要的方面。界面是计算机和玩家之间的沟通方式。就像任何人类语言一样,它是程序员必须将他/她试图与玩家分享的雪崩般想法、构思和情感过滤进去的渠道。界面将决定什么可以做,什么不能做。界面由输入和输出组成。在开发界面时,程序员应该开发静态显示屏幕动态显示屏幕静态显示是指不受玩家操作(即玩家的输入)影响的屏幕。而动态显示则是由玩家操作(即玩家的输入)控制的屏幕。

一些静态显示屏的例子是

游戏选择屏幕

游戏启动时玩家有哪些可用的选项?这描述了菜单上有哪些选项,它们如何以及在哪里出现在哪个屏幕上,玩家如何进入,以及如何退出。

游戏开始屏幕

游戏开始时屏幕是什么样的,启动参数是什么,角色在哪里等等?屏幕上有什么消息,在哪里?开场音乐?等等。

由于动态屏幕会根据玩家的输入而变化,它们的描述太多,无法在此一一列出。一些例子

消息屏幕

在开发界面时,您还需要处理响应玩家合法操作的屏幕,以告知他/她正在正确的轨道上。此外,在玩家犯下非法移动或操作时,您还需要处理用于警告玩家的屏幕。

游戏结束消息

这些屏幕包括消息和对诸如:玩家输了会怎样?玩家赢了会怎样?玩家获得高分会怎样?游戏结束后玩家去哪里?他/她如何开始新游戏?等问题的回答。

这一步涉及开发一个合适的玩法逻辑。这要求游戏程序员以程序代码的形式回答许多问题。这些问题包括:游戏如何玩?控制是什么?游戏目标是什么?玩家将如何实现游戏目标?等等。换句话说,我们必须说,由于游戏代表了一种事件驱动的情况,游戏程序员,也就是你,必须指定或编程包括

  1. 确定初始需求

    在编写游戏程序时,在选择游戏目标后,需要确定其初始需求。例如,要编写一个猜数字的游戏程序,你需要决定如何生成数字,涉及的玩家数量,允许玩家的猜测次数,评分方法等。在这里,我们的目标不是让你成为一名专业游戏程序员,而是更侧重于让你了解编写简单或基础游戏程序的想法。

    游戏总体描述: 游戏的总体描述包括游戏的概览、它的工作原理、每个级别的流程等。它从玩家的角度描述了游戏的所有部分

    • 他/她开始玩之前应该知道什么。
    • 他/她看到了什么。
    • 他/她做了什么。
    • 他/她对所见所为的预期反应。
  2. 开发界面
  3. 开发游戏玩法逻辑
    • 对用户/玩家操作的响应。
    • 对系统事件的响应。
    • 游戏规则。
    • 如果是双人游戏(电脑作为玩家),则电脑的移动和操作。
  4. 开发计分逻辑

    开发计分逻辑是开发游戏玩法逻辑的一个子集。为此,您必须首先确定您将在游戏中遵循的得分策略。您将决定允许的最大猜测次数,计分机制,是否与时间挂钩等。在此阶段,将处理里程碑事件,并相应地进行(正面或负面)得分。

    游戏中的里程碑事件

    玩家每隔一段时间都需要因为到达游戏中的某个点而得到奖励(或惩罚)。每个发生特殊情况的地方都称为里程碑事件。有一个仪表可以告知玩家他/她是否走在正确的(或错误的)方向上,并鼓励(或不鼓励)他/她继续前进。

现在我们已经讨论了游戏开发的这些不同阶段,让我们来开发一个简单的井字棋游戏。

游戏总体描述

  1. 这是一个双人游戏,所以程序会获取两个玩家的名字,并为他们分配O和X。
  2. 玩家轮流将他们的移动输入到他们选择的格子里。
  3. 程序需要确保没有格子被覆盖。
  4. 如果玩家试图输入他/她的移动到另一个玩家已经占用的格子里,那么机会就传给了另一个玩家。
  5. 程序需要一直运行,直到一个玩家获胜、玩家想退出游戏,或者没有剩余的移动。
  6. 玩家获胜后,程序会显示消息,并询问玩家是否想再次玩。

现在让我们来分析一下我们即将制作的游戏设计中的不同元素。

这是一个双人游戏,所以我们需要两个变量来存储他们的名字,并运行一个循环来依次要求玩家输入他们的移动。所以我们需要另一个变量来存储回合,以确定哪个玩家应该输入移动。以下是变量

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`会被更新。一个函数将检查用户按下了什么键(用户只能输入上、下、左、右或回车键),并将光标移动到指定的格子,并将分配给玩家的字符输入到数组中,并将其显示在屏幕上。它还确保没有格子被覆盖。如果用户试图覆盖格子,机会将作为对输入错误移动的玩家的惩罚传递给另一位玩家。在程序结束时,会询问用户是否想再次玩游戏。

  1. 初始要求
  2. 开发界面
    • 询问玩家姓名。
    • 显示轮到谁输入移动。
    • 显示玩家获胜或平局。
    • 当玩家想退出时显示消息。
    • 显示一条消息询问玩家是否想再次玩。
  3. 开发游戏玩法逻辑

以下是我们需要的函数列表

  • 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

© . All rights reserved.