MFC学习笔记之二(制作人物动画+人物移动+地图拖曳)

2023-10-25 07:00

本文主要是介绍MFC学习笔记之二(制作人物动画+人物移动+地图拖曳),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

(三)制作人物动画

1)首先得有个人物图,比如说带4个方向的,比如说每个方向有四种形态的。

这样的话就有16张图了(他们还得尺寸相同),于是把这16张图放到一张图上,例如,每排放面向同一个方向的四个。

于是,就出现了像下面这张图

 

2)人物图出现了,那么为了让他们有不同的动画,自然不能像之前那样用Draw()函数了,毕竟,之前那个Draw()函数,只能描述在某张图上的某个区域画图。于是,使用Draw()的重载函数,其参数是

Draw(画在哪个DC起始x坐标起始y坐标, 图的宽度图的高度, 源图的起始x坐标源图的起始y坐标源图截取的宽度, 源图截取的高度);

总的来说,可以参考BitBlt函数来理解。

①画在哪个DC,跟其他的一样,例如画在内存DC上,就跟之前一样,比如cacheDC;

②第2,3个参数,指你在这个DC上的哪里开始画。这个xy坐标指的相对于DC上加载的(比如说DC加载位图)左上顶点(例如DC上加载一个图,左上的顶点是0,0)的坐标。

③第4,5个参数指这个图画的有多大,例如80,80就是指从第2,3个坐标开始计算,往右80和往下80,于是形成了一个矩形区域,这张图就画在这个矩形区域之中;

④第6,7个参数指从源图(也就是调用这个Draw函数的对象)的哪里(起点坐标)开始复制,例如假如这张图是320x320,那么160,160就是指从这张图的中心开始复制(于是左上、右上、左下这三个部分就被忽视了);

⑤第8,9个参数指从源图的起点坐标圈出多大一片矩形区域进行复制。

总的来说,2~5个参数是从第一个参数提供的DC中圈出一片区域,这个区域是被绘制的区域。第6~9个参数是指从调用对象的图片中圈出一片区域,这个区域的图片就被复制到2~5个参数提供的那个区域之中。

如果后者比前者的像素,那么被复制的图片就会被按比例缩小,相反则放大,因此应保证其尽量是一致的。

 

3)既然Draw函数可以选择性的截取人物图的某一部分,那么我们就得想办法设置其截取符合我们需要的部分。于是,需要有参数来描述方向(决定选择第几行的),还要有参数决定帧数(决定选择该行的第几个)。

因为不同动作和方向,只需要改变截取的部分,而每部分大小相同,因此分别给一个参数,让其可以影响第6,7个参数即可(因为高度和宽度是固定的,所以8,9参数保持不变)。因此声明int frameint direct(前者影响行,后者影响列)。

假如图片中,每个人物图都是80x80的尺寸,于是调用1x1图是,6,7参数为0,0,调用1x2图,6,7参数为80,02x2图则为80,80。因此,给frame赋值,让其表示的数字可以为0,80,160,240,给direct赋值,让其在不同方向时表示的数字可以为0,80,160,240。

因此,可以设置在按下w时(向上),让direct的值为240(表示第4行),其他类推。设置每次按下按键,人物动作都不同(每次加80,当超过240则自动归0)。

然后,让directframe的值能分别影响第6,7个参数(即让第6个参数为frame,让第7个参数为direct)。

而给framedirect初始赋值,应在OnPaint()函数之前(否则会导致每次都给初始赋值),例如在PreCreateWindow()函数之中。

而修改frame和direct的值,应该在键盘响应函数或者鼠标响应函数或者计时器函数等中进行修改。

如:

<span style="font-size:14px;">void CChildView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{// TODO: 在此添加消息处理程序代码和/或调用默认值switch (nChar){case 'D':myHero.x += 10;    //向右移动10个像素的单位  myHero.direct = 2;	//修改方向m_client.left -= 30;	//让背景图移动m_client.right -= 30;	//让背景图移动
.......省略部分}myHero.frame++;	//修改帧数if (myHero.frame == 4)myHero.frame = 0;		//假如超出第4帧,则归0
}</span>

这里之所以用的20,是因为在Draw函数里,对其乘以80了,因此效果是一样的。


(四)人物的移动拖着大地图移动


