スマホゲームでスムーズな移動を実装したい

RPGツクールMVのデフォルトの操作だと、マップをタッチすると「タッチしたところまで移動し、イベントがあれば起動」となっています。

直観的でわかりやすい反面、マップの左上に移動するには指を大きく動かさないといけないし、タッチする場所によっては主人公が指で隠れてしまいます。

アクション性の低いRPGならいいけど、アクションゲームを作る時にこの操作はちょっと厳しい……。

いろんなアプリを試したなかで、『白猫プロジェクト』と『ドラガリアロスト』のスワイプした方向に移動、タッチで攻撃(調べる)の操作感が気に入ったので、RPGツクールMVで再現しようとプラグインを書いてみた。

せっかくなので、ブラウザで動かせるデモも作りました。
スワイプ移動デモ
RPGツクールMV スワイプ移動デモ

スワイプ移動だと、右手の親指だけでキャラクターを動かせるのがいいですね。

サンシロ様のアナログムーブとあわせると、けっこうスムーズな移動ができるようになった気がします。

まだオブジェクトを調べにくいところがあるので、もうちょっと改善できるといいなぁ。

あと画面をズームしているとタッチした時の円の描画位置がずれるのを修正する必要あり。

書いたプラグインはこんな感じです。

(function(){
    'use strict';
    
    var originalX;
    var originalY;
    var touchX;
    var touchY;
    var wasSwipe;
    const TOUCH_CIRCLE_RADIUS = 36;
    const MOVE_RANGE = 24; // 移動のしきい値
    
    var _Scene_Map_processMapTouch = Scene_Map.prototype.processMapTouch;
    Scene_Map.prototype.processMapTouch = function() {
	// タッチ開始時に原点座標を取得しタッチ位置を描画
	if (TouchInput.isTriggered()){
	    // アイテムスロットクリック時は無効
	    if(this._mapItemSlotWindow){
		if(this._mapItemSlotWindow.contains(TouchInput.x, TouchInput.y)){
		    return;
		}
	    }

	    originalX = TouchInput.x;
	    originalY = TouchInput.y;

	    this._spriteset.drawTouchCenter(originalX, originalY);
	}    
	
	// タッチ中は座標を更新し続ける
	if(TouchInput.isPressed()){
	    touchX = TouchInput.x;
	    touchY = TouchInput.y;

	    // 現在のタッチ座標が原点座標と異なる場合のみ移動
	    wasSwipe = false;
	    if(touchX !== originalX || touchY !== originalY){
		this._spriteset.drawTouchOuter(touchX, touchY);
		this.processMapSwipe();
		wasSwipe = true;
	    }
	    
	    this._touchCount++;
	}


	if(this._touchCount > 0 && !TouchInput.isPressed()){
	    // スワイプしていなければタッチアクション実行
	    if(!wasSwipe){
		var direction = $gamePlayer.direction();
		var frontX = $gameMap.roundXWithDirection($gamePlayer.x, direction);
		var frontY = $gameMap.roundYWithDirection($gamePlayer.y, direction);
		$gamePlayer.triggerTouchActionD2(frontX, frontY);

	    }
	    
	    this._touchCount = 0;
	    this._spriteset.clearTouchSprites();
	    
	    originalX = null;
	    originalY = null;
	}
    };

    // スワイプ処理
    Scene_Map.prototype.processMapSwipe = function() {
	if (this._touchCount === 0 || this._touchCount >= 15) {
	    var diffX = TouchInput.x - originalX;
	    var diffY = TouchInput.y - originalY;
	    
	    if(Math.abs(diffX) >= MOVE_RANGE / 2){
		diffX = (diffX > 0) ? 1 : -1;
	    } else {
		diffX = 0;
	    }
	    
	    if(Math.abs(diffY) >= MOVE_RANGE){
		diffY = (diffY > 0) ? 1 : -1;
	    } else {
		diffY = 0;
	    }
	    
	    $gameTemp.setDestination($gamePlayer.x + diffX, $gamePlayer.y + diffY);		
	}
    };
  
    
    // ****************************
    // タッチスプライト描画
    // ****************************
    // メモ:
    // Sprite       : rpg_coreで定義されているスプライトクラス
    // Spriteset_Map: Scene_Mapからthis._spritesetで呼びだせる
    Spriteset_Map.prototype.createUpperLayer = function() {
	Spriteset_Base.prototype.createUpperLayer.call(this);
	this.createUISprites();	
    };

    Spriteset_Map.prototype.createUISprites = function(){
	this._touchCenter = new Sprite();
	this._touchOuter = new Sprite();
	this.addChild(this._touchCenter);
	this.addChild(this._touchOuter);
    }
    
    Spriteset_Map.prototype.drawTouchCenter = function(x, y){
	this._touchCenter.bitmap = new Bitmap(TOUCH_CIRCLE_RADIUS * 2, TOUCH_CIRCLE_RADIUS * 2);
	this._touchCenter.bitmap.drawCircle(TOUCH_CIRCLE_RADIUS, TOUCH_CIRCLE_RADIUS, TOUCH_CIRCLE_RADIUS, 'rgba(255,255,255,0.5)');
	
	this._touchCenter.x = x;
	this._touchCenter.y = y;

	this._touchCenter.anchor.x = 0.5;
	this._touchCenter.anchor.y = 0.5;
    };
    

    Spriteset_Map.prototype.drawTouchOuter = function(x, y){
	if(this._touchCenter){
	    var radius = TOUCH_CIRCLE_RADIUS / 2;
	    this._touchOuter.bitmap = new Bitmap(radius, radius);
	    this._touchOuter.bitmap.drawCircle(radius / 2, radius / 2, radius / 2, 'rgba(0,0,255,0.5)');

	    var radian =  Math.atan2(y - this._touchCenter.y, x - this._touchCenter.x);
	    this._touchOuter.x = this._touchCenter.x + (TOUCH_CIRCLE_RADIUS * Math.cos(radian));
	    this._touchOuter.y = this._touchCenter.y + (TOUCH_CIRCLE_RADIUS * Math.sin(radian));

	    this._touchOuter.anchor.x = 0.5;
	    this._touchOuter.anchor.y = 0.5;	  
	}
    };

    Spriteset_Map.prototype.clearTouchSprites = function(){
	if(this._touchCenter.bitmap){
	    this._touchCenter.bitmap.clear();
	}
	
	if(this._touchOuter.bitmap){
	    this._touchOuter.bitmap.clear();
	}
    };    
})();

Spriteは初めて使ったのですが、いろいろ遊べそうですね。

おすすめの記事