반응형

Enemy 스크립트 작성합니다 

 

Enemy 스크립트를 작성

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

public class Enemy : MonoBehaviour
{
    public GameObject deathVFX;
    public GameObject hitVFX;
    public Transform parent;
    public int scorePerHit = 15;
    public int hitPoint = 4;

    ScoreBoard scoreBoard;

    private void Start()
    {
        scoreBoard = FindObjectOfType<ScoreBoard>();
    }

    
    private void OnParticleCollision(GameObject other)
    {
        ProcessHit();
        //hitPoint 0 이하일때 적을 파괴한다 
        if (hitPoint <1 )
        {
            KillEnemy();
        }
                   
    }

    //점수를 증가한다 
    private void ProcessHit()
    {
        GameObject vfx = Instantiate(hitVFX, transform.position, Quaternion.identity);
        vfx.transform.parent = parent;
        hitPoint--;
        scoreBoard.IncreaseScore(scorePerHit);
    }

    //외부 물체와 부딛칠때 이팩트생성한다.그리고 삭제 
    private void KillEnemy()
    {
        GameObject vfx = Instantiate(deathVFX, transform.position, Quaternion.identity);
        vfx.transform.parent = parent;
        Destroy(gameObject);
    }


}

 

 

 

 

큐브(Cube) 이름을 Enemy로 바꿉니다 

 

 

빈 오브젝트를 만들고 이름을 Spawn A Runtime으로 바꿉니다 

 

 

 

 

 

 

 

 

각각 Enemy 에 스크립트를 붙이고 아래그림과 같이 이팩트와 오브젝트를 붙입니다 

 

Package Manager 를 열고 TextMeshPro를 설치합니다 

UI -> Text - TextMeshPro 를 실행합니다 

 

ScoreBoard 스크립트 작성

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

public class ScoreBoard : MonoBehaviour
{

    int score;

    TMP_Text scoreText;


    private void Start()
    {
        scoreText = GetComponent<TMP_Text>();
        scoreText.text = "Start";
    }
    public void IncreaseScore(int amountToIncrease)
    {
        score += amountToIncrease;
        scoreText.text = score.ToString();
    }
}

Text 를 왼쪽 아랫단에 보이게 합니다 

 

 

 

Canvas를 선택하고  Screen Space - Overlay 선택  Canvas Scaler -> Ui Scale Mode -> Scale with Screen Size ,  Reference Resolution  X : 1920 Y: 1080으로 합니다 

 

Text 이름을 ScoreBoard 로 합니다 

 

 

ScoreBoard 스크립트를 붙힘니다 

게임을 실행 시켜서  장애물을 파괴했을 때 점수와 이팩트가 잘 터지는지 확인합니다

 

게임자료

3DTimeLime_F.vol1.egg
10.00MB
3DTimeLime_F.vol2.egg
10.00MB
3DTimeLime_F.vol3.egg
9.75MB

반응형
반응형

아래 있는 Effect 유니티 파일을 다운로드합니다 

Effect.unitypackage
0.04MB

 

 

 

임포트 합니다 

 

 

 PlayerRig 오브젝트 자식으로 있는  PlayerShip을 선택하고   Prefabs 폴더 안에 있는 Laser 프리팹을 드래그하여 자식으로 놓고 하나 더 복사하여 이름을 Laser_Right , Laser_Left로 합니다 

그리고 각각의 위치를 비행기 날개에서 발사 되는 위치에 적당히 올려놓습니다 

 

 

 

PlayerControls 스크립트 수정합니다 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Rendering.HighDefinition;

public class PlayerControl : MonoBehaviour
{   
    public float controlSpeed = 10f;
    public float xRange = 12f;
    public float yRange = 10f;
    [SerializeField] GameObject[] lasers;

    public float positionPithcFactor = -2f;
    public float controlPithcfactor = -10f;
    public float positionYawFactor = 2f;

    public float controlRollFactor = -20f;
    [SerializeField] InputAction fire;

    float xThrow;
    float yThrow;
    void Update()
    {
        ProcessTranslation();
        ProcessRotation();
        ProcessFiring();
    }

    void OnEnable()
    {
        fire.Enable();
    }

    private void OnDisable()
    {
        fire.Disable();
    }

    //상하좌우로 움직일때 로컬 로테이션을 준다 
    void ProcessRotation()
    {
        //y축 로컬 움직임 팩터
        float pitchDueToPosition = transform.localPosition.y * positionPithcFactor;
        float pitchdueToControlThrow = yThrow * controlPithcfactor;


        float pitch = pitchDueToPosition + pitchdueToControlThrow;
        //x축 로컬 움직임 팩터
        float yaw = transform.localPosition.x * positionYawFactor;
        float roll = xThrow * controlRollFactor;

        //로컬축으로 로테이션한다
        transform.localRotation = Quaternion.Euler(pitch, yaw, roll);
    }
    private void ProcessTranslation()
    {
        //좌우 입력
        xThrow = Input.GetAxis("Horizontal");
        //상하 입력
        yThrow = Input.GetAxis("Vertical");

        //시간에 따라 좌우 움직임을 준다 
        float xoffset = xThrow * Time.deltaTime * controlSpeed;
        float rawXPos = transform.localPosition.x + xoffset;

        //플레이어 좌우 이동 제한
        float clampedXPos = Mathf.Clamp(rawXPos, -xRange, xRange);

        //시간에 따라 상하 움직임을 준다 
        float yoffset = yThrow * Time.deltaTime * controlSpeed;
        float rawYpos = transform.localPosition.y + yoffset;

        //플레이어 상하 이동 제한
        float clampedYPos = Mathf.Clamp(rawYpos, -yRange, yRange);

        //키를 누르면 오브젝트를 상하좌우로 움직이게 한다 
        transform.localPosition = new Vector3(clampedXPos, clampedYPos, transform.localPosition.z);
    }

    

    void ProcessFiring()
    {
    //마우스 오른쪽 버튼을 누르면 미사일(ON)시켜 발사하고 그러지 않을땐 Off 한다
        if (Input.GetButton("Fire1"))
        {
            SetLasersActive(true);
        }
        else
        {
            SetLasersActive(false);
        }
    }
    

   
    //레이져 발사 이팩트를 껐다 켰다 한다
    void SetLasersActive(bool isActive)
    {
        foreach (GameObject laser in lasers)
        {
            var emissionModule = laser.GetComponent<ParticleSystem>().emission;
            emissionModule.enabled = isActive;
        }
    }
  
}

 

Laser_Right , Laser_Left  오브젝트를 드래그 하여 PlayerControls 스크립트 Lasers 위치에 드래그하여 올려놓습니다 

 

 

 

 

게임을 실행시켜서 비행기에 총알이 나가는지 확인합니다 

 

총알이 튕겨져 나갈땐  Lasers 프리팹을 열고 Particle System -> Collision을 열고 Min Kill Speed 값을 올려 봅니다 

 

PlayerShip를 선택하고 Physics -> Box Collider를 선택합니다  

 

 

 

PlayerShip 를 선택하고 Physics -> Rigidbody를 선택합니다  

 

 

Box Collider -> is Trigger 체크하고 Use gravity 체크해제 하고 is Kinematic 체크합니다 

 

 

Collision Handler 스크립트 작성

비행기가 외부 물체와 부딪칠때 게임이 재실행하는 스크립트를 작성합니다 

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


public class CollisionHandler : MonoBehaviour
{
    public float loadDelay = 1f;
    public ParticleSystem crashVFX;



    //외부 물체와 충돌하였을때 
    private void OnTriggerEnter(Collider other)
    {
        StartCrashSequence();
    }

    //crashVFX 이팩트를 실행, PlayerControl실행을 중지한다.그리고 1초후 다음 씬으로 이동한다 
    private void StartCrashSequence()
    {      
        crashVFX.Play();
        GetComponent<PlayerControl>().enabled = false;
        GetComponent<MeshRenderer>().enabled = false;
        GetComponent<BoxCollider>().enabled = false;

        Invoke("ReloadLevel", loadDelay);
    }

    void ReloadLevel()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);
    }
}

 

 

PlayerShip에 드래그하여 붙입니다 

 

 

PlayerRig 하위 밑으로 Player Explosion VFX 프리팹을 드래그하여 놓고 비행체 중앙에 놓습니다 

 

Collision Handler 스크립트 Crash VFX 공간에 드래그 하여 Player Explosion VFX를 놓습니다 

 

비행기가 비행하는 선로에 큐브를 생성하여 비행기가 부딪칠 수 있도록 합니다 

 

 

File -> Build Settings 들어가서 현재 씬을 드래그하여 Build Settings에 올립니다 

게임을 실행하여 장애물인 큐브에 부딪 칠때  이팩트가 터지고 게임이 재실행되는지 확인합니다 

 

 

반응형
반응형

유니티 2023.1.2.1에서 작업

유니티 에셋을 들어가서 Realistic Terrain Collection Lite  무료 에셋을 다운로드합니다 

window -> Package Manager 를 열고  임포트 합니다

 

임포트 한 파일에서 Terrain 5 파일 안에 자기가 맘에 드는 Terrain 파일 하나를 골라 Hierarchy 안으로 끄집어 올려놓습니다  

 

 

 

 

 

에셋 스토어에 다시 들어가서 Star Sparrow Modular Spaceship 에셋을 다운 받습니다 

 

Package Manager 에서 임포트 하여 다운로드합니다 

 

 

 

 

 

다운로드한 Star Sparrow 파일에서 Prefabs 폴더의 Examples에서 맘에 드는 비행기를 하나 골라서 Hierarchy 위에 끄집어 올려놓습니다 

 

올려놓은 비행기의 이름을 PlayerShip 으로 바꾸고 Assets폴더에 Prefabs 폴더를 하나 만들어서 드래그하여 프리팹으로 만듭니다 

 

 

 

 

 

 

 

오른쪽 마우스 버튼을 눌러서  Create Empty 오브젝트를 만든 다음 이름을 PlayerRig 로 바꾸고  Transform 값을 아래 그림과 같이 놓습니다  그리고 자식으로 PlayerRig와 Transform 값을 같은 값으로 하고 아래 그림과 같이 자식으로 놓습니다 

그리고 PlayerRig 자식으로 Main Camera 를 두고 Transform 값을 아래 그림과 같이 하여 카메라 보는 각도가 비행기 뒤쪽에서 바라보게 끔 합니다 

 

 

 

 

 

Create Empty 를 만들고 이름을 Master Timeline으로 합니다 

Master Timeline 선택하고 Window -> Sequencing -> Timeline 을 클릭하여 Timeline 창을 보이게 합니다 

 

Master Timeline 오브젝트를 선택하고  Timeline 창에 Create 버튼을 클릭하고 타임라인저장할 Assets 폴더에 새로운 폴더를 만듭니다 

 

Master Timeline 오브젝트를 선택하고 Timeline 창에서 +버튼을 누르고   Animation Track 를 선택합니다 

 Timeline 창에서 오른쪽의 자물쇠 모야을 잠금으로 합니다 

 

 

PlayerRig 오브젝트를 드래그 하여 Timeline 창의 Animation Track에 드래그합니다  

 

Timeline 창에서 아래와 같이 빨간 버튼을 누릅니다 

 

PlayerRig를 움직이면 Timeline 시간대에 점이 찍힘니다 PlayerRig를 자기가 움직이고 싶은 위치와 Timeline 시간대에 갔다 놓으면 됩니다  

 

Timeline의 아래와 같이 선 모양의 아이콘을 클릭하면 Animated Values 가 나오는데 찍혀있는 점들을 움직이면 위치와 회전을 바꿀 수 있습니다 

 

 

 

 

 

아래와 같이 PlayerRig 움직여 Timeline 시간대에 점이 찍히는 방향대로 움직인 결과 입니다 

 

 

Assets에 Scripts 폴더를 만들고 새로운 스크립트를 생성하여 이름을 PlayerControl을 만듭니다

 

 

 

 

 

스크립트를 작성합니다 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//using UnityEngine.InputSystem;
//using UnityEngine.Rendering.HighDefinition;

public class PlayerControl : MonoBehaviour
{   
    public float controlSpeed = 10f;
    public float xRange = 12f;
    public float yRange = 10f;
   

    public float positionPithcFactor = -2f;
    public float controlPithcfactor = -10f;
    public float positionYawFactor = 2f;

    public float controlRollFactor = -20f;
   

    float xThrow;
    float yThrow;
    void Update()
    {
        ProcessTranslation();
        ProcessRotation();    
    }

    //상하좌우로 움직일때 로컬 로테이션을 준다 
    void ProcessRotation()
    {
        //y축 로컬 움직임 팩터
        float pitchDueToPosition = transform.localPosition.y * positionPithcFactor;
        float pitchdueToControlThrow = yThrow * controlPithcfactor;


        float pitch = pitchDueToPosition + pitchdueToControlThrow;
        //x축 로컬 움직임 팩터
        float yaw = transform.localPosition.x * positionYawFactor;
        float roll = xThrow * controlRollFactor;

        //로컬축으로 로테이션한다
        transform.localRotation = Quaternion.Euler(pitch, yaw, roll);
    }
    private void ProcessTranslation()
    {
        //좌우 입력
        xThrow = Input.GetAxis("Horizontal");
        //상하 입력
        yThrow = Input.GetAxis("Vertical");

        //시간에 따라 좌우 움직임을 준다 
        float xoffset = xThrow * Time.deltaTime * controlSpeed;
        float rawXPos = transform.localPosition.x + xoffset;

        //플레이어 좌우 이동 제한
        float clampedXPos = Mathf.Clamp(rawXPos, -xRange, xRange);

        //시간에 따라 상하 움직임을 준다 
        float yoffset = yThrow * Time.deltaTime * controlSpeed;
        float rawYpos = transform.localPosition.y + yoffset;

        //플레이어 상하 이동 제한
        float clampedYPos = Mathf.Clamp(rawYpos, -yRange, yRange);

        //키를 누르면 오브젝트를 상하좌우로 움직이게 한다 
        transform.localPosition = new Vector3(clampedXPos, clampedYPos, transform.localPosition.z);
    }
   
}

 

 

PlayerShip 오브젝트를 선택하고 PlayerControl 스크립트를 드래그 하여 붙입니다

그리고 아래그림과 같이 데이터 값을 넣습니다 

게임을 실행시켜서 AD(좌우) WS(상하) 키를 움직여서 비행기가 화면을 벋어나지 않고 잘 움직이는지 확인합니다  

 

 

 

반응형
반응형

이번 시간에는 게임 뷰에 점수를 보이게 하겠습니다

먼저 Hierarchy 뷰에서 마우스 오른쪽 버튼을 클릭하고   UI -> Text를 생성합니다 

그러면 Hierarchy 뷰에서 Canvas 가 생성됩니다 

Canvas 를 선택하고  UI Scale Mode -> Scale With Screen Size , Reference Resolution  1080 1920 , Match 0.5에 맞춥니다 

 

Canvas 자식으로 있는 Text 의 이름을 ScoreText로 바꾸고 ScoreText를 선택하여 Center를 클릭한 다음 아래 사진처럼 top 중앙에 올 수 있도록 합니다 

 

그리고 Canvas 를 클릭하고 마우스 오른쪽을 클릭하고 Create Empty 하여 GameObject를 생성한 다음 이름을 GameUI로 고친 후 ScoreText를 자식으로 놓습니다 

GameManager 스크립트를 수정합니다 

 

점수 Text 를 넣을 수 있는 네임스페이스(using UnityEngin.UI)를 추가하고 변수에 int score = 0 , pulic Text scoreText 추가 GameStart() 함수에 gameUI.SetActive(true)   StartCoroutine(UpdateScore()) 추가

 IEnumerator UpdateScore()
    {
        //1초 지날때 마다 1점씩 올라간다 
        while (true)
        {
            yield return new WaitForSeconds(1f);
            score++;

            scoreText.text = score.ToString();
        }
    }

 

추가 합니다 

 

반응형

 

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


public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    //
    public GameObject platformSpawner;

    public GameObject gameUI;

    public bool gameStarted;

    int score = 0;

    public Text scoreText;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (!gameStarted)
        {
            //마우스 클릭하면 차가 움직인다 
            if (Input.GetMouseButtonDown(0))
            {
                GameStart();
            }
        }
    }

    public void GameStart()
    {
        gameStarted = true;
        //platformSpawner 스크립트를 실행
        platformSpawner.SetActive(true);

        gameUI.SetActive(true);
        StartCoroutine(UpdateScore());
    }


    public void GameOver()
    {
        //platformSpawner 스크립트를 없앤다
        platformSpawner.SetActive(false);

        //1초후 게임재시작 함수에 전달 
        Invoke("ReloadGame",1);
    }

    //게임재시작
    void ReloadGame()
    {
        SceneManager.LoadScene("Game");
    }

    IEnumerator UpdateScore()
    {
        //1초 지날때 마다 1점씩 올라간다 
        while (true)
        {
            yield return new WaitForSeconds(1f);
            score++;

            scoreText.text = score.ToString();
        }
    }
}

 

Hierarchy 뷰에서 Canvas 자식으로 있는 GameUI를 끄고 GameManager를 선택하여 GameUI와 ScoreText를 연결합니다 

 

게임을 실행하여 점수가 올라가는지 확인합니다 

 

반응형
반응형

이번 시간에는 게임이 끝나면 게임을 재시작하는 스크립트를 짜고 3D 게임에서 오브젝트를 밝게 비추는 라이트를 고정해 놓는 작업을 하겠습니다

 

먼저 Hierarchy 뷰에서 GameManager 오브젝트를 선택하여 GameManager 스크립트를 열고 스크립트를 수정합니다 

 

 

반응형

 

GameManager 스크립트 수정

 

스크립트를 열고 네임스페이스 부분에서

using UnityEngine.SceneManagement;

를 추가합니다 

그리고 ReloadGame() 함수를 추가하여 GameOver() 함수에서 실행하는 명령을 추가합니다 

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    //
    public GameObject platformSpawner;

    public bool gameStarted;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (!gameStarted)
        {
            //마우스 클릭하면 차가 움직인다 
            if (Input.GetMouseButtonDown(0))
            {
                GameStart();
            }
        }
    }

    public void GameStart()
    {
        gameStarted = true;
        //platformSpawner 스크립트를 실행
        platformSpawner.SetActive(true);
    }


    public void GameOver()
    {
        //platformSpawner 스크립트를 없앤다
        platformSpawner.SetActive(false);

        //1초후 게임재시작 함수에 전달 
        Invoke("ReloadGame",1);
    }

    //게임재시작
    void ReloadGame()
    {
        SceneManager.LoadScene("Game");
    }
}

 

 

