using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 3f;
public bool isTouchTop;
public bool isTouchLeft;
public bool isTouchRight;
public bool isTouchBottom;
public float maxShortDelay = 0.1f;//실제 딜레이
public float curShortDelay;//한발 쏜 딜레이
public GameObject bulletObjA;
private Animator anim;
void Start()
{
anim = GetComponent<Animator>();
}
void Update()
{
Move();
Fire();
Reload();
}
void Move()
{
Vector3 curPos = this.transform.position;
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
this.transform.position = curPos + nextPos;
if(Input.GetButtonDown("Horizontal") || Input.GetButtonDown("Vertical"))
{
anim.SetInteger("Input", (int)h);
}
}
void Fire()
{
if (curShortDelay < maxShortDelay)
return;
if (Input.GetKey(KeyCode.Space))
{
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
curShortDelay = 0f;
}
}
void Reload()
{
curShortDelay += Time.deltaTime;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public int dmg;
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.tag == "BorderBullet")
{
Destroy(gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float speed;
public int health;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
Rigidbody2D rigid;
public GameObject explosionPrefab;
void Start()
{
spriteRenderer = GetComponent<SpriteRenderer>();
rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.down * speed;
}
void ReturnSprite()
{
spriteRenderer.sprite = sprites[0];
}
public void OnHit(int dmg)
{
health -= dmg;
spriteRenderer.sprite = sprites[1];
Invoke("ReturnSprite", 0.1f);
if (health <= 0)
{
GameObject explosion = Instantiate(explosionPrefab, transform.position, Quaternion.identity);
Destroy(explosion, 1.0f); // 폭발 효과는 1초 후에 파괴
Destroy(gameObject); // 적도 파괴
//Destroy(gameObject, 0.2f);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
{
Destroy(gameObject);
}
else if (collision.gameObject.tag == "Bullet")
{
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
OnHit(bullet.dmg);
Destroy(collision.gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public float maxSpawnDelay;
public float curSpawnDelay;
void Update()
{
curSpawnDelay += Time.deltaTime;
if(curSpawnDelay > maxSpawnDelay)
{
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
}
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 5);
Instantiate(enemyObjs[ranEnemy],
spawnPoints[ranPoint].position,
spawnPoints[ranPoint].rotation);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Explosion : MonoBehaviour
{
private Animator anim;
public void OnEnable()
{
anim = GetComponent<Animator>();
StartCoroutine(CoExplosion());
}
IEnumerator CoExplosion()
{
yield return new WaitForSeconds(0.5f);
Destroy(gameObject);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
public class PlayerCtrl : MonoBehaviour
{
private Transform tr;
public float moveSpeed = 10.0f;
public float turnSpeed = 80.0f;
private Animation anim;
private readonly float initHp = 100f;
public float currentHp;
public delegate void PlayerDieHandler(); //대리자 정의
public static event PlayerDieHandler OnPlayerDie; //이벤트 변수 정의
private void Awake()
{
//제일먼저 호출되는 함수
//스크리브가 비활성화되있어도 호출되는 함수
}
private void OnEnable()
{
//두번째로 호출되는 함수
//스크립트가 활성화 될때마다 호출되는 함수
}
IEnumerator Start()
{
this.currentHp = this.initHp;
// 세번째로 호출되는 함수
//Update함수가 호출되기 전에 호출되는 함수
//코루틴으로 호출될수 있는 함수
//예)
//IEnumerator Start(){}
tr = GetComponent<Transform>();
this.anim = GetComponent<Animation>();
//이름으로 실행 하거나
this.anim.Play("Idle");
//이름으로 가져와서 플레이 하거나
//this.anim.clip = this.anim.GetClip("Idle");
//this.anim.Play();
this.turnSpeed = 0f;
yield return new WaitForSeconds(0.3f);
this.turnSpeed = 80f;
}
private void FixedUpdate()
{
//일정간격으로 호출되는 함수 (기본값은 0.02초)
//물리엔진의 계산 주기와 일치
}
void Update()
{
//프레임마다 호출되는 함수
//호출 간격이 불규칙하다
//화면의 렌더링 주기와 일치 함
float h = Input.GetAxis("Horizontal"); // -1.0 ~ 0 ~ 1
float v = Input.GetAxis("Vertical"); // -1.0 ~ 0 ~ 1
float r = Input.GetAxis("Mouse X");
//Debug.Log("h=" + h);
//Debug.Log("v=" + v);
Debug.Log("r=" + r);
//this.tr.Translate(Vector3.forward * 1 * 0.01f , Space.Self); //로컬좌표 기준으로 이동
//this.tr.Translate(Vector3.forward * 1 * Time.deltaTime, Space.World); //월드좌표 기준으로 이동
//매 프레임마다 1유닛씩 이동 => 0.02초
//this.tr.Translate(Vector3.forward * 1 * Time.deltaTime);
//매 초마다 1유닛씩 이동
//프레임 레이트가 서로다른 기기에서도 개발자가 지정한 일정한 속도로 이동하기 위함
//this.tr.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed); //방향 * 속도 * 시간 => forward = (0,0,1) // v => -1~ 1 사이의 값 => v를 사용해서 앞뿐만이 아닌 뒤로도 이동가능
//전후좌우 이동방향 벡터 계산
Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h); // right = (1,0,0)
//방향 * 속도 * 시간
this.tr.Translate(moveDir.normalized * moveSpeed * Time.deltaTime);
//Vector3.up 축으로 turnSpeed만큼
this.tr.Rotate(Vector3.up * r * this.turnSpeed);
//캐릭터 애니메이션 실행
PlayerAnim(h, v);
}
private void LateUpdate()
{
//Update함수가 종료된 후 호출 되는 함수
}
private void PlayerAnim(float h, float v)
{
//키보드 입력값을 기준으로 동작할 애니메이션 실행
if (v >= 0.1f)
{
//0.25f초간 이전 애니메이션과 실행할 애니메이션을 보간
this.anim.CrossFade("RunF", 0.25f); //전진 애니메이션 실행
}
else if (v <= -0.1f)
{
this.anim.CrossFade("RunB", 0.25f);//후진
}
else if (h >= 0.1f)
{
this.anim.CrossFade("RunR", 0.25f);//오른쪽
}
else if (h <= -0.1f)
{
this.anim.CrossFade("RunL", 0.25f);//왼쪽
}
else
{
this.anim.CrossFade("Idle", 0.25f);//기본
}
}
private void OnTriggerEnter(Collider other)
{
if(this.currentHp >= 0f && other.CompareTag("Punch"))
{
this.currentHp -= 10f;
Debug.Log($"Player hp = {currentHp / initHp}");
if (this.currentHp <= 0f)
{
this.PlayerDie();
}
}
}
private void PlayerDie()
{
Debug.Log("Player Die!");
////Monster 태그를 가진 오브젝트들을 검색
//GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster");
////모든 몬스터의 OnplayerDie함수실행
//foreach(GameObject monster in monsters)
//{
// monster.SendMessage("OnPlayerDie", SendMessageOptions.RequireReceiver);
//}
PlayerCtrl.OnPlayerDie(); //ㄷ대리차 호출(이벤트 발생 Dispatch)
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletCtrl : MonoBehaviour
{
public float damage = 20.0f;
public float force = 1500f;
private Rigidbody rb;
void Start()
{
this.rb = this.GetComponent<Rigidbody>();
this.rb.AddForce(this.transform.forward * force);
Destroy(gameObject, 3f);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowCam : MonoBehaviour
{
public Transform targetTr;
private Transform camTr;
//대상으로 부터 떨어질 거리
[Range(2.0f, 20.0f)]
public float distance = 10.0f;
//Y축으로 이동할 높이
[Range(0.0f, 10.0f)]
public float height = 2.0f;
//반응속도
public float damping = 10f;
//SmoothDamp에 사용할 속도 변수
private Vector3 velocity = Vector3.zero;
//카메라 LookAt OffSet
public float targetOffset = 2.0f;
void Start()
{
this.camTr = GetComponent<Transform>(); //Transform 객체 캐싱
}
private void LateUpdate()
{
//추적할 대상의 뒤쪽 distance만큼 이동
//높이를 height만큼 이동
Vector3 pos = this.targetTr.position +
(-targetTr.forward * distance) +
(Vector3.up * height);
//구면 선형 보간 함수를 사용해 부드럽게 위치를 변경
//시작위치, 목표 위치, 시간
//this.camTr.position = Vector3.Slerp(this.camTr.position, pos, Time.deltaTime * damping);
//시작위치, 목표위치, 현재속도, 목표 위치까지 도달할 시간
this.camTr.position = Vector3.SmoothDamp(this.camTr.position, pos, ref velocity, damping);
//Camera를 피벗 좌표를 향해 회전
this.camTr.LookAt(this.targetTr.position + (targetTr.up * targetOffset));
}
}
그런데 플레이를 하다가 한번에 두발을 맞으면 체력이 2가 줄어드는 곳을 볼 수 있는데 이것을 수정할 것이다.
bool 변수를 다시 초기화 하는 부분도 꼭 구현해야한다.
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;
public class Player : MonoBehaviour
{
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchRight;
public bool isTouchLeft;
public int life;
public int score;
public float speed;
public float power;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameManager manager;
public bool isHit;
private Animator anim;
private void Awake()
{
anim = GetComponent<Animator>();
}
void Move()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") ||
Input.GetButtonDown("Vertical"))
{
anim.SetInteger("Input", (int)h);
}
}
private void Update()
{
Move();
Fire();
Reload();
}
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
if (curShorDelay < maxShotDelay)
return;
switch (power)
{
case 1://Power One
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShorDelay = 0;
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch(collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Right":
isTouchRight = true;
break;
case "Left":
isTouchLeft = true;
break;
}
}
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
if (isHit)
{
return;
}
isHit = true;
life--;
manager.UpdateLifeIcon(life);
if(life == 0)
{
manager.GameOver();
}
else
{
manager.RespawnPlayer();
}
gameObject.SetActive(false);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Right":
isTouchRight = false;
break;
case "Left":
isTouchLeft = false;
break;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public int dmg;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
}
}
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public string enemyName;
public int enemyScore;
public float speed;
public int health;
public Sprite[] sprites;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject player;
SpriteRenderer spriteRenderer;
Rigidbody2D rigid;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.down * speed;
}
private void Update()
{
Fire();
Reload();
}
void Fire()
{
if (curShorDelay < maxShotDelay)
return;
if (enemyName == "S")
{
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
}
else if (enemyName == "L")
{
GameObject bulletR = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
}
curShorDelay = 0; // 발사 후 딜레이 초기화는 발사 이후에 처리되도록 이동
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnHit(int dmg)
{
health -= dmg;
spriteRenderer.sprite = sprites[1]; //피격시 1번
Invoke("ReturnSprite", 0.1f);
if(health <= 0)
{
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
Destroy(gameObject);
}
}
void ReturnSprite()
{
spriteRenderer.sprite = sprites[0];
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "BorderBullet")
{
Destroy(gameObject );
}
else if(collision.gameObject.tag == "PlayerBullet")
{
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
OnHit(bullet.dmg);
Destroy(collision.gameObject);
}
}
}
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public float maxSpawnDelay;
public float curSpawnDelay;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public GameObject gameOverSet;
private void Update()
{
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > maxSpawnDelay)
{
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
//UI Score
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3); //소환될 적
int ranPoint = Random.Range(0, 9); //소환될 위치
GameObject enemy = Instantiate(enemyObjs[ranEnemy],
spawnPoints[ranPoint].position,
spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
//스피드도 가지고 와야한다.
Enemy enemyLogic = enemy.GetComponent<Enemy>();
//적비행기 생성 직후 플레이에 변수를 넘겨준다.
enemyLogic.player = player;
if (ranPoint == 5 || ranPoint == 6)
{
enemy.transform.Rotate(Vector3.back * 90); //z축으로 90도
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}else if(ranPoint == 7 || ranPoint == 8)
{
enemy.transform.Rotate(Vector3.forward * 90); //z축으로 90도
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else
{
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
}
public void UpdateLifeIcon(int life)
{
//UI Init Disable
for (int index = 0; index < 3; index++)
{
lifeImage[index].color = new Color(1, 1, 1, 0);
}
//UI Life Active
for (int index = 0; index < life; index++)
{
lifeImage[index].color = new Color(1, 1, 1, 1);
}
}
public void RespawnPlayer()
{
Invoke("RespawnPlayerExe", 2f);
}
private void RespawnPlayerExe()
{
player.transform.position = Vector3.down * 3.5f;
player.SetActive(true);
Player playerLogic = player.GetComponent<Player>();
playerLogic.isHit = false;
}
public void GameOver()
{
gameOverSet.SetActive(true);
}
public void GameRetry()
{
SceneManager.LoadScene(0);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
public class PlayerCtrl : MonoBehaviour
{
private Transform tr;
public float moveSpeed = 10.0f;
public float turnSpeed = 80.0f;
private Animation anim;
void Awake()
{
//제일먼저 호출되는 함수
//스크리브가 비활성화되있어도 호출되는 함수
}
private void OnEnable()
{
//두번째로 호출되는 함수
//스크립트가 활성화 될때마다 호출되는 함수
}
private void Start()
{
//세번째로 호출되는 함수
//Update함수가 호출되기 전에 호출되는 함수
//코루틴으로 호출될 수 있는 함수
//예)
//IEnumerator Start(){}
tr = GetComponent<Transform>();
anim = GetComponent<Animation>();
this.anim.Play("Idle");
//this.anim.clip = this.anim.GetClip("Idle");
//anim.Play();
}
private void FixedUpdate()
{
//일정간격으로 호출되는 함수 (기본값은 0.02초)
//물리엔진의 계산 주기와 일치
}
void Update()
{
//프레임마다 호출되는 함수
//호출 간격이 불규칙하다
//화면의 렌더링 주기와 일치 함
float h = Input.GetAxis("Horizontal"); // -1.0 ~ 0 ~ 1
float v = Input.GetAxis("Vertical"); // -1.0 ~ 0 ~ 1
float r = Input.GetAxis("Mouse X");
//Debug.Log("h=" + h);
//Debug.Log("v=" + v);
Debug.Log("r=" + r);
//this.tr.Translate(Vector3.forward * 1 * 0.01f , Space.Self); //로컬좌표 기준으로 이동
//this.tr.Translate(Vector3.forward * 1 * Time.deltaTime, Space.World); //월드좌표 기준으로 이동
//매 프레임마다 1유닛씩 이동 => 0.02초
//this.tr.Translate(Vector3.forward * 1 * Time.deltaTime);
//매 초마다 1유닛씩 이동
//프레임 레이트가 서로다른 기기에서도 개발자가 지정한 일정한 속도로 이동하기 위함
//this.tr.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed); //방향 * 속도 * 시간 => forward = (0,0,1) // v => -1~ 1 사이의 값 => v를 사용해서 앞뿐만이 아닌 뒤로도 이동가능
//전후좌우 이동방향 벡터 계산
Vector3 moveDir = (Vector3.forward * v) + (Vector3.right * h); // right = (1,0,0)
//방향 * 속도 * 시간
this.tr.Translate(moveDir.normalized * moveSpeed * Time.deltaTime);
//Vector3.up 축으로 turnSpeed만큼
this.tr.Rotate(Vector3.up * r * this.turnSpeed);
//캐릭터 애니메이션 실행
PlayerAnim(h, v);
}
private void LateUpdate()
{
//Update함수가 종료된 후 호출 되는 함수
}
private void PlayerAnim(float h, float v)
{
//키보드 입력값을 기준으로 동작할 애니메이션 실행
if (v >= 0.1f)
{
//0.25f초간 이전 애니메이션과 실행할 애니메이션을 보간
this.anim.CrossFade("RunF", 0.25f); //전진 애니메이션 실행
}
else if (v <= -0.1f)
{
this.anim.CrossFade("RunB", 0.25f);//후진
}
else if (h >= 0.1f)
{
this.anim.CrossFade("RunR", 0.25f);//오른쪽
}
else if (h <= -0.1f)
{
this.anim.CrossFade("RunL", 0.25f);//왼쪽
}
else
{
this.anim.CrossFade("Idle", 0.25f);//기본
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletCtrl : MonoBehaviour
{
public float damage = 20.0f;
public float force = 1500f;
private Rigidbody rb;
void Start()
{
this.rb = this.GetComponent<Rigidbody>();
this.rb.AddForce(this.transform.forward * force);
Destroy(gameObject, 3f);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowCam : MonoBehaviour
{
public Transform targetTr;
private Transform camTr;
//대상으로 부터 떨어질 거리
[Range(2.0f, 20.0f)]
public float distance = 10.0f;
//Y축으로 이동할 높이
[Range(0.0f, 10.0f)]
public float height = 2.0f;
//반응속도
public float damping = 10f;
//SmoothDamp에 사용할 속도 변수
private Vector3 velocity = Vector3.zero;
//카메라 LookAt OffSet
public float targetOffset = 2.0f;
void Start()
{
this.camTr = GetComponent<Transform>(); //Transform 객체 캐싱
}
private void LateUpdate()
{
//추적할 대상의 뒤쪽 distance만큼 이동
//높이를 height만큼 이동
Vector3 pos = this.targetTr.position +
(-targetTr.forward * distance) +
(Vector3.up * height);
//구면 선형 보간 함수를 사용해 부드럽게 위치를 변경
//시작위치, 목표 위치, 시간
//this.camTr.position = Vector3.Slerp(this.camTr.position, pos, Time.deltaTime * damping);
//시작위치, 목표위치, 현재속도, 목표 위치까지 도달할 시간
this.camTr.position = Vector3.SmoothDamp(this.camTr.position, pos, ref velocity, damping);
//Camera를 피벗 좌표를 향해 회전
this.camTr.LookAt(this.targetTr.position + (targetTr.up * targetOffset));
}
}
1, 2, 3, 4포인트는 x축은 변함이 없을 것이고 y축은 -1로 고정하여 아래로 내려오게
5, 6 포인트는 왼쪽(-1), y축은 -1
7, 8 포인트는 오른쪽(1), y축은 -1
생성시 보더불릿에 닿으면 사라져서 생성되지 않으니 위치 재조정
그림과 같이 옆모양으로 나오게 하기 위해 회전을 추가
이제 적의 총알을 만들 것이다.
기존의 불렛b를 복사한후 프리팹모드에 들어가서 이름을 바꾸고 저장
스프라이트 변경후 박스콜라이더 크기 재설정
하나더 만들어준 후 태그 달기
플레이어에 있는 코드를 가져와서 부착
네임은 오브젝트네임 자체에 들어가 있으므로 이름 변경
적이 생성될때 플레이어를 알아야한다.
적 비행기 생성 직후 플레이어 변수를 넘겨준다.
목표물 방향 = 목표물위치 - 자신의 위치
큰 적은 두발을 쏜다.
플레이어는 인스턴스화가 되어있지 않기에 할당X
if문에 총알을 발사할 이름을 넣었기 때문에 이름을 할당
적들의 총알이 너무 빠르므로 속도를 조절
플레이어의 온트리거에 추가
게임매니저가 플레이러를 리스폰 시킬수 있다.
플레이어도 게임매니저를 알아야한다.
public GameManager manager;
2초 후에 플레이어가 부활하게 할 것이다.
플레이어 스크립트에 리스폰 추가
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;
public class Player : MonoBehaviour
{
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchRight;
public bool isTouchLeft;
private Animator anim;
public float speed;
public float power;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameManager manager;
private void Awake()
{
anim = GetComponent<Animator>();
}
void Move()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") ||
Input.GetButtonDown("Vertical"))
{
anim.SetInteger("Input", (int)h);
}
}
private void Update()
{
Move();
Fire();
Reload();
}
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
if (curShorDelay < maxShotDelay)
return;
switch (power)
{
case 1://Power One
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShorDelay = 0;
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch(collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Right":
isTouchRight = true;
break;
case "Left":
isTouchLeft = true;
break;
}
}
else if(collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet")
{
manager.RespawnPlayer();
gameObject.SetActive(false);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Right":
isTouchRight = false;
break;
case "Left":
isTouchLeft = false;
break;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public int dmg;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
}
}
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public string enemyName;
public float speed;
public int health;
public Sprite[] sprites;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject player;
SpriteRenderer spriteRenderer;
Rigidbody2D rigid;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.down * speed;
}
private void Update()
{
Fire();
Reload();
}
void Fire()
{
if (curShorDelay < maxShotDelay)
return;
if (enemyName == "S")
{
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position;
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
}
else if (enemyName == "L")
{
GameObject bulletR = Instantiate(bulletObjB, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjB, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse); // 방향 벡터를 정규화하여 힘을 가하는 부분 수정
}
curShorDelay = 0; // 발사 후 딜레이 초기화는 발사 이후에 처리되도록 이동
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnHit(int dmg)
{
health -= dmg;
spriteRenderer.sprite = sprites[1]; //피격시 1번
Invoke("ReturnSprite", 0.1f);
if(health <= 0)
{
Destroy(gameObject);
}
}
void ReturnSprite()
{
spriteRenderer.sprite = sprites[0];
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "BorderBullet")
{
Destroy(gameObject );
}
else if(collision.gameObject.tag == "PlayerBullet")
{
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
OnHit(bullet.dmg);
Destroy(collision.gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public float maxSpawnDelay;
public float curSpawnDelay;
public GameObject player;
private void Update()
{
curSpawnDelay += Time.deltaTime;
if(curSpawnDelay > maxSpawnDelay)
{
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
}
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3); //소환될 적
int ranPoint = Random.Range(0, 9); //소환될 위치
GameObject enemy = Instantiate(enemyObjs[ranEnemy],
spawnPoints[ranPoint].position,
spawnPoints[ranPoint].rotation);
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
//스피드도 가지고 와야한다.
Enemy enemyLogic = enemy.GetComponent<Enemy>();
//적비행기 생성 직후 플레이에 변수를 넘겨준다.
enemyLogic.player = player;
if (ranPoint == 5 || ranPoint == 6)
{
enemy.transform.Rotate(Vector3.back * 90); //z축으로 90도
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}else if(ranPoint == 7 || ranPoint == 8)
{
enemy.transform.Rotate(Vector3.forward * 90); //z축으로 90도
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else
{
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
}
public void RespawnPlayer()
{
Invoke("RespawnPlayerExe", 2f);
}
private void RespawnPlayerExe()
{
player.transform.position = Vector3.down * 3.5f;
player.SetActive(true);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public float maxSpawnDelay;
public float curSpawnDelay;
private void Update()
{
curSpawnDelay += Time.deltaTime;
if(curSpawnDelay > maxSpawnDelay)
{
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
}
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3); //소환될 적
int ranPoint = Random.Range(0, 5); //소환될 위치
Instantiate(enemyObjs[ranEnemy],
spawnPoints[ranPoint].position,
spawnPoints[ranPoint].rotation);
}
}
적들의 비행선이 보더에 걸리면사라지므로 씬보다 위 보더보다 아래에 에너미포인트 그룹을 위치시킨다.
각각의 포인트의 x좌표를 -1.8부터 1.8까지 위치
각각을 어싸인
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;
public class Player : MonoBehaviour
{
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchRight;
public bool isTouchLeft;
private Animator anim;
public float speed;
public float power;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
private void Awake()
{
anim = GetComponent<Animator>();
}
void Move()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") ||
Input.GetButtonDown("Vertical"))
{
anim.SetInteger("Input", (int)h);
}
}
private void Update()
{
Move();
Fire();
Reload();
}
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
if (curShorDelay < maxShotDelay)
return;
switch (power)
{
case 1://Power One
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShorDelay = 0;
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch(collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Right":
isTouchRight = true;
break;
case "Left":
isTouchLeft = true;
break;
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Right":
isTouchRight = false;
break;
case "Left":
isTouchLeft = false;
break;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
public int dmg;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
}
}
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float speed;
public int health;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
Rigidbody2D rigid;
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
rigid = GetComponent<Rigidbody2D>();
rigid.velocity = Vector2.down * speed;
}
private void OnHit(int dmg)
{
health -= dmg;
spriteRenderer.sprite = sprites[1]; //피격시 1번
Invoke("ReturnSprite", 0.1f);
if(health <= 0)
{
Destroy(gameObject);
}
}
void ReturnSprite()
{
spriteRenderer.sprite = sprites[0];
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "BorderBullet")
{
Destroy(gameObject );
}
else if(collision.gameObject.tag == "PlayerBullet")
{
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
OnHit(bullet.dmg);
Destroy(collision.gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject[] enemyObjs;
public Transform[] spawnPoints;
public float maxSpawnDelay;
public float curSpawnDelay;
private void Update()
{
curSpawnDelay += Time.deltaTime;
if(curSpawnDelay > maxSpawnDelay)
{
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
}
void SpawnEnemy()
{
int ranEnemy = Random.Range(0, 3); //소환될 적
int ranPoint = Random.Range(0, 5); //소환될 위치
Instantiate(enemyObjs[ranEnemy],
spawnPoints[ranPoint].position,
spawnPoints[ranPoint].rotation);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
}
}
기존에 만들어 놓았던 Border를 복사해서 이름을 바꾸고
경계선을 재설정
태그를 새로 만든 후 할당
그럼 총알이 경계선에 닿으면 사라진다.
이제 총알을 만들 로직을 Player 스크립트에 작성할 것이다.
전 페이지에서 만들었던 업데이트문안에 있는 함수를 따로 캡슐화로 분리한 후 이름을 Move로 바꾸었고,
using System.Collections;
using System.Collections.Generic;
using System.Net.Http.Headers;
using UnityEngine;
public class Player : MonoBehaviour
{
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchRight;
public bool isTouchLeft;
private Animator anim;
public float speed;
public float power;
public float maxShotDelay; //실제 딜레이
public float curShorDelay; //한발 쏜 후 딜레이
public GameObject bulletObjA;
public GameObject bulletObjB;
private void Awake()
{
anim = GetComponent<Animator>();
}
void Move()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") ||
Input.GetButtonDown("Vertical"))
{
anim.SetInteger("Input", (int)h);
}
}
private void Update()
{
Move();
Fire();
Reload();
}
void Fire()
{
if (!Input.GetButton("Fire1"))
return;
if (curShorDelay < maxShotDelay)
return;
switch (power)
{
case 1://Power One
GameObject bullet = Instantiate(bulletObjA, transform.position, transform.rotation);
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.1f, transform.rotation);
GameObject bulletL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.1f, transform.rotation);
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = Instantiate(bulletObjA, transform.position + Vector3.right * 0.3f, transform.rotation);
GameObject bulletCC = Instantiate(bulletObjB, transform.position, transform.rotation);
GameObject bulletLL = Instantiate(bulletObjA, transform.position + Vector3.left * 0.3f, transform.rotation);
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShorDelay = 0;
}
void Reload()
{
curShorDelay += Time.deltaTime;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch(collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Right":
isTouchRight = true;
break;
case "Left":
isTouchLeft = true;
break;
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Right":
isTouchRight = false;
break;
case "Left":
isTouchLeft = false;
break;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "BorderBullet")
Destroy(gameObject); //모노비헤이어에 들어 있는 자체 함수 => 매개변수 오브젝트를 삭제하는 함수
}
}
그 후 Sprite Editor를 누르면 여러개의 이미지들이 묶여있는 것을 분할 할 수 있는데
크기로 24*24 여백을 1로 잘랐다.
우선 화면의 비율을 선택해주자
필자는 aspect로 하였고 비율을 입력하여 2가지 해상도를 만들었다.
비행기가 움직일 수 있게 코드 작성
transform은 time.deltatime이랑 세트로 생각하자!
현재의 위치에 키보드로 받을 위치를 더해준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float speed;
void Update()
{
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
}
}
움직여보면 원하지 않는 배경 밖으로 나가게 된다.
비행기에 Box Collider를 추가하여 부착해주면 초록색 경계가 생긴다.
피격 범위로도 사용할 것이므로 콜라이더의 크기를 줄여주었다.
맵의 바깥 부분에 Border라는 빈 오브젝트안에
방향 별로 box collider2D를 붙인 후 범위를 잡아주었다.
Player :
물리 연산이기에 모두 리지드바디2d적용
물리엔진을 많이 사용하지 않기에 키네틱을 사용
키넥틱이란? 다른 것이랑 충돌이 일어났을 때 영향을 받지 않겠다.
보더들은 고정이기때문에 static 사용
경계들을 각각 인지를 해야하기에 변수 선언
물리적인 힘은 넣을 필요 없으므로 collider대신에 온트리거 사용하였고,
각 경계마다 tag로 Border를 달아줄 것이다.
물체가 닿은 상태에서 h는 좌우 이동이므로 => 이동시 닿을 떄를 처리하는데 이때 각각 0으로 만들어서 이동을 멈춘다.
물체가 닿지 않았을 때 이동해야하므로 그 경우도 처리해야 한다.
태그를 달아준다.
현재 까지의 코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float speed;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchRight;
public bool isTouchLeft;
void Update()
{
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = this.transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "Border")
{
switch(collision.gameObject.name)
{
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Right":
isTouchRight = true;
break;
case "Left":
isTouchLeft = true;
break;
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
switch (collision.gameObject.name)
{
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Right":
isTouchRight = false;
break;
case "Left":
isTouchLeft = false;
break;
}
}
}
이제 애니메이션을 넣어줄 것이다.
잘라놓은 애니메이션 각각 4개를 클릭해서
하이어라이키에 있는 Player에 드래그해서 넣어주고 애니메이션 폴더를 생성후
각각 Center, Left, Right로 이름을 만들어서 저
플레이어에 애니메이션 컴포넌트가 생성됨
그 후 플레이어(애니메이션 컨트롤러)를 눌러서 이 창을 띄어주고
Make Transition으로 서로의 관계를 만들어 준다.
트랜지션은 파라미터로 이동한다.
매개변수 Input.GetAxisRaw() 값 그대로 사
각각의 트랜지션 설정을 해줄것인다.
즉각 반응을 위해 Has Exit Time은 꺼야한다.
Transition Duration은 보통3D에서 모션을 부드럽게 변화시키기 위해 사용하는데
부모 클래스에서 virtual 키워드를 사용하여 함수를 만들면,자식 클래스에서 이 함수를 재정의 할 수 있도록 허용
특징 :
1. virtual 이 붙은 함수는 자식 클래스에서 재정의가 가능하다.
2. 자식 클래스에서는 new 또는 override 키워드가 사용가능하다.
- override는 재정의를 하겠다는 확장의 의미이고, new 는 기본 클래스를 숨기는 의미이다.
3. 자식클래스의 함수 시그니쳐가 동일해야 재정의가 가능하다.
4. 자식클래스의 함수는 base 키워드를 사용해 부모 클래스의 함수를 호출 할 수 있다.
5. abstract 와는 달리 자식클래스에서 구현은 선택이다. (구현 안하면 부모의 함수 사용)
6. static, abstract, private,sealed 키워드와는 사용이 불가능하다.
그럼 왜 사용할까?
public class Monster
{
public virtual void hit()
{
Console.WriteLine("Monster hit");
}
}
public class Orc : Monster
{
public override void hit()
{
Console.WriteLine("Orc hit");
}
}
public class Elf : Monster
{
public new void hit()
{
Console.WriteLine("Elf hit");
}
}
public class Wolf : Monster
{
public void hit()
{
Console.WriteLine("Wolf hit");
}
}
class Program
{
static void Main(string[] args)
{
Monster monster1 = new Monster();
Orc monster2 = new Orc();
Elf monster3 = new Elf();
Wolf monster4 = new Wolf();
monster1.hit();
monster2.hit();
monster3.hit();
monster4.hit();
Monster monster5 = new Orc();
Monster monster6 = new Elf();
Monster monster7 = new Wolf();
Console.WriteLine("////////////////////");
monster5.hit();
monster6.hit();
monster7.hit();
}
}
이런식으로 하나씩 입력을 하면 원하는 값은 얻을 수 있지만
나중에 더 많아지면 코드가 길어지면 자연스럽게 코드가 무거워지고 가독성이 떨어지게 된다.
virtual 키워드로 지정된 메서드는 가상 메서드가 되는데 가상 메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된다.
자식 클래스는 override 키워드를 사용해 부모 클래스의 가상메서드를 재정의 할 수 있다.
override?
기반 클래스에서 선언된 메소드를 자식 클래스에서 재정의
기반 클래스에서 오버라이딩할 메소드를 미리 virtual로 한정
파생 클래스는 virtual 메소드를 override한정자를 이용하여 재선언
즉, 가상메서드는 자식 클래스가 오버라이드 할 수 있도록 허용된 메서드
base?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Person : MonoBehaviour
{
public virtual void Work()
{
Debug.Log("일하다");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Salesman : Person {
public override void Work()
{
base.Work();
Debug.Log("나는 셀러리맨이다");
}
private void Start()
{
Work();
}
}
보기와 같이 메서드의 이름이 Work로 같이 정의되어도 자식클래서에서 같은 이름 접근가능하고
부모클래스를 호출할 수 있다.
혹시나 부모클래스의 메소드가 자식 클래스에 의해 변형이 될가 봐 테스트 해보았고,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Salesman : Person {
public override void Work()
{
base.Work();
Debug.Log("나는 셀러리맨이다");
}
private void Start()
{
Person person = new Person();
person.Work();
}
}
결론은 보기과 같이 "아니" 다.
ChatGpt에게 수업이 끝나기 전 간단한 예제를 내달라 했는데 혼자서 입력해봤던 것과 난이도가 같아서
더 어려운 예제를 풀 것이다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fish : MonoBehaviour
{
//1.물고기를 나타내는 Fish 클래스를 작성합니다.
//이 클래스에는 Swim() 메서드가 있어야 합니다. 이 메서드는 "물고기가 헤엄칩니다."라는 메시지를 출력합니다.
public virtual void Swim()
{
Debug.Log("물고기가 헤엄칩니다.");
}
private void Start()
{
Swim();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Goldfish : Fish
{
//2.Fish 클래스를 상속받는 Goldfish 클래스를 작성합니다.
//이 클래스에는 Swim() 메서드를 오버라이드하여 "금붕어가 헤엄칩니다."라는 메시지를 출력하도록 구현합니다.
public override void Swim()
{
Debug.Log("금붕어가 헤엄칩니다.");
}
}
상속을 받을 때 클래스명 뒤에 MonoBehaviour를 지우고 상속받을 부모 클래스의 이름을 추가
3개의 오브젝트에 각각의 스크립트를 부착하였다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public void Attack()
{
Debug.Log("공격");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Orc : Monster
{
public void WarCry()
{
Debug.Log("전투함성");
Attack();
}
//public void Start()
//{
// WarCry();
//}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dragon : Orc
{
public void Fly()
{
Debug.Log("날기");
Attack();
WarCry();
}
private void Start()
{
Fly();
}
}
실행을 해보면 이런식의 순서로 호출된다.
Dragon 클래스의 Start 메서드가 호출
Start 메서드 내에서 Fly 메서드가 호출
Fly 메서드 내에서 Attack 메서드가 호출. 이때, Dragon 클래스가 Orc 클래스를 상속하고 있으므로, Orc 클래스의 Attack 메서드가 실행됩니다.
Attack 메서드가 실행되고 "공격"이라는 메시지가 출력
Fly 메서드 내에서 WarCry 메서드가 호출. WarCry 메서드는 Orc 클래스에 정의되어 있다.
WarCry 메서드가 실행되고 "전투함성"이라는 메시지가 출력된다.
WarCry 메서드 내에서 Attack 메서드가 호출되는데, Orc 클래스가 Monster 클래스를 상속하고 있으므로, Monster 클래스의 Attack 메서드가 실행됩니다.
Attack 메서드가 실행되고 다시 "공격"이라는 메시지가 출력된다.
쉽게 말해 부모 자식 관계가 Monster -> Oak -> Dragon 순으로 되어있으므로
서로 상속되어 클래스를 가져와 사용할 수 있다.
주의
Unity에서 MonoBehaviour를 상속한 클래스에서 Start 메서드를 사용할 때,
상속 관계에 있는 클래스의 Start 메서드 실행 순서는 보장되지 않는다.
이는 Unity 엔진이 각각의 MonoBehaviour를 포함한 오브젝트들에 대해 독립적으로 처리하기 때문이다.
자식클래스는 부모클래스의 속성을 받아서 사용할 수 있지만
반대로 일반적으로 부모클래스는 자식클래스의 속성과 메소드를 사용할 수 없다.
다형성이란?
오류1. WarCry()를 하면 몬스터들의 대미지를 올려주려다 오류가 발생했다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Orc : Monster
{
Monster[] monsters = GameObject.FindObjectsOfType<Monster>();
public void WarCry()
{
Debug.Log("전투함성");
//모든 몬스터 공격력 + 10 증가
for(int i = 0; i < monsters.Length; i++)
{
monsters[i].damage += 10;
Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
}
}
private void Start()
{
for (int i = 0; i < monsters.Length; i++)
{
Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
}
WarCry();
}
}
이것에 대해 알아보니
Unity에서는 MonoBehaviour의 생성자나 인스턴스 필드 초기화자에서 FindObjectsOfType를 호출하는 것을
허용하지 않으므로 대신에 Awake나 Start 메서드에서 호출해야 한다.
따라서 Orc 클래스의 생성자에서 FindObjectsOfType를 호출하는 것은 허용되지 않는다.
Orc 클래스의 WarCry 메서드 내에서 호출하거나, Orc 클래스의 Awake나 Start 메서드에서 호출해야 한다고 한다.
오류2. 호출이 2번된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public float damage = 10f;
public void Attack()
{
Debug.Log("공격");
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Orc : Monster
{
public void WarCry()
{
Debug.Log("전투함성");
//모든 몬스터 공격력 + 10 증가
Monster[] monsters = GameObject.FindObjectsOfType<Monster>();
for (int i = 0; i < monsters.Length; i++)
{
monsters[i].damage += 10;
Debug.LogFormat("추가된 공격력은 {0}", monsters[i].damage);
}
}
private void Start()
{
Monster[] monsters = GameObject.FindObjectsOfType<Monster>();
for (int i = 0; i < monsters.Length; i++)
{
Debug.LogFormat("기본 공격력은 {0}", monsters[i].damage);
}
WarCry();
}
}
주어진 코드에서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에
FindObjectsOfType<Monster>()를 사용하여 몬스터 배열을 얻고 있다.
FindObjectsOfType<Monster>()는 현재 씬에 있는 모든 Monster 클래스의 인스턴스를 찾아 배열로 반환한다.
Start 메서드에서 이를 호출하여 몬스터 배열을 가져오고,
그 다음에 WarCry 메서드를 호출하는 것은 상황에 따라 문제가 될 수 있다.
만약 Start 메서드가 호출될 때 씬에 이미 생성된 몬스터가 있다면,
WarCry 메서드에서 다시 같은 몬스터 배열을 가져오게 되어 몬스터 배열이 중복되어 보일 수 있다.
따라서 Orc 클래스의 Start 메서드에서 WarCry 메서드를 호출하기 전에 몬스터 배열을 가져오는 부분을 제거하면
이런 문제를 해결할 수 있고, WarCry 메서드에서만 몬스터 배열을 가져오도록하면 된다.