同时检测多个按键和平滑按键处理

getch() 函数,用于返回用户输入的字符。当连续按键时,该函数返回第一个字符和第二个字符之间,默认有 0.5 秒的延时,并且之后的连续字符,默认是每秒钟 15 次输入。这两个数值可以在控制面板中设置。

如果需要平滑的按键输入,或者同时按下多个按键,就不能用 getch() 了,需要使用另一个 Windows API 函数:GetAsyncKeyState()。该函数原型如下:

SHORT GetAsyncKeyState(
	int vKey		// virtual-key code
);

vKey 是要检测的按键的虚拟键码,常用的如 VK_UP、VK_DOWN 等,分别表示方向键的上、下等。需要注意:对于 26 个字母的键码,可以直接写 'A'、'B'……,而不要写 VK_A、VK_B。数字键也是,请直接写 '0'、'1'……。全部的 256 种虚拟键码,请参考 MSDN

...

使用 getch() 获得方向键和更多的功能键

通常来说,getch() 可以返回用户键入的字符。对于一些小游戏,希望用方向键控制的时候,怎么做呢?

先看看 MSDN 的解释:

When reading a function key or an arrow key, _getch and _getche must be called twice; the first call returns 0 or 0xE0, and the second call returns the actual key code.

简单来说,当 getch() 返回 0 或 0xE0 时,就表示用户按了功能键,这时候需要再调用一次 getch()。第二次 getch() 返回的值表示功能键,比如:

72 表示 上
80 表示 下
75 表示 左
77 表示 右

更多的功能键码,可以自己试验得出。

具体到程序中的应用,请参考“俄罗斯方块”的源代码:http://www.easyx.cn/samples/

...

弹出一个简单的对话框显示信息

有时候程序中会需要一个弹出窗口显示信息,还有些小游戏结束的时候,会简单的弹出一个对话框询问用户是否要重新来一句,如下图:

这篇文章就简单讲解一下这个功能如何实现。

首先,我们需要使用一个 Windows API 函数:MessageBox。该函数原型如下:

int MessageBox(
	HWND hWnd,          // handle to owner window
	LPCTSTR lpText,     // text in message box
	LPCTSTR lpCaption,  // message box title
	UINT uType          // message box style
);

第一个参数 hWnd 是指向父窗口的句柄,可以通过 EasyX 的函数 GetHWnd() 得到绘图窗口的句柄;

第二个参数 lpText 是要显示的字符串

...

在游戏中播放音乐

游戏没有声音多单调。

这里做一个简单的范例,用 mciSendString 函数播放 MP3 格式的音乐,先看看代码吧:

// 编译该范例前,请把 music.mp3 放在项目文件夹中
// 发布时,请把 music.mp3 和编译的 exe 放在一起
// 编译环境:VC6~VC2017 + EasyX_20190314(beta)
//
#include <graphics.h>
#include <conio.h>
// 引用 Windows Multimedia API
#pragma comment(lib,"Winmm.lib")

