새 씬을 만들어 공격 연습을 해보려한다.
플레이어를 중앙에 놓고, 새 메인 스크립트를 생성해 Test_PlayerControlAttackSceneMain 으로 명명하였다.
또한 namespace Test2로 묶어주었다.
몬스터 컨트롤러와 히어로 컨트롤러 수정. radius 는 1로 설정하였다.
-어택 버튼을 만들어 공격 테스트를 해보려한다: 버튼 컴포넌트의 On Click() 속성을 사용.
+) OnClick은 속성(필드)이므로 변수(=속성:문법상 속성 = 프로퍼티)이다.
-HeroController에 Attack메서드를 작성하고 버튼에 추가해주었다. => 메서드를 작성하고 넣어주기
: 게임오브젝트를 넣어주고, 메서드를 설정하는 것이다.
=> 변수에다 메서드를 넣었다? : 대리자이다. 즉 OnClick은 대리자인것이다.
https://docs.unity3d.com/2018.3/Documentation/ScriptReference/UI.Button.html
Unity - Scripting API: Button
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see: You've told us there are code samples on this page which don't work. If you know ho
docs.unity3d.com
-버튼은 코드 베이스로 만들 수도 있다.
-AddListener는 UnityAction을 넣을 수 있다
-콜백은 무조건 대리자! => UnityAction은 콜백이니까 대리자~
Attack 구현을 위해 코드 수정
-Impact하는 순간에 몬스터는 피해를 받는 것을 구현하려한다.
: 구현해야할 것 => 타격시 피해받는 몬스터의 애니메이션, 타격시 피해정보(영웅의 공격량만큼)를 몬스터가 저장
x 는 attack 애니메이션의 전체 실행시간이다. 12프레임째 충돌발생.
heroController를 바탕으로 몬스터 컨트롤러도 수정해준다.
이펙트 추가 후 최종코드
using System.Collections;
using System.Collections.Generic;
using Test;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace Test2
{
public class Test_PlayerControlAttackSceneMain : MonoBehaviour
{
[SerializeField]
private Button btnAttack;
private HeroController heroController;
private MonsterController monsterController;
[SerializeField]
private GameObject hitFxPrefab;
private GameObject fxGo;
// Start is called before the first frame update
void Start()
{
heroController = GameObject.FindObjectOfType<HeroController>();
monsterController = GameObject.FindObjectOfType<MonsterController>();
Debug.LogFormat("btanAttack:{0}", btnAttack);//button 컴포넌트의 인스턴스
//this.btnAttack.onClick.AddListener(Attack);//명명
//this.btnAttack.onClick.RemoveListener(Attack);//이벤트를 제거할 수도 있다.
//이벤트 등록
// this.monsterController.onHit = new System.Action(() => { });
//대리자 인스턴스가 메서드를 참조하는것
this.monsterController.onHit = () => {
Debug.Log("이펙트 생성");
Vector3 offset = new Vector3 (0, 0.5f, 0);
Vector3 tpos = this.monsterController.transform.position + offset;
Debug.LogFormat("생성위치:{0}",tpos);
//프리팹 인스턴스(복사본) 생성
fxGo = Instantiate(this.hitFxPrefab);
fxGo.transform.position = tpos;//위치 설정
fxGo.GetComponent<ParticleSystem>().Play();//파티클 실행
};
this.btnAttack.onClick.AddListener(() => {
//사거리 체크
//두점과의 거리, 두 오브젝트의 radius의 합
Vector3 start = this.monsterController.transform.position;
//몬스터 컨트롤러 컴포넌트가 붙어있는 게임 오브젝트의 transform컴포넌트의 position속성
Vector3 end = this.heroController.transform.position;
Vector3 c = start - end;//정규화되지 않은 방향벡터(방향과 크기),크기: 길이
DrawArrow.ForDebug(end, c);//시작위치, 방향
float distance = c.magnitude;
Debug.LogFormat("distance: {0}", distance);
Debug.DrawLine(start, end, Color.magenta, 2f); //영웅과 몬스터의 길이를 출력
//두 컴포넌트의 radius의 합
float radius = this.heroController.Radius + this.monsterController.Radius;
Debug.LogFormat("radius:{0}", radius);
Debug.LogFormat("<color=yellow>distance: {0},radius: {1}</color>", distance, radius);
Debug.LogFormat("<color=green>IsWithinRange: {0}</color>", IsWithinRange(distance, radius));
if (this.IsWithinRange(distance, radius))
{
//HeroController에게 공격을 명령
this.heroController.Attack(this.monsterController);
}
});//익명(람다)
}
// Update is called once per frame
void Update()
{
}
private bool IsWithinRange(float distance, float radius)
{
return radius > distance;//true
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using Test;
using UnityEngine;
namespace Test2
{
public class HeroController : MonoBehaviour
{
public enum eState
{
Idle,Run,Attack
}
[SerializeField]
private float radius = 1f;
private Animator anim;
private eState state;
private float impactTime = 0.399f;
private MonsterController target;
private Coroutine attackRoutine;
public float Radius
{
get
{
return this.radius;
}
}
// Start is called before the first frame update
void Start()
{
this.anim = GetComponent<Animator>();
this.target = GameObject.FindObjectOfType<MonsterController>();
}
// Update is called once per frame
void Update()
{
}
public void Attack(MonsterController target)
{
Debug.Log("Attack!!");
//anim.SetInteger("State",2);
PlayAnimation(eState.Attack);
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
GizmosExtensions.DrawWireArc(this.transform.position,this.transform.forward,360,this.radius);
}
private 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.attackRoutine != null)
{
this.StopCoroutine(this.attackRoutine);
}
this.attackRoutine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
break;
}
}
else
{
Debug.LogFormat("{0}는 현재 상태와 동일한 상태입니다.", state);
}
}
//코루틴 함수
//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.HitDamage();
Debug.LogFormat("target.state: {0}",this.target.state);
// this.target.state = MonsterController.eState.GetHit;
this.target.PlayAnimation(MonsterController.eState.GetHit);
Debug.LogFormat("after target.state: {0}", this.target.state);
yield return new WaitForSeconds(animStateInfo.length - this.impactTime); //0.72초만큼 기다림
this.target.PlayAnimation(MonsterController.eState.Idle);
//Idle애니메이션을 함
//this.anim.SetInteger("State", 0);
this.PlayAnimation(eState.Idle);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static Test2.HeroController;
using System;
namespace Test2
{
public class MonsterController : MonoBehaviour
{
public enum eState
{
Idle, Run, GetHit
}
[SerializeField]
private float radius = 1f;
private Animator anim;
public eState state;
public Action onHit;
public float Radius
{
get
{
return this.radius;
}
}
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
}
public void HitDamage()
{
//
//
//Debug.LogError("피해 애니메이션을 실행합니다.");
// this.anim.SetInteger("State", 2);
//this.state = eState.GetHit;
this.onHit();//대리자 사용
}
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;
}
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DestroyParticle : MonoBehaviour
{
private ParticleSystem ps;
// Start is called before the first frame update
void Start()
{
this.ps = this.GetComponent<ParticleSystem>();
//duration이 끝나면 제거 - 코루틴으로 구현
StartCoroutine(this.CoWaitForPlayAfterDestroy());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator CoWaitForPlayAfterDestroy()
{
yield return new WaitForSeconds(this.ps.main.duration);
Destroy(this.gameObject);
}
}
'3d 콘텐츠 제작' 카테고리의 다른 글
아이템 획득 후 장비 착용 연습 2 (0) | 2023.08.11 |
---|---|
아이템 획득시, 장착 연습 (0) | 2023.08.11 |
씬의 몬스터 제거시 포탈 생성 연습(동적 데이터 생성 및 관리) (0) | 2023.08.10 |
SimpleRPG-GameScene에서 플레이어 이동 및 Attack (0) | 2023.08.09 |
Unity-플레이어 조작 구현 연습-이동하기 (0) | 2023.08.08 |