2021年8月5日木曜日

#8 建物を設置、作成してみる

  こんにちは!ぴちおです。


RTSといえば、

施設の建設は必須。


メニューで建物選択状態にする

マウス上に建物オブジェクトを追従させる

親オブジェクトの近くのみ設置可能にする。

他のオブジェクトがある場合は設置不可にする。

クリックした場所にオブジェクトを表示。


実装したい内容は上記の通り


まずは

床と親オブジェクト、子オブジェクトとゲームマネージャーを設置

plane

名前:Ground

scale:x5,y1,z5

shpere

名前:HomeBase

scale:x2,y2,z2


cylinder

名前:Institution

Prefabフォルダを作成し、そこにドラッグでPrefab化する。

スクリーン上のは削除

new script「InstitutionController」をアタッチ


Empty Object

new script「GameController」をアタッチ


raycastで設置位置を確定するので

余計なのにrayが当たらないようにちょっと設定を加える


Groundはrayを必ず当てたいので

layerを1つ追加、「Ground」

でそれをセット


今回のテストではHomeBaseはray当てたくないので

Ignore RayCastにlayerを設定

Institutionは

まずはrayを当てたくないので

Ignore RayCastにlayerを設定

また、後でクリックしたときに区別したいので

tagにBuildingを追加して、それをセット


続いて、スクリプト

InstitutionController

これは今回のテストでは、設置したものがそれぞれ、どんなステータスかと区別するだけなので、

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class InstitutionController : MonoBehaviour
{
public string _Name;

}

これだけ


GameControllerは

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
private bool bMoodFlag = false;
public GameObject institutionObject;
Vector3 mouse;
Vector3 mouse3d;
public GameObject insobj;
InstitutionController iScript;
void Update()
{
if (bMoodFlag)
{
//建物設置モード
}
else
{
mouse = Input.mousePosition;
mouse.z = 10f;
mouse3d = Camera.main.ScreenToWorldPoint(mouse);
mouse3d.y = 1f;
if(Input.GetKeyUp (KeyCode.H))
{
bMoodFlag=true;
insobj = Instantiate(institutionObject,mouse3d,Quaternion.identity);
iScript = insobj.GetComponent<InstitutionController>();
iScript._Name = "House";
}
if(Input.GetKeyUp (KeyCode.B))
{
bMoodFlag=true;
insobj = Instantiate(institutionObject,mouse3d,Quaternion.identity);
iScript = insobj.GetComponent<InstitutionController>();
iScript._Name = "Barracks";
}
}
}
}

まずは、建築モードに入るところ、

建築モードフラグBmodeFlagのT/Fでアクションを変更する。

建築モードフラグFalse時に

キーHもしくはキーBを押すと

Prefabからinstitutionを生成する

また、そのinstitutionのスクリプトの_Nameにそれぞれのパラメータをセットする。

で、建築モードフラグをTrueにする


続いて、建築モードキャンセルアクション

