首先,tween动画需要能够完成以下几个功能。
- 设定时间,目标值,插值公式,完成动画运动
- 动画完成后调用回调函数
- 可以在同一动画过程中,进行多个数值的同时插值计算
- 动画可以进入队列顺序执行,也可以并发执行
- 可以销毁指定动画
根据上述需求,我们需要以下数据结构。
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 是我自己封装的队列,动态数组,字典映射的数据结构,可以轻易的使用别实现代替。