Word 크기

CPU가 한 번에 읽어들이는 Word의 크기가 다르다. 이는 CPU가 한 번에 처리할 수 있는 데이터의 크기가 다르다는 말과 같다; 32비트 CPU는 한 번에 32비트를 읽는다. 반면, 64비트 CPU는 한 번에 64비트를 읽는다. 따라서 64비트 운영체제는 32비트 운영체제보다 연산 속도가 빠르다.

 

메모리 주소 범위

32비트 운영체제는 2^32개의 메모리 주소를 가지기 때문에, 최대 4GB의 메모리만 사용할 수 있다. 그러나 64비트 CPU는 2^64개의 메모리 주소를 가지기 때문에, 최대 16EB(=10억 GB)의 메모리를 사용할 수 있다. 이는 (2025.4.1 기준) 현시점의 최대 메모리 용량이 6TB인 것을 감안했을 때, 한참 여유로운 수치이다. 

아무렴 64비트 운영체제는 32비트 운영체제에 비해 더 많은 양의 메모리를 활용할 수 있으며, 이는 특히 고사양 프로그램의 성능을 크게 향상시켜줄 수 있다.

 

Instruction Set

32비트 운영체제는 x86 Instruction Set을 사용하는 반면, 64비트 운영체제는 x86-64 Instruction Set을 사용한다.

 

레지스터

64비트 운영체제는 32비트 운영체제보다 CPU에 더 많은 레지스터를 제공한다. 이는 다양한 이점을 제공한다.

 

32비트 운영체제에서 쓰이는 MIPS ISA를 살펴보면 레지스터 공간과 Word 단위 때문에 Instruction 여러 개를 쓰는 경우가 있다. 그러나 64비트 운영체제에선 이런 일이 보다 적게 일어난다. 따라서 32비트 운영체제보다 성능이 뛰어나다.

또한 벡터 연산과 부동소수점 연산을 위한 SSE, AVX 등의 Instruction도 제공해주기 때문에, 더욱 효율적이다.

 

구동 가능 여부

32비트 운영체제용 프로그램은 64비트 운영체제에서 실행할 수 있지만, 64비트 운영체제용 프로그램은 32비트 운영체제에서 실행할 수 없다. 다양한 이유가 있지만, 가장 핵심적인 이유는 32비트 운영체제가 64비트 전용 Instruction을 처리하지 못한다는 것이다.

코드

참고로 Player에 Constant Force 컴포넌트를 달아주고, y축 Force를 -25 정도로 설정해줘야 잘 작동한다. 발밑에 땅이 있는 경우에만 점프하는 메커니즘은;

2024.03.10 - [Unity] - [Unity] 플레이어와 발판이 같이 움직이게 만들기

 

[Unity] 플레이어와 발판이 같이 움직이게 만들기

서론초창기 때 아무것도 모르고 transform.localScale = new Vector2(-4, 4); 이런 식으로 플레이어가 바라보는 방향을 바꿨었기에, Player가 MovingPlatform 위에 있을 때, 이를 따라가게 만들려면 엄청난 공을 들

quickclid.tistory.com

윗글에서 GroundSensor 부분을 참고하면 된다.

using UnityEngine;

public class Player : MonoBehaviour
{
    private Rigidbody rb;

    private float speed;
    private Vector3 accDirection;
    private float velDamp;
    private float jumpForce;

    private void Awake()
    {
        rb = GetComponent<Rigidbody>();

        speed = 200;
        accDirection = Vector3.zero;
        velDamp = 0.75f;
        jumpForce = 750;
    }

    private void FixedUpdate()
    {
        rb.linearVelocity += accDirection * speed * Time.fixedDeltaTime;
        rb.linearVelocity = new Vector3(rb.linearVelocity.x * velDamp, rb.linearVelocity.y, rb.linearVelocity.z * velDamp);
    }

    private void Update()
    {
        Move();
        Jump();
        Sneak();
    }

    private void Move()
    {
        accDirection = Vector3.zero;
        if (Input.GetKey(KeyCode.D)) accDirection += Vector3.right;
        if (Input.GetKey(KeyCode.A)) accDirection += Vector3.left;
        if (Input.GetKey(KeyCode.W)) accDirection += Vector3.forward;
        if (Input.GetKey(KeyCode.S)) accDirection += Vector3.back;
        accDirection = accDirection.normalized;
    }