게임을 재시작 할려면 신을 추가하여야 합니다 

File -> Build Settings 를 열고 Game 신을 추가합니다 

 

게임을 재시작하면 라이트가 꺼지는 현상을 보게 될것입니다 

그래서 라이트를 고정할려면 라이트맵을 만들어야 합니다 

Window -> Rendering -> Lighting에 들어갑니다 

 

 

 

Lighting  탭에서 맨 마지막에  Generate Lighting 을 클릭합니다 

 

라이트 맵이생성되었습니다 

 

게임을 실행하여 1초후 게임이 재실행되고 라이트가 고정되었는지 확인합니다 

 

 

반응형
반응형

이번에는 자동차가 도로에서 벗어나면 자동차가 떨어지고 게임이 끝나는 시연을 하겠습니다

 

먼저 Hierarchy뷰에서 car1을 선택하고 is Kinematic을 체크 해저 합니다 

is Kinematic을 해제하면 자동차가 중력의 영향을 받게 됩니다 

 

CarCtrl스크립트를 수정합니다 

 

Update() 함수에

     if (transform.position.y <= -2)
        {
            GameManager.instance.GameOver();
        }

항목을 추가합니다 

car1 이 중력을 받고 떨어지면 게임을 끝내는 GameOver() 함수를 실행합니다 

 

 

 

 

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

public class CarCtrl : MonoBehaviour
{
    public float moveSpeed;
    bool sideMoving = true;
    bool firstInput = true;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (GameManager.instance.gameStarted)
        {
            Move();
            CheckInput();
        }

        //자동차가 중력을 받고 떨어졌을때 y 값이 떨어지면 게임오버실행
        if (transform.position.y <= -2)
        {
            GameManager.instance.GameOver();
        }
    }

    void Move()
    {
        //물체의 z축 방향(forward)으로 moveSpeed 만큼 시간 프레임에 곱해준다 
        transform.position += transform.forward * moveSpeed * Time.deltaTime;
    }


    void CheckInput()
    {
        //첫 클릭은 무시하고 리턴한다 
        if (firstInput)
        {
            firstInput = false;
            return;
        }


        //마우스를 왼쪽버튼 클릭하면
        if (Input.GetMouseButtonDown(0))
        {
            ChangeDirection();
        }
    }
    void ChangeDirection()
    {
        //sideMoving 참일때  
        if (sideMoving)
        {
            sideMoving = false;

            //y축 방향으로 90도 회전한다
            transform.rotation = Quaternion.Euler(0, 90, 0);
        }
        else
        {
            sideMoving = true;
            transform.rotation = Quaternion.Euler(0, 0, 0);
        }
    }
}

 

 

 

GameManager스크립트를 수정합니다 

 

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    //
    public GameObject platformSpawner;

    public bool gameStarted;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (!gameStarted)
        {
            //마우스 클릭하면 차가 움직인다 
            if (Input.GetMouseButtonDown(0))
            {
                GameStart();
            }
        }
    }

    public void GameStart()
    {
        gameStarted = true;
        //platformSpawner 스크립트를 실행
        platformSpawner.SetActive(true);
    }


    public void GameOver()
    {
        //platformSpawner 스크립트를 없앤다
        platformSpawner.SetActive(false);
    }
}

 

GameManager 스크립트를 수정하고 GameManager스크립트 PlatformSpawner 빈 공간에 PlatformSpawner 오브젝트를 연결합니다 

 

 

 

Hierarchy뷰에서 PlatformSpawner 오브젝트를 선택하고 Inspector를 체크 해제합니다 

 

Main Camera를 선택하여 CameraFollow 스크립트를 수정합니다 

 

 

Update() 함수 부분을 수정하여 

      if (target.position.y >= 0)
        {
            Follow();
        }

 

자동차가 y값이 0 이상일 때만 따라 움직이게 합니다 

 

 

 

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

public class CameraFollow : MonoBehaviour
{
    public Transform target;

    Vector3 distance;
    public float smoothValu;

    // Start is called before the first frame update
    void Start()
    {
        // 카메라와 타겟 자동차와의 거리
        distance = target.position - transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        //자동차가 떨어지지 않으면 Follow()실행, 떨어지면 카메라가 움직이지 않는다 
        if (target.position.y >= 0)
        {
            Follow();
        }

       
    }

    void Follow()
    {
        //현재 카메라 포지션
        Vector3 currentPos = transform.position;

        //현재 타겟 자동차 
        Vector3 targetPos = target.position - distance;

        //currentPos(카메라)가 targetPos(자동차)을 smoothValu 만큼 time 프레임에 맞쳐 뒤따라간다
        transform.position =  Vector3.Lerp(currentPos, targetPos, smoothValu * Time.deltaTime);


    }
}

 

 

 

 

Platform 스크립트를 생성합니다 

 

Prefab 폴더의 PlatformP 프리 팹을  선택하고 Plaform 스크립트를 연결합니다

 

 

 

Platform 스크립트 작성

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

public class Platform : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        

    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void OnCollisionExit(Collision collision)
    {

        //Player태그를 가진 오브젝트와 닿았을때  0.2초후 Fall()실행
        if (collision.gameObject.tag == "Player")
        {
            Invoke("Fall", 0.2f);
        }
    }
    void Fall()
    {
        //중력의 영향을 받고 물체를 떨어진다  그리고 1초후 삭제 
        GetComponent<Rigidbody>().isKinematic = false;
        Destroy(gameObject, 1f);
    }
}

 

 

 

 

car1 오브젝트를 선택하고 Inspector에서 Tag -> Player를 줍니다 

 

 

게임을 실행하여 도로블록이 자동차가 지나갔을 때 아래로 떨어지고 자동차가 도로에서 벗어나면 자동차가 중력을 받고 떨어지고 블록 생성이 중단되는지 확인합니다 

 

반응형
반응형

자동차의 시점을 따라가도록 카메라의 스크립트를 달아 자동차를 따라가게 하고 자동차의 도로 역할을 하는 사각 블록을 자동으로 생성되는 스크립트를 만들어보겠습니다

 

 

Hierarchy 뷰에 Create Empty 를 만들어서  이름을 PlatformSpawner로 합니다 

 

Scripts폴더에 PlatformSpawner 스크립트를 생성하고 스크립트작성할 준비를 합니다 

 

 

 

 

PlatformSpawner 오브젝트의 PlatformSpawner 스크립트를 붙힙니다 

 

 

 

PlatformSpawner 스크립트작성

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

public class PlatformSpawner : MonoBehaviour
{
    public GameObject platform;

    public Transform lastPlatform;
    Vector3 lastPosition;
    Vector3 newPos;


    bool stop;
    // Start is called before the first frame update
    void Start()
    {
        lastPosition = lastPlatform.position;

        StartCoroutine(SpawnPlatforms());

    }

    // Update is called once per frame
    void Update()
    {

  



        //if (Input.GetKey(KeyCode.Space))
        //{
        //    SpawnPlatforms();
        //}
    }

    IEnumerator SpawnPlatforms()
    {

        while (!stop)
        {
            GeneratePosition();

            //마지막플랫폼블럭위치에 newPos만큼 새로운 플랫폼블럭을 생성한다
            Instantiate(platform, newPos, Quaternion.identity);

            lastPosition = newPos;

            yield return new WaitForSeconds(0.1f);
        }


    }

    //void SpawnPlatforms()
    //{
    //    GeneratePosition();


    //    //마지막플랫폼블럭위치에 newPos만큼 새로운 플랫폼블럭을 생성한다
    //    Instantiate(platform, newPos, Quaternion.identity);
        
    //    lastPosition = newPos;
    //}

// 랜덤으로 lastPosition 값을 x,y 값 2만큼 움직이게 한다 
    void GeneratePosition()
    {
        newPos = lastPosition;

        int rand = Random.Range(0, 2);

        if (rand > 0)
        {
            newPos.x += 2f;
        }
        else
        {
            newPos.z += 2f;
        }

    }
}

 

 

 

 

 

Hierarchy뷰에서  PlatformSpawner 오브젝트를 선택하고 작성한 PlatformSpawner 스크립트 공백에 Platform 에는 프리 팹으로 넣은 PlatformP 프리 팹을 넣어주고, Last Platform 에는 박스 오브젝트 복사 모듈인 PlatformP (3)을 넣어줍니다

 

 

자동차의 움직임을 자연스럽게 카메라를 따라가는 스크립트를 만듭니다

Main Camera를 선택하고 CameraFollow 스크립트를 생성하여 Main Camera에 붙입니다 

 

 

 

CameraFollow 스크립트 작성

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

public class CameraFollow : MonoBehaviour
{
    public Transform target;

    Vector3 distance;
    public float smoothValu;

    // Start is called before the first frame update
    void Start()
    {
        // 카메라와 타겟 자동차와의 거리
        distance = target.position - transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        Follow();
    }

    void Follow()
    {
        //현재 카메라 포지션
        Vector3 currentPos = transform.position;

        //현재 타겟 자동차 
        Vector3 targetPos = target.position - distance;

        //currentPos(카메라)가 targetPos(자동차)을 smoothValu 만큼 time 프레임에 맞쳐 뒤따라간다
        transform.position =  Vector3.Lerp(currentPos, targetPos, smoothValu * Time.deltaTime);


    }
}

 

 

 

 

Main Camera를 선택하고  CameraFollow 스크립트 빈칸에 Target -> car1, smoothValu 5를 줍니다 

게임을 실행하여 자동차 움직임의 시점을 따라가는지 확인하고  자동차 도로의 블록이 자동으로 생성되는지 확인합니다

 

 

3D Game 자동차 게임 지그재그 게임만들기 4 로 이동

 

반응형
반응형

스크립트를 작성하여 자동차를 움직여 보겠습니다 

 

Scripts 폴더에 CarCtrl 스크립트를 만들고  car1 오브젝트에 붙입니다  

그리고 잠시 Rigdbody 에서 is Kinematic을 체크합니다 

 

 

 

 

 

 

 

CarCtrl 스크립트 작성

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

public class CarCtrl : MonoBehaviour
{
    public float moveSpeed;
    bool sideMoving = true;
    bool firstInput = true;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (GameManager.instance.gameStarted)
        {
            Move();
            CheckInput();
        }

    }

    void Move()
    {
        //물체의 z축 방향(forward)으로 moveSpeed 만큼 시간 프레임에 곱해준다 
        transform.position += transform.forward * moveSpeed * Time.deltaTime;
    }


    void CheckInput()
    {
        //첫 클릭은 무시하고 리턴한다 
        if (firstInput)
        {
            firstInput = false;
            return;
        }


        //마우스를 왼쪽버튼 클릭하면
        if (Input.GetMouseButtonDown(0))
        {
            ChangeDirection();
        }
    }
    void ChangeDirection()
    {
        //sideMoving 참일때  
        if (sideMoving)
        {
            sideMoving = false;

            //y축 방향으로 90도 회전한다
            transform.rotation = Quaternion.Euler(0, 90, 0);
        }
        else
        {
            sideMoving = true;
            transform.rotation = Quaternion.Euler(0, 0, 0);
        }
    }
}

 

 

 

 

 

 

 

 

 

Move Speed 8을 넣습니다 

그리고 GameManager 스크립트를 만듭니다 

 

Hierachy 뷰에서 GameManager 오브젝트를 만들고 GameManager 스크립트를 붙입니다 

 

 

 

 

 

 

 

 

GameManager 스크립트 작성

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    public bool gameStarted;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (!gameStarted)
        {
            //마우스 클릭하면 차가 움직인다 
            if (Input.GetMouseButtonDown(0))
            {
                GameStart();
            }
        }
    }

    public void GameStart()
    {
        gameStarted = true;
    }


    public void GameOver()
    {
       
    }
}

 

 

 

 

 

 

 

 

 

 

게임을 실행시키고 자동차가  마우스를 클릭하였을때 움직이고 방향이 바뀌는지 확인합니다 

 

 

3D Game 자동차 게임 지그재그 게임 만들기 3

 

 

반응형
반응형

3D 자동차 게임 만들기

자동차가 순간 맞추어지는 도로에 따라 지그재그 움직이는 게임입니다 

 

 

 

가로 뷰로 게임을 실행하므로 

유니티 엔진의 Game 뷰를 1080 X 1920  맞춥니다

 

씬 이름을 Game으로 바꿉니다 

 

메인 카메라를 선택하고 Clear Flags를 Solid Color로 바꿉니다

그리고 Background 색깔을 각자 분위기에 맞게 색깔을 지정합니다

저는 푸른색으로 바꾸었습니다 

 

 

 

 

 

 

Hierarchy 뷰에서  GameObject ->Cube를  생성합니다 

Cube가 Game 뷰에서 생성된 모습을 볼 수 있습니다 

 

 

GameObject 인 Cube의 이름을 Platform이라 하고  Inspector 메뉴에서 Scale 4,1,4로 바꿉니다 

 

 

Main Camera를 선택하고  Scene뷰에서  Platform 오브젝트를 대각선 방향으로 윗면이 보이게 아래 그림처럼 방향을 설정하고  GameObjet-> Align With View를 선택하면 Scen 뷰에서 방향을 맞춘 Camera 방향이 Game 뷰에 맞추어지게 됩니다 

 

그리고 Main Camera를 선택하고 Inspector 메뉴에서 Projection -> Orthographic  

Size -> 7에 맞춥니다  

 

hierarchy에서  Platform을 복사하여 새로운 Platform을 만들고 위치를 z 3 만큼 움직이고 Scale 2,1,2 

를 맞추고 BoxCollider를 생성하고 RigidBody 생성하여  Is Kinematic 체크합니다 

 

 

 

 

그리고  복사한 Platform의 이름을 PlatformP로 바꾸고 Project  Assets 폴더에서  Prefab 폴더를 생성하고 PlatformP를 저장합니다 

 

PlaformP 오브젝트를 3개 더 복사하여 Z 간격을 각각 2 만큼씩 띄웁니다 

 

 

 

car1.FBX
0.04MB

 

car1 FBX 파일을 다운로드하여 유니티 엔진의 Project Assets의 Models 폴더를 만들고 car1 파일을 저장합니다 

 

 

 

 

다운로드한 car1 파일을 선택하고 Model -> Scale Factor 0.01을 합니다 

 

 

Models 파일에 있는 car1 파일을 드래그하여 Hierarchy 뷰 에 갖다 놓고 Scale 0.5 ,0.5, 0.5에 맞춥니다 

 

 

 

car1을 선택하고  Rigidbody 추가하고 BoxCollider를 추가합니다 

 

 

 

게임만들기 2편 보기

 

 

 

반응형
반응형

포토샵에서 작업한 적 캐릭터 이미지를 유니티에 끌어다 Textrue폴더에 넣습니다 

그리고 아래 그림과 같이 그림 Inspector를 바꿉니다 

그리고 Sprite Editor에 들어감니다 

 

Slice를 누르고 Automatic 으로 그림을 나눕니다

그리고 Pivot을 나중에 캐릭터가 움직임에 원활히 할수있도록 위치를 조종합니다  

 

 

 

 

 

빈오브젝트를 만들고 이름을 Enemy 하고 Enemy그림파일을 각각 관절에 맞게 Hierarchy 창에 끌어다 놓고 그림을 인간형에 맞게 맞춥니다 

 

Enemy의 각각의 관절에 Sorting Layer -> Enemy 를 만들고 그림이 위아래가 바뀌지 않도록 합니다 

 

 

 

 

 

Enemy 오브젝트의 Window -> animation -> animation 을 열고 Create new Clip을 Enemy_Run을 만들고 적 캐릭터가 움직이는 애니메이션을 만듭니다 

 

 

Enemy 스크립트를 생성하고 작성합니다 

 

 

 

 

Enemy 스크립트 작성 

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

public class Enemy : MonoBehaviour
{

    //적캐릭터의 생명카운트
    public int health;

    //Player 위치
    [HideInInspector]
    public Transform player;

    //스피드
    public float speed;

    //공격과 재공격의 시간 
    public float timeBetweenAttacks;
    // 데미지
    public int damage;
    private void Start()
    {
        //Player tag 오브젝트를 찾는다
        player = GameObject.FindGameObjectWithTag("Player").transform;
    }

    //공격을 받을때 생명이 줄어드는 함수
    public void TakeDamage(int damageAmount)
    {
        health -= damageAmount;
        if (health <= 0)
        {
            Destroy(gameObject);
        }
    }
}

 

 

 

 

 

 

Enemy 오브젝트를 선택하고 Enemy 스크립트를 붙히고 Tag에 Enemy를 생성하고  Box Collider2 D를 붙이고 isTrigger를 체크하고 Rigidbody2 D에서 Gravity scale을 0으로 하고 Freeze Rotation -> z를 체크합니다 

 

Prefabs에 있는 bullet 프리팹을 선택하여 BulletCtrl 스크립트를 수정합니다 

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

public class BulletCtrl : MonoBehaviour
{
    public float speed;
    public float lifeTime;
    public GameObject explosion;

    public int damage;
   
   
    void Start() 
    {
        // 총알을 시간에 맞게 지운다
        //Destroy(gameObject, lifeTime);
        Invoke("DestroyBullet", lifeTime);
    } 
      void Update() 
    { 
        // 시간프레임과 speed에 따라 y축방향으로 움직인다
        transform.Translate(Vector2.up * speed * Time.deltaTime); 
    }

    //총알을 삭제하고 이팩트를 생성한다 
    void DestroyBullet()
    {
        Destroy(gameObject); 
        Instantiate(explosion, transform.position, Quaternion.identity);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        //적캐릭터 오브젝트와 닿게 되면 이팩트를 생성하고 적캐릭터 함수에 전달하여 생명을 감소시킨다 
        if (collision.tag == "Enemy")
        {
            collision.GetComponent<Enemy>().TakeDamage(damage);
            Instantiate(explosion, transform.position, Quaternion.identity);
            DestroyBullet();
        }
    }


}

 

 

 

 

bulletCtrl 스크립트를 수정하고 Explosion  -> Particle System  speed 20 , LifeTime 3 , Damage1을 넣습니다  

 

 

 

Player 오브젝트를 선택하고 Tag  -> Player를 선택합니다  그리고 player 스크립트를 수정합니다

