見出し画像

SRPG Studio 技術検証 ヘックス制のシステムは構築可能なのか? →MapSimulator自作した結果

こんにちは熱帯魚です。
月末ならぬ月初記事ですね(まあ月初ですらない)

今回は数か月前のツイートで言ってたことを深ぼります

MapSimulatorの内部処理はブラックボックスとなっているので
自作するしか道はない!で作ったはいいけど重すぎてという話。

軽量化にチャレンジ。

(なお、海外の方では既にできている模様)

結論

厳しい。(HELP ME!)
可能そうではある。
しかし、実用に耐えうる軽量化は素人には不可能。

理由

デメリットに目をつぶれば動きはする

メリット
MapSimulator自作したため、攻撃マスと移動マスの拡張が自由自在となる。
ヘックス化やZOC設定も容易。
サモンナイトでいう銃的な射程(要は通常攻撃射程が武器ごとに自由)
も作れちゃう。
デメリット
重すぎ

肝心の重さだが、
最小サイズのマップかつ
移動5以下のユニットかつ
ユニット数10以下で1敵10秒単位の思考時間が発生する
レベルの重さなので、使えない

このデメリットに目をつぶることは不可能です。

ロジック

神記事

しかし、実際にはSRPGStudio用のロジック最適化が必須の模様。
おそらく私の自作したロジックが間違っていて
それが重さの原因だと推測される。

再起処理

疑似的な再起処理で実現(これも重さに関係?)

プログラミング豆知識
JavaScriptには処理階層が存在し、
再起処理の階層に限界がある

上記も構築段階で壁となった。
(最新版では違うらしい? ちなみに下記の手段では実現できなかったので全く別の方法で再起している)

後は重複検索しないようにロジックを工夫。

しかし、最低限これは必要という数まで落としても重かった。

処理入れ替え作戦

実際に公式処理の呼び出しを変更する

root.getCurrentSession().createMapSimulator()

上記呼び出しを自作関数に変更

Nettaigyo.createMapSimulator = function(){
		//return root.getCurrentSession().createMapSimulator();

		return createObject(Nettaigyo.MapSimulator);
}

返すクラス(Nettaigyo.MapSimulator)に
SRPGStudio側が用意しているAPIを全部自作(ただのアホ)

