https://github.com/takishita2nd/FarmGame
とりあえず、ボタンを配置してみた。
ボタンは下記リンク先のフリー素材を使用しています。
https://kopacurve.blog.fc2.com
ボタン+テキスト配置で作成したけど、たぶん、文字の入ったボタンを作った方が良いような気がする。
位置合わせがめんどくさい。
あ、そうそう、今回はAltseed2を使用しています。
.NEt Coreで動作するゲーム用ライブラリです。

https://github.com/takishita2nd/FarmGame

とりあえず、ボタンを配置してみた。
ボタンは下記リンク先のフリー素材を使用しています。
https://kopacurve.blog.fc2.com
ボタン+テキスト配置で作成したけど、たぶん、文字の入ったボタンを作った方が良いような気がする。
位置合わせがめんどくさい。
あ、そうそう、今回はAltseed2を使用しています。
.NEt Coreで動作するゲーム用ライブラリです。

とりあえず、こんなものをサクッと考えてみた。

こんな感じで、着手開始します。

https://github.com/takishita2nd/cocos2d-x_sample
こんなかんじになりました。
今回はスクロール処理にもう少し手を加えて、
スクロールしすぎないようにストッパ処理を追加しました。
if(questListMenu.isShow)
{
// Yの差分だけメニューを動かす
float divY = touch->getLocation().y - keepPosition.y;
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
if(questListMenu.questListMenu[0].parts.point.y - questListMenu.questListMenu[0].parts.size.height + divY < visibleSize.height)
{
for(int i = 0; i < QUEST_NUM; i++)
{
questListMenu.questListMenu[i].parts.point = Vec2(questListMenu.parts.point.x, questListMenu.parts.point.y - questListMenu.questListMenu->parts.sprite->getContentSize().height * (i + 1));
questListMenu.questListMenu[i].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point);
questListMenu.questListMenu[i].questName.point = Vec2(questListMenu.questListMenu[i].parts.point.x + questListMenu.questListMenu[i].parts.size.width / 30.0, questListMenu.questListMenu[i].parts.point.y);
questListMenu.questListMenu[i].questName.label->setPosition(questListMenu.questListMenu[i].questName.point);
}
}
else if(questListMenu.questListMenu[QUEST_NUM - 1].parts.point.y + divY >= origin.y)
{
int listcount = 0;
for(int i = QUEST_NUM - 1; i <= 0; i--)
{
questListMenu.questListMenu[QUEST_NUM - 1].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point.x, questListMenu.questListMenu[0].parts.point.y * listcount + origin.y);
questListMenu.questListMenu[QUEST_NUM - 1].parts.point.y = questListMenu.questListMenu[0].parts.point.y * listcount + origin.y;
questListMenu.questListMenu[QUEST_NUM - 1].questName.label->setPosition(questListMenu.questListMenu[i].questName.point.x, questListMenu.questListMenu[0].parts.point.y * listcount + origin.y);
questListMenu.questListMenu[QUEST_NUM - 1].questName.point.y = questListMenu.questListMenu[0].parts.point.y * listcount + origin.y;
listcount++;
}
}
else
{
for(int i = 0; i < QUEST_NUM; i++)
{
questListMenu.questListMenu[i].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point.x, questListMenu.questListMenu[i].parts.point.y + divY);
questListMenu.questListMenu[i].parts.point.y = questListMenu.questListMenu[i].parts.point.y + divY;
questListMenu.questListMenu[i].questName.label->setPosition(questListMenu.questListMenu[i].questName.point.x, questListMenu.questListMenu[i].questName.point.y + divY);
questListMenu.questListMenu[i].questName.point.y = questListMenu.questListMenu[i].questName.point.y + divY;
}
}
keepPosition.y += divY;
}
考え方としては、
リストの先頭が画面上部のサイズから下に移動しそうなときは初期位置に設定する、
リストの最後が画面の下から上にいs¥どうしそうなときは下からリストを並べる、
といった感じです。
じゃあ、次はタップで選択処理だな。

