#はじめに
みなさんはWiiやVR、kinnectのゲームで、敵を攻撃するとき、どのような動きをしますか?
アニメや漫画のように大振りで攻撃しますか?
少なくとも私はしません。
相手の当たり判定の境目あたりをドゥルルンって狙い、連続ヒットさせます。
みなさんもWiiスポーツリゾートのちゃんばらで、適当に小刻みに振っていたのではないでしょうか。
やめてあげてください。
ゲーム性が崩壊してしまいます。
私が作ったゲームでそれをやられては、泣いてしまいます。
でも、そんなことユーザーはお構いなしにやってきます。
どこかの黒の剣士くらいしかちゃんとプレイしてくれないと思います。閃光の人はきっと高速でつついて攻撃することでしょう。
そこで、今回は衝突の強さによって、与えられるダメージが変わるシステムでちゃんとプレーしてもらいましょう。
コード自体はしょうもないので、はよコード教えろやという方は、下までスクロールしてもらっても全く問題ありません。
#完成品
こっちのほうがよくわかるかも pic.twitter.com/RWoKFToiHn
— kz (@kzu_create) December 20, 2019
#従来のジェスチャーセンサーゲームの問題点
###当たり判定があったら、同じダメージが入る
これに尽きると思います。
特にWiiのゲームはほとんどそうだったのではないでしょうか。
#衝突の大きさの求め方
これは複数あると思います。
私ははじめ、2つの物体の移動速度を正しく取得し、計算しようとしました。
Rigidbodyのvelocityを用いる方法ですね。
この方法は先人の方がまとめてくれています。
[Unity]Rigidbodyの速度を表示する.
ただ、この方法はVRでもうまく動くのかなとか、全部にRigidbodyを付ける必要がある等懸念点がでてきました。
何より、2点でやると計算式めんどくさそうだなと。
そこで思いついたのが疑似物理的手法です。
結局2点の速さを求めるのでは、2点の進んだ距離を一定の時間で割れば、求められます。
なので、衝突時の1秒前の2点の距離がわかれば、衝突時のだいたいの勢いはわかるんじゃねみたいな感じです。
「俺は衝突前の1フレームに力入れて超加速したんだぞ」という人には対応できませんが、そうでもなければ、大振りして攻撃をするメリットが生まれ、ちょこちょこ攻撃するデメリットが生まれるので、よりリアルなゲームになると思います。
#ようやくソースコード
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DamageController : MonoBehaviour
{
[SerializeField]
private Transform enemy;
private float timeElapsed = 0.0f;
private float[] distanceArray = new float[10000];
private float collisionDistance = 0.0f;
private float damage = 0.0f;
// uGUI text
[SerializeField]
private TextChanger damagetext;
void Update()
{
timeElapsed += Time.deltaTime;
distanceArray[(int)(timeElapsed * 100)] = DistanceCheck();
}
private float DistanceCheck(){
float distance = Vector3.Distance(enemy.position, transform.position);
return distance;
}
void OnCollisionEnter(Collision other){
if(other.gameObject.tag == "enemy"){
collisionDistance = distanceArray[(int)(timeElapsed * 100 - 10)] - distanceArray[(int)(timeElapsed * 100)];
damage = System.Math.Abs(collisionDistance * 100);
damagetext.damageUI(damage);
}
}
}
このスクリプトはプレイヤーにつけ、エネミーを攻撃するためのものになっています。
private float[] distanceArray = new float[10000];
要素数の大きな配列を作っていますが、リストで代用可能だと思うので適宜変えて下さい。あるいは、用済みの要素を削除してください(ゲームつくるならほぼマスト)
ちなみに、このままでも重くはないです。
[SerializeField]
private TextChanger damagetext;
攻撃が成功した時に表示したいUIを入れます。
void Update()
{
timeElapsed += Time.deltaTime;
distanceArray[(int)(timeElapsed * 100)] = DistanceCheck();
}
時間経過timeElapsedでとり、distanceArray[]にDistanceCheck()の結果を格納しています。
常に値を取り続けますが、一定時間経過後、不要になるので消した方がいいです。
100をかけているのは、フレームごとに配列に変化をつけつつ、INT型にするためです。
private float DistanceCheck(){
float distance = Vector3.Distance(enemy.position, transform.position);
return distance;
}
Vector3.Distanceで、2点の距離を知ることができます。
void OnCollisionEnter(Collision other){
if(other.gameObject.tag == "enemy"){
collisionDistance = distanceArray[(int)(timeElapsed * 100 - 10)] - distanceArray[(int)(timeElapsed * 100)];
damage = System.Math.Abs(collisionDistance * 100);
damagetext.damageUI(damage);
}
}
衝突判定時、タグが"enemy"であれば、距離を測ります。
衝突しているんだから、衝突時の距離は当然0だと思うかもしれませんが、実際はCollisionがあるため、2つのオブジェクトの原点同士がぶつかることはほぼありません。
ダメージを絶対値にしているのは、連続ヒット時に、マイナスになってしますことがあるためです。
一回の衝突ごとに攻撃があたらない無敵時間をつくるか、あまりにも小さいダメージは無効にしてもいいと思います。
#まとめ
かなり無理やりな実装なので、このまま使うことはほとんどないとは思いますが、工夫次第で、よりリアルなバーチャル体験が作れるのではないでしょうか。
釣り気味なタイトルから入り、ふざけた「はじめに」からここまで読んでいただき、ありがとうございました!
一緒に夢あふれるバーチャル世界を作っていきましょう!