あおみかんのブログ

フリーランスのIT系エンジニア。つくば在住。

「どうぶつの森 ポケットキャンプ」は何故つまらないのか

このエントリを読む前に!

(11/23 22:32 追記)

この記事は、あくまでプレイした上で楽しめなかった人が納得するためのものです!

普通に楽しめてるって人は、こんな記事は気にせず楽しむ方が良いと思います。まわれ右!

(11/23 22:32 追記終わり)

以下、本文です

とうとう出ましたね、期待のタイトル「どうぶつの森 ポケットキャンプ」!

21日の夕方に配信開始されてから、速攻でDLしてプレイしてみたんですが、正直言って期待外れでした。

僕も小規模ながらゲームを作っている身として、なぜこのアプリがゲームとしてつまらないのか、自分なりに分析してみたので簡単に文章にまとめてみます。

どうぶつの森は何故神ゲーなのか

アプリ版のどうぶつの森ポケットキャンプ(以降「ポケ森」と表記)がつまらないという議論を頭ごなしにせず、まずは元々のシリーズタイトル「どうぶつの森」がいかに面白いゲームだったかを分析します。

ここでは、僕の記憶に根強い64版初代とGC版の2タイトルを元に書きますが、基本的には3DS版「とび森」まで一貫していたと思います。(※僕はDS版を除く全タイトルプレイしました)

自由度と目標設定

ゲームのデザインにおいて、目標を設定しそこに向かってプレイさせることはひとつの重要な要素と言われています。

ドラゴンクエストなら「りゅうおうを倒す」ことだし、マリオなら「ピーチ姫を助ける(ためにクッパを倒す)」ことですね。

スプラトゥーンなら「強くなってイカすイカになる」って所でしょうか。上記に比べると、少しユルめですね。

そんな中、どうぶつの森の冒頭からのゲームプレイの流れはこんな感じです。

  1. どうぶつたちが住む村に電車でやってきた主人公は *1
  2. たぬきち」に言われるがままに家のローンを組まされて
  3. 軽いバイトで稼ぎながら、どうぶつたちと知り合い
  4. 家を大きくしつつ村のどうぶつたちと仲良くなりつつ
  5. 暮らしていく

この中で、目標が設定されているのは、せいぜいたぬきちに関するくだりくらいのものです。 *2

結局、どうぶつの森というのは「好きに(のんびり)暮らす」というゲームであり、もはや旧来的な意味では「ゲーム」と言って良いのか不明ですが、とにかくあの村は居心地の良い「場所」でした。

徹底した「自由」

どうぶつの森の自由は徹底的でした。

たぬきちに負わされた借金を返す方法は、僕がいまパッと思い出せるだけでも以下のものがあります。

  • バイトでどうぶつたちに家具とかを配達する
  • (バイト以外でも)どうぶつのお願いを叶える(と何かがもらえるので売ったりする)
  • ハニワや化石を掘って売る
  • 果物を取って売る
    • 現実のともだちやどうぶつたちから特産品以外のくだものを貰って埋めて増やす果樹園スタイル(500ベルで売れる)
  • 釣った魚を売る
  • 虫を捕まえて売る
    • アクションが上手いならハチも捕まえれる!
  • あいことば手紙 *3
  • 兄弟など家族から何かを貰う *4

まだ2,3個くらいは手段がありそうですが思い出せないのでやめます。

そして、たぬきちの借金については返さないという選択肢すら、どうぶつの森では許されているんです。

借金を返さなくても村の時間は進んでいくし、ゲストのじゅうたん屋やボッタクリ家具やうらない屋は訪れます。たぬきちが怒ってやってくるような事もありませんし、普通に商店では家具も道具も売ってくれます。*5

おくゆかしいリワードたち

目標なくゲームプレイをして何が楽しいのか。という事は気になるポイントだと思います。

その1つが、ある種の「リワード」であり、それらが露骨でなく非常におくゆかしかった事にあると思います。これも箇条書きします。

  • どうぶつが素直に喜んでくれる
  • 村が発展していく
  • 金の道具が手に入る
  • 岩を叩くとたまにお金が手に入る
  • 埋まってるものを掘ると高価な化石が手に入る(ことがある)
  • 空を飛んでるプレゼントを追いかけるとたまに木にひっかかって、木を揺すると手に入る

これら、充実したリワードのどれもが、明示的に表になっていて目標を管理されるようなことはなく、自然とゲームプレイしているうちに達成されるのがポイントです。

これらのリワードはゲームプレイの目的や目標ではないのです。プレイヤーは、意識することもありますが、これらのリワードだけを追い求めてプレイするのではなく、あくまでベースは「好きに(のんびり)暮らす」ことです。