<span style="font-size:14px;">	//以下是根据人物移动,拖着大地图显示部分走//往右if (myHero.x + m_client.left > 600 && m_client.left + m_client.Width() > 800)		//如果人物的坐标在屏幕上位于靠右的200宽度了m_client.left = 600 - myHero.x;//往下if (myHero.y + m_client.top > 400 && m_client.top + m_client.Height() > 600)m_client.top = 400 - myHero.y;//往左if (myHero.x + m_client.left < 200 && m_client.left < 0)		//如果人物的坐标在屏幕上位于靠右的200宽度了m_client.left = 200 - myHero.x;//往上if (myHero.y + m_client.top < 200 && m_client.top < 0)m_client.top = 200 - myHero.y;m_client.right = m_client.left + 1800;	//根据屏幕的最左边的坐标,调整最右边的m_client.bottom = m_client.top + 1448;	//根据屏幕最上面的坐标,调整最下面的,这里不能用Width()和Height()来调整(因为宽度和高度在减少),正常应该是固定尺寸的</span>

说明:

①显示范围800x600;

②当位于靠近边界200宽度的位置时,不移动。

③未设置禁止人物出边界的代码。

 

(五)在(四)的基础上,根据人物实际坐标导出人物在屏幕上的坐标

<span style="font-size:14px;">//根据人物的实际坐标、地图在DC上画画时的坐标,以及地图宽度,返回人物在屏幕上显示的坐标
int GetHeroX(int x, int mapx,int mapwidth)	//参数1是人物的实际x坐标(即相对于大地图左上部分的坐标),参数2是地图的x坐标,参数3是大地图的宽度,返回值是人物在屏幕上的坐标
{if (x < 200)return x;	//当小于200时,显示部分位于地图最左边并且不动else if (x>mapwidth - 200)return x - (mapwidth - 800);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分else return x + mapx;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
int GetHeroY(int y, int mapy, int mapheight)	//返回人物在屏幕上的y坐标
{if (y < 200)return y;	//当小于200时,显示部分位于地图最左边并且不动else if (y>mapheight - 200)return y - (mapheight - 600);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分else return y + mapy;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
</span>

为了搭配上面使用,还应调整在内存DCDraw函数的代码


<span style="font-size:14px;">m_bg.Draw(m_cacheDC, m_client);	//绘制背景图,代码保持不变(由于m_client变化了,因此实际绘制是有变化的)
myHero.m_hero.Draw(m_cacheDC, GetHeroX(myHero.x, m_client.left, m_client.Width()),GetHeroY(myHero.y, m_client.top, m_client.Height()), 80, 80,myHero.frame * 80, myHero.direct * 80, 80, 80);		//绘制英雄图,坐标是英雄,绘制到缓冲DC上</span>

第一个参数不变;

2,3个参数,调用函数,返回人物在DC上的坐标(跟背景图是独立的,但符合和背景图的相对坐标);

4,5个参数,表示绘制的矩形有多大(80x80);

后面都保持不变。

四和五的大概原理如下图



(六)设置一个Hero

用于控制人物的类,原理和上面其实是相同的,只是优化整合到一个类里面

<span style="font-size:14px;">class Hero
{int x;	//人物的x坐标(指左上部分)int y;//人物的y坐标(指左上部分)int frame;		//人物的帧数int direct;		//人物的方向
public:Hero(int X, int Y, int Frame, int Direct) :x(X), y(Y), frame(Frame), direct(Direct) {}	//默认构造函数CImage m_hero;	//人物图int& GetX() { return x; }	//获得X坐标,可以通过这个函数修改x坐标(直接赋值的形式)int&GetY() { return y; }int GetFrame() { return frame; }	//获得帧数,用于绘图时决定是哪一帧int& GetDirect() { return direct; }	//获得方向,用于绘图时决定是哪一个方向的图,鼠标直接指向时,需要通过其修改方向int GetHeroX(CRect &m_client);	//根据背景图和实际坐标,获得在DC上的坐标int GetHeroY(CRect &m_client);void addX(int X);	//增加参数位移(包含正负)void addY(int Y);void addFrame();	//帧数变换void SetClient(CRect &m_client);	//设置背景图在绘制时的坐标,人物移动拖动地图时使用
};#include "stdafx.h"
#include "ChildView.h"void Hero::addX(int X)
{x += X;    //向右移动10个像素的单位  if (X > 0)direct = 2;	//修改方向elsedirect = 1;
}
void Hero::addY(int Y)
{y += Y;    //向右移动10个像素的单位  if (Y > 0)direct = 0;	//修改方向elsedirect = 3;}
void Hero::addFrame()
{frame++;if (frame > 3)frame = 0;
}
void Hero::SetClient(CRect &m_client)	//注意,这里第二个参数是背景图的宽度
{if (x < 0)x = 0;if (y < 0)y = 0;if (x > m_client.Width() - 80)x = m_client.Width() - 80;if (y > m_client.Height() - 80)y = m_client.Height() - 80;int wi = m_client.Width();int he = m_client.Height();//往右if (x + m_client.left > 600 && m_client.left + m_client.Width() > 800)		//如果人物的坐标在屏幕上位于靠右的200宽度了m_client.left = 600 - x;//往左if (x + m_client.left < 200 && m_client.left < 0)		//如果人物的坐标在屏幕上位于靠右的200宽度了m_client.left = 200 - x;m_client.right = m_client.left + wi;	//根据屏幕的最左边的坐标,调整最右边的//往下if (y + m_client.top > 400 && m_client.top + m_client.Height() > 600)m_client.top = 400 - y;//往上if (y + m_client.top < 200 && m_client.top < 0)m_client.top = 200 - y;m_client.bottom = m_client.top + he;	//根据屏幕最上面的坐标,调整最下面的,这里不能用Width()和Height()来调整(因为宽度和高度在减少),正常应该是固定尺寸的
}
//根据人物的实际坐标、地图在DC上画画时的坐标,以及地图宽度,返回人物在屏幕上显示的坐标
int Hero::GetHeroX(CRect &m_client)	//参数1是人物的实际x坐标(即相对于大地图左上部分的坐标),参数2是地图的x坐标,参数3是大地图的宽度,返回值是人物在屏幕上的坐标
{if (x < 200)return x;	//当小于200时,显示部分位于地图最左边并且不动else if (x>m_client.Width() - 200)return x - (m_client.Width() - 800);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分else return x + m_client.left;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
int Hero::GetHeroY(CRect &m_client)	//返回人物在屏幕上的y坐标
{if (y < 200)return y;	//当小于200时,显示部分位于地图最左边并且不动else if (y>m_client.Height() - 200)return y - (m_client.Height() - 600);	//当大于地图宽度-200时,显示部分位于地图最右边并且不动,mapwidth-800是减去地图左边不显示的部分else return y + m_client.top;	//如果在中间,则坐标是人物的x坐标-地图的x左边(相对于地图开始显示部分的x坐标 的坐标)	
}
</span>


这篇关于MFC学习笔记之二(制作人物动画+人物移动+地图拖曳)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/280832

相关文章

使用Folium在Python中进行地图可视化的操作指南

《使用Folium在Python中进行地图可视化的操作指南》在数据分析和可视化领域,地图可视化是一项非常重要的技能,它能够帮助我们更直观地理解和展示地理空间数据,Folium是一个基于Python的地... 目录引言一、Folium简介与安装1. Folium简介2. 安装Folium二、基础使用1. 创建

Qt把文件夹从A移动到B的实现示例

《Qt把文件夹从A移动到B的实现示例》本文主要介绍了Qt把文件夹从A移动到B的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录如何移动一个文件? 如何移动文件夹(包含里面的全部内容):如何删除文件夹:QT 文件复制,移动(

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Python重命名文件并移动到对应文件夹

《Python重命名文件并移动到对应文件夹》在日常的文件管理和处理过程中,我们可能会遇到需要将文件整理到不同文件夹中的需求,下面我们就来看看如何使用Python实现重命名文件并移动到对应文件夹吧... 目录检查并删除空文件夹1. 基本需求2. 实现代码解析3. 代码解释4. 代码执行结果5. 总结方法补充在

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

使用Python制作一个PDF批量加密工具

《使用Python制作一个PDF批量加密工具》PDF批量加密‌是一种保护PDF文件安全性的方法,通过为多个PDF文件设置相同的密码,防止未经授权的用户访问这些文件,下面我们来看看如何使用Python制... 目录1.简介2.运行效果3.相关源码1.简介一个python写的PDF批量加密工具。PDF批量加密

Qt QWidget实现图片旋转动画

《QtQWidget实现图片旋转动画》这篇文章主要为大家详细介绍了如何使用了Qt和QWidget实现图片旋转动画效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、效果展示二、源码分享本例程通过QGraphicsView实现svg格式图片旋转。.hpjavascript

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert