此文本来想投洛谷日报结果被毙掉了,本文仅有纪念意义
注:以下所有代码均以 Windows
下 Dev-C++ 5.11
运行为准,P党请跳过。
用 Dev-C++
运行本文代码时,需要包含以下头文件:
#include
#include
#include
阅读这篇文章,您只需要学完一本通中的 C++
语言即可。
\[\large\texttt{前 言}
\]
很多 OIer 都想用 C++
写一个游戏,但是仅仅凭 \(\texttt{NOI}\) 竞赛的内容是很难做到的,因为这需要掌握 C++
更多函数的功能。
于是,蒟蒻写了三篇文章:基础篇、进阶篇和实践篇。这是基础篇。(后面两篇都没写好)
蒟蒻以前做过一些小游戏,那么在基础篇给出一些写游戏比较使用的语句,在以后的实践中可以经常用到。
\[\large\texttt{基础篇 —— 目录}
\]
\(\texttt{1}_{\texttt{st}} \to\;\) system()
函数
\(\texttt{2}_{\texttt{nd}} \to\;\) 光标基本操作 · 移动光标
\(\texttt{3}_{\texttt{rd}} \to\;\) 控制颜色
\(\texttt{4}_{\texttt{th}} \to\;\) 使用键盘
\(\texttt{5}_{\texttt{th}} \to\;\) 随机函数
\(\texttt{E}_{\texttt{ed}} \to\;\) 附录/后记
system()
函数\(\large\text{1. 参数 "pause"}\)
在刚开始接触 C++
编程时,肯定都见过这条语句:
system("pause"); //检测到任意键按下后继续
都了解,这个语句可以在屏幕上打印 请按任意键继续
,接着等待你在键盘上按下一个键程序才会继续执行。这条语句当时是用于防止控制台运行完之后直接退出而看不到输出结果。
其实 system()
是一个标准函数,参数是一个字符串,效果等同于你 cmd
中输入这个字符串之后的结果。
运行结果:
\(\large\text{2. 参数 "shutdown"}\)
在你的好朋友远离电脑时,你可能在 \(\texttt{TA}\) 的电脑上还用过如下语句:
system("shutdown -s -t 1145"); //1145秒之后关机
运行效果:
然后在关机前可以用如下语句取消关机:
system("shutdown -a"); //取消 windows 的注销指令
运行效果:
是的,使用参数 shutdown -s -t XX
可以让你的电脑在 XX
秒后关机,切记不要在别人的电脑上使用,否则你可能遭到物理攻击。
参数 shutdown -a
可以在关机前取消关机命令。
这就是 shutdown
指令两个常用的参数。
题外话:\(\texttt{Q: }\)这和游戏有什么关系?\(\texttt{A: }\) 可以在游戏中坑人,请不要乱用,容易被打。
\(\large\text{3. 参数 "mode con"}\)
在很多 C++
游戏制作中,需要调整窗口大小以便玩家游玩,那么怎么调整窗口的大小呢?system()
函数有一个参数是 mode con
,可以做到这一点。
例如,这条语句可以把控制台窗口大小调成 \(114\times514\) 大小:
system("mode con lines=114 cols=514"); //调整窗口大小为 114*514
同时,你也可以把窗口调成 \(10\times20\) 大小:
system("mode con lines=10 cols=20"); //调整窗口大小为 10*20
要注意的是,这里的 \(10\times20\)的意义是高为 \(10\) 个换行符,宽为 \(20\) 个空格,一般换行符比空格要长一些。
两条语句的运行结果分别如下:
关于这种方式设置窗口大小,如果你设置的太大(例如 \(114514\times1919810\)),运行时会出问题,不妨自己试一试。
\(\large\text{4. 参数 "color"}\)
看这种参数的名字就知道,这种参数可以用来修改窗口的颜色(包括前景色和背景色),例如这条语句:
system("color b9"); //设置背景色为浅蓝,前景色为深蓝
运行结果如下:
那么为什么 color
后面接 b9
就是这种颜色呢?当你运行下面这条语句的时候,输出结果会告诉你答案:
system("color 1145141919810"); //不是必须这么写,color 后面的参数可以乱填,但是必须要大于两个字符
输出结果:
设置默认的控制台前景和背景颜色。
COLOR [attr]
attr 指定控制台输出的颜色属性。
颜色属性由两个十六进制数字指定 -- 第一个
对应于背景,第二个对应于前景。每个数字
可以为以下任何值:
0 = 黑色 8 = 灰色
1 = 蓝色 9 = 淡蓝色
2 = 绿色 A = 淡绿色
3 = 浅绿色 B = 淡浅绿色
4 = 红色 C = 淡红色
5 = 紫色 D = 淡紫色
6 = 黄色 E = 淡黄色
7 = 白色 F = 亮白色
如果没有给定任何参数,此命令会将颜色还原到 CMD.EXE 启动时
的颜色。这个值来自当前控制台
窗口、/T 命令行开关或 DefaultColor 注册表
值。
如果尝试使用相同的
前景和背景颜色来执行
COLOR 命令,COLOR 命令会将 ERRORLEVEL 设置为 1。
示例: "COLOR fc" 在亮白色上产生淡红色
这里尽管有些地方看不懂,但是中间的列表还是很清晰的,中间的列表表示了几种颜色的代号,还给了一个例子(最后一行),system()
参数中的所有字符均不区分大小写。
如果想要输出不同颜色的字怎么办呢?第三章会介绍。
\(\large\text{5. 参数 "cls"}\)
很简单,下面这条语句可以帮助我们清屏,这里不多讲:
system("cls");
这里先引入一个程序事例:\(\texttt{Link}\)(同步发表于 \(\texttt{UKE}\) 神贴中)
其中有一个 gotoxy()
函数,可以帮助我们跳转光标在控制台的位置。
函数是这样的(需要自己定义):
void gotoxy(int x,int y){
COORD position;
position.X=x;
position.Y=y; //设置光标坐标
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position); //移动光标坐标
}
这个函数有点长,大家想记就记下来,不想记就把这个模板搬到您的代码中去。
可以这么用这个函数:
gotoxy(11,45); //把光标设置到坐标 (11,45) 处
注意,这个 \((11,45)\) 一格的定义和 system("mode con")
里的一样,行是换行符,列是空格。\((11,45)\) 代表的是 \((\) 列 \(,\) 行 \()\),是有序数对而不是数组下标,有序数对的坐标从 \(0\) 开始。
例如写如下代码:
gotoxy(0,20);
printf("(0,20):luogu");
gotoxy(20,0);
printf("(20,0):luogu");
gotoxy(20,20);
printf("(20,20):luogu");
运行效果:
这个 gotoxy()
函数还有一个特性,可以覆盖字体。
例如如下代码:
gotoxy(20,20);
printf("114514191981011451419198101145141919810");
gotoxy(30,20);
printf("LUOGU!");
运行效果:
这运行效果是怎么来的?首先在坐标 \((20,20)\) 为起始点打印 114514191981011451419198101145141919810
,然后再跳转到坐标 \((30,20)\) 输出 LUOGU!
,然后新输出的 LUOGU!
覆盖了原先位置上的 114514
。
在上一章节的事例程序中还有一个函数 color()
,用于控制输出颜色。
这里的 color()
函数和 system("color")
不同的是,后者是改变控制台窗口的颜色设置,而前者是改变输出字体的颜色。
函数是这样的(需要自己定义):
const int blue=0,yellow=1,red=2,green=3,purple=4,white=5;//颜色常量
void color(int c){
switch(c)
{
case red:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);break; //红色原色
case green:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);break; //绿色原色
case yellow:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);break; //黄色:红色和绿色相加
case blue:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);break; //蓝色原色
case white:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);break; //白色:三原色相加
case purple:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE);break; //紫色:红色和蓝色相加
}
}
注意到,函数前面还有一行颜色常量的定义,这是为了避免 0,1,2,3,4,5
这样令人无法理解的常数出现。
可以这么使用它:
color(red); cout<<"red\n";
color(yellow); cout<<"yellow\n";
color(blue); cout<<"blue\n";
color(green); cout<<"green\n";
color(purple); cout<<"purple\n";
color(white); cout<<"white\n";
运行结果:
这里提供的 color()
函数的参数还不全面,大家可以自己尝试修改一下函数,发现更多的颜色(只要把颜色和颜色相加就可以得到新颜色)。
\(\large\text{1. getch() 函数的使用}\)
游戏中常常需要人机交互,最简便的交互方式之一就是使用键盘。(当然鼠标也可以交互,鼠标的操作将在进阶篇讲述)
我们很早就学过,读取字符最快的方法就是
A了这道题 祝你们成功(滑稽
放错了,是下面这个(
char ch=getchar();
然而,这种读取方式在游戏中并不常见,毕竟,如果是完全没接触过编程的人作为玩家,TA 肯定不知道输入字符之后需要换行。
#include
中有一个函数 getch()
,可以解决这个问题,用法和 getchar()
相同:
char ch=getch();
这种读取字符的方式有什么不同呢?
getchar()
需要换行之后才会结束读入,而 getch()
只需要按下某个键就可以结束读入。
getch()
函数读入字符时不会在窗口中显示你读入的字符,而 getchar()
会。
getch()
可以读到 Backspace
,tab
,上下左右键等各种 getchar()
不能读到的字符。(别想了,getch()
在赛场上是不能用的)
大家可以尝试运行一下下面两个不同的语句:
while(true) cout<
和
while(true) cout<
会发现二者有很大的不同。
\(\large\text{2. 检测按键是否按下}\)
声明:此函数部分引用这篇博客的内容,因为我就是在这里面学到这个操作的。
在很多刺激性游戏中,总不能等待用户输入字符才做出反应吧?所以需要一个函数,检测某个按键是否被按下。
在写函数之前,需要准备一个宏:
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
(这个宏请写在头文件下方)
然后函数这么写:
bool check(char ch) //检测某个按键是否按下,按下返回 true,否则返回 false,参数:按键表示的字符
{
if(KEY_DOWN(ch)) return true;
else return false;
}
然后尝试运行这段代码:
while(true)
{
if(check(' ')) //检测到空格按下就输出1,否则输出 0
cout<<1;
else
cout<<0;
system("cls"); //清屏
}
这就是关于一些键盘操作的语句。
很多小游戏都会生成随机数(角色随机等),C++
有一个函数可以实现:
cout<
这个函数就是 rand()
,用于返回一个随机数。
这个随机数是杂乱无章的,比如说我们想要产生一个 \([2,10]\) 的随机数怎么办呢?很简单,把 rand()
模上 \(10-2+1\) 再 \(+2\) 就行了:
cout<
也就是说,产生 \([l,r]\) 之间的随机数可以使用如下语句:
int num=rand()%(r-l+1)+l;
那么想产生负数怎么办呢?很简单,先产生一个 \([0,1]\) 之间的随机数,如果是 \(1\) 则为正,\(0\) 则为负:
int nev=rand()%2,num;
if(nev==1) num=rand();
if(nev==0) num=-rand();
想产生随机字符串怎么办呢?枚举 \(n\) 遍,每遍取二十六个字母之一即可:
for(int i=0; i
cout<
有时我们想产生一个特别大的数,而 rand()
只支持五位数,只要在程序开头加上这条语句即可:
srand(time(NULL)); //用时间作为随机种子值,有些编译器需要把 NULL 换成 0,
注意:对于某些编译器而言,每次产生随机数都需要 srand(time(NULL))
,因为某些编译器 rand()
的值不是 \(42\) 就是 \(41\)。
介绍如此多的操作,这里提供一个名字空间来组合它们,当你写游戏时可以直接复制它:
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
namespace BASE {
//控制光标位置
void gotoxy(int x,int y)
{
HANDLE hout;
COORD coord;
coord.X = x;
coord.Y = y;
hout = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hout, coord);
}
const int blue=0,yellow=1,red=2,green=3,purple=4,white=5;//颜色常量
void color(int c){ //设置颜色
switch(c)
{
case red:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);break;
case green:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);break;
case yellow:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED |FOREGROUND_GREEN);break;
case blue:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);break;
case white:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED |FOREGROUND_GREEN | FOREGROUND_BLUE);break;
case purple:SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED |FOREGROUND_BLUE);break;
}
}
//产生一个 [l,r] 范围的随机数
int random(int l,int r)
{
srand(time(NULL));
return rand()%(r-l+1)+l;
}
//检测按键是否按下
bool check(char ch) //检测某个按键是否按下,按下返回 true,否则返回 false,参数:按键表示的字符
{
if(KEY_DOWN(ch)) return true;
else return false;
}
//清屏
void Cls()
{
system("cls");
}
};
using namespace BASE;
\(\large\text{后记}\)
致谢:@Gorilla 提出改错意见。
彩蛋:C++
狼人杀游戏,可能有 Bug,凑合着玩吧。
\[\huge\texttt{The end.}
\]