ムカデっぽい動きを作りたかった
今作っているゲームのボスに画面上を縦横無尽に動き回るムカデのボスを実装しようと思ったのがこの記事を書くきっかけでした。
どんな感じの物を想定していたか
これはRisk of Rainというゲームに出てくるワームなのですがこのような感じの物を想定していました。
Risk of Rain
どうやって作ろう?
頭と体が別々で頭がPlayerに向かってい飛んで行って体が同じ速度で頭の方向に飛んでいけばそれっぽくみえるんじゃないかな?
まずは頭だけ飛んでいく処理
できた!
次は体も追従するように
サンプルコード
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
public class test :MonoBehaviour
{
Vector2 A, C, AB, AC; // ベクトル
public GameObject target; // 追いかける対象
public Sprite headSprite; // 頭の画像
GameObject head; // 頭オブジェクト
public int bodyLength=10; // 胴体の数
public Sprite bodySprite; // 胴体のPrefab
public GameObject[] bodys; // 胴体の配列
public Rigidbody2D[] rigidbody2Ds; //Rigidbody2Dの配列
public float speed; // 移動スピード
public float maxRot; // 曲がる最大角度
public float maxRot2; // 曲がる最大角度
void Start()
{
//頭を生やす
head = new GameObject();
head.transform.position = transform.position;
head.transform.SetParent(transform);
head.name = "Head";
head.AddComponent<SpriteRenderer>();
head.GetComponent<SpriteRenderer>().sprite = headSprite;
head.AddComponent<BoxCollider2D>().isTrigger = true;
head.AddComponent<Rigidbody2D>();
head.GetComponent<SpriteRenderer>().sortingOrder = bodyLength;
//胴体の長さ分配列を用意
bodys = new GameObject[bodyLength];
rigidbody2Ds = new Rigidbody2D[bodyLength+1];
rigidbody2Ds[0] = head.GetComponent<Rigidbody2D>();
//胴体を生成
for (int i = 0; i < bodys.Length; i++)
{
bodys[i] = new GameObject();
bodys[i].name = "body" + i.ToString();
bodys[i].transform.SetParent(transform);
bodys[i].AddComponent<SpriteRenderer>();
bodys[i].GetComponent<SpriteRenderer>().sprite = bodySprite;
bodys[i].AddComponent<BoxCollider2D>().isTrigger=true;
bodys[i].transform.position=new Vector3(head.transform.position.x, head.transform.position.y-bodys[i].GetComponent<BoxCollider2D>().size.y*(i+1),head.transform.position.z);
bodys[i].AddComponent<Rigidbody2D>();
bodys[i].GetComponent<SpriteRenderer>().sortingOrder = bodys.Length - 1 - i;
rigidbody2Ds[i + 1] = bodys[i].GetComponent<Rigidbody2D>();
}
}
// Update is called once per frame
void Update()
{
HeadMove(Theta(head,target));
for (int i = 0; i < bodys.Length; i++)
{
BodyMove(Theta(bodys[i],(i==0)?head:bodys[i-1]),i);
}
}
// θを求める
float Theta(GameObject _myObject,GameObject _target)
{
A = _myObject.transform.position; // 自身の座標
C = _target.transform.position; // ターゲットの座標
AB = _myObject.transform.up; // 自身の上方向ベクトル
AC = C - A; // ターゲットの方向ベクトル
float dot = Vector3.Dot(AB, AC); // 内積
float rot = Acosf(dot / (Length(AB) * Length(AC))); // アークコサインからθを求める
// 外積から回転方向を求める
if (AB.x * AC.y - AB.y * AC.x < 0)
{
rot = -rot;
}
return rot * 180f / Mathf.PI; // ラジアンからデグリーに変換して角度を返す
}
// 移動処理
void BodyMove(float rot,int _i)
{
// 求めた角度が曲がる最大角度より大きかった場合に戻す処理
if (rot > maxRot2)
{
rot = maxRot2;
}
else if (rot < -maxRot2)
{
rot = -maxRot2;
}
bodys[_i].transform.eulerAngles += new Vector3(0, 0, rot); // 回転
bodys[_i].GetComponent<Rigidbody2D>().velocity = AB.normalized * speed; // 上に移動
}
// 移動処理
void HeadMove(float rot)
{
// 求めた角度が曲がる最大角度より大きかった場合に戻す処理
if (rot > maxRot)
{
rot = maxRot;
}
else if (rot < -maxRot)
{
rot = -maxRot;
}
head.transform.eulerAngles += new Vector3(0, 0, rot); // 回転
head.GetComponent<Rigidbody2D>().velocity = AB.normalized * speed; // 上に移動
}
/// <summary>
/// ベクトルの長さを求める
/// </summary>
/// <param name="vec">2点間のベクトル</param>
/// <returns></returns>
float Length(Vector2 vec)
{
return Mathf.Sqrt(vec.x * vec.x + vec.y * vec.y);
}
/// <summary>
/// Acosの引数の値が+-1を越えたとき1に戻すAcos関数
/// </summary>
/// <param name="a">内積 / (ベクトルの長さ * ベクトルの長さ)</param>
/// <returns></returns>
float Acosf(float a)
{
if (a < -1) a = -1;
if (a > 1) a = 1;
return (float)Mathf.Acos(a);
}
}
まとめ
なにか違う…