C++游戏开发(2)

直接上代码

1.首先是头文件编写

#include <iostream>

#include <graphics.h>

#include <string>

2,添加画布

长1280,宽720

initgraph(1280, 720);

3.添加主循环

bool running = true;

while(runing)

{

}

4.定义结构体变量msg

ExMessge msg;

5.开启渲染缓冲区

BeginBatchDraw();//开启第二张画布

cleardevice();//清屏

FlushBatchDraw();//在第二张画布里画画

EndBatchDraw();//切换画布

6.按键或鼠标移动检测

peekmessage是WINDOWS自带的API函数,它可以检测外部输入设备的状态,比如你鼠标动一下,键盘按一下,它都能检测得到,然后将信号赋值给第一个参数结构体。

while (peekmessage(&msg))//循环检测按键或者鼠标移动

{

}

7.减少内存占用

DWORD start_time = GetTickCount();//记录下开始的时间

DWORD end_time = GetTickCount();//记录下结束的时间

DWORD delta_time = end_time - start_time;//记录下循环一次的时间

if (delta_time < 1000 / 144)//用来实现帧数,为1s投放144张画面,计算差值,用来弥补时间

{

Sleep(1000 / 114 - delta_time);

}

然后是总的初始状态设置。

复制代码
#include <iostream>
#include <graphics.h>
int main()
{
    initgraph(1280, 720);
    
    bool running = true;
    ExMessage msg;
    BeginBatchDraw();//开启第二张画布
    while (running)
    {
        DWORD start_time = GetTickCount();
        while (peekmessage(&msg))
        {

        }
        cleardevice();//清屏
        FlushBatchDraw();//在第二张画布里画画

        DWORD end_time = GetTickCount();
        DWORD delta_time = end_time - start_time;
        if (delta_time < 1000 / 144)
        {
            Sleep(1000 / 114 - delta_time);
        }
    }

    EndBatchDraw();//切换画布
    return 0;
}

画图在哪里画?

一定要放在清屏函数后,和调用画布前。

正式开始项目

1.加载资源。将素材图片放到项目的工程目录下面,这里也是项目的根目录

2.放置图片,这个函数放在clear的后面,开启第二张画布的前面

IMAGE img_background;//定义图片

loadimage(&img_background, _T("img/background.png"));//加载图片

第一个参数是图片的地址,第二个是图片在工程绝对路径,以工程路径为根路径,在img文件下。

putimage(0, 0, &img_background);//放置图片

如何让动画动起来?

角色动画分为两类,一种是序列帧动画,一种是关键帧动画

序列帧动画由一组图片构成,通过不断切换动画来实现动起来,借助视觉暂留效应。

关键帧动画就是通过几个点动来衬托整个物体动起来。

首先是初始化

int idx_current_anim;//用来记录索引,实现循环播放一组图片。

const int PLAYER_ANIM_NUM = 6;//记录一组图片的最大值

IMAGE img_player_left[PLAYER_ANIM_NUM];//用来记录一组图片

加载图片,告诉编译器图片的位置。 wstring 是拼接函数。loadimage是加载图片的函数。

复制代码
void LoadAnimation()
{
    for (size_t i = 0; i < PLAYER_ANIM_NUM; i++)
    {
        wstring path = L"img/player_left_" + to_wstring(i) + L".png";
        loadimage(&img_player_left[i], path.c_str());
    }
}

static int counter = 0;//记录游戏帧

if(++counter%5==0)//实现执行5次循环就切换下一张图片

idx_current_anim++;
//循环播放操作,PLAYER_ANIM_NUM=6,idx_current_anim=6时,将其赋值为0,相当于重新赋值为0,和if -else类似。

idx_current_anim = idx_current_anim % PLAYER_ANIM_NUM;

等价于

if (idx_current_anim == 6)

{

idx_current_anim = 0;

}

初始化加载

记住,初始化动画加载函数后一定要放在主函数里面调用!

动画周围正方形多余部分

图中,动画图与背景图格格不入。因为人物周围的白框太出戏了。

这里引用的是博主"晚晶"的代码。文章链接如下:

EasyX图片透明度

void putpicture(int dstx, int dsty, IMAGE* img, COLORREF color, int alpha) {//0~255 255表示不透明

DWORD* imgp = GetImageBuffer(img);

DWORD* bgimgp = GetImageBuffer();

int w, bw, h, i, j;

w = img->getwidth();

bw = getwidth();

h = img->getheight();

color += 0xff000000;

if (alpha < 0)alpha = 0;

else if (alpha > 255)alpha = 255;

for (i = 0; i < h; ++i)

for (j = 0; j < w; ++j)

if (imgp[i * w + j] != color)

bgimgp[(i + dsty) * bw + j + dstx] = RGB(

((int)(alpha / 255.0 * GetRValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetRValue(bgimgp[(i + dsty) * bw + j + dstx]))),

((int)(alpha / 255.0 * GetGValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetGValue(bgimgp[(i + dsty) * bw + j + dstx]))),

((int)(alpha / 255.0 * GetBValue(imgp[i * w + j]) + (1 - alpha / 255.0) * GetBValue(bgimgp[(i + dsty) * bw + j + dstx])))

);

}

