정렬해서 앞에서부터 차례로 비교하면서 겹치는 것만 정답에 추가하는 방식으로 진행해보았다.
using System;
using System.Linq;
public class Solution {
public string solution(string X, string Y) {
//숫자가 커서 문자열로 해야됨.
string answer = "";
string small = "";
string big = "";
X = new string(X.ToCharArray().OrderBy(x => x).ToArray());
Y = new string(Y.ToCharArray().OrderBy(x => x).ToArray());
if (X[0] < Y[0])
{
small = X;
big = Y;
}
else
{
small = Y;
big = X;
}
while (big.Length > 0)
{
if (small[0] == big[0])
{
answer += small[0];
small = small.Remove(0, 1);
}
else big = big.Remove(0, 1);
if (small.Length == 0)
break;
}
answer = new String(answer.ToCharArray().OrderByDescending(x => x).ToArray());
if (answer.Length != 0)
{
if (answer[0] == '0')
answer = "0";
}
else if (answer.Length == 0)
answer = "-1";
return answer;
}
}
문제점 : 길이가 같을 때, 문자열이기 때문에 두 문자열의 대소 비교가 쉽지 않음.
작은 숫자를 기준으로 작성한 코드이기 때문에 둘의 순서가 뒤바뀌면 답이 달라짐.
비교하는 방식을 바꿔서, 맨 앞의 숫자를 비교해 더 큰 값을 찾게 하고,
숫자가 존재하는지 여부를 판별한 다음, 큰 숫자에서 삭제하는 방식으로 진행했다.
using System;
using System.Linq;
public class Solution {
public string solution(string X, string Y) {
//숫자가 커서 문자열로 해야됨.
string answer = "";
string small = "";
string big = "";
X = new string(X.ToCharArray().OrderBy(x => x).ToArray());
Y = new string(Y.ToCharArray().OrderBy(x => x).ToArray());
if (X[0] < Y[0])
{
small = X;
big = Y;
}
else
{
small = Y;
big = X;
}
for (int i =0; i < small.Length; i++)
{
long find = big.IndexOf(small[i]);
if (find != -1)
{
answer += small[i];
big = big.Remove((int)find, 1);
}
}
answer = new String(answer.ToCharArray().OrderByDescending(x => x).ToArray());
if (answer.Length != 0)
{
if (answer[0] == '0')
answer = "0";
}
else if (answer.Length == 0)
answer = "-1";
return answer;
}
}
문제 : 시간초과
의외로 런타임 에러나, 실패는 뜨지 않았으나, 입력되는 숫자가 조금만 커져도 시간이 오래 걸리기 때문에 통과하지 못함.
Remove가 안 좋은가 싶어서big = big.Substring(0,find) +big.Substring(find+1);
로도 작성해보았으나 결과는 똑같음.
여기부터는 팀이 바뀌어서 팀 노션 작성하고 팀원분들이 다들 전공자 분들이셔서 같이 풀어보았다.
팀원분이 위에 작성된 내 코드를 보고
for문 돌리면서 탐색하면 n2라 시간이 오래 걸리는게 맞다고 했다.
그래서 풀고 설명을 해주셨는데, 일단 코드부터 보자면
(출처 : 문정현님 깃허브)
using System.Collections.Generic;
class NumberMatch
{
public string Solution(string X, string Y)
{
var nums = new int[10];
var answer = new List<char>();
foreach (var c in Y)
nums[c - '0'] += 1;
foreach(var c in X)
{
if (nums[c - '0'] > 0)
{
answer.Add(c);
nums[c - '0'] -= 1;
}
}
if (answer.Count == 0)
return "-1";
answer.Sort();
answer.Reverse();
if (answer[0] == '0')
return "0";
return string.Concat(answer);
}
}
문자열들이 모두 정수형 숫자로 이루어진 점에 착안해, Y의 숫자들을 먼저 저장하고,
X의 숫자들을 인덱스로 삼아 nums의 숫자들을 하나씩 정답으로 꺼내준다.
nums[c]가 0보다 커야되기때문에 짝꿍 숫자만 도출 할 수 있다.
그리고 answer를 List로 작성했기 때문에 내용물들을 붙여주려면 Concat이라는 메서드를 사용해 붙여줘야한다.
(Concat : Techie Delight)
(참고 : soldier1295.log)
using System;
using System.Text;
using System.Linq;
public class Solution {
public string solution(string X, string Y) {
//숫자가 커서 문자열로 해야됨.
string answer = "";
int[] a = new int[10];
int[] b = new int[10];
for (int i =0; i<X.Length;i++){
a[(int)X[i]-48]++;
}
for (int i =0; i<Y.Length;i++){
b[(int)Y[i]-48]++;
}
for (int i =9; 0<=i;i--){
while (a[i] >0 && b[i]>0){
answer+=$"{i}";
a[i]--;
b[i]--;
}
}
if (answer.Length ==0){
answer = "-1";
}
else if (answer[0]=='0')
answer = "0";
return answer;
}
}
다른 코드도 보고 따라 작성해봤는데, 이 코드는 시간초과로 통과하지 못했다.
방식은 정현님이 가르쳐주신 것과 동일한데, answer에 저장하는 방식이 조금 다르다.
참고 자료에선 stringBuilder를 사용했지만, 없이 해보고 싶어서 이렇게 작성했다.
어려운 문제들을 만나면 계속 1차원적으로 하던 생각만 계속 하게 된다.
이 방향이 맞는 지도 모르면서 깊게 파기 보다는 옆으로도 가지를 계속 뻗쳐야 된다고 내가 그렇게 적었는데 아직도 못하고 있다.
근데 한 4시간 생각했는데 못 풀면 모르는거라 그냥 검색해보거나 물어보는게 맞다.
시간 너무 끌지말고 진행하도록 하자.
제한사항으로 나온 숫자가 크지 않아서, 평소대로 진행했는데
- 먼저 여벌 옷이 있는 친구들 중 도난당한 친구들을 체크해서 줄 수 있는 학생들 명단에서 제외시키고, 또 도난당한 명단에서도 제외시킨다.
- for문을 학생 길이만큼 돌려서 내 앞,뒤에 옷을 줄 수 있는 친구들이 있는 지 체크해본다. (첫번째 학생이나 마지막 학생의 경우 여벌옷이 있는 0, n+1 번째 친구가 존재할 수 없기 때문에 자연스레 예외처리된다.
- 앞친구가 가지고 있으면 앞을 삭제, 뒤면 뒤를 삭제, 둘 다 가지고 있을 시 앞 친구 것을 빌리도록 작성했다.
- 전체 학생 수에서 체육복을 빌리지 못한 학생 수를 제외하면 참석하는 학생의 수가 나온다.
using System;
using System.Linq;
public class Solution {
public int solution(int n, int[] lost, int[] reserve) {
//전체 학생의 수 n, 도난당한 학생들 lost, 여벌 체육복 있는 학생들 reverse
//옷 분배 다하고 lost만큼 n에서 빼주자. 그려려면 지워야됨.
lost = lost.OrderBy(x=>x).ToArray();
reserve = reserve.OrderBy(x=>x).ToArray();
foreach (int i in lost)
{
int a = Array.IndexOf(reserve, i);
if (a != -1)
{
reserve = reserve.Where(e => e != i).ToArray();
lost = lost.Where(e => e != i).ToArray();
}
}
int fore = 0;
int back = 0;
int length = lost.Length;
int[] get = lost;
for (int i = 0; i < length; i++)
{
fore = Array.IndexOf(reserve,lost[i] - 1);
back = Array.IndexOf(reserve, lost[i] + 1);
if (fore != -1 && back == -1)
{
reserve = reserve.Where(e => e != (lost[i] - 1)).ToArray();
get = get.Where(num => num != (lost[i])).ToArray();
}
else if (fore == -1 && back != -1)
{
reserve = reserve.Where(e => e != (lost[i] + 1)).ToArray();
get = get.Where(num => num != (lost[i])).ToArray();
}
else if (fore != -1 && back != -1)
{
reserve = reserve.Where(e => e != (lost[i] - 1)).ToArray();
get = get.Where(num => num != (lost[i])).ToArray();
}
}
int answer = n-get.Length;
return answer;
}
}
- 처음에 작성할 때 lost에 있는 학생들을 reserve에서는 제거했지만, lost에서 제거하지 않아 중복 적용되는 문제가 있었다. 천천히 읽어보고 다시 처리했다.
- 제출할 때 14번 문제즈음부터 한 2~3문제 틀렸었는데, 고민하다가 밑에 질문하기 찾아보니 내 상황이 딱 있어서 눌러봤다.
개때리고싶다. 왜 줄을 똑바로 안 서는데
오랜 기간 동안에 걸처 안정성과 보안패치를 제공하는 정책 또는 버전
에셋 : 게임에 필요한 모든 리소스(이미지, 사운드, 모델, 코드 등)를 의미
씬 : 게임의 각 장면 또는 화면을 의미
유니티에서 게임은 하나 이상의 씬으로 구성, 각 씬은 게임의 특정 부분을 담당
(씬마다 코드작성하면 좋겠지?) -> 수업 듣다보니 한 기능에도 두세개씩 들어감
게임 오브젝트
씬에 배치되는 모든 요소를 의미(보이든 안보이든)
예를 들어 캐릭터, npc 아이템, 장애물 등
게임 오브젝트는 계층 구조로 구성되어 이를 통해 그룹화하고 조작
우클릭하고 Q,E 수직 위아래로 움직임
휠클릭 : 내가 보고 있는 평면으로 움직임
두개중에 하나 맘에드는걸로 쓰면됨, 나는 wasd를 평소에 사용해서 이걸로 함
- 무언가를 추가할 때는 씬뷰에서 바라보고 있는 좌표에서 생성되기 때문에 트랜스폼을 리셋해주자.
물리적 연산을 처리하고 싶으면 리지드바디를 무조건 가지고 있어야됨 + 충돌체 (콜라이더)
처음에 UI만들고나면 캔버스 무조건 수정해줘야됨.
저작권 문제 생길수도있음
MonoBehaviour를 상속 오브젝트에다 연결해서 사용하려면 모노비헤비어를 상속받아 사용해야됨. 그 이외로 사용하려면 그럴 필요는 없음.
골드메탈 : 라이프 사이클
유니티 도큐먼트
순서대로
1. Awake
2. OnEnable
3. Start
---초기화---
4. FixedUpdate
---물리---
5. Update
6. LateUpdate
---게임로직---
7. OnDestroy
8. OnDisable
---오브젝트가 파괴되거나 꺼질 때---
일반적으로 유니티에 이미지를 import하면 PPU가 100으로 설정되어있음.
1이라는 단위(유니티에서 Cube 생성했을 때 크기) 안에 몇 픽셀이 들어오게 할거냐
이미지들을 일관되게 구성하는게 좋다
1사이즈(한 단위범위 내에 딱 맞게)에 맞춰줘야 이동 구성할 때 편하다.
이런 부분들을 고려해서 ppu를 정해야하는데
ppu가 높아질수록 고해상도가 되기때문에 성능적인 부분까지 고려해야한다.
부모오브젝트를 가지면 부모오브젝트의 Transform에 모두 종속됨.
로컬좌표 = 부모로부터 나와의 거리 / 월드좌표 = 세상 중심부터 나와의 거리
최 상위 오브젝트는 로컬좌표 = 월드좌표
실제로 코드에서도 로컬과 월드 좌표를 구분해서 사용해줘야됨.
Position 얻기 : 오브젝트.position =월드 / 오브젝트.localposition하면 로컬
업데이트는 프레임단위로 발생하기때문에 컴퓨터사양에 따라 다름. 그렇기 때문에 코드적인 부분에서 조절해야함.
카메라 넓게 보려면 카메라 사이즈를 수정해야됨.
SerializeField = 프라이빗이여도 인스펙터에서 보여지게 함
충돌이 나려면 둘다 충돌체를 가지고 있어야되고 둘 중 하나는 리지드바디를 가지고있어야된다.
패키지 매니저 - 유니티 레지스트리에서 인풋 시스템이라는게 요즘에 잘나와서 사용하면 좋음
이 인풋 시스템을 여러번 사용해보면서 사용법을 터득할 예정이라고 함.
요기서 설치
인풋액션 파일 만들기(프로젝트 탭에서)
뭘로 조종하는 지 추가하기
내가 취하는 액션 타입과 돌려받는 값(액션을 취했을 때 돌려받을 값이 벡터2)
여기서 동작 설정함
public KeyCode Up;
public KeyCode Down;
이렇게 되있는데 이러면 인스펙터에서 내가 키 지정해줄수있음.
F2 누르면 이름바꾸기 바로됨
콜라이더 : 충돌감지를 가능하게 함.
리지드바디 : 물리 작용을 하기 위해선 무조건 달아줘야됨.
바둑판 방식으로 맵을 구성하는 걸 의미함
타일맵 만들기
하이어라키에서 2d - 타일맵 누르면 생성 가능
보는 방법 - 윈도우에 타일맵 보는 창 있음.
- 타일맵 콜라이더 설정하는 법
타일맵으로 맵 다 구성하고나서 콜리젼이라는 타일맵을 따로 만들어서 충돌처리 해줘야됨.
콜리젼에 타일로 못나가게 막아놓고 타일맵 콜라이더 씌운뒤에 타일 알파값 다 빼버리면 안보임
+하고나서 레이어 설정해주기 (앞으로 나오는 것들이 값이 크다)
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); //유니티에서는 쿼터니언을 쓰는데 우리는 오일러각을 쓰기때문에 오일러각을 쿼터니온으로 변경시켜줌
}
아크탄젠트를 구하면 y,x를 가지고서 각도가 나옴.
instantiate
동적 생성 / 원본으로 받은 애를 복제함
오브젝트 형식이란? 유니티에서 사용하는 대부분의 것이 오브젝트 형식을 최상위로 가지고있음.
그래서 instatiate는 오브젝트를 복제하기때문에 대부분의 것들을 복제할 수 있음.
자주 사용하니 기억할 것
Instantiate(testPrefab, projectileSpawnPosition);
이렇게 작성하면
뒤의 매개변수가 부모오브젝트로 들어간다는 소리고,Instantiate(testPrefab, projectileSpawnPosition.position,quaternion.identity);
이렇게 작성해야 테스트 프리팹이 생성될 트랜스폼 조건을 다 만족하는 것
이벤트란 어떤 동작이 감지 됬을 때 자동으로 등록된 메서드들을 실행시켜주는 기능이다.
이벤트를 활용하는 흐름을 알아보자.
위에서 설명한 패키지 Input System에서 값이 입력된다.
이 값들은 PlayerInputController.cs에서 감지한다.
public void OnMove(InputValue value) //샌드메시지 방식 : 앞에서 만든 move,Look,Fire 앞에 on을 붙이면 그 애들이 실행됬을때 돌려받는 메서드
{
//Debug.Log("OnMove"+value.ToString());
Vector2 moveInput = value.Get<Vector2>().normalized; //노멀라이즈드 하는 이유, 대각선 이동 때도 속도 평준화 (단위벡터로 만들어줌)
CallMoveEvent(moveInput);
}
public void OnLook(InputValue value)
{
//Debug.Log("OnLook"+value.ToString());
Vector2 newAim = value.Get<Vector2>();
//마우스 좌표를 월드에 있는 좌표로 변형해줘야됨. UI상 좌표는 좌상단부터 체크하기때문에 변환해주는거임
Vector2 worldPos = _camera.ScreenToWorldPoint(newAim); //마우스 포인터의 위치를 월드좌표로 받아옴.
newAim = (worldPos - (Vector2)transform.position).normalized; //마우스 좌표- 내 좌표해주면 방향이 나옴
if (newAim.magnitude >= .9f) //magnitude는 크기를 의미함 (여기서는 노멀라이즈드해서 1임 무조건 동작)
{
CallLookEvent(newAim);
}
}
여기서 신경써서 봐야될 부분은 OnMove라는 메서드로, Input System에서 이름으로 설정한 Actions의 앞에 On을 붙이면, 그 Actions가 실행 됬을 때 자동으로 실행되는 메서드다.
이 메서드들이 실행되면 TopDownCharacterController.cs로 전달된다.
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);
}
여기서는 OnMoveEvent에 등록된 메서드가 실행되면, direction이라는 매개변수를 전달해준다. 이 direction은 플레이어가 입력한 값이다.
이벤트를 타고 마지막을 실제 동작하는 부분까지 넘어온다.
PlayerInputController와 TopDownCharacterController는 상속된 부모-자식 클래스 관계기 때문에 따로 참조해주지 않았지만,
실제 오브젝트의 동작을 관리하는 스크립트로 MonoBehaviour를 상속해주어야 하기 때문에 TopDownCharacterController를 참조하는 선언을 해준다.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TopDownMovement : MonoBehaviour
{
private TopDownCharacterController _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() //리지드바디로 이동하기때문에 fixed에서 함
{
ApplyMovement(_movementDirection);
}
private void Move(Vector2 direction)
{
_movementDirection = direction;
}
private void ApplyMovement(Vector2 direction)
{
direction = direction * 5; //방향을 받아오고 속도를 곱해주면
_rigidbody.velocity = direction; //그만큼 가속도로 만들어줌.
}
}
업데이트 같은 부분에는 간결하게 쓰기 위해서 간단한 동작이라도 메서드로 만들어 사용한다.
이벤트 생성 -> InputSystem 등록 -> 구독 메서드 생성 -> 실제 동작 메서드 생성