【Cocos2d-x】Windowsとスマホで画像サイズが変わる件について

こちらの中で、スマホで表示すると画像サイズが変わるような事を書きましたが。

分かった。

sprite->getContentSize()で画像のサイズを取得することができるのですが、

Windowsとスマホではこの値が変わる。

なので、

クリッピングする時はPixel値を直接入力するのでは無くて、

このsprite->getContentSize()で取得した値を元にクリップするサイズを指定しなければならない。

    auto sprite = Sprite::create("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    sprite->setScale(3.0);
    auto str = String();
    str.appendWithFormat("width %f height %f", sprite->getContentSize().width, sprite->getContentSize().height);
    label->setString(str.getCString());

    double partswidth = sprite->getContentSize().width / 3.0;
    double partsheight = sprite->getContentSize().height / 4.0;
    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(12);
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,0, partswidth, partsheight)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(partswidth * 1,0, partswidth, partsheight)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(partswidth * 2,0, partswidth, partsheight)));

    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
    Animate* animate = Animate::create(animation);

    sprite->runAction(RepeatForever::create(animate));

    return true;

あとはプラットフォームに合わせて拡大すれば良い。

解決。

【Cocos2d-x】フリック処理を行う

もう一つやりたいのはフリック処理。

使用するハンドラはonTouchBeganとonTouchEndedの二つ。

タッチ開始の座標(sx,sy)とタッチリリースの座標(ex,ey)から、フリックの方向を求めるのですが、

上下左右の4方向だけだったら値の大小だけで良いかもしれませんが、

今回はアナログスティックの様な360°フリックの方向を出したいので、

ここでは三角関数を使用します。

アークタンジェント(tanの逆関数)を使用すれば、y/xから求めることができます。

関数名はatan2()です。

