Unityの忘備録です('◇')ゞ

自分用の忘備録です!

【ScreenPointToRay】銃口(Rayの発射位置)とカメラの間に壁がある時の対処方【Unity】

© UTJ/UCL




ScreenPointToRayでは、カメラ(スクリーン)中央の座標を取得できます。

Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));




例えば3人称視点のゲームで以下のスクリプトでカメラからターゲットまでの座標を取得する場合、
多くの場合カメラはプレイヤーから見て銃口より手前にあるので、銃口より手前に壁がある場合は壁にRayがヒットしてしまう事になります。

if (Physics.Raycast(ray, out RaycastHit hit, 10))
{
     //ヒットした時の処理
}




上記の事からRayの発射位置をカメラではなく銃口にしたいと考えへ下記のスクリプトを試しました。
カメラ中央へ1つ目のRayを飛ばし、取得した座標の方向へ2つ目のRayを銃口から飛ばすという方法です。

if (Physics.Raycast(ray, out RaycastHit hit, 10))
{
 if (Physics.Raycast(muzzle.position, hit.point , out RaycastHit hit2, 10)
   {
        //ヒットした時の処理
   }
}



しかし結果は同じで
①Rayの発射位置を変えたところで、銃口から手前の壁にRayが飛んでいくだけ!!※カメラは手前の壁にRayを当ててるので当たり前か...
②ScreenPointToRayは発射位置(Z軸)を指定できない!!※たぶん





そもそも上記の現象は当たり前で、1つ目のRayが当たった壁に2つ目のRayが飛ぶのは当然の事。
僕は途中までScreenPointToRayは画面の座標を取得してその方向へRayを飛ばすもので、発射位置をカメラから見て奥にすれば手前のオブジェクトには当たらない
という謎の勘違いしていました(-_-)






諦めようかと思ったのですが、裏を返せば銃口より手前にRayがヒットした事を取得できれば何とかなりそうです('◇')ゞ
こういう時すごい便利なのがTransform.InverseTransformPointです。
Transformの部分は基準にしたい座標のTransform型を指定します。
この場合、銃口(muzzle)を指定しています。
上記の関数は引数に入れた座標を、基準にした座標のローカル空間座標に変換してくれます。
下記スクリプトでは銃口から見たhit.pointのZ軸の値をFlaot型に格納しています。

if (Physics.Raycast(ray, out RaycastHit hit, 10))
{
     float dirZ = muzzlePoint.InverseTransformPoint(hit.point).z;
}






下記動画では、取得できたZ軸の値が0以下なら発砲しないという処理にしています。

youtu.be

Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));

float dirZ = 0.1f;// Rayがヒットしなくても発砲したいから0以上にする

if (Physics.Raycast(ray, out RaycastHit hit, 10))
{
      dirZ = muzzlePoint.InverseTransformPoint(hit.point).z; //ここで0以下になった場合発砲できない

     if (dirZ > 0)
       {
          if (Physics.Raycast(muzzle.position, hit.point , out RaycastHit hit2, 10)
            {                            
                 //何かにヒットした場合の処理 ※弾痕やダメージなど
            }
       } 
}

if (dirZ > 0)
{
    //発砲音や弾数を減らす処理
}






色々考えた結果上記の方法しか思いつきませんでした(´;ω;`)
正直、カメラの目の前に壁があろうとなかろうと目の前の壁を貫通して銃口より奥にRayが飛んで行ってくれれば一番いいのですが
調べても方法が分からなかったのでこのやり方でひとまず?良しとします。
もし参考になりましたら嬉しいです(^-^)
では('◇')ゞ