728x90

이 글은 인프런 "솔리디티 깨부수기" 강의를 수강한 후 정리한 글입니다. 

 

[지금 무료] 솔리디티 깨부수기 | D_One - 인프런

D_One | 이 강의를 통해서, 스마트 컨트랙 제작을 위한 솔리디티 언어를 배울수 있습니다., 코딩이 처음인 분들도 OK! 처음 배우는 솔리디티, 쉽게 시작해보세요. 강의 주제 📖 [사진] 이 강의에서

www.inflearn.com


event 란, 블록체인 네트워크의  블록에 특정값을 기록하는 것을 말한다.

예를들어서, 송금하기 라는 함수가 있다고 가정하였을때, 송금하기 버튼을 누르면, 누른 사람의 계좌와 금액이 이벤트로 출력이 되어서 블록체인 네트워크 안에 기록이 된다. 

로그를 사용하여, 블록에 각인시키는것은  일반적으로 string 이나 다른 값들을 스마트컨트랙에 저장하는것보다 효율적이다.

event 이벤트의이름 (쓰고자하는 타입과 이름);

 

아래 코드를 작

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0 <0.9.0;

contract lec13 {
   
    event info(string name, uint256 money);
    
    function sendMoney() public {
        emit info("KimDaeJin", 1000);
    }
}

sendMoney 함수를 보시면 emit을 통해서 info 이벤트를 생성했던 string name, uint256 money 두 개 값들을  받아서 출력하려고 한다. 

emit 이벤트이름 (이벤트 파라메터 값넣어주기)

 

 

remix에 코드를 실행해보면 아래와 같은 화면이 나온다. 

logs 부분에 저희의 info 이벤트를 확인하면 값은 김대진, 1000을 확인할 수 있다. 

즉, 블록 안에 event info가 저장이 되어서 info 값을이 블록 안에 저장이 되었기에 언제든지 들고 와서 쓸 수 있다. 

 

728x90

이 글은 인프런 "따라하면서 배우는 고박사의 유니티" 강의를 듣고 정리한 글입니다. 

 

[지금 무료] 따라하면서 배우는 고박사의 유니티 기초 | 고박사 - 인프런

고박사 | 유니티로 게임을 개발하고 싶은 초보자를 대상으로 하며, 유니티 설치부터 2D/3D 게임 개발에 필요한 기초 지식까지 자세하게 설명합니다. (강의에 사용되는 모든 리소스는 영상 상단의

www.inflearn.com


Unity Terrain

유니티에서 제공하는 미들웨어 엔진으로 높이 맵을 제작할 수 있는 툴

맵의 높낮이를 설정(높낮이 정보는 grayscale 형태의 raw 파일로 저장)하고,타일링된 텍스처를 여러 장 겹친 후 마스킹 텍스처의 RGBA를 섞어주는 방식

 

장점

  • 제작 및 수정이 용이하다 (작업시간 단축)
  • LOD (Level of Detail) 지원 (최적화 작업)
  • Asset Store에 제공되는 다양한 지형지물 오브젝트, Terrain 텍스처

단점

  • 메모리를 많이 사용한다
  • 연산이 많아 CPU 자원을 많이 사용한다 (=게임이 느려진다)
  • 2018.3 이상 버전은 “Draw Instanced” 기능으로 CPU의 비용을 절감하고 GPU로 처리

Terrain에 사용할 Asset Import

Asset Store에서 “Terrain Tools Sample Asset Pack” 검색 후 다운로드해야하지만 에셋스토어 만료로 아래 에셋을 다운받아 진행하겠다. 

https://assetstore.unity.com/packages/3d/vegetation/trees/realistic-tree-9-rainbow-tree-54622

 

Realistic Tree 9 [Rainbow Tree] | 3D 나무 | Unity Asset Store

Elevate your workflow with the Realistic Tree 9 [Rainbow Tree] asset from Pixel Games. Find this & other 나무 options on the Unity Asset Store.

assetstore.unity.com

 

 

 

 

Terrain 게임오브젝트 생성

GameObject > 3D Object > Terrain에서 생성한다.

 
 

​Terrain object 의 크기를 아래와 같이 설정한다. 


Create Neighbor Terrains

Terrain의 주변에 현재 Terrain의 기본 텍스쳐,크기를 가지는 Terrain 생성하는 메뉴이다. 

현재 Terrain이 배치된 곳을 기준으로 이웃한 네 위치에 Terrain을 생성할 수 있게 가이드라인을 생성된다. 클릭하면 동일한 크기의 Terrain이 생성되고, 생성된 Terrain을 기준으로 가이드라인의 개수도 늘어나게 된다.

​기본으로 설정된 Texture Layer가 있을 때, 이웃 Terrain에 그대로 적용된다. 


Paint Terrain

Terrain에 보여지는 이미지, 높낮이 등을 설정할 수 있는 메뉴로 아래 이미지와 같이 드롭다운으로 원하는 메뉴를 선택할 수 있다.

 

Raise or Lower Terrain

Brush 정보를 바탕으로 Terrain의 높낮이를 바꾸는 기능으로 Brush Size가 10으로 동일할 때, Opacity 값에 따른 높이 변화를 확인할 수 있다. 

 
 
 

Paint Holes

: Brush 정보를 바탕으로 Terrain에 구멍을 뚫거나 메꾸는 기능

 

Paint Texture

: Terrain에 Texture를 입혀주는 역할 (여러 장의 Texture 사용 가능)

 

Paint Texture 선택 > Edit Terrain Layers > create layer > 원하는 texture 선택

레이어를 선택했을 때 바로 아래에 있는 레이어 정보에서 레이어의 텍스처, 노말, 정보 등을 설정할 수 있으며, 레이어 에셋을 설정하여 설정을 변경할 수도 있다.

Terrrain Layer - Normal Map

Normal Map이란?

모델링(Mesh)을 좀 더 자세하게 표현하기 위해 사용되는 기법으로 
굴곡을 점(Vertex) 정보를 수정해서 표현하게 되면 폴리곤의 개수가 늘어나 
메모리가 증가하기 때문에 빛이 반사되는 벡터(Normal Vector) 정보를 수정해서 
굴곡을 표현하는 기법 (실제 물리적 충돌을 했을 때는 평지)

이때 벡터 정보를 바꾸는 이미지가 Normal Map Texture

 

Terrain Layer - Tiling Setting

 

: 원본 이미지를 어느정도 크기로 배치할지 설정

 

Terrain Layer을 몇가지를 더 추가하고 원하는 레이어를 선택하고 브러쉬 정보를 설정한 다음 scene view에 드래그하게 되면, 텍스처가 변화한다.

 

Terrain Asset에 현재 Terrain의 레이어 배치 정보 또한 함께 저장된다.

 

 

Set Height

: 높이의 최대치를 설정하여 Terrain에 평평한 언덕을 만드는 기능

 

Smooth Height

: Terrain의 언덕 가장자리를 부드럽게 다듬어 주는 기능

 
 

Stamp Terrain

: 설정된 Stamp Height 높이만큼 브러쉬와 동일한 모양을 Terrain에 찍어낸다.

Rasie or Lower Terrain처럼 탑을 쌓아가는 형태가 아닌 도장찍듯이 동일한 모양의 높이맵을 찍는다. 

“New Brush”를 이용해 원하는 브러쉬도 생성할 수 있다.“New Brush” > 원하는 텍스처 선택 (회색조의 텍스처가 더 정확한 모양을 낼 수 있음)

 
 

Paint Trees

: Unity의 Tree 오브젝트 또는 일반 오브젝트를 Terrain에 배치하는 기능

 

Unity의 Tree 오브젝트 생성은 GameObject > 3D Object > Tree 생성할 수 있으며, 나무잎, 가지 오브젝트를 직접 추가할 수 있다. 

 

Tree 오브젝트 등록

Edit Trees > Add Tree 에서 Tree Prefab에 원하는 오브젝트를 선택하고 add하면 생성된다. 

 

 

mass place tress 으로 원하는 숫자만큼 임의의 위치에 자동으로 나무를 배치할 수 있다. 

 

Paint Details

풀이나 돌 같은 지형지물을 심을 때 사용 (풀, 돌, 게임 오브젝트 등), 풀의 경우 바람에 흔들리는 것을 표현할 수 있다.

edit details > add grass texture로 풀 이미지 생성

풀이 흔들린다..


Terrain Settings

Basic Terrain

: 전체에 대한 기본 설정이라고 할 수 있다.

 

 

Tree & Detail Objects

: 나무, 오브젝트들에 대한 설정을 할 수 있다. 

 

 

Wind Settings for Grass

풀의 바람 설정이 가능하다. 

 

Mesh Resolution & Texture Resolutions

 
 

 

 

export raw 를 눌러 파일을 저장한다. 이후 새로운 씬을 생성한 뒤 terrain 오브젝트를 생성하고 import raw 버튼을 누르게 되면 저장했던 높이 맵 정보가 그대로 반영된다.

728x90

이 글은 인프런 "따라하면서 배우는 고박사의 유니티 기초" 강의를 수강한 후 정리한 글입니다. 

 

[지금 무료] 따라하면서 배우는 고박사의 유니티 기초 | 고박사 - 인프런

고박사 | 유니티로 게임을 개발하고 싶은 초보자를 대상으로 하며, 유니티 설치부터 2D/3D 게임 개발에 필요한 기초 지식까지 자세하게 설명합니다. (강의에 사용되는 모든 리소스는 영상 상단의

www.inflearn.com

 

이번 실습 진행을 위해서는 영상 하단에 게임 월드 패키지를 다운받고 Unity chan! 페이지(https://unity-chan.com/)에서 Data Download를 누르고 라이센스 동의한 후 아래 에셋을 다운받은 후 유니티에 임포트한다.

 

유니티 에셋 스토어에서 플레이어 캐릭터 애니메이션을 구현하기 위해 "RPG Character Mecanim Animation Pack FREE" 다운 받는다.

 

 


게임 월드 구성과 플레이어 캐릭터

Asset의 Prefab 폴더에 있는 게임월드를 hierarchy view에 드래그 한 후 main camera를 position을 설정한다.

이후 빈 오브젝트를 생성해서 player로 사용할 예정이다. 다운받은 캐릭터 에셋을 적용하기 전, model의 크기를 Scale Factor를 2로 키워 모델의 크기를 키워주고 Rig 탭 Optimize Game Object 최적화, 오른속에 무기들기때문에 Right hand 체크한다.


이것을 player의 자식으로 배치한다. 매우 중요하다!! 생성한 player 오브젝트 자식으로 배치하지 않으면 이후에 스크립트 적용시에 nullreferenceexception 오류가 발생할 수 있기 때문이다.. (실제 오류 사례...)

 

캐릭터가 핑크색이 된 이슈 해결

더보기

캐릭터가 마젠타 핑크색가 되어 버렸다..

이렇게 되는 이유는 materials의 문제가 발생한 경우로 일반적인 경우에는 Render Pipeline을 적용하면 된다고 했지만.. 2022.3.22 버전의 유니티에서는 그런 기능은 없다..

 

없다면 방법은 두 가지이다. 유니티 버전 다운그레이드를 하거나, 일일이 materials의 shader를 standard로 바꿔주면 된다. 

 

CameraController 스크립트

이전 강의 Navigation mesh 에서 작성한 스크립트랑 동일하며, main camera의 타겟 추적, 타겟과의 거리 조절, 회전을 제어하는 스크립트를 작성한다. 

using UnityEngine;

public class CameraController : MonoBehaviour
{
	[SerializeField]
	private	Transform	target;				// 카메라가 추적하는 대상
	[SerializeField]
	private	float		minDistance = 3;	// 카메라와 target의 최소 거리
	[SerializeField]
	private	float		maxDistance = 30;	// 카메라와 target의 최대 거리
	[SerializeField]
	private	float		wheelSpeed = 500;	// 마우스 휠 스크롤 속도
	[SerializeField]
	private	float		xMoveSpeed = 500;	// 카메라의 y축 회전 속도
	[SerializeField]
	private	float		yMoveSpeed = 250;	// 카메라의 x축 회전 속도
	private	float		yMinLimit = 5;		// 카메라 x축 회전 제한 최소 값
	private	float		yMaxLimit = 80;		// 카메라 x축 회전 제한 최대 값
	private	float		x, y;				// 마우스 이동 방향 값
	private	float		distance;			// 카메라와 target의 거리

	private void Awake()
	{
		// 최초 설정된 target과 카메라의 위치를 바탕으로 distance 값 초기화
		distance = Vector3.Distance(transform.position, target.position);
		// 최초 카메라의 회전 값을 x, y 변수에 저장
		Vector3 angles = transform.eulerAngles;
		x = angles.y;
		y = angles.x;
	}

	private void Update()
	{
		if ( target == null ) return;	// target이 존재하지 않으면 실행 하지 않는다

		// 오른쪽 마우스를 누르고 있을 때
		if ( Input.GetMouseButton(1) )
		{
			// 마우스를 x, y축 움직임 방향 정보
			x += Input.GetAxis("Mouse X") * xMoveSpeed * Time.deltaTime;
			y -= Input.GetAxis("Mouse Y") * yMoveSpeed * Time.deltaTime;
			// 오브젝트의 위/아래(x축) 한계 범위 설정
			y = ClampAngle(y, yMinLimit, yMaxLimit);
			// 카메라의 회전(Rotation) 정보 갱신
			transform.rotation = Quaternion.Euler(y, x, 0);
		}

		// 마우스 휠 스크롤을 이용해 target과 카메라의 거리 값(distance) 조절
		distance -= Input.GetAxis("Mouse ScrollWheel") * wheelSpeed * Time.deltaTime;
		// 거리는 최소, 최대 거리를 설정해서 그 값을 벗어나지 않도록 한다
		distance = Mathf.Clamp(distance, minDistance, maxDistance);
	}

	private void LateUpdate()
	{
		if ( target == null ) return;	// target이 존재하지 않으면 실행 하지 않는다

		// 카메라의 위치(Position) 정보 갱신
		// target의 위치를 기준으로 distacne만큼 떨어져서 쫓아간다
		transform.position = transform.rotation * new Vector3(0, 0, -distance) + target.position;
	}
	
	private float ClampAngle(float angle, float min, float max)
	{
		if ( angle < -360 )	angle += 360;
		if ( angle > 360 )	angle -= 360;

		return Mathf.Clamp(angle, min, max);
	}
}

위의 스크립트를 main camera의 component로 적용한 후 타겟 변수에는  player object의 자식으로 빈 오브젝트를 생성해서 CameraTarget으로 이름을 변경 후 위치를 조정한다. main camera의 타겟 변수에 적용한다. 


플레이어 오브젝트의 위치는 현재 모델의 발끝으로 설정되어 있어 카메라가 보는 위치 조정을 위해 빈 오브젝트를 만들어 설정하였다. 

 


플레이어 캐릭터 이동 

Movement3D & PlayerController 스크립트 

이동이 가능한 오브젝트에 이동 제어에 사용되는 Movement3D 스크립트 작성한다. 

using UnityEngine;

public class Movement3D : MonoBehaviour
{
    [SerializeField]
    private float   moveSpeed = 5;      // 이동 속도
    private Vector3 moveDirection;      // 이동 방향

    private CharacterController characterController;

    public float MoveSpeed
    {
        // 이동속도는 2~5 사이의 값만 설정 가능
        set => moveSpeed = Mathf.Clamp (value, 2.0f, 5.0f);
    }
    
    private void Awake()
    {
        characterController = GetComponent<CharacterController>();
    }
    
    private void Update()
    {
        // 이동 설정. CharacterController의 Move() 함수를 이용한 이동 
        characterController. Move(moveDirection * moveSpeed * Time.deltaTime);
    }

    public void MoveTo(Vector3 direction)
    {
        moveDirection = new Vector3(direction.x, moveDirection.y, direction.z);
    }

    public void JumpTo()
    {
        //캐릭터가 바닥을 밟고 있으면 점프
        if (characterController.isGrounded == true)
        {
            moveDirection.y = jumpForce;   
        }
    }

}

 

외부에서 사용하는 MoveTo()를 이용하여 이동방향을 설정하고, Update()에서 characterController class의 Move()를 호출하여 오브젝트가 moveDirection 방향으로 이동하게 된다. 



플레이어를 제어하는 PlayerController 스크립트를 작성한다. 

 

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] 
    private KeyCode     jumpKeyCode = KeyCode.Space;
    [SerializeField] 
    private Transform   cameraTransform;
    private Movement3D  movement3D;

    
    private void Awake()
    {
        Cursor.visible      = false;                   // 마우스 커서를 보이지 않게 
        Cursor.lockState    = CursorLockMode.Locked;   // 마우스 커서 위치 고정
        
        movement3D = GetComponent<Movement3D>();

    }
    private void Update()
    {
        // 방향키를 눌러 이동
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");

        // 이동 속도 설정 (앞으로 이동할때만 5, 나머지는 2)
        movement3D.MoveSpeed = z > 0? 5.0f: 2.0f;
        // 이동 함수 호출 (카메라가 보고있는 방향을 기준으로 방향키에 따라 이동)
        movement3D.MoveTo(cameraTransform.rotation * new Vector3(x, 0, z));

        // 회전 설정 (항상 앞만 보도록 캐릭터의 회전은 카메라와 같은 회전 값으로 설정)
        transform.rotation = Quaternion.Euler(0, cameraTransform.eulerAngles.y,0);
    }
}

캐릭터가 카메라가 바라보는 전방 방향을 계속 바라보도록 회전 설정을 추가하였으며 플레이어의 y축 회전값과 카메라의 y축 회전값이 동일하게 움직이고 있음을 알 수 있다.

 

player 오브젝트에 방금 작성한 스크립트 2개와 character controller도 적용한다. step offset을 0.4, center를 (0,1,0)으로 설정한다. playercController 스크립트에 Main camera를 등록한다. 

 


플레이어 캐릭터 물리와 점프

Movement3D 스크립트

 [SerializeField]
    private float   gravity = -9.81f;   // 중력 계수
    [SerializeField]
    private float   jumpForce = 3.0f;   // 점프 힘

'''

 // 중력 설정, 플레이어가 땅을 밟고 있지 않다면
        // y축 이동방향에 gravity * Time.deltaTime을 더해준다.
        if ( characterController.isGrounded == false)
        {
            moveDirection.y += gravity * Time.deltaTime;
        }

Movement3D 스크립트에 중력 계수와 뛰어오르는 힘을 변수로 설정하고, 플레이어가 공중에 떠 있을 때 moveDirection.y += gravity * Time.deltaTime을 해준다. 

 

PlayerController 스크립트 

외부에서 호출하는 JumpTo 함수를 통해 플레이어가 바닥을 밟고 있으면 y축 이동 방향에 jumpForce 값을 설정하여 jump를 하게 된다. jump키 입력과 JumpTo함수 참조하도록 수정한다. 

 if (Input.GetKeyDown(jumpKeyCode))
        {
            movement3D.JumpTo();
        }

 


플레이어 캐릭터 대기, 이동, 점프 애니메이션

 

애니메이션 적용 전 player에게 무기를 들려주겠다. 애니메이션 손동작에 맞추어 무기가 함께 움직여야 하기 때문에 캐릭터의 오른손 레퍼런스인 righthand object의 자식으로 sword asset을 설정하였다. 

무기의 Transform 정보를 수정한 뒤 material을 적용한다. 

 

Marie_sum Animator에 Player( Animator Cotroller )를 적용하고, Apply Root Motion을 해제한다. 

 

Marie_sum의 Animator view로 blend tree를 생성한 뒤 Movement로 설정한다. blend tree 내부로 이동하여 parameter를 float type 으로 horizontal와 vertical로 설정한다.


blend tree의 type을 2D Simple Directional로 설정하고 파라미터의 Pos x,Pos y축에도 아래 이미지와 같이 설정한다. 

5개의 motion을 만들어주고, 각 모션을 위처럼 설정한다. 

이후에 상태전이를 생성해서 movement에서 jump로 전이하는 조건 설정을 위해 트리거 타입의 파라미터 onJump를 생성한다. 그리고 상태 전이에서 Has Exit Time을 해제한다. 


스토어에서 받아온 RPG 캐릭터 에니메이션에는 이미 이벤트 함수가 설정되어 있기 때문에, 실습에서 사용하는 애니메이션 클립의 이번트에 가서 이벤트 함수를 삭제한다. 

events 탭에 delete event를 해주고 apply 해주면 된다. 

애니메이션 strafe-backward, strafe-right, strafe-left, attack-kick, attack1,2,3,4 모두 적용한다. 


PlayerAnimator

애니메이션 관리, 제어를 하는 PlayerAnimator 스크립트를 작성한다. 

using UnityEngine;

public class PlayerAnimator : MonoBehaviour
{
    [SerializeField]
    private GameObject attackCollision;
    private Animator    animator;
    
    private void Awake()
    {
        animator = GetComponent<Animator>();
    }
    
    public void OnMovement (float horizontal, float vertical)
    {
        animator.SetFloat("horizontal", horizontal);
        animator.SetFloat("vertical", vertical);
    }

    public void OnJump()
    {
        animator.SetTrigger("onjump");
    }

}

위의 스크립트는 애니메이터 파라미터를 제어하는 함수들이 정의되어 있고, OnMovement()는 대기와 이동의 애니메이션 재생에 사용되는 Horizontal, Vertical 파라미터를 제어하고, OnJump()는 애니메이션 재생에 사용되는 onJump 파라미터를 제어한다. 

PlayerController 스크립트 추가

대기, 이동, 점프 행동에 맞춰 애니메이션을 제어하기 위해 PlayerController 스크립트를 수정한다. 

    private PlayerAnimator playerAnimator;
    playerAnimator = GetComponentInChildren<PlayerAnimator>();
'''
  private void Update()
    {
        // 방향키를 눌러 이동
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        
        // 애니메이션 파라미터 설정 (horizontal, verticla)
        playerAnimator.OnMovement(x,z);
'''

      if (Input.GetKeyDown(jumpKeyCode))
            {
                playerAnimator.OnJump();
                movement3D.JumpTo();
            }
'''
}

 

playerAnimator 변수를 만들어 자식에 있는 컴포넌트를 받아오기 위해 GetComponentInChildren을 사용한다. playerAnimator.OnMovement(x,z)매개변수로 방향값을 사용해 대기, 이동 애니메이션을 제어한다. OnJump()를 스페이스 바를 누르면 호출하여, 점프 애니메이션이 되는 것을 알 수 있다. 

위의 playerAnimator 스크립트를 컴포넌트에 적용하고, 게임을 실행하면 대기, 이동, 점프 시에 각각의 애니메이션이 적용되는 것을 알 수 있다. 


플레이어 캐릭터 공격 애니메이션

 

이번에는 발차기를 이용한 공격과 무기를 이용한 연계 공격을 실습하고자 한다.

캐릭터의 animator view로 가서 원하는 공격 애니메이션을 드래그한다. 강의의 경우에서 사용한 2Hand-Sword-Attack-Kick-R1이 없기 때문에 애니메이션 중에서 Unarmed-Attack-R1을 넣어주었다.  


 

전이설정을 위해 trigger 타입의 파라미터 OnKickAttack도 생성하였다.


공격 콤보

animator view에 sub-state Machine을 하나 생성하고 이것의 이름을 WeaponComboAttack으로 설정한다. sub-state Machine의 내부로 이동해서 무기를 이용한 공격 애니메이션을 등록한다. 

 

연계 공격이 가능하도록 attack끼리 상태전이를 연결해준다.. entry에서 연결되는 첫 공격인 attack3으로 첫 공격이 되고, attack1,2,4를 연결해준다. states > movement로 연결해준다. 

공격 전이 조건 설정을 위해 trigger type의 파라미터 onWeaponAttack를 생성한다.

 

공격에서 공격으로 이어지는 상태전이를 선택하고 Condition에 해당 파라미터를 등록한다. 이때 Has Exit Time은 true로 둔다. 공격이 완전히 끝난 이후에 다음 공격을 할 수 있도록 하기 위함이다. 


모든 공격에서 Base Layer로 들어가는 상태전이는 Exit Time은 0.9, Transition Duration은 0.1로 설정한다.

 

Base Layer로 돌아가 Movement에서 WeaponComboAttack으로 이어지는 트랜젝션을 생성한다.

 

해당 트렌젝션은 원할 때 바로 갈 수 있도록 Has Exit Time을 풀어준다. Condition에 OnWeaponAttack을 등록한다. 


플레이어의 물리적 공격과 적 피격 

마우스 좌,우클릭을 하면  공격 애니메이션 재생이 되며 특정 프레임에 이벤트 함수 호출된다. 이때 충돌 박스 AttackCollision 오브젝트 활성화되어서 공격 충돌 박스에 오브젝트가 부딪히게 되면 Take Damage() 함수 호출하면서 피격 애니메이션, 오브젝트 색상 변경이 적용된다. 



Player의 공격을 받게 될 Enemy 오브젝트를 생성하고, Enemy AnimationController 생성하여,Enemy 오브젝트에 적용 후 Apply Root Motion을 해제한다. Capsule Collider 컴포넌트를 추가 후 범위를 지정한다. 


이후 Enemy 오브젝트에 Meterial를 적용한다. 

 

해당 캐릭터는 가만히 서있는 대기 동작과 공격당했을 때의 피격 동작을 수행한다. 피격은 어떤 상황에서도 발생할 수 있기에 Any State를 활용해서 연결되는 상태전이는 모든 상태에서 전이가 가능하다.

​Any state에서 전이하는 조건 설정을 위해 trriger 파라미터 onHit를 생성해서 Condition에 설정한다.  Has Exit Time을 해제한다.


EnemyController 스크립트

캐릭터가 공격받았을 때 애니메이션 재생 등의 처리를 하는 EnemyController 스크립트를 작성한다. 

using System.Collections;
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    private Animator            animator;
    private SkinnedMeshRenderer meshRenderer;
    private Color               originColor;

    private void Awake()
    {
        animator     = GetComponent<Animator>();
        meshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
        originColor  = meshRenderer.material.color;
    }

    public void TakeDamage(int damage)
    {
        // 체력이 감소되거나 피격 애니메이션이 재생되는 등의 코드를 작성 
        Debug.Log(damage+"의 체력이 감소합니다");
        // 피격 애니메이션 재생
        animator.SetTrigger("onhit");
        // 색상 변경
        StartCoroutine("OnHitColor");
    }

    private IEnumerator OnHitColor()
    {
        // 색을 빨간색으로 변경한 후 0.1초 후에 원래 색상으로 변경
        meshRenderer.material.color = Color.red;
        yield return new WaitForSeconds (0.1f);
        meshRenderer.material.color = originColor;
    }
}

 