第一个参数是x,第二个参数是y坐标,第三个参数图片应用地址,第四个参数是RGB值。第五个是透明度大小(0-255,255是完全不透明)

将这段代码进行调用,然后用putimage展示背景图,博主代码用来实现透明度图片,这里的背景图片最好是黑色更好,白色不好搞。

putimage(0, 0, &img_background);//放置图片

for (int toumdu = 0; toumdu < 255; toumdu++)

{

putpicture(100, 50, &img_player[idx_current_anim], RGB(255, 255, 255), toumdu);//放置图片

}

第二种方法:

//设置透明度

#pragma comment(lib,"MSIMG32.LIB")

inline void putimage_alpha(int x, int y, IMAGE* img)

{

int w = img->getwidth();

int h = img->getheight();

AlphaBlend(GetImageHDC(NULL), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });

}
putimage_alpha(500, 500, &img_player_left[idx_current_anim]);

直接使用这个作为渲染

控制角色移动

首先创建一个角色初始位置。

POINT player_pos = { 500,500 };

移动情况

if (msg.message == WM_KEYDOWN)

{

switch (msg.vkcode)

{

case VK_UP:

player_pos.y -= PLAYER_SPEED; break;

case VK_DOWN:

player_pos.y += PLAYER_SPEED; break;

case VK_LEFT:

player_pos.x -= PLAYER_SPEED; break;

case VK_RIGHT:

player_pos.x += PLAYER_SPEED; break;

}

}

定义移动速度

int PLAYER_SPEED=10;//玩家速度,其实就是一次位置移动大小

设置边界,只能在指定范围内移动,超过则原地不动

y的范围是30<y<600

if (player_pos.y > 600)

{

player_pos.y = 600;

}

if (player_pos.y < 30)

{

player_pos.y = 30;

}

x的范围是30<x<1200

if (player_pos.x > 1200)

{

player_pos.x = 1200;

}

if (player_pos.x < 30)

{

player_pos.x = 30;

}

移动顿挫感

每当我们长按一个移动键移动角色的过程中,角色会先动一下,然后,再连续移动。这是因为我们按下一个键后,WIN会记下一个消息,但如果是连续按下的话,就会等待一段时间后才连续记录消息。这是系统的原因,比如我们在打字过程中,长按一个j键,也是先显示一个j,然后才突然显示很多个j

解决办法

改变游戏人物的移动逻辑,之前是按下哪个键,就怎样怎样,松开之后就不执行了,只是检测有没有按下。

现在的逻辑是:按下某个键,移动,松开某个键,停止移动,同时检测按键的按下,松开。

设置4个bool状态,分别表示上下左右,初始值为false

bool is_move_up = false;

bool is_move_down = false;

bool is_move_left = false;

bool is_move_right = false;

按键循环采集采用if -if

while (peekmessage(&msg))//获取一个消息

