次のステップとして、スマホから利用することによって、スマホの情報を利用できるようにしたいなぁ、って思ってたんですが、
それ以前の問題が起きていました。
トップページ。
ひどいなこれ。
データ入力・編集・削除ダイヤログも崩れていました。
これはひどい。
まずはこれを直すところかは始めようか。

見た目は変わらないんですが。
前回のままだといろいろ扱いづらいので、左のウィンドウをクラス化しました。
そして、今回は、前回と同じように表示できるところまで作成しました。
このウィンドウをタップすると、画面中央に詳細が表示されるような動きにしたいと思っています。
//
// Created by ntaki on 2020/12/12.
//
#ifndef PROJ_ANDROID_CHARAWINDOW_H
#define PROJ_ANDROID_CHARAWINDOW_H
#include "cocos2d.h"
#include "GameStatusData/Character.h"
class CharaWindow {
private:
cocos2d::Vec2 point;
cocos2d::Size size;
cocos2d::Sprite* sprite;
cocos2d::Label* hpLabel;
cocos2d::Label* MpLabel;
public:
CharaWindow();
~CharaWindow();
void show(cocos2d::Scene* scene);
void hide(cocos2d::Scene* scene);
cocos2d::Size getSpriteContentSize();
cocos2d::Vec2 getPosition();
cocos2d::Size getSize();
void setPosition(cocos2d::Vec2 vector);
void setScale(float rate);
void setParameter(Character* chara);
bool isTouch(cocos2d::Vec2 vector);
};
#endif //PROJ_ANDROID_CHARAWINDOW_H
//
// Created by ntaki on 2020/12/12.
//
#include "CharaWindow.h"
CharaWindow::CharaWindow()
{
sprite = cocos2d::Sprite::create("btn02_03_s_bl.png");
if (sprite != nullptr)
{
sprite->setAnchorPoint(cocos2d::Vec2(0,0));
auto offset = sprite->getContentSize().width / 10.0;
hpLabel = cocos2d::Label::createWithTTF("Hello World", "fonts/msgothic.ttc", 6);
if(hpLabel != nullptr)
{
hpLabel->setAnchorPoint(cocos2d::Vec2(0, 1));
hpLabel->setPosition(cocos2d::Vec2(0, sprite->getContentSize().height));
sprite->addChild(hpLabel);
}
MpLabel = cocos2d::Label::createWithTTF("Hello World", "fonts/msgothic.ttc", 6);
if(MpLabel != nullptr)
{
MpLabel->setAnchorPoint(cocos2d::Vec2(0, 1));
MpLabel->setPosition(cocos2d::Vec2(0, sprite->getContentSize().height - 1 * hpLabel->getContentSize().height));
sprite->addChild(MpLabel);
}
}
}
CharaWindow::~CharaWindow()
{
}
void CharaWindow::setPosition(cocos2d::Vec2 vector)
{
if (sprite != nullptr)
{
point = vector;
sprite->setPosition(point);
}
}
void CharaWindow::setScale(float rate)
{
if (sprite != nullptr)
{
sprite->setScale(rate);
size = cocos2d::Size(sprite->getContentSize().width * rate, sprite->getContentSize().height * rate);
}
}
void CharaWindow::show(cocos2d::Scene* scene)
{
if (sprite != nullptr && scene != nullptr)
{
scene->addChild(sprite, 1);
}
}
void CharaWindow::hide(cocos2d::Scene *scene)
{
if (sprite != nullptr && scene != nullptr)
{
scene->removeChild(sprite, false);
}
}
cocos2d::Vec2 CharaWindow::getPosition()
{
return point;
}
cocos2d::Size CharaWindow::getSize()
{
return size;
}
cocos2d::Size CharaWindow::getSpriteContentSize()
{
if (sprite != nullptr)
{
return sprite->getContentSize();
}
}
void CharaWindow::setParameter(Character* chara)
{
auto hpStr = cocos2d::String();
hpStr.appendWithFormat("HP : %d", chara->MaxHp);
hpLabel->setString(hpStr.getCString());
auto mpStr = cocos2d::String();
mpStr.appendWithFormat("MP : %d", chara->MaxMp);
MpLabel->setString(mpStr.getCString());
}
bool CharaWindow::isTouch(cocos2d::Vec2 vector)
{
return false;
}
CharaWindow* window[4];
auto chara = GameStatus::GetGameData()->Charactors->begin();
for(int i = 0; i < 4; i++) {
window[i] = new CharaWindow();
window[i]->setParameter(chara.operator*());
window[i]->setScale((visibleSize.height / 4) / window[i]->getSpriteContentSize().height);
window[i]->setPosition(Vec2(xpos + origin.x, origin.y + window[i]->getSize().height * i));
window[i]->show(this);
chara++;
}
クラス化させることで、ウィンドウ表示処理がかなりスッキリしました。
このクラスの中で使用しているオブジェクトはnewで作成した物ではないので、cocos-2dx内部で適時ガベージコレクションされるでしょう。
Adapterパターンのコード例です。
Adapterには継承を使ったパターンと委譲を使ったパターンがあります。
まずは継承を使用したパターン。
package org.example.adapter;
public class Adaptee {
public void methodA()
{
}
public void methodB()
{
}
}
package org.example.adapter;
public interface Target {
public void targetMethod1();
public void targetMethod2();
}
package org.example.adapter;
public class Adapter extends Adaptee implements Target{
public void targetMethod1() {
methodA();
}
public void targetMethod2() {
methodB();
}
}
package org.example.adapter;
public class Main {
public static void main(String[] args)
{
Adaptee adaptee = new Adaptee();
adaptee.methodA();
adaptee.methodB();
Adapter adapter = new Adapter();
adapter.targetMethod1();
adapter.targetMethod2();
}
}
そして、委譲を使用したパターン。
package org.example.adapter;
public class Adapter implements Target{
Adaptee adaptee;
public Adapter()
{
adaptee = new Adaptee();
}
public void targetMethod1() {
adaptee.methodA();
}
public void targetMethod2() {
adaptee.methodB();
}
}
このパターンは元々存在していたクラスAdapteeを使用したいけど、インターフェースが合わない、と言う場合、新しいクラスAdapterを作成して使用できるようにした、というパターンです。
継承と委譲がありますが、自分としては委譲の方がしっくりきます。
動いた。😄
$ sudo apt-get install lirc
/boot/config.txtを書き換える。
今回は受信信号をGPIO21、赤外線LEDをGPIO20に接続しています。
# Uncomment this to enable infrared communication.
dtoverlay=gpio-ir,gpio_pin=21
dtoverlay=gpio-ir-tx,gpio_pin=20
/etc/lirc/lirc_options.confを書き換える。
まずはリモコンの信号を受信してデータを取るので、deviceは/dev/lirc1を記入する。
[lircd]
nodaemon = False
driver = default
device = /dev/lirc1
lircdを一時停止。
$ sudo service lired stop
以下のコマンドを入力後、リモコンのボタンを押す。
今回は音量UPのボタンを押してみた。
mode2 -d /dev/lirc1 > volup.txt
volup.txtには、いかの様に出力されました。
space 16777215
pulse 9059
space 4508
pulse 632
space 501
pulse 635
space 506
pulse 681
space 455
pulse 682
space 456
pulse 630
space 505
pulse 632
space 505
pulse 584
space 1684
pulse 681
space 455
pulse 584
space 1684
pulse 584
space 1684
pulse 584
space 1684
pulse 585
space 1684
pulse 585
space 1685
pulse 640
space 1581
pulse 689
space 1627
pulse 611
space 524
pulse 611
space 1658
pulse 615
space 523
pulse 615
space 1655
pulse 614
space 522
pulse 615
space 1655
pulse 613
space 1654
pulse 614
space 524
pulse 613
space 527
pulse 609
space 525
pulse 611
space 1658
pulse 588
space 549
pulse 608
space 1660
pulse 589
space 548
pulse 589
space 548
pulse 589
space 1679
pulse 592
space 1677
pulse 590
space 1675
pulse 594
pulse 13407
space 63383
space 40258
pulse 9119
space 2190
pulse 643
pulse 21195
space 121207
space 97010
pulse 9169
space 2141
pulse 688
pulse 22186
このデータの中から、一番最初の
space 16777215
と、後半の
pulse 13407
space 63383
space 40258
pulse 9119
space 2190
pulse 643
pulse 21195
space 121207
space 97010
pulse 9169
space 2141
pulse 688
pulse 22186
の部分は、いらないデータなので削除。
※基本的にpulseから始まり、pulse→space→pulse→・・・と続くので、データの数は必然的に奇数となる。なので、それに当てはまらない部分は余計なデータ。
/etc/lirc/lircd.conf.d/配下に、任意の名前.confでファイルを作成。
$ vi /etc/lirc/lircd.conf.d/tv.conf
ファイルの中身はこんな感じ。
begin remote
name tv
flags RAW_CODES
eps 30
aeps 100
gap 200000
toggle_bit_mask 0x0
begin raw_codes
name volup
9059 4508 632 501 635 506 681 455 682 456 630 505 632 505 584 1684 681
455 584 1684 584 1684 585 1684 585 1685 640 1581 689 1627 611 524 611 1658 615
523 615 1655 614 522 615 1655 613 1654 614 524 613 527 609 525 611 1658
588 549 608 1660 589 548 589 548 589 1679 592 1677 590 1675 594
name voldown
9087 4478 609 526 588 550 609 527 588 549 609 526 610 527 608 1659 611
526 610 1659 608 1658 612 1661 606 1658 608 1660 607 1660 611 526 611 1658 610
527 609 1659 610 1658 608 1659 609 1662 606 528 608 528 608 530 610 1658
608 528 608 530 608 526 610 529 608 1658 609 1659 608 1657 610
end raw_codes
end remote
2行目のnameはファイル名と合わせた方が混乱が少ない。
begin raw_codesの中にname コマンド名を記入し、次の行に信号データのみを羅列。※エディターの置換機能を使えば簡単
/etc/lirc/lirc_options.confを書き換える。
送信を行うので、lirc0に書き換える。
[lircd]
nodaemon = False
driver = default
device = /dev/lirc0
lircdを起動。
$ sudo service lired start
confの中身にエラーがあれば以下のコマンドでエラーが出力される。
$ sudo systemctl status lircd.service
以下のコマンドで作成したファイルが見えればOK。
$ irsend LIST '' ''
devinput-32
devinput-64
tv
赤外線LEDをテレビに向けて、コマンドを入力する。
$ irsend SEND_ONCE tv volup
$ irsend SEND_ONCE tv voldown
ようやくテレビが反応してくれました。
参考にしたサイト。
https://deviceplus.jp/hobby/entry_y18/
数字は完全にランダムです。
ちょっとゲームらしくなってきたでしょ。
#ifndef PROJ_ANDROID_CHARACTER_H
#define PROJ_ANDROID_CHARACTER_H
#include "cocos2d.h"
class Character {
public:
int Hp;
int MaxHp;
int Mp;
int MaxMp;
int Power;
int Speed;
int Magic;
public:
Character();
~Character();
};
#endif //PROJ_ANDROID_CHARACTER_H
#include "Character.h"
Character::Character()
{
MaxHp = cocos2d::random<int>(0, 100);
MaxMp = cocos2d::random<int>(0, 100);
Power = cocos2d::random<int>(0, 100);
Speed = cocos2d::random<int>(0, 100);
Magic = cocos2d::random<int>(0, 100);
}
Character::~Character()
{
}
#ifndef PROJ_ANDROID_GAMESTATUS_H
#define PROJ_ANDROID_GAMESTATUS_H
#include "cocos2d.h"
#include "Character.h"
#include <list>
class GameStatus {
private:
static GameStatus *gameStatus;
public:
std::list<Character*> *Charactors;
private:
GameStatus();
~GameStatus();
public:
static GameStatus* GetGameData();
static void Destroy();
};
#endif //PROJ_ANDROID_GAMESTATUS_H
#include "GameStatus.h"
GameStatus *GameStatus::gameStatus = nullptr;
GameStatus::GameStatus()
{
Charactors = new std::list<Character*>();
for(int i = 0; i < 4; i++)
{
Character *character = new Character();
Charactors->push_back(character);
}
}
GameStatus::~GameStatus()
{
for(Character *character = Charactors->front(); Charactors->empty() == true; character = Charactors->front())
{
delete character;
Charactors->pop_front();
}
delete Charactors;
}
GameStatus * GameStatus::GetGameData()
{
if(gameStatus == nullptr)
{
gameStatus = new GameStatus();
}
return gameStatus;
}
void GameStatus::Destroy()
{
delete gameStatus;
}
// ステータスウィンドウの配置
auto xpos = (visibleSize.width - sprite->getContentSize().width * scaleRate) / 2.0;
float windowScale = 0;
float windowHeight = 0;
Sprite *charaStatusWindow[4];
auto chara = GameStatus::GetGameData()->Charactors->begin();
for(int i = 0; i < 4; i++)
{
charaStatusWindow[i] = Sprite::create("btn02_03_s_bl.png");
if (charaStatusWindow[i] == nullptr)
{
problemLoading("'btn02_03_s_bl.png'");
}
else
{
if(windowScale == 0 || windowHeight == 0)
{
windowHeight = charaStatusWindow[i]->getContentSize().height;
windowScale = (visibleSize.height / 4) / windowHeight;
}
charaStatusWindow[i]->setPosition(Vec2(xpos + origin.x,origin.y + windowHeight * i * windowScale));
charaStatusWindow[i]->setAnchorPoint(Vec2(0,0));
charaStatusWindow[i]->setScale(windowScale);
this->addChild(charaStatusWindow[i], 1);
}
auto offset = charaStatusWindow[i]->getContentSize().width / 10.0;
auto hpLabel = Label::createWithTTF("Hello World", "fonts/msgothic.ttc", 12);
if (hpLabel == nullptr)
{
problemLoading("'fonts/msgothic.ttc'");
}
else
{
hpLabel->setAnchorPoint(Vec2(0, 1));
hpLabel->setPosition(Vec2(origin.x + xpos + offset, origin.y + windowHeight * (i + 1) * windowScale));
this->addChild(hpLabel, 2);
auto hpStr = String();
hpStr.appendWithFormat("HP : %d", chara.operator*()->MaxHp);
hpLabel->setString(hpStr.getCString());
}
auto MpLabel = Label::createWithTTF("Hello World", "fonts/msgothic.ttc", 12);
if (MpLabel == nullptr)
{
problemLoading("'fonts/msgothic.ttc'");
}
else
{
MpLabel->setAnchorPoint(Vec2(0, 1));
MpLabel->setPosition(Vec2(origin.x + xpos + offset, origin.y + windowHeight * (i + 1) * windowScale - hpLabel->getContentSize().height));
this->addChild(MpLabel, 2);
auto mpStr = String();
mpStr.appendWithFormat("MP : %d", chara.operator*()->MaxMp);
MpLabel->setString(mpStr.getCString());
}
chara++;
}
ウインドウ周りのクラス設計もちゃんと考えないといけないね。
あと、C++のList型は、他のプログラムのList型の様に使用できなくて、ちょっとめんどい。
完全にイテレータパターンのクラスなので、少々扱いづらい。
慣れるしか無いんだけど。
あと、乱数はcocos-2dxに搭載されているものが使用できます。
C++標準の乱数よりも使いやすいです。
Singletonパターンのコード例です。
package org.example.singleton;
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
public void method1() {
}
}
package org.example.singleton;
public class Main {
public static void main(String[] args) {
Singleton obj = Singleton.getInstance();
obj.method1();
}
}
Singletonはプログラム上にインスタンスが一つしか存在しない場合に使用されます。
インスタンスの取得はgetInstance()で取得して使用します。
このインスタンスは常に同じもので、一つのインスタンスをみんなで使い回すというイメージですね。
よく使われるパターンです。
まずは、前回のプログラムを元に、以下のようなプログラムを作成しました。
import RPi.GPIO as GPIO
import time
def __main__():
GPIO.setmode(GPIO.BCM)
GPIO.setup(21,GPIO.IN)
try:
while True:
out = GPIO.input(21)
if out == 0:
print(1)
else:
print(0)
time.sleep(0.0001)
except KeyboardInterrupt:
GPIO.cleanup()
__main__()
0.1ミリ秒周期で赤外線通信の受信信号を読み取って、0/1で出力するプログラムを作成してみました。
これを使用して、テレビのボリュームUP/DOWNの信号を読み取って、
こんなプログラムを作成して、赤外線LEDを点灯させるプログラムを作成。
import RPi.GPIO as GPIO
import time
pattern = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,
0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,
0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,0,1,1,1,0,
0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,
1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1]
def __main__():
GPIO.setmode(GPIO.BCM)
GPIO.setup(20,GPIO.OUT)
try:
for out in pattern:
GPIO.output(20, GPIO.HIGH)
time.sleep(0.0001)
GPIO.output(20, GPIO.LOW)
except KeyboardInterrupt:
GPIO.cleanup()
GPIO.cleanup()
__main__()
import RPi.GPIO as GPIO
import time
pattern = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,
0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,
0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,
1,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1]
def __main__():
GPIO.setmode(GPIO.BCM)
GPIO.setup(20,GPIO.OUT)
try:
for out in pattern:
GPIO.output(20, GPIO.HIGH)
time.sleep(0.0001)
GPIO.output(20, GPIO.LOW)
except KeyboardInterrupt:
GPIO.cleanup()
GPIO.cleanup()
__main__()
これで、リモコンの信号を再現できるはず!
そう思っていましたが、
テレビは何も反応しませんでした。
やっぱり考えは甘かったか。
そこで、ネットの情報を探してみると、
こんなサイトを見つけました。
https://deviceplus.jp/hobby/entry_y18/
リモコンの信号を読み取って、再現するライブラリがラズパイにあるらしい。
しかも、このサイトの中では、音声でテレビをコントロールしようともしている。
ならばこっちは、最終的にAlexaでコントロールできるようにしてしまおうか。
できるよね?
知らんけど。
いろいろ調べてみる。
残りは管理者画面を管理者以外アクセスできないようにする対処を入れます。
まずは、管理者アカウントを登録。
<?php
use Illuminate\Database\Seeder;
class ManagerAccountSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert(['name' => 'manager', 'email' => 'manage@gmail.com', 'password' => bcrypt('manager')]);
}
}
実際運用するときはもちろん値書き換えるよ。
あとは、このアカウント以外は管理者画面に入れないようにする。
class MaintenanceController extends Controller
{
public function index()
{
if(Auth::user()->email == 'manage@gmail.com') {
return view('maintenance');
}else{
return redirect('/home');
}
}
}
管理者以外は/homeにリダイレクトさせています。
まぁ、管理者は一人しかいないから、権限管理とかは使用せずに決め打ちでいいかと思います。
できれば一箇所変更すれば対応できるようにしたかったけど、うまい方法が見つからなかった。