当前位置: 首页 > news >正文

【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动

  [导读]本系列博文内容链接如下:

【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; 
}

相关文章:

【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动 在【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值一文中介绍了如何利用…...

Android 设备兼容性使用(详细版)

经典好文推荐,通过阅读本文,您将收获以下知识点: 一、设备兼容性分类 二、硬件设备兼容 三、软件 APP 兼容 四、兼容不同语言 五、兼容不同分辨率 六、兼容不同屏幕方向布局 七、兼容不同硬件 Feature 八、兼容不同SDK平台 一、设备兼容性分类 Android设计用于运行在许多不同…...

React 中的常见 API 和生命周期函数

目录 useStateuseEffectuseRefdangerouslySetInnerHTML生命周期函数 constructorcomponentDidMountstatic getDerivedStateFromPropsshouldComponentUpdatecomponentDidUpdatecomponentWillUnmount useState useState 是 React 的一个 Hook&#xff0c;用于在函数组件中添加…...

神经网络中遇到的 python 函数(Pytorch)

1.getattr() 函数用于返回一个对象属性值。 def getattr(object, name, defaultNone): # known special case of getattr"""getattr(object, name[, default]) -> valueGet a named attribute from an object; getattr(x, y) is equivalent to x.y.When a …...

分布式事务及解决方案

1、分布式事务 分布式事务就是在一个交易中各个服务之间的相互调用必须要同时成功或者同时失败&#xff0c;保持一致性和可靠性。在单体项目架构中&#xff0c;在多数据源的情况下也会发生 分布式事务问题。本质上来说&#xff0c;分布式事务就是为了保证不同数据库的数据一致性…...

【宏定义】——编译时校验

文章目录 编译时校验功能描述代码实现示例代码正常编译示例编译错误示例预处理之后的结果 代码解析!!estruct {int:-!!(e); }sizeof(struct {int:-!!(e); }) 参考代码 编译时校验 功能描述 用于在编译时检查一个条件是否为真&#xff0c;如果条件为真则会编译失败&#xff0c…...

C#学习系列之System.Windows.Data Error: 40报错

C#学习系列之System.Windows.Data Error: 40报错 前言报错内容解决总结 前言 在用户界面使用上&#xff0c;代码运行没有问题&#xff0c;但是后台报错&#xff0c;仔细研究了报错内容&#xff0c;解决问题&#xff0c;所以记录一下。 报错内容 System.Windows.Data Error: 4…...

【java安全】RMI

文章目录 【java安全】RMI前言RMI的组成RMI实现Server0x01 编写一个远程接口0x02 实现该远程接口0x03 Registry注册远程对象 Client 小疑问RMI攻击 【java安全】RMI 前言 RMI全称为&#xff1a;Remote Method Invocation 远程方法调用&#xff0c;是java独立的一种机制。 RM…...

rcu链表综合实践

基础知识 rcu-read copy update的缩写。和读写锁起到相同的效果。据说牛逼一点。对于我们普通程序员&#xff0c;要先学会使用&#xff0c;再探究其内部原理。 链表的数据结构&#xff1a; struct list_head {struct list_head *next, *prev; };还有一种&#xff1a;struct h…...

odoo16-python框架-动作

总结 1 模型和视图的 设计之美 view_ids, view_id,view_mode 最终目的都是为了生成views, 也就是视图. 模型是死的,像男人,一成不变 视图像女人,千变万化, 姿态万千 一阴一阳之谓道,设计之美又在这里得到了体现 2 所有的动作都可以通过web界面来配置 可以通过在"设…...

微信小程序——同一控件的点击与长按事件共存的解决方案

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

selenium自动化-获取元素属性信息

在写自动化过程中我们会想验证自己的代码是否正确&#xff0c;比如登录之后&#xff0c;通过用户名或其他信息来证明你登录成功&#xff0c;或者点击链接后&#xff0c;是否会跳转新的页面。通过获取元素属性信息&#xff0c;可以解决我们的疑惑。 一、获取内容对象的内容信息 …...

LabVIEW开发小型减阻试验平台

LabVIEW开发小型减阻试验平台 湍流摩擦在粘性流体的阻力中起着重要作用&#xff0c;减少湍流摩擦是流体力学领域的热门话题之一。在油气管道的长距离流体输送中&#xff0c;泵站提供的几乎所有动力都用于克服流体的胫骨摩擦。在流体输送领域&#xff0c;船舶的蒙皮摩擦阻力占总…...

解决分类任务中数据倾斜问题

大家好&#xff0c;在处理文本分类任务时&#xff0c;基准测试流行的自然语言处理架构的性能是建立对可用选项的理解的重要步骤。在这里&#xff0c;本文将深入探讨与分类相关的最常见的挑战之一——数据倾斜。如果你曾经将机器学习&#xff08;ML&#xff09;应用于真实世界的…...

Vue3 word如何转成pdf代码实现

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;word如何转换pdf 目录 1.前端部分 2.后端部分 在Vue 3中&#xff0c;前端无法直接将Word文档转换为PDF&#xff0c;因为Word文档的解析和PDF的生成通常需要在后端进行。但是&#xff0c;你可以通过Vu…...

fpga--流水灯

fpga流水灯的设计 思路&#xff1a;外部时钟频率50mhz&#xff0c;若要实现每隔0.5s闪烁一次&#xff0c;则使用内部计数器计数到24999999拉高一个周期电平&#xff0c;当电平被拉高的时候&#xff0c;进行LED灯电平的设置&#xff0c;每次检测到高电平&#xff0c;就进行一位…...

51单片机:数码管和矩阵按键

目录 一:动态数码管模块 1:介绍 2:共阴极和共阳极 A:共阴极 B:共阳极 C:转化表 3:74HC138译码器 4:74HC138译码器控制动态数码管 5:数码管显示完整代码 二:矩阵按键模块 1:介绍 2:原理图 3:矩阵按键代码 一:动态数码管模块 1:介绍 LED数码管&#xff1a;数码管是一种…...

Django + Xadmin 数据列表复选框显示为空,怎么修复这个问题?

问题描述&#xff1a; 解决方法&#xff1a; 后续发现的报错&#xff1a; 解决方案&#xff1a; 先根据报错信息定位到源代码&#xff1a; 在该文件顶部写入&#xff1a; from django.core import exceptions然后把&#xff1a; except models.FieldDoesNotExist修改为&…...

《向量数据库指南》——Milvus Cloud2.2.12 易用性,可视化,自动化大幅提升

Milvus Cloud又迎版本升级,三大新特性全力加持,易用性再上新台阶! 近期,Milvus Cloud上线了 2.2.12 版本,此次更新不仅一次性增加了支持 Restful API、召回原始向量、json_contains 函数这三大特性,还优化了 standalone 模式下的 CPU 使用、查询链路等性能,用一句话总…...

Python web实战 | 用 Flask 框架快速构建 Web 应用【实战】

概要 Python web 开发已经有了相当长的历史&#xff0c;从最早的 CGI 脚本到现在的全栈 Web 框架&#xff0c;现在已经成为了一种非常流行的方式。 Python 最早被用于 Web 开发是在 1995 年&#xff08;90年代早期&#xff09;&#xff0c;当时使用 CGI 脚本编写动态 Web 页面…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...