우선 한 버튼을 누를때마다 동작을 변화시킬 것이므로

캔버스에 빈 버튼안에 on, off로 나누었다.

텍스트는 보이지 않게 이미지를 껐다.

 

자리를 잘 잡아주어 이렇게 분류한다.

기본을 켜져있을 때로 주었고

눌렀을 때 !을 사용해서 서로의 이미지를 교차로 보일 수 있게 하였다.

using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.UI;

public class Switch : MonoBehaviour
{
    [SerializeField] private Button btnSwitch;
    [SerializeField] private GameObject btnOn;
    [SerializeField] private GameObject btnOff;
    private bool isOn = true;

    private void Start()
    {
        btnOn.SetActive(isOn);
        btnOff.SetActive(!isOn);
        
    }

    private void Update()
    {
        this.btnSwitch.onClick.AddListener(() => {
            OnOff();
        });
    }

    void OnOff()
    {
        isOn = !isOn;
        btnOn.SetActive(isOn);
        btnOff.SetActive(!isOn);
    }

}

https://docs.unity3d.com/ScriptReference/GameObject.SetActive.html

 

Unity - Scripting API: GameObject.SetActive

A GameObject may be inactive because a parent is not active. In that case, calling SetActive will not activate it, but only set the local state of the GameObject, which you can check using GameObject.activeSelf. Unity can then use this state when all paren

docs.unity3d.com

 

 

 

'낙서장 > UIUX' 카테고리의 다른 글

Skill Button  (0) 2024.03.18
Slider  (0) 2024.03.18
Button and Inputfield  (0) 2024.03.18

우선 가이드 라인을 캔버스에 적용한후

플레이하는 기계에 따라 유동적으로 변화할 수 있게

스케일모드와 레퍼런스 솔루션을 적용

 

 

알베도를 설정하여 가이드라인을 흐리게 설정

 

데모씬으로 들어가서 오브젝트들을 하나씩 활성화 시켜본 후 필요한 것을 찾고

그 안에있는 컴포넌트들을 확인해야 한다.

그리고 이런 느낌으로 메모장에 적어둔다.

버튼을 만들것인데 TMP로 만들것이다.

https://docs.unity3d.com/Packages/com.unity.textmeshpro@1.0/api/TMPro.TextMeshProUGUI.html

 

Class TextMeshProUGUI

Class TextMeshProUGUI Inherited Members Namespace: TMPro Assembly : solution.dll Syntax public class TextMeshProUGUI : TMP_Text, ILayoutElement Fields m_subTextObjects Declaration protected TMP_SubMeshUI[] m_subTextObjects Field Value Properties autoSizeTe

docs.unity3d.com

 

아까 메모장에 적어뒀던걸로 검색해서 이런식으로 assign

 

가져오면 이런식으로 sliced가 되있을 것인데 사이즈를 바꿔줄 것이다.

이러면 기본적인 크기가 조절되는데 다시 sliced로 바꿔야 이미지 크기를 조절할때

이미지가 찌그러지는 상황을 방지할 수 있다.

 

UI를 만들때 기초이자 필수인 과정인데

가이드란과 비교해서 그 위해 이미지를 덮고 이미지와 텍스트를껐다켰다하며 실제와 같은지 비교하고 조정한다.

조정할 것으로는 이미지크기, 텍스트, 색상 같은 것들이 있다.

UI들을 관리해줄 빈오브젝트 생성 후 스크립트 어싸인

 

이런식으로 직접 할당해줄수 있지만 나중에 컴포넌트들이 많으면 관리하기가 힘들고

혹시나 나중에 디자이너들과 협업을 할때 쉽게 건들여질 수 있는 부분으로 권장하지 않는다.

따라서 초기화

이런식으로 인스펙터에서 할당하지 않아도 클릭하면 인지될 수 있도록 코드를 구현할 것이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BlueButton : MonoBehaviour
{
    [SerializeField] public Button blueButton;

    private void Start()
    {
        this.blueButton.onClick.AddListener(() => {
            Debug.Log("Blue Button Clicked!");
        });
    }
}

 

이제 Inputfield를 만들 것인데 버튼과 마찬가지로 필요한 텍스쳐들을 찾아낸다.

칼라도 잊지말고 찾자

Allignment는 사실 크게 신경쓰지 않아도 된다.

위에 있는 것들은 버튼을 만들때 필요했던 것들 아래는 이번에 만들 인풋필드에 필요한 것들이다.

.

기존에 인풋필드에서 사용하는 폰트는 한글이 없으므로 설치후

window - textmeshpro - font asset creator

글자는 각 숫자로 정리 되어있는데 이것은 한글 범위를 찾아서 넣어줘고 제너레이트 해야 한다.

 

https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/FontAssetsCreator.html

 

Font Asset Creator | TextMeshPro | 4.0.0-pre.2

Font Asset Creator The Font Asset Creator converts Unity font assets into TextMesh Pro font assets. You can use it to create both Signed Distance Field (SDF) fonts and bitmap fonts. When you create a new font Asset, TextMesh Pro generates the Asset itself,

docs.unity3d.com

제너레이트 후 세이브

 

아까는 onClick()를 썼듯이 이번엔 이것을 사용할 것이다.

어싸인

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class InputField : MonoBehaviour
{
    [SerializeField] public TMP_InputField inputField;
    void Start()
    {
        this.inputField.onValueChanged.AddListener((word) =>
        {
            Debug.LogFormat("{0}", word);
        });
    }
}

이제 입력을 하고 버튼을 누르면 출력되게 할 것이다.

using UnityEngine;
using UnityEngine.UI;

public class BlueButton : MonoBehaviour
{
    public Button blueButton;
    private ShowText showText;

    void Start()
    {
        this.showText = GetComponent<ShowText>();
        blueButton.onClick.AddListener(() =>
        {
            showText.ShowMessage();
        });
    }
}

 

using TMPro;
using UnityEngine;

public class ShowText : MonoBehaviour
{
    public TMP_InputField inputField;

    public void ShowMessage()
    {
        Debug.Log(inputField.text);
    }

    //public void SubscribeToInputFieldChange()
    //{
    //    inputField.onValueChanged.AddListener((word) =>
    //    {
    //    });
    //}
}

'낙서장 > UIUX' 카테고리의 다른 글

Skill Button  (0) 2024.03.18
Slider  (0) 2024.03.18
Switch Button  (0) 2024.03.18

https://docs.unity3d.com/kr/530/Manual/OcclusionCulling.html

 

오클루전 컬링 - Unity 매뉴얼

오클루전 컬링(Occlusion Culling)은 다른 오브젝트에 가려(오클루전된) 카메라에 보이지 않는 오브젝트의 렌더링을 비활성화하는 기능입니다. 3D 컴퓨터 그래픽스에서는 대부분의 경우 카메라에서

docs.unity3d.com

 

큰 오브젝트에 가려질 오브젝트들도 만든다.

제일 큰 큐브와 플레인 설정

나머지 오브젝트들도 설정

 