위의 스크립트를 Enemy 오브젝트에 등록한다. player 자식오브젝트 AttackCollision을 생성해서 위치와 크기를 설정한 뒤 모습이 안보이게 mesh 해제, Box Collider, Rigidbody 추가하고 각각 is Trigger 체크, Use Gravity 해제 설정을 해준다.

 

PlayerAttackCollision 스크립트

공격 충돌 박스를 제어하는 PlayerAttackCollision 스크립트를 작성한다. 

using System.Collections;
using UnityEngine;

public class PlayerAttackCollision : MonoBehaviour
{
    private void OnEnable()
    {
        StartCoroutine("AutoDisable");
    }
    private void OnTriggerEnter (Collider other)
    {
        // 플레이어가 타격하는 대상의 태그, 컴포넌트, 함수는 바뀔 수 있다
        if (other.CompareTag("Enemy"))
        {
            other.GetComponent<EnemyController>().TakeDamage(10);
        }
    }
    private IEnumerator AutoDisable()
    {
        // 0.1초 후에 오브젝트가 사라지도록 한다
        yield return new WaitForSeconds(0.1f);
        gameObject.SetActive(false);
    }
}

충돌박스가 활성화되면 0.1초 뒤에 박스가 사라지도록 한다. 충돌 태그가 enemy 이면 TakeDamage 충돌박스에 부딪친 적의 체력을 감소시킨다.


