SimpleRPG - GameScene에서 몬스터 사망시 아이템 획득 후 장착 > 포탈타고 이동 구현+fade in, out>씬전환
Hero, Monster 동적 생성으로 수정
+) 몬스터 별로 공격 받는 이펙트 다르게 할까해서 코드를 좀 수정했다.
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class HeroController : MonoBehaviour
{
public enum eState
{
Idle, Run, Attack
}
public float radius = 1;
public float Radius
{
get
{
return this.radius;
}
}
public Transform WeaponTrans
{
get { return weaponTrans; }
}
public eState state;
[SerializeField] private Transform swordTrans;
private Transform weaponTrans;
[SerializeField] private Transform shieldTrans;
private MonsterController monsterController;
private ItemController item;
private ItemGenerator itemGenerator;
private Coroutine waitRoutine;
private Coroutine shieldRoutine;
private GameObject target;//타겟 게임 오브젝트
private Vector3 targetPosition;
private float moveSpeed = 2f;
private Animator anim;
private Coroutine routine;
private float impactTime = 0.399f;
private GameObject swordGo;
private GameObject shieldGo;
// Start is called before the first frame update
void Start()
{
this.monsterController = GameObject.FindObjectOfType<MonsterController>();
this.itemGenerator = GameObject.FindObjectOfType<ItemGenerator>();
this.target = monsterController.gameObject;
// this.transform.position = targetPosition;
this.anim = GetComponent<Animator>();
this.weaponTrans = swordTrans;//weaponTrans를 검 위치로 초기화
}
// Update is called once per frame
void Update()
{
}
public void Move(Vector3 targetPosition)
{
//타겟을 지움
this.monsterController = null;
this.targetPosition = targetPosition;
Debug.Log("Move");
//이동시작
if (this.routine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.routine);
}
this.routine = StartCoroutine(this.CoMove());//코루틴 시작, 변수에 넣어주는 것 잊지말자.
}
public void MoveToMonster(MonsterController target)
{
this.monsterController = target;
this.targetPosition = this.monsterController.transform.position;//이동할 목표 지점을 저장
Debug.Log("MoveToMonster");
// Debug.LogFormat("targetPosition: {0},{1}",targetPosition,this.target.gameObject.transform.position);
//이동시작
if (this.routine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.routine);
}
this.routine = StartCoroutine(this.CoMove());
// this.anim.SetInteger("State", 1);//Run Animation
}
private IEnumerator CoMove()
{
while (true)
{
this.transform.LookAt(this.targetPosition);//방향을 바라봄
this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);//정면으로 이동
this.PlayAnimation(eState.Run);//Run 애니메이션 실행
float distance = Vector3.Distance(this.transform.position, this.targetPosition);
// Debug.Log(distance);
if (this.monsterController != null) //타겟이 있을 경우
{//this.targetPosition은 Vector3라 null과 비교하면안된다. 주의.
if (distance <= (1f+1f))
{
this.PlayAnimation(HeroController.eState.Idle);
break;//break없으면 무한루프돼서 유니티 멈춘다.
}
}
else//타겟이 없으면
{
// Debug.Log(distance);
if (distance <= 0.1f)//** 0이 될수 없다 가까워질때 도착한것으로 판단 하자
{//도착
this.PlayAnimation(eState.Idle);//Idle 애니메이션 실행
break;//isMoveStart 같은 변수를 안쓸 수 있다.
}
}
yield return null;//다음 프레임 시작
}
Debug.Log("<color=yellow>도착!</color>");
}
public void PlayAnimation(eState state)
{
if (this.state != state)//중복막기
{
Debug.LogFormat("Hero: {0} => {1}", this.state, state);
this.state = state;
this.anim.SetInteger("State", (int)state);//정수 -> enum
switch (state)
{
case eState.Attack:
//이미 코루틴이 실행중이라면 중지
if (this.routine != null)
{
this.StopCoroutine(this.routine);
}
this.routine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
break;
}
}
else
{
//Debug.LogFormat("{0}는 현재 상태와 동일한 상태입니다.", state);
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
}
public void Attack(MonsterController target)
{
Debug.Log("Attack!!");
PlayAnimation(eState.Attack);
}
//코루틴 함수
//IEnumerator 반환타입을 갖는 1개 이상의 yield return을 포함하는 함수
private IEnumerator WaitForCompleteAttackAnimation()
{//상태는 바로 변하지않는다. 다음 프레임에 변한다. 코루틴으로 확인해보자
yield return null;//다음 프레임의 시작.yield return 위치에 주의.
Debug.Log("공격 애니메이션이 끝날때까지 기다림");
AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
bool isAttackState = animStateInfo.IsName("Attack01");//애니매이터의 애니메이션 이름
Debug.LogFormat("isAttackState:{0}", isAttackState);
if (isAttackState)
{
Debug.LogFormat("animatorStateInfo.Length:{0}", animStateInfo.length);
}
else
{
Debug.Log("attack state가 아닙니다.");
}
//yield return new WaitForSeconds(animStateInfo.length);
yield return new WaitForSeconds(this.impactTime); //Impact
Debug.Log("<color=red>Impact</color>");
//대상에게 피해를 입힘
// this.target.state = MonsterController.eState.GetHit;
this.monsterController.PlayAnimation(MonsterController.eState.GetHit);
Debug.LogFormat("after target.state: {0}", this.monsterController.state);
yield return new WaitForSeconds(animStateInfo.length - this.impactTime); //0.72초만큼 기다림
this.monsterController.PlayAnimation(MonsterController.eState.Idle);
//Idle애니메이션을 함
//this.anim.SetInteger("State", 0);
this.PlayAnimation(eState.Idle);
}
private IEnumerator CoEquipSword()
{
yield return null;//다음 프레임
Debug.LogFormat("[CoEquipSword] childCount:{0}", this.weaponTrans.childCount);
this.EquipSword();
}
private IEnumerator CoEquipShield()
{
yield return null;//다음 프레임
this.EquipShield();
}
private void OnTriggerEnter(Collider other)
{
if (other != null)
{//충돌하면
if (other.tag == "Item")
{
Debug.Log("아이템과충돌!");
Destroy(other.gameObject);
Debug.LogFormat("=> {0}", this.weaponTrans.childCount);
//this.onTriggerItem();
item = other.gameObject.GetComponent<ItemController>();
if (item != null)
{
Debug.LogFormat("<color=red>ItemType: {0}</color>", item.ItemType);
if (item.ItemType == GameEnums.eItemType.Sword)
{
this.SwordTransform();//검 위치 받아옴
// Debug.Log(this.weaponTrans.position);
this.RemoveEquippedItem();
if (this.waitRoutine != null)
{
this.StopCoroutine(this.waitRoutine);//이미 코루틴이면 실행 중지
}
this.waitRoutine = this.StartCoroutine(this.CoEquipSword());//코루틴 시작
}
else if (item.ItemType == GameEnums.eItemType.Shield)
{
this.ShieldTransform();//방패 위치 받아옴
this.RemoveEquippedItem();
if (this.shieldRoutine != null)
{
this.StopCoroutine(this.shieldRoutine);//이미 코루틴이면 실행 중지
}
this.shieldRoutine = this.StartCoroutine(this.CoEquipShield());//코루틴 시작
}
}
}
}
}
public bool HasWeapon()
{
return this.weaponTrans.childCount > 0;
}
public Transform ShieldTransform()
{
this.weaponTrans = this.shieldTrans;
return this.weaponTrans;
}
public Transform SwordTransform()
{
this.weaponTrans = this.swordTrans;
return this.weaponTrans;
}
private void RemoveEquippedItem()
{
//자식이 있는가?
Debug.LogFormat("자식의수: {0}", this.weaponTrans.childCount);
if (this.weaponTrans.childCount == 0)
{
//자식이 없다 (착용중인 무기가 없다)
Debug.Log("착용중인 무기가 없습니다.");
}
else
{
//자식이 있다 (착용중인 무기가 있다)
Transform child = this.weaponTrans.GetChild(0); //첫번째 자식 //무기를 제거
Destroy(child.gameObject);//무기 제거
Debug.LogFormat("childCount:{0}", this.weaponTrans.childCount);
}
}
private void EquipSword()
{
Debug.Log("생성시 부모를 지정");
//this.SwordTransform();
bool hasWeapon = this.HasWeapon();
if (!hasWeapon)
{
this.swordGo = this.itemGenerator.GenerateItem(GameEnums.eItemType.Sword, this.weaponTrans.position);
//GenerateItem은 instantiate할 때 부모 설정 안된다.
//만들어지고나서 부모 설정
this.swordGo.transform.SetParent(this.WeaponTrans);
Debug.Log("Sword 장착");
}
else
{
Debug.Log("이미 착용중입니다.");
}
}
private void EquipShield()
{
Debug.Log("생성시 부모를 지정");
//this.SwordTransform();
bool hasWeapon = this.HasWeapon();
if (!hasWeapon)
{
this.shieldGo = this.itemGenerator.GenerateItem(GameEnums.eItemType.Shield, this.weaponTrans.position);
//GenerateItem은 instantiate할 때 부모 설정 안된다.
//만들어지고나서 부모 설정
this.shieldGo.transform.SetParent(this.WeaponTrans);
this.shieldGo.transform.localPosition = Vector3.zero;
// go.transform.localRotation = Quaternion.identity;//회전을 초기화
this.shieldGo.transform.localRotation = Quaternion.Euler(new Vector3(-0.315f, -90, 0));
Debug.Log("Shield 장착");
}
else
{
Debug.Log("이미 착용중입니다.");
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HeroGenerator : MonoBehaviour
{
[SerializeField] private List<GameObject> prefabList;//동적 배열: 컬렉션은 사용전 반드시 인스턴스화
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < this.prefabList.Count; i++)
{
GameObject prefab = this.prefabList[i];
Debug.LogFormat("index: {0}, prefab: {1}", i, prefab);
}
}
// Update is called once per frame
void Update()
{
}
public HeroController GenerateHero(GameEnums.ePlayerType playerType, Vector3 initPosition)
{
Debug.LogFormat("monstertype:{0}", playerType);
int index = (int)playerType;//플레이어 타입 인덱스로 변경
Debug.LogFormat("index: {0}", index);
GameObject prefab = this.prefabList[index];
GameObject go = Instantiate(prefab);//위치를 결정 하지 않은 상태 (프리팹의 설정된 위치에 생성됨)
go.transform.position = initPosition;//위치를 설정
if (go.GetComponent<HeroController>() == null)
{
//동적으로 컴포넌트를 부착 할수 있음
HeroController controller = go.AddComponent<HeroController>();
}
return go.GetComponent<HeroController>();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System;
public class MonsterController : MonoBehaviour
{
public float radius =1f;
public int hp=3;
public enum eState
{
Idle, Run, GetHit, Die
}
public eState state;
public float Radius
{
get
{
return this.radius;
}
}
//public Action onHit;//using System작성하고 써야함
public System.Action<GameEnums.eItemType> onDie; //대리자변수 선언
[SerializeField] private GameObject hitFxPrefab;
[SerializeField] private GameEnums.eItemType rewardItemType;
private Animator anim;
// Start is called before the first frame update
void Start()
{
this.anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
}
public void HitDamage()
{
this.onHit();//대리자 사용
if (hp <= 0) //hp 0 이하
{
this.Die();
}
}
private void Die()
{
StartCoroutine(this.CoDie());
}
private IEnumerator CoDie()
{
// this.anim.SetInteger("State", 3);
this.PlayAnimation(eState.Die);
yield return new WaitForSeconds(2.0f);
this.onDie(this.rewardItemType); //대리자 호출
//죽으면 아이템 타입 리턴
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
}
public void PlayAnimation(eState state)
{
//state = eState.GetHit;
if (this.state != state)
{
Debug.LogFormat("Monster: {0} => {1}", this.state, state);
this.state = state;
this.anim.SetInteger("State", (int)state);//정수 -> enum
switch (state)
{
case eState.GetHit:
this.HitDamage();
break;
}
}
}
public void onHit()
{
hp -= 1;
Debug.Log("이펙트 생성");
Vector3 offset = new Vector3(0, 0.5f, 0);
Vector3 tpos = this.gameObject.transform.position + offset;
Debug.LogFormat("생성위치:{0}", tpos);
//프리팹 인스턴스(복사본) 생성
GameObject fxGo = Instantiate(this.hitFxPrefab);
fxGo.transform.position = tpos;//위치 설정
fxGo.GetComponent<ParticleSystem>().Play();//파티클 실행
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterGenerator : MonoBehaviour
{
[SerializeField] private List<GameObject> prefabList;//동적 배열: 컬렉션은 사용전 반드시 인스턴스화
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < this.prefabList.Count; i++)
{
GameObject prefab = this.prefabList[i];
Debug.LogFormat("index: {0}, prefab: {1}", i, prefab);
}
}
public MonsterController GenerateMonster(GameEnums.eMonsterType monsterType, Vector3 initPosition)
{
Debug.LogFormat("monstertype:{0}", monsterType);
int index = (int)monsterType;//몬스터 타입 인덱스로 변경
Debug.LogFormat("index: {0}", index);
GameObject prefab = this.prefabList[index];
GameObject go = Instantiate(prefab);//위치를 결정 하지 않은 상태 (프리팹의 설정된 위치에 생성됨)
go.transform.position = initPosition;//위치를 설정
if (go.GetComponent<MonsterController>() == null)
{
//동적으로 컴포넌트를 부착 할수 있음
MonsterController controller = go.AddComponent<MonsterController>();
}
return go.GetComponent<MonsterController>();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
private HeroController heroController;
private MonsterController monsterController;
private MonsterGenerator monsterGenerator;
private HeroGenerator heroGenerator;
[SerializeField]private ItemGenerator itemGenerator;
GameObject go;
private ItemController itemController;
private float maxDistance = 100f;
private List<MonsterController> monsterList = new List<MonsterController>();
private List<ItemController> itemList = new List<ItemController>();
[SerializeField] private GameObject portalPrefab;
[SerializeField] private Image dim;
private Transform nextScene;
private Coroutine routine;
private System.Action onFadeOutComplete;
private float distance;
private bool hasPortal = false;
// Start is called before the first frame update
void Start()
{
this.monsterController = GameObject.FindObjectOfType<MonsterController>();
this.monsterGenerator = GameObject.FindObjectOfType<MonsterGenerator>();
this.heroGenerator = GameObject.FindAnyObjectByType<HeroGenerator>();
this.itemController = GameObject.FindObjectOfType<ItemController>();
this.heroController = this.heroGenerator.GenerateHero(GameEnums.ePlayerType.DogWarrior, new Vector3(0, 0, 0));
MonsterController turtleController = this.monsterGenerator.GenerateMonster(GameEnums.eMonsterType.Tutle, new Vector3(-3, 0, 0));
turtleController.onDie = (rewardItemType) => {
Debug.Log("거북이 죽음");
var pos = turtleController.gameObject.transform.position;
//Debug.Log(pos);
this.monsterList.Remove(turtleController);
Destroy(turtleController.gameObject);
this.CreateItem(rewardItemType,pos);
};
MonsterController slimeController = this.monsterGenerator.GenerateMonster(GameEnums.eMonsterType.Slime, new Vector3(0, 0, 3));
slimeController.onDie = (rewardItemType) =>
{
Debug.Log("슬라임 죽음");
Vector3 pos = slimeController.gameObject.transform.position;
this.monsterList.Remove(slimeController);
Destroy(slimeController.gameObject);
this.CreateItem(rewardItemType, pos);
};
//만들어진 개체들을 그룹화 관리
//배열, 컬렉션
//동적 배열
this.monsterList.Add(turtleController);
this.monsterList.Add(slimeController);
Debug.LogFormat("this.monsterList.Count: {0}", this.monsterList.Count);
//리스트의 요소를 출력
foreach (MonsterController monster in this.monsterList)
{
Debug.LogFormat("monster: {0}", monster);
}
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))//마우스 좌클릭시
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction*100f, Color.red,3f);//ray 그리기
RaycastHit hit;// Raycast에서 out 키워드 사용을 위한 변수 선언
if (Physics.Raycast(ray, out hit, maxDistance)) //Ray 충돌검사
{
if (hit.collider.tag == "Monster")//몬스터 클릭시
{//태그를 통해 몬스터에 접근
Debug.LogFormat("Monster: {0}", hit.collider.tag);
//거리를 구한다.
Vector3 targetPosition = hit.collider.gameObject.transform.position;
Vector3 start = this.heroController.transform.position;
float distance = Vector3.Distance(start, targetPosition);
this.monsterController = hit.collider.gameObject.GetComponent<MonsterController>();
//각 반지름 더한거와 비교
float sumRadius = this.heroController.Radius + monsterController.Radius;
//Debug.Log(distance);
if (this.IsWithinRange(distance,sumRadius))//사거리 안에 들어옴 => attack
{
Debug.Log("Attack");
//HeroController에게 공격을 명령
this.heroController.Attack(this.monsterController);
}
else//이동
{
this.heroController.MoveToMonster(monsterController);
}
}
else if (hit.collider.tag == "Ground")
{
Debug.Log(hit.point);
this.heroController.Move(hit.point);
}
}
}
if (monsterList.Count <= 0)
{
//Debug.LogFormat("monsterCount:{0}", monsterList.Count);
if (this.hasPortal == false)
{
this.hasPortal = true;
this.CreatePortal();//Portal 생성
this.nextScene = this.portalPrefab.transform;
if (this.routine != null)
{
this.StopCoroutine(this.routine);//이미 코루틴이면 실행 중지
}
//이벤트 추가
this.onFadeOutComplete = () =>//fadeout 끝나면
{
SceneManager.LoadScene("Test_BossScene");
Debug.Log("Play Next Scene");
};
this.routine = StartCoroutine(this.CoNextScene());
}
}
}
private bool IsWithinRange(float distance, float radius)
{
return radius > distance;//true
}
private void CreateItem(GameEnums.eItemType itemType, Vector3 position)
{
Debug.LogFormat("<color=yellow>CreateItem: {0}</color>", itemType);
//Debug.Log(itemGenerator.prefabList[0]);
this.itemGenerator.GenerateItem(itemType, position);
}
private void CreatePortal()
{
//몬스터가 다 제거되면 포탈생성
Instantiate(this.portalPrefab);
this.portalPrefab.transform.position = new Vector3(6, 0, 10);
this.hasPortal = true;
}
private IEnumerator CoNextScene()
{
Color color = this.dim.color;
while (true)
{
//Debug.LogFormat("portal position: {0}", nextScene.position);
// Debug.LogFormat("hero position: {0}", this.heroController.gameObject.transform.position);
this.distance = Vector3.Distance(this.heroController.gameObject.transform.position, nextScene.position);
Debug.LogFormat("Portal distance: {0}", distance);
if (distance != 0 && distance < 5f)
{
Debug.Log(distance);
{
color.a += 0.01f;
this.dim.color = color;
Debug.Log(this.dim.color.a);
if (this.dim.color.a >= 1)
{
break;
}
}
}
yield return null;
}
Debug.LogFormat("fadeout complete!");
this.onFadeOutComplete(); //대리자 호출
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEnums
{
public enum ePlayerType
{
DogWarrior
}
public enum eMonsterType
{
Tutle,Slime
}
public enum eItemType
{
Potion,Sword,Shield
}
}
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static GameEnums;
public class ItemGenerator : MonoBehaviour
{
[SerializeField] private List<GameObject> prefabList;//동적 배열: 컬렉션은 사용전 반드시 인스턴스화
// Start is called before the first frame update
public GameObject GenerateItem(GameEnums.eItemType itemType, Vector3 itemPosition)
{
//아이템 타입에 따라 어떤 프리팹으로 복사본(인스턴스)를 생성할지 결정
//Debug.LogFormat("itemtype: {0}", itemType);
int index = (int)itemType;//아이템 타입 인덱스로 변경
//Debug.LogFormat("index: {0}", index);
GameObject prefab = this.prefabList[index];
GameObject go = Instantiate(prefab);//위치를 결정 하지 않은 상태 (프리팹의 설정된 위치에 생성됨)
go.transform.position = itemPosition;//위치를 설정
return go;
}
public GameObject GetItem(GameEnums.eItemType itemType)
{
GameObject go = Instantiate(this.prefabList[(int)itemType]);
return go;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemController : MonoBehaviour
{
[SerializeField] GameEnums.eItemType itemType;
public GameEnums.eItemType ItemType
{
get
{
Debug.LogFormat("itemController: {0}",this.itemType);
return this.itemType;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
'3d 콘텐츠 제작' 카테고리의 다른 글
HeroShoot-구현 내용/ 씬 구성 (0) | 2023.08.22 |
---|---|
SimpleRPG- Boss Scene(수정중) (0) | 2023.08.13 |
보스씬 넘어가기 연습 - 특정 위치가면 씬전환+fade out (0) | 2023.08.12 |
아이템 획득 후 장비 착용 연습 2 (0) | 2023.08.11 |
아이템 획득시, 장착 연습 (0) | 2023.08.11 |