Chain of Responsibilityのサンプルコードです。
package org.example.chainofresponsibility;
public class Question {
public int level;
public Question(int level){
this.level = level;
}
}
package org.example.chainofresponsibility;
public abstract class Handler {
private Handler next;
public Handler setNext(Handler next)
{
this.next = next;
return next;
}
public final void request(Question question)
{
if(judge(question)) {
// 処理完了
} else if(next != null) {
next.request(question);
} else {
// 処理不可能
}
}
protected abstract boolean judge(Question question);
}
package org.example.chainofresponsibility;
public class ConcreteHandler1 extends Handler {
@Override
protected boolean judge(Question question) {
if(question.level <= 1) {
return true;
} else {
return false;
}
}
}
package org.example.chainofresponsibility;
public class ConcreteHandler2 extends Handler{
@Override
protected boolean judge(Question question) {
if(question.level <= 2) {
return true;
} else {
return false;
}
}
}
package org.example.chainofresponsibility;
public class Main {
public static void main(String[] args)
{
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
handler1.setNext(handler2);
Question question = new Question(1);
handler1.request(question);
}
}
Chain of Responsibilityは処理を行う人(Handler)を数珠つなぎに配列しておき、リクエスト(Question)に対して先頭のHandlerから処理が可能かどうかを判定(judge)し、処理できない物であれば後ろのHandlerにまる投げする、という仕組みです。
なので、あらかじめHandlerを継承しているConcreteHandlerを作成して数珠つなぎを作っておく必要があります。
ConcreteHandlerにてjudge=trueならば、渡されたquestionを適切に処理し、judge=falseならば、そのquestionを後ろのConcreteHanderに渡します。


ロードボタンは何も実装していないので、何も置きませんが、
新規ゲームを選択すると、フェードイン、フェードアウトがかかって前回まで作成した画面に遷移します。
class TitleScene : asd.Scene
{
private asd.Layer2D layer = null;
private asd.TextureObject2D _newgame = null;
private asd.TextureObject2D _load = null;
private asd.Texture2D newgame1Image = asd.Engine.Graphics.CreateTexture2D("newgame1.png");
private asd.Texture2D newgame2Image = asd.Engine.Graphics.CreateTexture2D("newgame2.png");
private asd.Texture2D load1Image = asd.Engine.Graphics.CreateTexture2D("load1.png");
private asd.Texture2D load2Image = asd.Engine.Graphics.CreateTexture2D("load2.png");
private const int buttonWidth = 330;
private const int buttonHeight = 80;
public TitleScene()
{
}
protected override void OnRegistered()
{
layer = new asd.Layer2D();
AddLayer(layer);
// 下地
var background = new asd.GeometryObject2D();
layer.AddObject(background);
var bgRect = new asd.RectangleShape();
bgRect.DrawingArea = new asd.RectF(0, 0, 1900, 1000);
background.Shape = bgRect;
// 北海道の背景
var hokkaido = new asd.TextureObject2D();
hokkaido.Texture = asd.Engine.Graphics.CreateTexture2D("101.png");
hokkaido.Scale = new asd.Vector2DF(1.5f, 1.5f);
layer.AddObject(hokkaido);
// タイトル
var title = new asd.TextureObject2D();
title.Texture = asd.Engine.Graphics.CreateTexture2D("title.png");
title.Position = new asd.Vector2DF(250, 200);
layer.AddObject(title);
// 新規ゲームボタン
_newgame = new asd.TextureObject2D();
_newgame.Texture = newgame1Image;
_newgame.Position = new asd.Vector2DF(150, 450);
layer.AddObject(_newgame);
// ロードボタン
_load = new asd.TextureObject2D();
_load.Texture = load1Image;
_load.Position = new asd.Vector2DF(500, 450);
layer.AddObject(_load);
}
protected override void OnUpdated()
{
asd.Vector2DF pos = asd.Engine.Mouse.Position;
if(isOnMouse(pos, _newgame))
{
_newgame.Texture = newgame2Image;
}
else
{
_newgame.Texture = newgame1Image;
}
if (isOnMouse(pos, _load))
{
_load.Texture = load2Image;
}
else
{
_load.Texture = load1Image;
}
if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
{
if (isOnMouse(pos, _newgame))
{
var scene = new MainScene();
asd.Engine.ChangeSceneWithTransition(scene, new asd.TransitionFade(1.5f, 1.5f));
}
}
}
private bool isOnMouse(asd.Vector2DF pos, asd.TextureObject2D button)
{
if (pos.X > button.Position.X && pos.X < button.Position.X + buttonWidth
&& pos.Y > button.Position.Y && pos.Y < button.Position.Y + buttonHeight)
{
return true;
}
return false;
}
}

