C++第一人称FPS1.0

本文介绍了使用C++编写一个简单的2D第一人称视角游戏,玩家在迷宫中移动并避开障碍物,利用chrono库处理时间间隔,控制台输出显示游戏状态。
摘要由CSDN通过智能技术生成

说是FPS其实就是一个走迷宫😓。

跟着b站上的视频学的打的,加了点注释

#include<iostream>
#include<Windows.h>
#include<vector>
#include <algorithm>
/*
关于chrono
是c++的一个时间库
https://blog.csdn.net/albertsh/article/details/105468687
*/
#include<chrono>
using namespace std;
#define pi 3.1415926535

//屏幕
int nScreenWidth = 120;//屏宽120
int nScreenHeight = 40;//屏高40
wchar_t* screen = nullptr;
//屏幕初始化函数
void init_Screen() {
	screen = new wchar_t[nScreenWidth * nScreenHeight];
}


//玩家信息
float fPlayerX = 8.0f;//玩家坐标
float fPlayerY = 8.0f;
float fPlayerA = 0.0f;//?
float fFov = pi / 4.0;//视野范围
float fDepth = 16;//视野深度



//场地
int nMapWidth = 16;
int nMapHeight = 16;
wstring map;
//场地初始化函数
void init_map(){
	map += L"################";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"#      #       #";
	map += L"#      #       #";
	map += L"#              #";
	map += L"#         ######";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"#              #";
	map += L"################";
}


