忍者ブログ
  • 2024.10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 2024.12
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

【2024/11/24 08:56 】 |
Eclipseが動かない
Eclipseは起動が遅い。
起動したときに必ずSDK Content Loaderが走り、それが100%になるまでビルドその他の処理は実行できない。
そしてこれが遅い。
ネットワークが遅いとさらに遅い。

ネットワークが繋がっていても、
SDK Content Loaderが0%のまままったく進まないときがある。

そのときは、以下を参考。
参考

要するに
workspace/.metadata/.plugins/org.eclipse.core.resources/.projects
をまるごと消してしまう。
乱暴だ…。


削除してからEclipseを起動すると、プロジェクトの中身がなくなっている。
(プロジェクト名だけ残して、中身のファイルはなくなる)
Refreshコマンドを実行しても戻らない。
一度消して、再度プロジェクトの追加を行うことになる。
(File->New->Other->Android Project from Existing Code)

ライブラリプロジェクトも漏れなく消えるので、追加してBuild Projectをお忘れなく。


workspaceディレクトリ以下にあるプロジェクトは自動で復活するらしい。




あとは、adbプロセスを削除するという手もあるようだ。





PR
【2015/03/31 16:43 】 | Android | 有り難いご意見(0)
AndroidでYouTube動画再生
インテントを使ってYouTubeアプリを起動するだけ。


Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("vnd.youtube:" + youtubeId));
intent.putExtra("VIDEO_ID", youtubeId);
instance.startActivity(intent);



youtubeIdは動画ID。URLに含まれているもの。
インテントが無いときは、PlayストアのYouTubeアプリのページに飛ばしたりするとよいかと思われる

【2015/03/31 16:35 】 | Android | 有り難いご意見(0)
iOSの新testflightを使う
testflightが使えなくなり、iTunesConnectから使用するように変わった。

使い方のイメージとしては、「サブミット前の確認」。


まず、iTunesConnectに行き、最新のAppleの条件をAgreeする必要がある。
これをしなくても先のページに進めるが、アプリを登録するボタン(ヘッダの「+」ボタン)を押しても何も出ない、という妙なところで止められてしまう(不具合に見える。こんなところでも分からないと詰まってしまうようなところ)


Agreeしたら、iTunesConnectにアプリを登録する。


DeveloperCenterでDistribution用のProvisioning Profileを作っておく。
このとき、AdHoc用ではなく、Distribution用を作ることに注意。
AdHoc用でもこの先の手順でサブミットできるし、testflightで配布もできるのだが、testflightからインストールできるのが、Provisioning Profileに登録されているデバイスだけに限定されてしまうので不便なだけ。


で、Distribution用のProvisioning ProfileでアプリをXcodeでアーカイブする。
できたらSubmit。


少し待つと、iTunesConnectにアプリが載る。


ここで「プレリリース」をクリック。
内部テスターの欄に、testflightで配布したいメンバーのappleIDを登録する。
(開発メンバーは基本的に内部テスター。外部テスターというのもあるが、これは実際の顧客など外部の人に使ってもらうことを想定しており、外部テスターに配布するためにはAppleの審査を通した後配布するという手順を踏むことになるようだ)


で、「TestFlightベータ版テスト」のスイッチをONにし、
招待メールを送信。


これで、内部テスターのappleIDのアドレスにメールが届く。
iPhone、iPadなどでそのメールのリンクを踏むと、testflightのアプリに飛び、件のアプリがインストールできるページになる。
晴れてインストールできるというわけ。


たまにメールが飛ばないことがあるので、「TestFlightベータ版テスト」をOFF→ONにしたらメールが行ったということがあった。


また、上げ直す際は必ずビルド番号を上げておくこと。
あくまでサブミットへの手順の一部なので、ビルド番号をあげておかないと、XcodeからiTunesConnectへのアップロードができない。
再度アーカイブし直しという面倒なことになるので注意。

バージョンはあげなくていい。
というか、上げるとストアの情報など再設定が必要になったりtestflight的にも新しいもの扱いに鳴って再度招待メールを飛ばす必要があったりと面倒。

本当にアプリとしてバージョンをあげるときだけにとどめよう。

