황금을 얻기 위해 음침하고 요괴들이 많이 출 물 하는 숲에서 요괴들을 사냥합니다 황금을 얻기 위해 많은 요괴를 잡아야 합니다 스테이지마다 60초 동안 요괴 사냥을 합니다 요괴를 많이 잡을수록 황금을 많이 얻습니다 요괴들을 사냥하지 못하고 요괴의 공격을 받으면 에너지가 줄어듭니다 스테이지가 올라 갈수록 요괴들이 많이 나옵니다 황금을 많이 얻으면 더욱 업그레이드된 사냥총을 구입할 수 있습니다
유니티 프로젝트 파일을 만들고 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을 아래 그림과 같이 바꿉니다
마징가 제트는 가장 큰 특징은 뭐니 뭐니 해도 인간이 로봇에 탑승한다는 개념이다. 이는 70년대 일본에서 자동차가 상당히 고급 물품이었던 데다 자동차를 조종하는 기술이 특수한 기술로여겨져 아무나 할 수 없는 동경의 대상으로 여겨진 시대상을 반영한다.
그리고 마징가 Z는 여기서 더 나아가 인간과 로봇이 일체화된다는 구성을 선보인다. 작중의 묘사를 보면 카부토 코우지는 단순히 마징가 Z에 올라타는 것만이 아니다. 마징가 Z의 위기가 곧 카부토 코우지의 위기이고, 코우지의 사망이 곧 마징가 Z의 불능화로 인식되고 있다는 점을 알 수 있다. 또 마징가 Z의 캐릭터성이 오직 코우지가 탑승한 것을 전제로 형성되고 있는 것도 함께 눈여겨 봐야 할 부분이다. 동서양을 통틀어 모든 SF에서는 로봇이란 독립된 캐릭터를 부여받기 때문에 마징가 Z의 묘사는 낯선 것이다.
코우지가 마징가 Z에 타는 것은 단순한 조종자가 되는 정도를 넘어서 마징가 Z라는 로봇의 "자아"가 된다는 뜻이고, 마징가 Z는 코우지의 새로운 육체가 된다는 것이다. 따라서 이것은 본질적으로 파일럿으로서 탑승하는 일이나 사이보그화 되는 것(인간이 기계가 되는 것)과는 근본적인 차이를 가진다. 마징가 Z와 코우지는 생물과 기계를 초월하여 양분되어서는 성립할 수 없는 동일시된 존재이라는 말이며, 이 점은 서구 SF계에서는 보기 힘든 철저히 동양적인 사고 관인 것이다. 이 때문에 서양에선 마징가 Z를 비롯한 일본의 거대 로봇 애니메이션을 일본에만 있는 것이라 하며 재패니즈 로봇, 쇼군 로봇이라고 따로 분류했을 정도였다.
작품 내에서도 이런 속성은 "부려지는 기계"인기계수와 대비되며 더욱 뚜렷해진다. 일부 기계수는 나름대로 자아를 가지고 창조주인 닥터 헬에게 반항하기도 하지만, 그들은 어디까지나 "기계"이고 AI이다. 부여된 속성에 안주하고 거기에 충실할 뿐인 로봇이다. 인간이자 로봇인 마징가 Z는 그들이 지닌 기기묘묘한 능력에 대처하기 위해 스스로 성장하며 보다 완전한 존재로 거듭나게 되지만, 기계수들은 결국 성장하지 못한다. 이런 차이를 극적으로 표현한 에피소드가제트 스크랜더의 등장이다.
마징가 Z의 수많은 강화극 중에서도 특히 제트 스크랜더가 고평가되는 것은 마징가 Z가 가진 근본적인 한계를 끝없는 향상심을 통해 극복하게 되는 것 때문인데, 여기서 기계수는 "하늘을 날 수 있는 이점" 때문에 한때 마징가 Z를 궁지로 몰아넣지만 단지 그렇게 부여된 속성 이상 아무것도 가지지 못하고 그 이상을 가지도록 노력할 수도 없는 존재이기 때문에 끊임없이 향상되는 마징가 Z 앞에서 결국 마지막에 무릎을 꿇을 수밖에 없다. 이런 개조극은 기계로서의 속성과 성장을 통한 인간 된 속성을 동시에 표현했던, 매우 인상적인 연출로 오늘날까지도 기계와 인간의 차이를 융합시킨 뛰어난 해답이라 평가할 수 있겠다.
나가이 고는 탑승형이란 개념을 착안한 것에 대해서철인 28호같은 조종형은 조종자가 안전한 곳에서 조종만 하는 것은 지도자가 젊은이 병사들을 전장으로 보내서 조종하는 현대전과 비슷한 맥락의 비겁한 느낌이기 때문에 배제하였고,철완 아톰같은 인공지능 로봇은 아무리 인공지능 기술이 발전해도로봇은 인간보다 강한 육체를 가지고 있어 선민의식을 가지게 될 것이고, 이것은 곧 인간성의 결여로 이어진다고 생각해서 부정적으로 보았다. 나가이 고는 인간이 직접 탑승해서 목숨을 걸고 전장에 나가는 로봇을 만들어 인간성과 사람을 죽인다는 행위의 책임감을 강조하였고 "신도 악마도 될 수 있다" 라는 대사는 곧 싸움에는 책임을 지라는 의미라고 한다
엽총의 표준 게이지는 2와 3/4인치 약실에 12, 16, 20 구경을 말한다. 1960년경만 해도 이 구경의 약실은 2.5인치였다. 이러한 구형 총들은 아직도 많이 사용되고 있다. 그러므로 구형엽총을 사용하는 사람들은 엽탄 선택에 세심한 주의를 기울여야 한다. 12 구경은 지름이 18.4mm인 엽총으로 세계적으로 가장 널리 사용되고 있는 총이다. 16 구경은 내부 지름이 17mm인 총으로 일부 층이 사용하고 있다. 20 구경은 16mm로 정교하고 세밀하여, 반동이 작은 총을 선호하는 사람이 애호한다. 12 구경은 유럽의 경우 32g 엽탄을 주로 사용하고 있으며 16 구경의 경우 28g, 20 구경의 경우 24g을 사용한다. 총의 무게도 구경이 작아 질수록 줄어든다. 즉, 12 구경은 2.9∼3.9kg, 16 구경은 2.6∼2.9kg, 20 구경은 2.4∼2.7kg이다. 따라서 구경이 작아질수록 총기를 다루기는 편하지만 성능은 떨어진다. 그러나 최근에는 기관부나 탄환장전장치를 알루미늄 합금으로 제조하는 기술이 개발되어 12 구경의 무게를 2.7kg 또는 그 이하로 감소시킨 총이 생산되고 있어 총의 무게에 대한 개념이 달라지고 있다. 16 구경이나 20 구경은 큰 멧돼지 사냥에는 효력이 떨어지기 때문에 바람직하지 않다. 보통 26∼28g의 산탄을 내뿜는 20 구경은 숲 속에서의 사냥과 근거리 사냥에 훌륭한 성능을 발휘한다. 물론 오리 사냥이나 기타 큰 게임의 사냥에도 적절하지 않다. 따라서 유럽지역에서는 12구경을 다기능 구경으로 인정받게 될 것으로 보이며 앞으로도 12 구경은 최고의 신뢰를 얻게 될 것으로 보인다. 12 구경 총은 24∼42g의 산탄을 뿜어낼 수 있는 총으로 거의 모든 상황에서 사냥하기에 적당한 것으로 보고 있다. 다만 화약이나 가스압력을 고려해 볼 때 30∼36g의 엽탄을 사용하는 것이 효과적이다. 탄환의 속도, 패턴, 명중도 등의 효력이 가장 높기 때문이다. 20 구경은 탐미적인 엽사들을 위해 그 특수성을 강화시켜 나가야 될 것으로 판단된다. 20 구경으로 가장 좋은 결과를 얻으려면 24∼28g의 엽탄을 사용해야 한다.
전 세계가 초토화되고 좀비들의 습격이 시작됩니다 미친 좀비들이 사정없이 공격하여 옵니다 쳐들어 오는 좀비를 철조망이 무너지지 않게 좀비들을 무찌릅니다 좀비들을 없애면 랜덤으로 골드가 생성됩니다 생성된 골드를 획득하고 메인 요새 화면으로 돌아가 캐릭터를 강력하게 할 수 있는 무기를 구매합니다 총알도 강력하게 업그래드 할 총알을 구매할 수 있습니다 무기를 구매하여 포켓 지갑에 넣고 인간 요새를 지키기 위해 철조망을 사이에 두고 좀비들을 무찌릅니다 스테이지가 올라갈수록 좀비들의 숫자가 늘어나고 좀비들의 체력이 올라 감니다 그리고 강력한 좀비 보스들이 출현 합니다 화끈하게 좀비들을 쳐부술 수 있는 무기를 구해하여 좀비들을 없애고 스테이지를 올려 점점 강력한 좀비들과 대결을 하세요 지구의 운명이 당신에게 있습니다
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);
}
}
현재 미군 제식 소총이며 5.56 X 45 탄환을 사용하며 미 콜트사에서 생산한 단축형 자동소총이다 국군 하기 분류법에 따르면 자동보총이다 M16 A2의 단축형 M4 카빈의 개량형이며 차이점으로는 3 점사가 불가해지고 완전 자동 사격이 가능해졌으며 광학장비 장착을 위한 캐링 핸들 탈착이 가능하다는 점이다 베트남전 당시 특수 부대에게 보급된 XM177에 뿌리를 두고 있으며 M727 아부다비 카빈과 M733에 영향을 받았다
그리고 M16A2 보다 짧고 가벼우며 80% 정도의 부품들이 M16 A2와 호환된다
M4A1은 지속적인 자동 사격시 발생하는 열을 효과적으로 발산시키기 위한 굵은 총열을 가지고 있다
변수의 이름을 지을 때는 변수에 저장할 값의 특지를 가장 정확히 표현할 수 있는 이름을 찾으려고 노력해야 한다
name, speed, score처럼 포괄적인 이름이 아닌 playerName, carSpeed, opponentScore처럼 구체적인 이름을 하는 것이 좋다
변수의 동작 방식
유니티의 빈 오브젝트를 하나 만들어서 아래와 같이 VariableTest 스크립트를 작성하고 컴포넌트를 추가한다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VariableTest : MonoBehaviour
{
public int myNumber = 9;
void Start()
{
Debug.Log(2 + 9);
Debug.Log(11 + myNumber);
}
void Update()
{
}
유니티의 Play 클릭하면 아래와 같은 결과 값이 콘솔 창에 나온다
변수를 사용하면 원하는 값을 저장하거나 꺼내 쓸 수 있으며 변수 이름 그대로 사용해서 연산을 수행할 수 있다