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);//计算阴影的xint 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);}

限制玩家区域

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916982.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916982.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity开发2D类银河恶魔城游戏学习笔记目录

Unity开发2D类银河恶魔城游戏学习笔记 Unity教程&#xff08;零&#xff09;Unity和VS的使用相关内容 玩家状态机 Unity教程&#xff08;一&#xff09;开始学习状态机 Unity教程&#xff08;二&#xff09;角色移动的实现 Unity教程&#xff08;三&#xff09;角色跳跃的实现…

智慧社区项目开发(三)——基于 Spring Boot 实现动态路由加载:从数据库到前端菜单的完整方案

在后台管理系统中&#xff0c;不同用户角色往往拥有不同的操作权限&#xff0c;对应的菜单展示也需动态调整。动态路由加载正是解决这一问题的核心方案 —— 根据登录用户的权限&#xff0c;从数据库查询其可访问的菜单&#xff0c;封装成前端所需的路由结构并返回。本文将详细…

Python在自动化与运维领域的核心角色:工具化、平台化与智能化

&#x1f4dd;个人主页&#x1f339;&#xff1a;慌ZHANG-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在 IT 系统日益复杂、运维任务持续增长的今天&#xff0c;自动化已成为企业基础设施管理的关键方向。Python 以其简洁的语法、强大…

RAG实战指南 Day 28:RAG系统缓存与性能优化

【RAG实战指南 Day 28】RAG系统缓存与性能优化 开篇 欢迎来到"RAG实战指南"系列的第28天&#xff01;今天我们将深入探讨RAG系统的缓存机制与性能优化策略。在实际生产环境中&#xff0c;RAG系统往往面临高并发、低延迟的需求&#xff0c;而合理的缓存设计和性能优…

swanlab实验优雅起名

init中的参数的作用project&#xff1a;整个实验的名字&#xff1b;experiment_name&#xff1a;在这个实验中&#xff0c;你的名字是什么&#xff1b; 比如说现在我们要进行对比实验&#xff0c;PEAN和Triflownet分别是对比方法的名字&#xff0c;这样的好处是&#xff0c;她们…

Nestjs框架: NestJS 核心机制解析 —— DI(依赖注入)容器与模块化工作原理

理解 NestJS 的 DI 管理机制 我们想要了解依赖注入&#xff08;Dependency Injection, DI&#xff09;最核心的工作逻辑NestJS 拥有自己的一套 DI 管理系统&#xff0c;它通过一个称为 DI 容器 的机制&#xff0c;来统一管理应用中所有类&#xff08;class&#xff09;的依赖关…

日语学习-日语知识点小记-构建基础-JLPT-N3阶段(12):文法+单词

日语学习-日语知识点小记-构建基础-JLPT-N3阶段&#xff08;12&#xff09;&#xff1a;文法单词 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰2、知识点&#xff11;ーたぶん 多分&#xff12;ーV&#xff08;て&#xff09;いく ・ V&…

【赵渝强老师】OceanBase租户的资源管理

OceanBase数据库是多租户的数据库系统&#xff0c;一个集群内可包含多个相互独立的租户&#xff0c;每个租户提供独立的数据库服务。在OceanBase数据库中&#xff0c;使用资源配置&#xff08;Unit Config&#xff09;、资源单元&#xff08;Unit&#xff09;和资源池&#xff…

8K、AI、低空智联,H.266能否撑起下一代视频通路?

一、&#x1f4c8; 爆发式增长的 AI 与视频数据&#xff1a;智能时代的“数据燃料革命” 随着生成式 AI、大模型推理、多模态理解等技术的迅猛发展&#xff0c;视频数据从“记录工具”转变为“感知基础设施”&#xff0c;其在现代智能系统中的战略地位日益凸显。 1️⃣ 视频数…

保姆级别IDEA关联数据库方式、在IDEA中进行数据库的可视化操作(包含图解过程)

本文以mysql为例&#xff0c;学会了Mysql&#xff0c;其它的数据库也是类似的模版~如果您觉得这边文章对你有帮助&#xff0c;可以收藏防止找不到~如果您觉得这篇文章不错&#xff0c;也感谢您的点赞对我创作的支持1.1 打开侧边栏的Database2.2 选择要连接的数据库&#xff08;…

