質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2882閲覧

「Updateと各ボタンのコールバック関数」のみで、入力と描画を完結させる方法

tsubozaemon

総合スコア11

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2018/11/19 21:59

編集2018/11/20 13:27

前提・実現したいこと

「Updateと各ボタンのコールバック」しか実装していないコードで、
「一部処理をFixedUpdateに切り出す」等をせず、「早すぎない描画」と
「ある程度正確な入力受け取り」を実現する方法

発生している問題・エラーメッセージ

開発当初、Unityの「Updateの間隔」や「入力受け取り」について知らないままに
実装を始めてしまいました。
結果、現在の実装は各シーンごとに
・start()完了後に実行されるのは、updateと各ボタンのコールバック関数のみ
・updateでは「if(Input.GetMouseButtonDown(0))後に画面クリック時の処理」と
「毎フレームの計算処理(移動、カウンタ増加など)」を行っている
・コールバック関数では、各ボタンに対応した処理を書いている
と、updateに「入力受け取り」「定周期で行うべき計算」「描画」が混在してしまっています。

開発マシンでは不思議と「ちょうどいい速度」だったので、気にならなかったのですが
かなり形になってから別マシンで動かす機会があり、「Updateごとに移動などを行う」と
基本的に早すぎる、ということが(今更)判明しました。

「今からUpdateを修正し、入力以外の処理をFixedUpdateに切り出す」のも考えたのですが、
Update内は「入力受け取り」「定周期で行うべき計算」「描画」が相当強く相互に結びついてしまっており、下手に切り出すとバグの原因になりそうで、それは最終手段にしたいです。

 下記のソースが、「とりあえずの現実解」として、今動かしている実装なのですが
何かアイデアや、逆に「〇〇××しないと、この実装は致命的な問題がある」など
ご意見があったら教えてください。

該当のソースコード

// 現在のコードであり、当初の実装ではありません

Unity

