public void SaveData()
{
string data = JsonUtility.ToJson(playerData,true);
path = Path.Combine(Application.dataPath,playerData.name +"JSon");
File.WriteAllText(path, data);
}
public void LoadData()
{
string data = File.ReadAllText(path);
playerData = JsonUtility.FromJson<PlayerData>(data);
}
저장,로드씬하는 dataManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;
using UnityEngine.UI;
public class DataManager : MonoBehaviour
{
public class PlayerData
{
public string name;
}
public static DataManager instance;
public int CurrentIndex;
public PlayerData playerData = new PlayerData();
string path;
public Text playerName;
private void Awake()
{
if(instance == null)
{
instance = this;
}
else
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject);
}
public void SaveData()
{
string data = JsonUtility.ToJson(playerData,true);
path = Path.Combine(Application.dataPath,playerData.name +"JSon");
File.WriteAllText(path, data);
}
public void LoadData()
{
string data = File.ReadAllText(path);
playerData = JsonUtility.FromJson<PlayerData>(data);
}
public void LoadSceneMainScene()
{
playerData.name = playerName.text;
SceneManager.LoadScene("MainScene");
Debug.Log(playerName.text);
}
}
스크립터블 오브젝트는 Unity에서 데이터를 저장하고 관리하는 유연한 데이터 컨테이너입니다.
게임에서 재사용 가능한 데이터 또는 설정을 저장하는 데 사용됩니다.
코드와 데이터를 분리하여 코드를 더 깔끔하고 관리하기 쉽게 만듭니다.
하나의 스크립터블 오브젝트를 여러 게임 오브젝트에서 참조하거나 재사용할 수 있습니다.
Unity 에디터와 통합되어 인스펙터 창에서 직접 수정하고 관리할 수 있습니다.
우선 스텟 만들기
캐릭터는 HP / 스피드 / 공격력 으로 구성.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum StatsChangeType
{
Add, [더하기]
Multiple, [곱하기]
Override, [덮어쓰기]
}
[Serializable]
public class CharacterStats
{
public StatsChangeType statsChangeType;
[Range(1, 100)] public int maxHealth;
[Range(1f, 20f)] public float speed;
public AttackSO attackSO;
}
AttackSo를 쓰기위해서 스크립터블 오브젝트를 만들자.
빈폴더 생성 > 이름 ScriptableObject > 하위에 또 빈폴드오브젝트 생성 ,이름은 Script > Script생성 > AttackSO
얘는 만들 오브젝트인 ScriptableObject 를 상속받음
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="DefaultAttackData", menuName ="TopDownController/Attacks/Default", order = 0)]
public class AttackSO : ScriptableObject
{
[Header("Attack Info")]
public float size;
public float delay;
public float power;
public float speed;
public LayerMask target;
[Header("Knock Back Info")]
public bool isOnKnockback;
public float knockbackPower;
public float knockbackTime;
}
Scriptable하위에 RangeAttackData를 만들어줄차례
단적인 예로 화살, 즉 원거리 기술을 써줄것이기 때문에 Range데이터를 만들어야한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "RangedAttackData", menuName = "TopDownController/Attacks/Ranged", order = 1)]
public class RangedAttackData : AttackSO
{
[Header("Ranged Attack Data")]
public string bulletNameTag;
public float duration;
public float spread;
public int numberofProjectilesPerShot;
public float multipleProjectilesAngel;
public Color projectileColor;
}
이렇게하면 빈오브젝트를 생성할때 만들기를 누르면 옆에 뜨게된다.
요런식으로
Scriptable Object 하위에 Datas 폴더를 만들고 거기에 RangedAttackData를 생성한다.
그럼 헤더에 붙여준것들이 뜨게된다.
지금은 플레이어를 가져왔고 이것도 몬스터한테도 따로 해줄수있을듯?
아래로 설정해준다.
이건 공용이다, 한놈이 바뀌면 다바뀌기 때문에 우회하는 코드를 써주어야한다.
이를 우회하는게 Handler코드.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterStatsHandler : MonoBehaviour
{
[SerializeField] private CharacterStats baseStats;
public CharacterStats CurrentStates { get; private set; }
public List<CharacterStats> statsModifiers = new List<CharacterStats>();
private void Awake()
{
UpdateCharacterStats();
}
private void UpdateCharacterStats()
{
AttackSO attackSO = null;
if (baseStats.attackSO != null)
{
attackSO = Instantiate(baseStats.attackSO);
}
CurrentStates = new CharacterStats { attackSO = attackSO };
// TODO
CurrentStates.statsChangeType = baseStats.statsChangeType;
CurrentStates.maxHealth = baseStats.maxHealth;
CurrentStates.speed = baseStats.speed;
}
}
Create → Input Action 클릭 → Top Down Controller 2D로 수정
이동은W,S,A,D
바라보는 방향은 마우스값을 가져온다.
이런식으로 Action을 만들어준다. 그럼 이제 스크립트에서 코드로 연결해주어야한다.
이건 TopDownPlayerController.cs에 작성한다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopDownCharacterController : MonoBehaviour
{
public event Action<Vector2> OnMoveEvent;
public event Action<Vector2> OnLookEvent;
public void CallMoveEvent(Vector2 direction)
{
OnMoveEvent?.Invoke(direction);
}
public void CallLookEvent(Vector2 direction)
{
OnLookEvent?.Invoke(direction);
}
}
Control Type이 Vector2이기 때문에 CallMoveEvent나 CallLookEvent는 Vertor2의 값을 가져온다.
그 뒤에 direction 은 w는 위 S는 아래처럼 방향을 가르킨다.
이렇게 Action을 연결해주었다면
실제로 wasd의 인풋값을 받아 Action을 실행해줄 스크립트도 하나 필요하다.
TopDownPlayerController은 Player에 집어넣어줄 필요가없다.
PlayerInputController를 달아주면된다. 이는 TopDownPlayerController를 상속받기때문.
PlayerInputController.cs를 만들어주자 이 스크립트는 TopDownPlayerController를 상속받는다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerInputController : TopDownCharacterController
{
private Camera _camera;
private void Awake()
{
_camera = Camera.main;
}
[캐릭터 이동할때 캐릭터를 중심으로 카메라를 고정하기 위해 작성]
public void OnMove(InputValue value)
{
Vector2 moveInput = value.Get<Vector2>().normalized;
CallMoveEvent(moveInput);
}
public void OnLook(InputValue value)
{
Vector2 newAim = value.Get<Vector2>();
Vector2 worldPos = _camera.ScreenToWorldPoint(newAim);
newAim = (worldPos - (Vector2)transform.position).normalized;
if (newAim.magnitude >= .9f)
// Vector 값을 실수로 변환
{
CallLookEvent(newAim);
}
}
public void OnFire(InputValue value)
{
Debug.Log("OnFire" + value.ToString());
}
}
여기까지했다면 PlayerInputController으로 TopDownPlayerController을 호출하는데 까지 온것이다.
그럼 실제로 이동을 시켜야한다.
TopDownMovement.cs 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopDownMovement : MonoBehaviour
{
private TopDownCharacterController _controller;
[현재 Player에게는 PlayerInputController가 걸려있는데 이보다 더 상위인 Controller를 받아옴]
private Vector2 _movementDirection = Vector2.zero;
private Rigidbody2D _rigidbody;
private void Awake()
{
_controller = GetComponent<TopDownCharacterController>();
_rigidbody = GetComponent<Rigidbody2D>();
}
private void Start()
{
_controller.OnMoveEvent += Move;
}
private void FixedUpdate()
{
ApplyMovment(_movementDirection);
}
private void Move(Vector2 direction)
{
_movementDirection = direction;
}
private void ApplyMovment(Vector2 direction)
{
direction = direction * 5;
_rigidbody.velocity = direction;
}
}
[System.Serializable]
public class UserData
{
public string Name;
public int Balance;
public int Cash;
public UserData(string name,int balance,int cash)
{
Name = name;
Balance = balance;
Cash = cash;
}
[GameManager.cs] 에다가 UserData.cs을 붙여준다.
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
public UserData User;
private void Awake()
{
if(Instance == null)
Instance = this;
else
Destroy(gameObject);
}
UI를 관리하는 [PopUpBank.cs]에서 gamaManager을 통해 UserData값을 가져온다.
public class PopUpBank : MonoBehaviour
{
[SerializeField] private Text userName;
[SerializeField] private Text balance;
[SerializeField] private Text cash;
[SerializeField] private GameObject popupError;
private void Start()
{
Refresh();
}
private void Refresh()
{
userName.text = GameManager.Instance.User.Name;
balance.text = GameManager.Instance.User.Balance.ToString();
cash.text = GameManager.Instance.User.Cash.ToString();
}
}