X-Y座標系と円座標系、2点間の角度を求める

前回作成した「X-Y座標系と円座標系」の確認のためにちょっとしたサンプルを作成しようと思います。

takachan.hatenablog.com

作成するもの

Cocos2d-xを使って中心に自機に見立てた四角を置き、多面をクリックすると、自機とタップ位置の角度を求めて玉を発射するサンプルを作成します。

作ったもの

実際に作ったサンプルです。クリックするとその方向と、プラスマイナス15度ずつに同時に3発の弾を発射します。

環境

作成時の環境は以下の通りです。Win + VC環境です。

  • Cocos2d-x 3.16
  • Windows10
  • VisualStudio2017
  • Win32プロジェクト使用(それ以外は動作未確認)

2点間の角度を求める

Cocos2d-xで2点間の角度を求める方法です。2つのNodeオブジェクトのXYの位置からアークタンジェントを用いて計算します。

また、より一般的な操作はこちらを参照してください。

#include <cmath>

// 基準となるオブジェクト(=Node*)とタッチ位置(=Touch*)からラジアンを返します。
double getRadian(Node* source, Touch* touch)
{
    double rad = 
        atan2(touch->getLocation().y - source->getPosition().y,
                  touch->getLocation().x - source->getPosition().x);

    return rad;
}

// ラジアンを角度に変換するときは以下マクロを使用
double degree = CC_RADIANS_TO_DEGREES(this->getRadian(node, touch));

// 自前で計算しないでも以下の式でラジアンが出せる
Point dist = touch->>getLocation() - source->getPosition();
float rad = dist.getAngle();

角度と距離の計算の詳細はこちらでも記載してます。

コード

以下動画のコードです。

ヘッダー側コード

面倒なのでコードを一部ヘッダー側に実装しています。

#pragma once

#include "cocos2d.h"
#include "MessageDialog.h"
#include "HpBar.h"

class Bullet;

class HelloWorld : public cocos2d::Layer
{
    // 発射した弾を記録するリスト
    cocos2d::Vector<Bullet*> bulletList;

public:
    // いつもの
    static cocos2d::Scene* createScene();
    virtual bool init();
    CREATE_FUNC(HelloWorld);

    // 1フレームごとの処理
    void update(float delta);
};

// 弾を表すクラス
class Bullet : public cocos2d::Sprite
{
public:
    // 発射角のラジアン
    double rad = 0;

    // CREATE_FUNC相当
    static Bullet* create()
    {
        Bullet *pRet = new(std::nothrow) Bullet();
        if (pRet && pRet->init())
        {
            pRet->autorelease();
            return pRet;
        }
        else
        {
            delete pRet;
            pRet = nullptr;
            return nullptr;
        }
    }

    // 弾の見た目の初期化
    virtual bool init()
    {
        if (!cocos2d::Sprite::init())
        {
            return false;
        }
        
        this->setTextureRect(cocos2d::Rect(0, 0, 2, 2));
        this->setColor(cocos2d::Color3B::YELLOW);

        return true;
    }

    // 1フレームごとの移動処理
    void update(double delta)
    {
        cocos2d::Vec2 pos = this->getPosition();

        // 新しい座標の計算
        double newx = cos(this->rad) * 6 + pos.x;
        double newy = sin(this->rad) * 6 + pos.y;

        this->setPosition(newx, newy);
    }

    // 弾が画面外に出たかの確認(true : 外に出た / false : 中にいる)
    bool isOutScreen()
    {
        cocos2d::Size size = cocos2d::Director::getInstance()->getVisibleSize();
        cocos2d::Vec2 pos = this->getPosition();

        return pos.x < -10 || pos.x > size.width + 10 ||
               pos.y < -10 || pos.y > size.height + 10;
    }
};

class BulletUtil
{
public:
    // 弾を生成する時の共通操作
    static Bullet* create(double xpos, double ypos, double rad)
    {
        Bullet* bullet = Bullet::create();
        bullet->rad = rad;
        bullet->setPosition(cocos2d::Vec2(xpos, ypos));

        return bullet;
    }
};

実装側コード

以下実装側の処理になります。

//#pragma execution_character_set("utf-8")

#include <stdlib.h>
#include <vector>
#include <cmath>

#include "HelloWorldScene.h"

using namespace cocos2d;
using namespace cocos2d::ui;

// テンプレートのまま
Scene* HelloWorld::createScene()
{
    auto scene = Scene::create();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

bool HelloWorld::init()
{
    if (!Layer::init())
    {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();

    double xCenter = visibleSize.width / 2;
    double yCenter = visibleSize.height / 2;

    // 自機に見立てた四角を中心に置く
    Sprite* player = Sprite::create();
    player->setTextureRect(Rect(0, 0, 5, 10));
    player->setPosition(xCenter, yCenter);
    player->setColor(Color3B::GRAY);
    player->setTag(TAG_PLAYER);

    this->addChild(player);

    // タッチしたときの処理
    EventListenerTouchOneByOne* evListener = EventListenerTouchOneByOne::create();
    evListener->onTouchBegan = [this, xCenter, yCenter](Touch* touch, Event* event)
    {
        Sprite* player = (Sprite*)this->getChildByTag(TAG_PLAYER);
        Vec2 pos = player->getPosition();

        // 自分とタッチした場所からラジアンを計算
        double rad = 
            atan2(touch->getLocation().y - player->getPosition().y,
                  touch->getLocation().x - player->getPosition().x);

        Bullet* bullet = BulletUtil::create(pos.x, pos.y, rad);
        this->addChild(bullet);
        this->bulletList.pushBack(bullet);

        Bullet* bullet2 = BulletUtil::create(pos.x, pos.y, bullet->rad - CC_DEGREES_TO_RADIANS(15));
        this->addChild(bullet2);
        this->bulletList.pushBack(bullet2);

        Bullet* bullet3 = BulletUtil::create(pos.x, pos.y, bullet->rad + CC_DEGREES_TO_RADIANS(15));
        this->addChild(bullet3);
        this->bulletList.pushBack(bullet3);

        return false;
    };

    _eventDispatcher->addEventListenerWithSceneGraphPriority(evListener, this);

    this->scheduleUpdate();

    return true;
}

void HelloWorld::update(float delta)
{
    // 拡張for文を回しながら削除していくための定型処理
    auto it = this->bulletList.begin();
    while (it != this->bulletList.end())
    {
        Bullet* bullet = (Bullet*)*it;

        if (bullet->isOutScreen())
        {
            it = this->bulletList.erase(it); // 画面外に出たら削除
        }
        else
        {
            bullet->update(delta); // 1フレームぶん更新
            it++;
        }
    }
}

本当に大したことありませんでしたが、今回は以上です。