首页 > 代码库 > 动画精灵的实现
动画精灵的实现
1. 动画精灵概念
动画就是动态的画面,在计算机中表现为一种运行时数据结构和算法。数据结构表示动画的存储方式,可以事先存储,也可以运行时计算获得。
而算法则声明如何将这种数据结构映射到屏幕上。
精灵是一个渲染单元,存储一些表示如何渲染到屏幕上的数据。
动画精灵是一种特殊的精灵,因此这次的目标就是创建一个精灵类(Sprite)的子类(AnimatedSprite).
2. Sprite类
在设计之前,需要预览一下Sprite类的代码。(省略了用于缩放和旋转的部分代码)
/// <summary> /// 精灵类 /// </summary> public class Sprite { /// <summary> /// 顶点数量 /// </summary> const int VertexAmount = 6; //2个三角形 /// <summary> /// 顶点坐标 /// </summary> Vector[] _vertexPositions = new Vector[VertexAmount]; /// <summary> /// 顶点色彩(局部色彩) /// </summary> GlColor[] _vertexColors = new GlColor[VertexAmount]; /// <summary> /// 顶点映射(纹理贴图用于渲染多边形的特定部分) /// </summary> GlPoint[] _vertexUVs = new GlPoint[VertexAmount]; /// <summary> /// 纹理 /// </summary> Texture2D _texture = new Texture2D(); /// <summary> /// 获取或设置精灵纹理 /// </summary> public Texture2D Texture { get { return _texture; } set { _texture = value; //默认使用纹理自身的宽高 InitVertexPositions(CenterPosition, _texture.Width, _texture.Height); } } /// <summary> /// 获取顶点数组 /// </summary> public Vector[] VertexPositions { get { return _vertexPositions; } } /// <summary> /// 获取顶点颜色数组 /// </summary> public GlColor[] VertexColors { get { return _vertexColors; } } /// <summary> /// 获取顶点坐标数组 /// </summary> public GlPoint[] VertexUVs { get { return _vertexUVs; } } /// <summary> /// 获取或设置宽度 /// </summary> public double Width { get { // 获取实际显示在屏幕上的宽度 return _vertexPositions[1].X - _vertexPositions[0].X; } set { InitVertexPositions(CenterPosition, value, Height); } } /// <summary> /// 获取或设置高度 /// </summary> public double Height { get { // topleft - bottomleft return _vertexPositions[0].Y - _vertexPositions[2].Y; } set { InitVertexPositions(CenterPosition, Width, value); } } /// <summary> /// 创建一个精灵 /// </summary> public Sprite() { InitVertexPositions(Vector.Zero, 1, 1); SetColor(GlColor.GetWhite()); SetUVs(new GlPoint(0, 0), new GlPoint(1, 1)); //正确设置默认的初始位置 _currentPosition = new Vector( _vertexPositions[0].X + Width / 2, _vertexPositions[0].Y - Height / 2, _vertexPositions[0].Z); } /// <summary> /// 初始化顶点信息 /// </summary> void InitVertexPositions(Vector center, double width, double height) { double halfWidth = width / 2; double halfHeight = height / 2; //顺时针创建两个三角形构成四方形 // TopLeft, TopRight, BottomLeft _vertexPositions[0] = new Vector(center.X - halfWidth, center.Y + halfHeight, center.Z); _vertexPositions[1] = new Vector(center.X + halfWidth, center.Y + halfHeight, center.Z); _vertexPositions[2] = new Vector(center.X - halfWidth, center.Y - halfHeight, center.Z); // TopRight, BottomRight, BottomLeft _vertexPositions[3] = new Vector(center.X + halfWidth, center.Y + halfHeight, center.Z); _vertexPositions[4] = new Vector(center.X + halfWidth, center.Y - halfHeight, center.Z); _vertexPositions[5] = new Vector(center.X - halfWidth, center.Y - halfHeight, center.Z); } /// <summary> /// 获取或设置中心位置 /// </summary> public Vector CenterPosition { get { return _currentPosition; } set { Matrix m = new Matrix(); m.SetTranslation(value - _currentPosition); ApplyMatrix(m); _currentPosition = value; } } /// <summary> /// 设置颜色 /// </summary> public void SetColor(GlColor color) { for (int i = 0; i < Sprite.VertexAmount; i++) { _vertexColors[i] = color; } } /// <summary> /// 设置UV,进行纹理映射 /// </summary> public void SetUVs(GlPoint topLeft, GlPoint bottomRight) { // TopLeft, TopRight, BottomLeft _vertexUVs[0] = topLeft; _vertexUVs[1] = new GlPoint(bottomRight.X, topLeft.Y); _vertexUVs[2] = new GlPoint(topLeft.X, bottomRight.Y); // TopRight, BottomRight, BottomLeft _vertexUVs[3] = new GlPoint(bottomRight.X, topLeft.Y); _vertexUVs[4] = bottomRight; _vertexUVs[5] = new GlPoint(topLeft.X, bottomRight.Y); } /// <summary> /// 应用矩阵操作 /// </summary> public void ApplyMatrix(Matrix m) { for (int i = 0; i < VertexPositions.Length; i++) { VertexPositions[i] *= m; } } }
sprite类只存储用于绘制的数据,自己不负责绘制自身,而是交由Render类负责。
Matrix类用于矩阵运算,应用矩阵可以实现平移、缩放、旋转的效果,简化了sprite类的设计。
3. 一致的素材格式
在flash中,只有关键帧由用户提供,其余帧通过补间完成,而这次设计的动画精灵的所有帧全部由用户提供(来源于图片素材),这样大大简化了设计。
因此所面临的问题只是如何收集和呈现素材。“收集”即将零散的图片资源加载为有序组织的数据结构,“呈现”即将内存的数据利用图形渲染器绘制屏幕上。
我选择C#+opengl的渲染构件,已经实现将普通sprite渲染到屏幕上,只需要考虑如何依次的向opengl传送需要渲染的纹理,双缓冲和其他的渲染技术不需考虑。
动画所必须的素材可能拥有不同的呈现格式,但需要将这些素材加载为一致内存表示。大致有两种方式可以解决这个问题。
第一就是对动画素材进行预处理,通过非编程的手段将素材表示为一致的标准格式。
第二就是针对不同的格式制定相应的加载算法。很明显应该采用第一种解决方案,这有利于简化系统设计。
约定一下的素材格式:
1.一种动画精灵由一个图像文件(png/jpg/...)提供。
2.动画由若干帧组成,每帧的大小是相同的
3.帧按文字的排列方式排列,不能留空隙。
4.除了提供文件之外,还需指明帧的大小和数量。
4. 动画精灵的类设计
由于已经存在sprite类,所以可以简单的通过继承复用一些方法。我们需要做的就是将当前帧映射到显示数据上(顶点数组、颜色数组、UV数组),
这通过运行时计算获得,也可以事先计算。
默认的UV包含整个纹理图像,这就是常规的sprite显示方式-显示整个图像。可以通过修改UV只显示部分图像,
如果每一次当前帧改变的时候都重新设置UV以把纹理图像中代表相应帧的画面显示出来,就可以实现动画的效果。
通过倒推法可以完成此项设计。
->知道当前帧,如何设置UV数据
->调用基类的SetUVs方法,传递两个能够唯一确定显示区域的坐标。
->已经知道整张图像的大小和单位帧的大小,只要知道当前帧在纹理图像上的左上角坐标就可以了。
->可以计算得知一行有多少帧,而且已经知道了帧的大小...
->OK
这其实并不是倒推法,这是解决问题的真正的正常思路。总不可能从条件出发,那才是违逆正常思维的倒推法。
/// <summary> /// 动画精灵 /// </summary> public class AnimatedSprite : Sprite { /// <summary> /// 总帧数 /// </summary> int _totalFrame; /// <summary> /// 帧宽 /// </summary> double _frameWidth; /// <summary> /// 帧高 /// </summary> double _frameHeight; /// <summary> /// 当前帧 /// </summary> int _currentFrame; /// <summary> /// 当前帧的计时 /// </summary> double _currentFrameTime; /// <summary> /// 获取或设置每一帧持续的时间 /// </summary> public double FrameDuration { get; set; } /// <summary> /// 是否循环播放 /// </summary> public bool Looping { get; set; } /// <summary> /// 是否播放结束 /// </summary> public bool Finished { get; private set; } /// <summary> /// 获取一行的帧数量 /// </summary> public int RowFrame { get { return (int)Math.Round(Width / _frameWidth); } } /// <summary> /// 创建一个可播放的动画精灵 /// </summary> public AnimatedSprite() { Looping = false; Finished = false; FrameDuration = 0.05; _frameHeight = 0; _frameWidth = 0; _currentFrame = 0; _totalFrame = 1; _currentFrameTime = FrameDuration; } /// <summary> /// 拨动到下一帧 /// </summary> public void AdvanceFrame() { _currentFrame = (_currentFrame + 1) % _totalFrame; } /// <summary> /// 获取帧值在图源中的位置索引 /// </summary> GlPoint GetIndexFromFrame(int frame) { GlPoint point = new GlPoint(); point.Y = frame / RowFrame; point.X = frame - (point.Y * RowFrame); return point; } /// <summary> /// 更新显示数据 /// </summary> void UpdateDisplay() { GlPoint index = GetIndexFromFrame(_currentFrame); Vector startPosition = new Vector(index.X * _frameWidth, index.Y * _frameHeight); Vector endPosition = startPosition + new Vector(_frameWidth, _frameHeight); SetUVs(new GlPoint((float)(startPosition.X / Width), (float)(startPosition.Y / Height)), new GlPoint((float)(endPosition.X / Width), (float)(endPosition.Y / Height))); } /// <summary> /// 通知动画精灵的总帧数 /// </summary> public void SetTotalFrame(int totalFrame) { _totalFrame = totalFrame; _currentFrame = 0; _currentFrameTime = FrameDuration; UpdateDisplay(); } /// <summary> /// 通知动画精灵的帧大小,每一帧将应用一致的尺寸 /// </summary> public void SetFrameSize(double width, double height) { _frameWidth = width; _frameHeight = height; UpdateDisplay(); } /// <summary> /// 处理更新 /// </summary> public override void Process(double elapsedTime) { if (_currentFrame == _totalFrame - 1 && Looping == false) { Finished = true; return; } _currentFrameTime -= elapsedTime; if (_currentFrameTime < 0) { _currentFrameTime = FrameDuration; AdvanceFrame(); UpdateDisplay(); } } /// <summary> /// 缩放时维护帧大小 /// </summary> public override void SetScale(double x, double y) { _frameWidth /= _scaleX; _frameHeight /= _scaleY; base.SetScale(x, y); _frameWidth *= x; _frameHeight *= y; UpdateDisplay(); } }
5. 测试
这里写了简单的测试代码,更重要的测试是在实际运行时观察效果。
public void ProcessTest() { AnimatedSprite target = new AnimatedSprite(); target.Texture = new Texture2D(0, 256, 256); target.SetTotalFrame(16); target.SetFrameSize(64, 64); target.CenterPosition = Vector.Zero; Assert.IsTrue(target.CurrentFrame == 0); Assert.IsTrue(target.Finished == false); double elapsedTime = 0.32; MultiProcess(target, elapsedTime); Assert.IsTrue(target.CurrentFrame == 3); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); MultiProcess(target, elapsedTime); Assert.IsTrue(target.Finished == true); Assert.IsTrue(target.CurrentFrame == 15); }
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。