태그는 위와 같이 설정해 줄 수 있다. 

 

PlayerAnimator 스크립트 추가

using UnityEngine;

public class PlayerAnimator : MonoBehaviour
{
    [SerializeField]
    private GameObject attackCollision;
    private Animator animator;

    private void Awake()
    {
        animator = GetComponent<Animator>();
    }

    public void OnMovement(float horizontal, float vertical)
    {
        animator.SetFloat("horizontal", horizontal);
        animator.SetFloat("vertical", vertical);
    }

    public void OnJump()
    {
        animator.SetTrigger("onJump");
    }

    public void OnKickAttack()
    {
        animator.SetTrigger("onKickAttack");
    }
    public void OnWeaponAttack()
    {
        animator.SetTrigger("onWeaponAttack");
    }
    public void OnAttackCollision()
    {
        attackCollision.SetActive(true);
    }
}

특정 프레임에서 호출하도록 스크립트를 설정하였다. 이후 Marie_sum 캐릭터 오브젝트에 등록된 playerAnimator 스크립트에 AttackCollision에 생성한 충돌박스를 넣어준다. 

 

현재 사용하고 있는 공격 animation에 가서 event에 원하는 프레임을 설정하고 add event를 이용해 함수를 생성한다. 호출을 원하는 함수를 등록한다.

 

 

