Upgrade to Pro — share decks privately, control downloads, hide ads and more …

「釣り★スタ」でのCocos2d-JSを使ってのアプリアップデート事例 (1)

「釣り★スタ」でのCocos2d-JSを使ってのアプリアップデート事例 (1)

Cocos2d-x Talks #2(2015/3/13)発表資料
https://atnd.org/events/62594

グリー株式会社 Wright Flyer Studios部 中 貴弘
グリー株式会社 Web Game Studio部 / 釣りスタグループ 和田孝尚

日本最初のモバイルソーシャルゲームである「釣り★スタ」
フィーチャーフォンの時代から存在するゲームをCocos2d-JSを使ってのアプリ作成。
既に稼働中のサービスに対して行うアプリアップデート、その中で一番重視したことは実現できたのか。実際にプレイしたユーザからの反応等も交えて開発の事例を紹介できればと思います。

gree_tech

March 12, 2015
Tweet

More Decks by gree_tech

Other Decks in Technology

Transcript

  1. 和田孝尚 ショウカイ エンジニア アプリ実装を主に担当 レベル:  38  HP: 166  MP:  63 2011年

    グリー入社 2013年 釣り★スタチームへ これが初のCOCOS2D-X GL verts: 192 GL calls: 8 57.82 / 0.004
  2. • HTMLやらFlashLiteで構成 レキシ • SP対応時当初はHTML5(Canvasとか) • 2013年後半LWFに対応 GL verts: 72

    GL calls: 4 59.71 / 0.008 ※LWF(LightWeightSWF)とはGREEが開発しているオープンソース(zlib License)のフレームワークです。
  3. カンケイ COCOS2D-JS JavaScript JSB Objective-C iOS Android Java C++ COCOS2D-X

    Library GREE-SDK JSB GL verts: 510 GL calls: 15 59.04 / 0.011
  4. ガゾウ var bgLayer = cc.Sprite.create("RaidBattle/bgselect.jpg"); bgLayer.setPosition( cc.p(layerSize.width*.5, layerSize.width*.5)); this.addChild(bgLayer); •

    Sprite(CCSprite)で画像表示 • 1枚画像なんかは 基本これのみ • リソースパス等あれば初期化時に設定する GL verts: 126 GL calls: 5 57.89 / 0.009
  5. GL verts: 72 GL calls: 4 57.56 / 0.007 ギョエイ

    • DrawNode(CCDrawNode)を利用してみてる • 図形描画でアニメーション等検討 • ゲーム中の標的でアニメーションが必要
  6. シヨウ JS_FN("drawDot", js_cocos2dx_CCDrawNode_drawDot, 3, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("drawSegment", js_cocos2dx_CCDrawNode_drawSegment, 4,

    JSPROP_PERMANENT | JSPROP_ENUMERATE), • 2系だと描画命令が少ない • 3系だともう少し増える • 2系と3系で機能が違うものも多々ある GL verts: 126 GL calls: 5 57.56 / 0.006
  7. タイオウ JS_FN("drawDot", js_cocos2dx_CCDrawNode_drawDot, 3, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("drawSegment", js_cocos2dx_CCDrawNode_drawSegment, 4,

    JSPROP_PERMANENT | JSPROP_ENUMERATE), • 2系だと描画命令が少ない • 3系だともう少し増える • 2系と3系で機能が違うものも多々ある var fishSp = cc.Sprite.create("RaidBattle/fish_sprite.png"); var fishBody = cc.Sprite.create(); var animeCache = cc.AnimationCache.getInstance(); var fishAnime = animeCache.getAnimation("raidfish"); if(fishAnime == null){ var fish1 = cc.SpriteFrame.createWithTexture( fishSp.getTexture(), cc.rect(0, 0, 200, 100)); var fish2 = cc.SpriteFrame.createWithTexture( fishSp.getTexture(), cc.rect(0, 100, 200, 100)); var animFrames = []; animFrames.push(fish1); animFrames.push(fish2); fishAnime = cc.Animation.create(animFrames, 0.05); animeCache.addAnimation(fishAnime, "raidfish"); } fishBody.runAction( cc.RepeatForever.create(cc.Animate.create(fishAnime))); GL verts: 186 GL calls: 7 57.94 / 0.008
  8. JS_FN("drawDot", js_cocos2dx_CCDrawNode_drawDot, 3, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_FN("drawSegment", js_cocos2dx_CCDrawNode_drawSegment, 4, JSPROP_PERMANENT

    | JSPROP_ENUMERATE), • 2系だと描画命令が少ない • 3系だともう少し増える • 2系と3系で機能が違うものも多々ある var fishSp = cc.Sprite.create("RaidBattle/fish_sprite.png"); var fishBody = cc.Sprite.create(); var animeCache = cc.AnimationCache.getInstance(); var fishAnime = animeCache.getAnimation("raidfish"); if(fishAnime == null){ var fish1 = cc.SpriteFrame.createWithTexture( fishSp.getTexture(), cc.rect(0, 0, 200, 100)); var fish2 = cc.SpriteFrame.createWithTexture( fishSp.getTexture(), cc.rect(0, 100, 200, 100)); var animFrames = []; animFrames.push(fish1); animFrames.push(fish2); fishAnime = cc.Animation.create(animFrames, 0.05); animeCache.addAnimation(fishAnime, "raidfish"); } fishBody.runAction( cc.RepeatForever.create(cc.Animate.create(fishAnime))); ヨウテン • Animation(CCAnimation)やAction類の利用 • 1画面内で使う画像などは1枚にまとめる • 既存の素材を再編集して使い回し GL verts: 246 GL calls: 8 58.23 / 0.014
  9. テイギ common lineHeight=32 base=0 scaleW=1 scaleH=1 pages=1 packed=0 page id=0

    file="raid_font.png" chars count=28 char id=48 x=5 y=0 width=50 height=64 xoffset=0 yoffset=0 xadvance=50 page=0 chnl=0 // 0 ʢதུʣ char id=19975 x=192 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ສ char id=33021 x=256 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ೳ GL verts: 66 GL calls: 3 59.02 / 0.007
  10. リヨウ common lineHeight=32 base=0 scaleW=1 scaleH=1 pages=1 packed=0 page id=0

    file="raid_font.png" chars count=28 char id=48 x=5 y=0 width=50 height=64 xoffset=0 yoffset=0 xadvance=50 page=0 chnl=0 // 0 ʢதུʣ char id=19975 x=192 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ສ char id=33021 x=256 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ೳ var rod_status = cc.LabelBMFont.create(“ສೳ","raid_font.fnt"); rod_status.setPosition(cc.p(160, 365)); rod_status.setZOrder(1); this.addChild(rod_status); GL verts: 126 GL calls: 4 58.59 / 0.007
  11. ヨウテン common lineHeight=32 base=0 scaleW=1 scaleH=1 pages=1 packed=0 page id=0

    file="raid_font.png" chars count=28 char id=48 x=5 y=0 width=50 height=64 xoffset=0 yoffset=0 xadvance=50 page=0 chnl=0 // 0 ʢதུʣ char id=19975 x=192 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ສ char id=33021 x=256 y=192 width=64 height=64 xoffset=0 yoffset=0 xadvance=64 page=0 chnl=0 // ೳ • LabelBMFont(CCLabelBMFont)を利用 • 子要素にアクセスして文字列アニメ等 • 白色で作ると色変更なんかも可能 GL verts: 186 GL calls: 6 58.84 / 0.006
  12. • 背景等は固定画像 ハイケイ • アイテム類は可変画像 • フルスクリーン表示 ユーザ所持アイテム イベント毎に増加 •

    アイテムの所持状態に左右される • 総量がかなりの量、7年以上の物量 • 全てをダウンロードするには不向き GL verts: 198 GL calls: 8 59.32 / 0.012
  13. テイギ JSClass js_class = { "ImageLoader", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,

    JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, basic_object_finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; DownloadResource::js_class = js_class • JavaScript側でのクラス定義 • 基本は既存クラスの処理を真似すればいい GL verts: 126 GL calls: 5 57.74 / 0.007
  14. テイギ • JavaScript側でのクラス定義 • 基本は既存クラスの同類の処理を真似 Property,function,static_functionの定義 static JSPropertySpec props[] =

    { {0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER} }; static JSFunctionSpec funcs[] = { JS_BINDED_FUNC_FOR_DEF(ImageLoader, loadURL), JS_FS_END }; static JSFunctionSpec st_funcs[] = { JS_FS_END }; GL verts: 246 GL calls: 7 57.98 / 0.006
  15. テイギ static JSPropertySpec props[] = { {0, 0, 0, JSOP_NULLWRAPPER,

    JSOP_NULLWRAPPER} }; static JSFunctionSpec funcs[] = { JS_BINDED_FUNC_FOR_DEF(ImageLoader, loadURL), JS_FS_END }; static JSFunctionSpec st_funcs[] = { JS_FS_END }; ImageLoader::js_parent = NULL; ImageLoader::js_proto = JS_InitClass(cx, global, NULL, &ImageLoader::js_class , ImageLoader::_js_constructor, 0, props, funcs, NULL, st_funcs); • 既存クラスの継承等も可能 • サンプル類は豊富にあるので読んで理解 • 2系と3系で微妙に違うので注意 GL verts: 366 GL calls: 9 57.98 / 0.006
  16. テイギ JS_BINDED_CONSTRUCTOR_IMPL(ImageLoader) { ImageLoader* pAuthorizer = new ImageLoader(); pAuthorizer->autorelease(); js_proxy_t

    *p; jsval out; JSObject *obj = JS_NewObject(cx, &ImageLoader::js_class, ImageLoader::js_proto, ImageLoader::js_parent); if (obj) { JS_SetPrivate(obj, pAuthorizer); out = OBJECT_TO_JSVAL(obj); } JS_SET_RVAL(cx, vp, out); p =jsb_new_proxy(pAuthorizer, obj); JS_AddNamedObjectRoot(cx, &p->obj, "ImageLoader"); return JS_TRUE; } GL verts: 66 GL calls: 3 58.24 / 0.007
  17. JS_BINDED_CONSTRUCTOR_IMPL(ImageLoader) { ImageLoader* pAuthorizer = new ImageLoader(); pAuthorizer->autorelease(); js_proxy_t *p;

    jsval out; JSObject *obj = JS_NewObject(cx, &ImageLoader::js_class, ImageLoader::js_proto, ImageLoader::js_parent); if (obj) { JS_SetPrivate(obj, pAuthorizer); out = OBJECT_TO_JSVAL(obj); } JS_SET_RVAL(cx, vp, out); p =jsb_new_proxy(pAuthorizer, obj); JS_AddNamedObjectRoot(cx, &p->obj, "ImageLoader"); return JS_TRUE; } テイギ var imageLoader = new ImageLoader(); JavaScript GL verts: 132 GL calls: 5 58.32 / 0.006
  18. テイギ JS_BINDED_FUNC_IMPL(ImageLoader, loadURL) { jsval *arg = JS_ARGV(cx, vp); if

    (argc == 1) { JSStringWrapper arg0(arg[0]); CCHttpReqest *request = new CCHttpRequest(); request->setUrl(arg0.c_str()); request->setRequestType(CCHttpRequest::kHttpGet); request->setResponseCallback(this, httpresponse_selector(ImageLoader::onLoadCompleted)); CCHttpClient::getInstance()->send(request); request->release(); return JS_TRUE; } return JS_FALSE; } GL verts: 66 GL calls: 3 58.27 / 0.007
  19. テイギ JS_BINDED_FUNC_IMPL(ImageLoader, loadURL) { jsval *arg = JS_ARGV(cx, vp); if

    (argc == 1) { JSStringWrapper arg0(arg[0]); CCHttpReqest *request = new CCHttpRequest(); request->setUrl(arg0.c_str()); request->setRequestType(CCHttpRequest::kHttpGet); request->setResponseCallback(this, httpresponse_selector(ImageLoader::onLoadCompleted)); CCHttpClient::getInstance()->send(request); request->release(); return JS_TRUE; } return JS_FALSE; } var imageLoader = new ImageLoader(); imageLoader.loadURL(“http://xxx.com/test.png”); JavaScript GL verts: 132 GL calls: 5 58.26 / 0.008
  20. テイギ void ImageLoader::onLoadCompleted(CCHttpClient *client, CCHttpResponse *response) { if (!response->isSucceed()) {return;}

    std::vector<char> *buffer = response->getResponseData(); CCImage *image = new CCImage(); image->initWithImageData(&(buffer->front()), buffer->size()); /* ~response͔Βը૾ੜ੒ޙ~ */ js_proxy_t* p = jsb_get_native_proxy(this); JSContext* cx = ScriptingCore::getInstance()->getGlobalContext(); js_proxy_t *sp = js_get_or_create_proxy<cocos2d::CCTexture2D>(cx, texture); jsval retval; jsval v[] = { v[0] = OBJECT_TO_JSVAL(sp->obj) }; ScriptingCore::getInstance()->executeFunctionWithOwner( OBJECT_TO_JSVAL(p->obj),"imageRequestComplete", 1, v, &retval); image->release(); } GL verts: 66 GL calls: 3 57.97 / 0.004
  21. テイギ void ImageLoader::onLoadCompleted(CCHttpClient *client, CCHttpResponse *response) { if (!response->isSucceed()) {return;}

    std::vector<char> *buffer = response->getResponseData(); CCImage *image = new CCImage(); image->initWithImageData(&(buffer->front()), buffer->size()); /* ~response͔Βը૾ੜ੒ޙ~ */ js_proxy_t* p = jsb_get_native_proxy(this); JSContext* cx = ScriptingCore::getInstance()->getGlobalContext(); js_proxy_t *sp = js_get_or_create_proxy<cocos2d::CCTexture2D>(cx, texture); jsval retval; jsval v[] = { v[0] = OBJECT_TO_JSVAL(sp->obj) }; ScriptingCore::getInstance()->executeFunctionWithOwner( OBJECT_TO_JSVAL(p->obj),"imageRequestComplete", 1, v, &retval); image->release(); } var imageLoader = new ImageLoader(); imageLoader.imageRequestComplete = function(texture) { var t = texture.getContentSize() var loadSprite = getXXXXImageSprite(); loadSprite.setTexture(texture); loadSprite.setTextureRect( cc.rect(0, 0, t.width, t.height)); } imageLoader.loadURL(“http://xxx.com/test.png”); JavaScript GL verts: 132 GL calls: 5 57.75 / 0.005
  22. テイギ float CCDeviceInfo::getStausBarHeight(){ CGRect statusBarRect = [UIApplication sharedApplication].statusBarFrame; CGFloat statusBarHeight

    = statusBarRect.size.height > statusBarRect.size.width ? statusBarRect.size.width : statusBarRect.size.height; return statusBarHeight; } iOS public static int getStausBarHeight(){ return 0; } Java GL verts: 84 GL calls: 6 58.83 / 0.009
  23. テイギ float CCDeviceInfo::getStausBarHeight(){ CGRect statusBarRect = [UIApplication sharedApplication].statusBarFrame; CGFloat statusBarHeight

    = statusBarRect.size.height > statusBarRect.size.width ? statusBarRect.size.width : statusBarRect.size.height; return statusBarHeight; } iOS public static int getStausBarHeight(){ return 0; } Java • iOSなら拡張子*.mmにしてC++とObj-C併用 • AndroidはJNI利用して呼び出し GL verts: 144 GL calls: 8 58.82 / 0.007
  24. テイギ CCDirector *pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();

    CCSize frameSize = pEGLView->getFrameSize(); pDirector->setContentScaleFactor(frameSize.width/320); cocos2d::CCEGLView::sharedOpenGLView()->setDesignResolutionSize( 320, 480, kResolutionFixedWidth); AppDelegate.cpp • 利用する画像の基準でScaleを指定 • 固定サイズで調整するのが簡単(320,480) GL verts: 132 GL calls: 6 58.79 / 0.006
  25. テイギ CCDirector *pDirector = CCDirector::sharedDirector(); pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();

    CCSize frameSize = pEGLView->getFrameSize(); pDirector->setContentScaleFactor(frameSize.width/320); cocos2d::CCEGLView::sharedOpenGLView()->setDesignResolutionSize( 320, 480, kResolutionFixedWidth); AppDelegate.cpp • 利用する画像の基準でScaleを指定 • 固定サイズで調整するのが簡単(320,480) var imgScale = 1/imgDirector.getContentScaleFactor(); var scaleSize = cc.rect(x*imgScale, y*imgScale, width*imgScale, height*imgScale); createWithTexture౳ɿRetina(x2)૝ఆ var imgScale = imgDirector.getContentScaleFactor()/2; imageSprite.setScale(imgScale); Sprite౳ɿRetina(x2)૝ఆ GL verts: 266 GL calls: 10 58.79 / 0.006
  26. カイシ bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector *pDirector =

    CCDirector::sharedDirector(); pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); ScriptingCore* sc = ScriptingCore::getInstance(); /* ~লུ~ */ sc->start(); CCScriptEngineProtocol *pEngine = ScriptingCore::getInstance(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); ScriptingCore::getInstance()->runScript("cocos2d-jsb.js"); return true; } GL verts: 66 GL calls: 3 59.47 / 0.005
  27. bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector *pDirector = CCDirector::sharedDirector();

    pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); ScriptingCore* sc = ScriptingCore::getInstance(); /* ~লུ~ */ sc->start(); CCScriptEngineProtocol *pEngine = ScriptingCore::getInstance(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); ScriptingCore::getInstance()->runScript("cocos2d-jsb.js"); return true; } カイセキ • JS自体はアプリ内に存在 • runScriptから処理を開始 • 難読化しても読もうと思えば読める GL verts: 126 GL calls: 5 59.78 / 0.005
  28. bool AppDelegate::applicationDidFinishLaunching() { // initialize director CCDirector *pDirector = CCDirector::sharedDirector();

    pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); ScriptingCore* sc = ScriptingCore::getInstance(); /* ~লུ~ */ sc->start(); CCScriptEngineProtocol *pEngine = ScriptingCore::getInstance(); CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine); ScriptingCore::getInstance()->runScript("cocos2d-jsb.js"); return true; } シボウ 書き換えも可能 GL verts: 126 GL calls: 5 59.25 / 0.005
  29. タイサク 起動時 • 更新頻度低いもの含めてチェック • ファイルのハッシュ値比較 • 問題なければJS実行 通常時 •

    その時点で必要なもののみチェック • ファイルのハッシュ値比較 • 更新があればスクリプト再起動 GL verts: 138 GL calls: 6 59.07 / 0.006
  30. ヘンコウ var direvtor = cc.Director.getInstance(); var color_a = cc.c4b(0, 0,

    0, 255); var color_b = cc.BLACK; var sp_test = cc.Sprite.create(“test.png”); var direvtor = cc.director; var color_a = cc.color(0, 0, 0, 255); var color_b = cc.color.BLACK; var sp_test = new cc.Sprite(“test.png”); Cocos2d-html5 v2.x Cocos2d-JS v3.x GL verts: 84 GL calls: 6 59.37 / 0.008
  31. ヘンコウ var direvtor = cc.Director.getInstance(); var color_a = cc.c4b(0, 0,

    0, 255); var color_b = cc.BLACK; var sp_test = cc.Sprite.create(“test.png”); var direvtor = cc.director; var color_a = cc.color(0, 0, 0, 255); var color_b = cc.color.BLACK; var sp_test = new cc.Sprite(“test.png”); Cocos2d-html5 v2.x Cocos2d-JS v3.x • Webもサポートするなら書き方注意 • 基本はそのまま • 旧記述のままだと挙動が変わるケースも GL verts: 144 GL calls: 8 59.48 / 0.008
  32. ヘンコウ this.setTouchMode(cc.TOUCH_ONE_BY_ONE); this.setTouchEnabled(true); this.onTouchBegan = function( touch ) { /*~

    TODO:Touch Event ~*/ } cc.eventManager.addListener(cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, onTouchBegan:function (touches, event) { /*~ TODO:Touch Event ~*/ } }), this); Cocos2d-html5 v2.x Cocos2d-JS v3.x GL verts: 84 GL calls: 6 59.36 / 0.007
  33. ヘンコウ this.setTouchMode(cc.TOUCH_ONE_BY_ONE); this.setTouchEnabled(true); this.onTouchBegan = function( touch ) { /*~

    TODO:Touch Event ~*/ } cc.eventManager.addListener(cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, onTouchBegan:function (touches, event) { /*~ TODO:Touch Event ~*/ } }), this); Cocos2d-html5 v2.x Cocos2d-JS v3.x • Touchイベントはcocos2d-xと同様の変更 • WebもやるならMouseイベントも設定 • サンプルにだいたい欲しい処理がある GL verts: 144 GL calls: 8 59.34 / 0.008