Spine深入学习 —— 动画

SkeletonAnimation

继承于SkeletonRenderer

播放动画

cpp 复制代码
spTrackEntry* SkeletonAnimation::setAnimation (int trackIndex, const std::string& name, bool loop) {
	spAnimation* animation = spSkeletonData_findAnimation(_skeleton->data, name.c_str());
	if (!animation) {
		log("Spine: Animation not found: %s", name.c_str());
		return 0;
	}
	return spAnimationState_setAnimation(_state, trackIndex, animation, loop);
}

首先根据name去寻找动画,spAnimation 对象主体是spTimeline,也就是Spine的时间线。

cpp 复制代码
spTrackEntry* spAnimationState_setAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop) {
	spTrackEntry* entry;
	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
	int interrupt = 1;
	spTrackEntry* current = _spAnimationState_expandToIndex(self, trackIndex);
	if (current) {
		if (current->nextTrackLast == -1) {
			/* Don't mix from an entry that was never applied. */
			self->tracks[trackIndex] = current->mixingFrom;
			_spEventQueue_interrupt(internal->queue, current);
			_spEventQueue_end(internal->queue, current);
			_spAnimationState_disposeNext(self, current);
			current = current->mixingFrom;
			interrupt = 0;
		} else
			_spAnimationState_disposeNext(self, current);
	}
	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, current);
	_spAnimationState_setCurrent(self, trackIndex, entry, interrupt);
	_spEventQueue_drain(internal->queue);
	return entry;
}

AnimationState

按时间顺序应用动画, 队列待播放的后续动画, 在动画间混合(淡入淡出), 以及在各动画之上相互堆叠应用多个动画(动画分层).

这是Spine提供的两种动画之一------------动画状态。

在几乎所有情况下, 都建议使用动画状态API而非时间轴API. 与底层的时间轴API相比, 动画状态API会让诸如在某段时间内应用动画、排队动画、mix动画、以及同时应用多个动画等任务变得更加容易. 动画状态API内部使用的也是时间轴API, 因此可以将其看作是时间轴API的封装器.

AnimationState建立在时间轴API上,可处理多数播放需求,向后播放除外。如需反向播放,可直接使用时间轴API,或使用框选缩放来复制和反向播放动画。

通道(Track)

Track可分层应用动画,每个track存储了一个动画和播放参数。Track编号从零累加(track索引在内部是一个数组索引)。在将AnimationState应用到一个骨架后,track动画会从最低的track号开始依序应用。

播放

spAnimationState_setAnimation的本质就是播放track动画,将使用指定的动画取代该track中的当前动画和任何已排队的动画。如果在前一动画和当前动画之间定义了混合动画时间,则当前动画将在混合动画时间上混出,让动画过渡更流畅。

会返回一个spTrackEntry对象,可以多种方式自定义播放。

默认动画将继续应用直至另一动画播放或track清空。要在一个具体时间后停止动画,可设置spTrackEntry trackEnd时间。

排队播放
cpp 复制代码
spTrackEntry* spAnimationState_addAnimation (spAnimationState* self, int trackIndex, spAnimation* animation, int/*bool*/loop,
											 float delay) {
	spTrackEntry* entry;
	_spAnimationState* internal = SUB_CAST(_spAnimationState, self);
	spTrackEntry* last = _spAnimationState_expandToIndex(self, trackIndex);
	if (last) {
		while (last->next)
			last = last->next;
	}

	entry = _spAnimationState_trackEntry(self, trackIndex, animation, loop, last);

	if (!last) {
		_spAnimationState_setCurrent(self, trackIndex, entry, 1);
		_spEventQueue_drain(internal->queue);
	} else {
		last->next = entry;
		if (delay <= 0) {
			float duration = last->animationEnd - last->animationStart;
			if (duration != 0)
				delay += duration * (1 + (int)(last->trackTime / duration)) - spAnimationStateData_getMix(self->data, last->animation, animation);
			else
				delay = 0;
		}
	}

	entry->delay = delay;
	return entry;
}

spAnimationState_addAnimation安排该动画在此track当前动画或最后排队的动画后播放。如果此track空了,则等于调用setAnimation

返回 spTrackEntry对象。

空动画

如果清空了一个track,动画将停止应用。要混入或混出一个动画,可指定一个空动画,即没有时间轴的动画。空动画作为一个占位符,可设置混合时间。

cpp 复制代码
spTrackEntry* spAnimationState_addEmptyAnimation(spAnimationState* self, int trackIndex, float mixDuration, float delay) {
	spTrackEntry* entry;
	if (delay <= 0) delay -= mixDuration;
	entry = spAnimationState_addAnimation(self, trackIndex, SP_EMPTY_ANIMATION, 0, delay);
	entry->mixDuration = mixDuration;
	entry->trackEnd = mixDuration;
	return entry;
}

