C 实现通用Tween缓动动画(2)Tween数据结构
2016-07-05 20:53
267 查看
首先,tween动画需要能够完成以下几个功能。
设定时间,目标值,插值公式,完成动画运动
动画完成后调用回调函数
可以在同一动画过程中,进行多个数值的同时插值计算
动画可以进入队列顺序执行,也可以并发执行
可以销毁指定动画
根据上述需求,我们需要以下数据结构。
TweenActionValue,封装了动画中变化的一个数值和相关属性。
含有数值的Get,Set方法。由于这是一个通用的算法,所以tween并不知道需要变化的数值是哪个对象的哪个属性,需要通过传入target和get,set方法来获取。
插值公式类型
数值的from和to的范围
自定义参数
是否是相对数值。相对还是绝对数值的意义是,如果是相对,value则是相对于当前数值的增量,如果是绝对数值,value则是目标数值。这个属性决定了from和to最终的数值。
TweenAction,封装了动画的一个可执行的动作,可以被tween管理和执行的最小单位,所有数值的变化将会通过TweenAction作用到TweenActionValue上。
包含action执行的target,配合TweenActionValue上的get,set将会把动画的数值变化作用到target之上。
用户自定的参数
action的时间长度
是否进入队列。tween会根据tweenid绑定多个action,这些action可以进入队列顺序执行,也可以并发执行。
包含多个TweenActionValue,也就是说一个action可以同时变化多个数值,每个数值都有自己的一套状态属性,但运动时间是共享的。
包含action完成后的回调函数
TweenAction和TweenActionValue已经包含了,我们进行tween动画必要的属性和状态。前面已经说了action执行具有队列和并发机制,也就是说动画是一组action的运行结果,所以我们需要一个数据结构来管理和控制相关的一系列action.
TweenData,封装了一组TweenAction。
queue,所有队列模式的action,都不会立马执行会进入到这个队列里。
current,并发执行action可能会有多个,放在这个数组里同时在执行,所有非队列的action会直接进入这个数组。
currentAction,标示了queue中的正在运行的action,每次从queue取出标示,并放入current即会进入运行状态。
这一组action会被一个tweenId唯一关联,我们就可以通过tweenId来管理一个动画的多个action了。当我们有多个动画,就会产生多个tweenId,所以我们还需要一个用来管理所有动画的类,并且真正的去产生和执行这些动画,去驱动动画不断的执行。
这就是管理所有tween动画的封装。我们可以创建action,运行action,删除action。最重要的是,update方法会每帧调用,不断的驱动每个动画的时间流逝从而执行action的变化。完整实现类如下:
解释几点:
tweenDataMap,就是用一个简单的map,把tweenId和TweenData绑定到一起。
核心的关系就是,tweenId对应了一个TweenData,TweenData相当于一组TweenAction,每一个TweenAction含有零或多个TweenActionValue,TweenActionValue的变化由时间,初始数值,最终数值,插值公式共同计算得出,通过target和get,set方法作用到真实的动画对象上。
update每帧执行,通过每帧的时间变量,驱动action属性的变化,推动动画的执行。
ArrayQueue,ArrayList,ArrayIntMap 是我自己封装的队列,动态数组,字典映射的数据结构,可以轻易的使用别实现代替。
设定时间,目标值,插值公式,完成动画运动
动画完成后调用回调函数
可以在同一动画过程中,进行多个数值的同时插值计算
动画可以进入队列顺序执行,也可以并发执行
可以销毁指定动画
根据上述需求,我们需要以下数据结构。
typedef float (*TweenActionValueOnGet)(void* target); typedef void (*TweenActionValueOnSet)(void* target, float value); typedef struct { void* userData; float value; float fromValue; float toValue; /** * When TweenAction's target value need to get */ TweenActionValueOnGet OnGet; /** * When TweenAction's target value need to set */ TweenActionValueOnSet OnSet; /** * The motion is relative or absolute default true */ bool isRelative; /** * Default tween_ease_linear */ TweenEaseType easeType; } TweenActionValue;
TweenActionValue,封装了动画中变化的一个数值和相关属性。
含有数值的Get,Set方法。由于这是一个通用的算法,所以tween并不知道需要变化的数值是哪个对象的哪个属性,需要通过传入target和get,set方法来获取。
插值公式类型
数值的from和to的范围
自定义参数
是否是相对数值。相对还是绝对数值的意义是,如果是相对,value则是相对于当前数值的增量,如果是绝对数值,value则是目标数值。这个属性决定了from和to最终的数值。
typedef struct TweenAction TweenAction; typedef void (*TweenActionOnComplete)(TweenAction* tweenAction, void* userData); struct TweenAction { /** * Bind data can not get from context */ void* userData; /** * Target execute TweenAction */ void* target; float duration; float curTime; /** * Means action running in queue or immediately default true */ bool isQueue; TweenActionValue* actionValues; int actionValueCount; /** * When action complete call back, passed action and custom data */ TweenActionOnComplete OnComplete; };
TweenAction,封装了动画的一个可执行的动作,可以被tween管理和执行的最小单位,所有数值的变化将会通过TweenAction作用到TweenActionValue上。
包含action执行的target,配合TweenActionValue上的get,set将会把动画的数值变化作用到target之上。
用户自定的参数
action的时间长度
是否进入队列。tween会根据tweenid绑定多个action,这些action可以进入队列顺序执行,也可以并发执行。
包含多个TweenActionValue,也就是说一个action可以同时变化多个数值,每个数值都有自己的一套状态属性,但运动时间是共享的。
包含action完成后的回调函数
TweenAction和TweenActionValue已经包含了,我们进行tween动画必要的属性和状态。前面已经说了action执行具有队列和并发机制,也就是说动画是一组action的运行结果,所以我们需要一个数据结构来管理和控制相关的一系列action.
typedef struct { /** * Target action value queue, run each after over */ ArrayQueue(TweenAction*) queue [1]; /** * Target current running action value */ ArrayList(TweenAction*) current[1]; /** * One running action of queue */ TweenAction* currentAction; } TweenData;
TweenData,封装了一组TweenAction。
queue,所有队列模式的action,都不会立马执行会进入到这个队列里。
current,并发执行action可能会有多个,放在这个数组里同时在执行,所有非队列的action会直接进入这个数组。
currentAction,标示了queue中的正在运行的action,每次从queue取出标示,并放入current即会进入运行状态。
这一组action会被一个tweenId唯一关联,我们就可以通过tweenId来管理一个动画的多个action了。当我们有多个动画,就会产生多个tweenId,所以我们还需要一个用来管理所有动画的类,并且真正的去产生和执行这些动画,去驱动动画不断的执行。
typedef struct { /** * TweenAction's actionValues initialize space by actionValueCount * Once Action running will free by ATween when action complete * * userData is NULL * target is NULL * isQueue is true * duration is 0 * OnComplete is NULL */ TweenAction* (*CreateAction) (int actionValueCount); /** * Bind TweenActions to tweenId and running * if *tweenIdPtr is NULL will set real tweenId value * else set *tweenIdPtr to tweenId */ void (*RunActions) (Array(TweenAction*)* actions, void** tweenIdPtr); /** * Remove tweenId's all actions immediately, return false when tweenId not in use * we can or not clean up tweenId binded data for actions * when tweenId not in use must cleanup for release memory */ bool (*TryRemoveAllActions) (void* tweenId, bool isCleanUp); /** * Find TweenAction in current or queue, and remove it * if tweenId not in use return false * if not found TweenAction return false */ bool (*TryRemoveAction) (void* tweenId, TweenAction* action); /** * Called every frame */ void (*Update) (float deltaTime); } _ATween_; extern _ATween_ ATween[1];
这就是管理所有tween动画的封装。我们可以创建action,运行action,删除action。最重要的是,update方法会每帧调用,不断的驱动每个动画的时间流逝从而执行action的变化。完整实现类如下:
/** * Key is identified pointer, value is TweenData */ static ArrayIntMap(TweenData*) tweenDataMap[1] = AArrayIntMap_Init(TweenData*, 10); static TweenAction* CreateAction(int actionValueCount) { int actionValueSize = sizeof(TweenActionValue) * actionValueCount; TweenAction* tweenAction = (TweenAction*) malloc(sizeof(TweenAction) + actionValueSize); tweenAction->curTime = 0.0f; tweenAction->actionValueCount = actionValueCount; tweenAction->duration = 0.0f; tweenAction->OnComplete = NULL; tweenAction->userData = NULL; tweenAction->isQueue = true; tweenAction->target = NULL; tweenAction->actionValues = (TweenActionValue*) ((char*) tweenAction + sizeof(TweenAction)); for (int i = 0; i < actionValueCount; i++) { tweenAction->actionValues[i].userData = NULL; tweenAction->actionValues[i].value = 0.0f; tweenAction->actionValues[i].fromValue = 0.0f; tweenAction->actionValues[i].toValue = 0.0f; tweenAction->actionValues[i].OnGet = NULL; tweenAction->actionValues[i].OnSet = NULL; tweenAction->actionValues[i].isRelative = true; tweenAction->actionValues[i].easeType = tween_ease_linear; } return tweenAction; } static void SetActionValue(TweenAction* action) { for (int i = 0; i < action->actionValueCount; i++) { TweenActionValue* actionValue = &action->actionValues[i]; ALog_A(actionValue->OnGet && actionValue->OnSet, "TweenActionValue OnSet OnGet must not NULL"); actionValue->fromValue = actionValue->OnGet(action->target); if (actionValue->isRelative) { actionValue->toValue = actionValue->value + actionValue->fromValue; } else { actionValue->toValue = actionValue->value; } } } static void RunActions(Array(TweenAction*)* actions, void** tweenIdPtr) { TweenData* tweenData = NULL; void* tweenId = *tweenIdPtr; if (tweenId == NULL) { // not give tweenId, we use TweenData pointer for it tweenData = (TweenData*) malloc(sizeof(TweenData)); tweenId = tweenData; *tweenIdPtr = tweenData; } int index = AArrayIntMap->GetIndex(tweenDataMap, (intptr_t) tweenId); if (index < 0) { if (tweenData == NULL) { tweenData = (TweenData*) malloc(sizeof(TweenData)); } tweenData->currentAction = NULL; AArrayQueue->Init(sizeof(TweenAction*), tweenData->queue); tweenData->queue->arrayList->increment = 5; AArrayList->Init(sizeof(TweenAction*), tweenData->current); tweenData->current->increment = 5; AArrayIntMap->InsertAt(tweenDataMap, (intptr_t) tweenId, -index - 1, &tweenData); } else { tweenData = *(TweenData**) AArrayIntMap->GetAt(tweenDataMap, index); } for (int i = 0; i < actions->length; i++) { TweenAction* action = AArray_Get(actions, i, TweenAction*); if (action->isQueue) { AArrayQueue->Push(tweenData->queue, &action); } else { AArrayList->Add(tweenData->current, &action); SetActionValue(action); } } } static bool TryRemoveAction(void* tweenId, TweenAction* action) { int index = AArrayIntMap->GetIndex(tweenDataMap, (intptr_t) tweenId); if (index >= 0) { TweenData* tweenData = *(TweenData**) AArrayIntMap->GetAt(tweenDataMap, index); for (int i = 0; i < tweenData->current->size; i++) { TweenAction* tweenAction = AArrayList_Get(tweenData->current, i, TweenAction*); if (action == tweenAction) { if (action == tweenData->currentAction) { tweenData->currentAction = NULL; } AArrayList->RemoveByEnd(tweenData->current, i); free(tweenAction); return true; } } for (int i = tweenData->queue->topIndex; i < tweenData->queue->arrayList->size; i++) { TweenAction* tweenAction = AArrayList_Get(tweenData->queue->arrayList, i, TweenAction*); if (action == tweenAction) { AArrayQueue->RemoveAt(tweenData->queue, i); free(tweenAction); return true; } } } return false; } static bool TryRemoveAllActions(void* tweenId, bool isCleanUp) { int index = AArrayIntMap->GetIndex(tweenDataMap, (intptr_t) tweenId); if (index >= 0) { TweenData* tweenData = *(TweenData**) AArrayIntMap->GetAt(tweenDataMap, index); for (int i = 0; i < tweenData->current->size; i++) { free(AArrayList_Get(tweenData->current, i, TweenAction*)); } AArrayList->Clear(tweenData->current); TweenAction* tweenAction; while ((tweenAction = *(TweenAction**) AArrayQueue->Pop(tweenData->queue, (void*[]) {NULL}))) { free(tweenAction); } tweenData->currentAction = NULL; if (isCleanUp) { AArrayQueue->Release(tweenData->queue); AArrayList->Release(tweenData->current); free(tweenData); AArrayIntMap->RemoveAt(tweenDataMap, index); } return true; } return false; } static void Update(float deltaTime) { for (int i = tweenDataMap->arrayList->size - 1; i > -1 ; i--) { TweenData* tweenData = *(TweenData**) AArrayIntMap->GetAt(tweenDataMap, i); // get current action of queue actions if (!tweenData->currentAction) { tweenData->currentAction = *(TweenAction**) AArrayQueue->Pop(tweenData->queue, (void*[]) {NULL}); if (tweenData->currentAction) { // add current action into current array AArrayList->Add(tweenData->current, &tweenData->currentAction); SetActionValue(tweenData->currentAction); } } if (tweenData->current->size == 0) { // all actions complete continue; } for (int j = tweenData->current->size - 1; j > -1; j--) { TweenAction* action = AArrayList_Get(tweenData->current, j, TweenAction*); action->curTime += deltaTime; if (action->curTime > action->duration) { action->curTime = action->duration; } for (int k = 0; k < action->actionValueCount; k++) { TweenActionValue* actionValue = action->actionValues + k; actionValue->OnSet ( action->target, ATweenEase->interpolates[actionValue->easeType] ( actionValue->fromValue, actionValue->toValue, action->curTime / action->duration ) ); } if (action->curTime == action->duration) { // action complete AArrayList->RemoveByEnd(tweenData->current, j); if (action->OnComplete) { action->OnComplete(action, action->userData); } if (tweenData->currentAction == action) { tweenData->currentAction = NULL; } free(action); } } } } _ATween_ ATween[1] = { CreateAction, RunActions, TryRemoveAllActions, TryRemoveAction, Update, };
解释几点:
tweenDataMap,就是用一个简单的map,把tweenId和TweenData绑定到一起。
核心的关系就是,tweenId对应了一个TweenData,TweenData相当于一组TweenAction,每一个TweenAction含有零或多个TweenActionValue,TweenActionValue的变化由时间,初始数值,最终数值,插值公式共同计算得出,通过target和get,set方法作用到真实的动画对象上。
update每帧执行,通过每帧的时间变量,驱动action属性的变化,推动动画的执行。
ArrayQueue,ArrayList,ArrayIntMap 是我自己封装的队列,动态数组,字典映射的数据结构,可以轻易的使用别实现代替。
相关文章推荐
- sdut 数据结构实验之栈三:后缀式求值
- C语言的数据结构
- vector自定义数据结构数组按照指定元素排序
- 【数据结构与算法】Hash Table
- 字符串-KMP算法实现(数据结构基础 第4周)
- 数据结构与算法_学习笔记(1)
- 《数据结构》复习之图
- 字符串-字符串类的实现(数据结构基础 第4周)
- 字符串-英语数字转换器(数据结构基础 第4周)
- python 2.7 中文教程-5:数据结构
- 求圆圈中剩下的最后一个数字
- 【数据结构】顺序队列
- 团体程序设计天梯赛-练习集 L2-004. 这是二叉搜索树吗?GU
- 一个鼓,左右手轮流敲打。
- opencv 基本数据结构
- Hbase系统架构及数据结构
- redis源码分析之数据结构(一)链表adlist.c
- 数据结构 猫狗队列
- 数据结构笔记:线性表链式存储结构
- 图的应用详解-数据结构