void Update()
{
if (bMoodFlag)
{
//キャンセル
if(Input.GetKeyUp (KeyCode.Escape))
{
bMoodFlag=false;
insobj.SetActive (false);
}
}
else
{

建築モードTrue中にEscキーを押すとキャンセルになる。

キャンセルとは建築モードフラグをFalseにして、Prefabから作成したオブジェクトを非表示にする

※テストなので作り込まないが、Prefabから作成するとき、この非表示のオブジェクトが存在すれば、新規つくらず、そのオブジェクトを表示するようにする。


ここからが苦戦したところ、

1つ目がHome Baseから一定の距離にしかinstitutionを追従しないようにする

一定の距離離れた場合、ギリギリの距離で追いかける

if (bMoodFlag)
{
//建物設置モード
//マウス移動
Vector2 touchScreenPosition = Input.mousePosition;
touchPointToRay = gameCamera.ScreenPointToRay( touchScreenPosition );
touchScreenPosition.x = Mathf.Clamp( touchScreenPosition.x, 0.0f, Screen.width );
touchScreenPosition.y = Mathf.Clamp( touchScreenPosition.y, 0.0f, Screen.height );

Vector3 pos = PreObject.transform.position;

RaycastHit hitInfo = new RaycastHit();

if( Physics.Raycast( touchPointToRay, out hitInfo ,Mathf.Infinity,LayerMask) )
{
pos = hitInfo.point;
insobj.transform.position = pos;

まずは、距離関係なく、また、HomeBaseに被りながらもおいかけるようにする。

仕組みとしては

カメラからrayを飛ばしてGround上の座標を取得

あ、LayerMaskはPublicにして、インスペクターからGroundを設定しておきます。


そうすると、他のオブジェクトに当たらず床のカメラからみてクリックした座標がGetできます。

それで、institutionの位置を絶えず移動させれた追従します。

if( Physics.Raycast( touchPointToRay, out hitInfo ,Mathf.Infinity,LayerMask) )
{
if (Vector3.Distance(PreObject.transform.position,hitInfo.point)<InstallableDistance && Vector3.Distance(PreObject.transform.position,hitInfo.point)>1.2)
{
pos = hitInfo.point;
}
else
{
Vector3 dt = hitInfo.point - PreObject.transform.position;
float radians = Mathf.Atan2(dt.z,-dt.x);
float angle = radians * Mathf.Rad2Deg-90;
float radian = angle * Mathf.Deg2Rad;
if(Vector3.Distance(PreObject.transform.position,hitInfo.point)>=InstallableDistance)
{
pos.z = Mathf.Cos(radian) * InstallableDistance + PreObject.transform.position.z;
pos.x = Mathf.Sin(radian) * InstallableDistance + PreObject.transform.position.x;
}
else if(Vector3.Distance(PreObject.transform.position,hitInfo.point)<=1.2)
{
pos.z = Mathf.Cos(radian) * 1.4f + PreObject.transform.position.z;
pos.x = Mathf.Sin(radian) * 1.4f + PreObject.transform.position.x;
}
}
insobj.transform.position = pos;

続いて、一定距離以上離れない、HomeBaseには乗らないようにします。

マウスの位置とHomeBaseの距離を計測します。

if (Vector3.Distance(PreObject.transform.position,hitInfo.point)<InstallableDistance && Vector3.Distance(PreObject.transform.position,hitInfo.point)>1.2)

InstallableDistanceこれが設置可能距離

HomeBaseの距離は1.2直打ちです。ほんとはオブジェクトの半径とかやるんだろうけどTestなので・・

この条件にある場合だけ、移動にすれば、一定範囲のみ移動します。

ただ、その条件に外れた場合、そこでオブジェクトが止まってしまうので範囲内ギリギリで移動するようにします。

仕組みは

HomeBaseとマウスの座標を元に角度を取得

あとは、SinとかCosとか使って座標を求めます。

私は学がないので、ここら辺よくわかりません。



最後に

クリックした場所に何もなければ

施設を設置する。


何もないかどうかの判定は

//クリック時
if (Input.GetMouseButtonUp(0))
{
//他の建物がないか確認
RaycastHit hit;
// SphereCastのレイを飛ばしターゲットと接触しているか判定
if(Physics.SphereCast(touchPointToRay, 0.5f, out hit, 100f)) {
if (hit.transform.name == "Ground")
{
insobj.layer = 0;
bMoodFlag=false;
}
else{Debug.Log("設置できません");}
}
else{bMoodFlag=false;}
}

マウスクリックしたときに

またrayを飛ばします。

今度はsphercastという点ではなく、球体のrayを飛ばす感じです。

カメラからマウスの位置に半径0.5fの球体を飛ばしてヒットするか確認します。

ヒットした場合は、設置できない

他のものにぶつからずGroundがヒットした場合は設置可能として

オブジェクトを設置します。

設置する際に、そのオブジェクトが次に、この置けるか判定の時にちゃんとrayにぶつかるようにlayerをデフォルト(0)に変更

建設モードをfalseにセットで完了です。


と、思ったら、

これだとマウスが設置可能範囲内の場合はいいのですが、

上で考慮した、設置可能範囲外の場合、ガスガス設置できてしまう。

ので、ちょっと修正

Vector3 ispos = insobj.transform.position;
ispos.y += 10f;
if(Physics.SphereCast(ispos,0.5f,Vector3.down,out hit,100f)){

上で考慮した範囲外、オブジェクトが表示されている場所からレイを落としてみる

一応、10fほど高いところから真下にレイ発射に変更。


本当はRTSであれば

いきなり設置ではなく、建設中の状態にして、

その待ち時間完了後、設置完了なんでしょうけど、

それは別の記事で、待ち時間を検討してみます。



上で省略した変数も含めて全部はこうなりました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
private float clickTime = 0;
private bool clickFlg = false;
private GameObject mainCamera;
private Vector3 dragStartPosision;
private float mouseDownTime = 0;
private bool bMoodFlag = false;
public GameObject institutionObject;
Vector3 mouse;
Vector3 mouse3d;
public GameObject insobj;
public GameObject PreObject;
public LayerMask LayerMask;
public float InstallableDistance=2;
private Camera gameCamera;
private Ray touchPointToRay;
InstitutionController iScript;
void Start()
{
gameCamera = Camera.main;
mainCamera = gameCamera.gameObject;
}
void Update()
{
if (bMoodFlag)
{
//建物設置モード
//マウス移動
Vector2 touchScreenPosition = Input.mousePosition;
touchPointToRay = gameCamera.ScreenPointToRay( touchScreenPosition );
touchScreenPosition.x = Mathf.Clamp( touchScreenPosition.x, 0.0f, Screen.width );
touchScreenPosition.y = Mathf.Clamp( touchScreenPosition.y, 0.0f, Screen.height );

Vector3 pos = PreObject.transform.position;
RaycastHit hitInfo = new RaycastHit();
if( Physics.Raycast( touchPointToRay, out hitInfo ,Mathf.Infinity,LayerMask) )
{
if (Vector3.Distance(PreObject.transform.position,hitInfo.point)<InstallableDistance && Vector3.Distance(PreObject.transform.position,hitInfo.point)>1.2)
{
pos = hitInfo.point;
}
else
{
Vector3 dt = hitInfo.point - PreObject.transform.position;
float radians = Mathf.Atan2(dt.z,-dt.x);
float angle = radians * Mathf.Rad2Deg-90;
float radian = angle * Mathf.Deg2Rad;
if(Vector3.Distance(PreObject.transform.position,hitInfo.point)>=InstallableDistance)
{
pos.z = Mathf.Cos(radian) * InstallableDistance + PreObject.transform.position.z;
pos.x = Mathf.Sin(radian) * InstallableDistance + PreObject.transform.position.x;
}
else if(Vector3.Distance(PreObject.transform.position,hitInfo.point)<=1.2)
{
pos.z = Mathf.Cos(radian) * 1.4f + PreObject.transform.position.z;
pos.x = Mathf.Sin(radian) * 1.4f + PreObject.transform.position.x;
}
}
insobj.transform.position = pos;
//クリック時
if (Input.GetMouseButtonUp(0))
{
//他の建物がないか確認
RaycastHit hit;
// SphereCastのレイを飛ばしターゲットと接触しているか判定
if(Physics.SphereCast(touchPointToRay, 0.5f, out hit, 100f)) {
if (hit.transform.name == "Ground")
{
insobj.layer = 0;
bMoodFlag=false;
}
else{Debug.Log("設置できません");}
}
else{bMoodFlag=false;}
}
//キャンセル
if(Input.GetKeyUp (KeyCode.Escape))
{
bMoodFlag=false;
insobj.SetActive (false);
}
}
else
{
mouse = Input.mousePosition;
mouse.z = 10f;
mouse3d = Camera.main.ScreenToWorldPoint(mouse);
mouse3d.y = 1f;
if(Input.GetKeyUp (KeyCode.H))
{
bMoodFlag=true;
insobj = Instantiate(institutionObject,mouse3d,Quaternion.identity);
iScript = insobj.GetComponent<InstitutionController>();
iScript._Name = "House";
}
if(Input.GetKeyUp (KeyCode.B))
{
bMoodFlag=true;
insobj = Instantiate(institutionObject,mouse3d,Quaternion.identity);
iScript = insobj.GetComponent<InstitutionController>();
iScript._Name = "Barracks";
}
}
}
}


参考にさせていただいたサイト:

https://www.urablog.xyz/entry/2017/04/28/213010

0 件のコメント: