노력과 삽질 퇴적물

유니티: 입문 및 기초(2) 본문

프로그래밍note/엔진 관련

유니티: 입문 및 기초(2)

MTG 2016. 1. 3. 23:43

* Unity3D 5.3.1f1 기준으로 작성됐습니다.

* 다른버전을 원하시면 유니티 아카이브에서 찾을수 있습니다. [#링크]

* 해당 포스트는 입문자용으로 최소한의 내용으로 구성했기 때문에 난이도가 올라가는 부분은 생략했습니다.


유니티: 입문 및 기초(1)

유니티: 입문 및 기초(2)






0. 프로젝트 구성


이후의 예시등은 아래의 캡쳐기준입니다.


* 프로젝트 파일 용량을 줄이려면 (프로젝트명)\Library\ShaderCache내에 있는 폴더를 다 지워도 괜찮지만 

(프로젝트명)\\Library\metadata에 있는걸 삭제시 프로젝트 자체를 열지 못하게 됩니다.






1. 카메라


1) 2D와 3D 전환

오브젝트 배치보다 순서가 앞서는 이유는 화면상 보이는 방식이 다르기때문입니다.

2D게임을 의도했어도 카메라가 3D기준이면 스프라이트 배치시 좌표를 깔끔하게 맞추는 작업이 비효율적으로 될수도 있습니다.



2) 좌표

> Unity3D :: 유니티 카메라 관련 좌표값 변경 스크립트들



3) 기타

> 2D게임으로 생성했어도 스프라이트 배치와 미리보기가 매끄럽지 못하면 해당 옵션을 확인해봐야 합니다.

  projection에는 2가지 옵션값이 있으며 해당 옵션의 차이는 아래와 같습니다.

perspective도 연출에 따라서 2D게임에도 가능하겠지만, 평면적인 배치에 친화적인건 Orthographic쪽입니다.





2. 씬편집


* 씬이란?

 게임 오브젝트들이 배치된 공간으로 씬의 내용물은 디스플레이로 보여지면 카메라를 이용해서 보여지는 영역이 조절됩니다.

안드로이드 API쪽으로는 액티비티랑 비슷한 개념입니다.

제가 아는 범위에서 차이점을 정리하면 아래와 같습니다.

액티비티=디스플레이에 존재하는 출력화면

   씬=카메라로 제어되는 시야내 배치된 오브젝트들의 출력화면



1) 계층(Hierarchy)에서

 가급적 연관성이 높은 오브젝트를 빈 오브젝트(Empy Object)를 이용해서 폴더처럼 묶어두는게 좋다고 봅니다.

 가령, 벽돌을 여러개 쌓아서 성을 만들었는데 성을 다른위치로 옮겨야 합니다. 폴더처럼 묶어두었다면 그 덩어리를 통채로 이동시키면 되지만, 성을 쌓은 벽돌들이 개별이라면 어떻게 될까요?

 아니면 캐릭터 머리위에 따라 다니는 체력게이지 혹은 레벨을 표시하는 객체가 있습니다. 그리고 캐릭터에 부속되는 무기 혹은 이펙트 이미지 출력물의 좌표를 매번 지정하지 않는 방법이 있다면?


> UI관련 캔버스의 경우 씬내에서 2개 이상 사용가능하며 UI 컴포넌트 사용시, 캔버스가 필수라고 합니다.

UI 튜토리얼

UNITY IN DEPTH :: [UI] 체력바 HUD 만들기



2) 인스펙터(Inspector)에서

씬에 존재하는 오브젝트를 대상으로 속성을 지정할수 있습니다.

작업 가능한 속성을 크게 분류하면 아래와 같습니다.

 태그

유니티 - 매뉴얼: 태그

> 명명법에 맞춰 태그명을 지정해두면 최적화에도 유용하다는 글이 보이긴 합니다.

 좌표

 Transform

 x,y,z축 방향으로 좌표값 조정.

 계층에서 빈 오브젝트를 생성해도 기본적으로 있는 속성값입니다.

 Scale에 있는 값을 1보다 크게 하면 조정된 값만큼 부피가 증가합니다.

 물리

 Pysics, Pysics 2D

 충돌체크(xxx Collider), 중력등등

 Pysics가 3D기반, Pysics 2D에 2D기반 충돌처리로 구성됐습니다.

 충돌체크의 경우 오브젝트의 형태나 구현하려는 방식에 맞춰서 선택하시면 됩니다.

