「技術」カテゴリーアーカイブ

【北海道大戦】じゃんけんバトルを実装

実際にじゃんけんバトルを実装していきます。

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

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

        private void cycleProcessSelectDeffenceAction(asd.Vector2DF pos)
        {
            if(_player == Player.Deffence)
            {
                _image_gu_deffence.Show();
                _image_choki_deffence.Show();
                _image_par_deffence.Show();
                _image_gu_deffence.OnMouse(pos);
                _image_choki_deffence.OnMouse(pos);
                _image_par_deffence.OnMouse(pos);
            }
            else
            {
                var r = Singleton.Random;
                switch(r.Next(0, 3))
                {
                    case 0:
                        selectedDeffece = BattleIcon.Icon.Gu;
                        break;
                    case 1:
                        selectedDeffece = BattleIcon.Icon.Choki;
                        break;
                    case 2:
                        selectedDeffece = BattleIcon.Icon.Par;
                        break;
                }
                _status = GameStatus.SelectAttackAction;
            }
        }

        private void cycleProcessSelectAttackAction(asd.Vector2DF pos)
        {
            if (_player == Player.Attack)
            {
                _image_gu_attack.Show();
                _image_choki_attack.Show();
                _image_par_attack.Show();
                _image_gu_attack.OnMouse(pos);
                _image_choki_attack.OnMouse(pos);
                _image_par_attack.OnMouse(pos);
            }
            else
            {
                var r = Singleton.Random;
                switch (r.Next(0, 3))
                {
                    case 0:
                        selectedAttack = BattleIcon.Icon.Gu;
                        break;
                    case 1:
                        selectedAttack = BattleIcon.Icon.Choki;
                        break;
                    case 2:
                        selectedAttack = BattleIcon.Icon.Par;
                        break;
                }
                _status = GameStatus.ShowActionResult;
            }
        }

        private void cycleProcessShowActionResult(asd.Vector2DF pos)
        {
            _attackResult.SetIcon(selectedAttack);
            _attackResult.Show();
            _deffenceResult.SetIcon(selectedDeffece);
            _deffenceResult.Show();
        }

        private void onClickMouseSelectDeffenceAction(asd.Vector2DF pos)
        {
            if (_player == Player.Deffence)
            {
                if (_image_gu_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Gu;
                }
                else if (_image_choki_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Choki;
                }
                else if (_image_par_deffence.IsOnMouse(pos))
                {
                    selectedDeffece = BattleIcon.Icon.Par;
                }
                _image_gu_deffence.Hide();
                _image_choki_deffence.Hide();
                _image_par_deffence.Hide();
                _status = GameStatus.SelectAttackAction;
            }
        }

        private void onClickMouseSelectAttackAction(asd.Vector2DF pos)
        {
            if (_player == Player.Attack)
            {
                if (_image_gu_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Gu;
                }
                else if (_image_choki_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Choki;
                }
                else if (_image_par_attack.IsOnMouse(pos))
                {
                    selectedAttack = BattleIcon.Icon.Par;
                }
                _image_gu_attack.Hide();
                _image_choki_attack.Hide();
                _image_par_attack.Hide();
                _status = GameStatus.ShowActionResult;
            }
        }

        private void onClickMouseShowActionResult(asd.Vector2DF pos)
        {
            _attackResult.Hide();
            _deffenceResult.Hide();
            _status = GameStatus.SelectDeffenceAction;
        }

シーンの状態は、

  1. 防御側の選択
  2. 攻撃側の選択
  3. 結果の表示

の順で遷移していきます。

防御側選択状態では、プレイヤーが防御側ならば、じゃんけんアイコンを選択します。プレイヤーが攻撃側ならば、ランダムで相手が何を選択したかを決定します。

攻撃側選択状態は、防御側選択の逆で、プレイヤーが攻撃側ならば、じゃんけんアイコンを選択、プレイヤーが防御側ならば、ランダムで相手が何を選択したかを決定します。

そして最後に攻撃側と防御側が選択したアイコンを表示し、じゃんけんの結果を表示します。

まぁ、ここまでは表示をどうするかの処理が中心になります。

次回はじゃんけんの結果を処理する実装を行います。

【ラズパイ】【カメラ】プレビュー画面をラズパイから取得・表示

前回は、Webページをローカルで作成して、そこからブラウザに表示させていたのですが、

今回はWebページをラズパイに設置して、ブラウザからラズパイにアクセスすることによってプレビュー画面を表示させたいと思います。

動作はURLのパスによって変えます。