{

//按键被按下

if (msg.message == WM_KEYDOWN)

{

cout << "报告!按键被按下,请求指示!" << endl;

switch (msg.vkcode)

{

case 0x57:is_move_up=true; cout << "向上移动" << endl;; break;

case 0x53:is_move_down=true; cout << "向下移动" << endl; break;

case 0x44:is_move_right =true; cout << "向右移动" << endl; break;

case 0x41:is_move_left=true; cout << "向左移动" << endl; break;

}

}

else if (msg.message == WM_KEYUP)

{

cout << "按键弹起,请求指示!" << endl;

switch (msg.vkcode)

{

case 0x57:is_move_up = false; cout << "向上停止" << endl;; break;

case 0x53:is_move_down = false; cout << "向下停止" << endl; break;

case 0x44:is_move_right = false; cout << "向右停止" << endl; break;

case 0x41:is_move_left = false; cout << "向左停止" << endl; break;

}

}

然后再设置true的情况

/*野猪移动状态函数块*/

{

if (is_move_up) player_pos.y -= MOTION_SPEED;

if (is_move_down) player_pos.y += MOTION_SPEED;

if (is_move_right) player_pos.x += MOTION_SPEED;

if (is_move_left) player_pos.x -= MOTION_SPEED;

putimage_alpha(player_pos.x, player_pos.y, &ENEMY_LEFT[idx_current_anim]);

}

关于冲突按键ctrl

按下W S A 会间接触发"按下ctrl键",所以在使用按键定义时一定要注意,不要在ctrl判断里面加循环,或者system("pause"),system("cls")。

面向对象编程

首先引入vector容器

创建类和容器

复制代码
class Animation
{
public:
	Animation()
	{

	}
	~Animation()
	{

	}
private:
		int interval_ms=0;
	vector<IMAGE*>frame_list;
};

把加载图片资源的部分放在构造 函数里面

编写加载图片的函数在构造函数里面

复制代码
	Animation(LPCTSTR path,int num,int interval)
	{
		interval_ms = interval;
		TCHAR path_file[256];
		for (size_t i = 0; i < num; i++)
		{
			_stprintf_s(path_file, path, i);
			IMAGE* frame = new IMAGE();
			loadimage(frame, path_file);
            frame_list.push_back(frame);//添加到vector容器中
		}
	}

记住要释放内存。在析构函数中实现

复制代码
for (size_t i = 0; i < frame_list.size(); i++)
	delete frame_list[i];//释放vectro容器的所有值

编写帧索引更新和渲染的代码

用计数器和计时器来控制动画的帧更新有什么区别?

计数器的帧更新略微不稳定有时候1帧的时间长,有时短,但是计时器控制的帧是肯定比计数器稳定

复制代码
void Play(int x, int y, int delta)
{
	timer += delta;
	if (timer >= interval_ms)
	{
		idx_frame = (idx_frame + 1) % frame_list.size();
		timer = 0;
	}
}

在private中定义
private:
	int idx_frame = 0;//动画帧索引
	int timer = 0;//动画计时器

初始化

复制代码
	Animation anim_left_player(_T("img/player_left_%d.png"), 6, 45);//初始化左边图片
	Animation anim_right_player(_T("img/player_right_%d.png"), 6, 45);//初始化右边图片

翻转角色

复制代码
void DrawPlayer(int delta, int dir_x)
{
	static bool facing_left = false;
	if (dir_x < 0)facing_left = true;
	else if (dir_x > 0)facing_left = false;
	if (facing_left)anim_left_player.Play(player_pos.x, player_pos.y, delta);
	else anim_right_player.Play(player_pos.x, player_pos.y, delta);
}

添加阴影

复制代码
IMAGE img_shadow;//初始化玩家脚下的阴影
loadimage(&img_shadow, _T("img/shadow_player.png"));

定义阴影三度

复制代码
const int PLAYER_WIDTH = 80;//玩家宽度
const int PLAYER_HEIGHT = 80;//玩家高度
const int SHADOW_WIDTH = 32;//阴影宽度

计算阴影坐标,代码放到翻转函数中

复制代码
	int pos_shadow_x = player_pos.x + (PLAYER_WIDTH / 2 - SHADOW_WIDTH / 2);//计算阴影的x
	int pos_shadow_y = player_pos.y + PLAYER_HEIGHT - 8;//计算阴影的y
putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow);

然后开始测试

把代码放进循环中

复制代码
DrawPlayer(1000 / 100, Go_right-Go_left);

可以发现一个问题,就是玩家在走斜线时移动速度比左右上下快不少

使用代码

复制代码
		//防止往斜上方走速度过快
		int dir_x = Go_right - Go_left;
		int dir_y = Go_down-Go_up;
		double len_dir = sqrt(dir_x * dir_x + dir_y * dir_y);
		if (len_dir != 0)
		{
			double normalized_x = dir_x / len_dir;
			double normalized_y = dir_y / len_dir;
			player_pos.x += (int)(Player_speed * normalized_x);
			player_pos.y += (int)(Player_speed * normalized_y);
		}

限制玩家区域

相关推荐
风继续吹..1 小时前
后台管理系统权限管理:前端实现详解
前端·vue
yuanmenglxb20042 小时前
前端工程化包管理器:从npm基础到nvm多版本管理实战
前端·前端工程化
我不吃饼干2 小时前
【TypeScript】三分钟让 Trae、Cursor 用上你自己的 MCP
前端·typescript·trae
你的电影很有趣3 小时前
lesson30:Python迭代三剑客:可迭代对象、迭代器与生成器深度解析
开发语言·python
小杨同学yx4 小时前
前端三剑客之Css---day3
前端·css
程序员编程指南4 小时前
Qt 嵌入式界面优化技术
c语言·开发语言·c++·qt
q__y__L4 小时前
C#线程同步(二)锁
开发语言·性能优化·c#
二川bro5 小时前
第二篇:Three.js核心三要素:场景、相机、渲染器
开发语言·javascript·数码相机
云泽8085 小时前
数据结构前篇 - 深入解析数据结构之复杂度
c语言·开发语言·数据结构