게임을 재생하면 동작을 수행하게 된다. 이동, 점프, 공격 모두 정상적으로 되는데 왜 무한 루프를 하는걸까.. 알 수 없다. 

728x90

이 글은 인프런 "솔리디티 깨부수기" 강의를 수강한 후 정리한 글입니다. 

 

[지금 무료] 솔리디티 깨부수기 | D_One - 인프런

D_One | 이 강의를 통해서, 스마트 컨트랙 제작을 위한 솔리디티 언어를 배울수 있습니다., 코딩이 처음인 분들도 OK! 처음 배우는 솔리디티, 쉽게 시작해보세요. 강의 주제 📖 [사진] 이 강의에서

www.inflearn.com


이번 주차에서는 두 개 이상의 스마트 컨트랙의 상송받는 경우, 어떻게 해야 하는지에 대해서 알아 보겠다.

// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;


contract Father{
}

contract Mother{
}


contract Son{
}

위의 예제에서 Father, Mother, Son 컨트랙을 생성한 뒤에 아들 컨트랙은 아버지와 어머니 컨트랙에게 기능을 상속 받도록 구현한다. 

 

// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;


contract Father{
    uint256 public fatherMoney = 100;
    function getFatherName() public pure returns(string memory){
        return "KimJung";
    }
    
    function getMoney() public view returns(uint256){
        return fatherMoney;
    }
    
}