오쿨루더와 오쿨루디 설정을 잘 해놓아야 한다.

 

윈도우를 누르면 오쿨루전 설정이 보인다.

인스펙터를 확인해보면 새로운 창이 추가되었다.

전체를 선택한후 베이크를 한다.

그러면 이렇게 뒤에 가려진 오브젝트들은 사라진다.

어떻게 빛이 나가는지 보고 싶을경우 여기를 체크하면 빛의 방향과 크기를 볼 수있다.

 

주의사항

 

베이크창을 보면 이렇게 크기를 조절할 수 있는데

제일 앞에 있는 오브젝트가 이 스케일보다 커야 한다.

'산대특 > 게임 클라이언트 프로그래밍' 카테고리의 다른 글

Sprite Shooter - Player and Monster Animation  (0) 2024.03.14
Sprite Shooter - Destroy Barrel and Layer  (0) 2024.03.13
Zombie - IK  (0) 2024.03.07
Zombie - Line Render  (0) 2024.03.07
Tank - Rigidbody.MovePosition  (0) 2024.03.06

게임오브젝트 3개를 생성해서 각각 배경을 넣어주는데

order in layer로 계층구조를 잡아줄것이다

a부터 c까지 각각 -3, -4, -5

게임 오브젝트의 좌표는 모두 0 0 0

복사를 해서 각각 3개로 만들어주고

0의 Y좌표는 0

1의 Y좌표는 -10

2의 Y좌표는 10

카메라는 그대로

배경은 뒤로 옮겨 눈속임을 할것이다.

 

스크립트 생성후 배경이 움직이는 로직 작성

using System.Collections;
using System.Collections.Generic;
using TreeEditor;
using UnityEditor;
using UnityEngine;

public class Background : MonoBehaviour
{
    public float speed;
    void Start()
    {
        
    }

    void Update()
    {
        Vector3 curPos = transform.position;
        Vector3 nextPos = Vector3.down * speed * Time.deltaTime;
        transform.position = curPos + nextPos;
    }
}

그런데 이렇게 하면 맨뒤에 아무것도 없는 배경이 보인다.

따라서 배경을 무한으로 만들어 주어야한다.

 

카메라의 시점

카메라를 잡을때 메인카메라의 시점 * 2를해서 화면이 짤리기전에 한번 더 보여주게  하였고

y좌표가 -1일때 시작점을 바꾸어 연속적으로 보이게 하였다.

using System.Collections;
using System.Collections.Generic;
using TreeEditor;
using UnityEditor;
using UnityEngine;

public class Background : MonoBehaviour
{
    public float speed;
    public int startIndex;
    public int endIndex;
    public Transform[] sprites;

    float viewHeight;
    
    void Start()
    {
        viewHeight = Camera.main.orthographicSize * 2; // 카메라
    }

    void Update()
    {
        Vector3 curPos = transform.position;
        Vector3 nextPos = Vector3.down * speed * Time.deltaTime;
        transform.position = curPos + nextPos;

        if (sprites[endIndex].position.y < viewHeight * (-1))
        {
            //#.Sprite ReUse
            Vector3 backSpritePos = sprites[startIndex].localPosition;
            Vector3 frontSpritePos = sprites[endIndex].localPosition;

            sprites[endIndex].transform.localPosition = backSpritePos + Vector3.up * viewHeight;

            //이동이 완료되면 EndIndex, StartIndex 갱신
            //#.Cursor Index Change
            int startIndexSave = startIndex;
            startIndex = endIndex;
            endIndex = (startIndexSave -1 == -1) ? sprites.Length-1 : startIndexSave-1;
        }

    }
}

A, B, C를 나눈 이유는 원근감을 주기 위해서이다.

 

Parallax: 거리에 따른 상대적 속도를 활용한 기술

C는 속도1 B는2 A는4

 

using System.Collections;
using System.Collections.Generic;
using TreeEditor;
using UnityEditor;
using UnityEngine;

public class Background : MonoBehaviour
{
    public float speed;
    public int startIndex;
    public int endIndex;
    public Transform[] sprites;

    float viewHeight;
    
    void Start()
    {
        viewHeight = Camera.main.orthographicSize * 2; // 카메라
    }

    void Update()
    {
        

        if (sprites[endIndex].position.y < viewHeight * (-1))
        {
            Move();
            Scrolling();
        }

        void Move()
        {
            Vector3 curPos = transform.position;
            Vector3 nextPos = Vector3.down * speed * Time.deltaTime;
            transform.position = curPos + nextPos;
        }

        void Scrolling()
        {
            //#.Sprite ReUse
            Vector3 backSpritePos = sprites[startIndex].localPosition;
            Vector3 frontSpritePos = sprites[endIndex].localPosition;

            sprites[endIndex].transform.localPosition = backSpritePos + Vector3.up * viewHeight;

            //이동이 완료되면 EndIndex, StartIndex 갱신
            //#.Cursor Index Change
            int startIndexSave = startIndex;
            startIndex = endIndex;
            endIndex = (startIndexSave - 1 == -1) ? sprites.Length - 1 : startIndexSave - 1;
        }
    }
}

'산대특' 카테고리의 다른 글

2D Vertical Shooting Game 모작 최종  (0) 2024.04.08
팬텀로즈스칼렛 모작 최종  (0) 2024.04.08
2D Airplane - 5  (1) 2024.03.14
2D Airplane - 4  (0) 2024.03.12
2D Airplane - 3  (0) 2024.03.12

 

 

 

 

아이템을 만들것이다.

스프라이트를 나눠서 가져온다.

총알처럼 콜라이더와 리지드바디 추가 + 이즈트리거 체

이때 중력은 0

아이템 스크립트 생성후 3개에 부착

item 스크립트에 퍼블릭으로 string으로 선언하고 각 아이템의 이름을 직접 적어줄 것이다.

물체가 잘 내려오는지 확인

이제 애니메이션을 넣어줄것이다.

다른 오브젝트들도 똑같이 애니메이션을 만든다.

테스트

아이템 태그 부착

 

플레이어가 아이템을 먹어야하기에 onTriggerEnter2D를 사용할 것이다.

그런데 파워는 이미 선언되어 있고

파워 아이템을 먹으면 공격력이 증가되어야 하기에 변수를 따로 하나 더 선언

파워가 맥스 파워보다 증가되면 안되므로 조건을 걸어주고

맥스파워가 되면 보통 슈팅게임처럼 점수만 올려줄 것이다.

이제 Boom을 만들 것이다.

 

붐스킬 이미지를 가져오면 다른 오브젝트를 덮어버리는데

이는 order in layer로 층을 바꾸면 된다

현재 모두 0 이고 배경은 -1 이므로

배경은 -2로 붐스킬은 -1로 변경

 

그 후 붐스킬도 똑같이 애니메이션을 만들어 준다.

 

나중에 아이템 먹을 시 활성화 할것이므로 현재는 비활성화

 

그럼 플레이에게 따로 값을 주면된다.

 

