• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

spriter动画编辑器的cocos2d

mysql 搞代码 4年前 (2022-01-09) 24次浏览 已收录 0个评论

目前我的cocos2d-x编辑器的动画部分接口采用的是spriter动画编辑器提供的接口,spriter动画编辑器虽然简陋,但一般的需求基本上能够满足。可以在http://www.brashmonkey.com/spriter.htm下载,另外cocos2d-x的接口可以在论坛http://www.brashmonkey.com/foru

目前我的cocos2d-x编辑器的动画部分接口采用的是spriter动画编辑器提供的接口,spriter动画编辑器虽然简陋,但一般的需求基本上能够满足。可以在http://www.brashmonkey.com/spriter.htm下载,另外cocos2d-x的接口可以在论坛http://www.brashmonkey.com/forum/viewtopic.php?f=3&t=870下载。

接口导入进来之后,一切都很正常,然而在采用该接口进行开发时,发现有几个比较严重的问题。

问题1:资源采用多文件夹形式存储,渲染效率低

问题2:一个png只能有一个ccsprite,在编辑器是正常的,然而采用论坛那个接口导入到cocos2d-x就有问题。

问题3:精灵不能flip

问题4:不支持scale变换

陆续修改了上述4个问题

资源采用Sprite Frame实现

png的bug线性实现,时间复杂度O(1)

flip的实现参考cocos2d-x源码中CCTransitionFlipX的实现——摄像头换位

scale参考angle的变换就OK了

代码如下:

(代码中掺杂了编辑器部分的代码,精力有限,本代码仅供参考)

头文件:

//------------------------------------------------------------------------////	SCMLAnimator : KickStarter project Spriter renderer for cocos2d-x.////	Spriter website : http://www.<mark>来源gaodaimacom搞#^代%!码网</mark>kickstarter.com/projects/539087245/spriter////	Licensed under the BSD license, see LICENSE in root for details.// //	Copyright (c) 2012 James Hui (a.k.a. Dr.Watson)// //	For latest updates, please visit http://jameshui.com////------------------------------------------------------------------------#ifndef _CC_SPRITER_X_H_#define _CC_SPRITER_X_H_#include #include #include "JEvent.h"#include "cocos2d.h"#include "TouchSprite.h"#include "tinyxml.h"class CCSpriterX;#define FILE_SPRITE_SIZE 128namespace SCMLHelper{	struct File	{		File();		~File();		void Init(TiXmlNode *node);		int id;		std::string name;		float width;		float height;		//一个文件可能有多个关联		cocos2d::CCSprite* sprites[FILE_SPRITE_SIZE];	};	class Folder	{	public:		Folder();		~Folder();				void Init(TiXmlNode *node);				int GetFileCount();		File *GetFile(int index);	private:		int mId;		std::string mName;		std::vector  mFiles;	};	struct ObjectRef	{		void Init(TiXmlNode *node);		int id;		int timeline;		int key;		int z_index;	};	struct Object	{		void Init(TiXmlNode *node, CCSpriterX *animator, int timelineId);				int folder;		int file;		float x;		float y;		float angle;		float scaleX;		float scaleY;		float pivot_x;		float pivot_y;		int z_index;		cocos2d::CCSprite *sprite;	};	class Key	{	public:		Key();		~Key();				void Init(TiXmlNode *node, CCSpriterX *animator, int timelineId);				int GetObjectRefCount();		ObjectRef *GetObjectRef(int index);				int GetObjectCount();		Object *GetObject(int index);		float GetTime();		bool IsSpinCounterClockwise();	private:		int mId;		float mTime;		bool mSpinCounterClockwise;		std::vector  mObjects;		std::vector  mObjectRefs;		// will have bones later	};	class Timeline	{	public:		Timeline();		~Timeline();		void Init(TiXmlNode *node, CCSpriterX *animator);		int GetKeyframeCount();		Key *GetKeyframe(int index);	private:		int mId;		std::vector  mKeyframes;	};	class Animation	{    public:		CC_SYNTHESIZE(JEvent *, event, Event);        CC_SYNTHESIZE(std::string, afterAction, AfterAction);        CCSpriterX * spr;	public:		void Restart();		Animation(CCSpriterX * spr);		~Animation();        		void Update(float dt);		void Init(TiXmlNode *node, CCSpriterX *animator);        		void Render();		bool IsDone(); 	public:		std::string getName(){return mName;}	private:		int mId;		std::string mName;		float mLength;		bool mLooping;		bool mDone;		Timeline *mMainline;		int mCurrKeyframe;		std::vector  mTimelines;		float mTimer;		cocos2d::CCPoint mPosition;	};	class Entity	{    private:        CCSpriterX * spr;	public:		Entity(CCSpriterX * spr);		~Entity();		void StartWithEvent(const char * name, JEvent * event);		std::string CurrentAction();		void Update(float dt);		void Render();		void Start(const char * name,const char* _afterAction);		void SetId(int id);		void SetName(const char *name);		void AddAnimation(Animation *animation);		void NextAnimation();	private:		int mId;		std::string mName;		std::vector  mAnimations;		int mCurrAnimation;	};}class CCSpriterX : public TouchSprite{ private:	struct Pit{		int fileId;		int id;		int folderId;	};	Pit fileSprites[FILE_SPRITE_SIZE];	CC_SYNTHESIZE(ccColor3B, colorX, ColorX);public:	CCSpriterX();	~CCSpriterX(); 	virtual void setFlipX(bool bFlipX); 	bool initWithFile(const char *filename); 	std::string CurrentAction(); 	virtual void draw(void); 	virtual void update(float dt); 	static CCSpriterX * create(const char *filename); 	cocos2d::CCSprite * getSprite(int folderId, int fileId, int timelineId); 	void PlayNext(); 	void PlayWithEvent(const char * name, JEvent * event); 	void Play(const char* name,const char* _afterAction = 0); private:	std::vector  mFolders;	std::vector  mEntities;	int mCurrEntity;};#endif

