Candy Crash FriendsのUI
Candy Cransh Friendsというゲームをやっていて、ゲームに失敗すると、出現する画面で、自分では、ギブアップを選択しようとしたが、間違えて、アイテムの金塊を10個支払って、続けるボタンを押してしまった。一度、押してしまうと、キャンセルができない。
このゲームで、金塊というアイテムは、初期の段階で、進めると、無料で50個もらうかどうか選択できるものだ。
そこで、よく見てみると、続けるボタンをギブアップボタンよりも大きいサイズにしていて、かつ、視線の動きを上から下へ動かす段階で、どうしても目に入るように配置していて、さらに、画像だと分からないけれども、アニメーションで、続けるボタン→ギブアップボタンの順番で出現するようになっていて、間違えて押しやすくなっている。
ゲーム上では、金塊が減ると、再び入手するためには、お金を出して、買うしか手段がない。そして、金塊がちょっと減ってしまうと、(最初に与えられたものを取られると)精神的に気持ちが悪くて、初期の満タンの数である50個まで貯めたくなる。そこを狙っている。
他にも、ライフがゼロになった状態で出現する画面を見てみると、
ライフを追加するボタンが大きく表示されていて、ステージへ戻るための×ボタンを押す過程で、視線が、どうしても、ライフを追加するボタンに目がいってしまうし、それだけじゃなくて、片方は、「ライフを追加する」と、文字で表現している一方で、ステージへ戻る方のボタンは×の絵で表示されているだけで、この違いも、ライフを追加するボタンをクリックする方へ誘導している気がする。
何となく、そっちのほうが、それが何なのかを認知をするための脳への負荷が小さいので、クリックしてしまうのを狙ってるんじゃないか?
最後に紹介したい事例として、このゲームでは、1ステージクリアするたびに、金塊が溜まっていくが、最大30個までしか溜まらず、それ以上は、捨てられるアニメーションを毎回見せられる。これによって、金塊を取り出さなければ、もったいないという心理を植えつけようとする。
しかし、このゲームでは、金塊を取り出すには、課金しなければならない。
ステージ画面の右上の金塊ボタンをクリックすると、下は230円から、上は、11700円までの金塊を含めた課金アイテムがずらりと並び、いつでも買えるようになっている。
他の仕組みとして、ゲーム内では、全プレイヤーの総合ランキングみたいなものは表示されていない。これによって、プレイヤーのやる気が削がれないような仕組みにしているのかもしれない。
ただ、ステージ別のランキングはある。でも、そのランキングは、そのステージの歴代ランキングなのか、それとも、そのステージをクリアした、直近、10名のランキングなのかは不明。ランキングにはメリット/デメリットの両方があって、デメリット(自分と相手の差がありすぎたり、上位の顔ぶれが変わらなくて、やる気が削がれる)を減らして、メリット(競争心を煽り、やる気を出す)を増やす仕組みになっている気がします。
今まで紹介してきたように、Candy Crashのゲーム運営者はいかに、金塊をユーザーに買わせようとしているかという事例でした。
CakePHP3でルーティングを行う
開発環境は、Windows 10 Pro(64bit) + PHP 7.2.12 + CakePHP 3.7.2 + XAMPP Control Panel v3.2.2。
今回は具体的な事例として、トップページを、PostsController内のindexアクションにしたい場合の書き方を説明。
1. /config/routes.phpを開く
2.以下の部分を変更する
<?php $routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']); ?>
を
<?php $routes->connect('/', ['controller' => 'Posts', 'action' => 'index']); ?>
に変更する。
また、ユーザーが、/posts/indexや、/postsにアクセスしてきたときもトップページに飛ぶように設定しておくには、
<?php $routes->redirect('/posts/', '/',['persist' => true]); ?>
を、config/routes.php内のRouter::scope('/', function (RouteBuilder $routes) { ~ });ブロック内に追加する。
名前付きルーティング
名前付きルーティングとは、その名前のコントローラを作っていなくても、好きな名前をURLに設定することができるものです。Togetterの個別記事ページのようなルーティングを実現したい場合(/li/12345678のようなTogetterの個別記事ページのようなルーティングを書く場合)で、それを、PostsControllerのviewアクションで実現したい場合は、
<?php $routes->connect('/li',['controller' => 'Posts', 'action' => 'view'],['_name' => 'li']); $routes->connect('/li/:id',['controller' => 'Posts', 'action' => 'view']) ->setPatterns(['id' => '\d+']) ->setPass(['id']); ?>
を、config/routes.php内のRouter::scope('/', function (RouteBuilder $routes) { ~ });ブロック内に追加する。
逆に、/posts/view/234のようなアクセスをリダイレクトするためには、
<?php $routes->redirect('/posts/view/:id', ['controller' => 'Posts', 'action' => 'view'], ['persist' => true]) ->setPatterns(['id' => '\d+']) ->setPass(['id']); ?>
を、config/routes.php内のRouter::scope('/', function (RouteBuilder $routes) { ~ });ブロック内に追加する。
※設定を変更したのに、それが反映されない場合の解決法
この部分で、かなり時間を食ってしまったのでメモ。
解決法としては、
1.ブラウザ側のキャッシュを削除する
2.CakePHP側のキャッシュを削除する
/tmp/cache/myapp_cake_routes_route_collection
を削除する。(削除しても、変更すると、新しく生成されるので大丈夫)
自分のケースでは、1.をしても変わりはなくて、2.をしたら変更が反映されました。できれば、両方やっておいたほうがいいでしょう。
■参考リンク
https://book.cakephp.org/3.0/ja/development/routing.html
CakePHP3でルーティングを設定し、URLをカスタマイズする | (株)シャルーン
Windows10にBlenderを使う
開発環境は、Windows 10 Pro(64bit)。
1.公式サイトへアクセスし、画面をクリック
blender.org - Home of the Blender project - Free and Open 3D Creation Software
2.Download Blender 2.8 Beta(現時点)と書かれたボタンをクリック
3. 2.80 Beta Windows 64bit(現時点)をクリックし、zipファイルをダウンロード
4.blender-2.80-8996e26116f0-win64.zipを解凍するだけ
6.実行すると、Windows Defenderがストップする可能性があるので、詳細をクリックして、実行ボタンをクリックすると、Blenderが実行される。
7.中央のウィンドウのNextボタンをクリック
8.New FileのGeneralをクリック
おすすめリンク
Blender入門 - CGrad Project
PHPでarray_searchを使用する際の注意点
開発環境は、PHP 7.2.12。
条件文の中で使用する際に注意すべき部分
array_search関数は、配列の中に指定の値があれば、その値のキーを、指定の値がなければ、falseを返す関数なのですが、例えば、以下のコードのように、返り値としてインデックス番号の0が返る場合は、指定の要素があるのに、false扱いになってしまいます。
<?php $arr = ["test", "test2","test3"]; # false if( array_search("test", $arr) ){ echo "true"; }else{ echo "false"; } ?>
そこで、以下のようにコードを書き換えておきます。仮に、!== falseの部分を、!= falseとしてしまうと、同様に、falseと出力されてしまいます。
厳密な型の比較を確実に行い、意図しない動作を防ぐことが求められます。
<?php $arr = ["test", "test2","test3"]; # true if( array_search("test", $arr) !== false){ echo "true"; }else{ echo "false"; } ?>
条件文を書く際には、注意したい。
条件文内で変数を入れる際の注意点
array_searchを2回呼び出すのが処理的にどうなの?と思ったので、条件文内の変数に入れておこうと考えたが、想定した通りの動きにはならなかった。
<?php $arr = [1,2,3,4,5]; if($idx = array_search(3,$arr) !== false){ echo $idx; # 1が入る。 echo array_search(3,$arr); # 2が入る } ?>
文字列の値を用いてarray_searchを用いる際の注意点
文字列を値に指定する場合は、第三引数をtrueに指定しないと、きちんと動作しない。
<?php $arr = [0,1,2,3,4,"str",5,"key" => "value",6,7,8]; print_r( array_search("str",$arr) ); # 5ではなくて0が返ってくる print_r( array_search("str", $arr, true) ); # 5が返ってくる print_r( array_search("value",$arr) ); # keyではなく0が返ってくる print_r( array_search("value",$arr,true) ); # keyが返ってくる ?>
ImagicKを使って色々と画像を加工してみる
開発環境は、PHP 7.2.12 + ImageMagicK ImageMagick 7.0.7-11 Q16 x86 2017-11-23 + XAMPP Control Panel v3.2.2。
新しく画像を作成する
新しく画像を作成し、png画像のフォーマットとして、表示させます。
<?php $image = new Imagick(); $image->newImage(100, 100, new ImagickPixel('red')); $image->setImageFormat('png'); header('Content-type: image/png'); echo $image; ?>
実行結果
画像を保存する
<?php $image = new Imagick(); $image->newImage(100, 100, new ImagickPixel('red')); $image->setImageFormat('png'); $image->writeImage(realpath("test.png")); ?>
と書く。writeImageのファイル名は絶対パスで指定すること。
画像のサイズを変更する
以下のコードは、読み込んだ画像を、幅203px、高さ293pxに変換して表示している。
<?php $image = new Imagick(realpath('test.jpg')); $image->thumbnailImage(203,293); header("Content-type: image/jpeg"); echo $image; ?>
thumbnailImage関数の第1引数か、第2引数を0にすると、指定した幅 or 高さに応じて自動的に、0を指定した幅 or 高さを調節してくれる。もし、高さを知りたければ、Imagick::getImageHeight関数を用いると高さを取得できる。
<?php $image = new Imagick(realpath('test.jpg'); $image->thumbnailImage(220,0); # 例えば、高さは318pxに自動的になる header("Content-type: image/jpeg"); echo $image; ?>
参考リンク
http://php.net/manual/ja/imagick.thumbnailimage.php
http://php.net/manual/ja/imagick.getimageheight.php
http://php.net/manual/ja/imagick.getimagewidth.php
画像の品質を変更/取得する
スクリプトのある同一ディレクトリにtest.jpgがあったとして、画像の圧縮品質を取得します。品質が高いと画像のファイルサイズが大きくなり、逆に、低いとファイルサイズが小さくなります。
値は1~100のどれかになります。
<?php $img = new \Imagick(realpath("test.jpg")); echo "<b>getImageCompressionQuality: </b>".$img->getImageCompressionQuality(); ?>
次に画像の圧縮品質を変更してみたいと思います。
設定できる値は、1~100までです。jpeg画像だと、画像の見た目が変わりますが、png画像だと、画像の見た目は、全然変わりません。
<?php $img = new \Imagick(realpath("test.jpg")); $img->setImageCompressionQuality(100); header("Content-Type: image/jpeg"); echo $img; ?>
参考リンク
https://www.php.net/manual/ja/imagick.getimagecompressionquality.php
https://www.php.net/manual/ja/imagick.setimagecompressionquality.php
画像を一部切り取って抜き出す
今回は入力した画像から、画像の中心を基準にして、正方形に切り出すプログラムを書いてみたいと思います。
<?php $im = new Imagick(realpath('test.jpg')); $width = $im->getImageWidth(); $height = $im->getImageHeight(); $half_width = $width / 2; $half_height = $height / 2; if($width > $height){ # 縦幅の方が短い横長の画像 $upper_half_width = $half_width - $half_height; $im->cropImage($height,$height, $upper_half_width,0); }else if($width < $height){ # 横幅の方が短い縦長の画像 $upper_half_height = $half_height - $half_width; $im->cropImage($width,$width,0,$upper_half_height); }else{ # 最初から正方形の場合の画像 $im->cropImage($width,$height,0,0); } $im->setImageFormat('jpg'); header("Content-type: image/jpg"); echo $im;
2枚の画像を比較して差分を抽出する
下記の2枚の画像を使用します。
下記のプログラムでは、画像のパスは絶対パスで指定しないとエラーになる。
<?php $image1 = new imagick("C:/xampp/htdocs/image1.png"); $image2 = new imagick("C:/xampp/htdocs/image2.png"); $result = $image1->compareImages($image2, Imagick::METRIC_MEANSQUAREERROR); $result[0]->setImageFormat("png"); header("Content-Type: image/png"); echo $result[0]; ?>
実行結果
このように差分のみが赤く表示されていることが分かります。
サイトの中でデザインのどこが変更されたのか知るのにちょうどよいでしょう。
参考リンク
http://php.net/manual/ja/imagick.compareimages.php
Imagick::compareImages - ある画像を再構築された画像と比較する
画像のフォーマットを変換する
画像のフォーマットを変更するには、setImageFormat関数を用います。
例えば、以下のコードにて、読み込んだjpeg画像をpng画像に変換して表示します。
<?php $image = new Imagick(realpath('test.jpg')); $image->setImageFormat('png'); header("Content-Type: image/png"); echo $image; ?>
画像を半透明にする
自分の環境ではなぜか、setImageOpacity関数がなかったので代替方法を探して、あったので書いておきます。
<?php $image = new Imagick(); $image->newImage(300,300,new ImagickPixel('black')); $image->setImageFormat('png'); $image->transparentPaintImage(new ImagickPixel('black'), 0.8,10,false); header("Content-Type: image/png"); echo $image; ?>
実行結果
参考リンク
http://php.net/manual/ja/imagick.transparentpaintimage.php
画像を合成する
<?php $image = new Imagick(); $image->newImage(600,600, new ImagickPixel('#cccccc')); $image->setImageFormat('png'); $image2 = new Imagick(); $image2->newImage(300,300, new ImagickPixel('#ffffff')); $image2->setImageFormat('png'); $image->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT); $image->setImageArtifact('compose:args', "1,0,-0.5,0.5"); $image->compositeImage($image2, Imagick::COMPOSITE_MATHEMATICS, 0, 0); header('Content-Type: image/png'); echo $image; ?>
実行結果
上に被せる画像が透過PNGだと、setImageVirtualPixelMethod,setImageArtifactを除いて、表示させると、シルエットとして表示されます。
参考リンク
https://www.php.net/manual/ja/imagick.compositeimage.php
CakePHP3にてシェルとタスクを利用してコマンドラインからデータベースのデータを操作する
開発環境は、Windows 10 Pro(64bit) + PHP 7.2.12 + CakePHP 3.6.14 + XAMPP Control Panel v3.2.2。
基本的な操作
以下のプログラムをHelloShell.phpの名前でsrc/Shellディレクトリ内に保存する。
<?php namespace App\Shell; use Cake\Console\Shell; class HelloShell extends Shell { public function main() { $this->out(__('Hello world.')); } } ?>
コマンドラインのカレントディレクトリをアプリのルートディレクトリにしてから、
bin\cake hello
と入力すると、Hello world.と表示されます。
main()関数の中に処理を書いておくと、
bin\cake hello main
のように書かなくても、省略して実行できます。なので、main()関数以外の名前で、関数を使いたければ、例えば、hoge()と書いて、その中の処理を書いて、実行する際は、
bin\cake hello hoge
と入力すればOKです。
データベースへアクセスして操作してみる
1.テストのために以下のテーブルを作成します。
create table samples( id int auto_increment primary key, data varchar(255) not null, created datetime, modified datetime )charset=utf8mb4;
2.カレントディレクトリをアプリのルートディレクトリにして以下のコマンドを入力して、作成したテーブルに関係するファイルを自動的に作成しておきます。
bin\cake bake all samples
3.上記のHelloShell.phpを以下のソースコードに置き換えてください
<?php namespace App\Shell; use Cake\Console\Shell; class HelloShell extends Shell { public $tasks = ['Template']; public function main($taskname = '') { if($taskname === 'add'){ $this->Template->main(); }else if($taskname === 'delete'){ $this->Template->delete(); }else{ $this->out(__('No Task.')); } } } ?>
関数に引数を書くとコマンドラインから入力された値を受け取ることができます。
4. .src/Shellディレクトリ内にTaskディレクトリを作成します。
5.次に、Taskディレクトリ内に、TemplateTask.phpの名前で以下のコードを保存します。
<?php namespace App\Shell\Task; use Cake\Console\Shell; use Cake\I18n\Time; use Cake\ORM\TableRegistry; class TemplateTask extends Shell { public function initialize(){ date_default_timezone_set('Asia/Tokyo'); } public function main() { $samples = TableRegistry::getTableLocator()->get('Samples'); $query = $samples->query(); $query->insert(['data','created','modified']) ->values([ 'data' => '投稿しました', 'created' => Time::now(), 'modified' => Time::now() ]) ->execute(); $this->out(__('Insert Data.')); } public function delete(){ $samples = TableRegistry::getTableLocator()->get('Samples'); $query = $samples->query(); $query->delete() ->where(function($exp, $q){ return $exp->lte("created", new Time('1 hours ago') ); }) ->execute(); $this->out(__('Delete Data.')); } } ?>
6.カレントディレクトリをアプリのルートディレクトリにして、
以下のコマンドを実行すると、データが追加されます。
bin\cake hello add
以下のコマンドを実行すると、データを入力して1時間経過したデータを削除します。
bin\cake hello delete
それ以外に、単に、
bin\cake hello
と入力したり、hello以降に適当な値を入力しても、
bin\cake hello hoge
No Task.というメッセージが表示されるだけになります。
・Authコンポーネントを利用して認証機能を実装して、ログインしないと、データを追加や更新や削除できないようにしても、シェル経由で、データを追加や更新や削除することができるみたいです。
■本番環境ではどうなのか?(ロリポップの場合)
レンタルサーバーのロリポップのスタンダードプランでやってみたところ、CakePHPは普通に動作できるが、cronを使用したり、SSLでアクセスして、手動で実行させようと、bin/cake.phpを動作させると、PHPのバージョンはPHP 5.5.35 (cli) で、CakePHPを使用するには、5.6.0以上にしろと表示され、実行できない。
そこで、推奨できないが、緊急策として、CakePHPを介さずに、PHPでデータベースに直接アクセスして、データを追加するプログラムを作ってみた。このプログラムを実行するたびに、samplesテーブルにデータが追加される。
<?php date_default_timezone_set('Asia/Tokyo'); $now = date("Y/m/d H:i:s"); $url = ""; $user = ""; $password = ""; $database = ""; $connect = mysql_connect($url,$user,$password) or die("can't connect"); $db = mysql_select_db($database, $connect) or die("can't Select database"); $sql = "INSERT INTO samples (data,created,modified) VALUES ('test', '$now', '$now');"; $result = mysql_query($sql,$connect); echo $result; ?>
ただ、
上記のプログラムを検証してみると、SSLでログインして、php 絶対パスor 相対パスでのphpのプログラム名では、うまく動作するが、cronで、そのプログラムを指定すると、うまく動作しなかった。原因は分からないが、検証したところ、どうも、mysql_connect部分がおかしいみたい。
そこで、以下のようなPDOを利用したプログラムに切り替えると、cronでもきちんと動作した。
<?php date_default_timezone_set('Asia/Tokyo'); $now = date("Y/m/d H:i:s"); $url = ""; $user = ""; $password = ""; $database = ""; $dbh = new PDO("mysql:dbname=$database;host=$url",$username,$password); $count = $dbh->exec("INSERT INTO samples (data,created,modified) VALUES ('test', '$now', '$now')"); echo $count; ?>
参考リンク
シェル - 3.7
日付と時刻 - 3.7
データベースアクセス & ORM - 3.7
クエリービルダー - 3.7
PHPでデータベースに接続するときのまとめ - Qiita
データの追加(INSERT文) - データの追加と削除 - MySQLの使い方
PHPでmysql_connect関数を使いMySQLへ接続するサンプルです。 · GitHub
PHP: PDO - Manual
データの削除(DELETE文) - データの追加と削除 - MySQLの使い方