<- 중력이 필요한 오브젝트 사용

 사운드

 Audio

 효과음이나 배경음을 추가하려면 Audio Source, Audio Listener속성이 필요합니다.

 스크립트

 Script
 메인 스트림은 C#이고 구글링이 가능한 자료도 이쪽이 많기 때문에 C#을 추천드립니다. 몇몇 책자는 자바스크립트기준이더군요.

 스프라이트

 Sprite Renderer

 '3) 스프라이트 관련'항목 참조.

 매쉬

 유니티에서 기본으로 3D object에 있는 세부속성은

 해당 포스트는 목표상 텍스쳐를 적용하는 부분까지만 다루겠습니다.

 이펙트 (해당 포스트에서는 다루지 않습니다.)



3) 스프라이트 관련

(프로젝트명)\Assets\Sprite에 사용할 이미지 파일을 준비해야 합니다.

해당 경로내 이미지 파일은 Sprite Editor에서 스프라이트 시트처럼 이미지 파일내 사물들을 박스로 잘라서 분할처리도 가능합니다.


Sprite항목에서 이미지 파일을 지정해주면 됩니다.






3. 스크립트


1) 씬전환

1
2
3
4
5
6
7
8
9
10
11
12
void Update ()
{
    ... ... ...
    if (Input.GetKeyDown(KeyCode.Space))
    {
        ... ... ...
        Destroy(gameObject);
        Application.LoadLevel("next_sceneFile_name");
        //    (project_name)\Assets\Scenes내에 있는 씬파일(*.unity)
        ... ... ...
    }
}
cs

참고로

couldn't be loaded because it has not been added to the build settings or the AssetBundle has not been loaded. To add a scene to the build settings use the menu File->Build Settings.같은 에러 발생시

필요한 씬을 활성화 시킨채 빌드세팅애서 추가시키면 해결됩니다.

경로: File>Build settings.



2) 오브젝트 제어 관련

"Space.Self/World" using transform.Translate in #unity3d. #unitytips #gamedev #indiedev

Unity에서 게임오브젝트 접근하는 방법에 대해..

다른 오브젝트 접근하기 | SMART UNITY

> 계층을 통해 씬에 배치한 오브젝트에 스크립트파일을 드래그 앤 드랍시키면,

   해당 오브젝트에 스크립트 연결이 됩니다.

Inspector에서 변수값이나 오브젝트 변경을 쉽게 하려면 클래스 멤버의 접근제한자를 public으로 해야 합니다.

   클래스 멤버 자료형이 맞으면 계층에 있는 UI 구성 혹은 prefabs등을 드래그 앤 드랍으로 연결이 가능합니다.

> (2D기준)총알처럼 다수로 일회성으로 사용되는건 계층에 배치하지 않고,

  Prefabs로 하나 만들어서 다연발이 가능합니다.(Shot, ShotMove)

유니티 - 매뉴얼: 런타임에서의 프리팹의 인스턴스화


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using UnityEngine;
using System.Collections;
 
public class Motin : MonoBehaviour
{
    public float gap = 0.06F;
    public float motionSpeed = 0.002F;
    
    bool isUp = true;
    private Vector3 pos;
    private float orig_y = 0.0F;
    private float max_y = 0.0F;
    
    void Start()
    {
        pos = transform.position;
        orig_y = pos.y;
        max_y = orig_y + gap;
    }
 
    void Update ()
    {
        pos = transform.position;
        
        if(isUp == true)        {            pos.y += motionSpeed;        }
        else if(isUp == false)    {            pos.y -= motionSpeed;        }
        
        if(pos.y < orig_y)
        {
            isUp = true;
            pos.y = orig_y;
        }
        else if(pos.y > max_y)
        {
            isUp = false;
            pos.y = max_y;
        }
        
        transform.position = pos;
    }
    
    public Vector3 getPosition()
    {
        Vector3 tmpPos = transform.position;
        
        return tmpPos;
    }
}
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
 
public class ControlCharacter : MonoBehaviour
{
    public Text nameTxt;
    public string nameStr = "Insert_name";
 
    public Text hpText;
    public int hp = 0;
    public int hpMax = 100;
 
    private Vector3 pos;
    //private bool _isEnd = false;
    private Shot obj_shot;
    private Motion motion;
 
    void Start()
    {
        nameTxt = nameTxt.GetComponent<Text>();
        nameTxt.text = nameStr;
        hpText = hpText.GetComponent<Text>();
 
        obj_shot = GetComponent<Shot>();
        //motion = GetComponent<Motion>();
 
        SetText();
    }
 
    void Update()
    {
        if (Input.touchCount == 1)
        {
        }
    }
 
    private Vector3 tmpPos;
    void FixedUpdate()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (obj_shot != null)
            {
                tmpPos = transform.position;//motion.getPosition();
                obj_shot.doShot(true, tmpPos.x, tmpPos.y);
                GetComponent<AudioSource>().Play();
            }
        }
        
        SetText();
    }
 
    void SetText()
    {
        hpText.text = "HP: " + hp.ToString() + " / " + hpMax.ToString();
    }
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using UnityEngine;
using System.Collections;
 
