标签:des style blog http color os io 使用 ar
最近写lua写得没有力气了,所以想让脑袋放松一下,刚好看到有人在用swift做游戏:
于是脑子一短路,就想到了利用这些素材来做一个游戏。
本来不想记笔记的,但是由于选择物理引擎的时候遇到诸多问题,所以选择记录下来,目前只做了个雏形,需要再完善一点。
需要知识:
1 cocos2dx-3.2 基本知识
2 box2d有一定的了解。
由于比较简单,所以把所有代码给上了先,然后再简单介绍下遇到的问题之类的东西。
首先是主角,熊猫类:
1 #ifndef __PANDA_H__ 2 #define __PANDA_H__ 3 4 #include "cocos2d.h" 5 USING_NS_CC; 6 7 class Panda : public cocos2d::Node 8 { 9 public: 10 Vector<SpriteFrame*> run_frames; 11 Vector<SpriteFrame*> jump_effect_frames; 12 Vector<SpriteFrame*> roll_frames; 13 Vector<SpriteFrame*> jump_frames; 14 Animation * run_anim; 15 Animation * jump_anim; 16 Animation * jump_effect_anim; 17 Animation * roll_anim; 18 Sprite * shape; 19 Animation * anim; 20 int cur_state;//current state 21 // Here‘s a difference. Method ‘init‘ in cocos2d-x returns bool, instead of returning ‘id‘ in cocos2d-iphone 22 virtual bool init(); 23 //play status RUM||ROLL||JUMP etc 24 void play(unsigned int state); 25 26 void updateState(); 27 // implement the "static create()" method manually 28 CREATE_FUNC(Panda); 29 30 int getCurrentState(); 31 //panda 4 states 32 enum { 33 RUN = 1, 34 JUMP = 2, 35 JUMP2 = 3 36 }; 37 }; 38 39 #endif // __PANDA_H__
1 #include "Panda.h" 2 #include "cocos2d.h" 3 4 USING_NS_CC; 5 6 bool Panda::init() 7 { 8 if(!Node::init()) 9 return false; 10 11 run_frames = Vector<SpriteFrame*>(8); 12 roll_frames = Vector<SpriteFrame*>(8); 13 jump_effect_frames = Vector<SpriteFrame*>(4); 14 jump_frames = Vector<SpriteFrame*>(8); 15 char str[40] = {0}; 16 for(int i=1; i<=8;i++) 17 { 18 sprintf(str,"jump.atlas/panda_jump_0%d.png",i); 19 auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str); 20 if( frame != NULL ) 21 { 22 jump_frames.pushBack(frame); 23 } 24 25 sprintf(str,"roll.atlas/panda_roll_0%d.png",i); 26 auto frame_0 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str); 27 if( frame_0 != NULL ) 28 { 29 roll_frames.pushBack(frame_0); 30 } 31 32 sprintf(str,"run.atlas/panda_run_0%d.png",i); 33 auto frame_1 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str); 34 if( frame_1 != NULL ) 35 { 36 run_frames.pushBack(frame_1); 37 } 38 39 sprintf(str,"jump_effect.atlas/jump_effect_0%d.png",i); 40 auto frame_2 = SpriteFrameCache::getInstance()->getSpriteFrameByName(str); 41 if( frame_2 != NULL ) 42 { 43 jump_effect_frames.pushBack(frame_2); 44 } 45 } 46 47 run_anim = Animation::createWithSpriteFrames(run_frames,0.05f); 48 run_anim->retain(); 49 roll_anim = Animation::createWithSpriteFrames(roll_frames,0.05f); 50 roll_anim->retain(); 51 jump_anim = Animation::createWithSpriteFrames(jump_frames,0.05f); 52 jump_anim->retain(); 53 jump_effect_anim = Animation::createWithSpriteFrames(jump_effect_frames,0.05f); 54 jump_effect_anim->retain(); 55 shape= Sprite::createWithSpriteFrameName("run.atlas/panda_run_01.png"); 56 addChild(shape); 57 return true; 58 } 59 60 void Panda::play(unsigned int state) 61 { 62 Action * animate; 63 switch(state) 64 { 65 case RUN: 66 cur_state = Panda::RUN; 67 animate = RepeatForever::create(Animate::create(run_anim)); 68 animate->setTag(100); 69 shape->runAction(animate); 70 break; 71 case JUMP: 72 cur_state = Panda::JUMP; 73 animate = Animate::create(jump_anim); 74 animate->setTag(200); 75 shape->stopActionByTag(100); 76 shape->runAction(animate); 77 break; 78 case JUMP2: 79 animate = Sequence::create(Animate::create(jump_effect_anim),Animate::create(roll_anim)); 80 animate->setTag(300); 81 cur_state = Panda::JUMP2; 82 shape->runAction(animate); 83 break; 84 } 85 } 86 87 int Panda::getCurrentState() 88 { 89 return cur_state; 90 } 91 92 void Panda::updateState() 93 { 94 if (shape->getActionByTag(200) == nullptr && shape->getActionByTag(300) == nullptr) 95 { 96 play(Panda::RUN); 97 } 98 }
然后是场景类,里面有一些逻辑处理:
1 #ifndef __GAMEMAIN_H__ 2 #define __GAMEMAIN_H__ 3 4 #include "cocos2d.h" 5 #include "Panda.h" 6 #include "Box2D\Box2D.h" 7 #include "GLES-Render.h" 8 9 USING_NS_CC; 10 //whether show assets 11 #define SHOW_DEBUG true 12 //draw scale of physics world 13 #define DRAW_SCALE 30.0f 14 #define ALL_ALHA 0.5f 15 16 class GameMain : public cocos2d::Layer 17 { 18 public: 19 //physic engine related 20 b2World * world; 21 Map<b2Body,Sprite> * map; 22 b2Body * panda_body; 23 //objects move speed 24 float speed; 25 Sprite * far_bg; 26 Sprite * middle_bg_0; 27 Sprite * middle_bg_1; 28 Size cSize; 29 Size visibleSize; 30 Layer * platform_container; 31 Panda * panda; 32 GLESDebugDraw * debugDraw; 33 // there‘s no ‘id‘ in cpp, so we recommend returning the class instance pointer 34 static cocos2d::Scene* createScene(); 35 36 // Here‘s a difference. Method ‘init‘ in cocos2d-x returns bool, instead of returning ‘id‘ in cocos2d-iphone 37 virtual bool init(); 38 39 virtual void onEnter(); 40 41 virtual void onExit(); 42 43 void update(float dt); 44 45 virtual bool onTouchBegan(Touch * touch, Event * event); 46 virtual void onTouchEnded(Touch * touch, Event * event); 47 virtual void onTouchMoved(Touch * touch, Event * event); 48 49 virtual bool onContactBegin(const PhysicsContact& contact); 50 // implement the "static create()" method manually 51 CREATE_FUNC(GameMain); 52 53 // 54 // Overrides 55 // 56 virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override; 57 private: 58 void create_ground(); 59 void gen_step(const std::string& res, float posX, float posY); 60 void init_physics_world(); 61 }; 62 63 #endif // __HELLOWORLD_SCENE_H__
1 #include "GameMain.h" 2 #include "cocos2d.h" 3 #include "Box2D\Box2D.h" 4 #include "GLES-Render.h" 5 USING_NS_CC; 6 7 Scene* GameMain::createScene() 8 { 9 auto scene = Scene::create(); 10 auto layer = GameMain::create(); 11 scene->addChild(layer); 12 return scene; 13 } 14 15 16 bool GameMain::init() 17 { 18 if ( !Layer::init() ) 19 { 20 return false; 21 } 22 speed = 5.0f; 23 auto listener = EventListenerTouchOneByOne::create(); 24 listener->onTouchBegan = CC_CALLBACK_2(GameMain::onTouchBegan,this); 25 listener->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEnded,this); 26 listener->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved,this); 27 28 Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); 29 30 visibleSize = Director::getInstance()->getVisibleSize(); 31 Vec2 origin = Director::getInstance()->getVisibleOrigin(); 32 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate 33 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources 34 35 middle_bg_0 = Sprite::create("bbg_snow_bone.jpg"); 36 cSize = middle_bg_0->getContentSize(); 37 middle_bg_0->setPosition(visibleSize.width*0.5, visibleSize.height*0.5); 38 middle_bg_0->setVisible(SHOW_DEBUG); 39 addChild(middle_bg_0); 40 41 init_physics_world(); 42 43 //gen_step(); 44 create_ground(); 45 46 //init panda and its physics data 47 Point p(visibleSize.width/2,visibleSize.height/2); 48 b2BodyDef bodyDef; 49 bodyDef.type = b2_dynamicBody; 50 bodyDef.position.Set(p.x/DRAW_SCALE, p.y/DRAW_SCALE); 51 panda_body = world->CreateBody(&bodyDef); 52 b2MassData massdata; 53 massdata.mass = 100.0f; 54 panda_body->SetMassData(&massdata); 55 56 b2PolygonShape shape; 57 shape.SetAsBox(50.0f/DRAW_SCALE, 100.0f/DRAW_SCALE); 58 59 b2FixtureDef fixtureDef; 60 fixtureDef.shape = &shape; 61 fixtureDef.density = 1.0f; 62 fixtureDef.friction = 0.3f; 63 panda_body->CreateFixture(&fixtureDef); 64 65 panda = Panda::create(); 66 panda->setPosition(visibleSize.width/2, visibleSize.height/2); 67 Size panda_size = Size(50.0f,100.0f); 68 panda->play(Panda::RUN); 69 this->addChild(panda); 70 panda_body->SetUserData(panda); 71 panda->setVisible(SHOW_DEBUG); 72 73 gen_step("platform_l.png",51.0,100.0f); 74 gen_step("platform_m.png",196.0f,100.0f); 75 gen_step("platform_r.png",392.0f,100.0f); 76 return true; 77 } 78 79 void GameMain::init_physics_world() 80 { 81 b2Vec2 gravity; 82 gravity.Set(0.0f,-10.0f); 83 world = new b2World(gravity); 84 world->SetAllowSleeping(true); 85 world->SetContinuousPhysics(true); 86 debugDraw = new GLESDebugDraw(DRAW_SCALE); 87 uint32 flags = 0; 88 flags += GLESDebugDraw::e_shapeBit; 89 flags += GLESDebugDraw::e_aabbBit; 90 flags += GLESDebugDraw::e_centerOfMassBit; 91 debugDraw->SetFlags(flags); 92 world->SetDebugDraw(debugDraw); 93 } 94 95 void GameMain::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) 96 { 97 world->DrawDebugData(); 98 Layer::draw(renderer,transform,flags); 99 } 100 101 void GameMain::create_ground() 102 { 103 b2BodyDef ground_def; 104 ground_def.position.Set(visibleSize.width/2/DRAW_SCALE, visibleSize.height/2/DRAW_SCALE); 105 106 b2Body * ground_body = world->CreateBody(&ground_def); 107 108 /***/ 109 //bottom 110 b2PolygonShape ground_shape; 111 ground_shape.SetAsBox(visibleSize.width/2/DRAW_SCALE,0,b2Vec2(0,-visibleSize.height/2/DRAW_SCALE),0); 112 ground_body->CreateFixture(&ground_shape,0); 113 //top 114 ground_shape.SetAsBox(visibleSize.width/2/DRAW_SCALE,0,b2Vec2(0,visibleSize.height/2/DRAW_SCALE),0); 115 ground_body->CreateFixture(&ground_shape,0); 116 //left 117 ground_shape.SetAsBox(0,visibleSize.height/2/DRAW_SCALE,b2Vec2(-visibleSize.width/2/DRAW_SCALE,0),0); 118 ground_body->CreateFixture(&ground_shape,0); 119 //right 120 ground_shape.SetAsBox(0,visibleSize.height/2/DRAW_SCALE,b2Vec2(visibleSize.width/2/DRAW_SCALE,0),0); 121 ground_body->CreateFixture(&ground_shape,0); 122 } 123 124 bool GameMain::onTouchBegan(Touch * touch, Event * event) 125 { 126 Sprite * ball = Sprite::create("apple.png"); 127 ball->setVisible(SHOW_DEBUG); 128 Size contentSize = ball->getContentSize(); 129 addChild(ball); 130 b2BodyDef ballDef; 131 ballDef.type = b2_dynamicBody; 132 ballDef.position.Set(touch->getLocation().x/DRAW_SCALE, touch->getLocation().y/DRAW_SCALE); 133 b2Body * ballBody = world->CreateBody(&ballDef); 134 ballBody->SetUserData(ball); 135 b2MassData massData; 136 massData.center = b2Vec2(contentSize.width/2/DRAW_SCALE,contentSize.height/2/DRAW_SCALE); 137 massData.mass = 50; 138 b2CircleShape ballShape; 139 ballShape.ComputeMass(&massData,1.5f); 140 ballShape.m_radius=45.0f/DRAW_SCALE; 141 b2FixtureDef ballFixture; 142 ballFixture.density = 10.0f; 143 ballFixture.friction = 1.0f; 144 ballFixture.restitution = 0.6f; 145 ballFixture.shape = &ballShape; 146 147 ballBody->CreateFixture(&ballFixture); 148 ballBody->SetGravityScale(10); 149 /** 150 float vx = touch->getLocation().x > panda->getPositionX() ? 100.0f : -100.0f; 151 panda->updateState(); 152 int cur_state = panda->getCurrentState(); 153 if( cur_state == Panda::RUN ) 154 { 155 panda_body->ApplyForce(b2Vec2(vx,1000),panda_body->GetPosition(),true); 156 panda->play(Panda::JUMP); 157 }else if(cur_state == Panda::JUMP) 158 { 159 panda_body->ApplyForce(b2Vec2(vx,800),panda_body->GetPosition(),true); 160 panda->play(Panda::JUMP2); 161 }*/ 162 return true; 163 } 164 165 void GameMain::update(float dt) 166 { 167 world->Step(dt,10,10); 168 b2Body* b = world->GetBodyList(); 169 while (b) 170 { 171 b2Body* bNext = b->GetNext(); 172 Sprite * sp = (Sprite *)b->GetUserData(); 173 if( sp != nullptr ) 174 sp->setPosition(b->GetPosition().x*DRAW_SCALE,b->GetPosition().y*DRAW_SCALE); 175 176 b = bNext; 177 } 178 } 179 180 //generate floors 181 void GameMain::gen_step(const std::string& res, float posX, float posY) 182 { 183 Sprite * sp = Sprite::create(res); 184 sp->setPosition(posX, posY); 185 addChild(sp); 186 187 Size contentSize = sp->getContentSize(); 188 189 b2BodyDef bodyDef; 190 bodyDef.position.Set(posX/DRAW_SCALE, posY/DRAW_SCALE); 191 b2Body * body = world->CreateBody(&bodyDef); 192 193 b2PolygonShape shape; 194 shape.SetAsBox(contentSize.width/DRAW_SCALE, contentSize.height/DRAW_SCALE); 195 196 b2FixtureDef fixtureDef; 197 fixtureDef.shape = &shape; 198 fixtureDef.density = 1.0f; 199 fixtureDef.friction = 0.3f; 200 body->CreateFixture(&fixtureDef); 201 sp->setVisible(SHOW_DEBUG); 202 } 203 204 bool GameMain::onContactBegin(const PhysicsContact& contact) 205 { 206 log("Contacted..."); 207 return true; 208 } 209 210 void GameMain::onTouchEnded(Touch * touch, Event * event) 211 { 212 213 } 214 215 void GameMain::onTouchMoved(Touch * touch, Event * event) 216 { 217 218 } 219 220 void GameMain::onEnter() 221 { 222 Layer::onEnter(); 223 this->schedule(schedule_selector(GameMain::update),0.03f); 224 } 225 226 void GameMain::onExit() 227 { 228 delete world; 229 delete debugDraw; 230 Layer::onExit(); 231 this->unschedule(schedule_selector(GameMain::update)); 232 }
需要注意:
1 所有的熊猫动作打包成一个plist,所有的场景素材打包到另一个plist
这就是我们在场景类的init方法里面预加载的两个文件:
1 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate 2 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources
资源目录应该是这样的:
2 去cocos2d-x-3.2\tests\cpp-tests\Classes\Box2DTestBed下面将GLES-Render.h和GLES-Render.cpp拷到项目里面来,因为Box2d没有b2Debug的实现,这个是我们手头上需要的实现。
3 使用Box2d的时候极有可能会出现编译连接错误,找不到Box2d相关的类,这个时候需要将cocos2d-x-3.2\external\Box2D编译一下,然后在项目的连接器中加入引用:
4 通过b2World::SetDebugDraw(b2Draw * draw)将GLESDebugDraw实例设置为world的debugDraw之后,每帧调用world->DrawDebugData()是不会生效的,只有复写Layer::draw()方法并在其中调用此方法才会显示debugDraw。
由于debugDraw是调用显卡去渲染的,所以debugDraw是位于场景最底层的,当你把所有场景内的显示对象的通过setVisible设置为false的时候,你就可以看到了。
可以设置SHOW_DEBUG的值来设置显示对象的visible。
如下:
可以看到下面的debugDraw有点测漏了。
5 cocos2dx 3.2内置了chipmunk物理引擎,用起来比较简单,但是我之前学习ActionScript的时候有用过Box2d,并且Box2d的功能比较强大一些,所以选择了Box2d。ActionScript还有一个效率比Box2d稍强的无力引擎:Nape
以下是一开始用chipmunk实现的一些功能的代码:
1 #include "GameMain.h" 2 #include "cocos2d.h" 3 4 USING_NS_CC; 5 6 Scene* GameMain::createScene() 7 { 8 auto scene = Scene::createWithPhysics(); 9 scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); 10 scene->getPhysicsWorld()->setUpdateRate(0.5); 11 auto layer = GameMain::create(); 12 scene->addChild(layer); 13 return scene; 14 } 15 16 17 bool GameMain::init() 18 { 19 if ( !Layer::init() ) 20 { 21 return false; 22 } 23 24 speed = 5.0f; 25 26 auto listener = EventListenerTouchOneByOne::create(); 27 listener->onTouchBegan = CC_CALLBACK_2(GameMain::onTouchBegan,this); 28 listener->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEnded,this); 29 listener->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved,this); 30 31 auto contact_listener = EventListenerPhysicsContact::create(); 32 contact_listener->onContactBegin = CC_CALLBACK_1(GameMain::onContactBegin,this); 33 34 Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this); 35 Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(contact_listener,1); 36 37 visibleSize = Director::getInstance()->getVisibleSize(); 38 Vec2 origin = Director::getInstance()->getVisibleOrigin(); 39 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("panda.plist");//add panda animate 40 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("scene.plist");//add scene resources 41 42 middle_bg_0 = Sprite::create("bbg_snow_bone.jpg"); 43 cSize = middle_bg_0->getContentSize(); 44 middle_bg_0->setPosition(visibleSize.width*0.5, visibleSize.height*0.5); 45 middle_bg_1 = Sprite::create("bbg_snow_bone.jpg"); 46 middle_bg_1->setPosition(visibleSize.width*1.5, visibleSize.height*0.5); 47 addChild(middle_bg_0); 48 addChild(middle_bg_1); 49 50 gen_floor(); 51 52 panda = Panda::create(); 53 panda->setPosition(visibleSize.width/2, visibleSize.height/2); 54 Size panda_size = Size(50.0f,100.0f); 55 panda->play(Panda::RUN); 56 auto panda_body = PhysicsBody::createBox(panda_size); 57 panda_body->retain(); 58 panda->setPhysicsBody(panda_body); 59 this->addChild(panda); 60 61 auto edge_sp = Sprite::create(); 62 auto edge_body = PhysicsBody::createEdgeBox(visibleSize,PHYSICSBODY_MATERIAL_DEFAULT,3); 63 edge_sp->setPosition(visibleSize.width/2, visibleSize.height/2); 64 edge_sp->setPhysicsBody(edge_body); 65 edge_sp->setTag(0); 66 addChild(edge_sp); 67 return true; 68 } 69 70 bool GameMain::onContactBegin(const PhysicsContact& contact) 71 { 72 log("Contacted..."); 73 return true; 74 } 75 76 bool GameMain::onTouchBegan(Touch * touch, Event * event) 77 { 78 log("Touched....."); 79 int cur_state = panda->getCurrentState(); 80 panda->getPhysicsBody()->applyForce(Vect(500.0f,500.0f)); 81 if( cur_state == Panda::RUN ) 82 { 83 panda->play(Panda::JUMP); 84 }else if(cur_state == Panda::JUMP) 85 { 86 panda->play(Panda::JUMP2); 87 } 88 return true; 89 } 90 91 void GameMain::onTouchEnded(Touch * touch, Event * event) 92 { 93 94 } 95 96 void GameMain::onTouchMoved(Touch * touch, Event * event) 97 { 98 99 } 100 101 void GameMain::update(float dt) 102 { 103 /** 104 middle_bg_0->setPositionX(middle_bg_0->getPositionX()-speed); 105 106 if(middle_bg_0->getPositionX() < -cSize.width*0.5) 107 { 108 middle_bg_0->setPositionX(cSize.width*1.5); 109 } 110 111 middle_bg_1->setPositionX(middle_bg_1->getPositionX()-speed); 112 if(middle_bg_1->getPositionX() < -cSize.width*0.5) 113 { 114 middle_bg_1->setPositionX(cSize.width*1.5); 115 }*/ 116 } 117 118 void GameMain::onEnter() 119 { 120 Layer::onEnter(); 121 this->schedule(schedule_selector(GameMain::update),0.05f); 122 } 123 124 void GameMain::onExit() 125 { 126 Layer::onExit(); 127 this->unschedule(schedule_selector(GameMain::update)); 128 } 129 130 void GameMain::createFloor(int posx,int posy,int w,int h) 131 { 132 133 } 134 //generate floors 135 void GameMain::gen_floor() 136 { 137 Sprite * sp_l = Sprite::create("platform_l.png"); 138 auto body_l = PhysicsBody::createBox(sp_l->getContentSize()); 139 body_l->setDynamic(false); 140 sp_l->setPhysicsBody(body_l); 141 sp_l->setPosition(50.0f,150); 142 addChild(sp_l); 143 144 Sprite * sp_m = Sprite::create("platform_m.png"); 145 auto body_m = PhysicsBody::createBox(sp_m->getContentSize()); 146 body_m->setDynamic(false); 147 sp_m->setPhysicsBody(body_m); 148 sp_m->setPosition(sp_l->getPositionX() + (sp_l->getContentSize().width+sp_m->getContentSize().width)/2,150); 149 addChild(sp_m); 150 151 Sprite * sp_r = Sprite::create("platform_r.png"); 152 auto body_r = PhysicsBody::createBox(sp_r->getContentSize()); 153 body_r->setDynamic(false); 154 sp_r->setPhysicsBody(body_r); 155 sp_r->setPosition(sp_m->getPositionX() + (sp_m->getContentSize().width+sp_r->getContentSize().width)/2,150); 156 addChild(sp_r); 157 }
建议:
不想吐槽搜索引擎,建议大家遇到问题先尝试自己解决,解决不了的然后可以用国外的搜索引擎搜索一下,或者可以直接去StackFlow去搜索
参考文章:
(翻译)介绍Box2D的Cocos2D 2.X教程:弹球[Cocos2D-x For WP8]Box2D物理引擎How to enable Box2d debug draw with Coco2d-x 3.0 beta 2笔记:利用 Cocos2dx 3.2 与 Box2D制作一个跑酷游戏
标签:des style blog http color os io 使用 ar
原文地址:http://www.cnblogs.com/adoontheway/p/3953773.html