長所をことごとく破壊したポケ森

ここまでの文章を読んで、ポケ森をプレイした人なら、もう僕が言いたいことは分かると思います。

ポケ森は、これらの長所を完全に破壊してしまっています。ざっと書けば十分でしょう。

  • 明示的なゲーム目標(左上に常に出てくるレベルの概念、可視化されたどうぶつの好感度、リストアップされたタスク類、etc…)
  • しずえによって徹底的に管理されるTODOとリワード *6

やんわりとした目標の中で、小さな目標や目的を自分で見つけていくゲームだったオリジナルシリーズと比べて、ポケ森は日々やることを徹底的に明示化することで、ゲーム体験の自由度を圧倒的に下げてしまいました。

スマホゲームユーザーは文章をあまり読まないとか、目標を明確にしないとプレイしてくれないとか、色々と彼らがなぜこのデザインにしたか、推察することも出来ます。

でも、つまんないもんはつまんないし、俺はこのスマホアプリは、期待外れの駄作だと思う!

おわりに

つまらないゲームはつまらないから儲からない、面白いゲームは面白いから儲かる、そういうシンプルな世界に戻って欲しい。

僕は任天堂の作るゲームが好きだし、どうぶつの森はその中でも思い入れの強いタイトルです。それだけに、今作は認められない。

もしポケ森の収益が良いものでも、任天堂がそっちに舵を切らないことを願います。

*1:僕はみしらぬネコが好きですが最近のタイトルでは冷遇されてますね

*2:余談ですが、本来「どうぶつの森」シリーズでは「みしらぬネコ」が「ここでは何しててもいいし、のんびり暮らそう」的な事を言うのが、最重要な目標設定だと僕は考えています。DSかWiiのあたりでみしらぬネコを削った時点から、徐々にゲームシーケンスは壊れていったというのが僕の考えです

*3:64における手紙のあいことば解析は、ほぼチートでしたが、ある種の面白さがあった

*4:ひとりよりふたり

*5:返せば商店が繁盛して取扱物品が増えるけど、それはそれ

*6:しずえのキャラデザは好きですが、ポケ森での彼女の役割はことごとく、どうぶつの森を悪い意味でソシャゲ化する場面に使われていて残念です

Laravel(5.4)で、バリデーションエラーをフォームにリッチに表示したい時のサンプル(laravelcollective/Forms&HTML使ってる場合)

今日ふとした案件でLaravel触ってみたのですが、Railsのsimple_formにあるような、モデルのバリデーションでコケてる場合にinput要素のクラスにhas-error を足す良い方法が見当たらなかったので、ざっと作ってみました。

// app/Helpers/FormHelper.php
<?php
namespace App\Helpers;

class FormHelper
{
    public static function text($name, $errors, $options=[])
    {
        if( empty($options['class']) ){
            $options['class'] = '';
        }
        if( ! empty($errors->first($name)) ){
            $options['class'] .= ' has-error';
        }
        return \Collective\Html\FormFacade::text($name, null, $options);
    }
}

まず、これが今回のメイン。オプションでclass渡してないときに先頭に半角スペース入るけど、別に害がないのでこのままで良いかなと思ってる。

text以外にも使いたいものがあれば適宜勝手に定義すると良いです。 (PHPメタプログラミングについて調べるほどの情熱はなかった)

次に、このクラスにalias貼る。

// config/app.php
// 'aliases' => の後に以下をそれっぽく追加
[
  'FormHelper' => App\Helpers\FormHelper::class,
]

ここまで来たら、あとはblade viewの中から使うだけ。

// form.blade.php
// こんな風に使う
        {!! Form::model($inquiry, ['route' => ['inquiry.preview'], 'method' => 'put']) !!}
            <div class="form-group">
                {!! Form::label('title', 'Title') !!}
                {!! FormHelper::text('title', $errors, ['class' => 'form-control']) !!}
            </div>
            <div class='form-group'>
                {!! Form::submit('プレビュー', ['class' => 'btn btn-primary form-control']) !!}
            </div>
        {!! Form::close() !!}

ちなみに当然だけどコントローラ側で ['inquiry' => new Inquiry] っぽいことする必要はある。

モデル必要なければ Form::model じゃなく Form::open 使えば良いだけ。

明示的に $errors を渡してるのがダサい。 $errors の取得については、もっと賢い方法があるかもしれません。 Laravelに詳しい人いたらコメントくれると嬉しいです。

以下、参考にしたリファレンスなど:

docker公式イメージのタグのリストを取得する

元ネタの記事は以下のものです。

DockerHubのイメージのタグ一覧をコマンドで取得する | Mazn.net

本記事は、ちょっとだけ説明を足したり自分の環境向けにコマンドを弄ったメモです。

yumみたいに簡単なコマンドがないか調べたんだけど公式の方法は用意されてないみたいなので以下のようにする。

僕はMacOSX上のzshで実行してるけど、まぁマトモなシェルが動く環境なら動くんじゃないかな。

curl -s https://registry.hub.docker.com/v1/repositories/php/tags | json_pp | grep name | grep 5.6.30

この例では、公式のphpイメージ https://hub.docker.com/_/php/ のタグをリストアップして、その中で名前に5.6.30を含むものを表示している。

phpの部分に別なパッケージ名を入れれば別なパッケージ向けで動くみたい。

json_pp は入れとくと便利なので入れておくと良い。

meta_tags を使ってRailsアプリを楽にOGP対応する時のサンプル

表題の通りの事をやりたいケースって多いと思うんですが、いまいち分かりやすいサンプルが見当たらないので、ざっと書いとく。

やりたいことは、重複コードをなるべく減らして、楽にOGP対応すること。ついでに面倒なmetaタグのtitleとdescriptionも良い感じに設定すること。

前提

# Gemfile にて
gem 'meta_tags' 

で、bundle (install) しとく。

# application.html.slim とかのレイアウトファイルで以下。 _header.html.slim とかに分離してたらそっち。
ruby:
  og = {
    url: request.original_url,
    type: 'website',
    # こう書くとtitleメソッドで入れたのが使われるらしい
    title: :title,
    site_name: 'OGPの下に出るサイトの名前',
    # 画像は置き場所にあわせて適宜
    image: request.scheme + '://' + request.host + "/ogp.png",
    description: :description,
  }
head
  = display_meta_tags site: 'デフォルトのサイトの名前', description: 'でふぉるとの説明文', fb: { app_id: ENV['FACEBOOK_APP_ID'] }, og: og

ogを変数に入れてるのは単に可読性の都合です。

あとは、個別のページのviewで以下のようにする。

# show.htm.slim
ruby:
  # まぁ何か以下のような感じで使いたい変数を入れる。
  title @product.name
  description @product.description

  set_meta_tags og: {
    image: @product.image.url
  }

注意事項とか

こうすると、 (ブラウザのタイトルバーに出る) title 要素には "デフォルトのサイトの名前 - #{@product.name}" と出る。しかし、OGPのタイトルの部分には @product.name が出る。 ここはOGPとブラウザで役割違うと思うし、僕はこれで良いと思ってこうしてるんだけど、両者を併せたい場合はちょっと面倒。 以下のモンキーパッチが使えそう。

Open Graph og:site_name getting merged with title · Issue #119 · kpumuk/meta-tags · GitHub

Sunspot (RailsでSolrによる検索を動かすGem)をとりあえず日本語検索に対応する方法

日本語で割と最近っぽい情報が全然なかったので簡単にメモ。

Solr: 6.6.0 / 7.0.0

Sunspot: 2.2.7

<RAILS_APP>/solr/configsets/sunspot/conf/schema.xml の中で <fieldType name="text" から始まって </fieldType> で終わる部分を以下の内容に差し替える。

<fieldType name="text" class="solr.TextField" omitNorms="false" positionIncrementGap="100" autoGeneratePhraseQueries="false">
      <analyzer>
        <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/>
        <filter class="solr.JapaneseBaseFormFilterFactory"/>
        <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt" />
        <filter class="solr.CJKWidthFilterFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ja.txt" />
        <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>

stopwords_ja.txt 及び stoptags_ja.txt は solrをapache.orgから solr-7.0.0-src.tgz みたいなファイルをダウンロードしてきて 、その中にある solr-7.0.0/solr/core/src/test-files/solr/configsets/_default/conf/lang のうち _ja.txt で終わるファイルを <RAILS_DIR>/configsets/sunspot/conf/lang の中に入れれば良いみたい。 面倒ならとりあえずそれが必要な行を削っても、それなりには動く。

これをベースにtokenizerにuserDictionaryとか入れればユーザ辞書を使った検索とかも出来るっぽい。

今あまり時間がなくて雑なメモになってますが、もしもう少し丁寧に知りたい人がいたらTwitterのメンションとか、ここへのコメントくれれば追記します。

CloudFront配下にRailsを置いたときにSSL対応するMiddleware

最近CloudFront配下にアプリケーションサーバーを置くのが徐々に流行ってる気がします。

想定する構成としては、ブラウザが直接叩くのがCloudFrontで、その直下にELB、そのさらに下に直接pumaというような形式。 nginx使わないパターンですね。 assets以下はどのみちCloudFrontでキャッシュされるから、nginxで高速化するまでもないっていうのがこの設計の思想かなと思ってます。 pumaが重くならない程度に同時接続数を制御する役割もELBがになってくれますし。