パスが”/”ならば、Webページを、”/Streaming”ならカメラの画像を返すようにします。

まず、Pythonのコードはこうなりました。

import base64
import cv2
import json
import os
import sys
import time
import threading

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from http import HTTPStatus
from urllib.parse import urlparse

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):
        parsed = urlparse(self.path)
        if parsed.path == '/Streaming':
            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)
        elif parsed.path == '/vue.min.js':
            self.send_response(HTTPStatus.OK)
            with open('vue.min.js',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/javascript")
            self.end_headers()
            self.wfile.write(bytes(data))
        elif parsed.path == '/jquery-3.5.1.slim.min.js':
            self.send_response(HTTPStatus.OK)
            with open('jquery-3.5.1.slim.min.js',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/javascript")
            self.end_headers()
            self.wfile.write(bytes(data))
        else:
            self.send_response(HTTPStatus.OK)
            with open('index.html',mode='br') as f:
                data = f.read()
            self.send_header("Content-type", "text/html; charset=utf-8")
            self.end_headers()
            self.wfile.write(bytes(data))

__main__()

無駄にでかくなった気がする。

というのも、使用しているjsファイルもラズパイからGETしようとブラウザが動くので、jsファイルを送るように作成しないといけないのです。

めんどくさいから、他のライブラリを使用することも考えないといかんなぁ。

続いて、html側。

こちらはそんなに難しくはない。

URLを変えるだけなので。

<!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/Streaming').then(function(response){
                $("#camera").attr('src', response.data.image);
            }).catch(function(error){
            });
        }
      }
    })
  </script>
</body>
</html>

これで動作しました。

【ダイエット支援】【入力履歴機能】データベース構築

データベースをサクッと作成していきます。

$ php artisan make:migration create_eating_history_item
class CreateEatingHistoryItem extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('eating_history_items', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->double('protein');
            $table->double('liqid');
            $table->double('carbo');
            $table->double('calorie');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('eating_history_item_user', function (Blueprint $table) {
            $table->integer('user_id')
                  ->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');
            $table->integer('eating_history_item_id')
                  ->foreign('eating_history_item_id')
                  ->references('id')->on('eating_history_items')
                  ->onDelete('cascade');
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('eating_template_items', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->double('protein');
            $table->double('liqid');
            $table->double('carbo');
            $table->double('calorie');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('eating_template_items');
        Schema::dropIfExists('eating_history_item_user');
        Schema::dropIfExists('eating_history_items');
    }
}
$ php artisan migrate
class EatingHistoryItem extends Model
{
    protected $table = 'eating_history_items';
}
class EatingTemplateItem extends Model
{
    protected $table = 'eating_template_items';
}
class User extends Authenticatable
{

    public function EatingHistoryItems()
    {
        return $this->belongsToMany('App\Model\EatingHistoryItem');
    }
}

データの検索は、テンプレートからだけでなく、ユーザーが入力したヒストリデータからも取得できることを想定して構築してみました。

あと、開発環境がUbuntu 18.04から20.04になったのですが、

PHPのバージョンが7.2から7.4に変わりました。

たぶん、このままだとLaravelからデータベース(mysql)にアクセスできない(could not find driver)ので、php-mysqlをインストールし直す必要があるようです。

$ sudo apt-get install php-mysql

【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とすることで角度に変換できます。

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

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

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

    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);
        }

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

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

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

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

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

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

まずはラズパイ側。

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が使えるならばクライアントは何だってできます!

【ダイエット支援】もっと具体的な設計をしなくちゃいけなくて

たぶん、前回の記事の内容だけではコーディングはまだできなくって、

もっと具体的に動作の仕組みを考えなくちゃいけないと思いまして。

UIの動き

  • 文字を入力すると、データベースから入力候補を取得し、表示する
  • 入力した内容が入力候補と一致すると、その栄養素値が自動で入力される
  • 入力を空にすると、入力候補も空にする

テキスト窓に入力したかどうかはVue.jsではv-on:changeで処理を起動することができるみたいなので、これを使用します。

データベース

基本的には入力済みの食品・栄養素の情報(テンプレート)からデータを抽出して候補がある物から選択するという形にしたいと思います。

ただ、そう言った食品や栄養素の情報を全部用意するのは大変なので、ユーザーが入力した情報を記憶しておいて(ヒストリ)、管理者が正式データとして登録する、という仕組みにしたいと思います。

なので、管理者画面が必要になりますね。

データベースはテンプレートとヒストリの二つ用意しておきたいと思います。