플레이에게 퍼블릭으로 인스턴스를 만들어주고 어싸인

Boom을 먹으면 스킬을 활성화 하고 평상시에는 4초후 이펙트가 꺼지게 설정

적들은 배열로 관리하여 배열에서 찾아서 제거해준다.

퍼블릭으로 변경

플레이어의 파워와 맥스파워 설정

 보통 슈팅게임에서 아이템을 먹으면 저장을 하고

원하는 시기에 폭탄을 쏠 수 있도록 조정해야 한다.

그러므로 폭탄 개수를 저장할 변수 선언

폭탄도 파워와 같이 한계를 정할 것이므로 maxBoom까지 두개를 만들 것이다.

이제 Boom메소드를 만들것인데

아까 구현해던

이것을 다 긁어서 붐 메소드 안에 넣어줄 것이다.

붐 메서드에 기능은 이제 따로 빼두었는데

붐도 파워처럼 먹었을때에 대한 설정을 다시 지정해준다.

 

초기화

붐 아이콘도 라이프 아이콘을 복사해서 앵커의 위치를 바꾸어 준다.

게임 매니저에서 라이프 아이콘을 관리했던 것 처럼 복사해서

붐으로 만든 메소드에 집어 넣는다.

플레이어에서 게임매니저를 가져오고 업데이트하기 위해 메소드를 넣어준다.

알베도의 값을 0으로 줄인다.

만들어진 아이템 프리팹화

에너미가 사라진 곳에 아이템이 생성될 것이다.

어싸인

플레이 해보면 하나에서 아이템 2개가 나오는 오류?가 생긴다.

따라서 예외처리해야하는데

코드를 잘 살펴보면 디스트로이 되기 전에 온히트가 2번이라는 것을 알 수 있다.

이 코드만 추가해주면 예외처리가 된다.

 

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Net.Http.Headers;
using UnityEngine;

public class Player : MonoBehaviour
{
    public bool isTouchTop;
    public bool isTouchBottom;
    public bool isTouchRight;
    public bool isTouchLeft;

    public int life;
    public int score;
    public int speed;
    public int power;
    public int boom;
    public int maxBoom;
    public float maxPower;
    public float maxShotDelay; //실제 딜레이
    public float curShorDelay; //한발 쏜 후 딜레이
    
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject boomSkill;

    public GameManager manager;
    public bool isHit;
    public bool isBoomTime;

    private Animator anim;

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

    void Move()
    {
        float h = Input.GetAxisRaw("Horizontal");
        if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
            h = 0;
        float v = Input.GetAxisRaw("Vertical");
        if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
            v = 0;
        Vector3 curPos = this.transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
        transform.position = curPos + nextPos;

        if (Input.GetButtonDown("Horizontal") ||
            Input.GetButtonDown("Vertical"))
        {
            anim.SetInteger("Input", (int)h);
        }
    }

    private void Update()
    {
        Move();
        Fire();
        Boom();
        Reload();
    }

    void Fire()
    {
        if (!Input.GetButton("Fire1"))
            return;
        if (curShorDelay < maxShotDelay)
            return;

        switch (power)
        {

            case 1://Power One
                GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
                rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
                Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
                rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
        }
                curShorDelay = 0;
    }

    void Reload()
    {
        curShorDelay += Time.deltaTime;

    }
    
    void Boom()
    {
        if (!Input.GetKeyDown(KeyCode.Space))
            return;
        if (boom == 0)
            return;
        boom--;
        isBoomTime = true;
        manager.UpdateBoomIcon(boom);

        if (isBoomTime)
        {
            boomSkill.SetActive(true);
            Invoke("OffBoomSkill", 4f);
            GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
            for (int index = 0; index < enemies.Length; index++)
            {
                Enemy enemyLogic = enemies[index].GetComponent<Enemy>();
                enemyLogic.OnHit(1000);
            }
            GameObject[] bullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
            for (int index = 0; index < bullets.Length; index++)
            {
                Destroy(bullets[index]);
            }
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Border")
        {
            switch(collision.gameObject.name)
            {
                case "Top":
                    isTouchTop = true;
                    break;
                case "Bottom":
                    isTouchBottom = true;
                    break;
                case "Right":
                    isTouchRight = true;
                    break;
                case "Left":
                    isTouchLeft = true;
                    break;
            }
        }
        else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
        {
            if (isHit)
            {
                return;
            }
            isHit = true;
            life--;
            manager.UpdateLifeIcon(life);

            if(life == 0)
            {
                manager.GameOver();
            }
            else
            {
                manager.RespawnPlayer();
            }
            gameObject.SetActive(false);
        }
        else if(collision.gameObject.tag == "Item")
        {
            Item item = collision.gameObject.GetComponent<Item>();
            switch(item.type)
            {
                case "Coin":
                    score += 1000;
                    break;
                case "Power":
                    if(power == maxPower)
                    {
                        score += 500;
                    }
                    else
                    {
                        power++;
                    }
                    break;
                case "Boom":
                    if (boom == maxBoom)
                    {
                        score += 500;
                        
                    }
                    else
                        boom++;
                        manager.UpdateBoomIcon(boom);
                    break;
            }
            Destroy(collision.gameObject);
        }
    }

    void OffBoomSkill()
    {
        boomSkill.SetActive(false);
        isBoomTime = false;
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        switch (collision.gameObject.name)
        {
            case "Top":
                isTouchTop = false;
                break;
            case "Bottom":
                isTouchBottom = false;
                break;
            case "Right":
                isTouchRight = false;
                break;
            case "Left":
                isTouchLeft = false;
                break;
        }
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public int dmg;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
    }
}

 

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public Sprite[] sprites;

    public float maxShotDelay; //실제 딜레이
    public float curShorDelay; //한발 쏜 후 딜레이

    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject itemCoin;
    public GameObject itemPower;
    public GameObject itemBoom;
    public GameObject player;

    SpriteRenderer spriteRenderer;
    Rigidbody2D rigid;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        rigid = GetComponent<Rigidbody2D>();
        rigid.velocity = Vector2.down * speed;
    }
    private void Update()
    {
        Fire();
        Reload();
    }
    void Fire()
    {
        if (curShorDelay < maxShotDelay)
            return;

        if (enemyName == "S")
        {
            GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
        }
        else if (enemyName == "L")
        {
            GameObject bulletR = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
            GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
        }

        curShorDelay = 0;  // 발사 후 딜레이 초기화는 발사 이후에 처리되도록 이동
    }

    void Reload()
    {
        curShorDelay += Time.deltaTime;

    }
    public void OnHit(int dmg)
    {
        if (health <= 0)
            return;
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //피격시 1번
        Invoke("ReturnSprite", 0.1f);
        if (health <= 0)
        {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;

            //#.Random.Ratio Item Drop
            int ran = Random.Range(0, 10);
            if (ran < 3)
            {
                Debug.Log("No Item");
            }
            else if (ran < 6) { //Coin 30%
                Instantiate(itemCoin, transform.position, itemCoin.transform.rotation);
            }else if (ran < 8) { //Power 20%
                Instantiate(itemPower, transform.position, itemPower.transform.rotation);
            }else if(ran < 10) { //Boom 20%
                Instantiate(itemBoom, transform.position, itemBoom.transform.rotation);
            }



            Destroy(gameObject);
        }
    }

    void ReturnSprite()
    {
        spriteRenderer.sprite = sprites[0];
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "BorderBullet")
        {
            Destroy(gameObject );
        }
        else if(collision.gameObject.tag == "PlayerBullet")
        {
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            OnHit(bullet.dmg);
            Destroy(collision.gameObject);
        }
    }

}

 

