[导读]本系列博文内容链接如下:
【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值
【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
在【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值一文中介绍了如何利用getch()获得键盘码和各个键盘符号的码值。
今天继续介绍,利用wsad键和方向键两种方式,实现控制单个字符的移动。
目录
一、第一个小坑
二、通过wsad按键控制单个字符的移动
(一)字符移动的原理
1、左右移动
2、上下移动
(二)清空屏幕函数和头文件
(三)程序代码
1、清屏
2、再调用location()重新显示字符
3、检测按键码值,计算字符坐标值
三、通过方向按键控制单个字符的移动
(一)双码按键获取码值的方法
(二)实现代码
四、第二个小坑
一、第一个小坑
写程序要循序渐进,先实现最基本的功能,再不断深化。所以,要先编写一个简单程序测试一下getch()函数,在键盘上按下代表不同方向的按键,然后根据不同的按键,在屏幕上输出相应的方向信息,模拟控制字符移动的方向。程序的具体目的是:当按'w'键,屏幕输出"up";当按's'键,屏幕输出"down";当按'a'键,屏幕输出"left";当输入'd'键,屏幕输出"right"。
根据以上想法,编写程序代码如下:
#include <iostream>
#include "conio.h"
using namespace std;int main()
{while(1) //循环等待输入字符 {if(getch()==119) //如果输入字符'w' {cout<<"up"<<endl; //输出字符串"up" }else if(getch()==115) //如果输入字符's' {cout<<"down"<<endl; //输出字符串"down"}else if(getch()==97) //如果输入字符'a' {cout<<"left"<<endl; //输出字符串"left"}else if(getch()==100) //如果输入字符'd' {cout<<"right"<<endl;//输出字符串"right" }else //如果输入其他字符{; //无响应 }}return 0;
}
先简单测试一下基本的功能,结果发现一些问题:当每输入一次字符'w'时,屏幕显示一次"up",这个按键是正常的。
但是当测试其他三个字符时,却出现了异常:输入字符's'时,需要按两次键盘才显示一次"down";需要输入3次'a',才显示一次"left",需要输入4次'd',才显示一次"right"。
up //按1次'w'
down //按2次's'
left //按3次'a'
right //按4次'd'
本程序并没有完全实现按一次键盘就执行一次相应动作的目的。那么问题出在哪里了呢?
经过分析发现,程序当中if语句的前4个分支,其判定条件都是直接调用了getch()函数,而if语句执行的顺序是从第一个开始依次判定,当找到判定条件为真的那一个if分支时才跳出整个if语句。那么也就是说,无论按下了哪个按键,程序都需要从第一个if语句分支开始依次进行检测和判断,每一个分支都需要调用一次getch(),而每次调用getch()都需要有按键按下才能触发。所以,在第一个分支中的字符'a',按一次就能触发,而在第二个分支中的字符's',需要按2次才能触发,在第三个分支中的字符'a'需要按3次触发,在第四个分支的'd'需要按4次。
问题分析清楚了,那么怎么解决呢?解决的方案就是另外声明一个变量"key_value",用来存放每次按下按键利用getch()的值,然后在4个if分支中判断key_value的值是否与4个字符的码值相等。调整后的程序如下图所示。
#include <iostream>
#include "conio.h"
using namespace std;int main()
{int key_value; //声明存放按键码值的变量 while(1) //循环等待输入字符 {key_value=getch(); //获取按键码值 if(key_value==119) //如果输入字符'w' {cout<<"up"<<endl; //输出字符串"up" }else if(key_value==115) //如果输入字符's' {cout<<"down"<<endl; //输出字符串"down"}else if(key_value==97) //如果输入字符'a' {cout<<"left"<<endl; //输出字符串"left"}else if(key_value==100) //如果输入字符'd' {cout<<"right"<<endl;//输出字符串"right" }else //如果输入其他字符{; //无响应 }}return 0;
}
再对以上程序进行测试,发现运行正常了,4个字符都只需按1次即可输出相应字段了。
up //按1次'w'
down //按1次's'
left //按1次'a'
right //按1次'd'
二、通过wsad按键控制单个字符的移动
测试完按键控制的基本程序后,就需要继续编写程序,实现通过按键来控制图标进行移动的目标了,我们还是由最简单的情形开始——图标只由一个特殊字符'■'组成。
(一)字符移动的原理
字符移动的原理就是当按键按下后,根据按键的码值,分辨出要移动的意图,计算出字符的新位置(用行号和列号的坐标对来表示位置),然后将屏幕清空,再在新位置上重新显示字符。这样就完成了图标的移动。
1、左右移动
左右移动是通过在字符前增加或者减少空格的数量来实现的。增加空格数量,则字符向右移动,减少空格的数量则向左移动。
2、上下移动
上下移动是通过增加或减少字符所在行的上一行的换行符数量来实现的,增加换行符数量则字符向下移动,减少换行符的数量则字符向上移动。
(二)清空屏幕函数和头文件
清屏函数:system("cls")
所需头文件:windows.h
(三)程序代码
程序代码预先自定义了字符定位显示函数location(),函数有两个参量x,y,代表字符列号和行号。在主函数while(1)中,循环进行以下操作:
1、清屏
2、再调用location()重新显示字符
3、检测按键码值,计算字符坐标值
本例通过单码按键来进行控制字符移动。检测是否有按键按下,如果'w'键按下,y值减1;如果's'键按下,y值加1;如果'a'键按下,x值减1;如果'd'键按下,x值加1。
完整代码如下所示。
#include <iostream>
#include "conio.h"
using namespace std;void location(int x,int y) //预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角
{int i,j;for(j=0;j<y;j++) //输出y个换行符{cout<<endl;}for(i=0;i<x;i++) //输出x个空格 {cout<<" ";}cout<<"■"; //输出要显示的字符
} int main()
{int key_value; //声明存放按键码值的变量 int x=0,y=0; //声明字符坐标,x代表列值,y代表行值 while(1) //循环等待输入字符 {system("cls"); //清屏location(x,y); //重新打印字符 key_value=getch(); //获取按键码值 if(key_value==119) //如果输入字符'w' {y--; //字符上移一行,行值y减1if(y<0) //限定y值最小值为0{y=0;}}else if(key_value==115) //如果输入字符's' {y++; //字符下移一行,行值y加1if(y>30) //限定y最大值为30{y=30;}}else if(key_value==97) //如果输入字符'a' {x--; //字符左移一列,列值x减1if(x<0){x=0; //限定x最小值为0}}else if(key_value==100) //如果输入字符'd' {x++; //字符右移一列,列值x加1if(x>60){x=60; //限定x最大值为60}}else //如果输入其他字符{; //无响应 }}return 0;
}
三、通过方向按键控制单个字符的移动
以上介绍了使用四个单码按键'w'、's'、'a'、'd'来控制字符移动,下边再介绍一下,如何利用双码按键↑、↓、←、→来进行控制。
(一)双码按键获取码值的方法
双码按键和单码按键的控制方法只再获取键码的时候有一点区别,其他地方完全一样。双码按键的码值有两部分,需要调用两次getch()函数来分别获取。
单码按键获取码值的代码:
key_value=getch(); //获取按键码值
双码按键获取码值的代码:
key_value1=getch(); //获取按键码值1
key_value2=getch(); //获取按键码值2
(二)实现代码
#include <iostream>
#include "conio.h"
using namespace std;void location(int x,int y) //预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角
{int i,j;for(j=0;j<y;j++) //输出y个换行符{cout<<endl;}for(i=0;i<x;i++) //输出x个空格 {cout<<" ";}cout<<"■"; //输出要显示的字符
} int main()
{int key_value1,key_value2; //声明存放按键码值的两个变量 int x=0,y=0; //声明字符坐标,x代表列值,y代表行值 while(1) //循环等待输入字符 {system("cls"); //清屏location(x,y); //重新打印字符 key_value1=getch(); //获取按键码值1key_value2=getch(); //获取按键码值2 if(key_value1==224 && key_value2==72)//如果输入字符'↑' {y--; //字符上移一行,行值y减1if(y<0) //限定y值最小值为0{y=0;}}else if(key_value1==224 && key_value2==80)//如果输入字符'↓' {y++; //字符下移一行,行值y加1if(y>30) //限定y最大值为30{y=30;}}else if(key_value1==224 && key_value2==75) //如果输入字符'←' {x--; //字符左移一列,列值x减1if(x<0){x=0; //限定x最小值为0}}else if(key_value1==224 && key_value2==77) //如果输入字符'→' {x++; //字符右移一列,列值x加1if(x>60){x=60; //限定x最大值为60}}else //如果输入其他字符{; //无响应 }}return 0;
}
四、第二个小坑
以上两种控制字符移动的代码经测试,对于单码按键程序来说,如果不小心按了其他单码按键或者双码按键,对控制是没有影响的。可是对于双码按键程序来说,如果不小心按了一次单码按键,再按双码按键进行控制,发现无论按哪个控制键,无论按多少次,图标都不再移动了。
这是因为,假如按了一次单码键,key_value1获得了码值,但是key_value2没有获得码值,再次按双码键时,会把第一个码值赋值给了上一个key_value2,第二个码值赋值给了当前的key_value1,而当前的key_value2又未重新赋值,还保持原来的值不变。这就相当于每次按下双码键,获得的key_value1和key_value正好是相反的。因此控制程序才失效了。
也就是如果游戏再做得复杂些,要同时用到单码按键和双码按键,程序就会出问题,所以程序必须区分出是单码键按下还是双码键按下,然后确定出是哪个命令键按下了。
经优化调整后的最终程序如下所示:
#include <iostream>
#include "conio.h"
using namespace std;void location(int x,int y) //预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角
{int i,j;for(j=0;j<y;j++) //输出y个换行符{cout<<endl;}for(i=0;i<x;i++) //输出x个空格 {cout<<" ";}cout<<"■"; //输出要显示的字符 } int main()
{int key_value1,key_value2; //声明存放按键码值的两个变量bool up_btn_press=false,down_btn_press=false,left_btn_press=false,right_btn_press=false;//四个方向键是否按下标志,按下为true,未按下为false int x=0,y=0; //声明字符坐标,x代表列值,y代表行值 while(1) //循环等待输入字符 {system("cls"); //清屏location(x,y); //重新打印字符 key_value1=getch(); //获取按键码值1if(key_value1==224){key_value2=getch();switch(key_value2){case 72:up_btn_press=true; //置↑按下 标志 break;case 80:down_btn_press=true;//置↓按下 标志break;case 75:left_btn_press=true;//置←按下 标志break;case 77:right_btn_press=true;//置→按下 标志break;}} if(up_btn_press)//如果输入字符'↑' {y--; //字符上移一行,行值y减1if(y<0) //限定y值最小值为0{y=0;}up_btn_press=false;}else if(down_btn_press)//如果输入字符'↓' {y++; //字符下移一行,行值y加1if(y>30) //限定y最大值为30{y=30;}down_btn_press=false;}else if(left_btn_press) //如果输入字符'←' {x--; //字符左移一列,列值x减1if(x<0){x=0; //限定x最小值为0}left_btn_press=false;}else if(right_btn_press) //如果输入字符'→' {x++; //字符右移一列,列值x加1if(x>60){x=60; //限定x最大值为60}right_btn_press=false;}else //如果输入其他字符{; //无响应 }}return 0;
}