contract Mother{
    uint256 public motherMoney = 500;
    function getMotherName() public  pure returns(string memory){
        return "Leesol";
    }
    function getMoney() public view returns(uint256){
        return motherMoney;
    }
}


contract Son is Father, Mother {

}

위에서 Father, Mother 컨트랙에 동일한 기능을 적용한 뒤에 두 컨트랙을 Son 컨트랙에 상속시킨다. 이때, getMoney()라는 같은 이름의 함수가 Father , Mother 컨트랙에 각각 들어 있어서 상속을 받는 경우 오류가 발생하게 된다.

즉 두 개 이상의 스마트 컨트랙을 상속받는 경우에, 동일한 함수를 가지고 있는 경우 오버라이딩을 해줘야 한다,

 

// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;


contract Father{
    uint256 public fatherMoney = 100;
    function getFatherName() public pure returns(string memory){
        return "KimJung";
    }
    
    function getMoney() public view virtual returns(uint256){
        return fatherMoney;
    }
    
}

contract Mother{
    uint256 public motherMoney = 500;
    function getMotherName() public  pure returns(string memory){
        return "Leesol";
    }
    function getMoney() public view virtual returns(uint256){
        return motherMoney;
    }
}


contract Son is Father, Mother {

    function getMoney() public view override(Father,Mother) returns(uint256){
        return fatherMoney+motherMoney;
    }
}