    private void Jump()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            rb.linearVelocity = new Vector3(rb.linearVelocity.x, 0, rb.linearVelocity.z);
            rb.AddForce(Vector3.up * jumpForce);
        }
    }

    private void Sneak()
    {
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            speed = 50;
            return;
        }
        if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            speed = 200;
            return;
        }
    }
}

 

개념

Object Pooling이란 최적화를 하는 방법 중 하나이다. 주로 생성(Instantiate)과 소멸(Destroy)을 반복하는 오브젝트에 적용한다. 이는 힙 영역에 빈번한 메모리의 할당과 해제가 일어나는 것을 막아줘서, Garbage Collector의 부담을 줄인다.

 

적용 방식

Pool Manager에 모든 Instantiate를 위임한다. (Spawner와 같은 개별 객체가 Instantiate를 하기 위해선 public static PoolManager instance;처럼 선언했다는 가정 하에, PoolManager.instance.MyInstantiate(~~~)처럼, 반드시 PoolManager를 거쳐가야 한다)

 

MyInstantiate()는:

1. 만일 화면 내에 Instantiate를 하고 싶은 객체가 SetActive(false) 상태로 존재한다면? 그걸 SetActive(true)한다. 

2. 만일 화면 내에 Instantiate를 하고 싶은 객체가 없다면? Instantiate()한다. 

 

여기에서 핵심은,

원래는 Destroy(gameObject) 후 Instantiate를 함으로써 Instantiate와 Destroy를 반복하며 객체를 사용했다면,

Object Pooling에서는, 원래는 Destroy()를 할 부분에 SetActive(false)를 넣어서 오브젝트를 재사용한다는 것이다. 

 

Pool Manager

using UnityEngine;
using System.Collections.Generic;

public class PoolManager : MonoBehaviour
{
    public static PoolManager instance;

    private List<GameObject>[] pools; // 한 칸에 리스트 한 개를 담는 2차원 배열
    [SerializeField] private GameObject[] all_prefabs;

    private void Awake()
    {
        instance = this;

        pools = new List<GameObject>[all_prefabs.Length];
        for (int i = 0; i < pools.Length; i++) // prefab의 종류만큼 list를 만든다.
        {
            pools[i] = new List<GameObject>();
        }
    }

    // about "index"
    // Bird(0), Net(1)...
    public GameObject Get(int index)
    {
        GameObject select = null;

        for (int i = 0; i < pools[index].Count; i++)
        {
            if (pools[index][i].activeSelf == false)
            {
                select = pools[index][i];
                select.SetActive(true);
                break;
            }
        }

        if (select == null)
        {
            select = Instantiate(all_prefabs[index], transform);
            pools[index].Add(select);
        }

        return select;
    }

    public GameObject Get(int index, Vector2 position) // overloading, 특정 위치에 스폰해야 하는 경우
    {
        GameObject select = null;

        foreach (GameObject item in pools[index])
        {
            if (item.activeSelf == false)
            {
                select = item;
                select.transform.position = position;
                select.SetActive(true);
                break;
            }
        }

        if (select == null)
        {
            select = Instantiate(all_prefabs[index]);
            select.transform.position = position;
            pools[index].Add(select);
        }

        return select;
    }
}

 

public static PoolManager instance;는 다른 코드에서도 PoolManager를 자유롭게 참조하기 위함이다. 예를 들어 Spawner 코드에서...

 

Spawner

using UnityEngine;
using System.Collections;

public class Spawner : MonoBehaviour
{
    private int level;

    private void Awake()
    {
        level = 1;
    }

    private void Start()
    {
        StartCoroutine(SpawnEnemy());
    }

    IEnumerator SpawnEnemy()
    {
        while (true)
        {
            PoolManager.instance.Get(Random.Range(0, level));
            yield return new WaitForSeconds(1f);
        }
    }
}

 

이런 식으로 쉽게 사용할 수 있다. 여기에선 Get이 MyInstantiate와 같은 역할을 하고 있다. (참고로 0은 Bird 객체를 나타낸다) 그럼 Object Pooling을 적용한 객체는 어떤 식으로 구성되어 있는지 보자.

 

