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

    首先,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 是我自己封装的队列,动态数组,字典映射的数据结构,可以轻易的使用别实现代替。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值