Nobollel開発者ブログ

Nobollelのエンジニアが、UnityやCocos2d-xの旬な情報・技術を紹介します。

A quick look at Cocos Creator

Hello! Im Bjorn, one of the developers here at Nobollel. I mainly work with Cocos2d-x along with the now defunct editor Cocos Studio, today i will be taking a quick look at the latest addition to the Cocos2d-x family, Cocos Creator.

What is Cocos Creator?

f:id:nobollel:20161019173945p:plain

Cocos Creator is a complete game development solution, including:

It also comes with a built in version of Cocos2d-x, so all you need to get started is to head over to the download page and select your OS.

http://cocos2d-x.org/download

Those familiar with Cocos Studio won’t have a hard time getting used to the new editor. Cocos Creator looks and feels very similar, but with a few upgrades and tweaks here and there. For example, the new animation editor gives you better control over your animations, and allows you to separate different animation using Animation Clips rather than labeling keyframes.

Another great addition is the ability to add components to your objects on the scene, by either drag and drop, or with the “Add Component” button at the bottom of the Properties panel.

f:id:nobollel:20161019183753p:plain

Like Unity, adding custom components to your objects allows you to add your own parameters to the object, allowing for easy tweaks without having to touch the code.

Besides that, all your basic elements are still there, you have your sprites, buttons, progress bars and all that other good stuff you need for your levels and UI. The only things i find missing, as far as ui elements go, are checkboxes and sliders.

Even though some features are still missing, from what I’ve seen so far I’m pleased with the upgrades from Cocos Studio, but keep in mind that Cocos Creator is still fairly new, and can sometimes behaves a bit funny, usually sorted with a quick restart. With that being said, I’m not sure if its suitable for larger project as of yet. However, the team seem to be working hard, pushing out new updates, and I’m sure with time it will be an awesome tool to work with, and i can definitely see myself using it in the future.

Where to go from here?

While there are many similarities between the two, if you are a Unity developer interested in trying Cocos2d-x and Cocos Creator yourself, i suggest checking out the “Unity User Guide” over at the Cocos2d-x website.

http://cocos2d-x.org/docs/editors_and_tools/creator-chapters/getting-started/unity-guide/

And for a more detailed guide on how to create your first game with Cocos Creator, check out the Quick Start Guide.

http://cocos2d-x.org/docs/editors_and_tools/creator-chapters/getting-started/quick-start/

Admobでバナーの位置を微調整する(Android)

皆さんこんにちは、エンジニアの石橋です。

今回は前回の続きでアンドロイド用のライブラリの対応です。

Android Studio以下のフォルダをインポートします。

f:id:nobollel:20161011143742p:plain

以下のファイルを編集します。

