반응형

유니티 2D Sprite 및 Tile Palette의 각 tile마다 물리적 충돌과 그림자 적용을 위한 Physics Shape을 설정할 수 있다.

 

스프라이트를 임포트하면 Inspector - Sprite Mode - Generate Physics Shape 설정을 체크해서 Physics Shape 설정을 하지 않은 스프라이트에 대해 기본 Physics Shape을 생성하게 할 수 있다. 하지만 이 설정은 shape이 임의로 정해진다.

 

원하는 Physics Shape 설정을 위해 Sprite에서 Open Sprite Editor 클릭 - Sprite Editor를 Custom Physics Shape으로 바꾼다

 

아래 Outline Tool에서 Generate 클릭. (아니면 원하는 Tile에서 클릭&드래그로 Physics Shape을 만들 수 있다)

 

원하는 형태로 shape을 변경한다. shape은 여러 개 만들 수 있다. 여기서 만드는 Physics Shape은 그림자 뿐만 아니라 충돌 처리를 위한 경계로도 사용된다.

 

위 그림처럼 필요에 맞게 변경한다.

 

타일맵은 Tilemap Collider 2D를 추가하고 Composite Operation을 Merge로 바꾼다. Composite Collider 2D를 추가하고, 같이 추가되는 Rigidbody 2D에서 Body Type을 Static으로 바꾼다. (Body Type이 Dynamic으로 되어 있으면 게임 실행 시 밑으로 떨어진다)

 

Shadow Caster 2D를 추가하고 Casting Source를 Composite Collider 2D로 바꾼다. (Composite Collider 2D 관련 과정은 충돌 계산 및 그림자 효과를 효율적으로 만들기 위한 것이다. 그냥 Tilemap Collider 2D와 Shadow Caster 2D만 추가해서 Shadow Caster 2D의 Casting Source를 Tilemap Collider 2D로 바꾸고 사용해도 문제는 없다)

 

만약 위 과정을 진행 하고나서 나중에 Sprite의 Physics Shape을 바꾸면 이미 만들어져 있는 Tilemap에서는 바뀐 Physics Shape이 바로 적용되지 않는다. 이때는 Tilemap Collider 2D를 Reset한다. (그러면 Composite Operation을 다시 Merge로 바꿔줘야 한다)

 

반응형
Posted by J-sean
:
반응형

기본적인 2D 캐릭터 컨트롤러. 2D 테스트에 활용 할 수 있다.

빈 오브젝트에 스프라이트 렌더러 정도만 추가하고 사용하면된다.

 

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using UnityEngine;
 
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(CapsuleCollider2D))]
[RequireComponent(typeof(PlatformEffector2D))]
 
public class CharacterController2D : MonoBehaviour
{
    public float maxSpeed = 3.0f;
    public float jumpHeight = 6.0f;
    public float gravityScale = 1.5f;
 
    float moveDirection = 0.0f;
    float collisionCheckRadius = 0.0f;
    bool facingRight = true;
    bool isGrounded = false;
    bool isJumping = false;
 
    Rigidbody2D rigidBody;
    CapsuleCollider2D mainCollider;
    PlatformEffector2D platformEffector;
 
    Vector3 cameraPos;
    public Camera mainCamera;
 
    void Start()
    {
        rigidBody = GetComponent<Rigidbody2D>();
        mainCollider = GetComponent<CapsuleCollider2D>();
        platformEffector = GetComponent<PlatformEffector2D>();
 
        rigidBody.freezeRotation = true;
        // Freeze rotation: Prevents the Rigidbody2D from rotating due to collisions or physics forces.
        rigidBody.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
        // Collision Detection Mode: Continuous - Rigidbody2D will use continuous collision detection
        // to prevent fast-moving objects from passing through colliders.
        rigidBody.gravityScale = gravityScale;
 
        mainCollider.usedByEffector = true;
        platformEffector.useOneWay = false;
        platformEffector.useSideFriction = false;
        // PlatformEffector없이 Collider만 사용하면 캐릭터가 점프 후 벽에 붙는 효과가 발생한다. 마찰력 때문이다.
        // platformEffector.useSideFriction을 false로 설정하면 벽에 붙는 효과를 제거할 수 있다.
 
        facingRight = transform.localScale.x > 0.0f;
        collisionCheckRadius = mainCollider.size.x * 0.6f * Mathf.Abs(transform.localScale.x);
 
        if (mainCamera)
        {
            cameraPos = mainCamera.transform.position;
        }
    }
 
