using System;
using System.Text;
public class Solution {
public string solution(string s) {
StringBuilder answer = new StringBuilder();
int middleIndex = s.Length / 2;
if(s.Length % 2 == 0)
{
answer.Append(s[middleIndex - 1]);
answer.Append(s[middleIndex]);
}
else
{
answer.Append(s[middleIndex]);
}
return answer.ToString();
}
}
StringBuilder는 문자열을 효율적으로 편집하기 위한 클래스로, 여러 개의 문자열을 하나로 결합하거나, 문자열을 추가하거나 수정할 때 사용
결론적으로 문자열을 이용하려고 할때 사용하기 유용하다. 대표적인 메소드는 아래와 같다
Append
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
string result = sb.ToString(); // "Hello World"
Append는 문자열을 끝에 추가하는 데 사용된다.
StringBuilder sb = new StringBuilder("Hello");
sb.Insert(5, " Beautiful"); // "Hello Beautiful"
특정 위치에 문자열을 삽입
Remove
StringBuilder sb = new StringBuilder("Hello Beautiful");
sb.Remove(6, 9); // "Hello"
특정 위치에서부터 일정 길이의 문자열을 제거
Replace
StringBuilder sb = new StringBuilder("Hello World");
sb.Replace("World", "Universe"); // "Hello Universe"
특정 문자열을 다른 문자열로 대체
private void RotateArm(Vector2 direction)
{
float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
armRenderer.flipY = Mathf.Abs(rotZ) > 90f;
characterRenderer.flipX = armRenderer.flipY;
armPivot.rotation = Quaternion.Euler(0, 0, rotZ);
}
float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
direction)의 아크탄젠트를 계산하고 이것을 라디안에서 각도로(90도,180 등) 변환
armRenderer.flipY = Mathf.Abs(rotZ) > 90f;
Mathf.Abs는 값의 절대값을 반환한다.
rotZ가 90도 보다 클 경우 flipY를 true로 하여 스프라이트를 뒤집는다.
[CreateAssetMenu]
깔끔하게 데이터를 정리 수정하기 용이 해보인다.
[CreateAssetMenu(fileName = "DefaultAttackData", menuName = "TopDownCharacterController/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;
}
[CreateAssetMenu(fileName = "RangedAttackData", menuName = "TopDownCharacterController/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;
}
[Serializable]
public class CharacterStats
{
public StatsChangeType statsChangeType;
[Range(1, 100)] public int maxHealth;
[Range(1f, 20f)] public float speed;
public AttackSO attackSO;
}
public CharacterStats CurrentStates { get; private set; }
CreateAssetMenu로 생성한 Datas는 특별한 방식으로 가지고 와야 한다.
[CreateAssetMenu(fileName = "RangedAttackData", menuName = "TopDownCharacterController/Attacks/Ranged", order = 1)]
가지고 와야하는 위치는 CreateAssetMenu 생성할때 작성하는 듯.
[CreateAssetMenu("파일명",menuName = "생성 경로/ /", ?]
마지막 파라미터는 모르겠음
private void OnShoot(AttackSO attackSO)
{
RangedAttackData rangedAttackData = attackSO as RangedAttackData; //attackSO 하위인 RangedAttackData로 변환
float projectilesAngleSpace = rangedAttackData.multipleProjectilesAngel;
int numberOfProjectilesPerShot = rangedAttackData.numberofProjectilesPerShot;
float minAngle = -(numberOfProjectilesPerShot / 2f) *
projectilesAngleSpace + 0.5f * rangedAttackData.multipleProjectilesAngel;
for (int i = 0; i < numberOfProjectilesPerShot; i++)
{
float angle = minAngle + projectilesAngleSpace * i;
float randomSpread = Random.Range(-rangedAttackData.spread, rangedAttackData.spread);
angle += randomSpread;
CreateProjectile(rangedAttackData, angle);
}
}
projectilesAngleSpace : 화살끼리 퍼져있는 정도의 값
numberOfProjectilesPerShot : 한번에 발사되는 화살 값
RangedAttackData rangedAttackData = attackSO as RangedAttackData;
attackSO 하위인 RangedAttackData로 변환
float minAngle = -(numberOfProjectilesPerShot / 2f)
projectilesAngleSpace + 0.5f rangedAttackData.multipleProjectilesAngel;
첫 번째 프로젝타일의 각도를 계산 후 나머지 프로젝타일의 각도는 다중 프로젝타일 사이의 각도 간격과 프로젝타일 수를 고려하여 계산.
즉 화살의 각도는 projectilesAngleSpace 만큼 차이가 나게됨
그리고 for문에서 약간의 랜덤으로 일정한 간격이 안되게끔 함 (이 원리는 다른 함수도 뜯어봐야 해서 아직 모르겠음)
public static ProjectileManager instance; //싱글톤
private void Awake()
{
instance = this;
}
보통 게임매니저에서 자주 사용함.
static 클래스로 만들기
그렇게 되면 굳이 이 클래스를 생성하지 않아도 여러곳에서 이 클래스에 직접 바로 가능.
ProjectileManager instance값을 Awake()로 초기화 하면 Start보다 먼저 우선으로 실행하기 때문에 Start 메소드에서 부터 사용가능
GetComponentInChildren<//SpriteRenderer>(); //주석은 무시
해당 오브젝트에 컴포넌트 말고도 하위 컴포넌트까지 가져옴
생성과 소멸이라는 비용이 큰 작업을 최소화하기 위한 로직
빈번하게 생성되고 파괴되는 객체를 여기서 저장하고 재사용함
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
[System.Serializable]
public struct Pool
{
public string tag;
public GameObject prefab;
public int size;
}
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private void Awake()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>(); //큐에 넣어둠
foreach (var pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj); //생성한 오브젝트를 넣기
}
poolDictionary.Add(pool.tag, objectPool);
}
}
public GameObject SpawnFromPool(string tag)
{
if (!poolDictionary.ContainsKey(tag))
return null;
GameObject obj = poolDictionary[tag].Dequeue();//재활용
poolDictionary[tag].Enqueue(obj); //재활용한거 다시 넣기
return obj;
}
}
public void ShootBullet(Vector2 startPostiion, Vector2 direction, RangedAttackData attackData)
{
GameObject obj = objectPool.SpawnFromPool(attackData.bulletNameTag); //만든 obj 값
obj.transform.position = startPostiion; //obj 포지션 이곳으로 재정의
RangedAttackController attackController = obj.GetComponent<RangedAttackController>();
attackController.InitializeAttack(direction, attackData, this);
obj.SetActive(true);
}
Queue<//GameObject> : 게임오브젝트를 저장하는 큐 생성
반복문으로 Pool객체의 프리팹 오브젝트 생성
생성한 오브젝트를 큐에 넣음
SpawnFromPool에선 재활용 하는 오브젝트를 리턴함 그리고 재활용한 오브젝트를 다시 큐에 넣음
ShootBullet에서 리턴한 재활용 오브젝 포지션을 다시 플레이어 위치로 재정의
Shift + F12 : 참조 목록
Ctrl+ F : 검색
아쉽게도 오늘만에 못함 ಥ_ಥ
[아직 남은거]
[일요일]
[월요일]