그리고 Game 을 실행시키면 Enemy 가 총알에 맞고 Enemy Health가 0 이 되면 Enemy가 삭제되는 것을 확인할 수 있습니다  

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
    public float speed;
    private Rigidbody2D rigid;
    private Vector2 moveAmount;
    private Animator anim;

    public float health;

    private void Start() 
    { 
        rigid = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }
    private void Update()
    { 
        //키보드(WSAD)의 좌우상하를 방향을 정의해 준다
        Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"), 
            Input.GetAxisRaw("Vertical")); 
        //움직임방향에 속도를 곱한다
        moveAmount = moveInput.normalized * speed;

    //키보드를 누르면 Player 애니메이터 파라미터를 적용하고 캐릭터를 움직인다
      if (moveInput != Vector2.zero) 
        { 
            anim.SetBool("Run", true); 
        } else 
        { 
            anim.SetBool("Run", false); 
        }

    
    }
    
    private void FixedUpdate() 
    { //움직임 방향에 시간에 따라 리지드 바디 포지션값을 더한다
      rigid.MovePosition(rigid.position + moveAmount * Time.fixedDeltaTime); 
    }

    // Player의 체력을 감소 시킨다
    public void TakeDamage(int damageAmount)
    {
        health -= damageAmount;
        if (health <= 0)
        {
            Destroy(gameObject);
        }
    }
}

    

 

 

 

 

Enemy 오브젝트를 선택하고 Enemy 스크립트를 오브젝트에서 삭제 하고 MeleeEnemy 스크립트를 작성합니다 

 

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

public class MeleeEnemy : Enemy
{
    public float stopDistance;

    private float attackTime;

    public  float attackSpeed;
    private void Update()
    {
        if (player != null)
        {
            //Enemy가 Player의  stopDistance 거리 밖이라면 Player 방향으로 speed 속도로 돌진한다 
            if (Vector2.Distance(transform.position,player.position ) > stopDistance) 
            {
                transform.position =
                    Vector2.MoveTowards(transform.position, player.position, speed * Time.deltaTime);
            }
            else
            {
                //Enemy가 Player의  stopDistance 거리 안쪽에 있으면 attackTime의 딜레이를 주고 공격한다 
                if (Time.time >= attackTime)
                {
                    StartCoroutine(Attack());
                    attackTime = Time.time + timeBetweenAttacks;
                }

            }
        }
    }

    // 적캐릭터의 공격
    IEnumerator Attack()
    {
        player.GetComponent<Player>().TakeDamage(damage);

        Vector2 originalPosition = transform.position;
        Vector2 targetPosition = player.position;

        float percent = 0;

        // 한번 공격할때 공격 가속도와 치고 빠지기를 반복한다 
        while (percent <= 1)
        {
            percent += Time.deltaTime * attackSpeed;
            float formula = (-Mathf.Pow(percent, 2) + percent) * 4;
            transform.position = Vector2.Lerp(originalPosition, targetPosition, formula);
            yield return null;
        }
    }
}

 

 

 

 

 

Enemy 오브젝트의 MeleeEnemy 스크립트를 붙힘니다 

그리고 Health 2 , speed 5 , timeBetweenAttacks 2, damage 1, stopDistance 3, attackSpeed 8을 넣습니다  

 

Inspector 창에서 Player,Enemy 레이어를 추가합니다 

 

 

 

 

 

 

Edit -> ProjectSettings -> Phsics2D  에서 아래 그림과 같이

플레이어 레이어가있는 오브젝트와 적 레이어가 있는 오브젝트가 더 이상 서로 충돌하지 않기 때문에 체크 해제 합니다 

 

 

 

 

 

게임을 실행하면 적 캐릭터가 Player를 따라 다니면서 공격하는 것을 볼 수 있습니다 

Enemy_sheet.png
0.12MB

반응형
반응형

주위 배경을 위해서 Background 텍스쳐 파일을 하이 라키 뷰에 올리고 중앙에 맞춥니다 

그리고 텍스쳐 스케일을 카메라뷰보다 넓게 4,4,1로 맞춥니다  

 

 

새로운 오브젝트를 생성하고 이름을 CameraFollow 라 합니다 

 

CameraFollow 오브젝트의 자식으로 Main Camera를 옮겨다 놓습니다 

CameraFollow 스크립트를 작성합니다 

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

public class CameraFollow : MonoBehaviour
{
    //Player 포지션 
    public Transform playerTransform;

    public float speed;

    public float minX;
    public float maxX;
    public float minY;
    public float maxY;


    // Start is called before the first frame update
    void Start()
    {
        //Player 포지션을 가지고 온다 
        transform.position = playerTransform.position;
    }

    // Update is called once per frame
    void Update()
    {
        if (playerTransform != null)
        {
            //화면의 X축의 최대점과 최소점을 넘지않게 설정
            float clampedX = Mathf.Clamp(playerTransform.position.x, minX, maxX);
            //화면의 Y축의 최대점과 최소점을 넘지않게 설정
            float clampedY = Mathf.Clamp(playerTransform.position.y, minY, maxY);

            //카메라를 clampedX,clampedY 방향으로 speed 속도를 부드럽게 이동
            transform.position = Vector2.Lerp(transform.position,new Vector2(clampedX,clampedY), speed);
        }
        
    }
}

 

 

Player 오브젝트를 드래그하여 Player Transform 빈 공간에 드래그하여 붙이고 speed를 0.12  정도 합니다 

 

CameraFollow오브젝트를 X 쪽으로 -11 만큼 움직입니다 카메라가 배경 보이는 끝선에 맞춤니다 

그리고 Min X 에 -11을 넣습니다 

 

 

CameraFollow를 x축으로 그림영역 최댓값에 맞추고  MaxX를 11을 넣습니다 

 

CameraFollow를 y축으로 그림영역 최솟값에 맞추고  MinY를 -7을 넣습니다 

CameraFollow를 y축으로 그림영역 최댓값에 맞추고  MaxY를 5를 넣습니다 

 

 

하이라키뷰에 Stoper오브젝트 4개를 생성합니다 

 

Stoper 오브젝트에 BoxCollider2D를 붙이고 각각 위치를 배경 끝에 놓습니다 

 

 

Player 오브젝트에 BoxCollider2D 를 붙입니다 

게임을 실행하여 Player의 움직임에 Camera가 따라가고 배경의 보이는 부분까지만 Player가 제한되는 움직임을 보입니다 

 

 

반응형
반응형

포토샵에서 여러 가지 모양을 하얀색으로 그립니다 

바탕을 투명으로 하고 png 파일로 저장합니다 

ㅗㅗ

 

유니티에 Texture 폴더에 저장합니다 

 

 

 

particle 텍스쳐를 선택하고 아래와 같이 속성을 바꾸어줍니다 

그리고 Sprite Editor 를 클릭합니다 

Slice를 누르고 Type을  Automatic으로 하고 Slice 합니다  

 

 

Hierarchy 뷰에서 마우스 오른쪽 버튼을 누르고 Effects -> Paticle System을 생성합니다 

 

 

 

 

생성된 파티클 오브젝트를 선택하고 Rotation 을 0 ,0, 0으로 잡고  Paticle System 속성을 아래 그림과 같이 만듭니다 

 

 

 

Texture 폴더에 저장하였던 그림파일을  Texture Sheet Animation 에 Sprites에 하나씩 대입합니다 

 

Sorting Layer ID 를 새로 만들고 이름을 Effect로 하고 아래 그림과 같이 맨 마지막 위치에 레이어를 놓습니다 

 

 

 

Play  하면 아래 동영상과 같이  이팩트가 터지는 모습을 볼수있습니다 

Particle.unitypackage
0.13MB

반응형
반응형

 

Gun 오브젝트를 선택하고 총알이 나올 수 있는 포인트를 만들어 주어야 합니다 

그래서 새로운 오브젝트를 Create Empty 를 생성하고 자식으로 이름을 sPoint라고 합니다 

GunCtrl 스크립트 수정

아래와 같이 GunCtrl 스크립트를 수정합니다 

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

public class GunCtrl : MonoBehaviour
{
    public GameObject bullet;
    public Transform sPoint;
    public float timeBetweenShots;


    private float shotTime;

    void Update()
    {
        //카메라 스크린의 마우스 거리와 총과의 방향 
        Vector2 direction = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
        //마우스 거리로 부터 각도 계산
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        //축으로부터 방향과 각도의 회전값
        Quaternion rotation = Quaternion.AngleAxis(angle , Vector3.forward);
        transform.rotation = rotation;

        //마우스 왼쪽버튼을 눌렀을때
        if (Input.GetMouseButton(0))
        {
            if (Time.time >= shotTime)
            {
                //총알을 생성한다
                Instantiate(bullet, sPoint.position, Quaternion.AngleAxis(angle - 90,Vector3.forward));
                //재장전 총알 딜레이 
                shotTime = Time.time + timeBetweenShots;
            }
        }
    }
}

 

 

 

 

수정된 GunCtrl 스크립트에 프리팹으로 만들었던 bullet과 Gun 오브젝트의 자식으로 있는 sPoint를 GunCtrl스크립트에 연결합니다 

 

아래 동영상과 같이 총알이 나오는 것을 볼 수 있습니다 

 

bullet 프리팹을 선택하여 BulletCtrl 스크립트를 생성하고 작성합니다 

 

 

 

 

BulletCtrl 스크립트를 작성합니다 

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

public class BulletCtrl : MonoBehaviour
{
    public float speed;
    public float lifeTime;

    // Start is called before the first frame update
    void Start()
    {
        // 총알을 시간에 맞게 지운다 
        Destroy(gameObject, lifeTime);
    }

    // Update is called once per frame
    void Update()
    {
        // 시간프레임과 speed에 따라 y축방향으로 움직인다 
        transform.Translate(Vector2.up * speed * Time.deltaTime);
    }
}

 

 

아래 동영상과 같이 마우스에 따라 총이 움직이고 총알이 마우스에 따라 방향대로 발사되는 것을 확인합니다 

 

반응형
반응형

그림파일을 Texture 폴더에 끌어 옮겨 저장합니다 

 

Gun 텍스쳐타입을 아래 그림과 같이 바꿉니다 

 

 

 

 

Gun 그림파일의 속성에서 Sprite Editor를 들어가서 Pivot 점을 방아쇠 근처로 옮깁니다 

 

Gun 텍스쳐를 Hierachy 에 옮기고  Gun 오브젝트를 선택하여 Sorting Layer를 하나 만들고 order in Layer 1로 합니다 

 

bullet 텍스쳐를 Hierachy로 옮기고 SortingLayer를 Bullet을 만들고 Order in Layer 1로 합니다 

 

 

 

 

Prefabs 폴더를 만들고 드래그하여 프리팹을 만듭니다 

 

Gun 오브젝트를 Player 자식으로 옮기고 Gun 오브젝트에 GunCtrl 스크립트를 생성하여 작성합니다 

 

 

 

 

GunCtrl 스크립트를 작성합니다 

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

public class GunCtrl : MonoBehaviour
{

    void Update()
    {
        //카메라 스크린의 마우스 거리와 총과의 방향 
        Vector2 direction = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
        //마우스 거리로 부터 각도 계산
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        //축으로부터 방향과 각도의 회전값
        Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.forward);
        transform.rotation = rotation;
    }
}

 

 

 

 

 

게임을 실행 시키고 아래 동영상과 같이 마우스 움직임에 따라 총이 움직이는 것을 확인합니다 

Texture2.zip
0.04MB

반응형
반응형

포토샵에서 Player로 사용할 캐릭터를 각 관절을 나눈 후에 파일을 저장합니다 

왜냐하면 그림파일을 유니티에 옮긴 후에 캐릭터를 움직이는 애니메이션을 주기 위함입니다 

그림파일을 PNG 파일로 저장합니다 

 

 

유니티 새프로젝트를 만들고 Assets에 폴더 이름을 Texture 라 하고 그림파일을 임포트 합니다

그림파일을 드래그 하셔서 옮기셔도 파일이 Texture파일에 들어가게 됩니다 

 

 

 

 

 

 

 

그림파일을 안드로이드 환경에서 작업하기 위해서는 그림 파일 설정을 해 주어야 합니다 

아래 그림과 같이 설정값을 바꾸고 Sprite Editor에 들어갑니다 

 

Sprite Editor 에 들어가지 않으면 Window -> Package Manager 세 들어갑니다 

 

 

package Manager에서 2 DSprite를 인스톨합니다 

 

 

 

 

 

 

 

 

그러면 Sprite Ediltor에 들어가서  자동으로 그림파일을 나누는 작업을 합니다 

아래 그림과 같이 Slilce 해주고 Apply 합니다 

 

 

그림파일을 hierarchy 창에 옮긴 후 각각의 그림파일이 보일 수 있게 Sorting Layer를 지정하고 Order in Layer 각각의 값을 줍니다 

 

하이 라키에 빈 오프젝트를 만들고 이름을 Player 하고 그림파일을 자식으로 옮깁니다 

 

 

 

 

 

 

 

 

Player를 선택하고 Window -> Animation->Animation을 선택합니다 

 

Animation 탭에서 Create New Clip.. 을 선택하여  Player_idle 을 만들고 애니메이션을  만듭니다 

 

 

 

 

Player_Run 애니메이션을 만듭니다 

 

 

 

 

Scripts 폴더를 만들고 새로운 Player 스크립트를 만듭니다  

Player 오브젝트에 Player 스크립트를 연결하고 Rigidbody2D 컴포넌트를 생성해줍니다 

그리고 Player 스크립트를 작성합니다 

 

 

 

Player 스크립트 작성

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

public class Player : MonoBehaviour
{
    public float speed;

    private Rigidbody2D rigid;

    private Vector2 moveAmount;

    private void Start()
    {
        rigid = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
        //키보드(WSAD)의 좌우상하를 방향을 정의해 준다   
        Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"),
            Input.GetAxisRaw("Vertical"));

        //움직임방향에 속도를 곱한다
        moveAmount = moveInput.normalized * speed;
    }

    private void FixedUpdate()
    {
        //움직임 방향에 시간에 따라 리지드 바디 포지션값을 더한다 
        rigid.MovePosition(rigid.position + moveAmount * Time.fixedDeltaTime);
    }

}

 

 

키보드에서  상하좌우 키와 WSAD 키보드를 움직이면 캐릭터가 상하 좌우 방향 대로 움직이는 것을 확인합니다 

Player.psd
1.07MB

 

 

 

 

 

Player 오브젝트를 선택하고 Animation -> Animator 를 선택합니다 

 

 

Animator 창이 뜨면 Base Layer 에서 Player_idle과 Player_Run을 서로 Make Transition 하여 연결합니다 

그리고 Parameters 에서 + 버튼을 누르고 Bool을 선택하고 이름을 Run으로 합니다 

 

 

 

Player_idle에서 Player_Run으로 가는 선을 선택하고 Conditions에서 + 추가하고 Run을 선택하고 true 하며 아래 사진과 같이 속성 값을 바꿉니다 

 

Player_Run에서 Player_idle 가는 선을 선택하고  Conditions 에서 + 추가하고 Run 하고 fale 하며 아래와 같이 속성 값을 바꿉니다 

 

 

 

Player 스크립트를 수정 합니다 

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

public class Player : MonoBehaviour
{
    public float speed;

    private Rigidbody2D rigid;

    private Vector2 moveAmount;

    private Animator anim;
    private void Start()
    {
        rigid = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();


    }

    private void Update()
    {
        //키보드(WSAD)의 좌우상하를 방향을 정의해 준다   
        Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"),
            Input.GetAxisRaw("Vertical"));

        //움직임방향에 속도를 곱한다
        moveAmount = moveInput.normalized * speed;

        //키보드를 누르면 Player 애니메이터 파라미터를 적용하고 캐릭터를 움직인다 
        if (moveInput != Vector2.zero)
        {
            anim.SetBool("Run", true);
        }
        else
        {
            anim.SetBool("Run", false);
        }
    }

    private void FixedUpdate()
    {
        //움직임 방향에 시간에 따라 리지드 바디 포지션값을 더한다 
        rigid.MovePosition(rigid.position + moveAmount * Time.fixedDeltaTime);
    }

}

 

 

 

아래 화면과 같이 캐릭터가 평상시 Player_idle 에서 키보드 누르면 Player_Run으로 애니메이션 되는 것을 볼 수 있습니다 

 

반응형
반응형

탱크 게임 만들기 영상

 

 

Textrue 파일

tankgameTexure.unitypackage
0.06MB

유니티 프로젝트 파일을 만들고  Scenes 파일을 열여 씬 이름을 TankGame으로 바꿉니다 

 

 

 

Texture파일을 만들고 Textrure 파일을 임포트 합니다  파일을 유니티로 끌어오거나 아니면 Texture 폴더를 선택하고 마우스 오른쪽 버튼을 누르고 import package -> Custorm Package를 선택하여 다운로드한 Textrure 파일을 선택하면 됩니다 

 

Game 화면을 아래 그림과 같이 Type ->Filxed Resolution 을 1024 476으로 맞춥니다

 

 

 

하이 라키 뷰에서 BackGround 오브젝트를 만들고 Add Compnent를 선택하여 Sprite Renderer를 선택하고 그림 background를 넣고 SortingLayer를 Background를 생성하여 Order in Layer를 0으로 맞춥니다 

 

Game 뷰에서 아래 그림과 같은 화면이 됩니다 

 

 

하이 라키 뷰에서 Player 오브젝트를 만들고 Tag와 Layer를 Player 로 합니다 

 

 

 

Player 오브젝트 자식으로 Tank_body 오브젝트를 만들고 그림 tank_body를 넣고 Sorting Layer를 Add Sorting Layer 를 누르고   Player 생성하고  Order in Layer 0으로 맞춥니다 

Sorting Layer 순서입니다 

 

Player 자식으로 Tank_top 오브젝트를 만들고 Sprite Renderer를 생성하여 tank_cannon을 넣고 SortingLayer -> Player

Order in Layer ->1로 합니다 

 

 

 

 

하이 라키 뷰에서 Player 오브젝트를 선택하고 Rigidbody2 D를 붙이고 Gravity Scale ->0으로 하고 Box Collider2D를 붙입니다

 

Scripts 폴더를 만들고 Player 스크립트를 생성하여 작성합니다 

 

 

 

Player Scripts작성

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

public class Player : MonoBehaviour
{
    public float speed; //탱크 스피드

    private Rigidbody2D rb;