    void Update()
    {
        //if ((Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D)) && (isGrounded || Mathf.Abs(rigidBody.linearVelocity.x) > 0.01f))        
        if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D)) // 위 조건문으로 변경하면, 점프만 했을때 좌우로 움직이지 않게 된다.
        {
            moveDirection = Input.GetKey(KeyCode.A) ? -1 : 1;
        }
        else if (isGrounded || rigidBody.linearVelocity.magnitude < 0.01f)
        {
            moveDirection = 0.0f;
        }
 
        // Jumping logic. 키 입력 확인이 FixedUpdate()에서 처리되면 제대로 감지되지 않을때가 있다.
        if (Input.GetKeyDown(KeyCode.W) && isGrounded)
        {
            isJumping = true;
        }
 
        // Change facing direction
        if (moveDirection != 0)
        {
            if (moveDirection > 0 && !facingRight)
            {
                facingRight = true;
                transform.localScale = new Vector3(Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z);
            }
            else if (moveDirection < 0 && facingRight)
            {
                facingRight = false;
                transform.localScale = new Vector3(-Mathf.Abs(transform.localScale.x), transform.localScale.y, transform.localScale.z);
            }
        }
    }
 
    void FixedUpdate()
    {
        Vector3 groundCheckPos = mainCollider.bounds.min + new Vector3(mainCollider.size.x * 0.5f, mainCollider.size.x * 0.5f, 0.0f);
 
        // Check if player is grounded. OverlapCircleAll(): Get a list of all Colliders that fall within a circular area.
        Collider2D[] colliders = Physics2D.OverlapCircleAll(groundCheckPos, collisionCheckRadius);
 
        isGrounded = false;
 
        // 플레이어 콜라이더가 아니면 벽 또는 바닥 콜라이더라 가정하고 점프 할 수 있도록 충돌체크한다.
        // 벽을 타고 멀티 점프가 가능하다.
        if (colliders.Length > 0)
        {
            for (int i = 0; i < colliders.Length; i++)
            {
                if (colliders[i] != mainCollider)
                {
                    isGrounded = true;
                    break;
                }
            }
        }
 
        if (isJumping)
        {
            // Apply jump force            
            rigidBody.linearVelocity = new Vector2(rigidBody.linearVelocity.x, jumpHeight);
            //rigidBody.AddForce(new Vector2(0.0f, jumpHeight * rigidBody.mass), ForceMode2D.Impulse);
            isJumping = false;
        }
        else
        {
            // Apply movement velocity
            rigidBody.linearVelocity = new Vector2(moveDirection * maxSpeed, rigidBody.linearVelocity.y);
        }
 
        // OverlapCircleAll() 이 커버하는 범위를 시각적으로 표시하기 위해 디버그 라인 그리기.
        Debug.DrawLine(groundCheckPos, groundCheckPos - new Vector3(0.0f, collisionCheckRadius, 0.0f), isGrounded ? Color.green : Color.red);
        Debug.DrawLine(groundCheckPos, groundCheckPos + new Vector3(collisionCheckRadius * (facingRight ? 1 : -1), 0.0f, 0.0f), isGrounded ? Color.green : Color.red);
    }
 
    private void LateUpdate()
    {
        // Ensure the camera follows the player smoothly
        if (mainCamera)
        {
            // 카메라의 y, z 좌표는 고정하고, x 좌표만 플레이어의 x 좌표로 설정.
            mainCamera.transform.position = new Vector3(transform.position.x, cameraPos.y, cameraPos.z);
        }
    }
}
 

 

 

반응형
Posted by J-sean
:
반응형

스크립트 작성 시 SerializeField와 Range 어트리뷰트를 함께 사용할 수 있다.

 

스크립트를 작성한다. 두 가지 방법으로 작성 할 수 있다.

 

a, b 멤버 변수를 Range에 정해진 범위 내에서 조절 가능하다.

 

반응형
Posted by J-sean
:
반응형

애니메이션 커브를 사용해 보자.

 

Sphere와 Cube 2개(A지점, B지점)를 생성한다.

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Curve : MonoBehaviour
{
    public Transform targetA;
    public Transform targetB;
 
    public AnimationCurve lerpCurve;
 
    public Vector3 lerpOffset;
    public float lerpTime = 3.0f;
    private float timer = 0.0f;
 
    // Update is called once per frame
    void Update()
    {
        timer += Time.deltaTime;
        if (timer > lerpTime)
        {
            timer = lerpTime;
        }
 
        float lerpRatio = timer / lerpTime;
        Vector3 positionOffset = lerpCurve.Evaluate(lerpRatio) * lerpOffset;
 
        transform.position = Vector3.Lerp(targetA.position, targetB.position, lerpRatio) + positionOffset;
    }
}
 

 

스크립트를 작성한다.

 

작성한 스크립트를 Sphere에 추가한다.

 

TargetA, TargetB를 지정하고 Lerp Curve, Lerp Offset을 수정한다.

 

