效果
代码
main.cpp
#include "rain.h"
#include <time.h>
#include <vector>
#define MY_TIMER_ID 123 // 计时器ID
#define MY_TIMER_TIME 100 // 计时器间隔时间
LRESULT CALLBACK CallBack(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// 设置窗口结构体信息
WNDCLASSEX we;
we.cbClsExtra = 0;
we.cbSize = sizeof (WNDCLASSEX);
we.cbWndExtra = 0;
we.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // 背景定为全黑色
we.hCursor = NULL; // 不要鼠标
we.hIcon = NULL;
we.hIconSm = NULL;
we.hInstance = hInstance;
we.lpfnWndProc = CallBack;
we.lpszClassName = TEXT("rain");
we.lpszMenuName = NULL;
we.style = CS_HREDRAW | CS_VREDRAW; // 窗口样式没什么特殊的
if (0 == RegisterClassEx(&we))
{
MessageBox(NULL, TEXT("Register Fail"), TEXT("提示"), MB_OK);
return -1;
}
ShowCursor(FALSE); // 隐藏鼠标
// WS_EX_TOPMOST --- 该窗口应放置在所有非最上面的窗口之上,并且即使在停用该窗口的情况下也应保持在它们之上。
// WS_POPUP --- 该窗口是一个弹出窗口。
// 窗口大小为屏幕大小
HWND hWnd = CreateWindowEx(WS_EX_TOPMOST, TEXT("rain"), NULL, WS_POPUP,
0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
NULL, NULL, hInstance, NULL);
if (NULL == hWnd)
{
MessageBox(NULL, TEXT("Create Fail"), TEXT("提示"), MB_OK);
return -1;
}
ShowWindow( hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK CallBack(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
static std::vector<Rains*> container; // 雨容器 统一操作
static int screenX; // 屏幕宽
static int screenY; // 屏幕高
switch (nMsg)
{
case WM_CREATE:
{
// 获取屏幕信息
screenX = GetSystemMetrics(SM_CXSCREEN);
screenY = GetSystemMetrics(SM_CYSCREEN);
// 初始化四种雨
static Rains rain_5(10, 5, 5, screenX, screenY);
static Rains rain_10(15, 10, 10, screenX, screenY);
static Rains rain_20(20, 20, 30, screenX, screenY);
static Rains rain_40(10, 40, 40, screenX, screenY);
container.push_back(&rain_5);
container.push_back(&rain_10);
container.push_back(&rain_20);
container.push_back(&rain_40);
SetTimer(hWnd, MY_TIMER_ID, MY_TIMER_TIME, NULL); // 打开定时器
srand((unsigned)time(NULL)); // 设置随机数种子
break;
}
// 定时器绘制雨
case WM_TIMER:
{
HDC hdc = GetDC(hWnd);
// 创建内存DC
HDC insidehdc = CreateCompatibleDC(hdc);
// 创建兼容性位图
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, screenX, screenY);
// 将DC和位图绑定在一起
SelectObject(insidehdc, hbitmap);
// 窗口刷黑
PatBlt(insidehdc, 0, 0, screenX, screenY, BLACKNESS);
// 设置背景透明
SetBkMode(insidehdc, TRANSPARENT);
// 绘制雨
for (auto rains : container)
{
rains->paint(insidehdc);
}
// 将内存DC传递到窗口DC
BitBlt(hdc, 0, 0, screenX, screenY, insidehdc, 0, 0, SRCCOPY);
DeleteObject(hbitmap);
DeleteDC(insidehdc);
DeleteDC(hdc);
break;
}
case WM_KEYDOWN:
switch (wParam)
{
case VK_ESCAPE:
{
PostMessage(hWnd, WM_DESTROY, 0, 0); // 按esc退出 非阻塞
}
}
break;
case WM_DESTROY:
KillTimer(hWnd, MY_TIMER_ID); // 关闭定时器
PostQuitMessage (0);
break;
}
return DefWindowProc(hWnd, nMsg, wParam, lParam);
}
rain.h
#ifndef _RAIN_H
#define _RAIN_H
#include <Windows.h>
/// 数字雨
class Rains
{
public:
Rains(int rainLength, int rainWidth, int speed, int screenX, int screenY);
~Rains();
void paint(HDC &insidehdc);
private:
const int SPACE = 3; // 雨之间间隔为宽度的倍数
const double PROPORTION = 1.5; // 字母宽高比例
int screenX; // 屏幕宽度
int screenY; // 屏幕高度
int rainLength; // 长度
int rainWidth; // 宽度
int rainNum; // 数量
int *rainPos; // 位置
char **rainStr; // 内容
HFONT hfont; // 字体
int speed; // 下落速度
bool isNumberInEnglishLetters(int randomNumber);
char createRandCharacter();
void randChangeRainStr();
bool isRainOutOfScreen(int i);
void rain();
};
#endif // !_RAIN_H
rain.cpp
#include "rain.h"
Rains::Rains(int rainLength, int rainWidth, int speed, int screenX, int screenY) : rainLength(rainLength), rainWidth(rainWidth), speed(speed), screenX(screenX), screenY(screenY)
{
this->rainNum = screenX / (rainWidth * SPACE);
rainPos = (int*)malloc(rainNum * sizeof(int));
for (int i = 0; i < rainNum; ++i)
{
rainPos[i] = rand() % screenY; // 使雨在一开始下落的位置参差不齐
}
rainStr = (char**)malloc(sizeof(char*) * this->rainNum);
for (int n = 0; n < rainNum; ++n)
{
rainStr[n] = (char*)malloc(rainLength * sizeof(char));
}
hfont = CreateFont(int(rainWidth * PROPORTION), // 字体的高度 单位px
rainWidth, // 字体的宽度 单位px
0, // 字体的倾斜角
0, // 字体的倾斜角
FW_BOLD, // 字体的粗细
0, // 是否为斜体
0, // 是否有下划线
0, // 是否有删除线
DEFAULT_CHARSET, // 使用的字符集
OUT_DEFAULT_PRECIS, // 指定如何选择字体
CLIP_DEFAULT_PRECIS, // 确定剪裁的精度
DRAFT_QUALITY, // 如何与选择的字体符合
FIXED_PITCH | FF_SWISS, // 间距标志和属性标志
TEXT("Fixedays") // 字体的名字
);
}
Rains::~Rains()
{
if (NULL != rainPos)
{
free(rainPos);
rainPos = NULL;
}
if (NULL != rainStr)
{
for (int n = 0; n < rainNum; ++n)
{
free(rainStr[n]);
rainStr[n] = NULL;
}
free(rainStr);
rainStr = NULL;
}
if (NULL != hfont)
{
DeleteObject(hfont);
}
}
// 画雨
void Rains::paint(HDC &insidehdc)
{
randChangeRainStr();
rain();
// 绘制雨
SelectObject(insidehdc, hfont);
for (int i = 0; i < rainNum; ++i)
{
SetTextColor(insidehdc, RGB(255, 255, 255));
for (int j = 0; j < rainLength; ++j)
{
TextOut(insidehdc, i * rainWidth * SPACE, int(rainPos[i] - j * rainWidth * PROPORTION), &rainStr[i][j], 1);
SetTextColor(insidehdc, RGB(0, 255 - 255 / rainLength, 0));
}
}
}
// 数字是否为字母
bool Rains::isNumberInEnglishLetters(int randomNumber)
{
return (randomNumber >= 65 && randomNumber <= 90) || randomNumber >= 97;
}
// 产生随机字符
char Rains::createRandCharacter()
{
int randomNumber = 0;
while (1)
{
randomNumber = rand() % 123;
if (isNumberInEnglishLetters(randomNumber))
{
break;
}
}
return randomNumber;
}
// 随机改变雨的内容
void Rains::randChangeRainStr()
{
for (int i = 0; i < rainNum; ++i)
{
for (int j = 0; j < rainLength; ++j)
{
rainStr[i][j] = createRandCharacter();
}
}
}
// 雨是否超出了屏幕
bool Rains::isRainOutOfScreen(int i)
{
return rainPos[i] - rainWidth * PROPORTION * rainLength >= screenY;
}
// 雨往下落
void Rains::rain()
{
for (int i = 0; i < rainNum; ++i)
{
rainPos[i] += speed;
// 超过屏幕下方的部分重新回到上方
if (isRainOutOfScreen(i))
{
rainPos[i] = 0;
}
}
}
思路
使用定时器每隔一段时间刷新一下屏幕。
每次数字雨都下落一定高度,最后超出屏幕重置位置。
链接
百度云链接:https://pan.baidu.com/s/1g2lqs3cFjwv2W35yvvSmUw
提取码:ieug