using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public GameObject[] enemyObjs;
    public Transform[] spawnPoints;

    public float maxSpawnDelay;
    public float curSpawnDelay;

    public GameObject player;
    public Text scoreText;
    public Image[] lifeImage;
    public Image[] boomImage;
    public GameObject gameOverSet;
    private void Update()
    {
        curSpawnDelay += Time.deltaTime;

        if (curSpawnDelay > maxSpawnDelay)
        {
            SpawnEnemy();
            maxSpawnDelay = Random.Range(0.5f, 3f);
            curSpawnDelay = 0;
        }

        //UI Score

        Player playerLogic = player.GetComponent<Player>();
        scoreText.text = string.Format("{0:n0}", playerLogic.score);



    }
    void SpawnEnemy()
    {
        int ranEnemy = Random.Range(0, 3); //소환될 적
        int ranPoint = Random.Range(0, 9); //소환될 위치
        GameObject enemy = Instantiate(enemyObjs[ranEnemy],
                                        spawnPoints[ranPoint].position,
                                        spawnPoints[ranPoint].rotation);
        Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
        //스피드도 가지고 와야한다.
        Enemy enemyLogic = enemy.GetComponent<Enemy>();
        //적비행기 생성 직후 플레이에 변수를 넘겨준다.
        enemyLogic.player = player;
        if (ranPoint == 5 || ranPoint == 6)
        {
            enemy.transform.Rotate(Vector3.back * 90); //z축으로 90도
            rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
        }else if(ranPoint == 7 || ranPoint == 8)
        {
            enemy.transform.Rotate(Vector3.forward * 90); //z축으로 90도
            rigid.velocity = new Vector2(enemyLogic.speed, -1);
        }
        else
        {
            rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
        }
    }

    public void UpdateLifeIcon(int life)
    {
        //UI Init Disable
        for (int index = 0; index < 3; index++)
        {
            lifeImage[index].color = new Color(1, 1, 1, 0);
        }
        //UI Life Active
        for (int index = 0; index < life; index++)
        {
            lifeImage[index].color = new Color(1, 1, 1, 1);
        }
    }


    public void UpdateBoomIcon(int boom)
    {
        //UI Init Disable
        for (int index = 0; index < 3; index++)
        {
            boomImage[index].color = new Color(1, 1, 1, 0);
        }
        //UI Life Active
        for (int index = 0; index < boom; index++)
        {
            boomImage[index].color = new Color(1, 1, 1, 1);
        }
    }

    public void RespawnPlayer()
    {
        Invoke("RespawnPlayerExe", 2f);
        
    }
    private void RespawnPlayerExe()
    {
        player.transform.position = Vector3.down * 3.5f;
        player.SetActive(true);

        Player playerLogic = player.GetComponent<Player>();
        playerLogic.isHit = false;
    }
    public void GameOver()
    {
        gameOverSet.SetActive(true);
    }

    public void GameRetry()
    {
        SceneManager.LoadScene(0);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Item : MonoBehaviour
{
    public string type;
    private Rigidbody2D rigid;


    void Start()
    {
        rigid = GetComponent<Rigidbody2D>();
        rigid.velocity = Vector3.down * 3f; //아이템이 떨어지는 속도
    }

    void Update()
    {
        
    }
}

 

 

녹화_2024_03_13_22_03_51_619.mp4
2.26MB

 

이제 목숨과 점수를 만들 것이다.

UI는 앵커를 잘 잡아야 한다.

이미지는 쉬프트 알트 왼쪽상단

텍스트는 쉬프트 알트 중앙 상단

캔버스 크기 고정

 

캔버스 안에 있는 것들 크기 및 위치 재조정

 

버튼 같이 늘어나는? 것들은 경계선을 설정 => apply

빈 오브젝트에 넣는다.

적을 처치하면 점수를 받는다. ( enemy가 파괴될때 player의 스코어를 더해준다.)

UI이미지들은 게임매니저에서 관리

게임매니저의 업데이트문에서 점수 처리

세자리마다 쉼표로 나눠주는 숫자 양식

체력을 잃는 부분은 플레이어 스크립트에 있으므로

체력 이미지를 배열로 받아서

색의 알파를 조절하여 보이지 않게 할 것이다.

목숨이 없으면 게임오버 창이 나오게 할것이고 아닐시 리스폰

게임매니저에서 활성화 여부 조절하는 메서드 작성

이제 퍼블릭으로 생성된 컴포넌트에 부착

프리팹 모드로 들어가서 각각 점수 할당 500, 200, 50

 

실행을 해서 죽으면 게임오버가 나오고 재시작 버튼로직을 이제 만들것이다.

화면 전환을 하는 방식으로 사용할 것이다.

https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html

 

Unity - Scripting API: SceneManagement.SceneManager.LoadScene

Note: In most cases, to avoid pauses or performance hiccups while loading, you should use the asynchronous version of this command which is: LoadSceneAsync. When using SceneManager.LoadScene, the scene loads in the next frame, that is it does not load imme

docs.unity3d.com

 

File - Bullid Settings => 기본값은 0

그런데 플레이를 하다가 한번에 두발을 맞으면 체력이 2가 줄어드는 곳을 볼 수 있는데 이것을 수정할 것이다.

bool 변수를 다시 초기화 하는 부분도 꼭 구현해야한다.

using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;

public class Player : MonoBehaviour
{
    public bool isTouchTop;
    public bool isTouchBottom;
    public bool isTouchRight;
    public bool isTouchLeft;

    public int life;
    public int score;
    public float speed;
    public float power;
    public float maxShotDelay; //실제 딜레이
    public float curShorDelay; //한발 쏜 후 딜레이

    public GameObject bulletObjA;
    public GameObject bulletObjB;

    public GameManager manager;
    public bool isHit;
    private Animator anim;

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

    void Move()
    {
        float h = Input.GetAxisRaw("Horizontal");
        if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
            h = 0;
        float v = Input.GetAxisRaw("Vertical");
        if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
            v = 0;
        Vector3 curPos = this.transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
        transform.position = curPos + nextPos;

        if (Input.GetButtonDown("Horizontal") ||
            Input.GetButtonDown("Vertical"))
        {
            anim.SetInteger("Input", (int)h);
        }
    }

    private void Update()
    {
        Move();
        Fire();
        Reload();
    }

    void Fire()
    {
        if (!Input.GetButton("Fire1"))
            return;
        if (curShorDelay < maxShotDelay)
            return;

        switch (power)
        {

            case 1://Power One
                GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
                rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
                Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
                rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
        }
                curShorDelay = 0;
    }

    void Reload()
    {
        curShorDelay += Time.deltaTime;

    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Border")
        {
            switch(collision.gameObject.name)
            {
                case "Top":
                    isTouchTop = true;
                    break;
                case "Bottom":
                    isTouchBottom = true;
                    break;
                case "Right":
                    isTouchRight = true;
                    break;
                case "Left":
                    isTouchLeft = true;
                    break;
            }
        }
        else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
        {
            if (isHit)
            {
                return;
            }
            isHit = true;
            life--;
            manager.UpdateLifeIcon(life);

            if(life == 0)
            {
                manager.GameOver();
            }
            else
            {
                manager.RespawnPlayer();
            }
            gameObject.SetActive(false);

        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        switch (collision.gameObject.name)
        {
            case "Top":
                isTouchTop = false;
                break;
            case "Bottom":
                isTouchBottom = false;
                break;
            case "Right":
                isTouchRight = false;
                break;
            case "Left":
                isTouchLeft = false;
                break;
        }
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public int dmg;
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
    }
}

 

using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public string enemyName;
    public int enemyScore;
    public float speed;
    public int health;
    public Sprite[] sprites;
    public float maxShotDelay; //실제 딜레이
    public float curShorDelay; //한발 쏜 후 딜레이
    public GameObject bulletObjA;
    public GameObject bulletObjB;
    public GameObject player;
    SpriteRenderer spriteRenderer;
    Rigidbody2D rigid;

    private void Awake()
    {
        spriteRenderer = GetComponent<SpriteRenderer>();
        rigid = GetComponent<Rigidbody2D>();
        rigid.velocity = Vector2.down * speed;
    }
    private void Update()
    {
        Fire();
        Reload();
    }
    void Fire()
    {
        if (curShorDelay < maxShotDelay)
            return;

        if (enemyName == "S")
        {
            GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
            Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
            Vector3 dirVec = player.transform.position - transform.position;
            rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
        }
        else if (enemyName == "L")
        {
            GameObject bulletR = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
            GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
            Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
            Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
            Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
            Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
            rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
            rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);  // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
        }

        curShorDelay = 0;  // 발사 후 딜레이 초기화는 발사 이후에 처리되도록 이동
    }

    void Reload()
    {
        curShorDelay += Time.deltaTime;

    }
    private void OnHit(int dmg)
    {
        health -= dmg;
        spriteRenderer.sprite = sprites[1]; //피격시 1번
        Invoke("ReturnSprite", 0.1f);
        if(health <= 0)
        {
            Player playerLogic = player.GetComponent<Player>();
            playerLogic.score += enemyScore;
            Destroy(gameObject);
        }
    }

    void ReturnSprite()
    {
        spriteRenderer.sprite = sprites[0];
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "BorderBullet")
        {
            Destroy(gameObject );
        }
        else if(collision.gameObject.tag == "PlayerBullet")
        {
            Bullet bullet = collision.gameObject.GetComponent<Bullet>();
            OnHit(bullet.dmg);

            Destroy(collision.gameObject);
        }
    }

}

 