    private Vector2 moveAmount;//탱크 움직임 방향

    public float health;//탱크 체력

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

    void Update()
    {
        //탱크의 방향을 키보드 ad(좌우) ws(위,아래) 설정
        Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        //탱크방향에 속도를곱한다
        moveAmount = moveInput.normalized * speed;
    }

    //탱크가 움직일수 있도록 프레임을 곱한다
    private void FixedUpdate()
    {
        rb.MovePosition(rb.position + moveAmount * Time.fixedDeltaTime);
    }

    //적의 공격을 받을 때 체력을 감소시킨다
    public void TakeDamage(int damageAmount)
    {
        health -= damageAmount;
        if (health <= 0)
        {
            Destroy(gameObject);
        }
    }
}

 

 

작성한 Player스크립트를 Player 오브젝트에 붙입니다 

 

 

TankTopCtrl 스크립트를 생성하고 작성합니다 

 

 

 

TankTopCtrl스크립트 작성

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

public class TankTopCtrl : MonoBehaviour
{
    void Update()
    {
        Vector2 direction = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        Quaternion rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);
        transform.rotation = rotation;
    }
}

 

Player 자식으로 있는 Tank_top 오브젝트를 선택하고 TankTopCtrl 스크립트를 붙힘니다 

 

 

 

 

아래 동영상과 같이 탱크가 움직이고 마우스 움직임에 따라 포탑이 움직이면 됩니다 

 

반응형
반응형

하이 라키 뷰에서 마우스 오른쪽 버튼을 누르고 UI -> Text를 만듭니다 

3개를 만들어서 하나는 이름을 LiveText 하고 화면 오른쪽 위에 놓고 나머지 두 개는 GameOver, Success 라 하고 중앙에 놓습니다 

 

 

 

 

 

 

 

 

Scripts 폴더에 GameManager 를 만들고 스크립트를 작성합니다 

 

GameManager 스크립트 작성

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

public class GameManager : MonoBehaviour
{
    //남은 생명력
    public int lives = 3;

    //벽돌갯수
    public int bricks = 32;

    //게임 재시작 시간
    public float resetDelay;


    public Text txtLives = null;
    public GameObject gameOver = null;
    public GameObject success = null;
    public GameObject bricksPrefab;
    public GameObject paddle = null;
    public GameObject DeathParticles = null;
    public static GameManager Instance = null;

    private GameObject clonePaddle = null;


    void Start()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else if (Instance != this)
        {
            Destroy(gameObject);
        }

        SetUp();
    }

    //게임 시작시 패들과 벽돌을 불러온다
    public void SetUp()
    {
        if (paddle != null)
        {
            clonePaddle = Instantiate(paddle, paddle.transform.position, Quaternion.identity) as GameObject;
        }

        if (bricksPrefab != null)
        {
            Instantiate(bricksPrefab, bricksPrefab.transform.position, Quaternion.identity);
        }
    }


    //게임 재시작 설정
    void CheckGamOver()
    {
        //벽돌을 깻을 때
        if (bricks < 1)
        {
            if (success != null)
            {
                success.SetActive(true);
                //시간을 2.5배 빠르게
                Time.timeScale = 2.5f;
                Invoke("Reset", resetDelay);
            }
        }

        //생명력을 소진했을때
        if (lives < 1)
        {
            if (gameOver != null)
            {
                gameOver.SetActive(true);
                //시간을 0.25배 느리게 
                Time.timeScale = 0.25f;
                Invoke("Reset", resetDelay);
            }
        }

    }

    private void Reset()
    {
        //평균타임을 설정합니다
        Time.timeScale = 1f;

        Application.LoadLevel(Application.loadedLevel);
    }

    //생명력을 잃게되면 발생
    public void LoseLife()
    {
        lives--;

        if (txtLives != null)
        {
            txtLives.text = "남은 생명 : " + lives;
        }

        //파티클 발생
        if (DeathParticles != null)
        {
            Instantiate(DeathParticles, clonePaddle.transform.position, Quaternion.identity);
        }

        //패들 없애기
        Destroy(clonePaddle.gameObject);

        //딜레이 시간 만큼 지나면 패들 생산
        Invoke("SetupPaddle", resetDelay);
        CheckGamOver();
    }

    //패들 생산 
    private void SetupPaddle()
    {
        clonePaddle = Instantiate(paddle, transform.position, Quaternion.identity) as GameObject;
    }

    public void DestroyBrick()
    {
        bricks--;
        CheckGamOver();
    }

}

 

 

 

 

 

 

 

작성한 스크립트를 하이라키뷰에 GameManager 오브젝트를 만들 과 스크립트를 붙입니다

 

하이 라키 뷰에 오른쪽 마우스 버튼을 클릭하여 Effects ->Particle System을 생성시켜 이름을 hitParticle이라고 합니다 

그리고 아래 그림과 같이 속성을 바꿉니다

 

 

 

 

 

 

 

 만든  이팩트의 모습

 

이팩트를 프리 팹으로 만듭니다

 

Scripts 폴더에  BlocklCtrl 스크립트를 만듭니다

 

 

 

 

 

 

 

 

BlockCtrl 스크립트 작성

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

public class BlockCtrl : MonoBehaviour
{

    public GameObject brickParticle ;

    //공과의 충동할때
    private void OnCollisionEnter(Collision other)
    {
        if (brickParticle != null)
        {
            //이팩트를  발생시킨다
            Instantiate(brickParticle, transform.position, Quaternion.identity);

            GameManager.Instance.DestroyBrick();
            Destroy(gameObject);
        }

    }
}

 

block 오브젝트 을 열고 각각 32개 블록 Cube에 스크립트를 붙이고 BlockCtrl 스크립트의 Brick Particle에 hitParticle을 붙입니다

 

 

 

 

 

 

 

하이 라키 뷰에 있는 프리 팹들을 Overrides 합니다 

 

Prefabs 폴더에 있는 hitParticle을 Ctrl + D를 눌러서 복사합니다 

그리고 이름을 DethParticle이라고 합니다 

 

DethParticle의 Material을 red로 바꿉니다 

 

하이 라키 뷰에 GameOver, Success 텍스쳐를 Disable(안 보이게)합니다 

 

Scripts폴더에 DeathZone 스크립트를 만들고 작성합니다 

 

 

 

 

 

 

 

DeathZone 스크립트 작성

 

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

public class DeathZone : MonoBehaviour
{
    private void OnTriggerEnter()
    {
        GameManager.Instance.LoseLife();
    }
}

 

하이 라키 뷰에서  화면 맨 아래 있는 오브젝트인 Cube (3)을 선택하여 DeathZone 스크립트를 붙입니다 그리고 

is Trigger를 체크합니다 

 

 

하이 라키 뷰에서 GameManager 오브젝트를 선택하고 아래 그림과 같이 속성을 바꾸고 텍스쳐와 프리 팹을 연결합니다 

 

 

 

 

게임을 실행하여 정상으로 게임이 잘되는지 확인합니다 

 

 

작업 파일을 올려놓습니다 

참고자료가 조그 미나 마 도움이 되었으면 합니다 

부자 되시고 항상 복 받으세요 ㅎㅎ

BreakGame_Export3.unitypackage
0.06MB

 

반응형
반응형

 

하이 라키 뷰에 있는 Paddler과 Sphere를 동시에 잡고  Rigidbody  컴포넌트를 추가합니다

그리고 IsKinematic을 체크합니다

 

BallCtrl 스크립트를 생성하고 스크립트를 작성합니다 

 

 

 

BallCtrl 스크립트 작성

 

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

public class BallCtrl : MonoBehaviour
{
    //볼의 가속 속도
    public float BallInitialVelocity = 300f;

    //리지드 바디
    private Rigidbody ballRigidBody = null;

    //볼플레이 선택여부
    private bool isBallInPlay = false;

    private void Awake()
    {
        ballRigidBody = GetComponent<Rigidbody>();
    }
    void Update()
    {
        //마우스 오른쪽 키를 누르면 볼에 가속도를 준다 
        if (Input.GetButtonDown("Fire1") && !isBallInPlay)
        {
            transform.parent = null;
            isBallInPlay = true;
            ballRigidBody.isKinematic = false;
            ballRigidBody.AddForce(new Vector3(BallInitialVelocity, BallInitialVelocity, 0f));
        }
    }
}

 

 

BallCtrl 스크립트를 Sphere 오브젝트에 붙입니다 

 

 

게임을 실행시키면 마우스 오른쪽 버튼을 누르면 구슬이 튕겨져 블록으로 나가는 모습을 볼 수 있습니다 

 

반응형
반응형

아래 그림처럼 벽돌을 깨는 고전 게임을 만들어 보겠습니다 

옛날에 알카노이드 란 이름으로 나왔던 게임을 조금 흉내 내어 보겠습니다 

 

게임 화면을 576 : 1024 로 맞춥니다 

 

 

 

하이라키뷰에 있는 메인 카메라를 선택하고  Projection -> orthographic 으로 설정합니다

 

Assets에  materials폴더를 만들고 Create -> Physic Material을 선택하여 생성합니다 

 

 

 

Physic Material 의 속성을 아래 그림과 같이 바꿉니다

 

하면에 4개의 큐브를 만들 과 화면 태 투리에 큐브가 보일 정도로 위치를 합니다 

 

게임 화면에서 보일 때 밑에 있는 큐브는 약간 안 보이게 합니다 

 

 

 

네 개의 큐브를 선택하고  Box Collider -> Material에 있는 부분을 new Physic Material을 드래그하여 올려놓습니다

 

 

그리고 하이 라키 뷰에 빈 오브젝트를 만들고 이름을 block이라고 하고 자식으로 3D Object ->Cube를 32 개를 생성하여 아래 그림과 같이 4개의 큐브 안에 들어오게 하여 정렬을 시킵니다 

 

32개의 큐브를 선택하고  Box Collider -> Material에 있는 부분을 new Physic Material을 드래그하여 올려놓습니다

 

 

 

block 오브젝트를 선택하여 Prefabs 폴더를 만들어 거기에 드래그하여 프리 팹을 만듭니다

하이 라키 뷰에 3D Object ->Cube를 생성하고 이름을 Paddle로 하고 Transform을 아래 그림과 같이 하고  Box Collider -> Material에 있는 부분을 new Physic Material을 드래그하여 올려놓습니다

 

하이 라키 뷰에3D Object ->Sphere를 생성하고  Transform을 아래 그림과 같이 하고  Box Collider -> Material에 있는 부분을 new Physic Material을 드래그하여 올려놓습니다

 

Sphere를 Paddle의 자식으로 놓습니다

 

 

 

Paddle을 움직일 PaddleCtrl 스크립트를 생성하여 스크립트를 작성합니다

 

PaddleCtrl 스크립트 작성

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

public class PaddleCtrl : MonoBehaviour
{
    public float PaddleSpeed = 1f;//Paddle의 이동 스피드
    private Vector3 playerPos = new Vector3(0f, -3.5f, 0f);//Paddle의 초기 위치

    private void Update()
    {
        //Paddle 좌우 이동
        float xPos = transform.position.x + (Input.GetAxis("Horizontal") * PaddleSpeed);

        // Paddle 에이동 제한을 준다 
        playerPos = new Vector3(Mathf.Clamp(xPos, -2f, 2f), -3.5f, 0f);
        transform.position = playerPos;
    }
}

 

 

 

 

Materials 폴더를 선택하고 Create -> Material  두 개 만듭니다 

 

 

 

green 메터리얼은 Paddle에 붙이고 red는 Sphere에 붙힘니다 

 

작성한 PaddleCtrl을 Paddle 오브젝트에 붙히고 Paddle 오브젝트를 프리 팹으로 만듭니다

 

 

 

 

 

게임을 실행하면 Paddle  좌우 키를 움직일 때 방향대로 Paddle 이 움직이는 것을 볼 수 있습니다

 

반응형
반응형

유니티 UI를 활용하여 게임 시간과 그림 맞춤 카드 횟수 및 스테이지 시간을 화면에 출력하겠습니다

먼저 하이라키 뷰에서 오른쪽 마우스 키를 누르고 UI ->Text를 아래 그림과 같이 5개 생성합니다 

 

 

 

아래 화면과 같이 텍스트를 이동합니다 보통 텍스트 폰트를 변경하고 알맞은 색상을 선택하여야 하는데 그 부분은 생략하고 대략 텍스트 위치와 화면에 나타내는 것만 보이도록 하겠습니다

 

 

GameManager 스크립트를 수정합니다

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

public class GameManager : MonoBehaviour
{

    //게임 전체 시간, 스테이지 시간 , 스테이지 번호 , 맞춘 카드 의 숫자 
    public Text totalTimeText, stageTimeText, stageText, hitText, stageNumText;

    //클릭한 카드 번호
    static public int cardNum;

    //직전의 카드 번호
    int lastNum = 0;

    //스테이지의 전체 카드수
    int cardCnt;

    //카드 클릭 횟수
    int hitCnt = 0;

    //스테이지 번호
    static public int stageNum = 1;

    //스테이지 수
    int stageCnt = 6;

    //카드 배열 카드 섞기용
    int[] arCards = new int[50];

    //게임 시작 시간
    float startTime;

    // 스테이지 경과 시간
    float stageTime;

    //열거형 의 자료를 설정하는 정의문 나열의 자료는 0,1,2....의 값이 할당된다
    public enum STATE
    {
        START, HIT, WAIT, IDLE, CLEAR
    };

    static public STATE state = STATE.START;

    // Start is called before the first frame update
    void Start()
    {

        
        Screen.orientation = ScreenOrientation.LandscapeRight;
        Screen.sleepTimeout = SleepTimeout.NeverSleep;

        startTime = stageTime = Time.time;

        //시간초기화
        startTime = stageTime = Time.time;
        //스테이지 만들기
        // StartCoroutine(MakeStage());
    }

    void Update()
    {

        int time1 = (int)(Time.time - startTime);
        int time2 = (int)(Time.time - stageTime);

        //게임 전체 시간, 스테이지 시간 , 스테이지 번호 , 맞춘 카드 의 숫자 를 화면에 출력한다
        totalTimeText.text = "Total time : " + time1;
        stageTimeText.text = "Stage Time : " + time2;
        stageText.text = "Stage : " + stageNum;
        hitText.text = "Hit : " + hitCnt;

        switch (state)
        {
            //스테이지 만들기
            case STATE.START:
                StartCoroutine(MakeStage());
                break;

                // 같은 그림인지 판정
            case STATE.HIT:

                CheckCard();
                break;

                //스테이지를 클리어 하고 다음 스테이지를 만듬
            case STATE.CLEAR:
                StartCoroutine(StageClear());
                break;

        }

        //게임중지
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }

    }

    void CheckCard()
    {
        state = STATE.WAIT;

        //첫 번째 카드
        if (lastNum == 0)
        {
            //현재 카드 보존
            lastNum = cardNum;
            state = STATE.IDLE;
            return;
        }

        //이미지 찾기
        int img1 = (cardNum + 1) / 2;
        int img2 = (lastNum + 1) / 2;

        //다른 카드 이면  카드를 닫게한다
        if (img1 != img2)
        {
            StartCoroutine(CloseTwoCards());

            lastNum = 0;
            state = STATE.IDLE;
            return;
        }


        //같은 카드면 
        hitCnt += 2;

        //카드가 모두 열리면 스테이지를 클리어 한다
        if (hitCnt == cardCnt)
        {
            state = STATE.CLEAR;
            return;
        }

        //카드가 남아있으면 다른 카드를 조사한다
        lastNum = 0;
        state = STATE.IDLE;
    }

    //카드 닫기
    IEnumerator CloseTwoCards()
    {
        //테그로 카드 찾기
        GameObject card1 = GameObject.FindWithTag("card" + lastNum);
        GameObject card2 = GameObject.FindWithTag("card" + cardNum);


        //카드 닫기
        yield return new WaitForSeconds(0.2f);
        card1.SendMessage("CloseCard", SendMessageOptions.DontRequireReceiver);
        card2.SendMessage("CloseCard", SendMessageOptions.DontRequireReceiver);

    }

    //스테이지를 클리어 한다
    IEnumerator StageClear()
    {
        state = STATE.WAIT;

        yield return new WaitForSeconds(2);


        //스테이지 카드 제거
        for (int i = 1; i <= cardCnt; i++)
        {
            GameObject card = GameObject.FindWithTag("card" + i);
            Destroy(card);
        }

        //다음 스테이지 번호
        ++stageNum;
        if (stageNum > stageCnt)
        {
            Application.LoadLevel("GameStart");
            //return;
        }

        //스테이지 초기화
        stageTime = Time.time;
        lastNum = 0;
        hitCnt = 0;

        state = STATE.START;
    }
    IEnumerator MakeStage()
    {
        //현재 작업중으로 설정
        state = STATE.WAIT;

        StartCoroutine(ShowStageNum());

        //시작카드의 x좌표
        float sx = 0;

        //시작카드의 z좌표
        float sz = 0;

        
        SetCardPos(out sx, out sz);

        //카드섞기
        ShuffleCard();


        //시작카드의 번호
        int n = 1;

        //카드배열 읽기 배열의 1행을 읽고 변수 t에 할당한다
        string[] str = StageSet.stage[stageNum - 1];

        //배열의 행의 수만큼 반복
        foreach (string t in str)
        {
            // 각행의 문자열을 단일 문자 배열로 변환(문자열 좌우의 공백 제거) , 변수t의 자우 공백을 제거(Trim)하고 단일 문자배열로 변환
            char[] ch = t.Trim().ToCharArray();

            //카드의 x축 좌표
            float x = sx;

            //1행의 문자열 길이만큼 반복
            //배열의 ch의 한문자를 읽고 변수 c에 할당한다
            foreach (char c in ch)
            {
                switch (c)
                {

                    //맵의 내용이 * 이면 그위치에 카드 만들어서 배치
                    case '*':
                        //카드 만들기
                        GameObject card = Instantiate(Resources.Load("Prefab/Card")) as GameObject;

                        //카드 좌표설정
                        card.transform.position = new Vector3(x, 0, sz);

                        //태그 달기
                        //card.tag = "card" + n++;

                        
                        //섞인카드
                        card.tag = "card" + arCards[n++];
                        x++;
                        break;

                        //빈칸 처리
                    case '.':
                        x++;
                        break;


                        //반 칸 공백처리
                    case '>':
                        x += 0.5f;
                        break;


                        //반 줄 행간 처리
                    case '^':
                        sz += 0.5f;
                        break;
                }

                //카드를 표시한 후에는 지연 시간을 두어 카드가 배치되는 과정이 보이도록함
                if (c == '*')
                {
                    yield return new WaitForSeconds(0.03f);

                }
            }

            //한줄 아래로 이동
            sz--;
        }
        //입력 대기중으로 설정
        state = STATE.IDLE;
    }

    //카드의 시작 위치 계산
    void SetCardPos(out float sx, out float sz)
    {
        //가로 카드수 반 칸 공백 포함
        float x = 0;

        //세로 행수 반줄 행간 포함
        float z = 0;

        //가로 카드 최대수
        float maxX = 0;

        //스테이지 전체 카드수
        cardCnt = 0;

        //카드 배열 조사 맵 배열 을 읽음
        string[] str = StageSet.stage[stageNum - 1];

        //행의 수만큼 반복
        for (int i = 0; i < str.Length; i++)
        {
            //1행읽기
            string t = str[i].Trim();

            //각행의 카드수
            x = 0;

            //각행의 글자 수만큼 반복
            for (int j = 0; j < t.Length; j++)
            {
                //문자열(string)은 단일 문자(char)의 배열로 취급할수 있음
                switch (t[j])
                {
                    case '.':
                    case '*':

                        //카드배치에 필요한 공간
                        x++;
                        if (t[j] == '*')
                        {
                            //전체 카드수
                            cardCnt++;
                        }
                        break;
                    case '>':
                        x += 0.5f;
                        break;
                    case '^':
                        z -= 0.5f;
                        break;

                }
            }

            //각 행의 최대 카드수 계산
            if (x > maxX)
            {
                maxX = x;
            }
            //전체 행의 수
            z++;
        }
        //카드 가로 시작 위치
        sx = -maxX / 2;
        sz = (z - 1) / 2;

        //  StartCoroutine(CardOpen(cardCnt));
    }

    void ShuffleCard()
    {
        for (int i = 1; i <= cardCnt; i++)
        {
            arCards[i] = i;
        }
        //return;

        //카드섞기 15회정도 반복
        for (int i = 1; i <= 15; i++)
        {
            //임의의 난수를 두개 만든다
            int n1 = Random.Range(1, cardCnt + 1);
            int n2 = Random.Range(1, cardCnt + 1);

            //교환
            int t = arCards[n1];//배열의 값을 바꾼다
            arCards[n1] = arCards[n2];
            arCards[n2] = t;

            //StartCoroutine(CardOpen(arCards[n1]));
        }
    }

    //스테이지 시작시 스테이지 번호를 보여준다
    IEnumerator ShowStageNum()
    {
        stageNumText.text = "STATE " + stageNum;

        yield return new WaitForSeconds(2f);

        stageNumText.text = "";
    }
}

 

 

 

 