【2015/03/25 17:17 】 | iPhone | 有り難いご意見(0)
cocos2d-xでスクリーンショット
cocos2d-xでスクリーンショットを撮影する方法。

ios標準の機能だと、こんな感じになる。

UIWindow *window = [[UIApplication sharedApplication] keyWindow];

// キャプチャ画像を描画する対象を生成します。
UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();

// Windowの現在の表示内容を1つずつ描画して行きます。
for (UIWindow *aWindow in [[UIApplication sharedApplication] windows]) {
[aWindow.layer renderInContext:context];
}

// 描画した内容をUIImageとして受け取ります。
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();

// 描画を終了します。
UIGraphicsEndImageContext();



参考

しかし、この方法だとcocos2d-xで描画している画面は映らない。(上記コードで言うと、[aWindow.layer renderInContext:context]; の部分)



cocos2d-xの3.2以降だと、便利な機能がついている。
cocos2d::utils::captureScreen() がそれで、
以下コード例。


#include "base/ccUtils.h"
cocos2d::utils::captureScreen([&](bool succeed, const std::string &filePath){
if (succeed) {
//成功時
}
else{
//失敗時
}
}, "screenshot.png");




captureScreenの第1引数が完了時に実行するラムダ式。第2引数がファイル名。
キャプチャーが取れるとラムダ式の中が呼び出される。
ラムダ式の第2引数のfilePathは、保存したファイルの完全な絶対パスが入っている。

このパスを使って、スプライトをゲーム中に表示したり、Twitterにシェアしたりすることができる。
スプライトの場合は、通常通り
Sprite::create(filePath);



とすればよい。
ただし、そのままだと2回目以降、スクリーンショット画像が変化しない(1回目のスクリーンショットが表示されてしまう)
これは、Sprite::create()の中で自動的にテクスチャがキャッシュされているからだ。

bool Sprite::initWithFile(const std::string& filename)
{
:
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
:
}




そこで、毎回テクスチャキャッシュを削除すればよい。

Director::getInstance()->getTextureCache()->removeTextureForKey(filePath);



上記コードをSprite::create()の前に実行すればOK。


TwitterやFacebookにシェアする場合、UIImageインスタンスを作成する。

UIImage* image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:filePath]];



アセットではなくファイルパスなので、以下の書き方ではUIImageを作成できないので注意。
UIImage* image = [UIImage imageNamed:[NSString stringWithUTF8String:filePath]];



総合すると、スクリーンショットを撮ってTwitterに投稿するのは以下のようなコードになる。


XXX.cpp

cocos2d::utils::captureScreen([&](bool succeed, const std::string &filePath){
if (succeed) {
Director::getInstance()->getTextureCache()->removeTextureForKey(filePath);
twitterShare("投稿するテキスト", filePath.c_str(), "");
}
else{
}
}, "screenshot.png");


YYY.mm

void twitterShare(const char *text, const char *imageName, const char *url, ShareCallback_t successCallback= nullptr, ShareCallback_t failureCallback= nullptr, ShareCallback_t cancelCallback= nullptr){

if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {

AppController *appController = [[UIApplication sharedApplication] delegate];
RootViewController *viewController = appController.viewController;

SLComposeViewController *composeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];

if (text) {
[composeViewController setInitialText: [NSString stringWithUTF8String:text]];
}
if (url) {
[composeViewController addURL: [NSURL URLWithString:[NSString stringWithUTF8String:url]]];
}
if (imageName) {
UIImage* image = [UIImage imageNamed:[NSString stringWithUTF8String:imageName]];
if (!image) {
image = [UIImage imageWithContentsOfFile:[NSString stringWithUTF8String:imageName]];
}
if (image) {
[composeViewController addImage: image];
}
}

// 完了後のハンドラを設定する
[composeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {

switch (result) {
case SLComposeViewControllerResultDone:
if (successCallback) {
successCallback();
}
break;
case SLComposeViewControllerResultCancelled:
if (cancelCallback) {
cancelCallback();
}
break;
default:
if (failureCallback) {
failureCallback();
}
break;
}
[viewController dismissViewControllerAnimated:YES completion: nil];
}];

// ポップアップを起動する
[viewController presentViewController:composeViewController animated:YES completion:nil];
}
}