Bird - Enemy 클래스 상속

using UnityEngine;

public class Bird : Enemy
{
    private Rigidbody2D rb;
    private float speed;
    private int lane;

    private void Awake()
    {
        // Enemy class
        max_health = 5;

        // my class
        rb = GetComponent<Rigidbody2D>();
        speed = -5f;
        // lane (매번 OnEnable마다 랜덤으로 할당)
    }

    protected override void Start()
    {
        base.Start();
        lane = Random.Range(-1, 2); // -1 ~ 1
        transform.position = new Vector2(10, lane * 3.5f);
        rb.linearVelocity = new Vector2(speed, 0);
    }

    private void OnEnable()
    {
        Start(); // 이해하기 쉽도록 이렇게 짬. 익숙해지면 Start를 지울 것 같다!
    }

    private void Update()
    {
        if (transform.position.x < -15f)
        {
            gameObject.SetActive(false);
        }
    }
}

 

이 코드를 보면 화면 밖으로 날아갔을 시 SetActive(false)를 하고, SetActive(true)가 된 순간을 OnEnable()을 통해 포착하여 초기화를 해주고 있는 것을 볼 수 있다. (참고로 Enemy class를 상속하고 있다)

 

Enemy (참고용)

using UnityEngine;

public class Enemy : MonoBehaviour
{
    protected float health;
    protected float max_health;
    [SerializeField] private GameObject[] effect_particle;

    protected virtual void Start()
    {
        InitStat();
    }

    private void InitStat()
    {
        health = max_health;
    }

    public void TakeDamage(float damage)
    {
        if (health - damage > 0)
        {
            GenerateEffect("Hit");
            health -= damage;
        }
        else
        {
            Die();
        }
    }

    private void Die()
    {
        GenerateEffect("Die");
        gameObject.SetActive(false);
    }

    private void GenerateEffect(string effect_type)
    {
        if (effect_type == "Hit")
        {
            for (int i = 0; i < 10; i++) { GameObject temp = Instantiate(effect_particle[0], transform.position, transform.rotation); }
        }
        else if (effect_type == "Die")
        {
            for (int i = 0; i < 20; i++) { GameObject temp = Instantiate(effect_particle[1], transform.position, transform.rotation); }
        }
    }
}

서론

요새 모바일 게임에 관심이 좀 생겼는데, 그러다가 모바일 게임들은 "입력"을 받는 방식이 상당히 한정되어 있다는 사실을 깨달았다. 그래서 터치와 드래그를 구별하는 코드를 한 번 짜봤다. 

 

코드

using UnityEngine;

public class Node : MonoBehaviour
{
    private SpriteRenderer sr;

    [SerializeField] private bool isSelected;
    [SerializeField] private Vector2 startPos;

    private void Awake()
    {
        sr = GetComponent<SpriteRenderer>();

        isSelected = false;
    }

    private void OnMouseDown()
    {
        startPos = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
    }

    private void OnMouseUp()
    {
        if ((Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition) == startPos)
        {
            isSelected = !isSelected;
            if (isSelected)
            {
                sr.color = new Color(1, 1, 1, 1f);
            }
            else
            {
                sr.color = new Color(0, 0, 0, 0.25f);
            }
        }
    }

    private void OnMouseDrag()
    {
        if (isSelected)
        {
            Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            transform.position = new Vector3(mousePosition.x, mousePosition.y, 0);
        }
        else
        {
            return;
        }
    }
}

간단한 방법 (동시에 플레이되는 소리의 종류가 한 가지밖에 없을 때)

더보기

AudioSource 컴포넌트를 이용하면 된다.

  1. 원하는 GameObject를 선택한 뒤, AudioSource 컴포넌트를 부착한다.
  2. 이 컴포넌트의 Audio Resource 부분에 소리 파일(Audio Clip)을 드래그해 넣는다. (소리 파일의 형식은 주로 .wav나 mp3이다)
  3. 이렇게 하면 플레이(게임 시작) 버튼을 누르자마자 지정한 소리가 나온다.