GameManager 스크립트를 선택하고  하이라키 뷰에서 생성한 텍스트를 아래 그림과 같이 드래그하여 붙입니다

그리고 CardCtrl 스크립트를 선택하고 수정합니다

 

CardCtrl 스크립트 수정

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

public class CardCtrl : MonoBehaviour
{
    //이미지 번호
    int imgNum = 1;

    //카드 뒷면 이미지 번호
    int backNum = 1;

    // 오픈된 카드의 판별여부 
    bool isOpen = false;

    Animator anim;


    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();

        CardView();
    }
    
    //스테이지 첫 시작할때 전체 그림을 보여준다
    void CardView()
    {
        int cardNum = int.Parse(transform.tag.Substring(4));

        imgNum = (cardNum + 1) / 2;
        anim.Play("aniOpen");

        GameManager.cardNum = cardNum;

        StartCoroutine(CardCloseStart());
    }
    IEnumerator CardCloseStart()
    {
        yield return new WaitForSeconds(2f);
        anim.Play("aniClose");
    }


    void Update()
    {
        //왼쪽 마우스버튼 클릭 모바일에선 터치 
        if (Input.GetButtonDown("Fire1") && GameManager.state == GameManager.STATE.IDLE)
        {
            CheckCard();
        }
    }

    //카드체크
    void CheckCard()
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        //터치한 카드 식별
        if (Physics.Raycast(ray, out hit, Mathf.Infinity))
        {
            string tag = hit.transform.tag;
            if (tag.Substring(0, 4) == "card")
            {
                //터치한 카드의 OpenCard()함수 실행
                hit.transform.SendMessage("OpenCard", SendMessageOptions.DontRequireReceiver);
            }
        }
    }
    void OpenCard()
    {
        //열린 카드는 처리 없음
        if (isOpen) return;

        //열린 카드 참거짓 판정
        isOpen = true;

        // 카드 번호 Substring() 문자열의 일부분을 추출하는 함수 , 카드 번호(tag)는 card0~card32 있으므로 문자4번째부터(card~) 끝까지 추출한다
        int cardNum = int.Parse(transform.tag.Substring(4));

        //이미지 번호 카드 두장에 하나씩 같은 이미지를 할당하므로 이미지 번호는 (카드번호 +1)/2 로 구한다 정수 /정수 = 정수 이므로 소수 이하는 자동으로 잘림
        imgNum = (cardNum + 1) / 2;
        //카드 애니메이션 실행
        anim.Play("aniOpen");

        GameManager.cardNum = cardNum;
        GameManager.state = GameManager.STATE.HIT;

    }
    void CloseCard()
    {
        anim.Play("aniClose");
        isOpen = false;
    }
    //카드 앞면을 이미지을 가지고 온다
    void ShowImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("card" + imgNum) as Texture2D;
    }


    //카드 뒷면 이미지을 가지고 온다
    void HideImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("back" + backNum) as Texture2D;
    }
}

 

 

 

Main Camera 오브젝트를 선택하고 Clear Flags -> Depth only로 바꾸고  Depth 값을 1 합니다

 

그리고 MaskCamera를 선택하고 Active를 체크합니다

 

 

게임 씬 이름을 MainGame으로 바꾸고 

GameStart 신을 새로 생성합니다 

그리고 GameStart 씬으로 들어갑니다

MainCamera를 선택하고 Clear Flages -> Solid Color로 바꿉니다

 

 

Button을 생성하여 게임 스타트 이름을 하고 text를 생성하여 두니 회전 카드 게임 이란 타이틀을 넣습니다 

 

GameStart 스크립트를 생성합니다 

 

GameStart 스크립트 작성

 

 

GameStart 스크립트를  Button에 붙이고 On Click()에서 GameStart -> GameStartScene() 함수를 연결합니다 

 

File -> Build Settings를 들어가서 폴더 Scenes에 있는 GameStart와 MainGame을 드래그하여 Build Settings에 올립니다

 

 

 

게임을 실행하여 텍스트와 게임이 잘 진행되는지 확인합니다 

 

 

반응형
반응형

카드 판정에 따른 게임의 상태를 표시하고 카드가 모두 열리면 다음 스테이지로 이동하는 프로그램을 만들어 보겠습니다

먼제 GameManager 스크립트를 열고 수정합니다

 

GameManager 스크립트 수정

게임의 상태를 설정할 수 있는 enum 열거형의 자료를 설정하고 Star() 함수에 모바일 기기를 위한 설정을 추가합니다

초기의 스테이지 만드는 문장도 삭제합니다

그리고 Update() 함수에 state 값을 바꾸어서 반복 호출하지 않도록 MakeStage() 함수에 처음 state = STATE.WAIT을 추가하고 마지막에 state = STATE.IDLE을 추가합니다.

 

또한 카드 판정할 수 있는 CheckCard() 함수를 추가하여  카드를 판정하고 카드가 서로 다르면 CloseTwoCards() 함수를 추가하여 카드를 닫습니다

그래서 스테이지 카드가 모두 열리면 StageClear() 함수를 추가하여 스테이지를 클리어하고 다음 스테이지로 넘어가는 함수를 만듭니다

 

 

 

GameManager 스크립트 수정

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

public class GameManager : MonoBehaviour
{
    //클릭한 카드 번호
    static public int cardNum;

    //직전의 카드 번호
    int lastNum = 0;

    //스테이지의 전체 카드수
    int cardCnt;

    //카드 클릭 횟수
    int hitCnt = 0;

    //스테이지 번호
    static public int stageNum = 1;

    //스테이지 수
    int stageCnt = 6;

    //카드 배열 카드 섞기용
    int[] arCards = new int[50];

    //게임 시작 시간
    float startTime;

    // 스테이지 경과 시간
    float stageTime;

    //열거형 의 자료를 설정하는 정의문 나열의 자료는 0,1,2....의 값이 할당된다
    public enum STATE
    {
        START, HIT, WAIT, IDLE, CLEAR
    };

    static public STATE state = STATE.START;

    // Start is called before the first frame update
    void Start()
    {

        
        Screen.orientation = ScreenOrientation.LandscapeRight;
        Screen.sleepTimeout = SleepTimeout.NeverSleep;

        startTime = stageTime = Time.time;

        //시간초기화
        startTime = stageTime = Time.time;
        //스테이지 만들기
        // StartCoroutine(MakeStage());
    }

    void Update()
    {
        switch (state)
        {
            //스테이지 만들기
            case STATE.START:
                StartCoroutine(MakeStage());
                break;

                // 같은 그림인지 판정
            case STATE.HIT:

                CheckCard();
                break;

                //스테이지를 클리어 하고 다음 스테이지를 만듬
            case STATE.CLEAR:
                StartCoroutine(StageClear());
                break;

        }

        //게임중지
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }

    }

    void CheckCard()
    {
        state = STATE.WAIT;

        //첫 번째 카드
        if (lastNum == 0)
        {
            //현재 카드 보존
            lastNum = cardNum;
            state = STATE.IDLE;
            return;
        }

        //이미지 찾기
        int img1 = (cardNum + 1) / 2;
        int img2 = (lastNum + 1) / 2;

        //다른 카드 이면  카드를 닫게한다
        if (img1 != img2)
        {
            StartCoroutine(CloseTwoCards());

            lastNum = 0;
            state = STATE.IDLE;
            return;
        }


        //같은 카드면 
        hitCnt += 2;

        //카드가 모두 열리면 스테이지를 클리어 한다
        if (hitCnt == cardCnt)
        {
            state = STATE.CLEAR;
            return;
        }

        //카드가 남아있으면 다른 카드를 조사한다
        lastNum = 0;
        state = STATE.IDLE;
    }

    //카드 닫기
    IEnumerator CloseTwoCards()
    {
        //테그로 카드 찾기
        GameObject card1 = GameObject.FindWithTag("card" + lastNum);
        GameObject card2 = GameObject.FindWithTag("card" + cardNum);


        //카드 닫기
        yield return new WaitForSeconds(0.2f);
        card1.SendMessage("CloseCard", SendMessageOptions.DontRequireReceiver);
        card2.SendMessage("CloseCard", SendMessageOptions.DontRequireReceiver);

    }

    //스테이지를 클리어 한다
    IEnumerator StageClear()
    {
        state = STATE.WAIT;

        yield return new WaitForSeconds(2);


        //스테이지 카드 제거
        for (int i = 1; i <= cardCnt; i++)
        {
            GameObject card = GameObject.FindWithTag("card" + i);
            Destroy(card);
        }

        //다음 스테이지 번호
        ++stageNum;
        if (stageNum > stageCnt)
        {
            //Application.LoadLevel("GameStart");
            //return;
        }

        //스테이지 초기화
        stageTime = Time.time;
        lastNum = 0;
        hitCnt = 0;

        state = STATE.START;
    }
    IEnumerator MakeStage()
    {
        //현재 작업중으로 설정
        state = STATE.WAIT;

        //시작카드의 x좌표
        float sx = 0;

        //시작카드의 z좌표
        float sz = 0;

        
        SetCardPos(out sx, out sz);

        //카드섞기
        ShuffleCard();


        //시작카드의 번호
        int n = 1;

        //카드배열 읽기 배열의 1행을 읽고 변수 t에 할당한다
        string[] str = StageSet.stage[stageNum - 1];

        //배열의 행의 수만큼 반복
        foreach (string t in str)
        {
            // 각행의 문자열을 단일 문자 배열로 변환(문자열 좌우의 공백 제거) , 변수t의 자우 공백을 제거(Trim)하고 단일 문자배열로 변환
            char[] ch = t.Trim().ToCharArray();

            //카드의 x축 좌표
            float x = sx;

            //1행의 문자열 길이만큼 반복
            //배열의 ch의 한문자를 읽고 변수 c에 할당한다
            foreach (char c in ch)
            {
                switch (c)
                {

                    //맵의 내용이 * 이면 그위치에 카드 만들어서 배치
                    case '*':
                        //카드 만들기
                        GameObject card = Instantiate(Resources.Load("Prefab/Card")) as GameObject;

                        //카드 좌표설정
                        card.transform.position = new Vector3(x, 0, sz);

                        //태그 달기
                        //card.tag = "card" + n++;

                        
                        //섞인카드
                        card.tag = "card" + arCards[n++];
                        x++;
                        break;

                        //빈칸 처리
                    case '.':
                        x++;
                        break;


                        //반 칸 공백처리
                    case '>':
                        x += 0.5f;
                        break;


                        //반 줄 행간 처리
                    case '^':
                        sz += 0.5f;
                        break;


                }

                //카드를 표시한 후에는 지연 시간을 두어 카드가 배치되는 과정이 보이도록함
                if (c == '*')
                {
                    yield return new WaitForSeconds(0.03f);

                }
            }

            //한줄 아래로 이동
            sz--;
        }
        //입력 대기중으로 설정
        state = STATE.IDLE;
    }

    //카드의 시작 위치 계산
    void SetCardPos(out float sx, out float sz)
    {
        //가로 카드수 반 칸 공백 포함
        float x = 0;

        //세로 행수 반줄 행간 포함
        float z = 0;

        //가로 카드 최대수
        float maxX = 0;

        //스테이지 전체 카드수
        cardCnt = 0;

        //카드 배열 조사 맵 배열 을 읽음
        string[] str = StageSet.stage[stageNum - 1];

        //행의 수만큼 반복
        for (int i = 0; i < str.Length; i++)
        {
            //1행읽기
            string t = str[i].Trim();

            //각행의 카드수
            x = 0;

            //각행의 글자 수만큼 반복
            for (int j = 0; j < t.Length; j++)
            {
                //문자열(string)은 단일 문자(char)의 배열로 취급할수 있음
                switch (t[j])
                {
                    case '.':
                    case '*':

                        //카드배치에 필요한 공간
                        x++;
                        if (t[j] == '*')
                        {
                            //전체 카드수
                            cardCnt++;
                        }
                        break;
                    case '>':
                        x += 0.5f;
                        break;
                    case '^':
                        z -= 0.5f;
                        break;

                }
            }

            //각 행의 최대 카드수 계산
            if (x > maxX)
            {
                maxX = x;
            }
            //전체 행의 수
            z++;
        }
        //카드 가로 시작 위치
        sx = -maxX / 2;
        sz = (z - 1) / 2;

        //  StartCoroutine(CardOpen(cardCnt));
    }

    void ShuffleCard()
    {
        for (int i = 1; i <= cardCnt; i++)
        {
            arCards[i] = i;
        }
        //return;

        //카드섞기 15회정도 반복
        for (int i = 1; i <= 15; i++)
        {
            //임의의 난수를 두개 만든다
            int n1 = Random.Range(1, cardCnt + 1);
            int n2 = Random.Range(1, cardCnt + 1);

            //교환
            int t = arCards[n1];//배열의 값을 바꾼다
            arCards[n1] = arCards[n2];
            arCards[n2] = t;

            //StartCoroutine(CardOpen(arCards[n1]));
        }
    }
}

 

 

 

카드와 게임 메니져 GameManager 스크립트와 통신하기 위해서 CardCtrl 스크립트를 수정합니다

 

CardCtrl 스크립트 수정

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

public class CardCtrl : MonoBehaviour
{
    //이미지 번호
    int imgNum = 1;

    //카드 뒷면 이미지 번호
    int backNum = 1;

    // 오픈된 카드의 판별여부 
    bool isOpen = false;

    Animator anim;


    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();
    }

    void Update()
    {
        //왼쪽 마우스버튼 클릭 모바일에선 터치 
        if (Input.GetButtonDown("Fire1") && GameManager.state == GameManager.STATE.IDLE)
        {
            CheckCard();
        }
    }

    //카드체크
    void CheckCard()
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        //터치한 카드 식별
        if (Physics.Raycast(ray, out hit, Mathf.Infinity))
        {
            string tag = hit.transform.tag;
            if (tag.Substring(0, 4) == "card")
            {
                //터치한 카드의 OpenCard()함수 실행
                hit.transform.SendMessage("OpenCard", SendMessageOptions.DontRequireReceiver);
            }
        }
    }
    void OpenCard()
    {
        //열린 카드는 처리 없음
        if (isOpen) return;

        //열린 카드 참거짓 판정
        isOpen = true;

        // 카드 번호 Substring() 문자열의 일부분을 추출하는 함수 , 카드 번호(tag)는 card0~card32 있으므로 문자4번째부터(card~) 끝까지 추출한다
        int cardNum = int.Parse(transform.tag.Substring(4));

        //이미지 번호 카드 두장에 하나씩 같은 이미지를 할당하므로 이미지 번호는 (카드번호 +1)/2 로 구한다 정수 /정수 = 정수 이므로 소수 이하는 자동으로 잘림
        imgNum = (cardNum + 1) / 2;
        //카드 애니메이션 실행
        anim.Play("aniOpen");

        GameManager.cardNum = cardNum;
        GameManager.state = GameManager.STATE.HIT;

    }
    void CloseCard()
    {
        anim.Play("aniClose");
        isOpen = false;
    }
    //카드 앞면을 이미지을 가지고 온다
    void ShowImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("card" + imgNum) as Texture2D;
    }


    //카드 뒷면 이미지을 가지고 온다
    void HideImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("back" + backNum) as Texture2D;
    }
}

 

 

 

 

임포트 받았더 back5 를 back1으로 수정합니다 

 

게임을 실행시켜서 두 개의 그림이 맞으면 서로 열리고 안 맞으면 서로 닫힙니다 

그리고 그림이 모두 열리면 다음 스테이지로 넘어갑니다 

아래 동영상과 같이 게임이 실행되면 성공입니다

 

 

반응형
반응형

카드를 Touch 및 클릭하여 카드를 뒤집고 카드를 리소스의 순번대로 나오는 것을 랜덤으로 섞는 프로그램을 만들어 보겠습니다

