✈️

ワールドクラフトで飛ぶ乗りもの

2023/02/12に公開

ちょい複雑な動きだったので少し自信がないですが、とりあえず良いことにして公開します。
周回して飛ぶ乗りものです。

飛行機っぽいリアルさはあまりないので、ファンタジックな感じですかね。

unitypackage

cluster公式さんの「テンプレートワールド」など、クラフトアイテムをアップロードできる状態のUnityのプロジェクトに読み込んでください。
モデル・マテリアル・スクリプトなどなど、改変はお好きにどうぞ~
なお今回、スクリプトを直書きせずjsファイルに書き込んでいます。

https://vins-jp.sakura.ne.jp/pack/wc_flier.unitypackage

基本に自信がない人は

クラフトアイテムアップロードの基本はこの記事を。
スクリプトの基本はこの記事を。

青と赤の違い

青は乗らなくても飛ぶ、速度が遅い。
赤は乗らないと飛ばない、速度が速い
……などの違いがあります。

スクリプト全文(赤いほう)

とりあえず速くしたいならbaseSpeedとかforwardSpeedを上げて、直進を長くしたいならforwardLengthを、高いところまで飛びたいならupLengthとかを上げる感じで。

回転させる部分が不要な場合はrotatorNumを0にしてください。

const stopTillRiding = true;

const baseSpeed = 2.5;
const forwardSpeed = 4.0;

const yRotSpeed = 40.0;
const upwardSpeed = 20.0;

const forwardLength = 2.0;
const upLength = 3.0;
const stopLength = 2.0;
const slideLength = 5.0;

const landingY = 0.5;
const landingLength = 1;

const rotateSeconds = 360.0 / yRotSpeed;

const rotatorSpeed = 200.0;
const rotatorNum = 2;
const rotatorsAr = [];
for (let rotatorNo = 0; rotatorNo < rotatorNum; rotatorNo++) {
	rotatorsAr.push($.subNode("Rotator" + rotatorNo));
}

const speedVec = new Vector3(0, 0, forwardSpeed);

let ENUM_VAL = 0;
const STOP = ENUM_VAL++;
const FORWARD_A = ENUM_VAL++;
const UP = ENUM_VAL++;
const ROTATE_A = ENUM_VAL++;
const FORWARD_B = ENUM_VAL++;
const ROTATE_B = ENUM_VAL++;
const DOWN = ENUM_VAL++;
const LANDING = ENUM_VAL++;
const FORWARD_C = ENUM_VAL++;
const SLIDE_TO_FIRST_POS = ENUM_VAL++;
const END = ENUM_VAL++;

const initProc = () => {
	$.state.tick = 0;
	$.state.moveState = STOP;

	$.state.rotatorTick = 0;
	$.state.upwardVal = 0;

	$.state.initialized = true;
}

const checkFirstPos = (alwaysUpdate) => {
	if (!alwaysUpdate && $.state.firstPosChecked) {
		return;
	}
	$.state.firstPos = $.getPosition();
	$.state.pos = $.state.firstPos;
	$.state.firstQuat = $.getRotation();
	$.state.firstQuatY = $.state.firstQuat.createEulerAngles().y;
	$.state.rotYVal = $.state.firstQuatY;

	$.state.firstPosChecked = true;
}

const rotateAndMoveProc = (tickVal,specifiedY) => {
	$.state.rotYVal += tickVal * $.state.currentYRotSpeed;

	const quat = new Quaternion().setFromEulerAngles(new Vector3($.state.upwardVal, $.state.rotYVal, 0));
	$.setRotation(quat);
	const moveVec = speedVec.clone().applyQuaternion(quat).multiplyScalar(tickVal);
	if (!!specifiedY) {
		moveVec.set(moveVec.x, specifiedY*tickVal, moveVec.z);
	}
	$.state.pos = $.state.pos.add(moveVec);
}

const calcUpwardSpeedProc = (targetSpeed, rate) => {
	if (rate > 0) {
		if ($.state.upwardVal < targetSpeed) {
			$.state.upwardVal += upwardSpeed * rate;
			if ($.state.upwardVal > targetSpeed) {
				$.state.upwardVal = targetSpeed;
			}
		}
	} else {
		if ($.state.upwardVal > targetSpeed) {
			$.state.upwardVal += upwardSpeed * rate;
			if ($.state.upwardVal < targetSpeed) {
				$.state.upwardVal = targetSpeed;
			}
		}
	}
}

