您的位置:首页 > 理论基础 > 数据结构算法

C 实现通用Tween缓动动画(2)Tween数据结构

2016-07-05 20:53 267 查看
    首先,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 是我自己封装的队列,动态数组,字典映射的数据结构,可以轻易的使用别实现代替。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: