【Twitter】ランパの台詞botを作成してみる その2

というわけで、今回は「ランパの台詞botを作る」第2弾です。
前回の記事はコチラ!

【Twitter】ランパの台詞botを作成してみる その1

今回は台詞集から出力するテキストを抜き出し、ツイートするところまでやってみます!
ついでに作っている上で苦労していることも紹介します。

台詞の入力

とりあえず「オープニング~ステージ3-2」までと、その他回収できた台詞を書き出してみました!

20170116_02

見直してみると結構喋りますね。
ステージ3-2まででも3000文字近くあります。
ハマグリほどではありませんが、主人公が喋らないのでその影響もあるようです。
一方で全く喋らない(≒登場しない)ステージもそれなりにあるので、各ステージ満遍なく喋るというよりは、1ステージでの回数が多いといった印象です。

それにしても、3点リーダーが尋常でなく多いです。

DSC_0281

恐らく会話ウィンドウ的な都合があるのだと思いますが、それを差し置いても多いです。
実際にツイートさせる時は適度に削除しないと見にくくなってしまいます。

ゲーム自体の対象年齢故にひらがなも多いですが、簡単な漢字とカタカナは含まれているので、そこまで読みにくくはないと思います。
ただ句読点が一切ない上、上記の3点リーダーを削除してツイートする兼ね合いもあるため、適度に句読点は入れています。
それ以外は原文ママで問題ないと思われます。

twitterアイコンの作成

デフォルトのたまごアイコンでは味気ないので、アイコン用にランパを描いてみました。
ステージ選択のアイコンを元にしました。

20170116_rampa_icon1

うーん・・・また何とも言えない出来ですが、まあ及第点でしょう。
まあアイコンなんて小さいものなので、割と大雑把なくらいがちょうどよかったりします。