1 void Start () { 2 button[i].GetComponent<Button>().onClick.AddListener(() => OnButtonClick(index)); 3 } 4 5 void Update() { 6 if (Input.GetMouseButtonDown(0)) 7 { 8 // 今回「IsPassedInterval == false」になってUpdateを抜ける場合でも 9 // 「処理のインターバル中に画面はクリックした」という事実は保管しておかないと 10 // クリックが無かったことにされる可能性がある 11 isGamenClick = true;//isGamenClickはグローバル静的変数 12 } 13 if (!IsPassedInterval()) return; 14 if (cantSyoriCounter > 0) cantSyoriCounter--;//cantSyoriCounterはグローバル静的変数 15 if (isGamenClick)//当初はif(Input.GetMouseButtonDown(0)) 16 { 17 isGamenClick = false;//こうしないと次回Update時もコールされてしまう 18 ClickWindow();//画面クリック時の処理 19 } 20 // 以降、毎フレームごとの描画処理など 21 //(例)攻撃演出中 22 if (gameState == GAME_STATE_ATTACK) { 23 24 if (ensyutsuCounter <= 15) { 25 printDamage();//ダメージ量を表示 26 } 27 if (ensyutsuCounter == 10) { 28 targetHp -= damage;//ターゲットのHPを今回のダメージ分だけ減らす 29 } 30 ensyutsuCounter--; 31 if (ensyutsuCounter == 0) { 32 // 攻撃演出状態終了。初期状態に戻る 33 gameState == GAME_STATE_FIRST; 34 } 35 } 36 } 37 38 // 一定周期ごとにupdateの実質処理が行われるようにチェック 39 public static Boolean IsPassedInterval() 40 { 41 tempInterval += Time.deltaTime;//tempIntervalはグローバル静的変数 42 if (tempInterval > UPDATE_INTERVAL) 43 { 44 tempInterval = 0; 45 return true; 46 } 47 return false; 48 } 49 50 void OnButtonClick(int index){ 51 // ボタンを押された時の、カウンタの増加やゲーム内状態変数の変更など 52 if (cantSyoriCounter != 0) 53 { 54 return; 55 } 56 //ボタン押したときの処理(例) 57 if (gameState == GAME_STATE_ATTACKCHECK) {//攻撃決定するかの確認状態 58 gameState = GAME_STATE_ATTACK;//攻撃の演出に入る 59 ensyutsuCounter = 20;//20フレーム間、攻撃演出 60 cantSyoriCounter = 30;//連打で一気に進む防止のため 61 } 62 }

試したこと

まずは、上記のソースでの「IsPassedInterval」処理を追加しました。
が、それだけだと、「スキップされるupdate()」で拾った画面クリックが
いざ「スキップしないupdate()」が来た時に、無視されてしまうことに気づきました。
よってisGamenClickフラグを追加しました。

ボタンについては、cantSyoriCounterで「どんなに連打しても、
スキップしないupdateが30回呼ばれるまでは無視する」ようにしました。

思想としては、実装された処理の違いから
画面クリックは「クリックしたことを落とさないこと」重視、
ボタンクリックは「連打で一気に進まないこと」重視(cantSyoriCounterに引っかかって
無視されることがあるのは、大した問題ではないと判断)です。

補足情報(FW/ツールのバージョンなど)

Collision系の処理は使用していないので、気にしなくてよいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sakura_hana

2018/11/20 01:47

現状の移動のスクリプトをざっくりとでもいいので記載頂けますか?(多分そっちにTime.deltaTime入っていないだけだと思いますが)/また、やりたいことは「入力受け取りは毎フレーム行うが、一度入力があった後一定時間は受け取りたくない(連打は無効)」「計算は現実時間単位の一定周期(3秒毎とか)で行いたい」「フレームレートが変わっても移動は等速にしたい」でいいですか?(「描画」が指す内容によってはちょっと変わりそうですが)
tsubozaemon

2018/11/20 13:41 編集

「移動」という単語を使ってしまいましたが、マス目RPGですのでUpdate内での移動は「実質演出」です(目標マスへの移動自体は移動入力の瞬間で完了し、update内では実際には座標値の操作は行っていない。演出カウンタに応じた見た目の移動だけです)。それは大して重要な処理ではないので、かわりに、OnClickとUpdateの最重要処理である「攻撃コマンドハンドリング」の内容をソースに書いてみました。「やりたいこと」については、むしろ、「〇〇でなければいい」という感じです。具体的には「Updateでの演出の進行(上記でのensyutuCounterの減少)が、リアル時間で早すぎなければいい(演出が一瞬で終わらないように)」「updateで特定の1フレームのみ実行すべき処理(上記例でのターゲットのHP減少など)が、飛ばされたり2回呼ばれるのは絶対にNG」「画面タッチ(Input.GetMouseButtonDown)については、タッチしたのにいつまでも無反応という事態が起きなければいい」「ボタンについては、連打してしまった時に、画面更新が追いつく前に一気に処理が進まなければいい(連打が必要なゲームではないので、連打が無効化されるのは問題ない)」という3点が条件です。ご質問の回答としては、連打は無効→必須、計算は一定周期→そこまで厳密でなくていいが、早すぎて演出が一瞬で終わってしまうのは困る、フレームレートが変わっても移動(に限らず演出全般)は等速→別に必要ではない、ということになります。※フレームレートが期待より「遅い」場合、すべてが均等にもたつく感じになりますが、それは仕方ないと考えています。
guest

回答1

0

ベストアンサー

ご存知かもしれませんがおさらいから。
Updateは1フレームに1回呼ばれ、フレームレート(1秒間が何フレームか)はゲームの負荷により変わります。
つまりUpdateは、ある時は1秒間に60回呼ばれ、ある時は1秒間に30回呼ばれます。
つまり、例えば「20フレーム」と指定してしまうと、ある時は0.3秒かかり、ある時は0.6秒かかります。

なのでまずIsPassedIntervalは使わない方針でいいと思います。

「演出がリアル時間で早すぎないように」というのはつまり「どのような環境下(=フレームレートが変わって)でも○秒間表示させる」ということなので、演出については「○フレーム」ではなく「○秒」と指定するのが適切です。
ensyutsuCounterはフレーム単位ではなく秒単位と認識し、
ensyutsuCounter -= Time.deltaTime;などとして管理した方がいいでしょう。
(間にダメージ量表示等の処理を挟む場合でも「演出開始から○秒後、まだ呼ばれていなかったら処理を呼ぶ」みたいな作りにしておけばいいかと)

「連打の無効化」についても同様で、フレーム単位ではなく秒単位での管理にすべきかと思います。

その上で「画面タッチ」は毎フレーム受け付けにしておいて「演出中」「連打無効化中」(必要に応じて「計算中」も)なら何もしないでいいのではないかと思います。
(先行入力させたいなら「演出中」等にタッチされたら「タッチした」というフラグを立てておき、演出処理終了後にフラグをチェック、立っていたら「入力された」ということにして再度処理でいいかと)

希望通りの動きになるか分かりませんが、ご参考までに。

投稿2018/11/21 05:04

sakura_hana

総合スコア11427

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tsubozaemon

2018/11/21 11:51

すみません、やはり結局、IsPassedIntervalを使用する方法を選択します。 >つまり、例えば「20フレーム」と指定してしまうと、ある時は0.3秒かかり、ある時は0.6秒かかります。 >なのでまずIsPassedIntervalは使わない方針でいいと思います。 サンプルに載せた実装ですと、ensyutsuCounter をデクリメントするのは「IsPassedInterval = trueになったUpDateInterval」の中だけです。 IsPassedIntervalがtrueになるのはUPDATE_INTERVAL周期なので、「updateがコールされる間隔が、UPDATE_INTERVAL未満である限りは」マシンの負荷に関わらず、ensyutsuCounterが0になるまでの時間は「20 * UPDATE_INTERVAL」です。 ご提案の方法を採用しない理由ですが、 ・「演出開始から○秒後、まだ呼ばれていなかったら処理を呼ぶ」の中で、「まだ呼ばれてなかったら」を管理するフラグが多数の処理ごとに必要となること ・「演出中に実施する処理A、Bがあり、A→Bの順にやらなければならない」ような箇所が多数あり  「演出開始から〇秒後」のみのチェックだと、順序が怪しくなると思われる場所が多数あること  例 A:ターゲット敵のHP減少 B:敵全滅チェック   現在「 if (ensyutsuCounter = 5)処理B if (ensyutsuCounter = 10)処理A」  のように書いているところを、一括置換的に   if (演出開始から0.1秒以上 && 処理B未実施)処理B if (演出開始から0.05秒以上 && 処理A未実施)処理A  のように変えてしまうと、「処理が遅くて演出開始から0.12秒後に、はじめて上記箇所が呼ばれた」  場合、B→Aと実行されてしまう。 もちろん、上記はご回答を批判するものではなく、「もともとの描画/入力受け取り/計算の実装を、あまりに単純に考えすぎていたため、今から1フレームの概念を本格的に組み直すとなると非常に厳しい」という、私の方の問題です。 せっかくご回答いただいたのにすみません。
sakura_hana

2018/11/21 12:39

最終的にどうするかはご自分で決めるものなので、そこはお好きにどうぞ。 私も薄々「結果的にIsPassedInterval使う場合と変わらない気もするなー」と思ってましたし。 でもそうすると、なんで環境によって描画が早くなるのか分からんのです。 例えばUPDATE_INTERVALに「0.03」って指定してて(=30fps)、 実環境が60fpsの場合は2回に1回スキップされるから結局30fpsで描画される。(これは問題無し) 逆に実環境が15fpsの場合、1回ごとに呼び出されるが、Updateの時間は30fpsより長い。 ここで演出を「20フレーム」と指定している場合、30fpsなら0.6秒で完了、15fpsなら1.33秒で完了。 つまり本当は「開発機より性能の高いマシンだと、開発機と演出時間は同じ」「開発機より性能が低いマシンだと、開発機より演出が遅い(長い)」はずなのです。 結局よく分からんから全部フレームじゃなくて時間単位で管理すればよくね?って発想でした。 ちなみに if (演出開始から0.1秒以上 && 処理B未実施)処理B if (演出開始から0.05秒以上 && 処理A未実施)処理A こいつは if (演出開始から0.05秒以上 && 処理A未実施)処理A if (演出開始から0.1秒以上 && 処理B未実施)処理B こうすれば1.2秒後にこのメソッドに入ったとしても絶対に処理A→Bの順に行われます。 (本来0.05〜0.1秒の間に行われるはずだった演出がある場合は飛ばされますが) 更に一応別の選択肢を挙げるならば、諸々の処理をコルーチン化すると 「AとBを並列実行」「Aの処理完了を待ってからBを実行」「AとBの処理を待ち合わせ」等が可能になります。 http://tsubakit1.hateblo.jp/entry/2015/04/06/060608 今回の使用は難しいかもしれませんが「こういう方法もあるんだなー」という参考になれば幸いです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問