유니티 에셋을 들어가서 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");
}
}
먼저 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 오브젝트를 연결합니다
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);
}
}
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()
{
}
}
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 파일을 다운로드하여 유니티 엔진의 Project Assets의 Models 폴더를 만들고 car1 파일을 저장합니다
다운로드한 car1 파일을 선택하고 Model -> Scale Factor 0.01을 합니다
Models 파일에 있는 car1 파일을 드래그하여 Hierarchy 뷰 에 갖다 놓고 Scale 0.5 ,0.5, 0.5에 맞춥니다
빈오브젝트를 만들고 이름을 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);
}
}
}
주위 배경을 위해서 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가 제한되는 움직임을 보입니다
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);
}
}
아래 동영상과 같이 마우스에 따라 총이 움직이고 총알이 마우스에 따라 방향대로 발사되는 것을 확인합니다
유니티 프로젝트 파일을 만들고 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 스크립트를 붙힘니다
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 오브젝트를 선택하고 아래 그림과 같이 속성을 바꾸고 텍스쳐와 프리 팹을 연결합니다
하이라키뷰에 있는 메인 카메라를 선택하고 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 합니다
카드 판정에 따른 게임의 상태를 표시하고 카드가 모두 열리면 다음 스테이지로 이동하는 프로그램을 만들어 보겠습니다
먼제 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;
}
}
카드를 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]));
}
}
}
하이 라키 뷰에 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 값을 바꿉니다
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을 아래 그림과 같이 바꿉니다
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 자리에 꽥 프리팹을 연결합니다
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);
}
}
간단한 총쏘기 게임을 만들건대 게임을 좋아하는 분들이 쉽게 만들 수 있도록 설명하도록 노력하겠습니다
유니티 엔진을 열고 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);
}
}
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()
{
}
}
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();
}
}