【2015/03/21 12:04 】 | cocos2d-x | 有り難いご意見(0)
Androidのスレッド
Androidはiosと比べてスレッドに気をつけて実装する必要がある。

描画処理をメインスレッドで実行するとすぐ落ちる。
runOnUiThread()とは仲良くなっておこう。

runOnUiThread(new Runnable() {
@Override
public void run() {
View view = MainActivity.this.findViewById(R.id.myView);
view.setVisibility(View.VISIBLE);
}
});





cocos2d-xを使っている場合
cocos2d-xの処理は、できるだけメインループである

void cocos2d::Scheduler::update(float dt)



から呼び出される中で実行した方が良い。
といっても、普通に実装していれば大抵はこの中から呼び出される。


JavaからnativeのCメソッドを呼び出すような場合は、この限りでない。
例えば、Twitterでシェアしたのを確認した後、ゲーム内でインセンティブを与えたいといったようなとき、Java側からnativeメソッドを呼び出すようなことになるが、
このときはメインループから呼び出されない。

こういう場合、cocos2d-x内で画像読み込みや音声再生など、リソースにアクセスするような処理を行うと、
セグメンテーションエラーが起きたり、(Fatal signal 11 (SIGSEGV) at 0x00000000)

処理が止まったりする(タッチが反応せず、他のスレッドで描画や音声はそのまま再生できてたりする)。

そういうときは、Javaから呼び出すnativeメソッドではフラグを立てるだけにしておいて、
メインループからフラグをチェックしてインセンティブの処理を行う、のようにすれば回避できる。





【2015/03/20 18:58 】 | Android | 有り難いご意見(0)
動的にビューの位置を指定
Androidで、ビューの位置をpx単位で正確に指定したい時の方法。

ゲームアプリの広告表示の場合、
ゲーム部分はフレームワークが自動で拡大縮小してスクリーンサイズに合わせているため、
Androidのxmlでレイアウト指定するものと併せられない。
そこでpxを計算して表示することになる。

方針としては、marginでpx指定することになる。



//スクリーンサイズ取得
WindowManager wm = getWindowManager();
Display disp = wm.getDefaultDisplay();

//座標を計算
int bottomMargin = 1.0 * 120 / VIRTUAL_SCREEN_HEIGHT * disp.getHeight();

//MarginLayoutParamsにセット
View view = MainActivity.this.findViewById(R.id.view); //このビューはxmlでlayout_alignParentBottom="true"になっている想定(ので、bottomMarginが効く)
android.view.ViewGroup.MarginLayoutParams viewMargin = (android.view.ViewGroup.MarginLayoutParams)view.getLayoutParams();
viewMargin.bottomMargin = bottomMargin; //ここで指定
view.setLayoutParams(viewMargin);



ポイントとしては、
「pxでの指定」になること(dpではない)

途中計算で使っているdisp.getHeight()はpxの値が返ってくる。
また、MarginLayoutParams.bottomMarginもpx指定となっている。(top,left,rightも同様)

【2015/03/09 14:24 】 | Android | 有り難いご意見(0)
iOSでYouTube動画再生
iOSでYouTubeの動画を再生するときの方法。

画面でボタンをクリック→全画面で動画再生→完了ボタンを押したら元の画面に戻る
という流れにしたい。


WebViewを使うのが早い、という記事が多いが、WebViewだと完了ボタンを押したときに画面を閉じるということができない。
WebViewはhtmlを表示しているだけなので、動画コンテンツを再生しているかどうかは感知できない。だから完了ボタンを押したときに、明示的にWebViewを閉じる命令を出す必要があるが、そもそも完了ボタンを押したことを知ることができない。


そこで、MPMoviePlayerViewControllerを使う。
モーダルビューコントローラを表示し、そこで動画(URLで指定)を再生できる。完了したらモーダルは閉じる。これだ!

ただし注意。YouTubeのURLそのままだとiOSで再生できない。
再生可能な形式のURLを取得してくる非公式ライブラリがあるのでそれを使う
HCYoutubeParser

以下のようになる。


NSDictionary *dict = [HCYoutubeParser h264videosWithYoutubeID:@"youtubeID"];
NSURL *url = [NSURL URLWithString:dict[@"medium"]];

MPMoviePlayerViewController *vc = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
[self presentViewController:vc animated:YES completion:nil];