Nettaigyo.MapSimulator = defineObject(BaseObject,{
	_singleUnitSimulator:null,
	_simulationWeaponAllList:null,
	_simulationIndexArray:null,
	_simulationWeaponIndexArray:null,
	_zocMapIndexArray:null,
	_unitFilterFlag:0,
	initialize:function(){
		this._singleUnitSimulator = createObject(Nettaigyo.SingleUnitSimulator);
		this._simulationIndexArray = [];
		this._simulationWeaponAllList = [];
		this._simulationWeaponIndexArray = [];
		this._zocMapIndexArray = [];
	},
	arrayClear: function(){
		this._simulationWeaponAllList = [];
		this._simulationIndexArray = [];
		this._simulationWeaponIndexArray = [];
		this._singleUnitSimulator.arrayClear();
		this._singleUnitSimulator.setZocMapIndex(this._zocMapIndexArray);
	},
	setZocMapIndex: function(arr){
		this._zocMapIndexArray = arr;
	},
	updateZocMapIndex:function(unit){
		if(!unit){
			return;
		}
		var unitType = unit.getUnitType();
		this.setZocMapIndex(Nettaigyo.ZocControl.getFilterArray(FilterControl.getReverseFilter(unitType)));
	},
	updateZocMapIndexFilter:function(unitFilter){
		this.setZocMapIndex(Nettaigyo.ZocControl.getFilterArray(Nettaigyo.FilterControl.getReverseFilterToFliter(unitFilter)));
	},
	_updateAllArray: function(){
		var i, sim;
		var len = this._simulationWeaponAllList.length;
		var moveAndWeaponArray = [];
		this._simulationIndexArray = [];
		this._simulationWeaponIndexArray = [];
		for (i = 0; i < len; i++) {
			simu = this._simulationWeaponAllList[i];
			this._simulationIndexArray = Nettaigyo.ArrayUtil.hasMerge(this._simulationIndexArray, simu.getMapIndexArray());
		}
		moveAndWeaponArray = this._simulationIndexArray;
		for (i = 0; i < len; i++) {
			simu = this._simulationWeaponAllList[i];
			moveAndWeaponArray = Nettaigyo.ArrayUtil.hasNewArray(moveAndWeaponArray, simu.getMapIndexWeaponArray());
			this._simulationWeaponIndexArray = Nettaigyo.ArrayUtil.hasMerge(this._simulationWeaponIndexArray, moveAndWeaponArray);
		}

		this._arraySort();

	},

	_setArray: function(){
		this._simulationIndexArray = this._singleUnitSimulator.getMapIndexArray();
		this._simulationWeaponIndexArray = this._singleUnitSimulator.getMapIndexWeaponArray();

		this._arraySort();

	},

	//公式関数ではインデクス配列は低い順にソートされている
	_arraySort:function(){
		this._simulationIndexArray.sort(function(a,b){
			var r = a - b;
			if( r !== 0){
				return r;
			}
			return 0;
		});

		if(this._simulationWeaponIndexArray == null || this._simulationWeaponIndexArray.length < 1){
			return;
		}
		this._simulationWeaponIndexArray.sort(function(a,b){
			var r = a - b;
			if( r !== 0){
				return r;
			}
			return 0;
		});
	},


	//----------------------------------------------------------
	// 下記から公式に存在する関数
	//

	startSimulation: function(unit, moveMaxCount){
		this.updateZocMapIndex(unit);
		this.arrayClear();
		this._singleUnitSimulator.startSimulation( unit, moveMaxCount);
		this._setArray();
	},
	startSimulationRange: function(x, y, startRange, endRange){
		this.arrayClear();
		this._singleUnitSimulator.startSimulationRange(x, y, startRange, endRange);
		this._setArray();
	},
	startSimulationWeapon: function(unit, moveMaxCount, startRange, endRange){
		this.updateZocMapIndex(unit);
		this.arrayClear();

		this._singleUnitSimulator.startSimulationWeapon(unit, moveMaxCount, startRange, endRange);
		
		this._setArray();
	},
	startSimulationWeaponAll: function(unitFilter){
		this.updateZocMapIndexFilter(unitFilter);
		this.arrayClear();

		this._unitFilterFlag = unitFilter;	

		var i, j, count, list, unit, rangeData;
		var listArray = FilterControl.getListArray(unitFilter);
		var listCount = listArray.length;

		for (i = 0; i < listCount; i++) {
			list = listArray[i];
			count = list.getCount();
			for (j = 0; j < count; j++) {
				//unit
				unit = list.getData(j);

				//まずtempSimulator作成
				var tempSimulator = createObject(Nettaigyo.SingleUnitSimulator);
				tempSimulator.copyDisableParam(this._singleUnitSimulator);
				tempSimulator.setZocMapIndex(this._zocMapIndexArray);

				//rangeDataをもとにunitの移動範囲と攻撃範囲を作る
				rangeData = Nettaigyo.StructureBuilder.buildAttackRangeMetrics(unit,false);
				tempSimulator.startSimulationWeapon(
					unit,
					Nettaigyo.PanelControl.getRangeMov(unit), 
					rangeData.startRange, 
					rangeData.endRange
				);

				//生成されたシュミレータを格納する
				this._simulationWeaponAllList.push(tempSimulator);
			}
		}

		this._updateAllArray();
	},

	startSimulationWeaponPlus: function(unit){
		this.updateZocMapIndexFilter(unitFilter);
		this.arrayClear();

		var i, sim;
		var newArray = [];
		var len = this._simulationWeaponAllList.length;
		var flag = false;
		for (i = 0; i < len; i++) {
			simu = this._simulationWeaponAllList[i];

			flag = simu.isCurrentUnit(unit);
			if(flag){
				//マップ上に存在しなければ除外する
				if (unit.getAliveState() !== AliveType.ALIVE) {
					continue;
				}
			}else{
				flag = simu.isInfluence(unit);
			}

			//再計算する
			if(flag){
				simu.reStartSimulationWeapon();
			}

			newArray.push(simu);
		}
		this._simulationWeaponAllList = newArray;

		this._updateAllArray();
	},

	disableMapUnit:function(){this._singleUnitSimulator.disableMapUnit();},
	disableTerrain:function(){this._singleUnitSimulator.disableTerrain();},
	disableRestrictedPass:function(){this._singleUnitSimulator.disableRestrictedPass();},

	getSimulationIndexArray: function(){
		return this._simulationIndexArray;
	},
	getSimulationWeaponIndexArray: function(){
		return this._simulationWeaponIndexArray;
	},

	getSimulationMovePoint: function(mapIndex){
		return this._singleUnitSimulator.getSimulationMovePoint(mapIndex);
	},

	//公式プラグインにも用途が乗っていないので戻り値の運用について予測できないため
	//とりあえずTOPを返しておく
	getSimulationDirection: function(mapIndex){
		return DirectionType.TOP;
	},

	isSimulationMark: function(mapIndex){
		return this._singleUnitSimulator.isSimulationMark(mapIndex);
	},

	setSimulationMark: function(mapIndex, flag){
		this._singleUnitSimulator.setSimulationMark(mapIndex, flag);
	},

	resetSimulationMark: function(){
		this._singleUnitSimulator.resetSimulationMark();
	},
	getLoopCount: function(){
		return this._singleUnitSimulator.getLoopCount();
	},

	getMovePointFromLoopIndex: function(loopIndex){
		return this._singleUnitSimulator.getMovePointFromLoopIndex(loopIndex);
	},

	getPosIndexFromLoopIndex: function(loopIndex){
		return this._singleUnitSimulator.getPosIndexFromLoopIndex(loopIndex);
	},

	getAreaValue: function(){
		return this._singleUnitSimulator.getAreaValue();
	}
});