먼저 CardCtrl 스크립트를 열고 수정합니다

 

 

 

 

CardCtrl 스크립트 수정

Update()와 CheckCard() 함수 부분을 수정 및 추가합니다

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

public class CardCtrl : MonoBehaviour
{
    //이미지 번호
    int imgNum = 1;

    //카드 뒷면 이미지 번호
    int backNum = 1;

    // 오픈된 카드의 판별여부 
    bool isOpen = false;

    Animator anim;


    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();
    }

    void Update()
    {
        //왼쪽 마우스버튼 클릭 모바일에선 터치 
        if (Input.GetButtonDown("Fire1") )
        {
            CheckCard();
        }
    }

    //카드체크
    void CheckCard()
    {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        //터치한 카드 식별
        if (Physics.Raycast(ray, out hit, Mathf.Infinity))
        {
            string tag = hit.transform.tag;
            if (tag.Substring(0, 4) == "card")
            {
                //터치한 카드의 OpenCard()함수 실행
                hit.transform.SendMessage("OpenCard", SendMessageOptions.DontRequireReceiver);
            }
        }
    }
    void OpenCard()
    {
        if (isOpen) return;
        isOpen = true;

        // 카드 번호 Substring() 문자열의 일부분을 추출하는 함수 , 카드 번호(tag)는 card0~card32 있으므로 문자4번째부터(card~) 끝까지 추출한다
        int cardNum = int.Parse(transform.tag.Substring(4));

        //이미지 번호 카드 두장에 하나씩 같은 이미지를 할당하므로 이미지 번호는 (카드번호 +1)/2 로 구한다 정수 /정수 = 정수 이므로 소수 이하는 자동으로 잘림
        imgNum = (cardNum + 1) / 2;
        //카드 애니메이션 실행
        anim.Play("aniOpen");

       //GameManager.cardNum = cardNum;
        //GameManager.state = GameManager.STATE.HIT;

    }
    void CloseCard()
    {
        anim.Play("aniClose");
        isOpen = false;
    }
    //카드 앞면을 이미지을 가지고 온다
    void ShowImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("card" + imgNum) as Texture2D;
    }


    //카드 뒷면 이미지을 가지고 온다
    void HideImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("back" + backNum) as Texture2D;
    }
}

 

 

 

 

아래 동영상과 카드를 터치하였을 때 카드가 열립니다 그런데 카드가 섞이지 않고 순번대로 열립니다 

 

 

 

 

GameManager 스크립트 수정합니다

 

 

 

GameManager 스크립트 수정

카드를 섞는 함수를 작성

ShuffleCard() 함수 작성 및 카드 태그 인식 부분 수정

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

public class GameManager : MonoBehaviour
{
    //클릭한 카드 번호
    static public int cardNum;

    //직전의 카드 번호
    int lastNum = 0;

    //스테이지의 전체 카드수
    int cardCnt;

    //카드 클릭 횟수
    int hitCnt = 0;

    //스테이지 번호
    static public int stageNum = 1;

    //스테이지 수
    int stageCnt = 6;

    //카드 배열 카드 섞기용
    int[] arCards = new int[50];

    //게임 시작 시간
    float startTime;

    // 스테이지 경과 시간
    float stageTime;


    // Start is called before the first frame update
    void Start()
    {
        //시간초기화
        startTime = stageTime = Time.time;
        //스테이지 만들기
         StartCoroutine(MakeStage());
    }

    void Update()
    {

    }

    IEnumerator MakeStage()
    {
        //시작카드의 x좌표
        float sx = 0;

        //시작카드의 z좌표
        float sz = 0;

        
        SetCardPos(out sx, out sz);

        //카드섞기
        ShuffleCard();


        //시작카드의 번호
        int n = 1;

        //카드배열 읽기 배열의 1행을 읽고 변수 t에 할당한다
        string[] str = StageSet.stage[stageNum - 1];

        //배열의 행의 수만큼 반복
        foreach (string t in str)
        {
            // 각행의 문자열을 단일 문자 배열로 변환(문자열 좌우의 공백 제거) , 변수t의 자우 공백을 제거(Trim)하고 단일 문자배열로 변환
            char[] ch = t.Trim().ToCharArray();

            //카드의 x축 좌표
            float x = sx;

            //1행의 문자열 길이만큼 반복
            //배열의 ch의 한문자를 읽고 변수 c에 할당한다
            foreach (char c in ch)
            {
                switch (c)
                {

                    //맵의 내용이 * 이면 그위치에 카드 만들어서 배치
                    case '*':
                        //카드 만들기
                        GameObject card = Instantiate(Resources.Load("Prefab/Card")) as GameObject;

                        //카드 좌표설정
                        card.transform.position = new Vector3(x, 0, sz);

                        //태그 달기
                        //card.tag = "card" + n++;

                        
                        //섞인카드
                        card.tag = "card" + arCards[n++];
                        x++;
                        break;

                        //빈칸 처리
                    case '.':
                        x++;
                        break;


                        //반 칸 공백처리
                    case '>':
                        x += 0.5f;
                        break;


                        //반 줄 행간 처리
                    case '^':
                        sz += 0.5f;
                        break;


                }

                //카드를 표시한 후에는 지연 시간을 두어 카드가 배치되는 과정이 보이도록함
                if (c == '*')
                {
                    yield return new WaitForSeconds(0.03f);

                }
            }

            //한줄 아래로 이동
            sz--;
        }

    }

    //카드의 시작 위치 계산
    void SetCardPos(out float sx, out float sz)
    {
        //가로 카드수 반 칸 공백 포함
        float x = 0;

        //세로 행수 반줄 행간 포함
        float z = 0;

        //가로 카드 최대수
        float maxX = 0;

        //스테이지 전체 카드수
        cardCnt = 0;

        //카드 배열 조사 맵 배열 을 읽음
        string[] str = StageSet.stage[stageNum - 1];

        //행의 수만큼 반복
        for (int i = 0; i < str.Length; i++)
        {
            //1행읽기
            string t = str[i].Trim();

            //각행의 카드수
            x = 0;

            //각행의 글자 수만큼 반복
            for (int j = 0; j < t.Length; j++)
            {
                //문자열(string)은 단일 문자(char)의 배열로 취급할수 있음
                switch (t[j])
                {
                    case '.':
                    case '*':

                        //카드배치에 필요한 공간
                        x++;
                        if (t[j] == '*')
                        {
                            //전체 카드수
                            cardCnt++;
                        }
                        break;
                    case '>':
                        x += 0.5f;
                        break;
                    case '^':
                        z -= 0.5f;
                        break;

                }
            }

            //각 행의 최대 카드수 계산
            if (x > maxX)
            {
                maxX = x;
            }
            //전체 행의 수
            z++;
        }
        //카드 가로 시작 위치
        sx = -maxX / 2;
        sz = (z - 1) / 2;

        //  StartCoroutine(CardOpen(cardCnt));
    }

    void ShuffleCard()
    {
        for (int i = 1; i <= cardCnt; i++)
        {
            arCards[i] = i;
        }
        //return;

        //카드섞기 15회정도 반복
        for (int i = 1; i <= 15; i++)
        {
            //임의의 난수를 두개 만든다
            int n1 = Random.Range(1, cardCnt + 1);
            int n2 = Random.Range(1, cardCnt + 1);

            //교환
            int t = arCards[n1];//배열의 값을 바꾼다
            arCards[n1] = arCards[n2];
            arCards[n2] = t;

            //StartCoroutine(CardOpen(arCards[n1]));
        }
    }
}

 

 

 

 

아래 동영상과 같이 카드가 일렬 배열대로 안 나오고 섞어서 나옵니다

 

반응형
반응형

게임 스테이지마다 큐브로 만든 카드를 배열하여 맵을 만들어 보겠습니다

 

먼저 card0~card32까지 태그를 만듬니다

 

맵은 별도의 텍스트 파일로 만든 후 게임 시작 시 그것을 불러와서 처리하도록 할 수 있지만, 절차가 복잡하므로 맵을 2차원 가변 배열로 만듭니다. C#의 2차원 가변 배열은 다음과 같은 형식으로 만듭니다

 static public string [][] stage = new string[][]