//哈希函数的到离玩家不同距离的墙壁的符号
void GetWallHash(short &nshade ,float fDistanceToWall) {
	//扩展ASCII码字符对应UNICODE编码
	//https://blog.csdn.net/weixin_40013868/article/details/78072168
	if (fDistanceToWall <= fDepth / 4.0f)     nshade = 0x2588;
	else if (fDistanceToWall < fDepth / 3.0f) nshade = 0x2593;
	else if (fDistanceToWall < fDepth / 2.0f) nshade = 0x2592;
	else if (fDistanceToWall < fDepth)        nshade = 0x2591;
	else                                      nshade = ' ';//太远看不见
}
//哈希函数的到离玩家不同距离的地面的阴影
void GetFloorHash(short& nshade, float fDistanceToFlayer) {
	if (fDistanceToFlayer < 0.25)      nshade = '*';
	else if (fDistanceToFlayer < 0.5) nshade = '+';
	else if (fDistanceToFlayer < 0.75) nshade = '-';
	else if (fDistanceToFlayer < 0.9) nshade = '.';
	else                               nshade = ' ';
}
//判断玩家向此方向运动是否会撞墙
bool HitTheWall(int x,float felapsedTime) {
	float fX = fPlayerX; 
	float fY = fPlayerY; 
	bool WillItCrash = FALSE;
	switch (x)
	{
	case 1:
		fX+=sinf(fPlayerA) * 5.0f * felapsedTime;
		fY+=cosf(fPlayerA) * 5.0f * felapsedTime;
		if (map[(int)fY * nMapWidth + (int)fX] == '#')
			WillItCrash = TRUE;
		break;
	case 2:
		fX -= sinf(fPlayerA) * 5.0f * felapsedTime;
		fY -= cosf(fPlayerA) * 5.0f * felapsedTime;
		if (map[(int)fY * nMapWidth + (int)fX] == '#')
			WillItCrash = TRUE;
		break;
	default:
		break;
	}
	return WillItCrash;
}
int main() {
	//初始化屏幕
	init_Screen();
	//获取控制台地址
	HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	SetConsoleActiveScreenBuffer(hConsole);
	DWORD dwBytesWritten = 0;



	//初始化地图
	init_map();
	//得到时间戳  何为时间戳? 
	// 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数
	auto tp1 = chrono::system_clock::now();
	auto tp2 = chrono::system_clock::now();
	//game loop 
	while (1) {
		tp2 = chrono::system_clock::now();
		//duration类的对象表示时间间隔,单位秒
		chrono::duration<float>elapsedTime = tp2 - tp1;
		tp1 = tp2;
		//得到这次循环和上次循环的时间差
		float felapsedTime = elapsedTime.count();
		//游戏控件
		 /*
		关于GetAsyncKeyState
			是判断一个按键是否被按下
			https ://blog.csdn.net/FlowShell/article/details/5422395
		*/
		//旋转
		if (GetAsyncKeyState((unsigned short)'A') & 0x8000)
			fPlayerA -= (0.8f)* felapsedTime;
		if (GetAsyncKeyState((unsigned short)'D') & 0x8000)
			fPlayerA += (0.8f)* felapsedTime;
		//前进后退
		if (GetAsyncKeyState((unsigned short)'W') & 0x8000) {
			//撞墙检测
			if (!HitTheWall(1, felapsedTime)) {
				fPlayerX += sinf(fPlayerA) * 5.0f * felapsedTime;
				fPlayerY += cosf(fPlayerA) * 5.0f * felapsedTime;
			}
		}
		if (GetAsyncKeyState((unsigned short)'S') & 0x8000) {
			if (!HitTheWall(2, felapsedTime)) {
				fPlayerX -= sinf(fPlayerA) * 5.0f * felapsedTime;
				fPlayerY -= cosf(fPlayerA) * 5.0f * felapsedTime;
			}
		}

		for (int x = 0; x < nScreenWidth; x++) {
			
			float fRayAngle = (fPlayerA - fFov / 2.0f) + ((float)x / (float)nScreenWidth * fFov);
			//到墙的距离
			float fDistanceToWall = 0;
			//撞墙标志
			bool bHitWall = FALSE;
			//边界标志
			bool bBoundary = FALSE;
			//视野的单位向量,表示玩家看的方向
			float fEyeX = sinf(fRayAngle);
			float fEyeY = cosf(fRayAngle);
			
			while (!bHitWall && fDistanceToWall < fDepth) {
				fDistanceToWall += 0.1f;
				//看到的坐标                    
				int nTestX = (int)(fPlayerX + fEyeX * fDistanceToWall);
				int nTestY = (int)(fPlayerY + fEyeY * fDistanceToWall);
				//出界?
				if (nTestX < 0 || nTestX >= nMapWidth || nTestY < 0 || nTestY >= nMapHeight){
					bHitWall = TRUE;
					fDistanceToWall = fDepth;
				}
				else{
					//撞墙?
					//光线是入站的,测试以查看光线单元是否为墙块
					if (map[nTestY * nMapWidth + nTestX] == '#'){
						bHitWall = TRUE;
						//对墙壁增加边界
						vector<pair<float, float>>p;
						for(int tx=0;tx<2;tx++)
							for (int ty = 0; ty < 2; ty++){
								//墙角指向玩家的向量
								float vy = (float)nTestY + ty - fPlayerY;
								float vx = (float)nTestX + tx - fPlayerX;
								//墙角到玩家的距离
								float d = sqrt(vx * vx + vy * vy);
								float dot = (fEyeX * vx / d) + (fEyeY * vy / d);
								p.push_back({ d,dot });
							}
						//lambda表达式实现vector中pair的排序方法
						//实现墙角到玩家按距离排序
						sort(p.begin(), p.end(),
						[](const pair<int, int>& a, const pair<int, int>& b) { return a.first < b.first; });

						float fBound = 0.01;
						//三维层面只能看到立方体的三条边
						if (acos(p.at(0).second) < fBound)bBoundary = TRUE;
						if (acos(p.at(1).second) < fBound)bBoundary = TRUE;
						//if (acos(p.at(2).second) < fBound)bBoundary = TRUE;
					}
				}
				//天花板
				int nCeiling = (float)(nScreenHeight / 2.0) - nScreenHeight / ((float)fDistanceToWall);
				//地板
				int nFloor = nScreenHeight - nCeiling;

				short Wall_hash = ' '; 
				GetWallHash(Wall_hash,fDistanceToWall);
				if (bBoundary) Wall_hash = ' ';//边界
				for (int y = 0; y < nScreenHeight; y++) {
					if (y < nCeiling) {
						screen[y * nScreenWidth + x] = ' ';
					}
					else if (y > nCeiling && y <= nFloor) {
						screen[y * nScreenWidth + x] = Wall_hash;
					}
					else {
						short Floor_hash = ' ';
						float b = 1.0f - (((float)y - nScreenHeight / 2.0f) / ((float)nScreenHeight / 2.0f));
						GetFloorHash(Floor_hash, b);
						screen[y * nScreenWidth + x] = Floor_hash;
					}
				}
			}
		}
		//显示数据
		/*
		关于sprintf、swprintf与wsprintf的用法与区别
		https://blog.csdn.net/Zz22333/article/details/89084562
		*/
		swprintf_s(screen, 40, L"X=%3.2f, Y=%3.2f, A=%3.2f, FPS=%3.2f", fPlayerX, fPlayerY, fPlayerA, 1.0f / felapsedTime);
		//显示地图
		for (int nx = 0; nx < nMapWidth; nx++)
			for (int ny = 0; ny < nMapHeight; ny++)
				screen[(ny + 1) * nScreenWidth + nx] = map[ny * nMapWidth + nx];
		//显示玩家位置
		screen[((int)fPlayerY + 1) * nScreenWidth + (int)fPlayerX] = '*';
		screen[nScreenWidth * nScreenHeight - 1] = '\0';
		//绘制场地
		WriteConsoleOutputCharacter(hConsole, screen, nScreenHeight * nScreenWidth, { 0 , 0 }, &dwBytesWritten);
	}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
使用的 主要技术有: 1、大面积山脉渲染,使用Heightmap构造地形,7级 LOD 细节精度,地形无限重复循环 。 2、清晰、自然的地表贴图。 3、读取.MD2 、.MS3D 格式的3D模型文件。 4、粒子系统,产生爆炸、炊烟等特效。 5、太阳光晕。 6、使用Blend模拟Brightness/contrast调节图象亮度。 操作控制 可以在GameSetting菜单中设定: 1.视频属性 ( Video Setting ) 1).屏幕分辨率( Resolution ) 游戏率默认为800*600,在任务执行前可改变分辨率,但不会立即生效,只有在初始化任务时才改变分辨率。 2).屏幕亮度( Brigthness ) 在不同的硬件配置上,屏幕亮度往往表现出较大差异,通过该项可将亮度调节到最佳。 3).视野范围 ( Visible Distance ) 调节地形绘制的距离。对于配置较低的硬件,适当降低视野距离可提高帧速率,但由于远处地形网格较粗,对帧速率提高贡献并不大。 4).雾浓度 ( Fog Density ) 2.音频属性 ( Audio Setting ) 1).背景音乐(music) 可以打开或关闭背景音乐,可以调节音量。 音乐播放 audio/music/menu.mp3 ,如果你有自己喜爱的mp3音乐文件可以将它替换 menu.mp3 文件。 2).音效( sound ) 可以打开或关闭音效,可以调节音量。游戏中的枪声与人物的发声具有3D效果。 3.键盘操作 ( Keyboard Setting ) 以下操作可以更改 Up、Down、Left、Right设定移动操作。 Fire 射击 Jump 跳跃 Zoom 放大远处景物 Help 弹出帮助 修改方法:用鼠标点击选项,然后输入新的按键。 另外,游戏保留了几个开发模式下的操作: Page Up 提升视点高度 Page Down 降低视点高度 (可以看到地形绘制区域) L 线框模式 F 冻结所有敌人 V 隐身 N 敌人攻击力为零 I 隐藏房子 O 隐藏敌人 P 隐藏树木 在游戏运行中,按 Help 项的按键获得帮助。 4.鼠标操作 鼠标的移动可改变视角,默认设定左键为射击,右键为放大。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值