【メモ】イベント制作備忘録
記録にすらならないものでしたが、備忘録として残しておきます。
- 作ったもの
- コメント連動ギミック
【サンプル付き】イベントで使えるコメント連動ギミックをつくろう!「コメント取得 API」を使う
イベントまたは開発者環境でないと機能しないので注意ですね。僕は親アイテムオブジェクトをScriptableItemにして、子ノードのアイテムオブジェクトの挙動を変えるように使いました。
onCommentReceivedで受け取ったコメントはあくまでトリガーだけの機能を持っていて、そのトリガーをもとにonUpdateで子ノードのアイテムオブジェクトの挙動を変える処理をするようにしました。
$.onCommentReceived((comments) => {
// $.log("comments " + comments.map(c => c.body));
const indexRegex = /([0-9]+)/;
const speedRegex = /(u|d|U|D|↑|↓|上|下|速|遅)/;
for (const comment of comments) {
const matchIndex = comment.body.match(indexRegex);
const matchSpeed = comment.body.match(speedRegex);
if (matchIndex && matchSpeed) {
let number = parseInt(matchIndex[1]);
const direction = matchSpeed[1];
let speed = 0;
if (["u", "U", "↑", "上", "速"].includes(direction)) {
speed += 1;
} else if (["d", "D", "↓", "下", "遅"].includes(direction)) {
speed += -1;
}
NodeManager.PushSpeedInfo($, number, speed);
}
}
})
子ノードのアイテムオブジェクトにはそれぞれ0から9までの数字が割り当てられていて、それぞれ移動速度を持っています。コメントに数字と"up"、数字と"down"が来たら対応する子ノードのアイテムオブジェクトの速度が変わるようにしました。
添付したコードでは、"up"と"down"ではなく、"u"や"d"だけで反応するようにしています(ほかにもちらほらありますけど)。正規表現とspeedを上げ下げする処理に箇所で文字一致判断していてかっこ悪いですが、まぁ許してください。
PushSpeedInfoにはオブジェクトを要素にする配列として追加しているだけだったとおもいます。
コメント連動全体
const NodeManager = (($) => {
const node_size = 1;
const node_height = 0.025;
const width = 5 - node_size * 0.5;
const depth = node_size * 0.5;
const subNodes = [
$.subNode("Node0"),
$.subNode("Node1"),
$.subNode("Node2"),
$.subNode("Node3"),
$.subNode("Node4"),
$.subNode("Node5"),
$.subNode("Node6"),
$.subNode("Node7"),
$.subNode("Node8"),
$.subNode("Node9"),
]
const textNode = $.subNode("Text");
let _textIndexSpeed = [];
const createPhaseManager = () => {
let _time = 0;
let _speed = 1;
const period = 10;
const trapezoidalWave = (t) => {
if (t < 0.25) {
return t * 8 - 1;
} else if (t < 0.5) {
return 1;
} else if (t < 0.75) {
return 5 - t * 8;
} else {
return -1;
}
};
const UpdateTime = ($, deltaTime) => {
_time += deltaTime * _speed;
if (_time > period) {
_time = 0;
}
}
const GetPhase = ($) => {
let t = _time % period / period;
return trapezoidalWave(t);
}
const AddSpeed = (shift) => {
let current_speed = _speed + shift;
_speed = Math.min(Math.max(current_speed, 1), 5);
}
return { UpdateTime, GetPhase, AddSpeed };
};
const phaseManagers = subNodes.map(() => createPhaseManager());
const GetNodePosition = (i, phase) => {
let center = Math.floor((1 - subNodes.length) * 0.5 + 0.5);
return new Vector3(
(i + center) * node_size - depth,
node_height,
phase * width);
}
const Update = ($, deltaTime) => {
for (let i = 0; i < subNodes.length; i++) {
phaseManagers[i].UpdateTime($, deltaTime);
const pos = GetNodePosition(i, phaseManagers[i].GetPhase($))
subNodes[i].setPosition(pos);
}
// インターバルをすぎたら、_textIndexSpeedの先頭要素を取得してphaseManagersの
{
let time = $.state._time ?? 0;
time += deltaTime;
const INTERVAL = 1;
if (time > INTERVAL) {
time = 0;
if (_textIndexSpeed.length > 0) {
const { index, speed } = _textIndexSpeed.shift();
phaseManagers[index].AddSpeed(speed);
} else {
textNode.setText("");
}
}
$.state._time = time;
}
}
PushSpeedInfo = ($, number, speed) => {
index = number % subNodes.length;
// $.log("index " + index);
// $.log("speed" + speed);
_textIndexSpeed.push({ index, speed });
if (_textIndexSpeed.length > 0) {
// テキストを更新
let text = "";
let count = 0;
for (const { index, speed } of _textIndexSpeed) {
text += `Box${index}:${speed > 0 ? "UP" : "DOWN"}\n`;
count++;
if (count >= 3) {
break;
}
}
textNode.setText(text);
}
}
return { Update, PushSpeedInfo };
})($);
$.onUpdate(deltaTime => {
NodeManager.Update($, deltaTime);
});
他にもコメント連動する機能がありますが、Update関数の内容をかえているだけです。
- たらいを落とす
** ステージ
ギフトを投げられた時に任意の処理が追加できるようになりました!
https://note.com/cluster_official/n/nb0cdc849a56c
イベント専用ワールドということで、giftを使ったお邪魔機能を作ろうと思いました。
- やらなかったこと
たらいをアイテムとして生成したかった。コメントを受け取ってアイテムを作ればよいと思ったけど
ワールドアイテムテンプレートから生成されたアイテムがワールドコンポーネントを持っていてもそれは動作しません。
と記載がありました。
ワールドコンポーネントが何であるかを調べていません。Player Enter Warp Portalを設定していて安全のため生成することはやめました。
World Item Template List コンポーネント
createItem
destroy
World Item Reference List コンポーネント
- やったこと
たらいにはPlayer Enter Warp Portalを設定して、それとは別にギフトを受信するアイテムオブジェクトを用意しました。なんで分けたのか記憶がないのですが、たらいはたらで独立したアイテムにしたほうがすっきりすると思っちゃたんだと思います。ギフトアイテムとたらいはアイテム間でメッセージやり取りするようにしました。たいしたことはやっていませんのでペタっと添付します。
コードなんてGitHubでpublic公開していますが、たぶんリンクがお亡くなりになるのでここで供養しておきます
たらい
const subNodeText = $.subNode("Text");
$.onReceive((messageType, arg, sender) => {
// $.log(`<tub move> onReceive: ${arg}`);
switch (messageType) {
case "<tub manager> initialize":
sender.send("<tub marker> receive initialize", {});
break;
case "<tub manager> Set Position":
// $.log(`<manager> Set Position: ${arg}`);
// $.log(`arg.position: ${arg.position}`);
// $.log(`arg.displayName: ${arg.displayName}`);
$.setPosition(arg.position);
subNodeText.setText(arg.displayName);
break;
}
}, { item: true, player: false });
ギフト
const initializeHandles = () => {
const key = "<tub manager> initialize";
return ($) => {
let itemHandles = $.getItemsNear(new Vector3(), Infinity);
for (let itemHandle of itemHandles) {
const arg = {};
arg.itemHandle = $.itemHandle;
itemHandle.send(key, {});
}
}
};
const NodeManager = (($) => {
const node_size = 1;
const width = 5 - node_size * 0.5;
const subNodePosition = $.subNode("Position");
let _giftSender = [];
const createPhaseManager = () => {
let _time = 0;
let _speed = 1;
const period = 10;
const trapezoidalWave = (t) => {
if (t < 0.5) {
return t * 4 - 1;
} else {
return 3 - t * 4;
}
};
const UpdateTime = ($, deltaTime) => {
_time += deltaTime * _speed;
if (_time > period) {
_time = 0;
}
}
const GetPhase = ($) => {
let t = _time % period / period;
return trapezoidalWave(t);
}
return { UpdateTime, GetPhase };
};
const createTubManager = (() => {
const _tubMarkers = [];
const addMarker = ($, marker) => {
// $.log(`addMarker`);
if (marker) {
_tubMarkers.push(marker);
}
};
const sendMessageMarker = ($, number, key, message) => {
// $.log("sendMessageMarker");
if (_tubMarkers.length > 0) {
let index = number % _tubMarkers.length;
// $.log("_tubMarkers.length:" + _tubMarkers.length);
_tubMarkers[index].send(key, message);
}
};
return {
addMarker, sendMessageMarker
};
});
const phaseManager = createPhaseManager();
const tubManager = createTubManager();
const Update = ($, deltaTime) => {
phaseManager.UpdateTime($, deltaTime);
let position = subNodePosition.getPosition();
position.z = phaseManager.GetPhase($) * width;
subNodePosition.setPosition(position);
// インターバルをすぎたら、_giftSenderの先頭要素を取得してphaseManagersの
{
let time = $.state._time ?? 0;
time += deltaTime;
const INTERVAL = 2;
if (time > INTERVAL) {
time = 0;
if (_giftSender.length > 0) {
let giftSender = _giftSender.shift();
// $.log("Update giftSender:" + giftSender);
const { number, displayName, position } = giftSender;
// $.log("Update displayName:" + displayName);
// $.log("Update displayName:" + displayName);
// $.log("Update position:" + position);
// $.log("Update number:" + number);
const key = "<tub manager> Set Position";
tubManager.sendMessageMarker($, number, key, { position: position, displayName: displayName });
}
}
$.state._time = time;
}
}
PushGiftInfo = ($, displayName) => {
let number = $.state._number ?? 0;
let position = subNodePosition.getPosition();
// $.log("PushGiftInfo position:" + position);
_giftSender.push({ number, displayName, position });
$.state._number = number + 1;
}
AddTub = ($, tub) => {
tubManager.addMarker($, tub);
}
return { Update, PushGiftInfo, AddTub };
})($);
$.onUpdate(deltaTime => {
NodeManager.Update($, deltaTime);
});
$.onGiftSent((gifts) => {
// $.log("onGiftSent called");
for (let i = 0; i < gifts.length; i++) {
let displayName = gifts[i].senderDisplayName;
NodeManager.PushGiftInfo($, displayName);
}
});
const handleInitializeHandles = initializeHandles();
$.onStart(() => {
handleInitializeHandles($);
});
$.onReceive((messageType, arg, sender) => {
// $.log(`<manager> onReceive: ${arg}`);
switch (messageType) {
case "<tub marker> receive initialize":
// $.log(`<tub marker> receive initialize: ${sender}`);
NodeManager.AddTub($, sender);
break;
}
}, { item: true, player: false });
- Rigidbodyコンポーネントで重力onにすると変な挙動になる件
Rigidbodyコンポーネントで重力onにすると、宙に浮くけどその場にとどまらないような変な挙動になったと思います。変だなーとしか思わなかったんですが、Movable Itemが必要なんですね。。。
Rigidbodyコンポーネントは、Movable Item コンポーネントが追加されている場合のみ使用されます。
たらいが下に落ちなくて困った記憶があります。
- マテリアルのemission切り替え
https://discord.com/channels/682526731311251636/1296013133672091672/1344348109777670227
にも書いたのですが、ゲームオブジェクトのマテリアルのemission切り替えは、マテリアルのemissionフラグが立っていないと効かないような感じです。細かい仕様は調べていません、ごめんなさい。
つかうとemissionが変わるコードです。供養供養。
emission
const materialHandle = $.material("material0");
let colorIndex = 0;
const rainbowColors = [
[1, 0, 0], // Red
[1, 0.5, 0], // Orange
[1, 1, 0], // Yellow
[0, 1, 0], // Green
[0, 1, 1], // Cyan
[0, 0, 1], // Blue
[1, 0, 1] // Magenta
];
$.onUse((isDown, player) => {
// $.log(`colorIndex=${colorIndex}`);
if (!isDown) { return; }
if ($.getGrabbingPlayer() === null) { return; }
const [R, G, B] = rainbowColors[colorIndex];
// $.log(`{R,G,B}=${R},${G},${B}`);
materialHandle.setEmissionColor(R, G, B, 1);
colorIndex = (colorIndex + 1) % rainbowColors.length; // get next index
});
- 寿限無のコメント
もう一つ、寿限無をコメントするイベント用ワールド作ったので備忘録のために記載します。
jugemu
// const subNodeText = $.subNode("Text");
const JugemuManager = (($) => {
const jugemuFull =
"じゅげむ" + "じゅげむ" +
"ごこうのすりきれ" +
"かいじゃりすいぎょの" +
"すいぎょうまつ" + "うんらいまつ" + "ふうらいまつ" +
"くうねるところにすむところ" +
"やぶらこうじのぶらこうじ" +
"ぱいぽぱいぽぱいぽのしゅーりんがん" +
"しゅーりんがんのぐーりんだい" +
"ぐーりんだいのぽんぽこぴーのぽんぽこなーの" +
"ちょうきゅうめいのちょうすけ";
const toHiragana = (text) => {
const mapping = {
"寿": "じゅ", "限": "げ", "無": "む",
"五": "ご", "劫": "こう", "擦": "す", "切": "き",
"海": "かい", "砂": "じゃ", "利": "り",
"水": "すい", "魚": "ぎょ",
"行": "ぎょう", "末": "まつ", "雲": "うん", "来": "らい", "風": "ふう",
"食": "く", "寝": "ね", "処": "ところ", "住": "す",
"藪": "やぶ", "柑": "こう", "子": "じ",
"パ": "ぱ", "イ": "い", "ポ": "ぽ", "シ": "し", "ュ": "ゅ", "ー": "ー",
"リ": "り", "ン": "ん", "ガ": "が", "グ": "ぐ", "ダ": "だ", "コ": "こ",
"ピ": "ぴ", "ナ": "な",
"長": "ちょう", "久": "きゅう", "命": "めい", "助": "すけ",
"ジ": "じ", "ゲ": "げ", "ム": "む",
"ゴ": "ご", "ウ": "う", "ノ": "の", "ス": "す", "キ": "き", "レ": "れ",
"カ": "か", "ャ": "ゃ",
"ギ": "ぎ", "ョ": "ょ",
"マ": "ま", "ツ": "つ", "ヤ": "や", "ブ": "ぶ", "ラ": "ら", "フ": "ふ",
"ク": "く", "ネ": "ね", "ル": "る", "ト": "と", "ロ": "ろ", "ニ": "に",
"チ": "ち", "メ": "め", "ケ": "け",
};
return text.split("").map(c => mapping[c] || c).join("");
};
_dancingPlayers = []; //ダンスプレイヤー
let _commentQueue = [];
let _isJugemuMode = false;
let _currentIndex = 0;
const sound = $.audio("Sound");
const soundEffect = $.audio("SoundEffect");
function StopJugemu($) {
_isJugemuMode = false;
sound.stop();
_currentIndex = 0;
_dancingPlayers = [];
}
const PopComment = ($) => {
// $.log("_commentQueue" + _commentQueue);
if (_commentQueue.length > 0) {
const comment = _commentQueue.shift();
// $.log("comment" + comment);
// $.log("_currentIndex" + _currentIndex);
let input = toHiragana(comment.body.trim());
// $.log("input: " + input);
let expected = jugemuFull.slice(_currentIndex, _currentIndex + input.length);
// $.log("expected: " + expected);
if (_isJugemuMode) {
if (expected === input) {
// 入力が現在のターゲットの続きなら進行
_currentIndex += input.length;
let playerHandle = comment.sender;
if (playerHandle !== null) {
// not ghost or group viewing users
let players = _dancingPlayers;
_dancingPlayers = players.concat(playerHandle);
}
if (_currentIndex === jugemuFull.length) {
// $.log("ok"); // 全部正しく言えた
StopJugemu($);
soundEffect.play();
}
} else {
// 入力が現在のターゲットの続きでないなら失敗
// $.log("ng");
StopJugemu($);
}
} else {
StopJugemu($);
if (jugemuFull.startsWith(input)) {
// 「じゅ」などの部分入力でもモード開始
_isJugemuMode = true;
sound.play();
_currentIndex = input.length;
let playerHandle = comment.sender;
if (playerHandle !== null) {
// not ghost or group viewing users
let players = _dancingPlayers;
_dancingPlayers = players.concat(playerHandle);
}
}
}
}
}
const AddComments = ($, comments) => {
// $.log("AddComments " + comments.map(c => c.body));
_commentQueue = _commentQueue.concat(comments);
}
// idでフィルタリングと重複削除を行う関数
function filterAndRemoveDuplicates(array) {
const seenIds = new Set(); // 重複をチェックするためのセット
return array.filter((obj) => {
// 重複チェック
if (seenIds.has(obj.id)) {
return false; // すでに同じidのオブジェクトがある場合は除外
} else {
seenIds.add(obj.id); // 新しいidをセットに追加
return true; // 重複していない場合は残す
}
});
}
const humanoidAnimation = $.humanoidAnimation("Dance0");
const _danceLength = humanoidAnimation.length;
_danceTick= 0; //ダンスの秒数
const Dance = ($) => {
// $.log("dancingPlayers: " + _dancingPlayers.length);
// $.log("danceTick: " + danceTick);
// $.log("danceLength: " + _danceLength);
//現在のダンス秒数から現在のポーズを取得
let pose = humanoidAnimation.getSample(_danceTick);
let dancingExistingPlayers = _dancingPlayers.filter((player) => player.exists());
let duplicatesRemovedArray = filterAndRemoveDuplicates(dancingExistingPlayers);
//ワールドで対象のプレイヤーにポーズをとらせる
duplicatesRemovedArray.forEach((p) => p.setHumanoidPose(pose, { timeoutSeconds: 1.0, timeoutTransitionSeconds: 0.3, transitionSeconds: 0.11 }));
//今回いたプレイヤーは記録しておく
_dancingPlayers = dancingExistingPlayers;
}
const UpdateDance = ($, deltaTime) => {
//ダンスの秒数を進める
_danceTick += deltaTime;
if (_danceTick > _danceLength) {
//ダンスが終わったら先頭のほうに戻す
_danceTick -= _danceLength;
}
}
return { PopComment, AddComments, UpdateDance, Dance };
})($);
$.onCommentReceived((comments) => {
JugemuManager.AddComments($, comments);
})
const IntervalUpdater = (() => {
let _tick = 0; // 切りかえを行うまでの秒数を計測
const INTERVAL = 0.25; // 適当な秒数
const IsEnough = ($, deltaTime) => {
_tick += deltaTime;
if (_tick < INTERVAL) {
return false;
}
_tick -= INTERVAL;
return true;
}
return { IsEnough }
})();
$.onUpdate((deltaTime) => {
JugemuManager.PopComment($);
JugemuManager.UpdateDance($, deltaTime);
if (!IntervalUpdater.IsEnough($, deltaTime)) {
return;
}
JugemuManager.Dance($);
});
- ダンスの設定
Humannoid Animation Listでダンスを設定しています。
痛い目を見ているのでアニメーションの設定画像も添付します。
Rigタブのアニメーションタイプと、Animationタブの時間をループオプションは何度も設定を忘れます。
またItemAudioSetListで音楽をセットしています。
- 車の操作のコメント
ワールドのアイテムをコメントで操作する備忘録も記述します
** 素材はこちらを利用しています。あくまで見た目を利用しており、自作したモデルで問題ないです。
*** cluster向けドライブワールド・レースゲーム制作キット「LinxCarSystem」 + オリジナルカーモデル「Felis」
*** https://booth.pm/ja/items/5160159
** Phsic Materialの作成
*** もうなぜ作ったのか記憶にないのですが、クルマが跳ねるためにマテリアルを作成しています。参考ページはこちら。
** Unityでのコンポーネント設定
*** おおもとのゲームオブジェクトにScriptable ItemとMovable ItemとRigidbodyを設定しています。抗力は適切な値を設定してください。
*** サブノードの一つはBoxColiderを設定しています。マテリアルに自作したPhysic Materialを設定します
** サンプルコード
*** たくさんあるうち、本質的なコードの抜出を共有します。特定の挙動単位で高階関数を用意します。コメントのコールバック関数onCommentReceivedと物理挙動をするタイミングのコールバック関数onPhysicsUpdateでそれぞれ呼ばれるための関数を高階関数は提供します。
*** 位置や回転を指定するとき、グローバル座標系で指定することになります。アイテムの正面方向をグローバル座標系で取得するには、アイテムの正面方向をアイテムの座標系からグローバル座標系へ変換するだけでいいですね。この座標系の変換関数の取得と掛け方についてちゃんと理解している人はほんの一握りだと思います(僕もよく知らない)。頑張って何か書こうと思いましたがよく知らないので諦めます。。。
回転サンプル
const dirUpItem = new Vector3(0, 1, 0) // アイテムの上のベクトル
const RotateManager = (($) => {
const Regex = /(回転|かいてん|カイテン|ローテーション)/;
let _active = false
const onCommentReceived = ($, message) => {
if (Regex.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_active = false;
let itemRotation = $.getRotation();
const intensityTorque = 4;
const dirTorque = dirUpItem.clone().multiplyScalar(intensityTorque);
const velTorque = dirTorque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velTorque);
const intensity = 3;
const dir = dirUpItem.clone().multiplyScalar(intensity);
$.addImpulsiveForce(dir);
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
$.onCommentReceived((comments) => {
// $.log("comments " + comments.map(c => c.body));
for (const comment of comments) {
RotateManager.onCommentReceived($, comment.body);
}
})
$.onPhysicsUpdate(deltaTime => {
RotateManager.onPhysicsUpdate($, deltaTime);
});
** コード
*** SubNodeの表示切替とかいろいろやってますのでたくさん行数があります。後この後音鳴らしたり、particle表示したりするのでだいぶ変更しますね。すみませんね
ポップサンプル
const dirFrontItem = new Vector3(1, 0, 0) // アイテムの正面のベクトル
const dirUpItem = new Vector3(0, 1, 0) // アイテムの上のベクトル
const dirRightItem = new Vector3(0, 0, 1) // アイテムの右のベクトル
const DangerousDrivingManager = (($) => {
const REGEX = /(あおり|煽り|アオリ)(運転|うんてん|ウンテン)/;
const INTERVAL = 0.5; // [s]
const TRY_COUNT = 4;
let _active = false
let _counter = 0;
let _time = 0;
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_time += deltaTime;
if (_time < INTERVAL) {
return;
}
_time = 0;
// Y軸(上)方向に力を加える
$.addImpulsiveForce(dirUpItem);
// Y軸回転を加える
let itemRotation = $.getRotation();
let torque = _counter % 2 == 0 ? dirUpItem : dirUpItem.clone().negate();
// $.log("torque: " + torque);
const velocity = torque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velocity);
if (_counter >= TRY_COUNT) {
_counter = 0;
_active = false;
}
_counter++;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const AccelerationManager = (($) => {
const UpRegex = /(u|U|↑|上)/;
const DownRegex = /(d|D|↓|下)/;
let _upActive = false
let _downActive = false
const onCommentReceived = ($, message) => {
if (UpRegex.test(message)) {
_upActive = true;
}
if (DownRegex.test(message)) {
_downActive = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_upActive ^ _downActive) {
let itemRotation = $.getRotation();
const dir = _upActive ? dirFrontItem.clone() : dirFrontItem.clone().negate();
let intensity = 4;
const velocity = dir.applyQuaternion(itemRotation).multiplyScalar(intensity);
$.addImpulsiveForce(velocity);
_upActive = false;
_downActive = false;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const PopManager = (($) => {
const Regex = /(ポップ|ぽっぷ)/;
let _active = false
const onCommentReceived = ($, message) => {
if (Regex.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_active = false;
let itemRotation = $.getRotation();
const dir = dirFrontItem.clone().add(dirUpItem.clone());
const intensity = 4;
const velocity = dir.applyQuaternion(itemRotation).multiplyScalar(intensity);
// $.log("addImpulsiveForce: velocity: " + velocity);
$.addImpulsiveForce(velocity);
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const RotateManager = (($) => {
const Regex = /(回転|かいてん|カイテン|ローテーション)/;
let _active = false
const onCommentReceived = ($, message) => {
if (Regex.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_active = false;
let itemRotation = $.getRotation();
const intensityTorque = 4;
const dirTorque = dirUpItem.clone().multiplyScalar(intensityTorque);
const velTorque = dirTorque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velTorque);
const intensity = 3;
const dir = dirUpItem.clone().multiplyScalar(intensity);
$.addImpulsiveForce(dir);
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const LeftRightManager = (($) => {
const LeftRegex = /(←|ヒダリ|左|ひだり)/;
const RightRegex = /(→|ミギ|右|みぎ)/;
let _leftActive = false
let _rightActive = false
const onCommentReceived = ($, message) => {
if (LeftRegex.test(message)) {
_leftActive = true;
}
if (RightRegex.test(message)) {
_rightActive = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_leftActive ^ _rightActive) {
let itemRotation = $.getRotation();
const dir = _rightActive ? dirUpItem.clone() : dirUpItem.clone().negate();
const intensityTorque = 4;
const dirTorque = dir.clone().multiplyScalar(intensityTorque);
const velTorque = dirTorque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velTorque);
_leftActive = false;
_rightActive = false;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const SuperAccelerationManager = (($) => {
const REGEX = /(超|ちょう|チョウ)(加速|かそく|カソク)/;
let _active = false
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_active = false;
let itemRotation = $.getRotation();
const dir = dirFrontItem.clone();
const intensity = 100;
const velocity = dir.applyQuaternion(itemRotation).multiplyScalar(intensity);
$.addImpulsiveForce(velocity);
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const DoughnutManager = (($) => {
const REGEX = /(ドーナッツターン|どーなっつたーん)/;
const INTERVAL = 5; // [s]
let _active = false
let _time = 0;
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_time += deltaTime;
if (_time > INTERVAL) {
_active = false;
_time = 0;
return;
}
let itemRotation = $.getRotation();
let torque = dirUpItem;
const velocityTorque = torque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velocityTorque);
let intensity = 0.5;
const dir = dirFrontItem.clone().multiplyScalar(intensity);
// $.log("dir: " + dir);
const velocity = dir.applyQuaternion(itemRotation);
$.addImpulsiveForce(velocity);
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const DeathRoleManager = (($) => {
const REGEX = /(デスロール|ですろーる)/;
const INTERVAL = 0.5; // [s]
const TRY_COUNT = 6;
let _active = false
let _counter = 0;
let _time = 0;
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_time += deltaTime;
if (_time < INTERVAL) {
return;
}
_time = 0;
$.addImpulsiveForce(dirUpItem);
let itemRotation = $.getRotation();
let intensity = 2.0;
let torque = dirFrontItem.clone().multiplyScalar(intensity); // X軸回転
const velocity = torque.applyQuaternion(itemRotation);
$.addImpulsiveTorque(velocity);
if (_counter >= TRY_COUNT) {
_counter = 0;
_active = false;
}
_counter++;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const LovingManager = (($) => {
const REGEX = /(愛|あい|アイ)しているの(サイン|さいん)/;
let hazardLampNode = $.subNode("HazardLamp");
const INTERVAL = 0.5; // [s]
const TRY_COUNT = 10;
let _active = false
let _counter = 0;
let _time = 0;
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_time += deltaTime;
if (_time < INTERVAL) {
return;
}
_time = 0;
let currentActive = hazardLampNode.getEnabled();
hazardLampNode.setEnabled(!currentActive);
if (_counter >= TRY_COUNT) {
_counter = 0;
_active = false;
hazardLampNode.setEnabled(false);
}
_counter++;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
const PumpingBrakeManager = (($) => {
const REGEX = /(ポンピングブレーキ|ぽんぴんぐぶれーき)/;
let hazardLampNode = $.subNode("BrakeLamp");
const INTERVAL = 0.1; // [s]
const TRY_COUNT = 6;
let _active = false
let _counter = 0;
let _time = 0;
const onCommentReceived = ($, message) => {
if (REGEX.test(message)) {
_active = true;
}
}
const onPhysicsUpdate = ($, deltaTime) => {
if (_active) {
_time += deltaTime;
if (_time < INTERVAL) {
return;
}
_time = 0;
let currentActive = hazardLampNode.getEnabled();
hazardLampNode.setEnabled(!currentActive);
if (_counter >= TRY_COUNT) {
_counter = 0;
_active = false;
hazardLampNode.setEnabled(false);
}
_counter++;
}
}
return { onCommentReceived, onPhysicsUpdate };
})($);
$.onCommentReceived((comments) => {
// $.log("comments " + comments.map(c => c.body));
for (const comment of comments) {
AccelerationManager.onCommentReceived($, comment.body);
DangerousDrivingManager.onCommentReceived($, comment.body);
DeathRoleManager.onCommentReceived($, comment.body);
DoughnutManager.onCommentReceived($, comment.body);
LeftRightManager.onCommentReceived($, comment.body);
LovingManager.onCommentReceived($, comment.body);
PopManager.onCommentReceived($, comment.body);
PumpingBrakeManager.onCommentReceived($, comment.body);
RotateManager.onCommentReceived($, comment.body);
SuperAccelerationManager.onCommentReceived($, comment.body);
}
})
$.onPhysicsUpdate(deltaTime => {
AccelerationManager.onPhysicsUpdate($, deltaTime);
DangerousDrivingManager.onPhysicsUpdate($, deltaTime);
DeathRoleManager.onPhysicsUpdate($, deltaTime);
DoughnutManager.onPhysicsUpdate($, deltaTime);
LeftRightManager.onPhysicsUpdate($, deltaTime);
LovingManager.onPhysicsUpdate($, deltaTime);
PopManager.onPhysicsUpdate($, deltaTime);
PumpingBrakeManager.onPhysicsUpdate($, deltaTime);
RotateManager.onPhysicsUpdate($, deltaTime);
SuperAccelerationManager.onPhysicsUpdate($, deltaTime);
});
Discussion