要从装配姿势混入一个动画,可设置一个空动画,然后设置混合时间:

cpp 复制代码
state.setEmptyAnimation(track, 0);
TrackEntry entry = state.addAnimation(track, "run", true, 0);
entry.mixDuration = 1.5;

要混出一个动画到装配姿势,可设置或排队一个指定了混合时间的空动画:

cpp 复制代码
state.setAnimation(track, "run", true, 0);
state.addEmptyAnimation(track, 1.5, 0);

当动画到达TrackEntry trackEnd时间时,该动画设置的关键帧属性将设置装配姿势,并清空track。可根据需要使用setEmptyAnimation或addEmptyAnimation将骨架混回到装配姿势,而非让其即刻发生。

spTrackEntry

lua本身没有导出

设置或排队动画的方法返回一个TrackEntry,可用于进一步自定义播放。

cpp 复制代码
struct spTrackEntry {
	spAnimation* animation;
	spTrackEntry* next;
	spTrackEntry* mixingFrom;
	spAnimationStateListener listener;
	int trackIndex;
	int /*boolean*/ loop;
	float eventThreshold, attachmentThreshold, drawOrderThreshold;
	float animationStart, animationEnd, animationLast, nextAnimationLast;
	float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale;
	float alpha, mixTime, mixDuration, interruptAlpha, totalAlpha;
	spIntArray* timelineData;
	spTrackEntryArray* timelineDipMix;
	float* timelinesRotation;
	int timelinesRotationCount;
	void* rendererObject;
	void* userData;

#ifdef __cplusplus
	spTrackEntry() :
		animation(0),
		next(0), mixingFrom(0),
		listener(0),
		trackIndex(0),
		loop(0),
		eventThreshold(0), attachmentThreshold(0), drawOrderThreshold(0),
		animationStart(0), animationEnd(0), animationLast(0), nextAnimationLast(0),
		delay(0), trackTime(0), trackLast(0), nextTrackLast(0), trackEnd(0), timeScale(0),
		alpha(0), mixTime(0), mixDuration(0), interruptAlpha(0), totalAlpha(0),
		timelineData(0),
		timelineDipMix(0),
		timelinesRotation(0),
		timelinesRotationCount(0) {
	}
#endif
};

在里面有一个spAnimationStateListener对象,表示监听事件。

cpp 复制代码
void _spEventQueue_drain (_spEventQueue* self) {
	int i;
	if (self->drainDisabled) return;
	self->drainDisabled = 1;
	for (i = 0; i < self->objectsCount; i += 2) {
		spEventType type = (spEventType)self->objects[i].type;
		spTrackEntry* entry = self->objects[i+1].entry;
		spEvent* event;
		switch (type) {
			case SP_ANIMATION_START:
			case SP_ANIMATION_INTERRUPT:
			case SP_ANIMATION_COMPLETE:
				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
				break;
			case SP_ANIMATION_END:
				if (entry->listener) entry->listener(SUPER(self->state), type, entry, 0);
				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, 0);
				/* Fall through. */
			case SP_ANIMATION_DISPOSE:
				if (entry->listener) entry->listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), SP_ANIMATION_DISPOSE, entry, 0);
				_spAnimationState_disposeTrackEntry(entry);
				break;
			case SP_ANIMATION_EVENT:
				event = self->objects[i+2].event;
				if (entry->listener) entry->listener(SUPER(self->state), type, entry, event);
				if (self->state->super.listener) self->state->super.listener(SUPER(self->state), type, entry, event);
				i++;
				break;
		}
	}
	_spEventQueue_clear(self);

	self->drainDisabled = 0;
}
相关推荐
vip4513 小时前
游戏开发2025年最新版——八股文面试题(unity,虚幻,cocos都适用)
unity·虚幻·cocos2d
charon87788 小时前
UE5: Content browser工具编写02
c++·ue5·游戏引擎
蓝裕安1 天前
DesignMode__unity__抽象工厂模式在unity中的应用、用单例模式进行资源加载
unity·游戏引擎
tealcwu1 天前
【Unity基础】Unity中跨平台使用SQLite
unity·游戏引擎
阿汪队1 天前
【Unity单机游戏框架】K-Framework
unity·游戏引擎
UTwelve1 天前
【UE5】将2D切片图渲染为体积纹理,最终实现使用RT实时绘制体积纹理【第二篇-着色器制作】
ue5·游戏引擎·虚幻·着色器·虚幻4
Artistation Game1 天前
二、鼠标的解锁与锁定
unity·c#·游戏引擎
@Sunset...2 天前
Unity数据持久化4——2进制
unity·c#·游戏引擎
yukino_NZB2 天前
Unity网络开发记录(二):采用多线程处理服务端对客户端的连接和信息处理
网络·unity·游戏引擎
ue星空2 天前
将Mixamo的模型和动画导入UE5
游戏·ue5·游戏引擎·虚幻·虚幻引擎