{

    new string[] {"문자열, "문자열", },  //스테이지 1

  new string [] {"문자열, "문자열", },  //스테이지 2

   new string [] {"문자열, "문자열", }, //스테이지 3

}; //배열의 끝

 

StageSet 스크립트를 생성하고 작성합니다

 

 

 

 

StageSet 스크립트 작성

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

public class StageSet : MonoBehaviour
{
    //스테이지 2차원 배열을 만든다
    static public string[][] stage = new string[][]
       {

        new string[]
        {
            "   .*..*.   ",
            "   .****.   ",
            "   ******   ",
            "   .****.   ",
            "   .*..*.   "

        },

        new string[]
        {
            "   .*...*.   ",
            "   >**..**.  ",
            "   ***.***   ",
            "   >**..**   ",
            "   .*...*.   "
        },

        new string[]
        {
            "   **....**   ",
            "   **.**.**   ",
            " ^ ..*..*..   ",
            " ^ **.**.**   ",
            "   **....**   "
        },

        new string[]
        {
            "   ...**...   ",
            " ^ ..*..*..   ",
            " ^ .*.**.*.   ",
            " ^ ..*..*..   ",
            " ^ **.**.**   ",
            " ^ ..*..*..   ",
            " ^ .*.**.*.   ",
            " ^ ..*..*..   ",
            "   ...**...   ",
        },

        new string[]
        {
            "   .*.>*.>*   ",
            "   **.**.**   ",
            " ^ ..*..*..   ",
            " ^ **.**.**   ",
            " ^ ..*..*..   ",
            " ^ .*.**.*.   ",
            "   .*.>*.>*   ",
        },

        new string[]
        {
            "    .**...**.   ",
            "   > ***..***.  ",
            "    ****.****   ",
            "   > ***..***.  ",
            "    .**...**.   "
        },

       };
}

 

 

 

 

하이 라키 뷰에 GameManager 오브젝트를 만들고 GameManager 스크립트를 생성하여 오브젝트에 붙입니다

 

 

 

GameManager 스크립트 작성

 

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

public class GameManager : MonoBehaviour
{
    //클릭한 카드 번호
    static public int cardNum;

    //직전의 카드 번호
    int lastNum = 0;

    //스테이지의 전체 카드수
    int cardCnt;

    //카드 클릭 횟수
    int hitCnt = 0;

    //스테이지 번호
    static public int stageNum = 1;

    //스테이지 수
    int stageCnt = 6;

    //카드 배열 카드 섞기용
    int[] arCards = new int[50];

    //게임 시작 시간
    float startTime;

    // 스테이지 경과 시간
    float stageTime;


    // Start is called before the first frame update
    void Start()
    {
        //시간초기화
        startTime = stageTime = Time.time;
        //스테이지 만들기
         StartCoroutine(MakeStage());
    }

    void Update()
    {

    }

    IEnumerator MakeStage()
    {
        //시작카드의 x좌표
        float sx = 0;

        //시작카드의 z좌표
        float sz = 0;

        
        SetCardPos(out sx, out sz);

        //시작카드의 번호
        int n = 1;

        //카드배열 읽기 배열의 1행을 읽고 변수 t에 할당한다
        string[] str = StageSet.stage[stageNum - 1];

        //배열의 행의 수만큼 반복
        foreach (string t in str)
        {
            // 각행의 문자열을 단일 문자 배열로 변환(문자열 좌우의 공백 제거) , 변수t의 자우 공백을 제거(Trim)하고 단일 문자배열로 변환
            char[] ch = t.Trim().ToCharArray();

            //카드의 x축 좌표
            float x = sx;

            //1행의 문자열 길이만큼 반복
            //배열의 ch의 한문자를 읽고 변수 c에 할당한다
            foreach (char c in ch)
            {
                switch (c)
                {

                    //맵의 내용이 * 이면 그위치에 카드 만들어서 배치
                    case '*':
                        //카드 만들기
                        GameObject card = Instantiate(Resources.Load("Prefab/Card")) as GameObject;

                        //카드 좌표설정
                        card.transform.position = new Vector3(x, 0, sz);

                        //태그 달기
                        card.tag = "card" + n++;
                       // card.tag = "card" + arCards[n++];
                        x++;
                        break;

                        //빈칸 처리
                    case '.':
                        x++;
                        break;


                        //반 칸 공백처리
                    case '>':
                        x += 0.5f;
                        break;


                        //반 줄 행간 처리
                    case '^':
                        sz += 0.5f;
                        break;


                }

                //카드를 표시한 후에는 지연 시간을 두어 카드가 배치되는 과정이 보이도록함
                if (c == '*')
                {
                    yield return new WaitForSeconds(0.03f);

                }
            }

            //한줄 아래로 이동
            sz--;
        }

    }

    //카드의 시작 위치 계산
    void SetCardPos(out float sx, out float sz)
    {
        //가로 카드수 반 칸 공백 포함
        float x = 0;

        //세로 행수 반줄 행간 포함
        float z = 0;

        //가로 카드 최대수
        float maxX = 0;

        //스테이지 전체 카드수
        cardCnt = 0;

        //카드 배열 조사 맵 배열 을 읽음
        string[] str = StageSet.stage[stageNum - 1];

        //행의 수만큼 반복
        for (int i = 0; i < str.Length; i++)
        {
            //1행읽기
            string t = str[i].Trim();

            //각행의 카드수
            x = 0;

            //각행의 글자 수만큼 반복
            for (int j = 0; j < t.Length; j++)
            {
                //문자열(string)은 단일 문자(char)의 배열로 취급할수 있음
                switch (t[j])
                {
                    case '.':
                    case '*':

                        //카드배치에 필요한 공간
                        x++;
                        if (t[j] == '*')
                        {
                            //전체 카드수
                            cardCnt++;
                        }
                        break;
                    case '>':
                        x += 0.5f;
                        break;
                    case '^':
                        z -= 0.5f;
                        break;

                }
            }

            //각 행의 최대 카드수 계산
            if (x > maxX)
            {
                maxX = x;
            }
            //전체 행의 수
            z++;
        }
        //카드 가로 시작 위치
        sx = -maxX / 2;
        sz = (z - 1) / 2;

        //  StartCoroutine(CardOpen(cardCnt));
    }

}

 

 

 

 

하이 라키 뷰에 MaskCamera를 잠시 꺼두고 아래 그림과 같이 Transform 값을 바꿉니다

 

Background1 Transform값을 바꿉니다

 

Background2 Transform값을 바꿉니다

 

 

 

Main Camera Transform 값을 바꿉니다

 

게임을 실행시키면 아래와 같이 게임 화면에 카드가 배열되는 모습을 볼 수 있습니다

오른쪽 약간의 공백은 텍스쳐가 들어갈 공간을 남겨두어서 약간 왼쪽으로 치우친 모습입니다

 

반응형
반응형

 

그림을 나타낼 큐브를 만들고 애니메이션을 주는 작업을 하겠습니다

먼저 GameObject에서  Cube을 생성합니다

 

 

cardGame_texture.unitypackage
3.18MB

Resources 폴더에 그림파일을 임포트 합니다

 

 

 

 

Materials 폴더에 Material을 생성하고 이름을 card_back으로 하고 Card 오브젝트 에 메터리얼을 드래그하여 붙이고 메터리얼 Shader를 Lagacy Shaders -> Transparent -> Diffuse를 선택합니다

그리고 그림을  Resources 파일에 있는 back 5를 붙입니다

 

Card 오브젝트에 붙일 CardCtrl 스크립트를 생성하여 스크립트 작업을 합니다

 

CardCtrl 스크립트 작성

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

public class CardCtrl : MonoBehaviour
{
    //이미지 번호
    int imgNum = 1;

    //카드 뒷면 이미지 번호
    int backNum = 1;

    // 오픈된 카드의 판별여부 
    bool isOpen = false;

    Animator anim;


    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();
;
    }


    //카드 앞면을 이미지을 가지고 온다
    void ShowImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("card" + imgNum) as Texture2D;
    }


    //카드 뒷면 이미지을 가지고 온다
    void HideImage()
    {
        transform.GetComponent<Renderer>().material.mainTexture = Resources.Load("back" + backNum) as Texture2D;
    }
}

 

 

 

 

작성한 스크립트를 Card 오브젝트에 붙입니다

 

메인 카메라를 선택하고 Transform을 그림과 같이 바꿉니다

 

 

 

 

BackManager 자식으로 Camera 오브젝트를 생성하여 이름을 MaskCamera 라하고  Transform을 아래 그림과 같이 바꿉니다

 

 

Card 오브젝트를 선택하고  Window -> Animation -> Animation을 선택합니다

 

aniOpen  애니메이션을 생성하고 붉은 단추를 누르고 0 프레임에  Rotation Z값을 0을 줍니다

 

aniOpen  애니메이션을  30 프레임에  Rotation Z값을 -180을 줍니다

 

 

 

aniOpen 애니메이션에 15 프레임 정도에 Add event를 넣고 ShowImage() 함수를 연결합니다

 

aniOpen 애니메이션에 0 프레임에 Add event를 넣고 HideImage() 함수를 연결합니다

 

 

그리고  aniClose 애니메이션을 생성하고 빨간 단추를 누르고 0 프레임에 Rotation Z값을 -180 을 주고 30프레임에 Rotation Z값을 0을 줍니다

 

 

 

aniClose 애니메이션을 선택하고 0프레임에 Add event를 추가하고 ShowImage() 함수를 연결합니다

 

aniClose 애니메이션을 선택하고 15 프레임에 Add event를 추가하고 HideImage() 함수를 연결합니다

 

aniClose, aniOpen 애니메이션의  Loop Time을 체크 해제합니다

 

 

 

Animator에 들어가서 Create State -> empty를 생성하여  Set as layer Default state를 선택합니다

 

 

Resources 폴더에 Prefab 폴더를 생성하고 Card 오브젝트를 드래그하여 프리 팹을 만듭니다

 

 

하이라키에 있는 Card 오브젝트는 삭제합니다

반응형
반응형

카드 그림을 맞추는 게임을 만들어 보겠습니다

 

 

 

이게임은 고도의 집중력과  기억력이 요구되는 게임입니다

먼저 인터넷에서 게임에 쓰일 배경 이미지를 모읍니다

무료 배경 이미지를 인터넷에 들어가면 많이 찾을 수 있을 것입니다

 

 

 

먼저 Project 뷰에서 Resources 폴더를 만듭니다

 

 

인터넷에서 다운 배경 이미지 파일을 이름을 imgBack0~5까지 순번 되로 이름을 적습니다

이름 스팰링 하나 틀리면 안 되니 주의하여야 합니다

 

빈 오프젝트를 생성하고 이름을 BackManager 라 하고 메인 카메라와 라이트를 자식으로 둡니다 

 

 

 

그리고 GameObject -> 3D Object -> Plane을 하나 생성합니다

 

Plan의 이름을 Background1으로 하고 backManager 자식으로 둡니다

 

project 뷰에서 Materials 폴더를 만들고 Material을 생성합니다

 

 

 

Material의 이름을 back 1으로 합니다 

 

 

Background1을 선택하고 스케일을  2.3 ,1, 1.2로 하고 back 1 메터리얼을 드래그하여 붙입니다

그리고 Shader 속성을  Lefacy Shaders -> Transparent -> Diffuse를 선택하고 Resources 폴더에 있는  배경 이미지를  갖다 붙입니다 

 

 

 

 

Background1 오브젝트를 선택하여 Ctrl_D 키를 누르고 복사된 오브젝트를 이름을 Background2 라하고 Position  y 값을  -0.5를 줍니다

 

 

 

Project 뷰에서 Scrips 폴더를 만들고 BackManager 스크립트를 생성합니다

 

BackManager 스크립트 작성

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

public class BackManager : MonoBehaviour
{
    //배경 이미지
    Transform back1;
    Transform back2;

    //오버랩 지연 시간
    int delayTime = 3;

    //현재의 시간
    float currentTime = 0;


    //이미지 갯수
    int imgCnt = 6;
    //현재 이미지 번호
    int imgNum = 1;

    void Start()
    {
        //배경찾기
        back1 = transform.Find("Background1");
        back2 = transform.Find("Background2");


    }
    // Update is called once per frame
    void Update()
    {
        //지연시간 처리
        currentTime += Time.deltaTime;

        if (currentTime >= delayTime)
        {
            currentTime = 0;

            //이미지 오버랩
            StartCoroutine(OverlapImage());
        }

    }

    IEnumerator OverlapImage()
    {
        //이미지 알파값 설정
        //이미지의 두명도는 0~1의 값이다 0은 완전 투명 , 1은 완전불투명
        for (float alpha = 1; alpha >= 0; alpha -= 0.05f)
        {
            //배경이미지를 점점 투명하게 만든다
            back1.GetComponent<Renderer>().material.color = new Vector4(1, 1, 1, alpha);

            //배경이미지를 점점 불투명하게 만든다
            back2.GetComponent<Renderer>().material.color = new Vector4(1, 1, 1, 1 - alpha);


            //화면 갱신까지 대기
            yield return new WaitForFixedUpdate();

        }

        //배경 바꾸기
        Transform tmp = back1;
        back1 = back2;
        back2 = tmp;

        //다음 이미지 준비
        //Mathf.Repeat() 0~<한계값-1> 을 반복으로 처리하는 함수
        imgNum = (int)Mathf.Repeat(++imgNum, imgCnt);

        //배경2에 새로운 이미지 할당
        back2.GetComponent<Renderer>().material.mainTexture = Resources.Load("imgBack" + imgNum) as Texture2D;

        currentTime = 0;
    }
}

 

 

 

BackManager 스크립트를  BackManager 오브젝트에 붙입니다

 

게임을 실행하면 아래 동영상과 같이 Resoceses 폴더에 들어 있는 이미지 imgBack0 ~1  순번대로 변환되는 것을 보실 수 있습니다 

 백그라운드 그림 이미지를 자연스럽게 바꾸는 방법을 살펴보았습니다 

 

반응형
반응형

하이 라키 뷰에서 Gun 오브젝트를 선택하여 AddComponent -> Animation을 생성합니다 

 

 

하이 라키 뷰에 Gun 오브젝트를 선택하고  Window -> Animaton -> Animaton을 클릭합니다 

 

 

Animation 타임라인에서 Create 를 누르고  Assets에 Animation 폴더를 생성하고  파일 이름을 ChargeBullet으로 하여  저장합니다 

 

 

 

아래 동영상과 같이 타임 라인에 왼쪽 위에 빨간 버튼을 누르고 총이 왼쪽으로 회전 하는 애니메이션을 만듭니다

각각 타임라인에  라인 바를 갖다 놓고 총의 움직임을 주면선 애니메이션을 만듭니다

 

하이 라키 뷰에 Gun 오브젝트를 선택하고 자식으로 Cyilnder를 생성하고 position 0, 0.04, 0.251

Rotation 90, 0, 0   Scale 0.1, 0.15, 0.1로 맞춥니다 

총을 쏘았을 때 탄피가 튕겨 나오는 애니메이션을 할 수 있게 총안쪽에  보이지 않는 위치에 놓습니다 

 

 

 

Gun오브젝트의 Cylinder를 선택하고 window -> Animation -> Animation을 선택하여 빨간 버튼 아래에 있는 애니메이션 선택 바를 누르고 Create new Clip을 선택하여 애니메이션 이름을 Fire 하고 저장합니다 

 

 

 

 

 

그림과 같이 탄피가 나오는 애니메이션을 Cylinder를 타임라인에 움직이며 애니메이션을 줍니다 

 

Gun 오브젝트를 선택하고 Animation에 그림과 같이 애니메이션을 연결합니다 

Gun 오브젝트를 선택하고 자식으로 빈 오브젝트를 만들어 그림과 같이 총끝머리에 위치시키고 이름을 spPoint라고 합니다 차후에 총알 발사 위치와 총알 화염 이팩트가 터지는 위치입니다 

 

 

Gun 오브젝트를 선택하고 Gun Tag를 만들어서 Tag를 Gun으로 합니다  

 

 

 

source_effect.unitypackage
0.01MB

 

이팩트 소스를 다운로드하여 그림과 같이 Prefabs 폴더에 이팩트 파일을 저장합니다 

 

 

 

 

 

GunCtrl 스크립트 수정

 

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

public class GunCtrl : MonoBehaviour
{
    //총 발사음
    public AudioClip sndFire;
    //실탄 장전음
    public AudioClip sndCocking;
    //배경음악
    public AudioClip sndStage;
    //게임 오버음
    public AudioClip sndOver;

    //프리팹 접시,참새, 폭파화염,총구화염, 총알
    public Transform clay, bird, exp, gunFire, fireBullet;

    //탄피,총구위치
    Transform bulletCase, spPoint;

    //실패 횟수
    static public int miss;

    //성공횟수
    int hit;

    //남은 총알
    int bulletCnt;

    //시작시간, 종료시간
    float startTime, overTime;
    bool gameOver = false;

    //화면폭과높이
    int width, height;

    void Start()
    {
        Screen.orientation = ScreenOrientation.Landscape;
        Screen.sleepTimeout = SleepTimeout.NeverSleep;

        Cursor.visible = false;

        SetStage();
    }
    private void Update()
    {
        // 10번 미스 되면 게임 오버
        if (miss >= 10)
        {
            gameOver = true;
            overTime = Time.time;
        }

        if (gameOver)
        {
            return;
        }

        // Esc 누르면 게임종료
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            EditorApplication.isPlaying = false;
        }

        MakeClay();
        RotateGun();

        if (Input.GetMouseButtonDown(0))
        {
            FireGun();
        }
        if (bulletCnt < 5 && Input.GetMouseButtonDown(1))
        {
            StartCoroutine(ChargeGun());
        }

    }

    //총회전 
    void RotateGun()
    {
        //카메라부터 거리를 설정
        Vector3 pos = Input.mousePosition;

        //화면의 기준으로 총의 움직임을 제한한다
        pos.x = Mathf.Clamp(pos.x, 0, Screen.width);
        pos.y = Mathf.Clamp(pos.y, 0, Screen.height);

        //카메라로 부터의 거리
        pos.z = 13.2f;
        //마우스 위치를 월드 좌표로 변환
        Vector3 view = Camera.main.ScreenToWorldPoint(pos);

        //총의 회전
        transform.LookAt(view);

    }

    void FireGun()
    {
        //실탄 장전 애니메이션이 진행중이면 발사금지
        if (GetComponent<Animation>().isPlaying)
        {
            return;
        }

        RaycastHit hit;

        //카메라 시점에서 커서 위치 계산
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Physics.Raycast(ray, out hit, Mathf.Infinity);

        //총을 클릭하면 실탄 재장전
        if (hit.transform.tag == "Gun" && bulletCnt < 5)
        {
            StartCoroutine(ChargeGun());
            return;
        }

        //총구 앞에 화면
        Instantiate(gunFire, spPoint.position, Quaternion.identity);
        //총탄 발사 
        Instantiate(fireBullet, spPoint.position, spPoint.rotation);
        //탄피 배출 애니메이션
        GetComponent<Animation>().Play("Fire");

        CheckTarget(hit);

        //남은 실탄 수 처리
        bulletCnt--;
        if (bulletCnt<= 0)
        {
            StartCoroutine(ChargeGun());
        }
    }

    //목표물 적중여부 판정
    void CheckTarget(RaycastHit hit)
    {
        switch (hit.transform.tag)
        {
            //접시 명중되었을 때 점수 처리및 폭발이팩트 생성및 제거
            case "Clay":
                this.hit++;
                Instantiate(exp, hit.transform.position, Quaternion.identity);
                Destroy(hit.transform.gameObject);
                break;

                // 참새 사망시 미스 처리및 참새연결 스크립트의 DeadBird 함수 와 통신 
            case "Bird":
                Instantiate(gunFire, hit.transform.position, Quaternion.identity);
                miss++;
                hit.transform.SendMessage("DeadBird", SendMessageOptions.DontRequireReceiver);
                break;

        }
    }

    //장전 애니메이션 
    IEnumerator ChargeGun()
    {
        while (GetComponent<Animation>().isPlaying)
        {
            yield return 0;
        }

        GetComponent<Animation>().Play("ChargeBullet");
        yield return new WaitForSeconds(0.5f);
        bulletCnt = 5;
    }

    //접시 생성
    void MakeClay()
    {
        if (Random.Range(0, 1000) > 970 && !GetComponent<Animation>().isPlaying)
        {
            if (Random.Range(0,100)< 70)
            {
                Instantiate(clay);
            }
            else
            {
                Instantiate(bird);
            }
        }
    }

    //장전 음악 
    void SoundClick()
    {
        bulletCase.gameObject.GetComponent<Renderer>().enabled = true;
        AudioSource.PlayClipAtPoint(sndCocking, Vector3.zero);
    }

    //탄발사음 
    void SoundFire()
    {
        bulletCase.gameObject.GetComponent<Renderer>().enabled = true;
        AudioSource.PlayClipAtPoint(sndFire, Vector3.zero);
    }

    //탄피 감추기 애니메이션
    void HideBullet()
    {
        bulletCase.gameObject.GetComponent<Renderer>().enabled = false;
    }
    void SetStage()
    {
        width = Screen.width;
        height = Screen.height;

        spPoint = GameObject.Find("spPoint").transform;

        bulletCase = transform.Find("Cylinder");

        bulletCase.gameObject.GetComponent<Renderer>().enabled = false;

        startTime = Time.time;
        hit = miss = 0;
        bulletCnt = 5;
        gameOver = false;

        GetComponent<AudioSource>().loop = true;
        GetComponent<AudioSource>().Play();
    }

    void OnGUI()
    {
        // GUI.skin = skin;

        //게임진행시간 
        float time = Time.time - startTime;

        if (!gameOver)
        {
            //커서위치에 조준점 표시
            GUI.DrawTexture(new Rect(Input.mousePosition.x - 24, height - Input.mousePosition.y - 24, 48, 48),
                            Resources.Load("crossHair") as Texture2D);
        }
        else
        {
            time = overTime - startTime;
        }

        //남은 실탄수 표시
        for (int i = 1; i <= bulletCnt; i++)
        {
            GUI.DrawTexture(new Rect(i * 12, height - 20, 8, 16), Resources.Load("bullet") as Texture2D);
        }

        //점수 등 표시
        string sHit = "<size='30'>HIT : " + hit + "</size>";
        string sMiss = "<size='30'>MISS : " + miss + "</size>";
        string sTime = "<color='yellow'><size='30'>Time : " + (int)time + "</size></color>";

        GUI.Label(new Rect(30, 20, 120, 40), sHit);
        GUI.Label(new Rect(width / 2 - 40, 20, 160, 40), sTime);
        GUI.Label(new Rect(width - 120, 20, 120, 40), sMiss);

        string msg = "Shoot : Left Btn  Charge : Gun Click";
        GUI.Label(new Rect(width - 380, height - 40, 380, 40), msg);

        //게임오버 처리
        if (gameOver)
        {
            Cursor.visible = true;
            if (GetComponent<AudioSource>().clip != sndOver)
            {
                GetComponent<AudioSource>().clip = sndOver;
                GetComponent<AudioSource>().loop = false;
                GetComponent<AudioSource>().Play();
            }

            //Play Game 버튼 생성 및 버튼On 시 게임다시 진행
            if (GUI.Button(new Rect(width / 2 - 70, height / 2 - 50, 140, 60), "Play Game"))
            {
                Application.LoadLevel("MainGame");
            }

            //게임에서 나옴
            if (GUI.Button(new Rect(width / 2 - 70, height / 2 + 50, 140, 60), "Quit Game"))
            {
                EditorApplication.isPlaying = false;
            }
        }
    }
}

 

 

 

 

 

 

 

 

GunCtrl 스크립트를 작성하고   Sound 파일에 있는 음악 소스 파일을 그림과 같이 연결합니다 

 

Prefabs파일에 있는 프리 팹을 GunCtrl 스크립트에 연결합니다 

 

 

Gun 오브젝트를 선택하고  AudioSource를 만들어서 AudioClip에 Stage 파일을 연결합니다

 

 

 

 

 

 

 

Gun오브젝트를 선택하고 애니메이션 타임라인을 열고 애니메이션 타임라인 3/2 되는 지점에 애니메이션 이벤트를 만들어서 Function을 SoundClick() 함수에 연결합니다 

 

Fire 애니메이션을 선택하고 첫 타임라인에 SoundFire() 함수를 연결하는 애니메이션 이벤트를 만듭니다 

 

Fire 애니메이션 마지막 타임 라인에 HideBullet() 함수를 연결하는 애니메이션 이벤트를 만듬니다 

 

ClayCtrl 스크립트를 열고 아래 그림과 같이 GunCtrl.miss++; 를 추가합니다 

 

 

 

 

 

 

 

 

아래 동영상과 같이 총쏘는 게임을 만들어 보았습니다 

여기까지 따라 오신분들 너무 수고 많으셨습니다

감사해요 ㅎㅎ

 

반응형
반응형

하이 라키 뷰에 오른쪽 마우스를 누르고 3D object -> Cylinder를 만듭니다

 

그리고 스케일을 조정하여 1, 0.02, 1 을 값을 넣습니다 

 

그림과 같이 납작한 접시 모양이 됩니다 

 

하이라키 뷰에 Cylinder의 CrayCtrl 스크립트를 만들어 붙입니다 

ClayCtrl 스크립트작성

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

public class ClayCtrl : MonoBehaviour
{

    float speed; //접시 속도
    float rotZ; //접시 각도
    float gravity; // 추락속도

    int[] dir = { -1, 1 };
    int dirX; // 이동방향
    void Start()
    {
        SetPosition();
    }

    // Update is called once per frame
    void Update()
    {
        //접시의 이동속도를 감속한다
        gravity -= 1.3f * Time.deltaTime;
        //접시의 이동방향과 속도를 설정

        //접시를 회전을 주어 월드좌표로 이동
        Vector3 move = new Vector3(speed * dirX, gravity, 0) * Time.deltaTime;
        transform.Translate(move, Space.World);


        //화면에 접시가 벗어 나면 제거 한다
        if (Mathf.Abs(transform.position.x)>8 || transform.position.y < -3)
        {
           // GunCtrl.miss++;
            Destroy(gameObject);
        }
        
    }

    void SetPosition()
    {
     // 랜덤으로 접시속도 설정
        speed = Random.Range(3, 5f);

        //추락속도 설정
        gravity = 2f;

        //이동 방향 설정
        dirX = dir[Random.Range(0, 2)];

        //화면 의 좌우 위치
        float posX = -8 * dirX;

        //접시의 높이 및 회전설정
        float posY = Random.Range(2.5f, 4);
        transform.position = new Vector3(posX, posY, 9);
        transform.eulerAngles = new Vector3(-50, 0, Random.Range(10, 20f) * dirX);
    }
}

 

 

에셋에 Prefabs 폴더를 만들고 Clay 오브젝트를 드래그하여 프리 팹을 만들고 하이 라키 뷰에 있는 Clay는 삭제합니다 

 

 

참새가 죽을 때 나오는 꽥 오브젝트를 만들겠습니다.

Texture 폴더에 있는 꽥 그림파일을 하이 라키 뷰에 드래그하여 올려놓습니다 

 

OCtrl 스크립트를 만들고 꽥 오브젝트에 붙입니다 

 

OCtrl 스크립트 작성

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

public class OCtrl : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Destroy(gameObject, 1.5f);
    }


}

 

 

 

하이 라키뷰에 있는 꽥 오브젝트를 Prefabs 폴더에 드래그 하여 프리팹을 만듬니다 

하이라키뷰에 꽥 오브젝트는 삭제합니다 

 

 

 

하이라키 뷰에 Cube 오브젝트를 생성하여 스케일을 1, 0.02, 1을 맞춥니다 

 

Cube의 이름을 FireBullet으로 바꾸고 FireBullet 스크립트를 생성하여 붙입니다 

 

 

FireBullet 스크립트 작성

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

public class FireBulletCtrl : MonoBehaviour
{

    //속도
    float speed = 60f;

    // 총알 삭제 시간
    float delay = 0.5f;
    void Update()
    {
        // 총알 속도 설정
        float Move = speed * Time.deltaTime;

        //총알의 이동 방향 설정
        transform.Translate(Vector3.forward * Move);

        // 총알 삭제를 설정한다 
        delay -= Time.deltaTime;
        if (delay <= 0)
        {
            Destroy(gameObject);
        }
    }
}

 

 하이 라키 뷰에 FireBullet 오브젝트를 드래그 하여 Prefabs 폴더에 생성합니다 