using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public GameObject[] enemyObjs;
    public Transform[] spawnPoints;

    public float maxSpawnDelay;
    public float curSpawnDelay;

    public GameObject player;
    public Text scoreText;
    public Image[] lifeImage;
    public GameObject gameOverSet;
    private void Update()
    {
        curSpawnDelay += Time.deltaTime;

        if (curSpawnDelay > maxSpawnDelay)
        {
            SpawnEnemy();
            maxSpawnDelay = Random.Range(0.5f, 3f);
            curSpawnDelay = 0;
        }

        //UI Score

        Player playerLogic = player.GetComponent<Player>();
        scoreText.text = string.Format("{0:n0}", playerLogic.score);



    }
    void SpawnEnemy()
    {
        int ranEnemy = Random.Range(0, 3); //소환될 적
        int ranPoint = Random.Range(0, 9); //소환될 위치
        GameObject enemy = Instantiate(enemyObjs[ranEnemy],
                                        spawnPoints[ranPoint].position,
                                        spawnPoints[ranPoint].rotation);
        Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
        //스피드도 가지고 와야한다.
        Enemy enemyLogic = enemy.GetComponent<Enemy>();
        //적비행기 생성 직후 플레이에 변수를 넘겨준다.
        enemyLogic.player = player;
        if (ranPoint == 5 || ranPoint == 6)
        {
            enemy.transform.Rotate(Vector3.back * 90); //z축으로 90도
            rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
        }else if(ranPoint == 7 || ranPoint == 8)
        {
            enemy.transform.Rotate(Vector3.forward * 90); //z축으로 90도
            rigid.velocity = new Vector2(enemyLogic.speed, -1);
        }
        else
        {
            rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
        }
    }

    public void UpdateLifeIcon(int life)
    {
        //UI Init Disable
        for (int index = 0; index < 3; index++)
        {
            lifeImage[index].color = new Color(1, 1, 1, 0);
        }
        //UI Life Active
        for (int index = 0; index < life; index++)
        {
            lifeImage[index].color = new Color(1, 1, 1, 1);
        }
    }


    public void RespawnPlayer()
    {
        Invoke("RespawnPlayerExe", 2f);
        
    }
    private void RespawnPlayerExe()
    {
        player.transform.position = Vector3.down * 3.5f;
        player.SetActive(true);

        Player playerLogic = player.GetComponent<Player>();
        playerLogic.isHit = false;
    }
    public void GameOver()
    {
        gameOverSet.SetActive(true);
    }

    public void GameRetry()
    {
        SceneManager.LoadScene(0);
    }
}

'산대특' 카테고리의 다른 글

팬텀로즈스칼렛 모작 최종  (0) 2024.04.08
2D Airplane - 원근감있는 무한 배경 만들  (0) 2024.03.17
2D Airplane - 4  (0) 2024.03.12
2D Airplane - 3  (0) 2024.03.12
2D Airplane - 2  (0) 2024.03.10

총알도 똑같이 잘라준다.

 

총알 충돌 시 이벤트가 생기므

총알에 박스콜라이더와 리지드바디 추가

에드포스로 날릴것이므로 리지드바디 -> 다이나믹

중력은 받지 않으므로 - 그래비티 스케일 0

재사용해서 사용할것이므로 프리팹화

총알 A를 복사해서 만든후 스프라이트에 다른 모양을 넣은 후 콜라이더 크기 조절

 

총알B를 저장하면 A를 복사해서 만든 것이므로 다시 프리팹화할때 알림 문구가 뜨는데

새로운 것이면 Original클릭

 

총알이 나중에 무수히 많아질 수 있으므로 지울 수 있는 또 다른 경계선 생성할 것인데

Bullet이라는 스크립트를 새로 생성

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
    }
}

