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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年深圳杯数学建模A题影响城市居民身体健康的因素分析

2023年深圳杯数学建模 A题 影响城市居民身体健康的因素分析 原题再现&#xff1a; 以心脑血管疾病、糖尿病、恶性肿瘤以及慢性阻塞性肺病为代表的慢性非传染性疾病&#xff08;以下简称慢性病&#xff09;已经成为影响我国居民身体健康的重要问题。随着人们生活方式的改变&am…

opencv-18 什么是色彩空间?

1.什么是色彩空间类型&#xff1f; 色彩空间类型&#xff0c;也称为颜色空间类型或色彩模型&#xff0c;是一种表示图像中颜色的方式。在计算机图形学和数字图像处理中&#xff0c;有许多种色彩空间类型&#xff0c;每种类型有不同的表达方式和特点。 常见的色彩空间类型包括&a…

maven

一、为什么需要使用maven 如今我们构建一个项目需要用到很多第三方的类库 &#xff0c;例如我们在开发项目中 需要引入 这些依赖jar包 一个项目Jar包的数量之多往往让我们瞠目结舌&#xff0c;并且Jar包之间的关系非常复杂&#xff0c;一个Jar包往往又会引用其他Jar包&#x…

flex 弹性布局学习

一.Flex布局: Flex是Flexible Box 缩写“弹性布局”,用来为盒状模型提供最大得灵活性。 任何一个容器都可以指定为 Flex 布局。 .box{display: flex; } 行内元素也可以使用 Flex 布局。 .box{display: inline-flex; } Webkit 内核的浏览器&#xff0c;必须加上-webkit前缀。…

微服务模式:业务服务模式

无论是单体应用还是微服务&#xff0c;构建企业应用的业务逻辑/服务在更多方面上都有相似之处而不是差异。在两种方法中&#xff0c;都包含服务、实体、仓库等类。然而&#xff0c;也会发现一些明显的区别。在本文中&#xff0c;我将试图以概念性的方式强调这些区别&#xff0c…

火山引擎VeDI最新分享:消费行业的数据飞轮从“四更”开始

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 数据飞轮&#xff0c;正在为消费行业的数字化升级提供一套全新模式。 在刚刚结束的《全链路增长&#xff1a;数据飞轮转动消费新生力》专场活动上&#xff0c;火山引…

MySQL 8.0详细安装配置教程

一. 前言 MySQL是目前最为流行的开源数据库产品&#xff0c;是完全网络化跨平台的关系型数据库系统。它起初是由瑞典MySQLAB公司开发&#xff0c;后来被Oracle公司收购&#xff0c;目前属于Oracle公司。因为开源&#xff0c;所以任何人都能从官网免费下载MySQL软件&#xff0c…

SSIS对SQL Server向Mysql数据转发表数据 (二)

1、在SQL Server数据库创建一个数据库表&#xff1a;users USE [Test1] GO/****** Object: Table [dbo].[users] Script Date: 2023/7/27 16:25:11 ******/ SET ANSI_NULLS ON GOSET QUOTED_IDENTIFIER ON GOCREATE TABLE [dbo].[users]([id] [int] IDENTITY(1,1) NOT NUL…

一些有意思的人工智能发展状况数据

随着大型语言模型&#xff08;LLM&#xff09;的引入&#xff0c;机器学习&#xff08;ML&#xff09;和人工智能&#xff08;AI&#xff09;首次被日常开发人员所使用。这些令人感觉很神奇的应用程序&#xff0c;甚至是拥有数十亿研发支出的&#xff0c;在以前连大型科技公司几…

Vue2基础七、refnextTick自定义指令

零、文章目录 Vue2基础七、ref&nextTick&自定义指令 1、ref **作用&#xff1a;**利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例**特点&#xff1a;**查找范围 → 当前组件内 (更精确稳定)&#xff0c;用document.querySelect(‘.box’) 获取的是整个页面…

nacos安装与基础配置

源码 https://github.com/alibaba/nacos https://gitee.com/mirrors/Nacos 编译 git clone https://github.com/alibaba/nacos.git cd nacos/ mvn -Prelease-nacos -Dmaven.test.skiptrue clean install -U ls -al distribution/target/// change the $version to your ac…

【数据结构】实验八:树

实验八 树 一、实验目的与要求 1&#xff09;理解树的定义&#xff1b; 2&#xff09;掌握树的存储方式及基于存储结构的基本操作实现&#xff1b; 二、 实验内容 题目一&#xff1a;采用树的双亲表示法根据输入实现以下树的存储&#xff0c;并实现输入给定结点的双亲结点…

基于罪名法务智能知识图谱(含码源):基于280万罪名预测、20W法务问答与法律资讯问答功能

项目设计集合&#xff08;人工智能方向&#xff09;&#xff1a;助力新人快速实战掌握技能、自主完成项目设计升级&#xff0c;提升自身的硬实力&#xff08;不仅限NLP、知识图谱、计算机视觉等领域&#xff09;&#xff1a;汇总有意义的项目设计集合&#xff0c;助力新人快速实…

如何构建适合自己的DevOps软件测试改进方案

​目录 DevOps成熟度模型分析 构建适合企业自身性能的测试过程改进框架 资料获取方法 根据2022年的DevOps全球调查报告显示&#xff0c;主流软件企业采用或部分采用DevOps且已获得良好成效的占比已达70%&#xff0c;DevOps俨然成为当下软件开发研究的重要方向。 测试作为软…

uni-app 微信小程序:启用组件按需注入

uni-app 微信小程序&#xff1a;启用组件按需注入 文章目录 uni-app 微信小程序&#xff1a;启用组件按需注入一、官方文档按需注入注意事项 二、HBuilder X 设置三、效果 一、官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/ability/lazyload.html 按…

内存泄漏是什么?有什么危害

内存泄漏是什么&#xff1f;有什么危害 1. 前言1.内存泄漏是什么&#xff1f;2. 为什么会发生内存泄漏3. 内存泄漏的危害4. 总结 1. 前言 在各种项目开发中&#xff0c;内存泄漏是一个很严重的问题。对资源管理、性能优越、系统稳定性&#xff0c;以及是否安全产生极大印象。本…

CCD光斑图像质量分析仪的作用和工作原理

激光光斑的成型在激光加工中起着至关重要的作用&#xff0c;在实际加工中激光可以被变换成各种形状以满足加工需求&#xff0c;激光光斑的形状大致可以被分为四种&#xff1a;矩形、环形、椭圆形、线形。矩形的激光光斑适用于激光切割、激光焊接&#xff0c;环形的激光光斑适用…

【Python入门系列】第十八篇:Python自然语言处理和文本挖掘

文章目录 前言一、Python常用的NLP和文本挖掘库二、Python自然语言处理和文本挖掘1、文本预处理和词频统计2、文本分类3、命名实体识别4、情感分析5、词性标注6、文本相似度计算 总结 前言 Python自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&…

注解和反射02--Java反射

反射 动态和静态语言获取反射对象Java反射机制提供的功能Java反射优缺点反射相关的主要API 动态和静态语言 要学习反射&#xff0c;首先我们需要了解一下静态和动态语言。 动态语言&#xff1a;是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可…

C语言习题练习

C语言习题练习 一、offsetof宏二、交换奇偶位三、原地移除数组总结 一、offsetof宏 首先我们要了解什么是offsetof宏&#xff1a; . 此具有函数形式的宏返回数据结构或联合类型中成员成员的偏移值&#xff08;以字节为单位&#xff09;。 . 返回的值是size_t类型的无符号整数…