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

(三)制作人物动画

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

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

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

 

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

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

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

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

②第2,3个参数,指你在这个DC上的哪里开始画。这个x、y坐标指的相对于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 frame和int direct(前者影响行,后者影响列)。

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

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

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

而给frame和direct初始赋值,应在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>

这里之所以用的2和0,是因为在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>

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

<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;	//修改方向
	else
		direct = 1;
}
void Hero::addY(int Y)
{
	y += Y;    //向右移动10个像素的单位
	if (Y > 0)
		direct = 0;	//修改方向
	else
		direct = 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>
时间: 2025-01-26 09:45:11

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

VSTO学习笔记(二)Excel对象模型

原文:VSTO学习笔记(二)Excel对象模型 上一次主要学习了VSTO的发展历史及其历代版本的新特性,概述了VSTO对开发人员的帮助和效率提升.从这次开始,将从VSTO 4.0开始,逐一探讨VSTO开发中方方面面,本人接触VSTO时间不长,也是一次尝试.鉴于Excel在整个Office家族中的重要地位,故先从Excel开始介绍,后续内容会陆续介绍Word.PowerPoint.Outlook.InfoPath等.由于VSTO 4.0建立在Office 2010基础之上,先介绍一下Office

Jquery 学习笔记(二)jQuery性能优化指南

Jquery 学习笔记(二) -jQuery性能优化指南 2009年11月30日 一 作者:   邦畿千里   1,总是从ID选择器开始继承 在jQuery中最快的选择器是ID选择器,因为它直接来自于JavaScript的getElementById()方法. 例如有一段HTML代码: <div id="content"> <form method="post" action="#"> <h2>交通信号灯<

C#开发WINDOWS应用程序时消息的处理(C#学习笔记之二)

作者:浙江省温岭市电信局 王骏WINDOWS应用程序是靠消息驱动的,在VC中我们通过CLASSWIZARD可以为某窗口类添加消息处理函数,CLASSWIZARD将为你添加消息映射,对于WINDOWS消息,生成的消息处理函数重载了基类的虚拟方法.而在C#中如何处理消息呢?本文针对VS.NET BETA1环境下的C#简单地介绍WINDOWS消息以及自定义消息的处理方法.示例代码下载 17K一.生成一个名为MSGApplication的工程工程的建立方法请参考:C#学习笔记之一二.处理WM_PAINT

WPF and Silverlight学习笔记(二十五)

WPF and Silverlight学习笔记(二十五):使用CollectionView实现对绑定数据的排序.筛选.分组 在第二十三节,我们使用CollectionView实现了对于绑定数据的导航,除导 航功能外,还可以通过CollectionView对数据进行类似于DataView的排序.筛选 等功能. 一.数据的排序: 使用第二十四节的数据源,查询所有 的产品信息: 1: <Window x:Class="WPF_24.CollectionViewSortData" 2:

HTML5 video标签(播放器)学习笔记(二):播放控制

HTML5 video标签(播放器)学习笔记(二):播放控制 本文的目录: 1.获取影片总时长 2.播放.暂停 3.获取影片已播放时间和设置播放点 4.音量的获取和设置 第一.获取影片总时长 对播放器(video)操作,首先要得到的是影片的一些信息,其中一个就是总时长,除了内容以为,总时长也是第一时间要显示的.在对video进行操作的的前先给video标签添加一个ID,这样方便我们获取video元素 代码如下: <video id="myVideo" controls prelo

kvm虚拟化学习笔记(十二)之kvm linux虚拟机在线扩展磁盘

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://koumm.blog.51cto.com/703525/1295296 KVM虚拟化学习笔记系列文章列表 ---------------------------------------- kvm虚拟化学习笔记(一)之kvm虚拟化环境安装http://koumm.blog.51cto.com/703525/1288795 kvm虚拟化学习笔记(二)之linux kvm虚拟机安装 h

Swift学习笔记(1)过渡动画(CATransition和UIViewAnimation)的用法

Swift学习笔记(1)过渡动画(CATransition和UIViewAnimation)的用法 CATransition和UIViewAnimation是场景切换时常用的两种过渡动画 目录 Swift学习笔记1过渡动画CATransition和UIViewAnimation的用法 目录 CATransition CATransition的type属性 CATransition的subtype属性 代码示例 UIViewAnimationTransition UIViewAnimationTr

Akka学习笔记(二):Actor Systems

Akka学习笔记(二):Actor Systems 图中表示的是一个Actor System,它显示了在这个Actor System中最重要实体之间的关系. 什么是actor,是一个封装了状态和行为的对象,每个actor都通过message交流,从自己的mailbox中读取别的actor发送的消息. 注意: ActorSystem是重量级的对象,会创建1...N个线程,所以一个application一个ActorSystem. 层次结构 假设有一个actor,它的一个功能过于复杂,为了降低复杂度

Mysql学习笔记(二)数据类型 补充

原文:Mysql学习笔记(二)数据类型 补充 PS:简单的补充一下数据类型里的String类型以及列类型... 学习内容: 1.String类型 2.列类型存储需求   String类型: i.char与varchar char与varchar的类型相似,但是他们的保存方式和检索方式不同... char的存储结构是固定长度的存储...即指定了几个字节,那么就占用几个字节,如char(4),那么无论存入的是什么字串,那么都占用四个字节...char的 可表示长度范围为0-255的任何值,当保存的字