個人ページの背景は・・・後回しにします(ぁ
今の自分のイラスト力では描ける気がしないので・・・。

bot用PHPファイルの修正

前回ほぼコピペだったPHPファイルを修正します。
自分は手続き型っぽく書くのが嫌いなので、意味もなくクラスをインスタンス化するようにしました。
ツイートする部分は同じですが、ランパの台詞の抽出と整形部分を大きく変えています。

ツイートの際の理想の形式は「ランパの台詞 (改行)【発言した場面】」です。
また、台詞が複数行になることもあるので、その点も考慮する必要があります。

<?php

require_once("twitteroauth-master/autoload.php");
require_once('twitteroauth-master/src/TwitterOAuth.php');

use Abraham\TwitterOAuth\TwitterOAuth;

/**
 * ランパの台詞集から台詞を抽出・ツイートするクラス
 */
class TwitterRampaBot {

    private $customer_key;
    private $customer_secret;
    private $access_token;
    private $access_token_secret;

    const file_path = 'rampa_serifu.txt';
    const max_tweet_len = 140;

    /**
     * コンストラクタ
     */
    public function __construct() {
        $this->customer_key = "customer_key";
        $this->customer_secret = "customer_secret";
        $this->access_token = "access_token";
        $this->access_token_secret = "access_token_secret";

        mb_internal_encoding('UTF-8');
    }

    /**
     * つぶやきを送信する
     */
    public function tweet() {
        $message = $this->getTweetWord();

        // 文字数チェック
        if (self::max_tweet_len < mb_strlen($message)) {
            trigger_error('ツイート可能な文字数を超えています。', E_USER_ERROR);
            exit;
        }

        // サーバにつぶやきを送信
        $connection = new TwitterOAuth($this->customer_key, $this->customer_secret, $this->access_token, $this->access_token_secret);
        $connection->post("statuses/update", array("status" => $message));
    }

    /**
     * ツイート用の文字列を取得・整形する
     *
     * @return string
     */
    private function getTweetWord() {
        // テキストファイルから文字列読み込み
        $input_lines = file(self::file_path);
        $words = array();

        // 先頭 # の行を取得するまでループ
        while(true) {
            $line_num = rand(0, count($input_lines));

            // 先頭が # で始まる行を抽出
            if (preg_match('/^\# .*$/', $input_lines[$line_num])) {
                // # 以降を場面として取得
                $scene = $this->excludeLineFeed($input_lines[$line_num]);
                $scene = str_replace('# ', '', $scene);

                // 複数行の台詞を全て取得するまでループ
                for ($i = $line_num + 1; ; $i++) {
                    // 文字列か空or改行コードのみの場合は終了
                    if (empty($input_lines[$i]) || preg_match('/^' . PHP_EOL . '$/', $input_lines[$i])) {
                        break;
                    }

                    $words[] = $this->excludeLineFeed($input_lines[$i]);
                }
                break;
            }
        }

        // 台詞と発言場面を設定
        $message = '';
        foreach ($words as $word) {
            $message .= $word . PHP_EOL;
        }

        $message .= '【' . $scene . '】';

        return $message;
    }

    /**
     * 指定した文字列から改行コードを除外する
     *
     * @param string $str
     * @return string
     */
    private function excludeLineFeed($str) {
        return str_replace(PHP_EOL, '', $str);
    }
}

$twitterRampaBot = new TwitterRampaBot();
$twitterRampaBot->tweet();

exit;

?>

GitHub から落とした「TwitterOAuth」を使用していますが、バージョンによってやり方に差があるようです。
自分も他サイトのコピペでは全然動きませんでした。

あれこれ考えた結果、ファイルから取得した後、正規表現で取捨選択するのが最も手っ取り早かったです。
テキストファイルから抽出した文字列から、以下の条件で抽出します。
・# から始まる行を発言場面として抽出
・#以下から次の空白行 (実際は改行コード) までを台詞として抽出
・空白行は抽出しない

台詞集となるテキストファイルもそれに則って書いていきます。
サンプルとして、いくつか台詞を記載します。

# ステージ1-3
・・・どうしてこんなところに・・・
それに何でたたかっていたんだ・・・
うう・・・思い出そうとするとアタマがイタイ・・・
もしかして・・・ボクのきおく・・・

# ステージ1-4
あ・・・うん!!
スタフィーのボクをたすけたいって思う気もちが
ボクのへんしんのチカラ、『ドランパ』を思い出させてくれたんだ・・・

# ステージ2-2
あ、そうだ!ボクとチカラをあわせて下からゴォォォーってやってみる?
ドランパで、上ボタンをおしながらゴォォォーってできるよ
ためしてみて!

注意点として、Linux 上から動作させる関係上、改行コードは「LF」にしておく必要があります。
サクラエディタであれば保存時に指定できます。

かなり効率の悪い処理ですが、何度も実行するような処理ではないので、速度はあまり気にしません。
他にもいろいろ考えましたが、.ini は設定ファイルの趣が強く、このためだけに yaml や json のパーサを入れるのも嫌なので、とりあえずこの形でいってみようと思います!

苦労している点

プレイ日記に片足突っ込みつつ、製作している上で苦戦していることを紹介します。

データが消える

ある意味最大の壁です。
本体がダメなのか、ソフトが古いためか、プレイ中に接触不良→次回起動時にデータ消失ということがあります。
特にカートリッジを抜いておくと、次回起動時に差しても高確率で認識しません。

スロットからカートリッジを出さなければ順調なので、差しっぱなしにすることでカバーしていますが、当分の間3DSがスタフィー専用機となることを意味します。
最近3DSはほとんど起動していないので、あまり実害はないと思われますが、MHXX発売までには完成させたいところです。

ステージクリア後にオートセーブ

カートリッジ故にセーブは高速で、普通にプレイする分には問題ありません。
ただ、ランパの台詞を取り逃してしまったり、間違えていた場合、ステージをクリアするまでにリセットしないとアウトです。
ストーリーが進んでしまうと読み直すことができないので、漏れなくニューゲームとなります。
後半のステージでこれをやってしまうと悲惨なので、最も注意すべき人的エラーですね。

会話後に再度会話をすると内容が異なる

ステージの所々に会話イベントがありますが、会話後に再度キャラクターと会話すると、内容が異なる場合があります。
ゲーム的には素晴らしい作り込みですが、台詞をまとめる上では注意が必要です。

DSC_0283

しかも再会話時の台詞は長いものが多いです。
これも上記のオートセーブと同じく、気付かずにストーリーを進めてセーブされると、最初からやり直す羽目になりかねません。

まとめ

そんなわけで、ランパbotの作成日記その2でした!
まだ途中までしか作っていませんが、アカウントを作ってしまった上、完成がいつになるか分からないので、今のレパートリーで試運転してみようと思います。
cron で1日2回、12時間置きにツイートさせるように設定しています。

ひとまず問題なく稼働しているみたいです。
あとはランパの台詞を逃すことなく追加していけば完成すると思われます!

【Twitter】ランパの台詞botを作成してみる その1

【2017/01/16】
サンプルソースを修正しました。

というわけで、今回はタイトル通り、Twitterのbot作成に挑戦してみます!
作るのは伝説のスタフィーに登場するキャラクター「ランパ」の台詞集です!

ranpa31

上のような、宇宙服を着たウサギのようなキャラです。
(自分が描いたイラストなので実物とは異なります)
「ランパ星」というまんまな星の王子で、いろいろな動物に変身することができます。
「キャラクターとしての魅力は?」と言われれば・・・回答が難しいところですが、自分は「見た目」と「至って普通な性格」と返します。
主人公であるスタフィーとの変身形態の1つにアザラシがあるので、むしろそっちに惹かれたりしていましたが・・・。

DSC_0278

そんなランパの台詞集、誰も作っていなかったので、ネタとしてのポストは空いています。
前からbotを作ってみたかったのもあるので、どんな感じなのかやってみました!

Twitterアカウントの作成

まずはbot用のTwitterアカウントを作成します。
マイアカウントでログインしっぱなしなので一度ログアウトし、新しくアカウントを作成します。

新規アカウントのためにはメールアドレスが必要ですが、bot専用のアカウントを作るのも嫌なので、Gmailのエイリアス機能を使用します。
[アドレス]+[任意文字列]@gmail.com でメールを自動的に転送してくれるらしいです。
細かいことはぐぐればたくさんでるので割愛します。

shot2ss20161223224502738

「ランパ」とだけ打つと、歯の矯正手術とかそっち系のばかりヒットするため、頭にスタフィーと入れておきます。
ちなみにランパのスペルは「Rampa」らしいです。
海外版では「bunston」と全く別名だったりしますが、とりあえずランパで行きます!

shot2ss20161223224725777

電話番号認証の後、ユーザIDを入力します。
まあ日本の方ならアカウント名見れば分かると思うので、シンプルにいきます。

あとはおすすめされる通知設定やフォローを全て拒否して完了です。

「Twitter Developers」でアプリケーションの作成

Twitter連携用のアプリケーションを作成します。

https://dev.twitter.com/

サイト上の「My Apps」をクリックし、次画面の右上からログインしましょう。

shot2ss20161223231237770

アプリ名、アプリの詳細、サイトURLを入力します。
割と適当でも大丈夫です。
サイトを持っていない場合は適当なURL(example.comとか)でも良いらしいです。

作成時に電話番号認証が行われていない場合、エラーとなって先に進めません。
しかも電話番号はアカウント間で共有することが出来ないそうです。
面倒ですが、メインアカウントの電話番号で一時的に認証してしまいました。

完了後にアプリケーションの詳細画面が表示されるので、「Application Settings」のあたりを撮っておきます。
上タブの「Keys and Access Tokens」の情報と、画面下の「Create My Access Token」クリック後の情報もメモしておきます。

実行用PHPファイルの作成と自動実行設定

GitHub上で公開されている「TwitterOauth」というPHPで作るのが一般的らしいです。
ほぼ他の参考サイトのコピペです。

<?php
require_once("twitteroauth-master/autoload.php");
require_once('twitteroauth-master/src/TwitterOAuth.php');

use Abraham\TwitterOAuth\TwitterOAuth;

$customer_key = "customer_key";
$customer_secret = "customer_secret";
$access_token = "access_token";
$access_token_secret = "access_token_secret";

$filelist = file('rampa_serifu.txt');
if(shuffle($filelist) ){
    $message = $filelist[0];
}

$connection = new TwitterOAuth($customer_key, $customer_secret, $access_token, $access_token_secret);
$connection->post("statuses/update", array("status" => $message));
?>

file() でテキストファイルの中身を取得し、無作為に1行抽出しているようです。
TwitterOAuth のパスや各種キーは環境に応じて設定して下さい。

実際は後述の台詞集入力で手間取っているため、まだ動かしていません。
またツイート時に「どこで発言したか」も入れたいので、単純にテキストファイルから取ってくるだけではきついかなーとも思います。
行く行くは正規表現であれこれして出力する感じでしょうか。

台詞集の入力

あとはランパの台詞を読み込ませるだけなのですが・・・。
ランパが登場するのは「伝説のスタフィー たいけつ!ダイール海賊団」という作品になりますが、もう発売から10年近く経つゲーム故、台詞をまとめてあるサイトなんてどこにもありません。
もちろんROM解析する知識・技術もなければ、それを行うための機材もありません。

ただ、ソフト自体は今でもとってあります。DSもあります。
つまりこういうことです!

DSC_0277

素でゲームを最初からエンディングまでプレイし、ランパの発言を残すことなく記録していくしかないでしょう。
クリアデータがありますが、一度クリアしたステージのストーリーを再度見ることは出来ません。
また、マリオやカービィと違ってステージ中にも会話が入るので、そういった意味でもプレイしないと厳しいです。

必要なのは「ランパが落ちてくるところからダイール撃破まで」のステージ1~8で、9~10は(確か)一言も喋らないので除きます。
隠しステージの攻略が必要かどうかは微妙なところです。
(うる覚えですが、ドランパに関することでランパが何か喋った気がします)
その他サブ要素(今日のゲストとメモ)で幾つか台詞があった気がしますが、既存のクリアデータで回収できます。

・・・と思いきや、セーブ中にソフトの接触不良→再起動時にデータ真っ白というアクシデント!
サブ要素をメモった後だったのが幸いですが、(ほぼ)コンプリートしたデータが消し飛んでしまいました。
元々最初からやり直す予定だったので、新規で始めますが・・・。

DSC_0279

DSC_0280

何かステージがところどころおかしいです。
水中なのに地上扱いになっていたり、壁のブロックが消えていたりします。
使用している3DSのカートリッジスロットの認識が悪いので、本体を入れ替えてからプレイしないと危険な気がします・・・。
ちょっとやり方を考える必要がありそうです。

というトラブルがあったことを踏まえると、実際の完成はかなり先の話になってしまいそうです。
こういう「台詞bot」って割とよく見かけますが、どうやって作っているのでしょうか。
やはり気合で入力するしかないのでしょうか・・・。

一気にやるのは無理があるので、暇なときにやっていくようにします。
ゲーム的な難易度は低めなので、「詰まる」という意味での障害はないと思われます。

まとめ

というわけで、Twitterでランパの台詞botを「形だけ」作ってみました!
botだけなら自作でも簡単に作れてしまうようです。
むしろゲーム内のランパの台詞をかき集める作業の方が大変かもしれません・・・。
気が向いたらときに少しずつ作っていくしかなさそうです。

余談

海外では「starfy wiki」というシリーズ全体のwikiがあります。
スタフィーシリーズは5作目まで海外で発売されていなかったにも関わらずです。
過去シリーズはおろか、過去に発売されたグッズやROM内の没データまで、内容量も凄まじいです。
英語のサイトですが、スタフィー好きな方は読んでみると面白いかもしれません。

http://starfywiki.org/wiki/Main_Page

【Unity】横スクロールアクションのカメラワーク制御について

というわけで、今更ながらあけましておめでとうございます!
今年も「ゴマちゃんフロンティア」をよろしくお願いします!

例年であれば「あけおめ」専用の記事を書いているのですが、毎年振り返ってもロクなこと語っていないので、今年はやめておこうと思います。
「ゲーム開発の目標!」なんて守れたことはほとんどないので(ぁ

shot2ss20170106203043668

新年1発目は「横スクロールアクションにおけるカメラワークの制御」になります!
ゲームにおいてカメラワークは超重要な要素ですが、自分なりに作った仕組みはどれも欠陥品で、使い物になりませんでした。
他サイト様を参考にしつつ、やっとそれなりのものが出来たのでメモしておきます。

カメラの描画範囲に応じた制御

カメラワークを制御する目的の1つに、「カメラの描画範囲がステージの範囲を超えないこと」があります。
・・・何を言っているかよく分からないと思うので、以下のgifアニメーションをご参照下さい。

20170106_01

20170106_02

上はカメラとプレイヤー位置(用のオブジェクト)を親子関係でくっ付けているだけなので、カメラ移動は完全にプレイヤーの移動に依存します。例えば、「ステージが横スクロールのみで縦スクロールさせたくない!」という場合でも、プレイヤーがジャンプすればカメラが上下に動いてしまう状態です。
また、カメラから見たプレイヤーの位置は常に画面の中央です。ステージ端まで行ってもプレイヤーが中央にいるため、画面の半分近くを壁(ステージ外の空間)が占領してしまいます。

下は本記事の趣旨である「カメラ移動範囲」の制御を取り入れたものです。
こちらもカメラとプレイヤー位置を親子関係でくっ付けていますが、ステージの各セクションごとに決められた範囲(以下セクション範囲と呼称します)に応じて、カメラの描画範囲を超えないようにカメラ移動を制限します。
gifアニメーションでは少し分かりにくいですが、これならステージの範囲を超えればカメラが上下せず、画面端まで行けばカメラ移動も止まります。

実装方法ですが、基本的には下記サイトの方法で設定しています。
非常に参考になりましたorz
http://pokelabo.co.jp/creative-blog/?p=340

shot2ss20170106202106038

オブジェクトとしては、CameraController と Section、SectionArea があります。
CameraController の下に mainCamera が存在し、Section はステージに配置したオブジェクトの親にしています。
わざわざ CameraController の子にしているのは、プレイヤー位置から見た相対的なカメラ位置・回転を変更できるようにしたかったためです。
SectionArea はセクションの範囲の基準点となる空オブジェクトです。
それぞれのオブジェクトにスクリプトを設定します。

【CameraController】

using UnityEngine;
using System.Collections;

/// <summary>
/// ゲーム画面上のメインカメラの制御を行うクラス
/// </summary>

public class CameraController : MonoBehaviour {

    // カメラの幅と高さ
    private float cameraRangeWidth, cameraRangeHeight;

    // 各オブジェクトとコンポーネント
    private GameObject mainCamera;
    private GameObject player;

    // セクション範囲定義用
    private Rect SectionRect;
    private Vector3 top_left, bottom_left, top_right, bottom_right;

    void Start() {
        // パラメータに値を設定
        player = GameObject.FindGameObjectWithTag("Player");
        mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
    }

    void Update() {
        // プレイヤーキャラの位置に追従させる
        transform.position = player.transform.position;

        Vector3 newPosition;

        // カメラ描画範囲の上下左右を取得
        float distance = Vector3.Distance(mainCamera.transform.position, player.transform.position);
        bottom_left = mainCamera.GetComponent<Camera>().ViewportToWorldPoint(new Vector3(0, 0, distance));
        top_right = mainCamera.GetComponent<Camera>().ViewportToWorldPoint(new Vector3(1, 1, distance));
        top_left = new Vector3(bottom_left.x, top_right.y, bottom_left.z);
        bottom_right = new Vector3(top_right.x, bottom_left.y, top_right.z);

        cameraRangeWidth = Vector3.Distance(bottom_left, bottom_right);
        cameraRangeHeight = Vector3.Distance(bottom_left, top_left);

        // カメラ位置をセクション範囲内に収める
        float newX = Mathf.Clamp(newPosition.x, SectionRect.xMin + cameraRangeWidth/2, SectionRect.xMax-cameraRangeWidth/2);
        float newY = Mathf.Clamp(newPosition.y, SectionRect.yMin + cameraRangeHeight/2, SectionRect.yMax - cameraRangeHeight/2);

        transform.position = new Vector3(newX, newY, newPosition.z);
    }

    void OnDrawGizmos()
    {
        // カメラ描画範囲を表示
        Gizmos.color = Color.green;
        Gizmos.DrawLine(bottom_left, top_left);
        Gizmos.DrawLine(top_left, top_right);
        Gizmos.DrawLine(top_right, bottom_right);
        Gizmos.DrawLine(bottom_right, bottom_left);
    }
}

【SectionController】

using UnityEngine;
using System.Collections;

public class SectionController : MonoBehaviour {

    // セクションのカメラ範囲制御
    public Transform SectionArea;
    public float rect_width, rect_height, collider_depth;

    private Rect SectionRect;

    // マネージャ
    private GlobalManager globalManager;

    void Start () {
        // マネージャ取得
        globalManager = GlobalManager.getInstance();

        // セクション範囲定義
        SectionRect = new Rect(SectionArea.position.x, SectionArea.position.y, rect_width, rect_height);

        // セクション判定用オブジェクトに範囲を設定
        SectionArea.GetComponent<SectionArea>().setSectionRect(SectionRect);

        // CameraControllerにセクション範囲を渡すための判定定義
        SectionArea.transform.position = new Vector3(SectionRect.center.x, SectionRect.center.y, transform.position.z);
        BoxCollider boxCollider = SectionArea.GetComponent<BoxCollider>();
        boxCollider.size = new Vector3(SectionRect.width, SectionRect.height, collider_depth);
    }

    void OnDrawGizmos()
    {
        if (globalManager) {
            // セクション範囲を描画
            float base_depth = globalManager.stageManager.baseDepth;

            Vector3 lower_left = new Vector3 (SectionRect.xMin, SectionRect.yMax, base_depth);
            Vector3 upper_left = new Vector3 (SectionRect.xMin, SectionRect.yMin, base_depth);
            Vector3 lower_right = new Vector3 (SectionRect.xMax, SectionRect.yMax, base_depth);
            Vector3 upper_right = new Vector3 (SectionRect.xMax, SectionRect.yMin, base_depth);

            Gizmos.color = Color.red;
            Gizmos.DrawLine(lower_left, upper_left);
            Gizmos.DrawLine(upper_left, upper_right);
            Gizmos.DrawLine(upper_right, lower_right);
            Gizmos.DrawLine(lower_right, lower_left);
        }
    }
}

globalManager など、多少触れていない要素が含まれていますが、ただ他のコンポーネントから値を取っているだけなので適当に流して下さい。

CameraController の Mathf.Clamp() がミソのようです。
これで CameraController のX軸Y軸がセクション範囲から出ないようにコントロールしています。
計算式の意味は参考サイトの持ってきただけなので、自分でもよく分かっていませんが・・・。

shot2ss20170106202358374

SectionController の boxCollider は後述する「セクション移動時のエリア再設定」用です。
セクション範囲の大きさをインスペクターから rect_width, rect_height, collider_depth に設定します。
その際、SectionArea の位置を左下とした値に設定する必要があります。
また、こちらは動的に動かしたりはしないので、本当に定義するだけです。

20170106_04

この状態で実行しシーンビューに切り替えると、セクション範囲が赤枠で、カメラ範囲が緑枠で表示されます。
上のgifアニメーションは mainCamera を選択している状態です。緑枠が赤枠を超えた場合、mainCamera の位置が動いていないのが分かると思います。

セクション移動時のエリア再設定

実際のゲームで考えてみると、ステージ全体を通して1セクションで出来ていることはなかなかないです。
「右に進んだら上に行って、今度は下ってまた右に~」なんてことが多いのではないでしょうか。
途中で特殊なエリア (中ボス部屋とか) を挟んだりする場合もあります。

なので、セクションを複数定義した際に CameraController のセクション範囲を再定義できるようにしておきます。
また、各セクションの子オブジェクトにセクション範囲を定義したオブジェクトを作ります。
付けるスクリプトは以下になります。

using UnityEngine;
using System.Collections;

public class SectionArea : MonoBehaviour {

    private Rect SectionRect;
    private CameraController cameraController;

    /// <summary>
    /// セクション範囲を設定する
    /// </summary>
    /// <param name="rect"></param>
    public void setSectionRect(Rect rect) {
        this.SectionRect = rect;
        cameraController = GameObject.FindGameObjectWithTag("CameraController").GetComponent<CameraController>();
    }

    void OnTriggerEnter(Collider c) {
        if (TagUtility.getParentTagName(c.gameObject.tag) == "Player") {
            cameraController.setSectionRect(SectionRect);
        }
    }
}

OnTriggerEnter() 時に CameraController にセクション範囲を渡すので、胴スクリプトにセッタを作ります。
至って普通のセッタです。

public void setSectionRect(Rect SectionRect) {
    this.SectionRect = SectionRect;
}

あとはプレイヤーがセクション範囲に触れれば勝手にセットされます。

セクション移動時のカメラワーク

一通りの実装はできましたが、セクションからセクションへ移動した際のカメラワークが微妙で、カメラが一瞬で移動するので視覚的にはよろしくありません。
こういった処理では iTween を使用するのがベターですが、「移動する先」が分からないと処理しようがないです。
現状でも一瞬で移動するのは、Update() の「セクション範囲にカメラを収める」ための処理で勝手に動いている感じです。

ということで、setSectionRect() 実行時に移動先の地点を割り出すための再計算を行い、 iTween による移動処理を加えます。
再計算といってもやっていることは Update() 内と変わりません。
ついでに移動用に関数として、moveCameraPosition() も作っておきます。

public void setSectionRect(Rect SectionRect) {
    // 移動後のカメラ位置取得のため、カメラ移動範囲の再計算
    float newX = Mathf.Clamp(mainCamera.transform.position.x, SectionRect.xMin + cameraRangeWidth/2, SectionRect.xMax-cameraRangeWidth/2);
    float newY = Mathf.Clamp(mainCamera.transform.position.y, SectionRect.yMin + cameraRangeHeight/2, SectionRect.yMax - cameraRangeHeight/2);
    Vector3 newPos = new Vector3(newX, newY, mainCamera.transform.position.z);

    moveCameraPosition(mainCamera.transform.position, newPos);
    this.SectionRect = SectionRect;
}

public void moveCameraPosition(Vector3 oldPos, Vector3 newPos, float time = 1f) {
    iTween.MoveTo(mainCamera, iTween.Hash(
        "position", newPos,
        "time", time
    ));
}

20170106_03

iTween.MoveTo() により、適用前よりもなめらかにカメラが移動してセクション再設定が行われます。
なかなか良い感じですね!

まとめ

そんなわけで、横スクロールアクションにおけるカメラワークについて考えてみました!
やっとまともなカメラワークが実現できたので一安心です。

ただ、実装が完全に横スクロール向けのカメラワークなので、3Dとのハイブリッドはほぼできなくなりました。
いっそ開き直ってゲームにすることを優先し、次作るゲームに繋げるというのもありかなーと思い始めていたりします。
なので気にせずやっていこうと思います!