コードが汚い!
のは置いておくとする。

pushより固定長にした方が早い?
計算せずに定数にできるものは定数化など
記述の軽量化についてはいろいろ試しましたが、
あんまり変わらなかったのでやはり自作ロジックの問題

配列操作が多すぎるのでそこは軽量化のポイントかなと。

中身の処理はマスに重みを決めて
キャラの移動から引いて
検索範囲を広げていくタイプの処理です。(オーソドックス)

重さ

銃の射程とかヘクスを考慮せずに基本機能
(改造せずにSRPGStudioが提供している機能、要するにスクエア)
のみで実行させても
1敵ユニットが動くのに10秒?ほどかかる

これはマップの広さとキャラの移動力に比例して重くなる
(内部処理事情的に仕方ない。ちなみに味方の移動範囲表示も
多少重くはなるが敵の思考時間より遥かに軽量)

銃射程やZOCを加えても重さほぼ変わらない。
(ヘックスは同じ移動・射程でのマスが多くなるので体感重くなる?)

動画

結局使い物にならなかったので見た目は整えてないです。
(一見軽そうだが敵側は別処理にしてるのでこの軽さ
マップを広くしたりユニット増やすと激重になる)

一見へんな見た目になっていますが、
0.5マスずつずらすとこんな感じ↓
地味にZOCも採用。

hex処理テスト

まとめ

現在制作中のゲームがほぼ完成したので
クリア後のおまけ的に1ステージこっそり追加しようと心見ましたが…。

重くてイライラが勝る!(1敵10秒以上はゲームとして終わってる)

数か月前のソースを何とか軽量化したものの
やはりちょっと厳しいかなという印象ですね。
軽量化できても見た目の体裁整えたり
周囲1マスの判定(踊り等)も書き換える必要があるので手間もかかる。

実現するにはまず自作MapSimulatorロジックを
1から見直さないと無理そうです。

PS

海外の方がプラグインを公開するのを待ちましょう

この記事が気に入ったらサポートをしてみませんか?