그러나 위의 간단한 방법에는 상당히 많은 문제가 있는데,

1. 원할 때 소리를 플레이할 수 없다는 것

2. 코드를 좀 짜서, 1번 문제를 해결했다고 하더라도, 두 종류 이상의 효과음을 한 번에 플레이할 수 없다. (한 효과음이 재생되고 있는 도중에, 다른 효과음이 나온다면, 재생되고 있던 효과음이 중단된다.)

 

그렇다면 보다 많은 종류의 효과음이 동시에 플레이될 수 있는 구조를 만들려면 어떻게 해야 할까?

 

 

구체적인 방법 (동시에 플레이되는 소리의 종류가 두 가지 이상일 때)

여러 개의 AudioSource 컴포넌트를 이용하면 된다.

그러나 많은 수의 AudioSource 컴포넌트를 각각의 GameObject에 붙이고, 관리하는 것은 매우 힘들기 때문에, AudioManager 객체를 하나 만들어줘야 한다.

 

다음은 AudioManager 클래스의 코드이다.

using Unity.VisualScripting;
using UnityEngine;

public class AudioManager : MonoBehaviour
{
    [SerializeField] private AudioClip bgmClip;
    private AudioSource bgmPlayer;

    [SerializeField] private AudioClip[] sfxClips;
    private AudioSource[] sfxPlayers;

    private void Awake()
    {
        bgmPlayer = gameObject.AddComponent<AudioSource>();
        bgmPlayer.clip = bgmClip;

        sfxPlayers = new AudioSource[sfxClips.Length];
        for (int i = 0; i < sfxClips.Length; i++)
        {
            sfxPlayers[i] = gameObject.AddComponent<AudioSource>();
            sfxPlayers[i].clip = sfxClips[i];
        }
    }

    private void Start()
    {
        PlayBGM();
    }

    private void PlayBGM()
    {
        bgmPlayer.loop = true;
        bgmPlayer.Play();
    }

    public void PlaySFX(int index)
    {
        sfxPlayers[index].Play();
    }
}

 

 

(참고로 "[SerializedField] private" <-- 이 구절은 private 멤버 변수를 public하게 Inspector 창에서 접근할 수 있도록 만들지만, 멤버 변수 자체는 private의 성질을 지니도록 하는 관용어구...와 같은 것이다. 이 코드를 볼 때는 간단하게 [SerializedField] private == public이라고 생각하면 된다. 아니면 그냥 신경쓰지 않아도 된다! 그냥 아무 의미가 없는 것으로 생각해도 코드를 이해하는 데는 아무런 지장이 없다!)

 

보통 Manager류 코드는 빈 객체(Empty GameObject)에다가 Manager용 스크립트만 부착해서 사용한다. AudioManager도 그런 식으로 사용할 것을 가정하고 만들었다.

 

bgm(배경음악) 관련 부분부터 보면 이해하기 편할 것이다.

 

bgm 관련 부분

더보기
  1. bgmClip 변수를 통해, AudioClip(소리 파일)을 받을 공간을 하나 형성해준다. (이 변수는 [SerializedField] private을 통해 형성되었기 때문에, Inspector 창에서 직접 소리 파일(wav, mp3)을 드래그해 넣을 수 있다.)
  2. bgmPlayer = gameObject.AddComponent<AudioSource>();를 통해 bgmPlayer 변수에 새로 생성한 AudioSource 컴포넌트를 할당(참조)해준다. 참고로 새로 생성한 AudioSource 컴포넌트는 AudioManager GameObject에 붙는다. (아까 말한 빈 객체에 붙는다.)

 

이렇게 하면 게임을 실행하자마자, "bgm용 AudioClip"만을 위한 AudioSource 컴포넌트가 AudioManager GameObject에 생성되게 된다. 

 

그 이후, Start() 메서드와 PlayBGM() 메서드를 통해 배경음악을 재생해주면 된다. (참고로 AudioSource 컴포넌트는, 이 컴포넌트에 할당되어 있는 AudioClip(Audio Resource)을 반복재생(loop)할지 결정할 수 있다. 그게 bgmPlayer.loop = true;로 표현된 것이다.)

 

sfx 관련 부분

