OI 之我的游戏发展史
Shuhang_JOKER1 · · 科技·工程
我一直对编游戏这件事很感兴趣。
壹:Python 打字方式改进
在一年半之前,我学的是 Python。
当时觉得很有意思,没有什么压力感。
我记得第一课的时候我就自己打了个文字小游戏,然后觉得自己无敌了。。。
不过一开始弱弱的我对在屏幕上输出文字的方式很烦恼,直接输出不仅不好看,还要停几秒作为缓冲时间,如果看的快会觉得慢,看的慢又会觉得来不及。
怎么办呢?其实很简单,可以像 AI 那样一个字一个字打,写个函数控制好间隔的时间,用的时候调用函数,参数是要输出的字符串。
下面有一个示例,可以实现这样的效果,用的是最简单的循环 + sleep 函数。
import time
def Print(s,huanhang = 1):
for i in s:
print(i,end = '')
time.sleep(0.02)
if huanhang == 1:
print()
但如果想要暂停呢?这时就可以用 keyboard 库监听全局按键,需要稍稍复杂一点。如果有这个需求,那么可以使用。
下面是一个按空格暂停的示例。
import time
import keyboard
import threading
is_paused = False
pause_lock = threading.Lock()
def monitor_space():
global is_paused
while True:
keyboard.wait('space')
with pause_lock:
is_paused = not is_paused
if is_paused:
print("\n[输出已暂停,按空格键继续]", end='', flush=True)
else:
print("\r ", end='', flush=True)
print("\r", end='', flush=True)
def Print(s, huanhang=1):
global is_paused
thread = threading.Thread(target=monitor_space, daemon=True)
thread.start()
for i in s:
while is_paused:
time.sleep(0.1)
print(i, end='', flush=True)
time.sleep(0.02)
if huanhang == 1:
print()
当然还有一些更厉害的,可以实现倒退功能。但是比较少见。可以用多线程并发控制实现。
下面是按 p 回退的实现。
import time
import keyboard
import threading
current_pos = 0
is_paused = False
running = True
lock = threading.Lock()
def monitor_keys():
global is_paused, current_pos, running
while running:
event = keyboard.read_event(suppress=False)
if event.event_type == keyboard.KEY_DOWN:
with lock:
if event.name == 'space':
is_paused = not is_paused
if is_paused:
print("\n[输出暂停,按空格继续]", end='', flush=True)
else:
print("\r", end='', flush=True)
elif event.name == 'p':
if current_pos > 0:
current_pos -= 1
print('\b \b', end='', flush=True)
def Print(s, huanhang=1):
global current_pos, running, is_paused
thread = threading.Thread(target=monitor_keys, daemon=True)
thread.start()
try:
for i, char in enumerate(s):
while is_paused:
time.sleep(0.1)
print(char, end='', flush=True)
current_pos += 1
time.sleep(0.05)
if huanhang == 1:
print()
except KeyboardInterrupt:
pass
finally:
running = False
print("\n[输出结束]")
经过这样的实现,感觉看起来会比原来好很多。
贰:Python 模拟加载
既然是做游戏,肯定就要有真实感。
重要的,加载系统肯定少不了。毕竟大部分游戏都需要加载。我们通过 Python 可以手写一个简易的加载系统。
当然,我们就用恒定的数字来表示进度。比如用
接着我们可以认识一下 print('\x1bc'),它的作用是清空屏幕。
搭配起来,我们就得到了极其简单的代码:
Print('开机中······')
jindu = [0,3,5,8,11,15,16,18,21,24,28,33,35,38,41,44,48,51,54,56,59,61,63,66,68,72,74,77,79,83,86,88,92,94,97,99,100]
for j in jindu:
print('\x1bc')
if j < 10:
print('引擎中······')
elif j < 30:
print('网络加载中······')
elif j < 45:
print('资源加载中······')
elif j < 75:
print('程序加载中······')
elif j < 90:
print('选项加载中······')
else:
print('用户加载中······')
print(f'[{j}%~~~~~]')
time.sleep(random.uniform(0.2,0.3))
print('\x1bc')
Print('网络连接')
time.sleep(1)
print('\x1bc')
Print("开机成功!")
print('\x1bc')
叁:Python 内部游戏实现
这个就比较广泛了。大部分简单的游戏是问答体系。
但是如果自己输结果就太广泛了。所以可以用选择题的形式来进行问答。
你可以通过 time 库和 random 库来写很多东西,像计算题,概率抽奖之类的。
还有,如果要调用网址,可以用 webbrowser 来实现。
例如:
webbrowser.open('https://cybermap.kaspersky.com/')
这里实现一个猜数小游戏:
import random
def guess_number_game():
number = random.randint(1, 100)
guess_count = 0
print("欢迎来到猜数字小游戏!")
print("我已经想好了一个 1 到 100 之间的整数,你来猜猜看吧!")
while True:
try:
guess = int(input("请输入你的猜测:"))
guess_count += 1
if guess < 1 or guess > 100:
print("请输入 1~100 之间的整数!")
elif guess < number:
print("太小了!再大一点!")
elif guess > number:
print("太大了!再小一点!")
else:
print(f"恭喜你!猜对了!答案就是 {number}!")
print(f"你一共猜了 {guess_count} 次。")
break
except ValueError:
print("请输入一个有效的整数!")
while True:
play_again = input("还想再玩一次吗?(yes/no): ").strip().lower()
if play_again == 'yes' or play_again == 'y':
guess_number_game()
break
elif play_again == 'no' or play_again == 'n':
print("谢谢游戏,再见!")
break
else:
print("请输入 yes 或 no。")
if __name__ == "__main__":
guess_number_game()
肆:Python 游戏结尾
这部分比较简单,或许你可以用 886 这种低级结尾,或者是关于游戏时长及成绩的。
这是其中一种比较规范的:
time_end = time.time() #结束计时
time_c = time_end - time_start #运行所花时间
time_c = int(time_c)
time_w = time_c / 60;
Print("本次运行你用了" + str(time_w) + "分")
if time_w > 60:
Print("你的运行时间过久,为保护青少年视力,运行一次时间不得超过60分钟!")
elif time_w > 50:
Print("你的运行时间有一点久,以后注意!")
elif time_w > 40:
Print("你的运行时间还不错,以后争取做得更好!")
else:
Print("你的运行时间刚刚好,以后保持!")
伍:C++ 游戏中获取打出的字符
后来我学了 C++。在这个时候,我先制作了一个用了计时的小游戏,需要小猴编程里的库,具体请看代码:
#include <ctime>
#include <iostream>
#include <xiaohoucode.h>
using namespace std;
int main()
{
cout << "欢迎来到时间机器" << endl;
sleep(1);
cout << "你要设置什么?1倒计时 2闹钟:" << endl;
int qe;
cin >> qe;
if(qe == 1)
{
cout << "请问你要定一个几分的倒计时?" << endl;
int n;
cin >> n;
cout << "要设置秒吗?1要 2不要:" << endl;
int m;
cin >> m;
if(m == 1)
{
cout << "要设置多少秒?" << endl;
int y;
cin >> y;
if(y < 10)
{
cout << n << ":" << "0" << y << endl;
}
else
{
cout << n << ":" << y << endl;
}
sleep(1);
clear();
for(int i = y-1; i >= 0; i--)
{
if(i < 10)
{
cout << n << ":" << "0" << i << endl;
}
else
{
cout << n << ":" << i << endl;
}
sleep(1);
clear();
}
for(int i = n-1; i >= 0; i--)
{
for(int j = 59; j >= 0; j--)
{
if(j < 10)
{
cout << i << ":" << "0" << j << endl;
}
else
{
cout << i << ":" << j << endl;
}
sleep(1);
clear();
}
}
cout << endl;
cout << "倒计时结束!^_^" << endl;
}
else if(m == 2)
{
cout << n << ":" << "00" << endl;
sleep(1);
clear();
for(int i = n-1; i >= 0; i--)
{
for(int j = 59; j >= 0; j--)
{
if(j < 10)
{
cout << i << ":" << "0" << j << endl;
}
else
{
cout << i << ":" << j << endl;
}
sleep(1);
clear();
}
}
cout << endl;
cout << "倒计时结束!^_^" << endl;
}
else
{
cout << endl;
}
}
if(qe == 2)
{
cout << "请问你要设置一个几分钟后的闹钟?" << endl;
int x;
cin >> x;
cout << "要设置秒吗?1要 2不要:" << endl;
int s;
cin >> s;
if(s == 1)
{
cout << "要设置多少秒?" << endl;
int m;
cin >> m;
cout << "闹钟已定" << endl;
for(int i = 1; i <= m; i++)
{
sleep(1);
}
for(int i = 1; i <= x; i++)
{
for(int i = 1; i <= 60; i++)
{
sleep(1);
}
}
cout << endl;
cout << "闹钟到了!滴滴滴!" << endl;
}
else if(s == 2)
{
cout << "闹钟已定" << endl;
for(int i = 1; i <= x; i++)
{
for(int i = 1; i <= 60; i++)
{
sleep(1);
}
}
cout << endl;
cout << "闹钟到了!滴滴滴!" << endl;
}
else
{
cout << endl;
}
}
return 0;
}
但后来决定制作关于上下左右跑酷的游戏。如何获取打出的字符呢?
这里就需要用到 _kbhit 和 getch() 了。_kbhit 用于检查当前是否有键盘输入,getch() 用来读入字符。两者配合 switch 语句即可实现内容。
下面是一部分:
int getk;
while(1){
if(_kbhit){
getk=getch();
break;
}
}
switch(getk){
case 'H':
getk='w';
break;
case 'K':
getk='a';
break;
case 'P':
getk='s';
break;
case 'M':
getk='d';
break;
}
return getk;
这样就可以实现很多操作,比如暂停游戏,上下左右移动什么的。
陆:C++ windows.h库
在 windows.h 库,有几个好用的东西:
-
SetConsoleTextAttribute() 函数:
Windows 提供了一个未公开在标准 windows.h 中的扩展函数:SetCurrentConsoleFontEx,可以用来改变控制台字体。但这需要:
- 包含 <windows.h>
- 链接 kernel32.lib
- 使用 CONSOLE_FONT_INFOEX 结构
例如:
#include <iostream> #include <windows.h> using namespace std; int main() { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hConsole, 12); cout << "这是红色文字" << endl; SetConsoleTextAttribute(hConsole, 10); cout << "这是绿色文字" << endl; SetConsoleTextAttribute(hConsole, 14); cout << "这是黄色文字" << endl; SetConsoleTextAttribute(hConsole, 15); cout << "这是白色文字(默认)" << endl; return 0; } -
Sleep() 函数:
可以停住一段时间。Sleep(1000) 可以暂停一秒。 在程序中很好用。
-
CreateWindowEx{} 函数:
可以创建一个窗口,也很好用。
例如:
HWND hwnd = CreateWindowEx( 0, "MyGameClass", "我的小游戏", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL ); -
GetDC() 和 ReleaseDC() 函数:
GetDC() 用于获取指定窗口的 DC,ReleaseDC() 用于释放 DC,两者配合 GDI 函数(如 Rectangle,Ellipse,TextOut)使用。
例如:
HDC hdc = GetDC(hwnd); Ellipse(hdc, 100, 100, 200, 200);// 画一个椭圆(这里是圆) ReleaseDC(hwnd, hdc);另外还有一些画图形的函数,这里不再解释。
这些函数可以进行基础的绘画了,可以制作一些 2D 小游戏,例如坦克大战。只是感觉似乎能画的内容不多。
比如下面这个画房子的代码:
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (msg) {
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
FillRect(hdc, &rect, (HBRUSH) GetStockObject(WHITE_BRUSH));
int x = (rect.right - 200) / 2;
int y = rect.bottom - 150;
HBRUSH hHouseBrush = CreateSolidBrush(RGB(220, 80, 80));
SelectObject(hdc, hHouseBrush);
Rectangle(hdc, x, y - 100, x + 200, y);
HPEN hRoofPen = CreatePen(PS_SOLID, 2, RGB(100, 30, 30));
HBRUSH hRoofBrush = CreateSolidBrush(RGB(150, 40, 40));
SelectObject(hdc, hRoofPen);
SelectObject(hdc, hRoofBrush);
POINT roof[3] = {
{x, y - 100},
{x + 100, y - 150},
{x + 200, y - 100}
};
Polygon(hdc, roof, 3);
HBRUSH hDoorBrush = CreateSolidBrush(RGB(100, 60, 30));
SelectObject(hdc, hDoorBrush);
Rectangle(hdc, x + 80, y - 60, x + 120, y);
HBRUSH hWindowBrush = CreateSolidBrush(RGB(255, 255, 150));
SelectObject(hdc, hWindowBrush);
Rectangle(hdc, x + 30, y - 80, x + 70, y - 40);
Rectangle(hdc, x + 130, y - 80, x + 170, y - 40);
DeleteObject(hHouseBrush);
DeleteObject(hRoofPen);
DeleteObject(hRoofBrush);
DeleteObject(hDoorBrush);
DeleteObject(hWindowBrush);
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
const char CLASS_NAME[] = "SimpleHouseClass";
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClass(&wc)) {
MessageBox(NULL, "窗口类注册失败!", "错误", MB_ICONERROR);
return 0;
}
HWND hwnd = CreateWindowEx(
0,
CLASS_NAME,
"画一个简单的房子",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 500, 400,
NULL, NULL, hInstance, NULL
);
if (!hwnd) {
MessageBox(NULL, "创建窗口失败!", "错误", MB_ICONERROR);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
柒:C++ 简单 windows.h 小游戏
我们可以尝试做一个坦克按空格发射炮弹的小游戏。
-
创建窗口
这部分可以保持不变,主要是设置窗口的基本属性,如标题、大小等。
-
处理键盘消息(空格键)
这部分也基本相同,主要是在检测到空格键按下时,创建新的炮弹对象。
-
使用定时器实现动画
通过 SetTimer 设置定时器,定期更新炮弹的位置并重绘画面。
-
GDI 绘图与颜色
示例:
背景色:使用浅蓝色代表天空,RGB 值为 (135, 206, 235)。
坦克车身:灰色矩形,RGB 值为 (100, 100, 100)。
坦克炮管:黑色矩形,RGB 值为 (0, 0, 0)。
坦克履带:深灰色矩形,RGB 值为 (60, 60, 60)。
炮弹:红色圆形,RGB 值为 (255, 0, 0)。
我们可以通过 CreateSolidBrush 函数来创建这些颜色的画刷,并通过 SelectObject 将它们选入设备上下文(HDC)中,以便后续绘图函数使用。
-
处理越界
最后就是确保每次移动后检查炮弹是否超出屏幕顶部,以移除它们。
下面是代码:
#include <windows.h>
#include <vector>
struct Shell {
int x, y;
Shell(int x, int y) : x(x), y(y) {}
};
const char CLASS_NAME[] = "TankGame";
HWND hwnd;
std::vector<Shell> shells;
int tankX = 0, tankY = 0;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (msg) {
case WM_CREATE:
SetTimer(hwnd, 1, 16, NULL);
break;
case WM_KEYDOWN:
if (wParam == VK_SPACE) {
shells.push_back(Shell(tankX + 50, tankY - 10));
}
break;
case WM_TIMER:
if (wParam == 1) {
for (auto it = shells.begin(); it != shells.end(); ) {
it->y -= 5;
if (it->y < 0) {
it = shells.erase(it);
} else {
++it;
}
}
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_PAINT:
{
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
FillRect(hdc, &rect, CreateSolidBrush(RGB(135, 206, 235)));
tankX = (rect.right - 100) / 2;
tankY = rect.bottom - 30;
HBRUSH hBodyBrush = CreateSolidBrush(RGB(100, 100, 100));
SelectObject(hdc, hBodyBrush);
Rectangle(hdc, tankX, tankY, tankX + 100, tankY + 30);
HBRUSH hGunBrush = CreateSolidBrush(RGB(0, 0, 0));
SelectObject(hdc, hGunBrush);
Rectangle(hdc, tankX + 45, tankY - 20, tankX + 55, tankY);
HBRUSH hTrackBrush = CreateSolidBrush(RGB(60, 60, 60));
SelectObject(hdc, hTrackBrush);
Rectangle(hdc, tankX - 5, tankY + 25, tankX + 105, tankY + 30);
Rectangle(hdc, tankX - 5, tankY + 30, tankX + 105, tankY + 35);
HBRUSH hShellBrush = CreateSolidBrush(RGB(255, 0, 0));
SelectObject(hdc, hShellBrush);
for (const auto& shell : shells) {
Ellipse(hdc, shell.x - 5, shell.y - 5, shell.x + 5, shell.y + 5);
}
DeleteObject(hBodyBrush);
DeleteObject(hGunBrush);
DeleteObject(hTrackBrush);
DeleteObject(hShellBrush);
DeleteObject(GetStockObject(WHITE_BRUSH));
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
KillTimer(hwnd, 1);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClass(&wc)) {
MessageBox(NULL, "注册窗口类失败!", "错误", MB_ICONERROR);
return 0;
}
hwnd = CreateWindowEx(
0,
CLASS_NAME,
"坦克发射炮弹游戏",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 500,
NULL, NULL, hInstance, NULL
);
if (!hwnd) {
MessageBox(NULL, "创建窗口失败!", "错误", MB_ICONERROR);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
捌:C++ EasyX 库
后来我学习了 EasyX 库。但这个需要到 easyx 这里下载,在引用时加上头文件 graphics.h 就可以画图形了。
安装的话,登进网址后,点击下载,再按照安装步骤来。
下面是几个常用的函数:
-
initgraph(x,y)
创建一个 x*y 大小的窗口。
-
setbkcolor(RGB(x,y,z))
设置绘图设备的背景颜色。(RGB 控制颜色,0≤x,y,z≤256)
-
cleardevice()
清除屏幕并将当前位置设置为 (0,0)。
-
settextstyle(x, y, _T(z));
设置字体高、宽、字体名称。
-
outtextxy(x,y,_T(z));
在坐标(x,y)的地方输出字符串 z。
假如你想输出变量,其中一种方法是用 swprintf_s:
wchar_t wScore[105], wLength[105]; swprintf_s(wScore, L"%d", score); swprintf_s(wLength, L"%d", length); outtextxy(1, 1, _T("积分: ")); outtextxy(80, 1, wScore); outtextxy(150, 1, _T("长度: ")); outtextxy(230, 1, wLength); -
solidrectangle(x1,y1,x2,y2)
绘制一个从左上角 (x1, y1) 到右下角 (x2, y2) 的实心矩形。
-
setfillcolor(RGB(x,y,z))
用于设置图形的填充颜色。
-
line(x1,y1,x2,y2)
绘制一个从左上角 (x1, y1) 到右下角 (x2, y2) 的直线。
-
circle(x,y,r)
在坐标(x,y)绘制一个半径为 r 的圆。
另外,认识一下 getchar() 函数,它可以读入一个字符,然后配合之前的知识就可以做简单的游戏了。
玖:C++ 简单的贪吃蛇小游戏
通过之前学习的内容,我们即可变出简单的贪吃蛇小游戏,上下左右或 wasd 操控,整体像在一个没有打印的网格上移动。
注意:只有下载了 EasyX 库才能运行成功。
#include <bits/stdc++.h>
#include <graphics.h>
#include <stdio.h>
#include <conio.h>
#include<string.h>
#define NODE_WIDTH 20
struct node {
int x, y;
};
int score = 0;
node snakeMove(node* snake, int length, int d) {
node tail = snake[length - 1];
for (int i = length - 1; i > 0; i--)
snake[i] = snake[i - 1];
node newHead;
newHead = snake[0];
if (d == 0)
newHead.y--;
else if (d == 1)
newHead.y++;
else if (d == 2)
newHead.x--;
else
newHead.x++;
if (newHead.x < 0 || newHead.x >= 75 || newHead.y < 0 || newHead.y >= 40) {
settextstyle(200, 0, _T("宋体"));
outtextxy(500, 100, _T("GAME"));
Sleep(1000);
settextstyle(250, 0, _T("宋体"));
outtextxy(450, 400, _T("OVER"));
Sleep(1000);
exit(0);
}
snake[0] = newHead;
return tail;
}
node createFood(node* snake, int length) {
node food;
while (1) {
food.x = rand() % 75;
food.y = rand() % 40;
int i;
for (i = 0; i < length; i++)
if (snake[i].x == food.x && snake[i].y == food.y)
break;
if (i < length)
continue;
else
break;
}
return food;
}
int main() {
srand(time(0));
initgraph(1500, 800);
setbkcolor(RGB(rand() % 256 + 1, rand() % 256 + 1, rand() % 256 + 1));
cleardevice();
node snake[100] = { {5, 7}, {4, 7}, {3, 7}, {2, 7}, {1, 7} };
int length = 5;
int d = 3;
node food = createFood(snake, length);
while (1) {
cleardevice();
settextstyle(30, 0, _T("宋体"));
wchar_t wScore[105], wLength[105];
swprintf_s(wScore, L"%d", score);
swprintf_s(wLength, L"%d", length);
outtextxy(1, 1, _T("积分: "));
outtextxy(80, 1, wScore);
outtextxy(150, 1, _T("长度: "));
outtextxy(230, 1, wLength);
int left, top, right, bottom;
for (int i = 0; i < length; i++) {
left = snake[i].x * NODE_WIDTH;
top = snake[i].y * NODE_WIDTH;
right = (snake[i].x + 1) * NODE_WIDTH;
bottom = (snake[i].y + 1) * NODE_WIDTH;
solidrectangle(left, top, right, bottom);
}
left = food.x * NODE_WIDTH;
top = food.y * NODE_WIDTH;
right = (food.x + 1) * NODE_WIDTH;
bottom = (food.y + 1) * NODE_WIDTH;
setfillcolor(YELLOW);
solidrectangle(left, top, right, bottom);
setfillcolor(WHITE);
Sleep(200);
if (_kbhit()) {
int c = _getch();
switch (c)
{
case 'w':
if (d != 1)
d = 0;
break;
case 's':
if (d != 0)
d = 1;
break;
case 'a':
if (d != 3)
d = 2;
break;
case 'd':
if (d != 2)
d = 3;
break;
case 72:
if (d != 1)
d = 0;
break;
case 80:
if (d != 0)
d = 1;
break;
case 75:
if (d != 3)
d = 2;
break;
case 77:
if (d != 2)
d = 3;
break;
case 32:
setbkcolor(RGB(rand() % 256 + 1, rand() % 256 + 1, rand() % 256 + 1));
cleardevice();
}
}
node tail = snakeMove(snake, length, d);
if (snake[0].x == food.x && snake[0].y == food.y) {
if (length < 100) {
snake[length] = tail;
length++;
score += 10;
}
food = createFood(snake, length);
}
bool flag = false;
for (int i = 1; i < length; i++) {
if (snake[i].x == snake[0].x && snake[i].y == snake[0].y) {
flag = true;
break;
}
}
if (flag) {
settextstyle(200, 0, _T("宋体"));
outtextxy(500, 100, _T("GAME"));
Sleep(1000);
settextstyle(250, 0, _T("宋体"));
outtextxy(450, 400, _T("OVER"));
Sleep(1000);
exit(0);
}
}
getchar();
closegraph();
return 0;
}
拾:C++ GetAsyncKeyState() 优化
GetAsyncKeyState() 函数用于检查某个键是否被按下,前面的代码中必须要让黑色的窗口出现在屏幕上,十分麻烦,而且反应很慢,于是想到了这个函数。
GetAsyncKeyState() 是否简洁快速,反应很快,用过都会感觉很灵敏。这也是 windows.h 库里的一个函数。
于是我们修改代码得到最终结果:
#include <bits/stdc++.h>
#include <graphics.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <windows.h>
#define NODE_WIDTH 20
struct node {
int x, y;
};
int score = 0;
node snakeMove(node* snake, int length, int d) {
node tail = snake[length - 1];
for (int i = length - 1; i > 0; i--)
snake[i] = snake[i - 1];
node newHead;
newHead = snake[0];
if (d == 0)
newHead.y--;
else if (d == 1)
newHead.y++;
else if (d == 2)
newHead.x--;
else
newHead.x++;
if (newHead.x < 0 || newHead.x >= 75 || newHead.y < 0 || newHead.y >= 40) {
settextstyle(200, 0, _T("宋体"));
outtextxy(500, 100, _T("GAME"));
Sleep(1000);
settextstyle(250, 0, _T("宋体"));
outtextxy(450, 400, _T("OVER"));
Sleep(1000);
exit(0);
}
snake[0] = newHead;
return tail;
}
node createFood(node* snake, int length) {
node food;
while (1) {
food.x = rand() % 75;
food.y = rand() % 40;
int i;
for (i = 0; i < length; i++)
if (snake[i].x == food.x && snake[i].y == food.y)
break;
if (i < length)
continue;
else
break;
}
return food;
}
int main() {
srand(time(0));
initgraph(1500, 800);
setbkcolor(RGB(rand() % 256 + 1, rand() % 256 + 1, rand() % 256 + 1));
cleardevice();
node snake[100] = { {5, 7}, {4, 7}, {3, 7}, {2, 7}, {1, 7} };
int length = 5;
int d = 3;
node food = createFood(snake, length);
while (1) {
cleardevice();
settextstyle(30, 0, _T("宋体"));
wchar_t wScore[105], wLength[105];
swprintf_s(wScore, L"%d", score);
swprintf_s(wLength, L"%d", length);
outtextxy(1, 1, _T("积分: "));
outtextxy(80, 1, wScore);
outtextxy(150, 1, _T("长度: "));
outtextxy(230, 1, wLength);
int left, top, right, bottom;
for (int i = 0; i < length; i++) {
left = snake[i].x * NODE_WIDTH;
top = snake[i].y * NODE_WIDTH;
right = (snake[i].x + 1) * NODE_WIDTH;
bottom = (snake[i].y + 1) * NODE_WIDTH;
solidrectangle(left, top, right, bottom);
}
left = food.x * NODE_WIDTH;
top = food.y * NODE_WIDTH;
right = (food.x + 1) * NODE_WIDTH;
bottom = (food.y + 1) * NODE_WIDTH;
setfillcolor(YELLOW);
solidrectangle(left, top, right, bottom);
setfillcolor(WHITE);
Sleep(200);
if (GetAsyncKeyState('W') && d != 1) {
d = 0;
}
else if (GetAsyncKeyState('S') && d != 0) {
d = 1;
}
else if (GetAsyncKeyState('A') && d != 3) {
d = 2;
}
else if (GetAsyncKeyState('D') && d != 2) {
d = 3;
}
else if (GetAsyncKeyState(VK_UP) && d != 1) {
d = 0;
}
else if (GetAsyncKeyState(VK_DOWN) && d != 0) {
d = 1;
}
else if (GetAsyncKeyState(VK_LEFT) && d != 3) {
d = 2;
}
else if (GetAsyncKeyState(VK_RIGHT) && d != 2) {
d = 3;
}
else if (GetAsyncKeyState(VK_SPACE)) {
setbkcolor(RGB(rand() % 256 + 1, rand() % 256 + 1, rand() % 256 + 1));
cleardevice();
}
node tail = snakeMove(snake, length, d);
if (snake[0].x == food.x && snake[0].y == food.y) {
if (length < 100) {
snake[length] = tail;
length++;
score += 10;
}
food = createFood(snake, length);
}
bool flag = false;
for (int i = 1; i < length; i++) {
if (snake[i].x == snake[0].x && snake[i].y == snake[0].y) {
flag = true;
break;
}
}
if (flag) {
settextstyle(200, 0, _T("宋体"));
outtextxy(500, 100, _T("GAME"));
Sleep(1000);
settextstyle(250, 0, _T("宋体"));
outtextxy(450, 400, _T("OVER"));
Sleep(1000);
exit(0);
}
}
getchar();
closegraph();
return 0;
}
拾壹:C++ EasyX 画立体图形
EasyX 本身并没有画立体图形的函数,但我们可以通过二维画出三维的效果。
依靠刚刚认识的 line 等函数,我们可以间接实现画立体图形。
下面是画立方体的代码;
#include <graphics.h>
#include <conio.h>
void convert3Dto2D(int x, int y, int z, int& screenX, int& screenY, int viewWidth, int viewHeight, int depth)
{
screenX = x - y + (viewWidth >> 1);
screenY = ((x + y) >> 1) - z + depth + (viewHeight >> 1);
}
int main()
{
initgraph(640, 480);
int viewWidth = 640;
int viewHeight = 480;
int depth = 200;
int cube[8][3] = { {50, 50, 50}, {150, 50, 50}, {150, 150, 50}, {50, 150, 50},
{50, 50, 150}, {150, 50, 150}, {150, 150, 150}, {50, 150, 150} };
for (int i = 0; i < 4; i++)
{
int x1, y1, x2, y2;
convert3Dto2D(cube[i][0], cube[i][1], cube[i][2], x1, y1, viewWidth, viewHeight, depth);
convert3Dto2D(cube[(i + 1) % 4][0], cube[(i + 1) % 4][1], cube[(i + 1) % 4][2], x2, y2, viewWidth, viewHeight, depth);
line(x1, y1, x2, y2);
convert3Dto2D(cube[i + 4][0], cube[i + 4][1], cube[i + 4][2], x1, y1, viewWidth, viewHeight, depth);
convert3Dto2D(cube[((i + 1) % 4) + 4][0], cube[((i + 1) % 4) + 4][1], cube[((i + 1) % 4) + 4][2], x2, y2, viewWidth, viewHeight, depth);
line(x1, y1, x2, y2);
convert3Dto2D(cube[i][0], cube[i][1], cube[i][2], x1, y1, viewWidth, viewHeight, depth);
convert3Dto2D(cube[i + 4][0], cube[i + 4][1], cube[i + 4][2], x2, y2, viewWidth, viewHeight, depth);
line(x1, y1, x2, y2);
}
_getch();
closegraph();
return 0;
}
拾贰:C++ 游戏库配合 ofstream 和 ifstream
有时候,游戏需要存一下账号信息,下次运行还要用的,那么就不能用寻常方法了。
ifstream 可以从文件里读取信息,ofstream 可以往文件里放信息。
要读取信息首先是这两行代码用于找到文件:
string filePath = "C:\\example\\data.txt";
ifstream readFileStream(filePath);
然后可以用 is_open() 函数判断是否打开,接着用 getline 函数读取信息。
如果要放信息首先也是这两行代码用于找到文件:
string filePath = "C:\\example\\data.txt";
ofstream writeFileStream(filePath, ios::app);
然后也可以用 is_open() 函数判断是否打开,接着用 writeFileStream << newContent 的方式在文件里加入 newContent 这个字符串。
接下来是一个使用示例:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string filePath = "C:\\example\\data.txt";
ifstream readFileStream(filePath);
if (readFileStream.is_open()) {
cout << "文件内容如下:" << endl;
string line;
while (getline(readFileStream, line)) {
cout << line << endl;
}
readFileStream.close();
} else {
cout << "无法打开文件进行读取" << endl;
}
ofstream writeFileStream(filePath, ios::app);
if (writeFileStream.is_open()) {
string newContent = "\n这是通过程序添加的新行";
writeFileStream << newContent;
cout << "已成功向文件添加新内容: " << newContent << endl;
writeFileStream.close();
} else {
cout << "无法打开文件进行写入" << endl;
}
return 0;
}
拾叁:C++ 更高级的库
这里介绍的是较简单的游戏,不支持音效和 3D(音效只能用 windows.h 的 PlaySound 函数等),而且没那么好看,假如你想要制作更高级的游戏,建议用一下几个库:
| 库名 | 优点 | 适合类型 |
|---|---|---|
| Allegro 5 | 简单易学,支持图像、音频、字体、动画 | 2D 游戏(如平台跳跃、RPG) |
| SFML | C++ 接口优雅,支持高清纹理、音效、网络 | 2D/轻量 3D,跨平台 |
| SDL2 | 高性能,被 Unity、Godot 内部使用 | 2D 游戏、模拟器、引擎开发 |
| Raylib | 轻量、现代,支持 OpenGL,有中文文档 | 2D/3D 小游戏、原型开发 |
这些库的效果都很好,建议使用。
拾肆:结尾
看完这个文章后,希望你也能制作出自己的游戏!
感谢观看!