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円ゲームは、レバーとハンマー機構によりコインを叩くわけですが、今回は擬似的に実装しています。ハンマー機構を作っても面白いですが、物理で動く部分が増えるとコインをはじく結果への不安定さが増えることになり、ゲームとして面白くなるか、運ゲーになるか微妙なところなので擬似的な方を採用しました。
レバーの領域を表示しています。この範囲にコインがあれば、レバー処理でコインに力を加える処理が働きます。
ここまでのソースコードはこちら
次回ははずれ穴とゴールの処理の追加です。
]]>