(bgm 관련 부분과 거의 같다. bgm은 Audio Clip이 하나고, sfx는 여러 개라는 것이 유일한 차이점이다.)

더보기
  1. sfxClips 배열을 통해, AudioClip(소리 파일)들을 받을 공간을 하나 형성해준다. (이 배열은 [SerializedField] private을 통해 형성되었기 때문에, Inspector 창에서 직접 소리 파일(wav, mp3)을 드래그해 넣을 수 있다.)
  2. sfxPlayers = new AudioSource[sfxClips.Length];를 통해 sfxClip의 개수만큼 sfxPlayer(AudioSource 컴포넌트)를 위한 공간을 만들어준다. --> 이는 각각의 sfxClip을 위한 AudioSource 컴포넌트를 AudioManager에 부착하기 위함이다.
  3. for문을 통해, sfxPlayers[i]에 AudioSource 컴포넌트를 "각각" 할당한다. 그 뒤, sfxPlayers[i]의 Audio Resoure(Audio Clip)을 sfxClips[i]로 설정해준다. 

 

이렇게 하면 게임을 실행하자마자, "sfx용 AudioClip"만을 위한 AudioSource 컴포넌트가 AudioManager GameObject에 생성되게 된다. 

 

그 이후, Start() 메서드와 PlaySFX() 메서드를 통해 배경음악을 재생해주면 된다.

예를 들어, 만약에 Player가 점프할 때 소리를 플레이하고 싶다? 그러면..

  1. private AudioManager am;
  2. private void Awake() { am = FindFirstObjectByType<AudioManager>(); }
  3. public void Jump() { am.PlaySFX(5); }

위와 같은 방식으로 해주면 된다. (만약에 PlaySFX(숫자) 이런 형식이 불편하다면, enum을 사용할 수도 있다.)

 

AudioManager가 완성되면 이런 느낌일 것이다! (오른쪽에 보이는 Inspector 창처럼 되어있을 것이다!)

 

읽어주셔서 감사합니다!

Prefab이란?

자주 쓰이는 GameObject를 재사용하기 위해 Asset화 한 것이다. 


사용처

정말 많다. 총알/화살과 같은 Projectile, Hit/Death 효과를 책임지는 Particle, 심지어 Enemy나 Player도 Prefab화의 대상이다. Scene Template에도 사용된다.


Prefab Override

Instance(원본 Prefab으로부터 Scene에 Instance된 GameObject)를 수정한 경우:

  • 이 행위 자체는 원본 Prefab에 영향을 미칠 수 없다.
  • 원본 Prefab과의 변경점이 Override되어 저장된다. (Override된 Instance!)
  • Apply와 Revert 옵션이 활성화된다;
    (Apply : Instance로 원본 Prefab을 덮어씌움 / Revert : 원본 Prefab으로 Instance를 덮어씌움)
  • 이런 식으로 Apply나 Revert를 한 경우, "원본 Prefab"과 "Instance"의 차이는 고려되지 않는다. 그냥 그대로 덮어씌워버린다.

원본 Prefab을 수정한 경우:

  • Instance가 Override되지 않은 경우, 원본 Prefab으로 Instance를 덮어씌운다.
  • Instance가 Override된 경우, 수정된 부분을 확인한다;
    >> 만일 수정된 부분이 같다면, 원본 Prefab에서 수정한 부분이 Instance에 적용되지 않는다. (예시 : Instance Override를 scale.x를 1 -> 2로 바꿈으로써 했다. 원본 Prefab에선 scale.x를 1 -> 6으로 바꿨다. GIT CONFLICT!)
    >> 만일 수정된 부분이 다르다면, 원본 Prefab에서 수정한 부분이 Instance에 적용된다.
  • 참고로 원본 Prefab을 수정할 땐 Auto Save를 끄고 작업하는 것이 좋다. Instance와는 다르게 Apply나 Revert 옵션을 선택하는 과정이 존재하지 않으므로...

Scene Template과의 연계

Scene Template의 옵션에서, 지정한 Assets를 Clone 할 것인지 Reference 할 것인지 선택할 수 있다. 이때, Prefab의 경우 무조건 Reference로 하는 것이 좋다. 어짜피 Instance에는 Override 기능이 있어서 Tilemap과 같이 각 Scene(Stage)마다 배치가 달라지는 경우에도 Prefab을 충분히 사용할 수 있다. 


