【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()函数实现键盘控制单个字符移动
[导读]本系列博文内容链接如下: 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动 在【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值一文中介绍了如何利用…...
Android 设备兼容性使用(详细版)
经典好文推荐,通过阅读本文,您将收获以下知识点: 一、设备兼容性分类 二、硬件设备兼容 三、软件 APP 兼容 四、兼容不同语言 五、兼容不同分辨率 六、兼容不同屏幕方向布局 七、兼容不同硬件 Feature 八、兼容不同SDK平台 一、设备兼容性分类 Android设计用于运行在许多不同…...
React 中的常见 API 和生命周期函数
目录 useStateuseEffectuseRefdangerouslySetInnerHTML生命周期函数 constructorcomponentDidMountstatic getDerivedStateFromPropsshouldComponentUpdatecomponentDidUpdatecomponentWillUnmount useState useState 是 React 的一个 Hook,用于在函数组件中添加…...
神经网络中遇到的 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、分布式事务 分布式事务就是在一个交易中各个服务之间的相互调用必须要同时成功或者同时失败,保持一致性和可靠性。在单体项目架构中,在多数据源的情况下也会发生 分布式事务问题。本质上来说,分布式事务就是为了保证不同数据库的数据一致性…...
【宏定义】——编译时校验
文章目录 编译时校验功能描述代码实现示例代码正常编译示例编译错误示例预处理之后的结果 代码解析!!estruct {int:-!!(e); }sizeof(struct {int:-!!(e); }) 参考代码 编译时校验 功能描述 用于在编译时检查一个条件是否为真,如果条件为真则会编译失败,…...

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

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

rcu链表综合实践
基础知识 rcu-read copy update的缩写。和读写锁起到相同的效果。据说牛逼一点。对于我们普通程序员,要先学会使用,再探究其内部原理。 链表的数据结构: struct list_head {struct list_head *next, *prev; };还有一种:struct h…...
odoo16-python框架-动作
总结 1 模型和视图的 设计之美 view_ids, view_id,view_mode 最终目的都是为了生成views, 也就是视图. 模型是死的,像男人,一成不变 视图像女人,千变万化, 姿态万千 一阴一阳之谓道,设计之美又在这里得到了体现 2 所有的动作都可以通过web界面来配置 可以通过在"设…...

微信小程序——同一控件的点击与长按事件共存的解决方案
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

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

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

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

Vue3 word如何转成pdf代码实现
🙂博主:锅盖哒 🙂文章核心:word如何转换pdf 目录 1.前端部分 2.后端部分 在Vue 3中,前端无法直接将Word文档转换为PDF,因为Word文档的解析和PDF的生成通常需要在后端进行。但是,你可以通过Vu…...
fpga--流水灯
fpga流水灯的设计 思路:外部时钟频率50mhz,若要实现每隔0.5s闪烁一次,则使用内部计数器计数到24999999拉高一个周期电平,当电平被拉高的时候,进行LED灯电平的设置,每次检测到高电平,就进行一位…...

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

Django + Xadmin 数据列表复选框显示为空,怎么修复这个问题?
问题描述: 解决方法: 后续发现的报错: 解决方案: 先根据报错信息定位到源代码: 在该文件顶部写入: from django.core import exceptions然后把: 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 开发已经有了相当长的历史,从最早的 CGI 脚本到现在的全栈 Web 框架,现在已经成为了一种非常流行的方式。 Python 最早被用于 Web 开发是在 1995 年(90年代早期),当时使用 CGI 脚本编写动态 Web 页面…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...