33.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--财务服务--记账

这篇文章我们一起把记账模块从单体应用迁移到微服务架构中。记账模块的功能想必大家都已经了解了&#xff0c;主要是记录用户的收入和支出&#xff0c;以及对这些记录的删除修改和查询等操作。具体的功能可以参考单体应用专栏&#xff0c;在这里就不多讲了。我们现在一起开始迁…

Cursor结合Playwright MCP Server支持自动化

Cursor结合Playwright MCP Server支持自动化 今天分享一下 playwright MCP Server&#xff0c;其提供了浏览器自动化能力&#xff0c;使大型语言模型能够在真实的浏览器环境中与网页交互&#xff0c; 也可以执行任务&#xff0c;例如运行JavaScript、截屏和导航网页元素&…

Python 求梯形面积的程序(Program to find area of a Trapezoid)

梯形的定义&#xff1a; 梯形是凸四边形&#xff0c;至少有一对边平行。平行边称为梯形的底边&#xff0c;另外两条不平行的边称为梯形的腿。梯形也可以有两对底边。在上图中&#xff0c;CD || AB&#xff0c;它们构成底边&#xff0c;而另外两条边&#xff0c;即AD和BC&#…

C语言 —— 指针(4)

动态内存分配动态内存需要手动申请&#xff0c;手动归还&#xff0c;其内存是开辟在堆区。申请的函数为&#xff1a;void *malloc(size_t size) &#xff08;需包含头文件#include<stdlib.h>&#xff09;size&#xff1a;要分配的内存大小&#xff0c;以字节为单位。申请…

常用算法思想及模板

今天继续整理一些关于算法竞赛中C适用的一些模板以及思想。 保留x位小数 保留x位小数在C语言中可以使用printf中的"%.xf"来实现&#xff0c;但是很多C选手由于关闭了同步流&#xff0c;害怕cin、cout与scanf、printf混用容易出错&#xff0c;所以就给大家介绍一个强…

GitLab 仓库 — 常用的 git 命令

在公司的 gitlab 公共仓库中写代码做项目时&#xff0c;主要涉及以下常用 git 命令&#xff1a;一、单个命令讲解1. 拉取代码&#xff08;1&#xff09;git clone [仓库 URL]‌克隆远程仓库到本地&#xff08;需确保 URL 正确&#xff09; ‌&#xff08;‌2&#xff09;git pu…

【28】C# WinForm入门到精通 ——多文档窗体MDI【属性、方法、实例、源码】【多窗口重叠、水平平铺、垂直平铺、窗体传值】

文章目录1多文档窗体MDI2 基本设置3 实例&#xff1a;多窗口重叠、水平平铺、垂直平铺3.1 主窗口属性设置3.2 主窗口3.3 主窗口窗口添加MenuStrip菜单3.4 添加处理函数3.5 测试效果4 利用窗体参数定义进行传值4.1 在Form2、Form3添加相关控件4.2 Form3 定义函数public Form3(st…

【计算机科学与应用】基于Session欺骗攻击的Web应用程序防护

导读&#xff1a; 本文对Web应用程序开发中的Session欺骗攻击进行了阐述&#xff0c;详细讲解了防范Session欺骗攻击的三种传统方法&#xff0c;并给出了防范代码&#xff0c;分析了三种传统防范方法的不足&#xff0c;新设计了一种通过Referer信息验证来加强对Session欺骗的防…

yolo8+阿里千问图片理解(华为简易版小艺看世界)

✅ 实现目标 按下空格键 → 获取摄像头当前画面&#xff1b; 将图片上传给 大模型 接口&#xff0c;让其“看图说话”&#xff1b; 获取返回描述后&#xff0c;以字幕形式展示在图像画面上&#xff1b; 持续显示识别结果&#xff0c;直到下次按空格。 &#x1f9e0; 需要准…

【ee类保研面试】数学类---线性代数

25保研er&#xff0c;希望将自己的面试复习分享出来&#xff0c;供大家参考 part0—英语类 part1—通信类 part2—信号类 part3—高数类 part100—self项目准备 文章目录线性代数知识点大全**1. 余子式与代数余子式****2. 行列式的含义****3. 矩阵的秩&#xff08;Rank&#xf…