기존에 만들어 놓았던 Border를 복사해서 이름을 바꾸고

경계선을 재설정

 

태그를 새로 만든 후 할당

 

그럼 총알이 경계선에 닿으면 사라진다.

 

이제 총알을 만들 로직을 Player 스크립트에 작성할 것이다.

전 페이지에서 만들었던 업데이트문안에 있는 함수를 따로 캡슐화로 분리한 후 이름을 Move로 바꾸었고,

총알을 만들며 총알이 충돌을 일으켜야하므로 리지드바디를 사용

 

https://docs.unity3d.com/ScriptReference/ForceMode2D.html

 

Unity - Scripting API: ForceMode2D

Success! Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable. Close

docs.unity3d.com

퍼블릭으로 만들어둔 공간에 프리팹 할당

실행해보면 무수히 만들어진다.

 

그림과 같이 총알끼리 물리적 충돌로 인해 여러뱡항으로 나오는데

이것은 프리팹에서 isTrigger를 체크하면 해결된다.

 

총알이 눌렀을 때만 발사되게 조건을 넣어줄 것인데

!를 사용하는것이 아닌 조건일때만 실행되게 코드를 변경해줘도 된다.(코드 스타일)

이제 다음은 총알 딜레이를 추가할 것이다.

시간에 따라 현재 딜레이에 추가해줄것이고 

발사는 딜레이에 영향을 받아줄 것이므로 Fire문 안에 넣는다.

그리고 발사 후 딜레이를 없애주기 위해 마지막엔 딜레이를 0으로 초기화

총알을 쏠 때마다 딜레이를 변경해줄 수 있다.

 

총알의 파워를 입력해줄 것인데

케이스로 나눠서 각각 파워1, 2, 3일때 다르게 나오게 수치 변경

그리고 총알마다 간격을 조절하여

현재 위치해서 벡터값을 더해주면

총알의 생성위치를 원하는 간격으로 조절할 수 있다.

 

완성된 코드

using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;

public class Player : MonoBehaviour
{
    public bool isTouchTop;
    public bool isTouchBottom;
    public bool isTouchRight;
    public bool isTouchLeft;
    private Animator anim;

    public float speed;
    public float power;
    public float maxShotDelay; //실제 딜레이
    public float curShorDelay; //한발 쏜 후 딜레이

    public GameObject bulletObjA;
    public GameObject bulletObjB;

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

    void Move()
    {
        float h = Input.GetAxisRaw("Horizontal");
        if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
            h = 0;
        float v = Input.GetAxisRaw("Vertical");
        if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
            v = 0;
        Vector3 curPos = this.transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
        transform.position = curPos + nextPos;

        if (Input.GetButtonDown("Horizontal") ||
            Input.GetButtonDown("Vertical"))
        {
            anim.SetInteger("Input", (int)h);
        }
    }

    private void Update()
    {
        Move();
        Fire();
        Reload();
    }

    void Fire()
    {
        if (!Input.GetButton("Fire1"))
            return;
        if (curShorDelay < maxShotDelay)
            return;

        switch (power)
        {

            case 1://Power One
                GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
                Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
                rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                break;
            case 2:
                GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
                GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
                Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
                rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
            case 3:
                GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
                GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
                GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
                Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
                Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
                rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
                rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);

                break;
        }
                curShorDelay = 0;
    }

    void Reload()
    {
        curShorDelay += Time.deltaTime;

    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Border")
        {
            switch(collision.gameObject.name)
            {
                case "Top":
                    isTouchTop = true;
                    break;
                case "Bottom":
                    isTouchBottom = true;
                    break;
                case "Right":
                    isTouchRight = true;
                    break;
                case "Left":
                    isTouchLeft = true;
                    break;
            }
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        switch (collision.gameObject.name)
        {
            case "Top":
                isTouchTop = false;
                break;
            case "Bottom":
                isTouchBottom = false;
                break;
            case "Right":
                isTouchRight = false;
                break;
            case "Left":
                isTouchLeft = false;
                break;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "BorderBullet")
        Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
    }
}

 

'산대특' 카테고리의 다른 글

2D Airplane - 원근감있는 무한 배경 만들  (0) 2024.03.17
2D Airplane - 5  (1) 2024.03.14
2D Airplane - 4  (0) 2024.03.12
2D Airplane - 3  (0) 2024.03.12
2D Airplane - 1  (0) 2024.03.09

 

 

 

 

스프라이트 게임을 자르기 위해 설정들을 이렇게 바꾸어 주었다.

그 후 Sprite Editor를 누르면 여러개의 이미지들이 묶여있는 것을 분할 할 수 있는데

크기로 24*24 여백을 1로 잘랐다.

 

우선 화면의 비율을 선택해주자

필자는 aspect로 하였고 비율을 입력하여 2가지 해상도를 만들었다.

 

비행기가 움직일 수 있게 코드 작성

transform은 time.deltatime이랑 세트로 생각하자!

현재의 위치에 키보드로 받을 위치를 더해준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed;
    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 curPos = this.transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
        transform.position = curPos + nextPos;

    }
}

 

움직여보면 원하지 않는 배경 밖으로 나가게 된다.

 

비행기에 Box Collider를 추가하여 부착해주면 초록색 경계가 생긴다.

 

피격 범위로도 사용할 것이므로 콜라이더의 크기를 줄여주었다.

맵의 바깥 부분에 Border라는 빈 오브젝트안에

방향 별로 box collider2D를 붙인 후 범위를 잡아주었다.

 

 

Player :

물리 연산이기에 모두 리지드바디2d적용

물리엔진을 많이 사용하지 않기에 키네틱을 사용

키넥틱이란? 다른 것이랑 충돌이 일어났을 때 영향을 받지 않겠다.

 

보더들은 고정이기때문에 static 사용

 

경계들을 각각 인지를 해야하기에 변수 선언

물리적인 힘은 넣을 필요 없으므로 collider대신에 온트리거 사용하였고,

각 경계마다 tag로 Border를 달아줄 것이다.

물체가 닿은 상태에서 h는 좌우 이동이므로 => 이동시 닿을 떄를 처리하는데 이때 각각 0으로 만들어서 이동을 멈춘다.

물체가 닿지 않았을 때 이동해야하므로 그 경우도 처리해야 한다.

태그를 달아준다.

 

현재 까지의 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed;
    public bool isTouchTop;
    public bool isTouchBottom;
    public bool isTouchRight;
    public bool isTouchLeft;

    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
            h = 0;
        float v = Input.GetAxisRaw("Vertical");
        if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
            v = 0;
        Vector3 curPos = this.transform.position;
        Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
        transform.position = curPos + nextPos;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Border")
        {
            switch(collision.gameObject.name)
            {
                case "Top":
                    isTouchTop = true;
                    break;
                case "Bottom":
                    isTouchBottom = true;
                    break;
                case "Right":
                    isTouchRight = true;
                    break;
                case "Left":
                    isTouchLeft = true;
                    break;
            }
        }
    }

    private void OnTriggerExit2D(Collider2D collision)
    {
        switch (collision.gameObject.name)
        {
            case "Top":
                isTouchTop = false;
                break;
            case "Bottom":
                isTouchBottom = false;
                break;
            case "Right":
                isTouchRight = false;
                break;
            case "Left":
                isTouchLeft = false;
                break;
        }
    }
}

 

 