さて、ともかくそんな構成にすると、SSL対応で手間取ったのでメモ。

バージョンは以下の想定です。

  • Rails 5.1.2 (4系でも変わらないとは思います)
  • Puma 3.7

困ること

何が困るかというと、CloudFrontでSSL→HTTPに変換して、ELB以降はずっとHTTPで取り回すと、X-Forwarded-Protoヘッダが渡ってこない事です。CloudFrontは何故か独自のCloudFront-Forwarded-Protoというヘッダを渡してきます。

これによって、 force_ssl! も動かないし、そもそも post,put,patchリクエストはCSRFをブロックする機構によって弾かれてしまいます。

フロントがELBだったら問題ないんですが、ここでは最前面にCoudFrontを置く想定…。

ここで対策は2つあります。

  1. ELBにもSSL鍵を指定して、CloudFrontからELBへの通信路をSSLにする
  2. AWS内しか通らない暗号化/解除の処理がムダでは?
  3. どうにかしてCloudFront-Forwarded-ProtoをRailsから認識させる。

ここでは後者を採用しました。

やったこと

以下のRack Middlewareを用意します。

# lib/cloud-front-header.rb
class CloudFrontHeader
  def initialize(app)
    @app = app 
  end 

  def call(env) 
    cf_proto = env['HTTP_CLOUDFRONT_FORWARDED_PROTO'].to_s
    if cf_proto && cf_proto.length > 0
      env['HTTP_X_FORWARDED_PROTO'] = cf_proto
    end

    @app.call(env)
  end 
end

そして、 application.rb に以下の内容を足します。 他にmiddleware使ってない場合は、どこに書いても問題ないと思います。

# config/application.rb
module MyApp
  class Application < Rails::Application
    # 前略
    require './lib/cloud-front-header.rb'
    config.middleware.use CloudFrontHeader
    # 後略
  end
end

と、強引な方法ではありますが、 CloudFront-Forwarded-Proto を勝手に X-Forwarded-Proto に上書きするという荒技でどうにかしたのでした。

Rack Middleware がバグるのは鬱陶しいので、to_s等を使った防衛的なコードになってますが、気に入らない人は適宜書き換えると良いです。

あと、置き場所を考え直すと require は要らないかも知れないです。 もし良い方法あったら誰かコメントとかで教えてください。

こういう事が出来るのはRackの優れたレイヤアーキテクチャのたまものですね。

Rails5で簡単にモバイル/PCのビュー分岐を行う

最近、仕事で必要だったので。 だいたい以下の記事を元にしてるだけですが、ちょいサンプルコードなど含めて解説。

stackoverflow.com

元々は mobylette というgemを使おうとしたらRails5では(たぶん4でも?)コケちゃうので自前実装気味に済ませたって経緯がある。

想定するサービス

基本はレスポンシブだけどTOPページ等の一部の画面だけPC/モバイルでの分岐をしているようなサービス

方法

まず、以下のconcernsを作る。

# app/controllers/concerns/respond_to_mobile_requests.rb

require 'active_support/concern'

module RespondToMobileRequests
  extend ActiveSupport::Concern

  # Regexp From: https://gist.github.com/dalethedeveloper/1503252/931cc8b613aaa930ef92a4027916e6687d07feac
  MOBILE_REGEXP = /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/  
  included do
    before_action :variant_mobile

    def variant_mobile
      # The solution from: https://stackoverflow.com/questions/39495834/mobile-view-in-rails-5
      request.variant = :mobile if is_mobile_request?
    end

    def is_mobile_request?
      @_is_mobile_request ||= MOBILE_REGEXP.match request.user_agent
    end
  end

end

次に、描画を振り分けたいコントローラおよびそこのアクションで以下のようにする

# app/controllers/home_controller.rb

class HomeController < ApplicationController
  include RespondToMobileRequests

  def index
    if is_mobile_request?
      # モバイルの時だけの処理
    else
      # PCの時だけの処理
    end
    
    # 以下のコードでビュー振り分け
    respond_to do |format|
      format.html.mobile
      format.html
    end
  end
end

あとはビューファイルとして index.html.erb (※hamlでもslimでもご自由に)と index.html+mobile.erb を作れば、勝手にビューを振り分けてくれる。

もしCloudFrontのカスタムヘッダ( CloudFront-Is-Mobile-Viewer 等)とかを使いたければ、request.user_agentにmatchかけてる部分の代わりに request.headers['CloudFront-Is-Mobile-Viewer'] 等を見ればいいと思う。