どうやらLinuxにはオープンソースのOCRソフト
Tesseract-OCRというものがあるようです。
これを試してみます。
こちらの記事を参考にしました。
https://kitakantech.com/tesseract-basic/
インストールは、
$ sudo apt-get install tesseract-ocr
$ sudo apt-get install tesseract-ocr-jpn
1つ目はTesseract-OCRの本体、2つ目は言語対応モジュールです。
使用方法は、こんな感じ。
$ tesseract test.jpg output -l jpn
これに対して、


読み込ませてみました。


うーん、読み込んでほしい部分が読み込まれてない。
写真だからダメなのだろうか。
たぶんスキャナで読み取った画像ならうまく読み込めるのかもしれないが。
栄養成分表をスキャナで読み取るなんて手間はありえないので、
ちょっと、この機能の実装を続けるかどうかは微妙ですな。
こんな感じになりました。
今まではタッチしたときの処理のみを実装していましたが、
タッチ処理のイベントハンドラは三つありまして、
// タッチアニメーション
auto listener1 = EventListenerTouchOneByOne::create();
listener1->onTouchBegan = CC_CALLBACK_2(HomeScene::onTouchBegan, this);
listener1->onTouchMoved = CC_CALLBACK_2(HomeScene::onTouchMoved, this);
listener1->onTouchEnded = CC_CALLBACK_2(HomeScene::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);
onTouchBeganはタッチ開始の処理、
onTouchMovedはタッチ中に動かしたとき、
onTouchEndedは画面から指をリリースときに動作します。
まずは、タッチ処理とスライド処理を分ける必要があるので、
タッチ判定は、タッチ時とリリース時のポジションが同じパーツならばタッチと判定します。
なぜ変更する必要があるのかというと、タッチ開始時に判定しちゃうとスライドの判定ができないのでリリース時にタッチ判定をする必要があります。
これはとりあえず仮の処理です。後で変えるかもしれません。
今回重要なのはスクロール処理。
タッチ開始時にその位置を記憶しておきます。
if(questListMenu.isShow)
{
keepPosition = touch->getLocation();
}
そして、指移動処理で、移動前と移動後の位置の差分をとり、その分だけパーツを移動させます。
void HomeScene::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *event)
{
log("move(%f, %f)", touch->getLocation().x, touch->getLocation().y);
if(questListMenu.isShow)
{
// Yの差分だけメニューを動かす
float divY = touch->getLocation().y - keepPosition.y;
for(int i = 0; i < QUEST_NUM; i++)
{
questListMenu.questListMenu[i].parts.sprite->setPosition(questListMenu.questListMenu[i].parts.point.x, questListMenu.questListMenu[i].parts.point.y + divY);
questListMenu.questListMenu[i].parts.point.y = questListMenu.questListMenu[i].parts.point.y + divY;
questListMenu.questListMenu[i].questName.label->setPosition(questListMenu.questListMenu[i].questName.point.x, questListMenu.questListMenu[i].questName.point.y + divY);
questListMenu.questListMenu[i].questName.point.y = questListMenu.questListMenu[i].questName.point.y + divY;
}
keepPosition.y += divY;
}
}
思った以上に滑らかなスクロールができました。
「これ以上スクロールさせない」といった処理も必要になりますが、
まぁ、今回はこれで良いでしょう。

Proxyパターンのサンプルコードです。
package org.example.proxy;
public interface Subject {
void request1();
void request2();
}
package org.example.proxy;
public class RealSubject implements Subject {
@Override
public void request1() {
// 時間のかかる処理
}
@Override
public void request2() {
// 時間のかかる処理
}
}
package org.example.proxy;
public class Proxy implements Subject{
private RealSubject real;
@Override
public synchronized void request1() {
realize();
real.request1();
}
@Override
public synchronized void request2() {
realize();
real.request2();
}
private void realize() {
if(real == null) {
real = new RealSubject();
}
}
}
package org.example.proxy;
public class Main {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request1();
subject.request2();
}
}
Proxyパターンは時間のかかる処理を実行する際、クラスを一つ噛ませて一部の処理を代行(Proxy)するパターンです。
上のサンプルコードではRealSubjectが時間のかかる処理を行い、それを実行する前にProxyが実行されます。
ProxyとReadSubjectはSubjectという共通のインターフェースを持っています。
この例では、「直接ReadSubjectを実行してもいいんじゃね?」って思いますが、
実際に利用されるパターンとしては、ReadSubjectでは時間のかかる処理のみを実装し、それ以外の処理をProxyで処理させる、というのが一般的のようです。