Lerp Curve는 위와 같이 수정한다.

 

플레이하면 Sphere가 부드럽게 이동한다.

 

반응형
Posted by J-sean
:
반응형

유도 미사일을 만들어 보자.

 

Square(Target)와 Capsule(Missile) 스프라이트를 생성한다.

 

Missile에 Rigidbody2D(Gravity Scale = 0)와 스크립트를 추가한다.

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
[RequireComponent(typeof(Rigidbody2D))]
public class Guide : MonoBehaviour
{
    public Transform target;
    private Rigidbody2D rb;
 
    public float speed = 5.0f;
    public float rotateSpeed = 200.0f;
 
    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }
 
    private void FixedUpdate()
    {
        Vector2 direction = (Vector2)target.position - rb.position;
        direction.Normalize();
 
        float rotateAmount = Vector3.Cross(direction, transform.up).z;
        rb.angularVelocity = -rotateAmount * rotateSpeed;
 
        rb.velocity = transform.up * speed;
    }
}
 

 

스크립트를 작성한다.

 

스크립트 Target 변수에 Target 오브젝트(Square)를 선택한다.

 

 

플레이 하면 미사일이 목표를 향해 날아간다.

 

반응형
Posted by J-sean
:
반응형

특정 조건에서 카메라를 흔들어 보자.

 

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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class CameraShake : MonoBehaviour
{
    public float shakeTime = 1.0f;
    public float shakeSpeed = 2.0f;
    public float shakeAmount = 1.0f;
 
    private Transform cam;
 
    // Start is called before the first frame update
    void Start()
    {
        cam = GameObject.FindGameObjectWithTag("MainCamera").transform;
    }
 
    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            StartCoroutine(Shake());
        }
    }
 
    IEnumerator Shake()
    {
        Vector3 originPosition = cam.localPosition;
        float elapsedTime = 0.0f;
 
        while (elapsedTime < shakeTime)
        {
            Vector3 randomPoint = originPosition + Random.insideUnitSphere * shakeAmount;
            cam.localPosition = Vector3.Lerp(cam.localPosition, randomPoint, Time.deltaTime * shakeSpeed);
 
            yield return null;
 
            elapsedTime += Time.deltaTime;
        }
 
        cam.localPosition = originPosition;
    }
}
 

 

'S'키를 누르면 카메라가 진동한다.

 

스크립트를 Cube 오브젝트에 추가하고 'S'키를 누르면 카메라가 진동한다.

 

반응형
Posted by J-sean
:
반응형

유니티 NavMesh Components는 아직 정식버전이 없다. 아래와 같이 설치하자.

 

Window - Package Manager를 실행한다.

 

+ 버튼을 클릭하고 Add package from git URL...을 선택한다.

 

com.unity.ai.navigation을 입력하고 Add 버튼을 클릭한다.

 

설치가 완료되면 Preview가 표시된다.

 

 

Packages에 AI Navigation이 포함되어 있다.

※ 참고

Navigation and Pathfinding

 

반응형
Posted by J-sean
:
반응형

에러나 예외 발생 기록을 남겨보자.

Application.RegisterLogCallback()는 deprecated 되었으므로 Application.logMessageReceived를 사용한다.

 

Cube를 하나 생성하고 스크립트(Move)를 추가한다.

 

Move 스크립트는 ESC키를 눌렀을때 Divide By Zero 예외가 발생한다.

 

메인 카메라에 Logger 스크립트를 추가한다.

 

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 System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class Logger : MonoBehaviour
{
    private System.IO.StreamWriter sw;
    public string logFileName = "log.txt";
 
    // Start is called before the first frame update
    void Start()
    {
        sw = new System.IO.StreamWriter(Application.persistentDataPath + "/" + logFileName);
        Debug.Log(Application.persistentDataPath + "/" + logFileName);
    }
 
    private void OnEnable()
    {
        Application.logMessageReceived += HandleLog;
    }
 
    private void OnDisable()
    {
        Application.logMessageReceived -= HandleLog;
    }
 
    void HandleLog(string logString, string stackTrace, LogType type)
    {
        sw.WriteLine("Logged at: " + System.DateTime.Now.ToString() + "\n"
            + "Log type: " + type + "\n"
            + "Log desc: " + logString + "\n"
            + "Trace: " + stackTrace
            );
    }
 
    private void OnDestroy()
    {
        sw.Close();
    }
}
 

 

위 내용을 Logger 스크립트에 작성한다.

 

 

실행하고 ESC키를 누른다. Console 창에 로그와 예외가 표시된다.

 

지정된 디렉토리에 로그 파일이 생성된다.

 

발생한 로그와 예외가 기록되어 있다.

 

반응형
Posted by J-sean
: