메테리얼 색상 변경
GameObject의 적용된 Material의 색상을 변경해 보자.
Material은 Render Component에 적용되어 있다.
이를 받아와 color의 값을 변경하면 색상을 변경할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MatColorChanger : MonoBehaviour
{
private BoxCollider boxCollider;
[SerializeField] private GameObject cube;
private Vector3 minBounds;
private Vector3 maxBounds;
void Start()
{
boxCollider = cube.GetComponent<BoxCollider>();
minBounds = boxCollider.bounds.min;
maxBounds = boxCollider.bounds.max;
}
float fit(float x, float oldMin, float oldMax)
{
return (x - oldMin) / (oldMax - oldMin);
}
void Update()
{
transform.position = new Vector3(
Mathf.Clamp(transform.position.x, minBounds.x, maxBounds.x),
transform.position.y,
Mathf.Clamp(transform.position.z, minBounds.z, maxBounds.z));
GetComponent<MeshRenderer>().material.color = new Color(
fit(transform.position.x, minBounds.x, maxBounds.x),
0,
fit(transform.position.z, minBounds.z, maxBounds.z));
}
}
대포 만들기
우선, Space바를 눌러서 발사하는 대포를 만들자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Cannon : MonoBehaviour
{
public GameObject cannonBall;
public GameObject firePoint;
[SerializeField] private float power = 30f;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameObject cannonBallInstance = Instantiate(cannonBall,
firePoint.transform.position,
transform.rotation);
cannonBallInstance.GetComponent<Rigidbody>().AddForce(firePoint.transform.forward * power, ForceMode.Impulse);
}
}
}
세기를 조절할 수 있게 만들어 보자.
그리고 Slider UI를 통해 이를 시각화해 보자.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Cannon : MonoBehaviour
{
public GameObject cannonBall;
public GameObject firePoint;
public Slider slider;
public float maxPower;
public float currentPower;
public float fillSpeed;
private bool bRevered = false;
private Vector3 originCameraPos;
private Quaternion originCameraRot;
private void Start()
{
originCameraPos = Camera.main.transform.position;
originCameraRot = Camera.main.transform.rotation;
}
void Update()
{
if (Input.GetKey(KeyCode.Space))
{
if (bRevered)
{
currentPower -= fillSpeed * Time.deltaTime;
currentPower = Mathf.Max(0f, currentPower);
}
else
{
currentPower += fillSpeed * Time.deltaTime;
currentPower = Mathf.Min(maxPower, currentPower);
}
if (currentPower == maxPower) bRevered = true;
else if (currentPower == 0f) bRevered = false;
}
if (Input.GetKeyUp(KeyCode.Space))
{
GameObject cannonBallInstance = Instantiate(cannonBall,
firePoint.transform.position,
transform.rotation);
cannonBallInstance.GetComponent<Rigidbody>().AddForce(firePoint.transform.forward * currentPower, ForceMode.Impulse);
currentPower = 0f;
bRevered = false;
}
slider.value = currentPower / maxPower;
}
}
탄환이 발사됐을 때, 카메라가 따라갈 수 있도록 코루틴을 사용해서 구현해 보자.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Cannon : MonoBehaviour
{
public GameObject cannonBall;
public GameObject firePoint;
public Slider slider;
public float maxPower;
public float currentPower;
public float fillSpeed;
private bool bRevered = false;
private Vector3 originCameraPos;
private Quaternion originCameraRot;
private void Start()
{
originCameraPos = Camera.main.transform.position;
originCameraRot = Camera.main.transform.rotation;
}
IEnumerator FollowCamera(GameObject cannonBall)
{
float time = 0f;
float duration = 3f;
Camera.main.transform.rotation = cannonBall.transform.rotation;
Camera.main.transform.Rotate(new Vector3(10f, 0f, 0f));
Vector3 initialCannonBallForward = cannonBall.transform.forward;
while (time <= duration)
{
var newCameraPos = cannonBall.transform.position - initialCannonBallForward * 10f;
newCameraPos.y += 2f;
Camera.main.transform.position = newCameraPos;
time += Time.deltaTime;
yield return null;
}
Camera.main.transform.position = originCameraPos;
Camera.main.transform.rotation = originCameraRot;
Destroy(cannonBall);
}
void Update()
{
if (Input.GetKey(KeyCode.Space))
{
if (bRevered)
{
currentPower -= fillSpeed * Time.deltaTime;
currentPower = Mathf.Max(0f, currentPower);
}
else
{
currentPower += fillSpeed * Time.deltaTime;
currentPower = Mathf.Min(maxPower, currentPower);
}
if (currentPower == maxPower) bRevered = true;
else if (currentPower == 0f) bRevered = false;
}
if (Input.GetKeyUp(KeyCode.Space))
{
GameObject cannonBallInstance = Instantiate(cannonBall,
firePoint.transform.position,
transform.rotation);
cannonBallInstance.GetComponent<Rigidbody>().AddForce(firePoint.transform.forward * currentPower, ForceMode.Impulse);
currentPower = 0f;
bRevered = false;
StartCoroutine(FollowCamera(cannonBallInstance));
}
slider.value = currentPower / maxPower;
}
}
추가로 충돌 판정 이후, 조각이 생성되도록 구현해 보자.
우선, 충돌 판정을 위해 OnCollisionEnter함수를 구현하자.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class Projectile : MonoBehaviour
{
public GameObject piece;
public int MinPiecesCount = 3;
public int MaxPiecesCount = 10;
private void OnCollisionEnter(Collision other)
{
int piecesCount = Random.Range(MinPiecesCount, MaxPiecesCount);
var velocity = other.relativeVelocity;
for (int i = 0; i < piecesCount; i++)
{
var dir = Quaternion.LookRotation(Random.insideUnitSphere) * velocity;
var instance = Instantiate(piece, other.contacts[0].point, Quaternion.LookRotation(dir));
instance.GetComponent<Rigidbody>().AddForce(dir, ForceMode.Impulse);
}
Destroy(gameObject);
}
}
충돌 판정이 시작되면 생성할 조각의 수를 정한다.
이후, 충돌 판정이 된 오브젝트에 대한 상대 속도를 구한다.
상대 속도와 랜덤한 방향을 곱해 최종적으로 조각이 이동할 벡터를 구한다.
그리고, 조각을 생성한 후 AddForce를 통해 물리적으로 힘을 가한다.
생성된 조각들은 지정된 범위에서 임의의 lifeTime을 결정하고 삭제한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Piece : MonoBehaviour
{
IEnumerator Start()
{
float lifeTime = Random.Range(2f, 4f);
yield return new WaitForSeconds(lifeTime);
Destroy(gameObject);
}
}
이제 대포를 움직이며 랜덤으로 스폰되는 몬스터를 맞춰 없애보자.
우선, 몬스터를 스폰하는 매니저 스크립트를 작성하자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterManager : MonoBehaviour
{
[Header("Area")]
[SerializeField] private BoxCollider monsterSpawnArea;
private Vector3 minBounds;
private Vector3 maxBounds;
[Header("Monster")]
[SerializeField] private Monster monsterPrefab;
[SerializeField] private int maxMonsterCount = 5;
public List<Monster> monsters = new List<Monster>();
void Start()
{
monsterSpawnArea = GetComponent<BoxCollider>();
minBounds = monsterSpawnArea.bounds.min;
maxBounds = monsterSpawnArea.bounds.max;
for (int i = 0; i < maxMonsterCount; i++)
{
RespawnMonster();
}
}
IEnumerator SpawnMonster()
{
float randCoolDown = Random.Range(1f, 3f);
yield return new WaitForSeconds(randCoolDown);
Vector3 spawnPosition = new Vector3(
Random.Range(minBounds.x, maxBounds.x),
2f,
Random.Range(minBounds.z, maxBounds.z)
);
var monster = Instantiate(monsterPrefab, spawnPosition, Quaternion.identity);
monster.monsterManager = this;
monsters.Add(monster);
}
public void RespawnMonster()
{
StartCoroutine(SpawnMonster());
}
}
BoxCollider로 몬스터가 생성될 수 있는 지역을 지정한 뒤, bounds를 통해 임의의 position을 골라 몬스터를 생성한다.
몬스터가 한 번에 생성되는 것이 아니라, 랜덤한 간격을 두고 생성되게 하고 싶어 코루틴을 사용했다.
몬스터 스크립트는 다음과 같다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public MonsterManager monsterManager;
public void DestroyWithRespawn()
{
monsterManager.RespawnMonster();
monsterManager.monsters.Remove(this);
Destroy(gameObject);
}
public void OnCollisionEnter(Collision other)
{
DestroyWithRespawn();
}
}
충돌이 일어나면 자신이 참조하고 있던 MonsterManager에게 Respawn을 요청하고 Destroy 된다.