source/android-library/app/src/main/java/com/google/unity/ads/Banner.java

    //C#側のenum値に対応するセンタリングを追加します。
    private static final int POSITION_CENTER = 6;
    //オフセットを受け取れるように引数を追加します。
    public void create(final String publisherId, final AdSize adSize, final int positionCode, final float x, final float y) {
    //positionCodeのswitch文にセンターポジションへの対応を追加します
    case POSITION_CENTER:
        adParams.gravity = Gravity.CENTER;
        break;
    //activity.addContentViewの下にオフセット変更処理を追加します
    adView.setTranslationX( x );
    adView.setTranslationY( y );

gradleでandroid-library/app/Tasks/other/makeJarを実行し、jarを作成。
unity-plugin-library.jarが出来たらUnity上に既に存在しているものに上書きします。

以上になります。

【Reactive Extensions】 IObservableの合成と分岐入門その3

おはこんばんにちは、tsuchimotoです。

今回は前回に引き続き、UniRxを使ったIObservableの合成と分岐入門の3回目になります。

前回までのリンク

SelectManyメソッド

SelectManyメソッドについて説明します。

SelectManyメソッドはざっくり言うとSelctメソッドとMergeメソッドを組合わせたものになります。

説明する前に前回紹介したMergeメソッドを再掲します。

// 返されたIObservable<T>をすべてマージ
public static IObservable<T> Merge<T>(this IObservable<IObservable<T>> sources);

このMergeメソッドのシグネチャIObservable<T>を返すようなIObservableを引数にとりました。

SelectManyメソッドのシグネチャは以下になります。

public static IObservable<R> SelectMany<T, R>(
    this IObservable<T> source,
    Func<T,IObservable<R>> selector
);

最初の引数はIObservable、2番目の引数はIObservable<R>を返すようなデリゲートをとります。

どこか似ていませんか?

そう、最初の引数と2番目の引数を組合わせると、Mergeメソッドの引数の形になるのです。

まとめると、もともとのIObservableを別のIObservableに変換して合成しているので、以下のように

Select(IObservableを別のIObservableに変換)して、Merge (合成)しているのと同じことなのです。

SelectMany(i => Observable.Return(i))
↓
Select(i => Observable.Return(i)).Merge()

SelectManyメソッドの使用例を以下に示します。

var source = new Subject<string>();
source
    // IObservable<string>からIObservable<IObservable<string>>に変換して合成
    .SelectMany(i => Observable
        // 0,1,2と値を3つ発行
        .Interval(TimeSpan.FromMilliseconds(250)).Take(3)
        // 値を変換
        .Select(j => i + j))
    // 購読
    .Subscribe(
        i => Debug.Log("OnNext: " +  i),
        i => Debug.Log("OnCompleted"));

// 値の発行
source.OnNext("a");
source.OnNext("b");
source.OnNext("c");
source.OnNext("d");

実行結果は以下のようになります。実行結果もSelect/Mergeの結果と全く同じになります。

OnNext: a0
OnNext: b0
OnNext: c0
OnNext: d0
OnNext: a1
OnNext: b1
OnNext: c1
OnNext: d1
OnNext: a2
OnNext: b2
OnNext: c2
OnNext: d2

次回はZipメソッド、CombineLatestメソッドについて解説します。

Select+Switchについて

こんにちは、Nobollelエンジニアの古屋です。 前回、UniRxのReactivePropertyについて記事を書きました。今回は、その中で出てきたSelect+Switchについてもう少し書こうと思います。

SelectMany

前回の記事では、Select+SwitchをSelectと対比させて書きましたが、Select+SwitchはSelectManyにも似ています。

SelectManyは、シーケンスから複数のシーケンスを分岐させるときに使います。たとえば、ボタンをクリックするとキャラクターが攻撃するような処理は、SelectManyを使って次のように書けます:

IObservable<Unit> Click() {
    // ボタンがクリックされる度にUnitを返す
}

IObservable<int> Attack() {
    // 攻撃を開始し、1秒後にダメージ量を返す
}

void Start() {
    Click().SelectMany(_ => Attack()).Subscribe(damage => {
        Debug.Log("damage=" + damage);
    });
}

このコードでは、単純にクリックされる度にキャラクターが攻撃するので、連続でクリックすると攻撃が何重にも走ります。シューティングのように弾を飛ばす攻撃ならこのままでも良いかもしれませんが、キャラクターが剣を振って攻撃するようなゲームでは、このままでは少し具合が悪いかもしれません。

Select+Switch

SelectManyをSelect+Switchに置き換えると、攻撃が重複しないようにできます。

void Start() {
    Click().Select(_ => Attack()).Switch().Subscribe(damage => {
        Debug.Log("damage=" + damage);
    });
}

上のコードでは、一度クリックしてAttackを開始し、Attackが完了する1秒以内にもう一度クリックすると、最初のAttackがキャンセルされ、2回目のAttackのダメージ量だけがログに表示されます。最も新しいAttackが優先され、同時に複数のAttackが重なって実行されることはなくなります。

このように、何かのイベントをトリガーに非同期処理を行い、かつ重複させたくないときにSelect+Switchを使うことができます。

ところで、さきほどのSelectManyのコードは、Select+Mergeでも同じことができます。こちらの方がSelect+Switchと形が似ていて、比較がわかりやすいかもしれません。

void Start() {
    Click().Select(_ => Attack()).Merge().Subscribe(damage => {
        Debug.Log("damage=" + damage);
    });
}

Cocos2d-xにおけるspineのメッシュアタッチメント機能について

こんにちは。清水です。

近年、リッチなアニメーションを作成することができる spine が「最」注目されています。
ja.esotericsoftware.com
f:id:nobollel:20160910150106p:plain

すでにこの spine はボーンアニメーション(スケルタルアニメーション)を作成できるツールとして知られていましたが、近年新しい機能として「メッシュアタッチメント」が備わりました。これにより1枚のイラストから滑らかなアニメーションを作成することができるようになりました。
サンプル1 サンプル2

この spine は、Cocos2d-x, Unity, libGDX, Corona SDKなど18種類のゲームエンジンをサポートしており、それぞれのランタイムをGitHubで提供しています。 github.com

逆に、Cocos2d-xもこの spine を標準でサポートしているため、特別なインストール作業など必要なく、特にCocos2d-x v3.13では spine の全機能を利用することができます。
※ブラウザアプリの場合は、メッシュアタッチメントを除く
※Cocos2d-x v3.12以前の場合は、上記GitHubよりフレームワークの差し替えが必要

手順は非常に簡単です。 以前に spine を利用したことがある方ならば、手順は何も変わっていないことが分かって頂けるでしょう。

1. アニメーション作成

spineを用い、メッシュアタッチメントを利用したアニメーションを作成します。(今回はエンジニア向け記事であるため、spineの利用方法については割愛します。)
f:id:nobollel:20160910151637p:plain

2. Xcodeでの設定

iOS向けにXcode上で、Header Search Pathsに下記を追加します。

$(SRCROOT)/../cocos2d/cocos/editor-support

f:id:nobollel:20160910151729p:plain

3. ヘッダー読み込み

利用したいクラスにおいて、spineのヘッダーを読み込みます。

#include <spine/spine-cocos2dx.h>

4. 実装

利用したい場所で、spineのファイルを読み込みます。

auto winSize = Director::getInstance()->getWinSize();

//spineで作成したキャラクターの表示
auto sumomo = spine::SkeletonAnimation::createWithFile("res/skeleton.json", "res/skeleton.atlas", 0.5f);
sumomo->setPosition(winSize / 2);
addChild(sumomo);

//spineで作成したアニメーションの実行
sumomo->setAnimation(0, "walk", true);

5. 実行

そして最後に実行すると、次のようなアニメーションが実行されます。
私はアニメーションセンスがないので、これが限界です。。。アニメーターさんが制作すれば、spineのサンプルのような美麗なアニメーションができるでしょう。 f:id:nobollel:20160910151830g:plain

Admobでバナーの位置を微調整する

皆さんこんにちは、エンジニアの石橋です。
今回はみんなが大好きな広告、GoogleのAdmobを取り上げたいと思います。
有名なN社さんやI社さんなどでは標準で付いているのですが、Google Admobではバナーのセンタリングとオフセットをサポートしていません。
レクタングルバナーを表示する際に困る場合があります。
幸いなことにAdmobはソースが公開されているのでカスタマイズすれば独自に表示することが出来る様になります。

この記事はバージョンは3.0.4を元にしています。
プラグインのダウンロードはこちら
GitHub - googleads/googleads-mobile-unity: Official Unity Plugin for the Google Mobile Ads SDK

編集が必要な箇所は以下のファイルになります。

  • GoogleMobileAds/Api/AdPosition.cs
  • GoogleMobileAds/Common/IBannerClient.cs
  • GoogleMobileAds/Platforms/iOS/BannerClient.cs
  • GoogleMobileAds/Common/DummyClient.cs
  • GoogleMobileAds/Api/BannerView.cs
  • GoogleMobileAds/Platforms/iOS/Externs.cs
  • Plugins/iOS/GADUBanner.h
  • Plugins/iOS/GADUInterface.h
  • Plugins/iOS/GADUBanner.m
  • Plugins/iOS/GADUInterface.m

結構な数がありますね、順を追って見ていきましょう。

GoogleMobileAds/Api/AdPosition.cs

//enum値のAdPositionにCenterがないので追加します
Center = 6 //custom

GoogleMobileAds/Common/IBannerClient.cs

//CreateBannerViewにオフセット値の引数を追加します
void CreateBannerView(string adUnitId, AdSize adSize, AdPosition position, float offsetX, float offsetY);

GoogleMobileAds/Platforms/iOS/BannerClient.cs

//IBannerClientを継承しているBannerClient.csでCreateBannerViewにオフセット値の引数を追加します
public void CreateBannerView(string adUnitId, AdSize adSize, AdPosition position, float offsetX, float offsetY) {
    IntPtr bannerClientPtr = (IntPtr) GCHandle.Alloc(this);

    if (adSize.IsSmartBanner) {
        //objective-cのメソッドへオフセットを渡します
        BannerViewPtr = Externs.GADUCreateSmartBannerView(
                    bannerClientPtr, adUnitId, (int)position, offsetX, offsetY );
            }
            else
            {
                //objective-cのメソッドへオフセットを渡します
                BannerViewPtr = Externs.GADUCreateBannerView(
                    bannerClientPtr, adUnitId, adSize.Width, adSize.Height, (int)position, offsetX, offsetY );
            }
            Externs.GADUSetBannerCallbacks(
                    BannerViewPtr,
                    AdViewDidReceiveAdCallback,
                    AdViewDidFailToReceiveAdWithErrorCallback,
                    AdViewWillPresentScreenCallback,
                    AdViewDidDismissScreenCallback,
                    AdViewWillLeaveApplicationCallback);
        }

GoogleMobileAds/Common/DummyClient.cs

//Dummyにも同じくオフセット値の引数を追加します
public void CreateBannerView(string adUnitId, AdSize adSize, AdPosition position, float offsetX, float offsetY)

GoogleMobileAds/Api/BannerView.cs

        //こちらもオフセット値の引数を追加します。
        public BannerView(string adUnitId, AdSize adSize, AdPosition position, float offsetX, float offsetY)
        {
            client = GoogleMobileAdsClientFactory.BuildBannerClient();
            //clientはIBannerClientなので上記でオフセット引数を追加済みなので渡してやります
            client.CreateBannerView(adUnitId, adSize, position, offsetX, offsetY);

GoogleMobileAds/Platforms/iOS/Externs.cs C#からObjective-cへの橋渡しの部分です。

        //オフセット値の引数を追加します。
        [DllImport("__Internal")]
        internal static extern IntPtr GADUCreateBannerView(
            IntPtr bannerClient, string adUnitId, int width, int height, int positionAtTop, float offsetX, float offsetY );

        //オフセット値の引数を追加します。
        [DllImport("__Internal")]
        internal static extern IntPtr GADUCreateSmartBannerView(
            IntPtr bannerClient, string adUnitId, int positionAtTop, float offsetX, float offsetY );

長かったC#のコード編集の次はObjective-c側です。

Plugins/iOS/GADUBanner.h

//AdPosition.csで追加したCenterを追加します
typedef NS_ENUM(NSUInteger, GADAdPosition) {
  
  ...
  
  kGADAdPositionCenter = 6
};

Plugins/iOS/GADUBanner.h

//以下の2つのメソッド定義にオフセット値の引数を追加します。
- (id)initWithBannerClientReference:(GADUTypeBannerClientRef *)bannerClient
                           adUnitID:(NSString *)adUnitID
                              width:(CGFloat)width
                             height:(CGFloat)height
                         adPosition:(GADAdPosition)adPosition
                            offsetX:(CGFloat)offsetX
                            offsetY:(CGFloat)offsetY;

- (id)initWithSmartBannerSizeAndBannerClientReference:(GADUTypeBannerClientRef *)bannerClient
                                             adUnitID:(NSString *)adUnitID
                                           adPosition:(GADAdPosition)adPosition
                                              offsetX:(CGFloat)offsetX
                                              offsetY:(CGFloat)offsetY;

Plugins/iOS/GADUBanner.m

//adViewDidReceiveAdで使うために一時的に保存するプロパティを追加します
@property(nonatomic, assign) CGFloat offsetX;
@property(nonatomic, assign) CGFloat offsetY;
//オフセットの受け取りとinitWithBannerClientReferenceへの受け渡しを追加します。
- (id)initWithBannerClientReference:(GADUTypeBannerClientRef *)bannerClient
                           adUnitID:(NSString *)adUnitID
                              width:(CGFloat)width
                             height:(CGFloat)height
                         adPosition:(GADAdPosition)adPosition
                            offsetX:(CGFloat)offsetX
                            offsetY:(CGFloat)offsetY
{
  GADAdSize adSize = GADAdSizeFromCGSize(CGSizeMake(width, height));
  return [self initWithBannerClientReference:bannerClient
                                    adUnitID:adUnitID
                                      adSize:adSize
                                  adPosition:adPosition
                                     offsetX:offsetX
                                     offsetY:offsetY];
}

//SmartBanner側も同じく処理します
- (id)initWithSmartBannerSizeAndBannerClientReference:(GADUTypeBannerClientRef *)bannerClient
                                             adUnitID:(NSString *)adUnitID
                                           adPosition:(GADAdPosition)adPosition
                                              offsetX:(CGFloat)offsetX
                                              offsetY:(CGFloat)offsetY
{
  // Choose the correct Smart Banner constant according to orientation.
  UIDeviceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
  GADAdSize adSize;
  if (UIInterfaceOrientationIsPortrait(currentOrientation)) {
    adSize = kGADAdSizeSmartBannerPortrait;
  } else {
    adSize = kGADAdSizeSmartBannerLandscape;
  }
  return [self initWithBannerClientReference:bannerClient
                                    adUnitID:adUnitID
                                      adSize:adSize
                                  adPosition:adPosition
                                     offsetX:offsetX
                                     offsetY:offsetY];
}
//オフセットを受け取ります
- (id)initWithBannerClientReference:(GADUTypeBannerClientRef *)bannerClient
                           adUnitID:(NSString *)adUnitID
                             adSize:(GADAdSize)size
                         adPosition:(GADAdPosition)adPosition
                            offsetX:(CGFloat)offsetX
                            offsetY:(CGFloat)offsetY
{
  self = [super init];
  if (self) {
    _bannerClient = bannerClient;
    _adPosition = adPosition;
    //受け取ったオフセットをプロパティに設定します
    _offsetX = offsetX;
    _offsetY = offsetY;
    _bannerView = [[GADBannerView alloc] initWithAdSize:size];
    _bannerView.adUnitID = adUnitID;
    _bannerView.delegate = self;
    _bannerView.rootViewController = [GADUBanner unityGLViewController];
  }
  return self;
}
- (void)adViewDidReceiveAd:(GADBannerView *)adView {
  ...

  switch (self.adPosition) {

    ...


    //Centerのenum値の処理を追加します。
    case kGADAdPositionCenter:
      center = CGPointMake(CGRectGetMidX(unityView.bounds), CGRectGetMidY(unityView.bounds));
      break;
  }
  //プロパティにセットされたオフセットをcenterに追加して位置をずらします。
  center = CGPointMake( center.x + _offsetX, center.y + _offsetY );

  ...
}

Plugins/iOS/GADUInterface.m

//CGFloatを使うのでUIKitをimport
+@import UIKit;
//オフセットの受け取りとGADUBannerへの受け渡しを追加します。
GADUTypeBannerRef GADUCreateBannerView(GADUTypeBannerClientRef *bannerClient, const char *adUnitID,
                                       NSInteger width, NSInteger height,
                                       GADAdPosition adPosition, float offsetX, float offsetY ) {
  GADUBanner *banner =
      [[GADUBanner alloc] initWithBannerClientReference:bannerClient
                                               adUnitID:GADUStringFromUTF8String(adUnitID)
                                                  width:width
                                                 height:height
                                             adPosition:adPosition
                                                offsetX:offsetX
                                                offsetY:offsetY];
  GADUObjectCache *cache = [GADUObjectCache sharedInstance];
  [cache.references setObject:banner forKey:[banner gadu_referenceKey]];
  return (__bridge GADUTypeBannerRef)banner;
}

/// Creates a full-width GADBannerView in the current orientation. Returns a reference to the
/// GADUBannerView.
GADUTypeBannerRef GADUCreateSmartBannerView(GADUTypeBannerClientRef *bannerClient,
                                            const char *adUnitID, GADAdPosition adPosition, float offsetX, float offsetY ) {
  GADUBanner *banner = [[GADUBanner alloc]
      initWithSmartBannerSizeAndBannerClientReference:bannerClient
                                             adUnitID:GADUStringFromUTF8String(adUnitID)
                                           adPosition:adPosition
                                              offsetX:offsetX
                                              offsetY:offsetY];
  GADUObjectCache *cache = [GADUObjectCache sharedInstance];
  [cache.references setObject:banner forKey:[banner gadu_referenceKey]];
  return (__bridge GADUTypeBannerRef)banner;
}

以上です。

手順としては

  1. Unity側でenum、オフセットを追加
  2. C#側で値を受け取り位置を計算

と、するだけです。 Androidは次回。

UniRxのReactivePropertyについて

こんにちは、Nobollelエンジニアの古屋です。

前回の投稿ではUniRxのコルーチンについて紹介しました。今回はReactivePropertyについて、Rxの細かい部分には触れずに、大まかにどんなことができるかについて書きたいと思います。

ReactivePropertyとは

ReactivePropertyは、ざっくり言うと、値の変更を通知してくれる変数のようなものです。new ReactiveProperty<T>()で作成し、Valueから値を取得・設定します。

var property = new ReactiveProperty<int>();
property.Value = 100; // 値の設定
Debug.Log(property.Value); // 値の取得

Subscribeを使えば、変更の通知をコールバックで受け取ることができます。もし解除したければ、戻り値のIDisposableをDisposeすれば良いです。

IDisposable subscription = property.Subscribe(x => {
    Debug.Log(x);
});

値を変更を常に通知してくれるので、用途は色々ありますが、たとえばデータをUI上の表示に直結させたりするのに使えます。

Rxのオペレータと組み合わせる

ReactivePropertyにRxのオペレータを適用することで、さらに複雑なことができます。個人的によく使うパターンをいくつか紹介します。

Select

Selectを使って、ReactivePropertyを別のReactivePropertyに変換できます。

var level = new ReactiveProperty<int>(1);
ReadOnlyReactiveProperty<bool> isLevelMax = level.Select(lv => (lv >= 100)).ToReadOnlyReactiveProperty();

Debug.Log(isLevelMax); // false
level.Value = 100;
Debug.Log(isLevelMax); // true

この例では、level.Valueが100以上になったら、自動的にisLevelMax.Valueがtrueになります。

CombineLatest

CombineLatestを使って、2つ以上のReactivePropertyを合成して別のReactivePropertyにできます。

var a = new ReactiveProperty<bool>(false);
var b = new ReactiveProperty<bool>(false);
var c = Observable.CombineLatest(a, b, (x, y) => x && y).ToReadOnlyReactiveProperty();

Debug.Log(c); // false
a.Value = true;
Debug.Log(c); // false
b.Value = true;
Debug.Log(c); // true

この例では、c.Valueの値は常にa.Value && b.Valueに等しくなります。CombineLatestがaとbのどちらかが変更される度に(x, y) => x && yが実行され、その戻り値がcの値になります。

Select+Switch

Select+Switchは、Selectのパターンを拡張したようなものです。ちょっとややこしいので、まずは例を見てください:

ReactiveProperty<Character> currentCharacter = new ReactiveProperty<Character>();
ReadOnlyReactiveProperty<float> currentAttack = currentCharacter.Select(c => (IObservable<float>)c.Attack).Switch().ToReadOnlyReactiveProperty();

操作するキャラクターが途中で切り替わるゲームがあったとして、currentCharacterは「現在のキャラクターオブジェクト」です。このとき、「現在のキャラクターの攻撃力」を返すcurrentAttackを作るためにSelect+Switchを使っています。

c.AttackがただのfloatならSelectだけで実現できますが、c.AttackがReactivePropertyの場合は、SelectでまずIObservable<IObservable<float>>に変換した後、SwitchでIObservable<float>に変換します。Switchは最後に流れてきたIObservable<float>の値を流すので、currentAttackからは、最後に設定されたキャラクターのAttackの値が取得できるようになります。

character1.Attack.Value = 100;
character2.Attack.Value = 200;

currentCharacter.Value = character1;
Debug.Log(currentAttack.Value); // 100

// キャラが変わったらそのキャラの攻撃力になる
currentCharacter.Value = character2;
Debug.Log(currentAttack.Value); // 200

// 攻撃力が後から変化しても適用される
character2.Attack.Value = 220;
Debug.Log(currentAttack.Value); // 220