public class Shot : MonoBehaviour
{
    public Transform PrefabShot;
    public float shootingRate = 0.25f;
    
    private float shootCooldown;
    private Vector3 pos;
 
    void Start()
    {
        shootCooldown = 0f;
    }
    
    void Update()
    {
        if (shootCooldown > 0)
        {
            shootCooldown -= Time.deltaTime;
        }
    }
    
    public void doShot(bool isShot, float argX, float argY)
    {
        shootCooldown = shootingRate;
 
        var shotTransform = Instantiate(PrefabShot) as Transform;
        pos.x = argX;
        pos.y = argY;
        shotTransform.position = pos;//argPos;//transform.position;
 
        ShotMove beam = shotTransform.gameObject.GetComponent<ShotMove>();
        if (beam != null)
        {
            beam._isShot = isShot;
        }
    }
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using UnityEngine;
using System.Collections;
 
public class ShotMove : MonoBehaviour
{
    public int damage = 10;
    public bool _isShot = false;
    public int startX, startY;
    public string tag = "ATK_to_Enemy";/*    ATK_to_enemy
                                            ATK_to_player
                                        */
 
    public Vector2 speed;// new Vector2(6, 4);
    private Vector2 movement;
 
    private Vector3 pos;
    
    void Start ()
    {
        pos.x = startX;
        pos.y = startY;
        transform.position = pos;
        //pos = transform.position;
    }
    
    void Update()
    {
        if(_isShot)
        {
            speed = new Vector2(64);
            pos = transform.position;
            pos.x = pos.x + speed.x;
            pos.y = pos.y + speed.y;
            
            if(pos.y > 40.0F)    {    Destroy(this.gameObject);        }
            //else                {    transform.position = pos;    }//너무 빨라서 조절 안 됨.
            else                {    movement = new Vector2( pos.x, pos.y);    }
        }
    }
    
    void FixedUpdate()
    {
        // Apply movement to the rigidbody
        GetComponent<Rigidbody2D>().velocity = movement;
    }
}
cs



3) 충돌체크






4. 조명


몇몇 용어만 정리해두도록 하겠습니다.


1) 조명의 요소[각주:1]

 환경광
 (Ambient Light)

 다른 표면에서 반사된 빛을 받는것.

 반사된 빛을 비교적 저렴하고 간단하게 구현하는 방법.

 난반사광

 (Diffuse Light)

 모든 방향으로 동일하게 반사되므로 위치와는 관계없이 관찰자의 눈에 빛이 도달하고 관찰자의 위치를 고려할 필요가 없다.

 정반사광

 (Specular Light)

 특정한 방향으로 진행하며, 표면에 닿으면 한 방향으로 강하게 반사하여 특정한 각도에서만 관찰할 수 있다. 관찰자의 시점을 모두 고려해야 한다.



2) 라이팅

> Point Lights / Spot Lights / Directional Lights / Area Lights

  유니티 - 매뉴얼: Lighting Overview

라이트(Light) 사용하기






5. IO


1) 클릭, 버튼 메시지

레이캐스트가 코드나 편집상 깔끔하나
NGUI 습작 초기에 레이캐스트가 먹통이라 임시로 쓰던 방법입니다.

 버튼 메시지는 캡쳐상 포함이긴 하지만,

 NGUI 최신버전에서는 비권장이기도 해서 굳이 쓸 필요는 없습니다.
 실제로도 코드상 버튼 메시지를 활용치 않았습니다.

 참고로 예시의 UI_controller는 계층(Hierarchy)에서 입력제어용 코드를 붙인 오브젝트 이름이고,
 함수명은 아래의 코드와 맞춰주면 됩니다.

MainCamera Culling Mask: Default, TransparentFX, IgnoreRaycast, Water


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
... ... ...
void Update ()
{
    if (Input.touchCount >= 1 || Input.GetKey(KeyCode.Mouse0))
    {
        DoClick();
    }
}
 
void DoClick()
{
    if (UICamera.lastHit.collider != null)
    {
        if (UICamera.lastHit.collider.name == "obj_name")
        {
            ... ... ...
        }
    }
}
... ... ...
cs






기타. 샘플 프로젝트


* 샘플에 포함된 이미지들은 학습용 예제로만 사용했습니다.

sample_proj.7z


Min-gu, Kim :: Physics.Raycast 활용하여 3D 공간에서 충돌 체크하기

> 레이캐스트 샘플






기타. 변경이력


일자

변경이력

2016-01-03

 초안

2016-03-16

 5. IO 추가.

2016-05-11

 Space.Self/World 팁 링크 추가.


  1. DirectX9를 이용한 3D GAME 프로그래밍 입문, pp151-152. [본문으로]