동일한 함수인 getMoney()함수에 virtual을 작성한 뒤에 Son 컨트랙에 override를 해준다. 오버라이드를 한다면 override ( 중복이름의 함수를 가진 스마트 컨트랙 명시)  해줘야 한다.

son 컨트랙을 배포한 결과는 다음과 같으며, 아버지에게 100, 어머니에게 500을 받아서 getMoney 함수는 600을 리턴한다. 

728x90

이 글은 인프런 "솔리디티 깨부수기" 강의를 수강한 후 정리한 글입니다. 

 

[지금 무료] 솔리디티 깨부수기 | D_One - 인프런

D_One | 이 강의를 통해서, 스마트 컨트랙 제작을 위한 솔리디티 언어를 배울수 있습니다., 코딩이 처음인 분들도 OK! 처음 배우는 솔리디티, 쉽게 시작해보세요. 강의 주제 📖 [사진] 이 강의에서

www.inflearn.com


 

오버라이딩(overriding)은 자식 컨트랙이 부모 컨트랙로부터 상속받은 함수를 오버라이드(override)하여, 상속받은 함수를 다르게 변경할 수 있으며, 즉 상속받은 함수를 덮어 씌울 수 있다. 

 

// SPDX-License-Identifier:GPL-30
pragma solidity >= 0.7.0 < 0.9.0;