읽어주셔서 감사합니다!

궁금한 점이 있다면 댓글로 문의주세요!

Singleton Pattern이란?

하나의 Scene에 "반드시" 하나의 Object만 존재하도록 강제하는 디자인 패턴이다. 


사용처

주로 Manager 계열의 Object에 사용된다.

 

이유 : 대부분의 경우 이러한 Object는 모든 Scene에 사용된다. 그러나 매번 넣어주기도 귀찮고, 실수할 가능성도 있다. 또한 Scene Template을 사용하더라도, 무조건 사용되는 Object들이 매번 Hierarchy 창을 차지한다면, 각각의 Scene을 편집하는 데 있어 상당한 방해가 된다.


사용법

대체로 Main Menu와 같은, "무조건 맞닥뜨리게 되는(=index가 0인 Scene)"에 Game, Data, Audio Manager Object를 배치한다. 그 후, 그것들을 DontDestroyOnLoad 목록에 등록하여 재사용한다.


Skeleton Code - Game Manager

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameObject game_manager;

    private void Awake()
    {
    	SingletonCheck();
    }
    
    private void SingletonCheck()
    {
    	if (game_manager == null)
        {
            game_manager = gameObject;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    private void Start()
    {
    	// 처음 로드됐을 시에만 "단 한 번" 실행하고 싶은 것들을 여기에.
    }
}

문제점

DontDestroyOnLoad를 사용하기 때문에, Start()가 단 한 번밖에 실행되지 않는다. (그러니까, Main Menu Scene을 10번 Load하더라도 Game Manager의 Start()는 단 한 번만 호출된다!) 따라서 "Scene이 Load되는 타이밍"에 "매번" 실행하고 싶은 메서드가 있다면, Delegate를 사용해야 한다.


해결책

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameObject game_manager;

    private void Awake()
    {
    	SingletonCheck();
    }
    
    private void SingletonCheck()
    {
    	if (game_manager == null)
        {
            game_manager = gameObject;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    private void Start()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }
    
    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (scene.name == "Main Menu")
        {
            // Main Menu Scene이 Load됐을 때만 하고 싶은 것을 적으면 됨.
            return;
        }
        if (scene.name == "Shop")
        {
            // Shop Scene이 Load됐을 때만 하고 싶은 것을 적으면 됨. 등등...
            return;
        }
    }
}

읽어주셔서 감사합니다!

궁금한 점이 있다면 댓글로 문의주세요!

Preview

https://youtu.be/6ZVn8UT4Ioc

오른쪽의 Sprite Renderer가 일을 참 잘한다...!

필요한 Script

  • Laser

Laser

필요한 Component;

  • Sprite Renderer : Pivot(Left Center), Texture's Mesh Type(Full Rect)
  • Box Collider 2D : isTrigger(true)
  • Laser(Script) : 하단 참조.
using UnityEngine;

public class Laser : MonoBehaviour
{
    private SpriteRenderer sr;
    private BoxCollider2D bc;

    private void Awake()
    {
        GetReferences();
        //InitFields();
    }

    private void GetReferences()
    {
        sr = GetComponent<SpriteRenderer>();
        bc = GetComponent<BoxCollider2D>();
    }

    private void FixedUpdate()
    {
        FormLaser();
    }

    private void FormLaser()
    {
        RaycastHit2D ray = Physics2D.Raycast(transform.position, transform.right, 100f, LayerMask.GetMask("Ground"));
        sr.size = new Vector2(ray.distance, 0.75f);
        bc.size = new Vector2(ray.distance, 0.5f);
        bc.offset = new Vector2(ray.distance / 2, 0);
    }
}

실제로 사용해보기 위해서는...

Box Collider 2D Component를 지니고 있으며, Layer를 "Ground"로 설정한 GameObject가 있어야 한다.


읽어주셔서 감사합니다!

궁금한 점이 있다면 댓글로 문의주세요!

Preview

https://youtu.be/B8_Z3RoIKCw