都市データの入力が終わりまして、
画面に都市の情報を表示させてみたいと思います。
今までの実装では

こんな感じになってました。
テキストだけを表示させている、と言う状態です。
これでは見づらいので、表示ウィンドウを作りたいと思います。
class InfomationWindow
{
private TextObject2D _valueText;
private GeometryObject2D _windowBox;
private GeometryObject2D[] _geometryObj = new GeometryObject2D[4];
private RectangleShape _rect;
private LineShape[] _line = new LineShape[4];
private const int rectWidth = 250;
private const int rectHeight = 70;
private const int xPositionOffset = 10;
public InfomationWindow()
{
}
public void AddLayer(Layer2D layer)
{
_windowBox = new GeometryObject2D();
_windowBox.DrawingPriority = 10;
_windowBox.Color = new Color(255, 255, 255, 255);
_rect = new RectangleShape();
layer.AddObject(_windowBox);
for(int i = 0; i < 4; i++)
{
_geometryObj[i] = new GeometryObject2D();
_geometryObj[i].DrawingPriority = 10;
_geometryObj[i].Color = new Color(0, 0, 0, 255);
_line[i] = new LineShape();
_line[i].Thickness = 5;
_geometryObj[i].Shape = _line[i];
layer.AddObject(_geometryObj[i]);
}
_valueText = new TextObject2D();
_valueText.Font = Singleton.Font;
_valueText.DrawingPriority = 20;
layer.AddObject(_valueText);
}
public void ShowText(Vector2DF pos, string text)
{
_rect.DrawingArea = new RectF(pos.X, pos.Y, rectWidth, rectHeight);
_windowBox.Shape = _rect;
_line[0].StartingPosition = new Vector2DF(pos.X, pos.Y);
_line[0].EndingPosition = new Vector2DF(pos.X + rectWidth, pos.Y);
_line[1].StartingPosition = new Vector2DF(pos.X + rectWidth, pos.Y);
_line[1].EndingPosition = new Vector2DF(pos.X + rectWidth, pos.Y + rectHeight);
_line[2].StartingPosition = new Vector2DF(pos.X + rectWidth, pos.Y + rectHeight);
_line[2].EndingPosition = new Vector2DF(pos.X, pos.Y + rectHeight);
_line[3].StartingPosition = new Vector2DF(pos.X, pos.Y + rectHeight);
_line[3].EndingPosition = new Vector2DF(pos.X, pos.Y);
_valueText.Text = text;
_valueText.Position = new Vector2DF(pos.X + xPositionOffset, pos.Y);
}
public void AppendText(Vector2DF pos, string text)
{
_valueText.Text += text;
_valueText.Position = new Vector2DF(pos.X + xPositionOffset, pos.Y);
}
}

画面に白い四角形を描画し、それに追加して、四辺に線を書きました。
あ、そうだ、タイトル画面作らないと。
次回はタイトル画面作ります。

あれからAlexaからWebAPIを実行する方法を探ってみたのですが、
結論から言うと、今のままでは無理っぽいです。
理由は、WebAPIを実行するためには、インターネットからHTTPSでアクセスできることが条件と言うことです。

今の環境下では、テレビの赤外線を使用しているラズパイはローカルネットワーク環境下でのみ実行可能で、インターネットからアクセスしようにも、プロバイダー(ビッグローブ)側で自宅IPへの直アクセスもブロックされているっぽいのです。
外からアクセスする手段が無い以上、どうすることもできません。
Arduinoだとこんな感じで使用できるみたいです。
https://dev.classmethod.jp/articles/smart-home-skill/
なので、Alexaスキル開発は一旦中断。
ただ、テレビの赤外線通信はこのままではもったいないので、別の活用法を考えてみたいと思います。
リモコンが無くても、PCやスマホからコントロールできるようにするとか。
ちょっとそっちの方で進めてみましょうか。
他にやることないし。