이제 애니메이션을 넣어줄 것이다.

잘라놓은 애니메이션 각각 4개를 클릭해서

하이어라이키에 있는 Player에 드래그해서 넣어주고 애니메이션 폴더를 생성후

각각 Center, Left, Right로 이름을 만들어서 저

플레이어에 애니메이션 컴포넌트가 생성됨

그 후 플레이어(애니메이션 컨트롤러)를 눌러서 이 창을 띄어주고

Make Transition으로 서로의 관계를 만들어 준다.

 

트랜지션은 파라미터로 이동한다.

매개변수 Input.GetAxisRaw() 값 그대로 사

각각의 트랜지션 설정을 해줄것인다.

즉각 반응을 위해 Has Exit Time은 꺼야한다.

Transition Duration은 보통3D에서 모션을 부드럽게 변화시키기 위해 사용하는데

필요 없으므로 0으로 변경

 

이제 인풋 파라미터를 받을 코드를 작성할 것이다.

그전에 애니메이터 변수 생성 후 초기화 진행

 

업데이트 문 안에 작성하고 좌우 방향은 h이므로 h를 넣고 float형이므로

(int)로 강제 형변환

\

배경을 집어 넣을 것인데 넣으면 플레이어가 보이지 않을 수 있다.

그러면 플레이어를 누르고 Order In Layer를 0에서 1로 변경

 

그러면 플레이어가 이제 잘보일 것이다.

마지막을 플레이를 해볼것인데 애니메이터 컨트롤러를 클릭하고 실행하면

실시간 애니메이션이 변경되며 사용됨을 눈으로 확인 할 수 있다.

애니메이터 실시간.mp4
0.71MB

'산대특' 카테고리의 다른 글

2D Airplane - 원근감있는 무한 배경 만들  (0) 2024.03.17
2D Airplane - 5  (1) 2024.03.14
2D Airplane - 4  (0) 2024.03.12
2D Airplane - 3  (0) 2024.03.12
2D Airplane - 2  (0) 2024.03.10

virtual?

 

요약하자면,,

부모 클래스에서 virtual 키워드를 사용하여 함수를 만들면, 자식 클래스에서 이 함수를 재정의 할 수 있도록 허용

 

특징 :

 1. virtual 이 붙은 함수는 자식 클래스에서 재정의가 가능하다.

 2. 자식 클래스에서는 new 또는 override 키워드가 사용가능하다.

    - override는 재정의를 하겠다는 확장의 의미이고, new 는 기본 클래스를 숨기는 의미이다. 

 3. 자식클래스의 함수 시그니쳐가 동일해야 재정의가 가능하다.

 4. 자식클래스의 함수는 base 키워드를 사용해 부모 클래스의 함수를 호출 할 수 있다.

 5. abstract 와는 달리 자식클래스에서 구현은 선택이다. (구현 안하면 부모의 함수 사용)

 6. static, abstract, private,sealed 키워드와는 사용이 불가능하다.

 

그럼 왜 사용할까?

public class Monster
{
    public virtual void hit()
    {
        Console.WriteLine("Monster hit");
    }
}
 
public class Orc : Monster
{
    public override void hit()
    {
        Console.WriteLine("Orc hit");
    }
}
 
public class Elf : Monster
{
    public new void hit()
    {
        Console.WriteLine("Elf hit");
    }
}
 
public class Wolf : Monster
{
    public void hit()
    {
        Console.WriteLine("Wolf hit");
    }
}

 

class Program
 {
     static void Main(string[] args)
     {
         Monster monster1 = new Monster();
         Orc monster2 = new Orc();
         Elf monster3 = new Elf();
         Wolf monster4 = new Wolf();
 
         monster1.hit();
         monster2.hit();
         monster3.hit();
         monster4.hit();
 
         Monster monster5 = new Orc();
         Monster monster6 = new Elf();
         Monster monster7 = new Wolf();
 
 
         Console.WriteLine("////////////////////");
         monster5.hit();
         monster6.hit();
         monster7.hit();
 
     }
 }

 

이런식으로 하나씩 입력을 하면 원하는 값은 얻을 수 있지만

나중에 더 많아지면 코드가 길어지면 자연스럽게 코드가 무거워지고 가독성이 떨어지게 된다.

 

virtual 키워드로 지정된 메서드는 가상 메서드가 되는데 가상 메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된다.

자식 클래스는 override 키워드를 사용해 부모 클래스의 가상메서드를 재정의 할 수 있다.

override?

 

기반 클래스에서 선언된 메소드를 자식 클래스에서 재정의

기반 클래스에서 오버라이딩할 메소드를 미리 virtual로 한정

파생 클래스는 virtual 메소드를 override한정자를 이용하여 재선언

 

즉, 가상메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된 메서드

 