ここらへん、本当はもっと詰めなくちゃいけない所ですが、今回はあとでどうにでもできるように、リスクが少ない方法を選択したいと思います。

あとでデータベース構成を変えるとなるとコードの修正も大変なので。

なので、今後の作業はこんな感じです。

  • データベースの構築
  • テンプレートからデータを入力する処理
  • ユーザーが入力したデータをヒストリに保存する処理
  • 管理者がヒストリからテンプレートに移す処理

よし、これでいきましょう。

【COCOS2D-X】タッチ処理を使用する。

さて、今回はタッチ処理を試してみたいと思います。

タッチ処理はスマホ専用の処理なので、Windowsでは動かないので、スマホで動作確認します。

で、タッチした座標を画面に表示させたいと思います。

    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = [this](Touch* touch, Event* event)->bool
    {
        auto str = String();
        str.appendWithFormat("Touch (%f %f)", touch->getLocation().x, touch->getLocation().y);
        label->setString(str.getCString());
        return true;
    };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

タッチ処理はイベントリスナーを使用するのですが、

EventListenerTouchOneByOneとEventListenerTouchAllAtOnceがあるのですが、前者はシングルタッチ、後者はマルチタッチのようです。

使用するハンドラ名が若干違うようです。

今回はシングルタッチを使用します。

で、今回初めてC++のラムダ式を使ったのですが、

[]はキャプチャといって、ラムダ式内に持っていく変数を指定するみたいなんですよね。

座標を表示するラベルなんですが、

ローカルにLabelオブジェクトを定義して[&label]って書いてもエラーで動かなかったので、

今回はサンプルコードに従って、labelはprivate変数に定義し、キャプチャには[this]とします。

こうすることでprivateのlabelオブジェクトを使用することができました。

あとは、引数touchからx,y座標をラベルに表示させる。

スマホだからか、画像のクリッピング処理がWindowsとは動きが違ってたんですよね。

ちょっとここは要調査ですわ。

もしかしたら、Windows用とスマホ用に画像を2種類用意しないといけないかもしれん。

【北海道大戦】バトルシーンへパラメータを渡す。

前回までの状況はこちら。

バトルシーンを作成するために、バトルシーンで使用するパラメータを設定します。

シーンはその都度作成しなくちゃいけないので、コンストラクタで渡すのが一番良いでしょう。

        public enum Player
        {
            Attack,
            Deffence
        }

        public BattleScene(City attack, City deffence, Player player)
        {
            _attack = attack;
            _attackPower = attack.Population;
            _deffence = deffence;
            _deffencePower = deffence.Population;
            _player = player;
            _status = GameStatus.SelectDeffenceAction;
        }

playerというパラメータは、プレイヤーが攻撃側か防御側かを示すパラメータです。

あれこれ悩んだ結果、これが一番簡単だろうと。

これを画面に表示させます。

            var attackCityLabel = new asd.TextObject2D();
            attackCityLabel.Font = Singleton.LargeFont;
            attackCityLabel.Text = _attack.Name;
            attackCityLabel.Position = new asd.Vector2DF(450, 150);
            layer.AddObject(attackCityLabel);

            var deffenceCityLabel = new asd.TextObject2D();
            deffenceCityLabel.Font = Singleton.LargeFont;
            deffenceCityLabel.Text = _deffence.Name;
            deffenceCityLabel.Position = new asd.Vector2DF(450, 650);
            layer.AddObject(deffenceCityLabel);

            _attackParam = new asd.TextObject2D();
            _attackParam.Font = Singleton.LargeFont;
            _attackParam.Text = "戦闘力:" + _attack.Population;
            _attackParam.Position = new asd.Vector2DF(700, 650);
            layer.AddObject(_attackParam);

            _deffenceParam = new asd.TextObject2D();
            _deffenceParam.Font = Singleton.LargeFont;
            _deffenceParam.Text = "戦闘力:" + _deffence.Population;
            _deffenceParam.Position = new asd.Vector2DF(700, 150);
            layer.AddObject(_deffenceParam);

さらに、

このフローからバトルシーンの状態を抽出。

        enum GameStatus {
            SelectDeffenceAction,
            SelectAttackAction,
            ShowActionResult
        }

これを実装。

        protected override void OnUpdated()
        {
            switch (_status)
            {
                case GameStatus.SelectDeffenceAction:
                    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;
                }
            }
        }

あとは、各状態について実装していけば良いのですな。

とりあえず今回はここまでにしておこう。