(三)制作人物动画
(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的值,应该在键盘响应函数或者鼠标响应函数或者计时器函数等中进行修改。
如:
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
}
这里之所以用的2和0,是因为在Draw函数里,对其乘以80了,因此效果是一样的。
(四)人物的移动拖着大地图移动
//以下是根据人物移动,拖着大地图显示部分走
//往右
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()来调整(因为宽度和高度在减少),正常应该是固定尺寸的
说明:
①显示范围800x600;
②当位于靠近边界200宽度的位置时,不移动。
③未设置禁止人物出边界的代码。
(五)在(四)的基础上,根据人物实际坐标导出人物在屏幕上的坐标
//根据人物的实际坐标、地图在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坐标 的坐标)
}
为了搭配上面使用,还应调整在内存DC上Draw函数的代码
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上
第一个参数不变;
第2,3个参数,调用函数,返回人物在DC上的坐标(跟背景图是独立的,但符合和背景图的相对坐标);
第4,5个参数,表示绘制的矩形有多大(80x80);
后面都保持不变。
四和五的大概原理如下图
(六)设置一个Hero类
用于控制人物的类,原理和上面其实是相同的,只是优化整合到一个类里面
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坐标 的坐标)
}