class SampleScene : public cocos2d::Scene
{
private:
    cocos2d::Label* label;
    cocos2d::Vec2 startPos, endPos;
    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = [this](Touch* touch, Event* event)->bool
    {
        startPos = touch->getLocation();
        return true;
    };
    listener1->onTouchEnded = [this](Touch* touch, Event* event)->bool
    {
        endPos = touch->getLocation();
        double angle = atan2(endPos.y - startPos.y,endPos.x - startPos.x) * 180.0 / M_PI;
        auto str = String();
        str.appendWithFormat("angle %f", angle);
        label->setString(str.getCString());
        return true;
    };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

atan2が返す値の単位はラジアンなので、これに×180/PIとすることで角度に変換できます。

よし、これで必要なことは一通りできたかな。

【テクテクライフ】平和の滝

なぜここが札幌の名所なんだろう。

この場所が結構やばくて、

これを見れば分かるだろうか。

札幌の中心部からかなり離れているのである。

行き方は、地下鉄東西線発寒南駅から、平和の滝入り口行きのバスに乗る。

そこから20分ぐらい歩く。

かなり田舎である。

漬いた。

駐車場から急な階段を降りていけば滝がある。

これだけだった。

うーん、なんでこんな所が名所なんだろう?🤔🤔🤔

登山道があって、ここから手稲山に抜けることができるみたい。

もうちょっと季節が進めば紅葉がきれいなんだろうなぁ。

ちょっと時期が早すぎたか。

プロジェクトセカイを始めた。

初音ミクなどのボーカロイド曲を題材としたリズムゲームで、

こういうゲームを見ると、どうしてもガルパと比べてしまう、バンドリーマーがおりまして。

ストーリーは主にユニット毎に用意されているのですが、

なかなか内容が重たくて。

初音ミクを初めとしたバーチャルシンガーがどのように関与してメンバーの関係をどうこうしていくのかが、見所です。

ガルパと比較してしまう、理由としては、

基本的なシステムがガルパをベースにしていること。

勿論オリジナル要素もあるのですが、

たぶん、今後リリースされるリズムゲームは、ガルパのシステムがベースになっていくんじゃ無いかと。

で、オイラのゲームの状況ですが、

初回ガチャで見事に一歌を引き当てることができ、さらに溜めた石でレンを引き当てました。

ちょっとラッキー。

まずは、ライブをこなしつつレベルを上げて、サイドストーリーとエリアアイテムを買いそろえていく、というのがメインになると思います。

そして、上級者はいきなりハードから始める方が多いと思いますが(ハードをフルコンすると、無料石がもらえる)

ただ、ガルパの経験からすると、後で必ずコインが不足します。

素材は揃っているのに。

なので、オイラはEasyからフルコンしていって、コインを貯めることを忘れずにプレイしています。

でもまぁ、これでもコインは不足する(と思う)んですけどね。

あとはイベントとかの報酬でキャラを獲得して、☆3の頭数を増やすことですかね。

それができれば序盤は戦えるはず。

ちなみに、どのくらいのレベルまで対応出来るかというと、レベル26、27でクリアできる、という腕前です。

多分難易度マスターは無理(今のところは)

そう考えると、ガルパってまだまだぬるま湯なんだなぁって思いました。

テクテクライフがリリースされました。

このゲームは、ちょっと昔にサービス終了したテクテクテクテクの後継アプリゲームです。

町中を歩いて、通ったエリアに色を塗っていく、というゲームで、

テクテクテクテクではそれにRPG的な要素も含まれていて、フィールドの敵と戦ってレベルを上げてボスを倒していく、という内容だったのですが、

今回、テクテクライフになって、そう言ったバトル要素は全て排除されました。

純粋に地図を塗っていくゲームになっています。

むしろ、こっちのほうが良かったかもしれない。

ボスが強すぎて、勝てなくて、ランクが頭打ちになってしまうという状況に陥っていたので。

その一方で、目的を持って地図を埋めていくという楽しみが増えています。

タイムスタンプラリーという機能が追加されていまして、

目的地近くに行くとチェックすることができて、コンプリートするとなにかご褒美がもらえるかもしれません。

札幌では、東豊線スタンプラリーと札幌名所スタンプラリーが用意されています。

時間があるときにいかがでしょうか。

また、今回新たに追加された便利な機能として、

バックグラウンドでもGPS信号をトレースして予約塗りすることができます。

有料ですが。

時間制と月更新のサブスク方式があるのですが、出歩くことが多い人は明らかにサブスクを利用した方がお得です。

そして、この機能を利用すると、ドラクエウォークと同時プレイができます。

最近、毎日とにかく歩きまくる、というのを実践していまして、

ドラクエウォークもやっと上級職に転職できまして、

これでさらにお出かけの楽しみが増えるというものです。

ほんと最近歩きまくってるな・・・

【ぼっち】餃子×酒×肴 大衆酒場036(ゼロサンロク)

久しぶりの新店舗開拓。

いや、GoToイート利用したかったけど、1人で予約取れる店少ないのよ。

もっとぼっちに優しい世界になってほしい。

看板メニューは餃子。

タレは各種揃っておりますが、餃子自体に味が付いているので、まずは何もつけずにそのまま頂いて、残りは自分のお好きなように調合して頂くと良いと思います。

この餃子が本当に美味しくて、ビールが進む!

旬の鰹のたたきも頂きました。

いやー旨かった!

比較的早い時間からオープンしているのも嬉しいですね。

15:00開始はギリギリディナータイムなので。

今回は非常に満足でした。

【北海道大戦】じゃんけんアイコンをクラス化

じゃんけんアイコンを使いやすい様にクラス化させます。

    class BattleIcon
    {
        public enum Icon
        {
            Gu,
            Choki,
            Par
        }

        public enum Position
        {
            Attack,
            Deffence
        }

        private asd.TextureObject2D _image = null;
        private Icon _icon;
        private Position _position;
        private int _x;
        private int _y;
        private int width = 80;
        private int height = 80;