その他、WebViewでいい場合は
参考

【2015/03/03 16:28 】 | iPhone | 有り難いご意見(0)
ローカル通知が多重に来る
applicationDidEnterBackgroundの中とかでローカル通知のスケジューリングを行う。
アプリを起動したときにスケジュール済みの通知を全削除したりするが、そのタイミングは
applicationWillEnterForeground
ではいけない。

必ず通るとは限らないからである。

起動
→バックグラウンドへ(ここで通知スケジューリング)
→他のアプリを起動するなどしているうちに、アプリが自然にシャットダウンする
→アプリを起動
→バックグラウンドへ(ここで通知が2重にスケジューリングされる!)

というわけで、通知を全削除する( [[UIApplication sharedApplication] cancelAllLocalNotifications];
)箇所は、

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url;
- (void)applicationWillEnterForeground:(UIApplication *)application;
の4つ…とかはめんどいので、

applicationDidEnterBackground
でスケジューリング登録する直前に全削除、が一番楽であろう。
【2015/02/26 18:14 】 | iPhone | 有り難いご意見(0)
cocos2d-xで文字送り
RPGとかノベルゲームでよくある、1文字ずつ表示する仕組み。


◯cocos2d-xの内部
文字を表示するのはLabelクラス。
これはSpriteBatchNodeの一種で、1文字を1つのSpriteで保持している。
文字数分だけ、子ノードにSpriteを持っているという構造になっている。
で、◯文字めのSpriteを返す getLetter() というメソッドがある。これを使う。


Label* label = Label::createWithTTF(text, FONT_NAME, fontSize);
label->setPosition(pos);
label->setAlignment(TextHAlignment::LEFT);
label->setVisible(false);
parent->addChild(label);

int textLength = label->getStringLength();
for (int letterIndex = 0; letterIndex < textLength; ++letterIndex) {
Sprite *sp = label->getLetter(letterIndex);
if (sp) {
sp->runAction(Sequence::create(
DelayTime::create(0.1 * letterIndex), //1文字0.1秒
CallFunc::create([sp](){
sp->setVisible(true); //ここで表示
SimpleAudioEngine::getInstance()->playEffect("se_name"); //文字表示の音を鳴らしたり
}),
NULL));
}
}



という感じ。


◯注意点
labelは使い回しできない。
一度runActionして1文字ずつ表示した後、
label->setString("another string");
して、再度runActionすると、文字の表示位置がずれまくり、そして落ちるという謎現象が起きた。
その場合、label->removeFromParent(); してから再度 Label::create() すれば正しく動いた。

◯注意点その2
int textLength = label->getStringLength();
の文字数が合っていない。textLengthの文字数だけでwhileをまわすと、たまに末尾の1〜2文字が表示されないことがあった。
どうやらcocos2d-x内部での文字の数え方に差異があるようだ。
面倒なので、textLength + 2 でループをまわしたりした。
ちゃんと調べたい



【2015/02/24 21:07 】 | cocos2d-x | 有り難いご意見(0)
cocos2dでスプライトが白くフェードアウトする
白くフェードアウトする場合、スプライトに


sprite->setOpacityModifyRGB(false);



と記述してある可能性がある。
SpriteBatchNodeを使用しているかどうかでも変わる(使用していると白くはならない)


void Sprite::updateColor(void)
{
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );

// special opacity for premultiplied textures
if (_opacityModifyRGB)
{
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
}

_quad.bl.colors = color4;
_quad.br.colors = color4;
_quad.tl.colors = color4;
_quad.tr.colors = color4;

// renders using batch node
if (_batchNode)
{
if (_atlasIndex != INDEX_NOT_INITIALIZED)
{
_textureAtlas->updateQuad(&_quad, _atlasIndex);
}
else
{
// no need to set it recursively
// update dirty_, don't update recursiveDirty_
setDirty(true);
}
}

// self render
// do nothing
}

void Sprite::setOpacityModifyRGB(bool modify)
{
if (_opacityModifyRGB != modify)
{
_opacityModifyRGB = modify;
updateColor();
}
}



【2015/02/24 16:13 】 | cocos2d-x | 有り難いご意見(0)
<<前ページ | ホーム | 次ページ>>