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?
Cocos Creator is a complete game development solution, including:
- Asset manager
- Scene editor
- Animation editor
- Javascript editor
- Windows, Mac, iOS, Android and browser support
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.
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.
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以下のフォルダをインポートします。
以下のファイルを編集します。
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
すでにこの spine はボーンアニメーション(スケルタルアニメーション)を作成できるツールとして知られていましたが、近年新しい機能として「メッシュアタッチメント」が備わりました。これにより1枚のイラストから滑らかなアニメーションを作成することができるようになりました。
この 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の利用方法については割愛します。)
2. Xcodeでの設定
iOS向けにXcode上で、Header Search Pathsに下記を追加します。
$(SRCROOT)/../cocos2d/cocos/editor-support
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のサンプルのような美麗なアニメーションができるでしょう。
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; }
以上です。
手順としては
と、するだけです。 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