        public BattleIcon(Icon icon, Position pos)
        {
            _icon = icon;
            _position = pos;
            _image = new asd.TextureObject2D();
            if (pos == Position.Attack)
            {
                _y = 500;
                switch (icon)
                {
                    case Icon.Gu:
                        _x = 300;
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _x = 450;
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _x = 600;
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
                _image.Position = new asd.Vector2DF(_x, _y);
            }
            else
            {
                _y = 250;
                switch (icon)
                {
                    case Icon.Gu:
                        _x = 300;
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _x = 450;
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _x = 600;
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
                _image.Position = new asd.Vector2DF(_x, _y);
            }
        }

        public void AddLayer(asd.Layer2D layer)
        {
            layer.AddObject(_image);
        }

        public void Show()
        {
            switch (_icon)
            {
                case Icon.Gu:
                    _image.Texture = Singleton.ImageGu;
                    break;
                case Icon.Choki:
                    _image.Texture = Singleton.ImageChoki;
                    break;
                case Icon.Par:
                    _image.Texture = Singleton.ImagePar;
                    break;
            }
        }

        public void Hide()
        {
            _image.Texture = null;
        }

        public void OnMouse(asd.Vector2DF pos)
        {
            if (IsOnMouse(pos))
            {
                switch (_icon)
                {
                    case Icon.Gu:
                        _image.Texture = Singleton.ImageGu2;
                        break;
                    case Icon.Choki:
                        _image.Texture = Singleton.ImageChoki2;
                        break;
                    case Icon.Par:
                        _image.Texture = Singleton.ImagePar2;
                        break;
                }
            }
            else
            {
                switch (_icon)
                {
                    case Icon.Gu:
                        _image.Texture = Singleton.ImageGu;
                        break;
                    case Icon.Choki:
                        _image.Texture = Singleton.ImageChoki;
                        break;
                    case Icon.Par:
                        _image.Texture = Singleton.ImagePar;
                        break;
                }
            }
        }

        public bool IsOnMouse(asd.Vector2DF pos)
        {
            if (pos.X > _x && pos.X < width + _x
                && pos.Y > _y && pos.Y < height + _y)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

とりあえず、使いそうなメソッドをいくつか実装しました。

    class BattleScene : asd.Scene
    {
        protected override void OnUpdated()
        {
            asd.Vector2DF pos = asd.Engine.Mouse.Position;

            switch (_status)
            {
                case GameStatus.SelectDeffenceAction:
                    cycleProcessSelectDeffenceAction(pos);
                    break;
                case GameStatus.SelectAttackAction:
                    break;
                case GameStatus.ShowActionResult:
                    break;
            }
            if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
            {
                switch (_status)
                {
                    case GameStatus.SelectDeffenceAction:
                        break;
                    case GameStatus.SelectAttackAction:
                        break;
                    case GameStatus.ShowActionResult:
                        break;
                }
            }
        }

        private void cycleProcessSelectDeffenceAction(asd.Vector2DF pos)
        {
            _image_gu_deffence.OnMouse(pos);
            _image_choki_deffence.OnMouse(pos);
            _image_par_deffence.OnMouse(pos);
        }

とりあえず、こんなところか。

マウスカーソルを合わせることで色を変えるようにしました。

うん、今日もがんばった。

Pixel5とPixel4a(5G)が発表されましたね。

思ったより早かったね。

販売開始は10月15日、価格はPixel4(5G)が60,500円、Pixel5が74,800円です。

Pixel4a(5G)で何が変わったか

まず、当然のことながら、プロセッサーが5G対応のSnapdragon 765Gになりました。

あとは、背面に広角カメラがついて2眼になっています。

Pixel5で何が変わったか

プロセッサーはPixel4a(5G)と同じSnapdragon 765Gですね。

Pixel4にあったMotion Senseが無くなった模様です。

そのおかげで、Pixel4よりも安くなりました。

このシリーズ、毎回なにかしら尖った機能が搭載されているのですが、

おそらく、今回はバッテリーシェアがそれに当たると思います。

Pixel5から他の端末に無線で充電できる機能です。

そのためか、Pixel4の弱点だったバッテリー容量も大幅に強化されています。

iPhoneより先に5Gに対応したことで大きくアドバンテージを取ったことになりますが、

そうなると、次の新型iPhoneの発表も期待されますね。

まぁ、オイラはPixel4a買ったばかりだから。

【ラズパイ】リモートでカメラのプレビュー表示

いや、今回は結構ハマった。

これが半日頑張った成果だ。

まずはラズパイ側。

import base64
import cv2
import json
import os
import sys
import urllib.parse
import time
import threading

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from http import HTTPStatus

PORT = 8000

cap = cv2.VideoCapture(0)

def __main__():
    thread = threading.Thread(target=httpServe)
    thread.start()
    
    try:
        while cap.isOpened():
            time.sleep(1)
    except KeyboardInterrupt:
        return

def httpServe():
    handler = StubHttpRequestHandler
    httpd = HTTPServer(('',PORT),handler)
    httpd.serve_forever()

class StubHttpRequestHandler(BaseHTTPRequestHandler):
    server_version = "HTTP Stub/0.1"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_GET(self):
        enc = sys.getfilesystemencoding()

        _, img = cap.read()
        resized_img = cv2.resize(img, (480, 320))
        _, encoded_img = cv2.imencode('.jpg', resized_img, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        dst_base64 = base64.b64encode(encoded_img).decode('utf-8')

        data = {
            'image': 'data:image/jpg;base64,' + dst_base64
        }

        encoded = json.dumps(data).encode()

        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Access-Control-Allow-Origin", "null")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()

        self.wfile.write(encoded)     

__main__()

そしてクライアント側。

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script src="vue.min.js"></script>
  <script src="jquery-3.5.1.slim.min.js"></script>
</head>
<body>
  <div id="app">
      <image id="camera" src="" />
  </div>

  <script>
    var app = new Vue({
      el: '#app',
      data: {
        timer: null,
      },
      created: function() {
        self = this;
        this.timer = setInterval(function() {self.onLoad()}, 50)
      },
      methods: {
        onLoad: function() {
            axios.get('http://pi4.local:8000').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>

まず、サーバ側の説明。

今回は、すでに使った実績のある、HTTPで通信を行います。

もっと下位層のプロトコルを使うと、もっと効率よくデータの送信ができるのですが、その分、扱いも難しくなります。

HTTPはTCP/UDPよりも、データのやりとりが多くなるので、少しモッサリ感がありますが、扱いが簡単になります。

実際、今回はブラウザで表示させているので!

カメラで撮影し、それを画像に落とすところまでは今まで通りですが、

今回はHTTPで送信するために、Base64に変換し、Jsonに載せて送信します。

Base64の頭にある「data:image/jpg;base64」というのは、このデータはJpegですよ、ということを示す文字列で、これがないと、受け手側は何のデータか判断できません。

「Access-Control-Allow-Origin」はCORS対策です。

例えばクロームなのでは、同じドメインでなければ画像が開けない、という制約がありまして、それを判断しているのが、リクエストヘッダーのOriginと、レスポンスヘッダーのAccess-Control-Allow-Origin。

この二つの値が一致しないと画像はエラーで表示されなくなります。(ただしブラウザによる)

今回はChrome側のOriginがnullだったので、それに合わせました。

FireFoxとかだったらまた話が変わってくるかもしれない。

続いて、クライアント側。

サクッと作成するために、Vue.jsを使用しました。

使用しているライブラリはaxios、jQueryです。

Laravelと同じような構成にしました。

画面には<div id=”app”>と<image id=”camera”>のみです。

やっていることは単純で、画面が作成されたらcreatedメソッドが実行されて、onLoad()を周期的にコールするようにしています。

50という数字を小さくすればヌルヌルになりますし(ただしラズパイ側の負荷が大きくなる)、大きくすればカクカクになります。

今回はラズパイにクーラーつけているから大丈夫だけど、ラズパイZeroでこの負荷はやばいと思う。

そして、レスポンスの中のBase64をimageのsrcに入れれば画像が表示されます。

これを早いサイクルで実行・画像更新することで、動画のように見せることが出来ます。

今回はWebカメラみたいに仕上がりましたが、HTTPが使えるならばクライアントは何だってできます!

openGLって今更やる必要があるのかって思った。

openTKでハマってしまったのを前に記事で書いたのですが、

本気でやるなら書籍を買うしかないと思って探しては見た物の、あまりピンとこないというか、アレだったので、

そんなときに見つけたのがこのページ。

細かい所は上のページを読んで貰うとして、

結論から言うと、

openGLの理論を学ぶとするのはいいけれど、

単に3Dアプリを作りたいと言うだけならば、既存のプラットフォームを使った方が早い。

例えばUnityとか。

openGLで悩むより、Unityの勉強を始めた方がいい。

そういえば、Unityの書籍を使った勉強、途中で止まっていたけど、

再開してみるかな。

自分、ぼっちですが何か?