using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Member : MonoBehaviour {
public Vector3 position;
public Vector3 velocity;
public Vector3 acceleration;
public Level level;
public MemberConfig conf;
Vector3 wanderTarget;
private void Start()
{
position = transform.position;
//FindObjectOfType은 getComponent와 유사하지만 전역적으로 component를 찾아온다
level = FindObjectOfType<Level>();
conf = FindObjectOfType<MemberConfig>();
position = transform.position;
velocity = new Vector3(Random.Range(-3, 3), Random.Range(-3, 3), 0);
}
private void Update()
{
acceleration = Combine(); //이동함수 호출
acceleration = Vector3.ClampMagnitude(acceleration, conf.maxAccelaeration);//ClampMagnitude: max값에 도달하면 그 이후의 값은 max로 반환(상한선)
velocity = velocity + acceleration * Time.deltaTime; //속도
velocity = Vector3.ClampMagnitude(velocity, conf.maxVelocity);
position = position + velocity * Time.deltaTime; //위치
WrapAround(ref position, -level.bounds, level.bounds); //범위 벗어날 경우
//이동방향으로 회전
transform.rotation = Quaternion.LookRotation(position - transform.position);
//위치 할당
transform.position = position;
}
//이동 함수1 - 모였다 퍼졌다
protected Vector3 Wander()
{
//이동 크기
float jitter = conf.wanderJitter * Time.deltaTime; //jitter(진폭)변수 생성
wanderTarget += new Vector3(0, RandomBinomial() * jitter, 0); //랜덤이항식*jitter으로 이동
wanderTarget = wanderTarget.normalized; //정규화: 방향에 상관없이 똑같은 크기를 받도록
wanderTarget *= conf.wanderRadius; //?? 이동 지름을 곱해줌
//위치 좌표
Vector3 targetInLocalSpace = wanderTarget + new Vector3(conf.wanderDistance, conf.wanderDistance, 0); //타겟이 로컬좌표(상대위치)에 있는, ??wanderDistance변수만큼 y축으로 더해줌
Vector3 targetInWorldSpace = transform.TransformPoint(targetInLocalSpace); //월드좌표(절대위치), transformpoint: local좌표 -> world좌표로 변환
targetInLocalSpace -= this.position; //방향
return targetInLocalSpace.normalized; //로컬좌표타겟 변수 정규화
}
//이동 함수2 - wander함수보다 더 랜덤하게 이동
Vector3 Cohesion()
{
Vector3 cohesionVector = new Vector3();
int countMembers = 0;
var neighbors = level.GetNeighbors(this, conf.cohesionRadius);
if (neighbors.Count == 0)
return cohesionVector;
//??neighbors리스트에 있는 member에 관한 반복문
foreach(var member in neighbors)
{
//isInFOV: 시야각 공식
if (isInFOV(member.position))
{
cohesionVector += member.position;
countMembers++;
}
}
if (countMembers == 0)
return cohesionVector;
//??
cohesionVector /= countMembers;
cohesionVector = cohesionVector - this.position;
cohesionVector = Vector3.Normalize(cohesionVector);
return cohesionVector;
}
//alignment(회전) - 이웃과 속도(벡터값 - 크기와 방향 가짐)를 맞추는 함수
Vector3 Alignment()
{
Vector3 alignVector = new Vector3(); //새로운 변수 공간 생성
var members = level.GetNeighbors(this, conf.alignmentRadius); //member로 현재 스크립트가 적용된 gameobject를 가져온다, conf.alignmentRadius는 alignment 적용거리의 범위(collider)이다
if (members.Count == 0)
return alignVector;
// 자신의 시야각에 멤버가 존재하면 정렬을 맞춤
foreach (var member in members)
{
//만약 시야각에 member가 들어오면, alignVector에 시야각에 들어온 member의 벡터값을 더해준다
if (isInFOV(member.position))
alignVector += member.velocity; //alignVector에 member의 속도(벡터값)를 더해주는
}
return alignVector.normalized;
}
//separation - 이웃과 일정한 거리를 띄우는
Vector3 Separation()
{
Vector3 separteVector = new Vector3();
var members = level.GetNeighbors(this, conf.separationRadius);
if (members.Count == 0)
return separteVector;
foreach(var member in members)
{
if(isInFOV(member.position))
{
Vector3 movingTowards = this.position - member.position; //member에서 멀어지는 자신의 방향
if(movingTowards.magnitude > 0)//만약 자신이 member와 접촉되지 않았다면
{
separteVector += movingTowards.normalized / movingTowards.magnitude; //1의 값(정규화 값)을 기준으로 자신의 원래 스칼라 값을 나누어주어서 이동해야되는 크기에 따라 속도를 정의한다 ,magnitude: 벡터의 길이(크기)
}
}
}
return separteVector.normalized;
}
Vector3 Avoidance()
{
Vector3 avoidVector = new Vector3();
var enemyList = level.GetEnemies(this, conf.avoidanceRadius); //level스크립트에서 enemy리스트 가져오기
if (enemyList.Count == 0)
return avoidVector;
foreach(var enemy in enemyList)
{
avoidVector += RunAway(enemy.transform.position); //적과 반대로 이동하는
}
return avoidVector.normalized;
}
Vector3 RunAway(Vector3 target)
{
Vector3 neededVelocity = (position - target).normalized * conf.maxVelocity; //적(target)의 위치와 반대방향으로 이동
return neededVelocity - velocity; //적을 피해 이동하는 벡터 - member가 원래 가야되는
}
//최종 조합 - wander() + cohesion() + alignment() + separation() + avoidance()의 결합 (snake game처럼 보이는)
virtual protected Vector3 Combine()
{
Vector3 finalVec = conf.cohesionPriority * Cohesion() + conf.wanderPriority * Wander()
+ conf.alignmentPriority * Alignment() + conf.separationPriority * Separation()
+ conf.avoidancePriority * Avoidance();
return finalVec;
}
//agent가 범위를 벗어나갈 때 초기값으로 이동시키는 함수
void WrapAround(ref Vector3 vector, float min, float max)
{
//wrapAroundfloat이라는 공식을 반환함
vector.x = WrapAroundFloat(vector.x, min, max);
vector.y = WrapAroundFloat(vector.y, min, max);
vector.z = 0;//WrapAroundFloat(vector.z, min, max); //2D, 3D
}
//wrapAroundFloat이라는 공식
float WrapAroundFloat(float value, float min, float max)
{
if (value > max)
value = min;
else if (value < min)
value = max;
return value;
}
//랜덤 매개변수 이항식 뺄셈
float RandomBinomial()
{
//random은 c언어에서는 0부터 1값만 나온다
//return Random.Range(0f, 1f) - Random.Range(0f, 1f);
return Random.Range(-1.0f, 1.0f);
}
//fov: 시야각 - '속도'와 '(변수)-현재 위치' 사이의 각도의 반환값이 conf.maxFOV보다 작을 때
bool isInFOV(Vector3 vec)
{
return Vector3.Angle(this.velocity, vec - this.position) <= conf.maxFOV;
}
}