void main()
{
	initgraph(640, 480);

	// 打开音乐
	mciSendString(_T("open music.mp3 alias mymusic"), NULL, 0, NULL);

	outtextxy(0, 0, _T("按任意键开始播放"));
...

程序的编写风格(代码格式)可以随心所欲

很多文章都阐述了编写程序时“代码格式”的重要性,甚至还有些硬性的规定。当然,这篇文章并不是否定格式的重要性,初学者甚至应该十分注重格式才对。

但是另一方面,编写程序还应该注重整体,通过代码格式展现出整体的结构。这里举一个例子,CodeBus 网站有一个模拟屏保变幻线的源代码:
https://codebus.cn/zhaoh/post/screensaver-mystify

其中有几行代码是很长的,并且违反了很多条“代码格式”的规定,这里转过来这几行:

	// 判断顶点是否越界
	if		(m_head->pos.x < 0)			{ m_head->pos.x = -m_head->pos.x;				m_step.x =  rand() % MAXSTEP + 1;	}
	else if (m_head->pos.x >= WIDTH)	{ m_head->pos.x -= m_...

学习编写一个完整的 Windows 应用程序

从 C 语言,到常见的 setup.exe 安装程序,究竟有多远?怎样实现双击 .xls 文件会自动启动 excel 并加载?这篇文章就解释这个问题。

请跟随以下步骤:

1. 编写应用程序。为了叙述方便,暂且叫 test.exe 吧。通常 test.exe 中会用若干 scanf 或 cin 来读取用户输入的参数,并根据参数来执行程序,这是大家在学习 C 语言的过程中见到的。

2. 要规定程序所需数据的格式。例如,word 需要使用 .doc 格式的数据,photoshop 需要使用 .psd 格式的数据。不管这些数据文件是文本的还是二进制的,只有格式确定下来,程序才能按照原定意图解释数据。数据格式就是将用户输入的全部数据都放进一个文件中,然后将 scanf 语句修改为读取该文件(读文件的程序部分请参考相关书籍)。

3. 设置文件关联。在上一步我们规定了一个我们需要的数据格式,现在给这个格式的文件自定一个扩展名,比如叫 .abc,然后双击该文件,Windows 会提示用户选择打开该类型的程序,我们选择步骤 1 编写的

...

解决问题:编译范例程序都出现上百个错误

刚开始用 EasyX,编译却出现上百个错误,例如:

error C2143: syntax error : missing ')' before '='
error C2072: 'initgraph' : initialization of a function
error C2059: syntax error : ')'
warning C4028: formal parameter 1 different from declaration
warning C4028: formal parameter 2 different from declaration
warning C4028: formal parameter 3 different from declaration
error C2143: syntax error : missing ')' before '*'
error C2081: 'bool' : name in formal parameter list ill

...

解决错误:error C2665: “outtextxy”: 2 个重载中没有一个可以转换所有参数类型

【错误描述】

一些程序在 VC6 下运行好好地,但是放到 VC2008 及更高版本 VC 下编译却报错误(以下仅以 VC2008 举例,高版本 VC 类似),例如使用如下语句:

outtextxy(10, 20, "Hello World");

在 VC6 下可以成功编译,但在 VC2008 下编译后会有错误。

中文版错误提示如下:

error C2665: “outtextxy”: 2 个重载中没有一个可以转换所有参数类型

英文版错误提示如下:

error C2665: 'outtextxy' : none of the 2 overloads could convert all the argument types

同样的,对于其他一些包含字符串调用的函数,例如 loadimage、drawtext 等,也会遇到类似问题。

【错误原因】

简单来说,这是由于字符编码问题引起的。

...

什么是 LPCTSTR?和 char* 有什么关系?

简单来说,LPCTSTR 就表示字符串指针。

那么和 char* 又有什么区别呢?

大家用 char 表示字符时,英文占用一个字节,中文站用两个字节。这样有一个严重的问题:两个字节,究竟是两个英文字符,还是一个中文字符?为了解决这个问题,unicode 编码诞生了。

常见的 unicode 编码不管中文英文都用两个字节表示,用 wchar_t 来定义。

为了让代码适应 char 和 wchar_t 两种情况,可以用 TCHAR 宏,这个宏当项目定义为 unicode 时表示 wchar_t,否则表示 char。

对应的,还有 _T("") 宏用来表示字符串,例如 _T("abc") 在项目为 unicode 时表示 L"abc",否则表示 "abc"。

然后再看 LPCTSTR,它是以下几个概念的缩写:Long Point Const Tchar STRing,这样意思就明确了吧。

窗口技巧:利用窗体句柄实现圆形窗口

该篇文章讲述如何做一个圆形窗口。根据该思路,可以实现各种形状的窗口。

阅读该文章前,请先阅读

  1. “VC绘图/游戏简易教程-10:用鼠标控制绘图/游戏程序”https://codeabc.cn/bestans/post/concise-lesson-10
  2. “VC绘图/游戏简易教程-15:窗体句柄(Windows 编程入门)”https://codeabc.cn/bestans/post/concise-lesson-15

【基础程序】

先写一个基础程序,实现按鼠标右键退出,完整代码如下:

#include <graphics.h>
#include <conio.h>

int main()
{
	initgraph(640, 480);	// 初始化图形窗口
	MOUSEMSG m;				// 定义鼠标消息

	while(true)
	{...

VC绘图/游戏简易教程--15:窗体句柄(Windows 编程入门)

教程总目录:https://www.codeabc.cn/bestans/post/concise-lesson-contents(里面包括VC下的graphics.h的配置方法)

EasyX 库有一个获取窗口句柄的功能,很是强大,这里介绍一下。

【窗体句柄】

窗体句柄是 Windows 下窗口的标识,可以理解为窗口的 ID。Windows SDK 中的许多窗口操作函数都需要指明窗体句柄,也就是说,有了句柄,我们可以通过 Windows SDK 中的 API 实现许多高级的窗体控制。

【函数原型】

窗体句柄为 HWND 类型,通过 GetHWnd() 函数可以返回绘图窗体的句柄。其函数原型是:
HWND GetHWnd();

【使用句柄】

举个例子,设置窗体标题文字的 Windows API 为:
BOOL SetWindowText(HWND hWnd, LPCTSTR lpString);
参数:
   h

...

读取图片的技巧:将图片内嵌到 exe 文件中

注:Visual C++ Express(学习版)不支持资源编辑,无法创建资源文件,因此也就无法实现内嵌图片资源。

场景描述

如果程序中需要使用一张图片,通常会用相对路径方式指定一个外部文件。例如:

loadimage(NULL, _T("test.jpg"));

这样,将编译后的 .exe 和 test.jpg 放在一起,就可以正确加载图片。许多游戏有几十个甚至上千个文件,就是有类似这样的许多外部数据。

但还有一些情况,希望图片能嵌入编译后的 .exe 里面,这样只需要拷贝一个 .exe 文件就能附带上所需图片。本文就介绍这种情况的实现方法。

什么是资源文件

windows 应用程序是可以包含各种“资源”的,例如:图标、对话框、菜单、快捷键等等,这些资源按照一定的格式,可以和 .exe 链接在一起。

我们所要做的,就是把图片放到资源

...

VC绘图/游戏简易教程--14:通过位运算实现颜色的分离与处理

教程总目录:https://www.codeabc.cn/bestans/post/concise-lesson-contents(里面包括VC下的graphics.h的配置方法)

本节课要求熟练掌握位运算,详见:
http://hi.baidu.com/bestans/blog/item/fb75b439404876e614cecb9f.html (已失效)

[颜色基础]

在 EasyX 库中,颜色是一个 int 类型的数据,转换为 16 进制后的颜色格式是 0xbbggrr,其中,bb/gg/rr 分别表示两位十六进制的蓝/绿/红颜色值,每种颜色的范围是 0x0~0xff,转换为十进制就是 0~255。

举几个颜色标示的例子:

  • 颜色  直接表示  RGB 宏标示
  • 纯绿色 0x00ff00  RGB(0, 255, 0)
  • 青色  0xffff00  RGB(0, 255, 255)  注:青
...

VC绘图/游戏简易教程--13:getimage / putimage / loadimag / saveimage / IMAGE 的用法

教程总目录:https://www.codeabc.cn/bestans/post/concise-lesson-contents(里面包括VC下的graphics.h的配置方法)

 getimage / putimage / loadimage / saveimage 这一组命令和 IMAGE 对象可以实现图像处理的相关功能,下面逐个介绍。
(有点类似 tc 中的 imagesize)

[加载图片]

实现加载图片主要分三步:
1. 定义 IMAGE 对象
2. 读取图片至 IMAGE 对象
3. 显示 IMAGE 对象到需要的位置

很简单,我们看一下完整的代码:

#include <graphics.h>
#include <conio.h>

int main()
{
	initgraph(640, 480);

	IMAGE img;	// 定义 IM...