Trail은 참 쓰기 편하면서도, 보기 좋다 :)

필요한 Script

  • Player
  • Ground Sensor
  • Moving Platform

Player

필요한 Component;

  • Sprite Renderer
  • Box Collider 2D : Friction & Bounciness가 모두 0인 Physical Material 2D가 필요하다. 플레이어가 벽에 달라붙지 않게 하기 위함이다.
  • Rigidbody 2D : Body Type(Dynamic), Gravity Scale(6), Collision Detection(Continuous), Constrains(Freeze Rotation Z만 체크)
  • Player(Script) : 하단 참조.
using UnityEngine;

public class Player : MonoBehaviour
{
    private Rigidbody2D rb;

    private float speed;
    private float vel_damp;
    private float jump_force;
    private Vector2 acc_direction;
    private bool is_grounded; public void SetIsGrounded(bool value) { is_grounded = value; }

    private void Awake()
    {
        GetReferences();
        InitFields();
    }

    private void GetReferences()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    private void InitFields()
    {
        speed = 125f;
        vel_damp = 0.75f;
        jump_force = 17.5f; 
        acc_direction = Vector2.zero;
        is_grounded = false;
    }

    private void FixedUpdate()
    {
        AdjustMovement();
    }

    private void AdjustMovement()
    {
        rb.linearVelocity += acc_direction * speed * Time.fixedDeltaTime;
        rb.linearVelocity = new Vector2(rb.linearVelocity.x * vel_damp, rb.linearVelocity.y);
    }

    private void Update()
    {
        Move();
        Jump();
    }

    private void Move()
    {
    	acc_direction = Vector2.zero;
        
        if (Input.GetKey(KeyCode.RightArrow)) acc_direction += Vector2.right;
        if (Input.GetKey(KeyCode.LeftArrow))  acc_direction += Vector2.left;
    }

    private void Jump()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow) && is_grounded)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocityX, jump_force);
        }
    }
}

 


Ground Sensor

(Player GameObject의 Child여야 한다)

 

필요한 Component;

  • Box Collider 2D : Is Trigger(체크)
  • GroundSensor(Script) : 하단 참조.
using UnityEngine;

public class GroundSensor : MonoBehaviour
{
    private Player player;

    private void Awake()
    {
        GetReferences();
        //InitFields();
    }

    private void GetReferences()
    {
        player = GetComponentInParent<Player>();
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Ground")
        {
            player.SetIsGrounded(true);
        }
    }

    // 매우 정확한 물리적-판정이 요구되는 게임에만 사용할 것.
    /*private void OnTriggerStay2D(Collider2D other)
    {
        if (other.tag == "Ground")
        {
            player_script.SetIsGrounded(true);
            return;
        }
    }*/

    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.tag == "Ground")
        {
            player.SetIsGrounded(false);
        }
    }
}

Moving Platform

(Tag : Ground)

 

필요한 Component;

  • Sprite Renderer
  • Box Collider 2D
  • MovingPlatform(Script) : 하단 참조.
using UnityEngine;

public class MovingPlatform : MonoBehaviour
{
    private Rigidbody2D rb;

    private Vector2 destination;

    // prefab화를 통해 사용하기 위함.
    [SerializeField] private float speed;
    [SerializeField] private Vector2 start_pos, end_pos;

    private void Awake()
    {
        GetReferences();
        InitFields();
    }

    private void GetReferences()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    private void InitFields()
    {
        destination = end_pos;
    }

    private void Start()
    {
        transform.position = start_pos;
    }

    private void Update()
    {
        Move();
    }

    private void Move()
    {
        transform.Translate((destination - (Vector2)transform.position).normalized * speed * Time.deltaTime);

        if (Vector2.Distance(transform.position, destination) <= 0.125f)
        {
            if (destination == start_pos) { destination = end_pos; }
            else                          { destination = start_pos; }
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.GetComponent<GroundSensor>())
        {
            other.transform.parent.transform.SetParent(transform);
        }
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.GetComponent<GroundSensor>())
        {
            other.transform.parent.transform.SetParent(null);
        }
    }
}

 


읽어주셔서 감사합니다!

궁금한 점이 있다면 댓글로 문의주세요!

+ Recent posts