Cocos2d-x 10円ゲーム講座 第11回、スプライトアクションを使ってユーザビリティを向上させる方法についです。
ゲームの骨格となる部分は完成しました。1つ問題点を上げるならボタンがボタンと解りづらいことです。つまりユーザビリティが悪いということです。
レバーは見た感じでわかるかもしれませんが、お金の投入口がボタンになっていることは、パッと見ただけではよくわからない可能性もあります。
スマートフォンの台頭で、マウスではなくタッチ操作が主流になってきました。マウスの場合は、マウスオーバーという手法によりボタンがボタンであることを明示できました。
しかし、タッチ操作ではマウスオーバーに相当する操作がありません。
ボタンを明示するよくある方法としてはグロウ処理を行うことです。ボタンを淡く光らせることで、ユーザーに装置であることを明示することができます。今回はボタン投入口とレバーにグロウ処理を施してみます。
用意したグロウ画像です。
グロウ画像をこれまで同様、Spriteクラスを使って表示します。
auto sprite = Sprite::create("coin_entry_glow.png"); sprite->setPosition(Vec2(visibleSize.width*0.7, visibleSize.height*0.9)); this->addChild(sprite, 2);
グロウ処理のためにSpriteにアクションを設定します。
// アクション auto blink = Blink::create(1.5f, 1); // 点滅 1.5秒で1回点滅 auto action = RepeatForever::create(blink); // blinkを永久に繰り返す sprite->runAction(action);
点滅を永久に繰り返すというアクションを設定します。
レバーも同様に設定します。
// レバーのglow処理 auto leverGlow = Sprite::create("lever_glow.png"); leverGlow->setAnchorPoint(Vec2(0.0f, 0.0f)); _lever[i]->addChild(leverGlow, 2); // 1.5秒に1回の点滅を永久に繰り返す leverGlow->runAction(RepeatForever::create(Blink::create(1.5f, 1)));
Cocos2d-xが使えるアクションの詳細についてはReferenceを見ると良いでしょう。
ソースコードはこちら
10円ゲームはレトロさがウリでもあるので、ボタンやレバーのグロウ処理はレトロさを損なう結果になりますが、ボタンとしての理解度は向上したと思います。
リアリティかユーザビリティか。どちらも良いに越したことはありませんが、ゲームとしてユーザビリティを重視する場面は多いと思います。
次回はパーティクルを使用します。
]]>
Cocos2d-x 10円ゲーム講座 第9回、はずれ穴とゴールの処理を追加します。
今回ははずれ穴とゴールの処理です。
現実の10円ゲームのはずれ穴は奥行きを持っていて、奥に10円玉が落ちることで10円ゲームの失敗となります。Cocos2d-xの物理エンジンで奥行きをもたせることは出来ないので、穴にコインが入ったかどうかをコインの位置で判定することにします。ゴールも同様です。
コインの位置を監視するには、Cocos2d-xのスケジュール機能を使うとフレーム毎にコインの位置を監視することができます。コインの位置の監視によってはずれ穴とゴール位置に来た事を検知します。
class OneCoinGame : public cocos2d::Layer { public: // スケジュール処理 void update(float delta); };
init()内でthis->scheduleUpdate();を実行します。
// スケジュール処理の開始 this->scheduleUpdate();
これでフレーム毎にupdate(float delta)が呼ばれるようになります。
続いてはupdate(float delta)の中身を実装していきます。
// 穴の矩形領域 Rect coinHole[] = { Rect(245, 430, 30, 30), Rect(67, 335, 30, 30), Rect(213, 335, 30, 30), Rect(260, 258, 30, 30), Rect(182, 136, 30, 30), Rect(61, 67, 30, 30), }; // ゴールの矩形領域 Rect goalHole = { Rect(117, 67, 30, 30), }; // フレーム毎の処理 void OneCoinGame::update(float delta) { ///////////////////////////// // コインがはずれ穴とゴールに入っている場合の処理 // コインの速度の取得 auto velocity = _coin->getPhysicsBody()->getVelocity(); float speed = velocity.getDistance(Vec2::ZERO); // コインの速度が一定速度以下かつコインの物理が有効でかどうか if (speed < 3.0f && _coin->getPhysicsBody()->isEnabled()) { // はずれ穴に入っているかどうかの確認 for (int i = 0; i < 6; i++) { if (coinHole[i].containsPoint(_coin->getPosition())) { // コインがはずれ穴に落ちた時の処理 // コインの物理動作を停止し、非表示にする _coin->getPhysicsBody()->setEnabled(false); _coin->setVisible(false); } } // ゴールに入っているかどうかの確認 if (goalHole.containsPoint(_coin->getPosition())) { // コインがゴールに到達した時の処理 // コインの物理動作を停止し、非表示にする _coin->getPhysicsBody()->setEnabled(false); _coin->setVisible(false); } } }
穴に触れた瞬間にコインが消えると、少し理不尽な感じが出るので、コインの速度を取得しコインの速度が遅い時だけ穴に落ちたことにします。これは実際の10円ゲームでは奥行きがあることを、2Dゲームで擬似的に表現するための処理です。コインの速度が速い時は穴を通り抜ける方が理不尽に感じないためです。
はずれ穴、ゴールどちらに到達してもコインは無くなることになるため、コインの物理動作を停止し非表示にします。
_coin->getPhysicsBody()->setEnabled(false); _coin->setVisible(false);
ゴールの場合はクリア報酬として景品を出します。ついでなので、この景品も剛体にして物理を適用してみます。
gift.png
景品。おもちゃ箱っぽいイメージです。
景品口の物理領域を実装。
// 景品口の物理領域 Vec2 vec[4] = { Vec2(202, 65), // 左上 Vec2(202, 23), // 左下 Vec2(301, 23), // 右下 Vec2(301, 65), // 右上 }; auto wall = Node::create(); wall->setPhysicsBody(PhysicsBody::createEdgeChain(vec, 4, PhysicsMaterial(1.0f, 0.5f, 0.2f))); wall->getPhysicsBody()->setDynamic(false); this->addChild(wall);
筐体と同じように実装します。
続いて景品を出す処理をゴールに到達した際に追加します。
// 景品を出す処理 auto gift = Sprite::create("gift.png"); gift->setPosition(Vec2(250.0f, 74.0f)); this->addChild(gift, 1); // 景品の物理設定 auto giftPhysics = PhysicsBody::createBox(Size(26.0f, 14.0f), PhysicsMaterial(0.6f, 0.3f, 6.0f)); giftPhysics->setMass(1.0f); // 重さ giftPhysics->setMoment(10.0f); // モーメント(大きいほど回転しにくい) gift->setPhysicsBody(giftPhysics);
景品口の物理設定を作り、少し上から景品を出現させて落とします。
これを実行してみると問題があることがわかります。
景品が筐体の衝突領域に接触し、景品口に出てくれません。
ゲームで物理物体を扱っていると、ある物体とは衝突して欲しいけど、こっちの物体とは衝突してほしくない、といったことが出てきます。その解決方法として、Cocos2d-xでは物体にカテゴリーやマスクが設定できます。
今回は景品をコインや筐体と接触しないようにカテゴリ設定を行います。
剛体をカテゴライズするにはsetCategoryBitmask()を使用します。また、setContactTestBitmask()で剛体の衝突するカテゴリを指定できます。
コインとレールはそれぞれ衝突するようにカテゴリを指定します。
coinPhysics->setCategoryBitmask(0x00000001); // 物体のカテゴリ coinPhysics->setCollisionBitmask(0x00000001); // 接触する物体のカテゴリを指定
wall->getPhysicsBody()->setCategoryBitmask(0x00000001); // 物体のカテゴリ wall->getPhysicsBody()->setCollisionBitmask(0x00000001); // 接触する物体のカテゴリを指定
rail->getPhysicsBody()->setCategoryBitmask(0x00000001); // 物体のカテゴリ rail->getPhysicsBody()->setCollisionBitmask(0x00000001); // 接触する物体のカテゴリを指定
コイン、レール、筐体のカテゴリを1(0x00000001)に設定し、接触する物体のカテゴリを同じく1(0x00000001)に設定します。これでコインとレール、筐体はそれぞれ接触することになります。動作としては今まで通りです。
次に景品の設定です。景品と景品口は接触して欲しいが、コインやレールとは接触して欲しくないので、コインやレールとは別のカテゴリである2(0x00000010)を設定します。
wall->getPhysicsBody()->setCategoryBitmask(0x00000010); // 物体のカテゴリ wall->getPhysicsBody()->setCollisionBitmask(0x00000010); // 接触する物体のカテゴリを指定
giftPhysics->setCategoryBitmask(0x00000010); // 物体のカテゴリ giftPhysics->setCollisionBitmask(0x00000010); // 接触する物体のカテゴリを指定
これで物体の衝突を制御できました。
ことが確認できます。
ゴールに導くのが難しくて動画撮れませんでした(笑)。内部ではテスト用のコインを作成して確認しています。
ソースコードはこちら
次回は効果音を鳴らすです。
]]>Cocos2d-x 10円ゲーム講座 第8回、レバー操作とコインをはじく処理を追加します。
今回は10円ゲームのキモ、レバー操作の実装です。
レバーを引いて離すと、引いた分に応じて力強くコインをはじきます。
ドラッグ処理でレバーのはじく強さを実現します。
レバーをタッチすることでレバー操作を開始、ドラッグすることでレバーの力強さを決定します。タッチを離した時にコインをはじく処理を行います。ドラッグ中はレバーをアニメーションさせます。
使用する画像はこちら。
lever.png
lever_background.png
レバーは、スティック部分とレバー背景の穴の画像を使います。
画像が用意できたら実装していきます。レバーははじく際にアニメーションさせるので、メンバ変数にします。その他タッチした際に使用する変数も定義しておきます。
class OneCoinGame : public cocos2d::Layer { private: cocos2d::Sprite *_lever[4]; // レバー // レバータッチ処理用 bool _isTouchLever; // レバーのタッチ状態 int _touchLeverNumber; // レバーのナンバー cocos2d::Vec2 _touchlocation; // タッチしたポイント };
// レバー関連初期処理 _isTouchLever = false; _touchLeverNumber = 0; _touchlocation = Vec2(0.0f, 0.0f);
特に変わったことはしていません。
レバーの表示処理
// レバー位置設定 Vec2 vec[4] = { Vec2(45, 400), Vec2(315, 321), Vec2(45, 255), Vec2(315, 160), }; for (int i = 0; i < 4; i++) { // レバーの背景 auto leverBackground = Sprite::create("lever_background.png"); leverBackground->setPosition(vec[i]); leverBackground->setAnchorPoint(Vec2(0.5f, 0.28f)); this->addChild(leverBackground, 2); // レバー部分 _lever[i] = Sprite::create("lever.png"); _lever[i]->setPosition(vec[i]); _lever[i]->setAnchorPoint(Vec2(0.5f, 0.28f)); this->addChild(_lever[i], 2); }
いつも通りSpriteクラスを使って画像を描画しているだけですが、1点だけポイントがあります。それがsetAnchorPoint()です。setAnchorPoint()は画像の基準となる点で、回転させる際に軸となる点にもなります。
AnchorPointを軸に回転する
レバーの棒はレバー穴を軸に回転して欲しいので、Vec2(0.5f, 0.28f)を設定しています。
レバーにタッチ処理を使うので、タッチイベントを受け取れるようにします。
タッチイベントには、onTouchBegan()、onTouchMoved()、onTouchEnded()があり、それぞれタッチ開始時、移動時、終了時に呼ばれます。
実際の操作と対応しているので、イメージしやすく直感的に理解できると思います。
シーンでタッチイベントを受け取るには、タッチイベントのクラスメソッドを定義します。
class OneCoinGame : public cocos2d::Layer { public: // タッチ処理 bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event); void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event); void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event); };
タッチイベントを受け取るためのリスナーイベントを登録します。
// タッチイベントを使用するための初期処理 // タッチに関するイベントリスナーを生成 auto listener = EventListenerTouchOneByOne::create(); listener->onTouchBegan = CC_CALLBACK_2(OneCoinGame::onTouchBegan, this); listener->onTouchMoved = CC_CALLBACK_2(OneCoinGame::onTouchMoved, this); listener->onTouchEnded = CC_CALLBACK_2(OneCoinGame::onTouchEnded, this); // タッチイベントをリスナーに登録 auto dip = Director::getInstance()->getEventDispatcher(); dip->addEventListenerWithSceneGraphPriority(listener, this);
これでこのシーンでタッチイベントを受け取れるようになりました。
レバーのタッチ処理を実装します。
// タッチ開始時の処理 bool OneCoinGame::onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event) { auto location = touch->getLocation(); bool isTouchLever = false; // タッチしたレバーの調査 for (int i = 0; i < 4; i++) { Rect rect = _lever[i]->getBoundingBox(); // タッチした点がレバー領域内かどうか if (rect.containsPoint(location)){ isTouchLever = true; // タッチしたレバーを記憶 _touchLeverNumber = i; // タッチした位置を記憶 _touchlocation = location; } } // レバーをタッチした場合、レバーのタッチ状態を設定する if (isTouchLever == true) { _isTouchLever = true; } else { _isTouchLever = false; } // return true; } // タッチ移動時の処理 void OneCoinGame::onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event) { auto location = touch->getLocation(); if (_isTouchLever) { float d = _touchlocation.getDistance(location); if (d >= 100.0f) { d = 100.0f; } // レバースプライトの更新 if (_touchLeverNumber == 0 || _touchLeverNumber == 2) { _lever[_touchLeverNumber]->setRotation(-90.0f * (d / 100.0f)); // 0 - 90 max } else { _lever[_touchLeverNumber]->setRotation(90.0f * (d / 100.0f)); // 0 - 90 max } } } // タッチ終了時の処理 void OneCoinGame::onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event) { auto location = touch->getLocation(); if (_isTouchLever) { float d = _touchlocation.getDistance(location); if (d >= 100.0f) { d = 100.0f; } // レバースプライトを初期位置に戻す _lever[_touchLeverNumber]->setRotation(0); // レバーのタッチ状態を解除 _isTouchLever = false; } }
レバーをタッチしたかどうかを判定するには、レバーの矩形とタッチした点の衝突判定の結果を調査します。
onTouchBegan()がタッチ開始時に呼ばれるイベントのため、onTouchBegan()でレバーがタッチされたかどうかを判定しています。
touch->getLocation()でタッチした点を取得できます。レバー画像側はSpriteクラスのgetBoundingBox()で画像の矩形領域が取得できるため、この矩形領域とタッチした点で衝突判定を行います。矩形領域と点の衝突判定には、矩形領域のクラスであるRectクラスのメソッドcontainsPoint()で判定することができます。
onTouchMoved()はドラッグ動作に対してのイベントです。onTouchMoved()でレバーのアニメーションを行い、レバーの引く処理を視覚的に表示しています。タッチを開始した点から、距離が離れるほどレバーを倒す(回転)する処理を入れます。
画像の回転処理は、SpriteクラスのメソッドsetRotation()で回転できます。
onTouchEnded()はタッチ終了時のイベントです。ここではonTouchMoved()で回転させたレバーを元に戻しています。
静止画では解りづらいですが、レバーがドラッグ処理で傾いています。
タッチイベントを利用してレバー操作が実現できました。次はタッチを離した際にコインをはじく処理を実装します。
class OneCoinGame : public cocos2d::Layer { public: // コインに力を加える void applyForceCoin(float vecx, float power); };
// コインに力学的な力を加える void OneCoinGame::applyForceCoin(float vecx, float power) { _coin->getPhysicsBody()->applyImpulse(Vec2(400.0f * power * vecx, 0.0f)); }
appliImpulse(Vec2 Impulse)で剛体(コイン)に対して瞬間的に力を加えることができます。
タッチを離した際に呼ばれるoneTouchEnded()にコインに力を加える処理を追加します。
// レバーの接触部分設定 Rect leverArea[4] = { Rect(45, 410, 30, 30), Rect(285, 330, 30, 30), Rect(45, 270, 30, 30), Rect(285, 170, 30, 30), }; // タッチ終了時の処理 void OneCoinGame::onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event) { auto location = touch->getLocation(); if (_isTouchLever) { float d = _touchlocation.getDistance(location); if (d >= 100.0f) { d = 100.0f; } // レバースプライトを初期位置に戻す _lever[_touchLeverNumber]->setRotation(0); // コインがレバー範囲にあるか確認 if (leverArea[_touchLeverNumber].containsPoint(_coin->getPosition())) { // 範囲内ならコインに力を加える if (_touchLeverNumber == 0 || _touchLeverNumber == 2) { this->applyForceCoin(1.0f, (d / 100.0f)); } else { this->applyForceCoin(-1.0f, (d / 100.0f)); } } // レバーのタッチ状態を解除 _isTouchLever = false; } }
レバーを動作させた際に、コインに逆方向への力を加えて動かします。
実際の10円ゲームは、レバーとハンマー機構によりコインを叩くわけですが、今回は擬似的に実装しています。ハンマー機構を作っても面白いですが、物理で動く部分が増えるとコインをはじく結果への不安定さが増えることになり、ゲームとして面白くなるか、運ゲーになるか微妙なところなので擬似的な方を採用しました。
レバーの領域を表示しています。この範囲にコインがあれば、レバー処理でコインに力を加える処理が働きます。
ここまでのソースコードはこちら
次回ははずれ穴とゴールの処理の追加です。
]]>Cocos2d-x 10円ゲーム講座 第7回、10円ゲーム筐体の物理設定を追加してコインと衝突させます。
前回はコインが重力の影響を受けて落下するようになりました。しかし他の物理設定は何もしていないため、筐体などにぶつかることなく落下し、ウィンドウから出ていくだけです。
今回は筐体の形に合わせて、物理設定を追加していきます。
筐体のハコの形に物理領域を設定して、コインと衝突させてみます。
コインの剛体はPhysicsBody::createCircle()を使って円形に設定しましたが、筐体のハコはPhysicsBody::createEdgeChain()を使って線の連続で設定することにします。
// 10円ゲーム筐体内部の領域設定 { Vec2 vec[4] = { Vec2(60, 600), Vec2(60, 60), Vec2(300, 60), Vec2(300, 600), }; auto wall = Node::create(); wall->setPhysicsBody(PhysicsBody::createEdgeChain(vec, 4, PhysicsMaterial(1.0f, 0.3f, 0.5f))); wall->getPhysicsBody()->setDynamic(false); this->addChild(wall); }
今回のポイントはsetDynamic(false)です。setDynamic()にfalseを指定することで、その物体は静止物体となり、重力の影響や物理の影響を受けません。この設定はデフォルトでtrueです。
コイン投入口をタッチしてコインが落下、コインがハコとぶつかって静止することが確認できます。
今回は10円ゲームで一番重要なレールの物理設定です。
レールで名前が合っているかどうかはわかりませんが、とにかくコインが転がる部分を便宜上レールということにします。
今まではコインと筐体のハコの衝突領域を設定していたため、特に考えることもなく円形と矩形で設定すれば大丈夫でした。
しかし、レールなどのちょっとした形の衝突領域はどう設定するか考える必要があります。これは主に3つ程考え方があります。
1.は理想。
物理エンジンの演算回数や、衝突領域の調整が問題点となります。
2.は矩形で近似させる方法。
2Dゲームでは角の部分の衝突判定に違和感を覚えることが多いので、右図のように小さめの矩形にすることも多いです。ただし、今回の場合はコインがレールにめり込んでしまうのが考えものです。
3.の独自に衝突領域を設定する方法
例えば上図の赤いラインのような形。10円ゲームの性質上、レールの下部分に触れることはあまり無い上に、下部分にはめり込んでも問題なさそうなので、今回はこの方法で設定しようと想います。
前回の筐体のハコと同じく、PhysicsBody::createEdgeChainで設定していきます。ただし、座標を打ち込んで行くのは非常に面倒なので、一工夫します。
この筐体のレールはPhotoshopのパスで作っているので、そのパスの座標を抽出してcsv化します(Photoshopは左上原点なので、Y座標は反転させる必要があります)。このcsvを読み込んでPhysicsBody::createEdgeChain()で設定します。
#include <vector> #include <fstream> bool OneCoinGame::init() { /*--- 略 ---*/ // railの物理設定 { std::vector<Vec2> railData; std::string filePath = FileUtils::getInstance()->fullPathForFilename("raildata.csv"); std::ifstream ifs(filePath.c_str()); std::string sheetLine; // ファイルが存在しない場合 if (!ifs) { CCLOG("raildata.csv file not found"); } // 1行ずつ読み込む while (getline(ifs, sheetLine)) { std::istringstream sheetStream(sheetLine); std::string temp; std::vector<std::string> cellData; // 1行分の文字列のリスト while (getline(sheetStream, temp, ',')) { // 1セル分の文字列をリストに追加する cellData.push_back(temp); } auto pos = Vec2(std::stoi(cellData[0]), std::stoi(cellData[1])); if (0 == std::stoi(cellData[2])) { // 終端以外 railData.push_back(pos); } else { // 終端 railData.push_back(pos); auto rail = Node::create(); rail->setPhysicsBody(PhysicsBody::createEdgeChain(railData.data(), railData.size(), PhysicsMaterial(1.0f, 0.5f, 0.2f))); rail->getPhysicsBody()->setDynamic(false); this->addChild(rail); // vectorをクリアする railData.clear(); railData.shrink_to_fit(); } } } }
raildata.csvについてはこちらに置いています。
Cocos2d-xのファイルの読み込みはFileUtilsを使います。プラットフォームの面倒なファイルのやりとりを簡単にしてくれます。特にAndroidやiOSで嬉しい機能です。
ファイルの読み込み後はセル毎に読み込み、筐体と同じように物理設定を行っていきます。
コイン投入口ボタンを押してコインを落下させると、コインがレールにぶつかって転がっていくことが確認できます。
ここまでのソースコードはこちら
次回はレバー操作とコインをはじく処理の追加です。
]]>Cocos2d-x 10円ゲーム講座 第6回、物理エンジンを使用してコインを動かします。
前回、コイン投入口ボタンを押すとコインが表示できるようになりました。
しかし、コインはまだ静止したまま動きません。理由はもちろん、コインの表示コードを実装しただけでコインの動きについては何一つ実装していないからです。
そこで今回は、コインが重力に従って落下したり、自然に動くように物理エンジンの設定を行います。
コインを物理物体として動作させる前提として、現在のシーンに物理エンジンを使用できるようにします。
現在のシーンを生成する時に、Scene::createWithPhysics()を実行することで、そのシーンで物理エンジンを使用できるようになります。
Scene* OneCoinGame::createScene() { // シーンを物理エンジンが使用できるシーンにする auto scene = Scene::createWithPhysics(); /*--- 略 ---*/ // 物理エンジン用設定 auto world = scene->getPhysicsWorld(); // 物理シーンの計算速度 world->setSpeed(3.0f); // 物理シーンのサブステップ数 world->setSubsteps(5); // デバッグ用の領域を表示 world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
OneCoinGame::createScene()内のScene::create();をScene::createWithPhysics();に置き換えます。これだけで物理エンジンが使用できるようになりました。合わせて物理エンジンの設定を行っておきます。
setSpeed(float speed)は物理シミュレーションの実行速度の設定です。デフォルトは1.0で3.0にすると3倍速で動作することになります。デフォルトだと少し遅いので、3.0を設定しています。
setSubsteps(int steps)は物理シミュレーションのサブステップ数を設定します。サブステップ数は物理シミュレーションが次のフレームまでに計算する回数で、デフォルトは1ですが、大きくすることで物理シミュレーションの精度が大きく向上します。今回はコインの動きにリアリティを持たせるために、サブステップ数を5としました。
setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL)を実行すると、剛体などの領域が表示されます。デバッグに便利な機能です。
Cocos2d-xの物理エンジンは剛体力学です。まずはコインという剛体に対して設定したいマテリアルを設定します。
// マテリアルの設定 PhysicsMaterial coinMaterial; coinMaterial.density = 0.1f; // 密度 coinMaterial.restitution = 0.5f; // 反発係数 coinMaterial.friction = 0.4f; // 摩擦係数
マテリアルの設定項目は3つあります。
densityは密度。単位体積あたりの質量を設定します。衝突した際の運動量に影響します。
restitutionは反発係数。衝突した際の跳ね返り具合を設定します。スーパーボールで0.9程度、野球のボール(硬球)で0.4程度という数値です。
frictionは摩擦係数。0にすると転がらずに氷の上を滑っているようになります。コインとしてある程度転がって欲しいので0.4を設定しました。
マテリアルを決定したら、次は剛体の設定です。コインは円形なので、PhysicsBody::createCircle()を使って、円形の衝突領域を設定します。
// 円形の物理設定 auto coinPhysics = PhysicsBody::createCircle(12.5f, coinMaterial); coinPhysics->setMass(1.0); // 重さ coinPhysics->setMoment(1.0f); // モーメント。回転させるための力。大きいほど回転しにくい // コインに剛体を関連付ける _coin->setPhysicsBody(coinPhysics); // コインを停止し、非表示にしておく _coin->getPhysicsBody()->setEnabled(false); _coin->setVisible(false);
PhysicsBody::createCircle()の第一引数は半径です。コインが直径25ピクセルなので、12.5を設定。第二引数のマテリアルは上で設定したマテリアルです。
物理の設定時に、重さとモーメントも設定します。モーメントとは回転させるための数値で、大きいほど回転しにくくなります。どちらもデフォルトではPHYSICS_INFINITYという最大値が設定されるため注意が必要です。
Spriteクラスの_coinにsetPhysicsBody(coinPhysics);と設定することで、コインが剛体となり物理的な影響を受けるようになります。
コインを剛体にできたら、コイン投入口ボタンを押すまで動作を停止するためにsetEnabled(false)を設定して動作を停止しておきます。setVisible(false)はスプライトを非表示にするだけで、物理シミュレーションが停止しないからです。
続いてコイン投入口ボタンを押した時の動作を変更します。
// コイン投入ボタン void OneCoinGame::insertCoinCallback(cocos2d::Ref* pSender) { Size visibleSize = Director::getInstance()->getVisibleSize(); // コインに働いている物理の力をリセットする _coin->getPhysicsBody()->resetForces(); _coin->getPhysicsBody()->setVelocity(Vec2(0.0f, 0.0f)); // 位置のリセット(投入口ボタンにセットする) _coin->setPosition(Vec2(visibleSize.width*0.7, visibleSize.height*0.9)); // コインの物理を有効にして表示する _coin->getPhysicsBody()->setEnabled(true); _coin->setVisible(true); }
resetForces()やsetVelocity()で、コインに働いている物理の力をリセットします。
コイン投入口ボタンを押すと、コインが重力に従って落下していきます。
筐体の物理設定を一切やっていないので、ただ落下していくだけですね。
ここまでのソースコードはこちら
]]>