源文件:

//------------------------------------------------------------------------////	CCSpriterX : KickStarter project Spriter renderer for cocos2d-x.////	Spriter website : http://www.kickstarter.com/projects/539087245/spriter////	Licensed under the BSD license, see LICENSE in root for details.// //	Copyright (c) 2012 James Hui (a.k.a. Dr.Watson)// //	For latest updates, please visit http://jameshui.com////------------------------------------------------------------------------#include "CCSpriterX.h"#include "Common.h"#include "jerror.h" USING_NS_CC;namespace SCMLHelper{	///////////////////////////////////////////////////////////////////////////////////	File::File()	{		for(size_t i=0; i<FILE_SPRITE_SIZE; ++i){			sprites[i] = 0;		}	}	File::~File()	{		for(size_t i=0; irelease();			}		}	}	void File::Init(TiXmlNode *node)	{		TiXmlElement *element = node->ToElement();		if (element)		{			int intValue;			float floatValue;			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				id = intValue;			else				id = 0;			name = element->Attribute("name");			if (element->QueryFloatAttribute("width", &floatValue) == TIXML_SUCCESS)				width = floatValue;			else				width = 0;			if (element->QueryFloatAttribute("height", &floatValue) == TIXML_SUCCESS)				height = floatValue;			else				height = 0;			if (name.size()>0)			{				//资源全部放到scml目录中!				std::string path = workPath+gConfig->read("res")+"/";								//sprite = CCSprite::create((path+"scml/"+name).c_str());				sprites[0] = CCSprite::createWithSpriteFrameName(name.c_str());				sprites[0]->retain(); 			}		}	}	///////////////////////////////////////////////////////////////////////////////////	Folder::Folder()		: mId(0)	{ 		mFiles.reserve(50); 	}	Folder::~Folder()	{		int count = mFiles.size();		for (int i=0;i<count;i++)			CC_SAFE_DELETE(mFiles[i]);		mFiles.clear();	}	int Folder::GetFileCount()	{		return mFiles.size();	}		File *Folder::GetFile(int index)	{		if (index ToElement();		if (element)		{			int intValue;			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				mId= intValue;			mName = element->Attribute("name")==0?".":element->Attribute("name");			for (TiXmlNode* fileNode = node->FirstChild(); fileNode; fileNode = fileNode->NextSibling())			{				File *file = new File();				file->Init(fileNode);				mFiles.push_back(file);			}		}	}	///////////////////////////////////////////////////////////////////////////////////	void ObjectRef::Init(TiXmlNode *node)	{		TiXmlElement *element = node->ToElement();		if (element)		{			int intValue;			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				id = intValue;			else				id = 0;			if (element->QueryIntAttribute("timeline", &intValue) == TIXML_SUCCESS)				timeline = intValue;			else				timeline = 0;			if (element->QueryIntAttribute("key", &intValue) == TIXML_SUCCESS)				key = intValue;			else				key = 0;			if (element->QueryIntAttribute("z_index", &intValue) == TIXML_SUCCESS)				z_index = intValue;			else				z_index = 0;		}	}	///////////////////////////////////////////////////////////////////////////////////	void Object::Init(TiXmlNode *node, CCSpriterX *animator, int timelineId)	{		sprite = NULL;        float scaleFactor = CCDirector::sharedDirector()->getContentScaleFactor();        		TiXmlElement *element = node->ToElement();		if (element)		{			int intValue;			float floatValue;			if (element->QueryIntAttribute("folder", &intValue) == TIXML_SUCCESS)				folder = intValue;			else				folder = 0;			if (element->QueryIntAttribute("file", &intValue) == TIXML_SUCCESS)				file = intValue;			else				file = 0;			if (element->QueryFloatAttribute("x", &floatValue) == TIXML_SUCCESS)				x = floatValue/scaleFactor;			else				x = 0;			if (element->QueryFloatAttribute("y", &floatValue) == TIXML_SUCCESS)				y = floatValue/scaleFactor;			else				y = 0;			if (element->QueryFloatAttribute("angle", &floatValue) == TIXML_SUCCESS)				angle = floatValue;			else				angle = 0;			if (element->QueryFloatAttribute("scale_x", &floatValue) == TIXML_SUCCESS)				scaleX = floatValue;			else				scaleX = 1;			if(scaleX QueryFloatAttribute("scale_y", &floatValue) == TIXML_SUCCESS)				scaleY = floatValue;			else				scaleY = 1;			if (element->QueryFloatAttribute("pivot_x", &floatValue) == TIXML_SUCCESS)				pivot_x = floatValue;			else				pivot_x = 0;			if (element->QueryFloatAttribute("pivot_y", &floatValue) == TIXML_SUCCESS)				pivot_y = floatValue;			else				pivot_y = 1;			if (element->QueryIntAttribute("z_index", &intValue) == TIXML_SUCCESS)				z_index = intValue;			else				z_index = 0; 			sprite = animator->getSprite(folder, file, timelineId);		}	}	///////////////////////////////////////////////////////////////////////////////////	Key::Key()		: mId(0)		, mTime(0)		, mSpinCounterClockwise(true)	{ 		mObjects.reserve(50);		mObjectRefs.reserve(50); 	}	Key::~Key()	{		int count = mObjects.size();		for (int i=0;i<count;i++)			CC_SAFE_DELETE(mObjects[i]);		mObjects.clear();		count = mObjectRefs.size();		for (int i=0;i<count;i++)			CC_SAFE_DELETE(mObjectRefs[i]);		mObjectRefs.clear();	}	int Key::GetObjectRefCount()	{		return mObjectRefs.size();	}	ObjectRef *Key::GetObjectRef(int index)	{		if (index < (int)mObjectRefs.size())			return mObjectRefs[index];		return NULL;	}	int Key::GetObjectCount()	{		return mObjects.size();	}	Object *Key::GetObject(int index)	{		if (index ToElement();		if (element)		{			int intValue;			float floatValue;			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				mId = intValue;			float time = 0;			if (element->QueryFloatAttribute("time", &floatValue) == TIXML_SUCCESS)		// was in milliseconds, convert to seconds instead				time = floatValue/1000.0f;			mTime = time;			if (element->QueryIntAttribute("spin", &intValue) == TIXML_SUCCESS)				mSpinCounterClockwise = !(intValue == -1);			for (TiXmlNode* objNode = node->FirstChild(); objNode; objNode = objNode->NextSibling())			{				element = objNode->ToElement();				const char *tabObj = element->Value();				if (strcmp(tabObj, "object_ref")==0)				{					ObjectRef *ref = new ObjectRef();					ref->Init(objNode);					mObjectRefs.push_back(ref);				}				else if (strcmp(tabObj, "object")==0)				{					Object *obj = new Object();					obj->Init(objNode, animator, timelineId);					mObjects.push_back(obj);				}			}		}	}	///////////////////////////////////////////////////////////////////////////////////		Timeline::Timeline()		: mId(0)	{		mKeyframes.reserve(50);	}	Timeline::~Timeline()	{		int count = mKeyframes.size();		for (int i=0;i<count;i++)		{			CC_SAFE_DELETE(mKeyframes[i]);		}	}	int Timeline::GetKeyframeCount()	{		return mKeyframes.size();	}	Key *Timeline::GetKeyframe(int index)	{		if (index ToElement();		if (element)		{			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				mId = intValue;			for (TiXmlNode* keyNode = node->FirstChild(); keyNode; keyNode = keyNode->NextSibling())			{				element = keyNode->ToElement();				if (element)				{					Key *keyframe = new Key();					keyframe->Init(keyNode, animator, mId);					mKeyframes.push_back(keyframe);				}			}		}	}	///////////////////////////////////////////////////////////////////////////////////	Animation::Animation(CCSpriterX * _spr)		: mId(0)        , spr(_spr)		, mCurrKeyframe(0)		, mMainline(NULL)		, mDone(false)		, mTimer(0)		,event(0),afterAction("")	{		mTimelines.reserve(50);	}	Animation::~Animation()	{		int count = mTimelines.size();		for (int i=0;iToElement();		if (element)		{			if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)				mId = intValue;			mName = element->Attribute("name");			if (element->QueryFloatAttribute("length", &floatValue) == TIXML_SUCCESS)				mLength = floatValue/1000.0f;							// was in milliseconds, convert to seconds instead			const char *looping = element->Attribute("looping");		// was set to "false" in alpha, but in fact looping all the time			mLooping = true;			for (TiXmlNode* lineNode = node->FirstChild(); lineNode; lineNode = lineNode->NextSibling())			{				element = lineNode->ToElement();				const char *tabLine = element->Value();				if (strcmp(tabLine, "mainline")==0)						// 1 mainline only				{					mMainline = new Timeline();					mMainline->Init(lineNode, animator);				}				else if (strcmp(tabLine, "timeline")==0)				{					Timeline *timeline = new Timeline();					timeline->Init(lineNode, animator);					mTimelines.push_back(timeline);				}			}		}	}	bool Animation::IsDone()	{		return mDone;	}	void Animation::Restart()	{ 		mDone = false;		mTimer = 0;		mCurrKeyframe = 0;	}  	float lerp(float a, float b, float t){		return a+(b-a)*t;	}	void Animation::Update(float dt)	{		mTimer += dt;		if (mTimer >= mLength)		{			mDone = true;			Restart();			// always looping for now		}		int count = mMainline->GetKeyframeCount();		Key *keyframe = mMainline->GetKeyframe(mCurrKeyframe);		float currTime = keyframe->GetTime();		Key *keyframeNext = NULL;		int next = mCurrKeyframe+1;		if (next > count-1)		// looping			next = 0;		keyframeNext = mMainline->GetKeyframe(next);		if (keyframeNext)		{			float nextTime = keyframeNext->GetTime();			if (next == 0)				nextTime = mLength;			if (mTimer >= nextTime)			{				mCurrKeyframe = next;				keyframe = keyframeNext;				currTime = keyframe->GetTime();				next = mCurrKeyframe+1;				if (next > count-1)				// looping					next = 0;				keyframeNext = mMainline->GetKeyframe(next);				if (keyframeNext == NULL)					return;				nextTime = keyframeNext->GetTime();				if (next == 0)					nextTime = mLength;			}			float t = (mTimer-currTime)/(nextTime-currTime);			int count = keyframe->GetObjectRefCount();			for (int i=0;iGetObjectRef(i);				ObjectRef *refNext = keyframeNext->GetObjectRef(i);				if (ref && refNext)				{					Key *keyRef = mTimelines[ref->timeline]->GetKeyframe(ref->key);					Object *obj = keyRef->GetObject(0);									// should be only 1 object					Key *keyRefNext = mTimelines[refNext->timeline]->GetKeyframe(refNext->key);					Object *objNext = keyRefNext->GetObject(0);					float x = lerp(obj->x, objNext->x, t); 					float y = lerp(obj->y, objNext->y, t);					float scaleX = lerp(obj->scaleX, objNext->scaleX, t);					float scaleY = lerp(obj->scaleY, objNext->scaleY, t);					float angle = objNext->angle-obj->angle;					if (keyRef->IsSpinCounterClockwise())					{						if (angle angle+360)-obj->angle;					}					else					{						if (angle > 0)						{							angle = (objNext->angle-360)-obj->angle;						}					}					if (ref->timeline != refNext->timeline)							t = 0;					angle = obj->angle+(angle)*t;					if (angle >= 360)						angle -= 360;					float px = obj->pivot_x+(objNext->pivot_x-obj->pivot_x)*t;					float py = obj->pivot_y+(objNext->pivot_y-obj->pivot_y)*t;					CCPoint newPos = ccp(x, y);					obj->sprite->setPosition(newPos);					obj->sprite->setRotation(-angle);					obj->sprite->setScaleX(scaleX);					obj->sprite->setScaleY(scaleY);					obj->sprite->setAnchorPoint(ccp(px, py));				}			}		}	}	void Animation::Render()	{		Key *keyframe = mMainline->GetKeyframe(mCurrKeyframe);		int count = keyframe->GetObjectRefCount();		for (int i=0;iGetObjectRef(i);			if (ref)			{				Key *keyRef = mTimelines[ref->timeline]->GetKeyframe(ref->key);				Object *obj = keyRef->GetObject(0);									// should be only 1 object				obj->sprite->setColor(spr->getColorX());				obj->sprite->visit();			}		}	}	///////////////////////////////////////////////////////////////////////////////////	Entity::Entity(CCSpriterX * _spr)		: mCurrAnimation(0)		, mId(0)        , spr(_spr)	{		mAnimations.reserve(50);	};	Entity::~Entity()	{		int count = mAnimations.size();		for (int i=0;iUpdate(dt);	}    std::string Entity::CurrentAction()    {        return mAnimations[mCurrAnimation]->getName();    }		void Entity::StartWithEvent(const char * name, JEvent * event)	{		for(size_t i=0; igetName() == std::string(name)){				mCurrAnimation = i;				mAnimations[i]->setEvent(event);				mAnimations[i]->Restart();			}		}	}    void Entity::Start(const char * name, const char * _afterAction)	{		for(size_t i=0; igetName() == std::string(name)){				mCurrAnimation = i;				mAnimations[i]->setAfterAction(_afterAction);				mAnimations[i]->Restart();			}		}	}	void Entity::Render()	{		Animation *animation = mAnimations[mCurrAnimation];		animation->Render();	}	void Entity::NextAnimation()	{		mCurrAnimation++;		if (mCurrAnimation >= (int)mAnimations.size())			mCurrAnimation = 0;		Animation *animation = mAnimations[mCurrAnimation];		animation->Restart();	}	void Entity::SetId(int id)	{		mId = id;	}	void Entity::SetName(const char *name)	{		mName = name;	}	void Entity::AddAnimation(Animation *animation)	{		mAnimations.push_back(animation);	}}///////////////////////////////////////////////////////////////////////////////////using namespace SCMLHelper;CCSpriterX::CCSpriterX(){	mFolders.reserve(50);	mEntities.reserve(50);	for(int i=0; itype = SCML;	animator->m_state = kLivingStateUngrabbed;	if (animator && animator->initWithFile(filename))	{		//由于是动画层,没有大小,这里设置大小,使之能够拾取		animator->setContentSize(CCSizeMake(100, 100));		animator->autorelease();		return animator;	}	CC_SAFE_DELETE(animator);	return NULL;}void CCSpriterX::update(float dt){	if (dt > 0.0167f)		dt = 0.0167f;	Entity *entity = mEntities[mCurrEntity];	entity->Update(dt); }void CCSpriterX::draw(void){	Entity *entity = mEntities[mCurrEntity];	entity->Render();}std::string CCSpriterX::CurrentAction(){    Entity *entity = mEntities[mCurrEntity];    return entity->CurrentAction();}void CCSpriterX::PlayWithEvent(const char * name, JEvent * event){	Entity *entity = mEntities[mCurrEntity];	entity->StartWithEvent(name, event);}void CCSpriterX::Play(const char* name, const char * _afterAction){ 	Entity *entity = mEntities[mCurrEntity];	entity->Start(name, _afterAction == 0?"":_afterAction);}void CCSpriterX::PlayNext(){	Entity *entity = mEntities[mCurrEntity];	entity->NextAnimation();}CCSprite *CCSpriterX::getSprite(int folderId, int fileId, int timelineId){	if (folderId GetFile(fileId);			if (file){				int id = this->fileSprites[timelineId].id;				if(id == -1){					for(int i=0; isprites[id] == 0){					file->sprites[id] = CCSprite::createWithSpriteFrameName(file->name.c_str());					file->sprites[id]->retain();				}				return file->sprites[id];			}						}	}	return NULL;}void CCSpriterX::setFlipX(bool bFlipX){	if(bFlipX != m_bFlipX){		float ex,ey,ez;		this->getCamera()->getEyeXYZ(&ex, &ey, &ez);		this->getCamera()->setEyeXYZ(ex, ey, bFlipX?-fabs(ez):fabs(ez));		this->m_bFlipX = bFlipX;	}}bool CCSpriterX::initWithFile(const char *filename){	char cfilename[256];	strcpy(cfilename, filename);	string name = strtok(cfilename, ".");	string suffix = strtok(cfilename, ".");  	mCurrEntity = 0;	unsigned long filesize;	string path = workPath+gConfig->read("res")+"/"+filename;	char *buffer = (char *)CCFileUtils::sharedFileUtils()->getFileData(path.c_str(), "rb", &filesize);		if (buffer == NULL)		return false;	//加载大图	string sfplist = workPath+gConfig->read("res")+"/scml/"+name+".plist";	string sfpng = workPath+gConfig->read("res")+"/scml/"+name+".png";	CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(sfplist.c_str(), sfpng.c_str());	TiXmlDocument doc;	doc.Parse(buffer);	TiXmlNode *root = doc.FirstChild("spriter_data"); 	if (root)	{				TiXmlElement *element = root->ToElement();		const char *version = element->Attribute("scml_version");		const char *generator = element->Attribute("generator");		const char *generatorVersion = element->Attribute("generator_version");					for (TiXmlNode* entityNode = root->FirstChild(); entityNode; entityNode = entityNode->NextSibling())		{			element = entityNode->ToElement();			if (element)			{				const char *tab = element->Value();				if (strcmp(tab, "folder")==0)				{					Folder *folder = new Folder();					folder->Init(entityNode);					mFolders.push_back(folder);				}				else if (strcmp(tab, "entity")==0)				{					int intValue;					Entity *entity = new Entity(this);					if (element->QueryIntAttribute("id", &intValue) == TIXML_SUCCESS)						entity->SetId(intValue);					entity->SetName(element->Attribute("name"));					for (TiXmlNode* animationNode = entityNode->FirstChild(); animationNode; animationNode = animationNode->NextSibling())					{						Animation *animation = new Animation(this);						animation->Init(animationNode, this);						entity->AddAnimation(animation);					}					mEntities.push_back(entity);				}			}		}	}	CC_SAFE_DELETE_ARRAY(buffer);	this->scheduleUpdate();	return true;}	

搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:spriter动画编辑器的cocos2d

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址