base?

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Person : MonoBehaviour
{
   public virtual void Work()
    {
        Debug.Log("일하다");
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Salesman : Person {
    public override void Work()
    {
        base.Work();
        Debug.Log("나는 셀러리맨이다");
    }
    private void Start()
    {
        Work();
    }
}

 

보기와 같이 메서드의 이름이 Work로 같이 정의되어도 자식클래서에서 같은 이름 접근가능하고

부모클래스를 호출할 수 있다.

 

혹시나 부모클래스의 메소드가 자식 클래스에 의해 변형이 될가 봐 테스트 해보았고,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Salesman : Person {
    public override void Work()
    {
        base.Work();
        Debug.Log("나는 셀러리맨이다");
    }
    private void Start()
    {
        Person person = new Person();
        person.Work();
    }
}

 

 

결론은 보기과 같이 "아니" 다.

 

ChatGpt에게 수업이 끝나기 전 간단한 예제를 내달라 했는데 혼자서 입력해봤던 것과 난이도가 같아서

더 어려운 예제를 풀 것이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Fish : MonoBehaviour
{
    //1.물고기를 나타내는 Fish 클래스를 작성합니다.
    //이 클래스에는 Swim() 메서드가 있어야 합니다. 이 메서드는 "물고기가 헤엄칩니다."라는 메시지를 출력합니다.
    public virtual void Swim()
    {
        Debug.Log("물고기가 헤엄칩니다.");
    }

    private void Start()
    {
        Swim();
    }
}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Goldfish : Fish
{
    //2.Fish 클래스를 상속받는 Goldfish 클래스를 작성합니다.
    //이 클래스에는 Swim() 메서드를 오버라이드하여 "금붕어가 헤엄칩니다."라는 메시지를 출력하도록 구현합니다.

    public override void Swim()
    {
        Debug.Log("금붕어가 헤엄칩니다.");
    }
}

 

 

자식클래스가 먼저 호출되고 부모클래스가 잇따라 호출되었다.

'낙서장 > C#' 카테고리의 다른 글

[C#] 메서드 오버로딩  (0) 2024.06.02
상속과 다형성  (1) 2024.03.08
흐름 제어  (1) 2024.02.26
데이터를 가공하는 연산자  (0) 2024.02.25
데이터를 담는 변수와 상수  (0) 2024.02.22

상속이란?

 

다른 클래스로부터 코드를 물려받는 것

 

상속의 대상 : 클래스의 멤버(필드, 메소드, 프로퍼티 등)

새로 선언하는 클래스 이름 뒤에 클론( : ) 과 기반 클래스의 이름을 표기하여 상속

물려주는 클래스 : 기반/부모 클래스, 물려받는 클래스 : 파생/자식 클래스

 

하나의 클래스가 하나의 상속만 되지만, 전이적으로 여러개를 이어서 사용 가능

 

 

상속을 받을 때 클래스명 뒤에 MonoBehaviour를 지우고 상속받을 부모 클래스의 이름을 추가

 

3개의 오브젝트에 각각의 스크립트를 부착하였다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
    public void Attack()
    {
        Debug.Log("공격");
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orc : Monster
{
    public void WarCry()
    {
        Debug.Log("전투함성");
        Attack();
    }
    //public void Start()
    //{
    //    WarCry();
    //}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Dragon : Orc
{
    public void Fly()
    {
        Debug.Log("날기");
        Attack();
        WarCry();
    }
    private void Start()
    {
        Fly();
    }
}

실행을 해보면 이런식의 순서로 호출된다.

  1. Dragon 클래스의 Start 메서드가 호출
  2. Start 메서드 내에서 Fly 메서드가 호출
  3. Fly 메서드 내에서 Attack 메서드가 호출. 이때, Dragon 클래스가 Orc 클래스를 상속하고 있으므로,                          Orc 클래스의 Attack 메서드가 실행됩니다.
  4. Attack 메서드가 실행되고 "공격"이라는 메시지가 출력
  5. Fly 메서드 내에서 WarCry 메서드가 호출. WarCry 메서드는 Orc 클래스에 정의되어 있다.
  6. WarCry 메서드가 실행되고 "전투함성"이라는 메시지가 출력된다.
  7. WarCry 메서드 내에서 Attack 메서드가 호출되는데, Orc 클래스가 Monster 클래스를 상속하고 있으므로,        Monster 클래스의 Attack 메서드가 실행됩니다.
  8. Attack 메서드가 실행되고 다시 "공격"이라는 메시지가 출력된다.

쉽게 말해 부모 자식 관계가 Monster -> Oak -> Dragon 순으로 되어있으므로

서로 상속되어 클래스를 가져와 사용할 수 있다.

 

주의

Unity에서 MonoBehaviour를 상속한 클래스에서 Start 메서드를 사용할 때,

상속 관계에 있는 클래스의 Start 메서드 실행 순서는 보장되지 않는다.

이는 Unity 엔진이 각각의 MonoBehaviour를 포함한 오브젝트들에 대해 독립적으로 처리하기 때문이다.

 

 

자식클래스는 부모클래스의 속성을 받아서 사용할 수 있지만

반대로 일반적으로 부모클래스는 자식클래스의 속성과 메소드를 사용할 수 없다.

 


 

다형성이란?

 

 

오류1. WarCry()를 하면 몬스터들의 대미지를 올려주려다 오류가 발생했다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orc : Monster
{
    Monster[] monsters = GameObject.FindObjectsOfType<Monster>();
    public void WarCry()
    {
        Debug.Log("전투함성");

        //모든 몬스터 공격력 + 10 증가
       
        for(int i = 0; i < monsters.Length; i++)
        {
            monsters[i].damage += 10;
            Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
        }

    }
    private void Start()
    {
        for (int i = 0; i < monsters.Length; i++)
        {
            Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
        }
        WarCry();
    }

}

 

 

이것에 대해 알아보니

Unity에서는 MonoBehaviour의 생성자나 인스턴스 필드 초기화자에서 FindObjectsOfType를 호출하는 것을

허용하지 않으므로 대신에 Awake나 Start 메서드에서 호출해야 한다.

따라서 Orc 클래스의 생성자에서 FindObjectsOfType를 호출하는 것은 허용되지 않는다.

Orc 클래스의 WarCry 메서드 내에서 호출하거나, Orc 클래스의 Awake나 Start 메서드에서 호출해야 한다고 한다.

 

오류2. 호출이 2번된다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
    public float damage = 10f;
    public void Attack()
    {
        Debug.Log("공격");
    }

}

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Orc : Monster
{
    public void WarCry()
    {
        Debug.Log("전투함성");

        //모든 몬스터 공격력 + 10 증가
        Monster[] monsters = GameObject.FindObjectsOfType<Monster>();

        for (int i = 0; i < monsters.Length; i++)
        {
            monsters[i].damage += 10;
            Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
        }

    }
    private void Start()
    {
        Monster[] monsters = GameObject.FindObjectsOfType<Monster>();

        for (int i = 0; i < monsters.Length; i++)
        {
            Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
        }
        WarCry();
    }

}

 

주어진 코드에서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에

FindObjectsOfType<Monster>()를 사용하여 몬스터 배열을 얻고 있다.

FindObjectsOfType<Monster>()는 현재 씬에 있는 모든 Monster 클래스의 인스턴스를 찾아 배열로 반환한다.

Start 메서드에서 이를 호출하여 몬스터 배열을 가져오고,

그 다음에 WarCry 메서드를 호출하는 것은 상황에 따라 문제가 될 수 있다.

만약 Start 메서드가 호출될 때 씬에 이미 생성된 몬스터가 있다면,

WarCry 메서드에서 다시 같은 몬스터 배열을 가져오게 되어 몬스터 배열이 중복되어 보일 수 있다.

따라서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에 몬스터 배열을 가져오는 부분을 제거하면

이런 문제를 해결할 수 있고, WarCry 메서드에서만 몬스터 배열을 가져오도록하면 된다.

 

 

여기서 자세히 보아야할 것은 다형성을 이용한 패턴이라는 것이다.

 

 

https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html

 

Unity - Scripting API: Object.FindObjectsOfType

This does not return assets (such as meshes, textures or prefabs), or objects with HideFlags.DontSave set. Objects attached to inactive GameObjects are only included if inactiveObjects is set to true. Use Resources.FindObjectsOfTypeAll to avoid these limit

docs.unity3d.com

 

명시한 타입의 모든 오브젝트를 찾아 배열로 반환, 씬에 있는 모든 Monster 타입의 오브젝트가 monsters배열에 저장됨

'낙서장 > C#' 카테고리의 다른 글

[C#] 메서드 오버로딩  (0) 2024.06.02
virtual ,override, base  (2) 2024.03.08
흐름 제어  (1) 2024.02.26
데이터를 가공하는 연산자  (0) 2024.02.25
데이터를 담는 변수와 상수  (0) 2024.02.22

+ Recent posts