그리고 하이 라키뷰에 있는 오브젝트는 삭제 합니다  

 

하이라키 뷰에  Quad를 생성하여 스케일을  0.8, 0.8 ,1로 맞춥니다 

 

 

 

Materials 폴더에 메터리얼을 생성하고 이름을 Bird 라하고 Shader -> Unlit/Transfarent를 하고 Sparrow그림파일을 선택하여 나타나게 합니다 

 

Materials파일에 있는 Bird 메터리얼을 Bird 오브젝트에 드래그하여 붙입니다 

 

Scripts폴더에  BirdCtrl 스크립트를 만들고 BirdCtrl 스크립트를 작성합니다 그리고 Bird 오브젝트에 붙임니다

 

 

BirdCtrl  스크립트 작성

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

public class BirdCtrl : MonoBehaviour
{
    //사망시 프리팹 연결 오브젝트
    public Transform oCtrl;


    //참새 이동 속도
    float speed;

    //죽었는지? 아닌지?
    bool isDead = false;

    void Start()
    {
        SetPosition();
        
    }


    void Update()
    {
        //속도 설정
        float Move = speed * Time.smoothDeltaTime;

        
        if (!isDead)
        {
            //날아갈때 오른쪽으로 이동
            transform.Translate(Vector3.right * Move, Space.World);
        }
        else
        {
            //떨어질대 아래로 이동
            transform.Translate(Vector3.down * Move, Space.World);
        }

        //화면을 벗어나면 제거
        if (transform.position.x > 7 || transform.position.y < -3)
        {
            Destroy(gameObject);
        }
    }

    //참새 사망시
    void DeadBird()
    {
        isDead = true;

        //참새사망시 180도 회전 설정
        transform.eulerAngles = new Vector3(0, 0, 180);

        //참새 위치 계산를 계산하여 꽥 오브젝트 생성할 위치을 설정하고 참새가 죽으면 생성한다
        Vector3 pos = new Vector3(transform.position.x, transform.position.y + 0.2f, 8);
        Quaternion rot = Quaternion.identity;

        rot.eulerAngles = new Vector3(0, 0, 0);
        Instantiate(oCtrl, pos, rot);
    }

    void SetPosition()
    {
        //참새의 속도 설정
        speed = Random.Range(2, 3f);

        //참새의  랜덤 위치설정
        transform.position = new Vector3(-7, Random.Range(1.4f, 4.7f), 8);
    }
}

 

 

Bird 오브젝트를 Prefabs폴더에 드래그하여 프리 팹을 만들고 Bird 프리팹을 선택하여 O Ctrl  자리에  꽥 프리팹을 연결합니다 

그리고 하이 라키 뷰에 있는  Bird오브젝트는 삭제합니다 

 

Biird 오브젝트를 선택하고 Tag에 Bird를 생성하여 태그 합니다 

 

Clay 프리 팹을 선택하여 Tag Clay를 선택하여 태그 합니다 

반응형
반응형

하이 라키 뷰에 빈 오브젝트를 만들고 이름을 Gun으로 하고 Transform position과 Rotation을 0으로 맞춥니다

 

 

FBX 파일에서 M4A1_FBX_2 오브젝트를 드래그하여 하이 라키 뷰의 Gun 오브젝트의 자식으로 놓습니다 

그리고 Position 값을 0으로 맞춥니다

 

그림과 같이 총의 위치가 되면 됨니다

 

GunCtrl 스크립트 작성

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

public class GunCtrl : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
      
    }
    private void Update()
    {
        RotateGun();
    }

    //총회전 
    void RotateGun()
    {
        //카메라부터 거리를 설정
        Vector3 pos = Input.mousePosition;

        //화면의 기준으로 총의 움직임을 제한한다
        pos.x = Mathf.Clamp(pos.x, 0, Screen.width);
        pos.y = Mathf.Clamp(pos.y, 0, Screen.height);

        //카메라로 부터의 거리
        pos.z = 13.2f;
        //마우스 위치를 월드 좌표로 변환
        Vector3 view = Camera.main.ScreenToWorldPoint(pos);

        //총의 회전
        transform.LookAt(view);

    }

}

 

 

 

작성한 스크립트를 Gun 오브젝트에 드래그하여 붙입니다 

 

영상과 같이 마우스를 움직였을 때 총이 회전하면 됩니다 

 

반응형
반응형

 

유니티 엔진으로 3D 오브젝트와 2D오브젝트를 활용하여 게임을 만들어 보겠습니다 

간단한 총쏘기 게임을 만들건대 게임을 좋아하는 분들이 쉽게 만들 수 있도록 설명하도록 노력하겠습니다

 

 

 

총쏘기게임만들기

 

www.youtube.com

 

 

 

source.unitypackage
3.00MB

유니티 엔진을 열고 Scenes 파일에 있는 씬의 이름을 MainGame으로 바꿉니다 

 

 

그리고  source파일을 다운로드하여  클릭하여 파일을 엽니다 

 

 

 

 

 

 

게임 소스를 확인하고 임포트 합니다 

 

 

에셋에 파일을 임포트 한 모습을 확인할 수 있습니다 

 

 

하이 라키 신에서  Main Camera를 선택하여 아래 그림과 같이   Transform Position 값을 0, 1.6 , -6.2로 바꾸고 카메라의 각도 Field of View를 30으로 바꿉니다  

 

 

 

 

 

하이 라키 뷰에서 오른쪽 마우스 버튼을 누르고 Quad를 선택하여 생성합니다 

 

그리고 이름을 BackGround라고 바꾸고 Transform  Position 값을 0, 2.3, 9.5   Scale 값을 17, 10 , 1로 바꿉니다 

 

그리고  프로젝트 뷰에서  Materials 빈 파일을 만들고 마우스 오른쪽 버튼을 누르고 Material을 생성합니다

 

 

 

 

 

 

material의 이름을 background 라바꾸고 속성에 들어가서 Shader -> Unlit/Transparent로 바꾸고 그림 초원 2를 선택하여 넣습니다 

 

 

 

 

 속성을 바꾼 메터리얼을 선택하여  하이 라키뷰의 BackGround 오브젝트를 선택하고 메터리얼을 드래그하여 붙혀 넣습니다

 

 

그리고 다시 하이라키 뷰의 Quad를 만들고 이름을 Sky 라 바꾼 다음 BackGround를 만든 것과 같이 Material를 생성하여 Shader를 Unlit/Transparent 토하고 그림을 sky1을 붙입니다 그리고 메터 리어를 Sky 오브젝트에 연결합니다 

그리고 Transform을 0, 2.6, 10   Scale 17, 10,  1 롤 변경합니다 

 

 

 

 

 

 

Texture 폴더의 sky1 그림파일에서  Wrap Mode -> Repeat로 바꿉니다 

 

 

 

 

Scripts 폴더에서 Sky 스크립트를 생성하여 스크립트를 작성합니다 

 

Sky 스크립트 작성

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

public class Sky : MonoBehaviour
{

    float speed = 0.02f;
    // Update is called once per frame
    void Update()
    {
        float ofs = speed * Time.time;
        transform.GetComponent<Renderer>().material.mainTextureOffset = new Vector2(ofs, -1);
    }
}

 

 

 

 

 

 

 

 

 

하이 라키 뷰에서 Sky를 선택하고 Sky 스크립트를 연결합니다 

 

게임 뷰에서 화면을 16 : 9로 맞춥니다 

 

 

 

 

 

 

 

 

아래 동영상과 같이 화면이 연속적으로 움직이면 됩니다 

 

 

반응형
반응형

 

 

 

Game Over 텍스트를 만들어서 게임 오버 환경을 만들겠습니다

하이라키 뷰에 Canvas 를 선택하고 오른쪽 마우스버튼을 눌러 UI -> Text를 만듬니다

 

새로 생긴 Text 오브젝트를 이름을 tetGameOver 로 바꾸고 속성을 아래 그림과 같이 바꿈니다

 

 

 

txtGameOver  오브젝트를 선택하고 Window -> Animation -> Animation 을 선택합니다

 

 

Animation 창이 나오면 Create 버튼을 누릅니다

 

저장할 Animation 폴더를 만들고 이름을 GameOver 라 하고 저장합니다

 

 

 

Animation 창에 Add Property 를 눌러서 Color + 를 선택하여 애니메이션 의 키를 줍니다 

 

애니며여션 창의 Preview 옆의 빨간 버튼을 누르고  첫 프레임의 키를 선택하여 GameOver 텍스쳐의 알파 값을 0 으로 하고  마지막 프레임을 선택하여 알파값을 255 로 줍니다

 

 

 

그리고 Animator 창으로 돌아가서 GameOver 애니메이션의 Loop Time을 체크 해제 합니다

 

그리고 빈 State 를 만들고 선택하여 Set as Layer Defult State 를 선택합니다

 

 

 

Animator 창에 Parameters를 선택하여  Trigger를 만들고 이름을 show 라 합니다

 

Start 와 GameOver  State 에 화살푤르 연결하고  has Exit Time 을 체크 해제하고 Conditons를 show를 선택합니다

 

UIManager 스크립트를 선택하고 스크립트를 수정합니다

 

 

 

   public Text gameOver;
   Animator animGameOver;

.

.

.

.

 private void Start()
    {
        animGameOver = gameOver.gameObject.GetComponent<Animator>();
        gameOver.enabled = false;
    }

    public void ShowGameOver()
    {
        gameOver.enabled = true;
        animGameOver.SetTrigger("show");
    }

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

public class UIManager : MonoBehaviour
{
    // 언제 어디서나 쉽게 접금할수 있도록 하기위해 만든 정적변수
    public static UIManager instance;

    public Text playerName;
    public Text playerMoney;
    public Image playerHPBar;

    public Text gameOver;

    Animator animGameOver;
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    private void Start()
    {
        animGameOver = gameOver.gameObject.GetComponent<Animator>();
        gameOver.enabled = false;
    }

    public void ShowGameOver()
    {
        gameOver.enabled = true;
        animGameOver.SetTrigger("show");
    }

    public void UpdatePlayerUI(PlayerParams playerParams)
    {
        playerName.text = playerParams.name;
        playerMoney.text = "Coin : " + playerParams.money.ToString();
        playerHPBar.rectTransform.localScale = 
            new Vector3((float)playerParams.curHp / (float)playerParams.maxHp, 1f, 1f);

    }
    void Update()
    {
        
    }
}

 

 

Canvas의 txtGameOver 오브젝트를 선택하고 UIManager 스크립트의 연결합니다 

 

PalyerFSM 스크립트를 선택하고 수정합니다

 

UIManager.instance.ShowGameOver();    public void ChangeToPlayerDead()
    {
        print("player was dead");
        ChangeState(State.Dead, PlayerAni.ANI_DIE);

        UIManager.instance.ShowGameOver();
    }

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

public class PlayerFSM : MonoBehaviour
{  
    public enum State
    {
        Idle,
        Move,
        Attack,
        AttackWait,
        Dead
    }
    //idle 상태를 기본 상태로 지정
    public State currentState = State.Idle;

    //마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정
    Vector3 curTargetPos;

    GameObject curEnemy;

    public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다

    public float moveSpeed = 2f; //초당 2미터의 속도로 이동

    float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간

    float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수

    float attackDistance = 1.5f; // 공격 거리 (적과의 거리)

    float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리

    PlayerAni myAni;

    PlayerParams myParams;

    EnemyParams curEnemyParams;
    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        myParams.deadEvent.AddListener(ChangeToPlayerDead);

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);

        curEnemy = GameObject.FindGameObjectWithTag("Enemy");
    }

    public void ChangeToPlayerDead()
    {
        print("player was dead");
        ChangeState(State.Dead, PlayerAni.ANI_DIE);

        UIManager.instance.ShowGameOver();
    }

    public void CurrentEnemyDead()
    {
        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
        print("enemy was killed");

        curEnemy = null;
    }

    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }

        print("Attack" + curEnemy.name +"...");

        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();

        int attackPower = myParams.GetRandomAttack();
        curEnemyParams.SetEnemyAttack(attackPower);

        //플레이어가 공격할때 나는 소리
        SoundManager.instance.PlayHitSound();
    }

    // 적을 공격하기 위한 함수 
    public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }

        //적(몬스터)의 파라미터를 변수에 저장
        curEnemyParams = enemy.GetComponent<EnemyParams>();

        if (curEnemyParams.isDead == false)
        {
            curEnemy = enemy;
            curTargetPos = curEnemy.transform.position;

            GameManager.instance.ChangeCurrentTarget(curEnemy);

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }
    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }
        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

                break;
            default:
                break;
        }
    }
   void IdleState()
    {

    }

    void MoveState()
    {
        TurnToDestination();
        MoveToDestination();
    }
    void AttackState()
    {
        attackTimer = 0f;

        //transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수
        transform.LookAt(curTargetPos);
        ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE);
    }
    void AttackWaitState()
    {
        if (attackTimer > attackDelay)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);

        }

        attackTimer += Time.deltaTime;
    }
    void DeadState()
    {

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        //사망 하였을때 움직임 리턴 처리 (여기서 끝냄)
        if (currentState == State.Dead)
        {
            return;
        }
        curEnemy = null;
        curTargetPos = tPos;
        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }
    void TurnToDestination()
    {
        // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함
        Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);

        //Quaternion.RotateTowards(현재의 rotation값, 최종목표rotation 값, 최대 회전각)
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        //Vector3.MoveTowards(시작지점, 목표지점,최대이동거리)
        transform.position = Vector3.MoveTowards(transform.position, curTargetPos,moveSpeed * Time.deltaTime);

        if (curEnemy == null)
        {
            //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
            if (transform.position == curTargetPos)
            {
                ChangeState(State.Idle, PlayerAni.ANI_IDLE);
            }
        }
        else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

EnemyFSM스크립트를 선택하고 수정합니다

 void AttackState()
    {
        if (player.GetComponent<PlayerFSM>().currentState == PlayerFSM.State.Dead)
        {
            ChangeState(State.NoState, EnemyAni.IDLE);
        }
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            attackTimer += Time.deltaTime;
        }
    }

 

 

 

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

public class EnemyFSM : MonoBehaviour
{
    public enum State
    {
        Idle,  //정지
        Chase,  //추적
        Attack,  //공격
        Dead,   //사망
        NoState  //아무 일도 없는 상태
    }

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

    CharacterController controller;

    float chaseDistance = 5f; // 플레이어를 향해 몬스터가 추적을 시작할 거리
    float attackDistance = 2.5f; // 플레이어가 안쪽으로 들오오게 되면 공격을 시작
    float reChaseDistance = 3f; // 플레이어가 도망 갈 경우 얼마나 떨어져야 다시 추적

    float rotAnglePerSecond = 360f; // 초당 회전 각도
    float moveSpeed = 1.3f; // 몬스터의 이동 속도

    float attackDelay = 2f;
    float attackTimer = 0f;

    public ParticleSystem hitEffect;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

    void Start()
    {
        myAni = GetComponent<EnemyAni>();
        myParams = GetComponent<EnemyParams>();
        myParams.deadEvent.AddListener(CallDeadEvent);

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

        player = GameObject.FindGameObjectWithTag("Player").transform;
        playerParams = player.gameObject.GetComponent<PlayerParams>();

        hitEffect.Stop();
        HideSelection();
    }

    //몬스터가 리스폰 될때 초기화 상태로 함
    public void AddToWorldAgain()
    {
        //  리스폰 오브젝트에서 처음 생성될때의 위치와 같게 함
        transform.position = originPos;

        GetComponent<EnemyParams>().InitParams();
        GetComponent<BoxCollider>().enabled = true;
    }
    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

    //몬스터가 죽는 순간 처리 명령어
    void CallDeadEvent()
    {
        ChangeState(State.Dead, EnemyAni.DIE);

        //몬스터가 죽은후 아이템 및 동전을 생성한다
        ObjectManager.instance.DropCoinToPosition(transform.position, myParams.rewardMoney);

        player.gameObject.SendMessage("CurrentEnemyDead");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

    public void ShowHitEffect()
    {
        hitEffect.Play();
    }

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                IdleState();
                break;
            case State.Chase:
                ChaseState();
                break;
            case State.Attack:
                AttackState();
                break;
            case State.Dead:
                DeadState();
                break;
            case State.NoState:
                NoState();
                break;          
        }
    }

    public void ChangeState(State newState, string aniName)
    {
        if (currentState == newState)
        {
            return;
        }

        currentState = newState;
        myAni.ChangeAni(aniName);
    }
    void IdleState()
    {
        if (GetDistanceFromPlayer() < chaseDistance)
        {
            ChangeState(State.Chase, EnemyAni.WALK);
        }
    }

    void ChaseState()
    {
        //몬스터가 공격 가능 거리 안으로 들어가면 공격 상태
        if (GetDistanceFromPlayer() < attackDistance)
        {
            ChangeState(State.Attack, EnemyAni.ATTACK);
        }
        else
        {
            TurnToDestination();
            MoveToDestination();
        }   
    }

    void AttackState()
    {
        if (player.GetComponent<PlayerFSM>().currentState == PlayerFSM.State.Dead)
        {
            ChangeState(State.NoState, EnemyAni.IDLE);
        }
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            attackTimer += Time.deltaTime;
        }
    }
    void DeadState()
    {
        GetComponent<BoxCollider>().enabled = false;
    }
    void NoState()
    {

    }

    void TurnToDestination()
    {
        Quaternion lookRotation = Quaternion.LookRotation(player.position - transform.position);

        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        // transform.position = Vector3.MoveTowards(transform.position, player.position, moveSpeed * Time.deltaTime);

        //몬스터의 이동을 캐릭터 컨트롤러로 바꿈 몬스터가 전방으로 moveSpeed  만큼의 빠르기로 이동하게 됨
        controller.Move(transform.forward * moveSpeed * Time.deltaTime);
    }

    //플레이어와 거리을 재는 함수
    float GetDistanceFromPlayer()
    {
        float distance = Vector3.Distance(transform.position, player.position);
        return distance;
    }

    // Update is called once per frame
    void Update()
    {
        UpdateState();
    }
}

 

 

게임을 실행하고 플레이어가 죽으면  GameOver 텍스쳐가 잘나오는지 확인 합니다

 

 

RPG_Game_Project.z01
10.00MB
RPG_Game_Project.z02
10.00MB
RPG_Game_Project.z03
10.00MB
RPG_Game_Project.z04
10.00MB
RPG_Game_Project.zip
8.63MB

반응형

+ Recent posts