見出し画像

【Advent Calendar 2021】VideoPokerのあれこれ

この記事は、Advent Calendar 2021 VCIの4日目の記事です。

VideoPokerVCIとは

まずこの記事のVideoPoker VCIについてです。
名前の通り、ゲームセンターのメダルコーナーにあるひっそりと置かれてるゲームを再現したVCIです。
SEED Onlineで無料公開されており、誰でも無料で取り込みいつでもどこでもVideoPokerが楽しめるようになっています。

画像1

SEED Online - VideoPoker

どうしてVideoPokerを作ろうと思ったのか

実はメダルゲームとしてのVideoPokerは一回も遊んだことがなく、
とあるソシャゲのミニゲームとして実装されていたものを遊んだぐらいでした。
それでも結構ゲームとして中毒性があったのと、VRChatでちょうどUdonChipsがリリースされたタイミングだったのもあり、せっかくなのでUdonChips向けに作ってみようと思ったのがきっかけでした。

booth.pm - UdonChips対応VideoPoker

UdonChips向けのVideoPokerはUdonSharpを利用して作ってあります。
こだわりとして出てきたカードは全員に同期したら盛り上がるんじゃないかとか、15回連続でダブルアップに成功したらジャックポットするとか色々な要素を盛り込んでみました。
ですが、まだU#の知識が浅い状態で作ったものなので、数多く設置すると同期周りでパケットロスが発生するかもしれないのでワールドに導入する場合は程々にしておいてください・・・(気が向いたら直します)

UdonChips版からVCI版を作るに当たってぶち当たったコトとか技術的な話

そもそもにVCIはLua言語でスクリプトを実装していくので、ほとんどC#で書かれているコードを全部Luaに置き換えていく必要がありました。
ちょこっと変えればいけるかな?って思ってましたがそんなことはなく、ほとんど1から作るレベルで作り直しました。
色々作る点で困った点とか、この変更やって作ってみたをダラダラと書いていきます。
そのため此処から先は技術的な話しメインになるので、技術的な話しに興味がない方は読み飛ばしてください。

uGUIが使えない

まずVRChat版から移植する時に一番困ったのはこれです。
もともとVRChat版はUnityの機能がほとんど使えたので、uGUIで作ってました。
なるべく見た目同じで操作感を変えたくなかったので全てQuadMeshに置き換え、操作用のレーザーを用意することで操作できるようにしました。
ではどのようにレーザー操作を行えるようにしているかですが次のサンプルソースを用意しました。

function onUse()
   if SelectedButton ~= "" then
       -- SelectedButton を選択した時の処理を実行する
   end
end

function onTriggerEnter(item, hit)
   if item == "Laser" then
       -- 手にあたったら無視
       if hit == " HandController" then
           return
       end

       -- 当たってるものの判定
       if hit == "ButtonA" then
           SelectedButton = hit
           return
       end
   end
end

function onTriggerExit(item, hit)
   if item == "Laser" then
       if hit == "HandController" then
           return
       end

       if hit == "ButtonA" then
           if SelectedButton == hit then
               SelectedButton = ""
           end
       end
   end
end

簡単に解説するとOnTriggerEnterでレーザーに当たったObject名を保存し、OnUseが実行されたタイミングでそのボタンが押された処理を行うようにしています。
ちゃんとフィルター処理が必要にはなってしまいますが、これでちょっと離れていても色々さなそうができるようになります。

カードの状態同期について

1人でも黙々と遊べるゲームですが、観戦者がいたら盛り上がったり会話が生まれるようにしたかったのでカードの状態を同期するようにしました。
ここで、状態を同期するために「vci.state」を使いたくなりますが、頻繁に書き換わる値の同期では「vci.state」はデバッグのログが流れすぎて不向きだと思ったので、SubItemとして同期するようにしました。
ではみえているカード自体にSubItemのコンポーネントをつけているかと、そうではありません。

画像2

別の見えないオブジェクトを1個用意してそこにSubItemをつけています。
そしてSubItemの localPositionとLocalScaleの値をそれぞれフラグとして利用します。
今回でいうと次のようになってます

-- 場に出てる、開いてる、HOLDしてる
card1.SetLocalScale(Vector3.__new(0, 0, 0))
-- 数字、スート、NONE
card1.SetLocalPosition(Vector3.__new(1, 0, 0))

つまり、「♣3で場に出されて表向きで未HOLD」となっていた場合、このSubItemは 「LocalPosition(3,1,0)」 「LocalScale(1,1,0)」で存在しているということになります。

なぜ本来のSubItemの座標同期を使わずにこの様な面倒くさい方法をとっているかというと、
こういった座標同期はプレイ中のユーザーの通信環境やサーバーの同期タイミングに大きく左右されやすいものです。
このためプレイ中のユーザー側ではきれいな移動・回転演出が行えていても、観戦者側では瞬間移動や途中で止まってしまうことが発生しゲームとして「重たいのかな?」とか「止まった?」というマイナスな印象を与えてしまう可能性があります。
ゲーム体験を損なわないよう、演出はローカルで制御しスムーズな演出を見てもらえるようにしています。
このように同期するものが少なくて、きれいな演出をしたい場合はこのようにSubItemをフラグとして扱うのはちょっとの手間でだいぶクオリティアップに繋がるのでおすすめです。

最後に

あまりこの記事を書く時間が取れず、あまり技術的な面で見てもだいぶ内容が薄い記事となってしまいました・・・
今回書いた場所以外で他にも「この辺の処理どうなってるの?」とかあったら色々追記していこうと思うのでバーチャルキャストのお座敷で会った時とか、この記事にコメントとかしてもらえると嬉しいです!

またちょっとずつですが、らーめんさんのカジノチップVCIとの連携版も作っているのでお楽しみに。

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