contract Father{
    string public familyName = "Kim";
    string public givenName = "Jung";
    uint256 public money = 100; 
    
    constructor(string memory _givenName) public {
        givenName = _givenName;
    }
    
    
    function getFamilyName() view public  returns(string memory){
        return familyName;
    } 
    
    function getGivenName() view public  returns(string memory){
        return givenName;
    } 
    
    function getMoney() view public returns(uint256){
        return money;
    }
    

}

contract Son is Father("James"){
    
}
    function getMoney() view  public virtual returns(uint256){
        return money;
    }

이전 강의에서 사용된 예제로 getMoney 함수에 오버라이딩할 함수에 virtual 을 명시해준다.

 

contract Son is Father("James"){
    
    uint256 public earning = 0;
    function work() public {
        earning += 100;
    }
    
     function getMoney() view  public override returns(uint256){
        return money+earning;
    }

}

아들 컨트랙이 아버지 컨트랙에게 돈 100 만원을 상속받는다고 가정하였을때, 아들이 그 100만원을 저금하고, 아들이 일을하면 월급을 또 저금 함으로써 아들돈이 증가 할 수 있는 경우를 위에와 같이 earning을 추가함으로서 결과를 출력할 수 있다. 

earning 변수는 Son 컨트랙이 일을 하였을 때 벌어 들이는 수입으로 work() 함수에서 earning 변수값을 100 씩 증가한다.

그리고 getMoeny 를 누르면 아버지에게 받은돈 moeny 와 아들이 번돈 earning 이 합쳐져서 아들의 재산을 보여준다. 

 

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Father {
    string public familyName = "Kim";
    string public givenName = "Jung";
    uint256 public money = 100;

    constructor(string memory _givenName) {
        givenName = _givenName;
    }

    function getFamilyName() public view returns (string memory) {
        return familyName;
    }

    function getGivenName() public view returns (string memory) {
        return givenName;
    }

    function getMoney() public view virtual returns (uint256) {
        return money;
    }
}

contract Son is Father("James") {
    uint256 public earning = 0;

    function work() public {
        earning += 100;
    }

    function getMoney() public view override returns (uint256) {
        return money + earning;
    }
}

 

+ Recent posts