cocos2dx-lua的spine區域性換裝之進階篇
之前寫了一篇文章關於cocos2dx-lua的spine區域性換裝的。主要原理就是根據cocos2dx中SpineRenderer.cpp中setSkin()方法,添加了addSkin()方法。並在
lua_cocos2dx_spine_auto.cpp中新增方法 int lua_cocos2dx_spine_SkeletonRenderer_addSkin(la_State* tolua_S),完成lua-building,使在lua端可以呼叫。
上一篇地址:http://blog.csdn.net/u010536615/article/details/51137235
寫進階篇的原因是:
Spine動畫製作時有個關鍵幀的東西,一般做動畫時第一幀就要把所有的相關部件(slots)等都key上關鍵幀。然而我們之前做動畫時並沒key上關鍵幀,所以之前addSkin()方法沒有任何問題。但將關鍵幀key上之後,儘管方法實現沒有任何問題,但addSkin()方法不管用了。於是就把spine的從解析到動畫實現的程式碼都看了一遍。使方法支援key上關鍵幀後的區域性換裝。
解決方法直接看標題3。
---------------------------------------------------------------1.換裝原理--------------------------------------------
更換slots插槽中的attachment部件。
具體是找到對應slots的attachment部件,然後遍歷skins中當前skin是否有該attachment部件,如果有,將該部件替換掉原slots中的attachment部件。
---------------------------------------------------------------2.問題產生原因
-------原因:
self.actor = sp.SkeletonAnimation:create("red/red.json", "red/red.atlas", 1)
self.actor:setAnimation(0, "red_wait", true)
self.actor:setPosition(450, 300)
self:addChild(self.actor)
其實只建立,不掉用動畫self.actor:setAnimation(0, "red_wait", true),addSkin()方法是好用的。
那麼問題就出在播放動畫的時候。播放動畫時會重新根據當前的skin替換掉slots中的attachment。所以之前設定的addSkin()中替換的,當動畫動起來時就不起作用了。
-------引擎帶的setSkin()起作用:
那為什麼setSkin()是好用的呢。這就是關鍵。
之前也說了,替換的時候是找到當前設定的skin。而引擎的邏輯中只能設定整體的skin,也就是整套換裝。沒有提供設定單個部件skin的功能。
setSkin方法中通過
CONST_CAST(spSkin*, self->skin) = newSkin;
來設定當前的整體裝備。
通過當前面板去替換每一個部件:
if (self->skin) {
spAttachment *attachment =spSkin_getAttachment(self->skin, slotIndex, attachmentName);
if (attachment)return attachment;
}
if (self->data->defaultSkin) {
spAttachment *attachment =spSkin_getAttachment(self->data->defaultSkin, slotIndex, attachmentName);
if (attachment)return attachment;
}
----所以我們需要做的就是給每一個slot都加上skin的屬性。
替換時查詢當前部件是否有skin,有的話應用該部件的skin。而不用整體的skin。
--------------------------------------3.解決方法--------------------------------------------
1.為每一個slot都加上skin的屬性
editor-support->spine->SlotData.h中spSlotData結構體中增加spSkin*const skin屬性並附上初始值。
新增Skin.h #include <spine/Skin.h>
typedef struct spSlotData {
constchar* const name;
const spBoneData* const boneData;
const char* attachmentName;
float r, g, b, a;
spBlendMode blendMode;
spSkin* const skin;//新增skin屬性
#ifdef __cplusplus
spSlotData() :
name(0),
boneData(0),
attachmentName(0),
skin(0),//新增skin屬性
r(0), g(0), b(0), a(0),
blendMode(SP_BLEND_MODE_NORMAL) {
}
#endif
} spSlotData;
2.addSkin()方法中,給替換的對應slot附上skin
editor-support->spine->Skeleton.c中修改我上一篇中的spSkeleton_addSkin方法:
void spSkeleton_addSkin (spSkeleton* self,spSkin* newSkin, constchar* partName) {
if (newSkin) {
int i;
for (i = 0; i < self->slotsCount; ++i) {
spSlot* slot = self->slots[i];
if (slot->data->attachmentName) {
if (strcmp(slot->data->attachmentName, partName) ==0) {
spAttachment* attachment =spSkin_getAttachment(newSkin, i, slot->data->attachmentName);
if (attachment)
{
CONST_CAST(spSkin*, self->data->slots[i]->skin) = newSkin;//slot->skin賦值
spSlot_setAttachment(slot, attachment);
}
}
}
}
}
}
3.替換slot的attachment部件時判斷當前slot的skin修改editor-support->spine->Skeleton.c中spSkeleton_getAttachmentForSlotIndex方法,其中兩個if是新增程式碼。
spAttachment* spSkeleton_getAttachmentForSlotIndex (constspSkeleton* self, int slotIndex, const char* attachmentName) {
if (slotIndex == -1)return 0;
if (self->skin) {
//新增
if (self->data->slots[slotIndex]->skin) {
spAttachment *attachment =spSkin_getAttachment(self->data->slots[slotIndex]->skin, slotIndex, attachmentName);
if (attachment)return attachment;
}
spAttachment *attachment =spSkin_getAttachment(self->skin, slotIndex, attachmentName);
if (attachment)return attachment;
}
if (self->data->defaultSkin) {
//新增
if (self->data->slots[slotIndex]->skin) {
spAttachment *attachment =spSkin_getAttachment(self->data->slots[slotIndex]->skin, slotIndex, attachmentName);
if (attachment)return attachment;
}
spAttachment *attachment =spSkin_getAttachment(self->data->defaultSkin, slotIndex, attachmentName);
if (attachment)return attachment;
}
return0;
}
4.應用
其中setSkin方法用來對比檢測用。
self.actor = sp.SkeletonAnimation:create("red/red.json", "red/red.atlas", 1)
self.actor:setAnimation(0, "red_wait", true)
self.actor:setPosition(450, 300)
self:addChild(self.actor)
self.actor:setSkin("HM_LV1")
local fashionStr = string.format("HM_LV%d", 4)
self.actor:addSkin("HM_LV2", "weap_wuqi")
self.actor:addSkin(fashionStr, "HM_huwan_z")
self.actor:addSkin(fashionStr, "HM_R_jiao")
self.actor:addSkin(fashionStr, "HM_jiao_L")--HM_L_jiao
self.actor:addSkin(fashionStr, "HM_dun")
self.actor:addSkin(fashionStr, "HM_kuzi")
--------------------------------------4.說明&備註--------------------------------------------
-------首先附上動畫key和不key關鍵幀後json檔案的區別:
key上:
"animations": {"red_wait": {"slots": {
"HM_R_jiao": {"attachment": [
{ "time": 0, "name": "HM_R_jiao" },
{ "time": 0.6, "name": null }],
"color": [
{ "time": 0, "color": "ffffffff" },
{ "time": 0.3, "color": "00ffffff" }
]}}}
不key上:
"animations": {"red_wait": {"slots": {
]}}}
其實就是key上了就有slots"HM_R_jiao"的屬性,關鍵就在attachment屬性,其實去掉attachment屬性,只保留color屬性,之前的addSkin()區域性換裝也是好用的。
-------動畫程式碼:
SkeletonAnimation.cpp中
void SkeletonAnimation::update (float deltaTime) {
super::update(deltaTime);
deltaTime *= _timeScale;
spAnimationState_update(_state, deltaTime);
spAnimationState_apply(_state, _skeleton);
spSkeleton_updateWorldTransform(_skeleton);
}
這段程式碼看看就行,重點在下面。-------解析上一段的程式碼的attachment屬性:
SkeletonJson.c檔案中的_spSkeletonJson_readAnimation方法:
else if (strcmp(timelineArray->name, "attachment") == 0) {
spAttachmentTimeline *timeline = spAttachmentTimeline_create(timelineArray->size);
timeline->slotIndex = slotIndex;
for (frame = timelineArray->child, i = 0; frame; frame = frame->next, ++i) {
Json* name = Json_getItem(frame, "name");
spAttachmentTimeline_setFrame(timeline, i, Json_getFloat(frame, "time", 0),
name->type == Json_NULL ? 0 : name->valueString);
}
animation->timelines[animation->timelinesCount++] = SUPER_CAST(spTimeline, timeline);
duration = timeline->frames[timelineArray->size - 1];
if (duration > animation->duration) animation->duration = duration;
}
-------其中spAttachmentTimeline_create(timelineArray->size):
spAttachmentTimeline* spAttachmentTimeline_create (int framesCount) {
spAttachmentTimeline* self =NEW(spAttachmentTimeline);
_spTimeline_init(SUPER(self),SP_TIMELINE_ATTACHMENT,_spAttachmentTimeline_dispose,_spAttachmentTimeline_apply);
CONST_CAST(int, self->framesCount) = framesCount;
CONST_CAST(float*, self->frames) =CALLOC(float, framesCount);
CONST_CAST(char**, self->attachmentNames) =CALLOC(char*, framesCount);
return self;
}
-------關鍵就在_spAttachmentTimeline_apply動畫應用attachment的方法:Animation.c中的_spAttachmentTimeline_apply()方法。
void _spAttachmentTimeline_apply (constspTimeline* timeline,spSkeleton* skeleton, float lastTime, float time,
spEvent** firedEvents,int* eventsCount,float alpha) {
int frameIndex;
const char* attachmentName;
spAttachmentTimeline* self = (spAttachmentTimeline*)timeline;
if (time < self->frames[0]) {
if (lastTime > time)_spAttachmentTimeline_apply(timeline, skeleton, lastTime, (float)INT_MAX,0,0, 0);
return;
} else if (lastTime > time) /**/
lastTime = -1;
frameIndex = time >= self->frames[self->framesCount -1] ?
self->framesCount -1 :binarySearch1(self->frames, self->framesCount, time) -1;
if (self->frames[frameIndex] < lastTime)return;
attachmentName = self->attachmentNames[frameIndex];
spSlot_setAttachment(skeleton->slots[self->slotIndex],
attachmentName ? spSkeleton_getAttachmentForSlotIndex(skeleton, self->slotIndex, attachmentName) :0);
}
--------重點:
最後一句是key上關鍵幀後addSkin()方法失效的關鍵。這句就是替換slot中的attachment部件。
spSlot_setAttachment(skeleton->slots[self->slotIndex],
attachmentName ? spSkeleton_getAttachmentForSlotIndex(skeleton, self->slotIndex, attachmentName) : 0);
--------重點重點spSkeleton_getAttachmentForSlotIndex方法:
spAttachment* spSkeleton_getAttachmentForSlotIndex (constspSkeleton* self, int slotIndex, const char* attachmentName) {
if (slotIndex == -1)return 0;
if (self->skin) {
spAttachment *attachment =spSkin_getAttachment(self->skin, slotIndex, attachmentName);
if (attachment)return attachment;
}
if (self->data->defaultSkin) {
spAttachment *attachment =spSkin_getAttachment(self->data->defaultSkin, slotIndex, attachmentName);
if (attachment)return attachment;
}
return0;
}
--------問題所在其中的self->skin是skeleton的屬性,所有部件只有統一的一個。每當動畫播放時,就用這統一的skin去替換掉所有的skin。所以想區域性換裝就要紀錄每個slot的skin,替換時單獨判斷。