const tickCheck = (tickVal) => {
	if ($.state.tick >= tickVal) {
		$.state.tick -= tickVal;
		$.state.moveState++;
		if ($.state.moveState == SLIDE_TO_FIRST_POS) {
			$.state.slideFromPos = $.getPosition();
		} else if ($.state.moveState == END) {
			landedProc();
		}
	}
}

const rotatorProc = (tickVal) => {
	if ($.state.moveState == SLIDE_TO_FIRST_POS || $.state.moveState == STOP) {
		return;
	}
	$.state.rotatorTick += tickVal * rotatorSpeed;
	for (let rotatorNo = 0; rotatorNo < rotatorNum; rotatorNo++) {
		rotatorsAr[rotatorNo].setRotation(new Quaternion().setFromEulerAngles(new Vector3(0, $.state.rotatorTick, 0)));
	}
}

const landedProc = () => {
	$.state.moveState = STOP;
	$.setPosition($.state.firstPos);
	$.setRotation($.state.firstQuat);
	$.state.pos = $.state.firstPos;

	$.state.rotYVal = $.state.firstQuatY;
	$.state.upwardVal = 0;
}

const easeOut = (t) => {
	return 1 - Math.pow(1 - t, 3);
};

const slideProc = () => {
	$.state.pos = $.state.slideFromPos.lerp($.state.firstPos, easeOut($.state.tick/slideLength));
}

$.onUpdate((deltaTime) => {
	if (!$.state.initialized) {
		initProc();
	}

	const tickVal = deltaTime * baseSpeed;
	$.state.tick += tickVal;

	rotatorProc(tickVal);
	switch ($.state.moveState) {
		case STOP:
			if (stopTillRiding && !$.state.isRiding) {
				$.state.tick = 0;
				return;
			}
			tickCheck(stopLength);
			if ($.state.moveState != STOP) {
				checkFirstPos(false);
			}
			break;
		case FORWARD_A: //離陸前
		case FORWARD_C: //着陸後
			$.state.currentYRotSpeed = 0;
			rotateAndMoveProc(tickVal);
			tickCheck(forwardLength);
			break;
		case FORWARD_B: //上空での前進
			$.state.currentYRotSpeed = 0;
			rotateAndMoveProc(tickVal);
			tickCheck(forwardLength * 2 + upLength * 2);
			break;
		case UP:
			calcUpwardSpeedProc(-upwardSpeed, -tickVal);
			$.state.currentYRotSpeed = 0;
			rotateAndMoveProc(tickVal);
			tickCheck(upLength);
			break;
		case ROTATE_A:
		case ROTATE_B:
			calcUpwardSpeedProc(0, tickVal);
			$.state.currentYRotSpeed = yRotSpeed;
			rotateAndMoveProc(tickVal);
			tickCheck(rotateSeconds / 2);
			if ($.state.moveState == ROTATE_A + 1) {
				$.state.currentYRotSpeed = $.state.firstQuatY + 180;
			} else if ($.state.moveState == ROTATE_B + 1) {
				$.state.currentYRotSpeed = $.state.firstQuatY;
			}
			break;
		case DOWN:
			calcUpwardSpeedProc(upwardSpeed, tickVal);
			$.state.currentYRotSpeed = 0;
			rotateAndMoveProc(tickVal);
			if ($.state.pos.y < $.state.firstPos.y + landingY) { //どうしても誤差が出るので、高度(Y)がlandingY以下になったらLANDINGに移行
				$.state.moveState++;
				$.state.tick = 0;
				$.state.sumVal = 0;
				$.state.landingSpeed = landingY / landingLength * 3 / 2;
			}
			break;
		case LANDING: //最後の高度(Y)の変化だけは計算で求めて着陸した感を強める
			calcUpwardSpeedProc(0, -1 / landingLength * tickVal);
			const yDownT = $.state.tick / landingLength;
			const yDownSpeed = (-yDownT*yDownT+1) * $.state.landingSpeed;
			$.state.sumVal += -yDownSpeed*tickVal;
			rotateAndMoveProc(tickVal, -yDownSpeed);
			tickCheck(landingLength);
			break;
		case SLIDE_TO_FIRST_POS: //どうしても誤差がでるので、着陸後初期位置にスライドさせる
			slideProc();
			tickCheck(slideLength);
			break;
	}

	if ($.state.moveState != STOP) {
		$.setPosition($.state.pos);
	}
});

$.onRide((flag) => {
	$.state.isRiding = flag;
	if (flag && $.state.moveState == STOP) {
		checkFirstPos(true);
	}
});

Discussion