BFS

Breath-First Search, 너비 우선 탐색

트리 자료구조의 순회 방법으로 같은 계층의 모든 노드를 먼저 탐색하고 다음 계층을 탐색하는 방식으로 순차적으로 진행하는 방식이다. 

https://en.wikipedia.org/wiki/Breadth-first_search

 

아직 탐색하지 않은 자식 노드를 추적하고 모든 노드를 탐색하기 위해서 일반적으로 FIFO 방식인 큐를 사용한다.

탐색을 시작할 때 먼저 루트 노드를 큐에 넣는다. 큐에서 가장 앞에 있는 노드를 먼저 탐색하고 그 자식 노드를 모두 큐에 넣는다. 그리고 큐에서 제거를 하면 가장 먼저 넣었던 노드가 빠지게 된다. 그리고 다시 큐의 가장 앞에 있는 노드를 탐색하고 자식을 큐에 넣고 가장 앞의 노드를 제거하는 과정을 큐가 빌 때까지 반복하면 최종적으로 모든 노드를 방문할 수 있게 된다.

 

 

이 방법을 확장하면 사이클 유무, 방향성, 가중치 유무 등에 상관없이 모든 형태를 포함할 수 있는 자유로운 구조로 노드와 간선으로 구성된 구조인 일반 그래프에도 적용할 수 있게 된다. 이때는 부모-자식 관계가 확실한 트리에서와는 달리 중복해서 탐색하는 것을 방지하는 작업이 추가로 필요하다.

 

시각적으로 확인하기 쉬운 이차원 그리드에서 이를 응용하면 특정 셀을 루트 노드로 간주하고 그 인접한 각 셀들을 자식 노드로 치환할 수 있다. 이때 셀들은 중복해서 인접하기 때문에 이를 체크하여 순차적으로 탐색을 이어가면 모든 셀을 중복 없이 탐색할 수 있다.

 

여기서 시작 셀 이외에 또 다른 셀을 도착 지점으로 지정하고 이 셀을 만날 때까지 탐색을 진행하고 각 셀들이 어떤 셀로부터 이어져 온 것인지에 대한 정보를 저장한다면 도착한 셀에서 이전 셀을 타고 올라가면 시작 셀에서부터 도착 셀까지 이어지는 경로를 알 수 있게 된다. 이 과정이 BFS를 응용한 길 찾기 알고리즘이다.

 

 

BFS Pathfinding

 

 

DFS

Depth-First Search, 깊이 우선 탐색

트리 자료구조의 순회 방법으로 한 방향으로 최대한 깊이 탐색한 후, 더 이상 갈 곳이 없으면 이전 노드로 돌아가서 다른 방향을 탐색하는 방식으로 진행한다.

 

https://en.wikipedia.org/wiki/Depth-first_search

 

아직 탐색하지 않은 경로를 추적하고 모든 노드를 탐색하기 위해서 일반적으로 LIFO 방식인 스택을 사용하거나 재귀 함수를 활용한다. 탐색을 시작하면 루트 노드부터 시작해서 자식이 없는 리프 노드가 나올 때까지 스택에 노드를 저장하면서 탐색을 진행한다. 리프노드에 도착하면 스택에서 제거하면서 다시 역으로 거슬러 올라가는 백트래킹 과정을 진행한다. 해당 노드에 아직 탐색하지 않은 다른 자식이 있는지 확인 후 있으면 다시 깊이 탐색을 진행하고 리프에서 다시 역으로 올라가는 작업을 스택이 빌 때까지 반복한다.

 

BFS나 DFS는 트리 구조에 따라서 다르겠지만 BFS는 같은 계층의 모든 노드를 저장하기 때문에 깊이난 얕지만 계층이 넓은 경우 메모리상 불리하고 DFS는 계층이 얇지만 트리가 깊다면 또 불리하기 때문에 구조에 따라 방법을 선택해야 한다.

 

DFS 또한 길 찾기에 적용시킬 수 있는데 BFS는 가장 인접한 노드를 탐색하기 때문에 자연스럽게 최단 경로를 찾게 되는 것과 달리 DFS의 경우에는 최단 경로는 보장하지 않지만 경로 존재 여부나 모든 경로 탐색 등의 경우에 유리하다.

 

DFS Pathfinding

728x90
반응형

'Computer' 카테고리의 다른 글

Pathfinding Visualizer - 탐색 알고리즘 시각화 툴  (0) 2025.05.29

탐색 알고리즘을 시각화하기 위한 웹 페이지

 

https://bakcoding.github.io/pathfinding-algorithm/index.html

 

Pathfinding Visualizer

 

bakcoding.github.io

728x90
반응형

'Computer' 카테고리의 다른 글

탐색 알고리즘 - BFS/DFS  (0) 2025.05.29

Flutter SDK 설치

먼저 Flutter를 설치하는 방법은 크게 두 가지가 있다.

 

첫 번째는 공식 홈페이지에 들어가서 직접 파일을 다운로드하여 압축을 풀고 환경변수 설정을 하는 것이다.

 

두 번째로는 Visual Studio Code를 활용해서 여기서 Flutter를 설치하는 방법이 있다.

 

해당 내용은 플러터 공식 홈페이지에서도 확인 가능하다.

 

첫 번째 방법

먼저 공식 홈페이지에서 SDK 압축 파일을 다운로드한 다음 적절한 경로에 옮겨두고 압축을 푼다.

이 경로에는 특수 문자나 공백을 포함해선 안되며 또한 권한이 필요한 폴더 내에 위치해서도 안된다.

 

저장한 위치의 경로를 복사해 둔 다음 이 경로 사용자 변수 또는 시스템 변수의 PATH에 등록한다.

Flutter - Env var

 

두 번째 방법

VSCode를 열고 확장 프로그램 마켓을 열고 flutter를 검색해서 가장 상단에 있는 걸 설치한다.

Flutter - VSCode market place

 

프로젝트 생성

플러터 SDK의 설치가 끝났다면 VSCode를 열고 팔레트를 열어서(Ctrl + Shift + P) 새로운 플러터 프로젝트를 생성한다.

 

Flutter - New Project

 

New Project를 선택하면 > 프로젝트 템플릿 > 프로젝트 생성 경로 > 프로젝트 이름

 

프로젝트 템플릿

프로젝트 템플릿은 Application, Empty, Skeleton이 있다.

 

Application은 기본적인 UI 구성 요소가 만들어져 있어 처음에 Flutter의 계층 구조나 State 개념을 파악하는데 괜찮을 듯하다.

 

Empty는 뜻 그대로 기본 main 함수와 MaterailApp()만 작성되어 있는 실행가능한 최소 상태이다.

 

Skeleton은 기본 페이지 구성과 라우팅 구조를 갖춘 템플릿으로 어느 정도 숙련도가 있다면 빠른 작업 진행에 유용할 것 같다.

 

생성 경로

해당 경로에 '프로젝트 이름' 폴더가 생성되고 IDE에서 해당 폴더를 연다. 이름은 경로 설정 후 입력가능하며 기본 이름값이 주어진다.

 

프로젝트 이름

프로젝트 폴더 이름뿐만 아니라 앱의 이름인 pubspec.yml 파일의 name 필드의 값도 해당 이름으로 설정된다. 

 

추가 패키지 설치

Flutter 프로젝트의 생성이 끝났다면 먼저 IDE에서 터미널을 열고, flutter doctor 명령어를 실행시켜 문제가 없는지 확인한다.

 

해당 명령어의 실행이 끝나면 체크 항목과 문제 사항을 볼 수 있는데 여기서 문제가 되는 부분들을 하나씩 해결해 주도록 한다.

 

이미 한 번 작업을 마친 상태였기 때문에 현재는 문제가 발생하지 않지만 일반적으로 색칠한 부분에서 문제가 발생할 것이다.

 

Flutter Doctor

 

Android Studio - 안드로이드 스튜디오가 없다면 요구되는 버전을 확인 후 설치한다.

Android toolchain - 안드로이드 스튜디오의 SDK 매니저에서 Android toolchain을 찾아서 설치한다.

Visual Studio - VS를 버전에 맞게 설치하고 추가 설치 항목에서 develop Windows apps를 설치한다.

 

문제가 모두 해결되었는지 다시 flutter doctor를 실행해서 확인한다.

 

여기까지 완료되었다면 기본적인 준비가 완료되었다.

 

다시 팔레트를 열어서 Flutter: Launch Emulator를 클릭해서 가상 머신을 실행시킨 후 터미널에서 flutter run을 실행시켜 앱을 띄워본다.

 

IDE의 이 버튼을 눌러도 실행을 시킬 수 있다.

Flutter - Run

 

Flutter - Run with vm

 

애뮬레이터를 추가하려면 안드로이드 스튜디오의 디바이스 매니저에서 추가하면 된다.

728x90
반응형

Player

Input System을 활용해서 간단한 플레이어를 만들어 본다.

 

구현할 기능은 Move, Sprint

 

디바이스는 키보드, Invoke C Sharp Events로 작업한다.

 

Move

Input Action에서 WASD의 입력을 받아서 플레이어 이동으로 처리한다.

 

Input Action - Move

 

Input Action에서 WASD키를 조합하여 Move 액션을 만든다.

 

스크립트에서 PlayerInput의 onActionTriggered 이벤트에 OnActionTriggered 함수를 리스너로 등록하고, OnActionTriggered 함수 안에서 각 액션에 대한 처리를 한다.

 

private PlayerInput playerInput;
private Vector2 moveInput;

private void Awake()
{
	...
    playerInput = GetComponent<PlayerInput>();
    playerInput.onActionTriggered += OnActionTriggered;
    ...
}

...


private void OnActionTriggered(InputAction.CallbackContext context)
{
    if (context.action.name == "Move" && context.performed)
    {
        OnMove(context);
    }
}

...

public void OnMove(InputAction.CallbackContext context)
{
    moveInput = context.ReadValue<Vector2>();
}

 

WASD 키 입력에 따라서 moveInput의 값이 수시로 업데이트되고 플레이어의 움직임을 관리하는 함수에서 moveInput 값을 사용해서 처리한다.

 

확실한 입력이 있을 때 만 처리하기 위해서 performed 인 경우를 체크한다.

 

Sprint

달리기를 위한 기능으로 눌린 상태에서 더 빠르게 움직이도록 한다.

Input Action - Sprint

 

Left Shift 키와 바인딩하여 누르고 있을 때 달리고 떼면 다시 걷도록 한다. 이 입력을 위해서 Press And Release로 키 입력을 받는다.

 

private void OnActionTriggered(InputAction.CallbackContext context)
{
	...
    
    if (context.action.name == "Sprint" && context.performed)
    {
        OnSprint(context);
    }

    ...
}

public void OnSprint(InputAction.CallbackContext context)
{
    isSprint = context.ReadValue<float>() == 1;
    currentSpeed = isSprint ? moveSpeed * 2f : moveSpeed;
    Debug.Log("On Sprint");
}

 

눌림 상태는 float 값으로 들어오며 눌리면 1 떼면 0으로 콜백이 들어온다.

 

이 값을 달리는 상태를 변경하는 플래그로 사용해서 처리한다.

 

간단하게 조작 로직을 구현해서 본다.

 

Player - Move&Sprint

 

728x90
반응형

'Develop > Unity' 카테고리의 다른 글

Input System - Player Input  (0) 2025.04.11
Input System - Input Actions  (0) 2025.04.10
InputSystem 기본 사용법  (0) 2025.03.25
유니티 기본 물리 샘플  (0) 2025.03.21
구글 계정 연동  (1) 2025.02.28

1번 행렬 덧셈

문제

N * M 크기의 두 행렬 A와 B가 주어졌을 때, 두 행렬을 더하는 프로그램을 작성하시오.

 

입력

첫째 줄에 행렬의 크기 N과 M이 주어진다. 둘째 줄부터 N개의 줄에 행렬 A의 원소 M개가 차례대로 주어진다.

이어서 N개의 줄에 행렬 B의 원소 M개가 차례대로 주어진다. N과 M은 100보다 작거나 같고, 행렬의 원소는 절댓값이 100보다 작거나 같은 정수이다.

 

출력

첫째 줄부터 N개의 줄에 행렬 A와 B를 더한 행렬을 출력한다. 행렬의 각 원소는 공백으로 구분한다.

 

C++

#include <iostream>
#include <vector>
using namespace std;
int main(){
    int n, m;
    cin >> n >> m;
    vector<vector<int>> arr_2d(n, vector<int>(m));
    
    for (int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cin >> arr_2d[i][j];
        }
    }
    for (int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            int val = 0;
            cin >> val;
            cout << arr_2d[i][j] + val << " ";
        }
        cout << "\n";
    }
}

 

C#

using System;
using System.Collections;
using System.Collections.Generic;
class Program{
    static void Main(string[] args){
        string input = Console.ReadLine();
        string[] input_arr = input.Split();
        int n = int.Parse(input_arr[0]);
        int m = int.Parse(input_arr[1]);
        int[][] arr_2d = new int[n][];
        
        for (int i = 0; i < n; i++){
            arr_2d[i] = new int[m];
        }
        
        for (int i = 0; i < n; i++)
        {
            string[] arr_val = Console.ReadLine().Split();
            for (int j = 0; j < m; j++)
            {
                arr_2d[i][j] = int.Parse(arr_val[j]);
            }
        }
        for (int i = 0; i < n; i++)
        {
            string[] arr_val = Console.ReadLine().Split();
            for (int j = 0; j < m; j++)
            {
                arr_2d[i][j] += int.Parse(arr_val[j]);
                Console.Write(arr_2d[i][j] + " ");
            }
            Console.WriteLine();
        }
    }
}

 

Python

n, m = map(int, input().split())
arr_2d_1 = [list(map(int, input().split())) for _ in range(n)]
arr_2d_2 = [list(map(int, input().split())) for _ in range(n)]
result = [[arr_2d_1[i][j] + arr_2d_2[i][j] for j in range(m)] for i in range(n)]
for row in result:
    print(' '.join(map(str, row)))

 

Node.js

const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const [n, m] = input[0].split(' ').map(Number);
const arr1 = [];
const arr2 = [];

for (let i = 1; i <= n; i++){
    arr1.push(input[i].split(' ').map(Number));
}

for (let i = n + 1; i <= 2 * n; i++){
    arr2.push(input[i].split(' ').map(Number));
}

var result = [];
for (let i = 0; i < n; i++){
    const row = [];
    for (let j = 0; j < m; j++){
        row.push(arr1[i][j] + arr2[i][j]);
    }
    result.push(row);
}

result.forEach(row => console.log(row.join(' ')));

 

2번 최댓값

문제

<그림 1>과 같이 9×9 격자판에 쓰인 81개의 자연수 또는 0이 주어질 때, 이들 중 최댓값을 찾고 그 최댓값이 몇 행 몇 열에 위치한 수인지 구하는 프로그램을 작성하시오. 예를 들어, 다음과 같이 81개의 수가 주어지면

https://www.acmicpc.net/problem/2566

 

이들 중 최댓값은 90이고, 이 값은 5행 7열에 위치한다.

 

입력

첫째 줄부터 아홉 번째 줄까지 한 줄에 아홉 개씩 수가 주어진다. 주어지는 수는 100보다 작은 자연수 또는 0이다.

 

출력

첫째 줄에 최댓값을 출력하고, 둘째 줄에 최댓값이 위치한 행 번호와 열 번호를 빈칸을 사이에 두고 차례로 출력한다. 최댓값이 두 개 이상인 경우 그중 한 곳의 위치를 출력한다.

 

이차원 배열에 저장하고 순회하면서 가장 큰 숫자를 찾고 그 숫자와 인덱스를 출력하면 되는 문제다. 그런데 이차원 배열을 굳이 만들지 않아도 입력을 처리할 때 바로 큰 수와 인덱스를 찾을 수 있긴 하다. 처음 풀이는 의도에 맞게 풀고 그 이후는 간단한 방법을 사용해 본다.

 

C++

#include <iostream>
#include <vector>
using namespace std;
int main(){
    int n = 9;
    vector<vector<int>> arr_2d(n, vector<int>(n, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            cin >> arr_2d[i][j];
        }
    }
    
    
    int max_val = arr_2d[0][0];
    int max_row = 0;
    int max_col = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (arr_2d[i][j] > max_val) {
                max_val = arr_2d[i][j];
                max_row = i;
                max_col = j;
            }
        }
    }
    cout << max_val << "\n";
    cout << max_row + 1 << " " << max_col + 1;
    
    return 0;
}

 

C#

using System;
class Program{
    static void Main(string[] args){
        int n = 9;
        int maxVal = 0;
        int row = 0;
        int col = 0;
        for (int i = 0; i < n; i++){
            string[] strArr = Console.ReadLine().Split();
            for (int j = 0; j < n; j++){
                int tmpVal = int.Parse(strArr[j]);
                if (tmpVal > maxVal){
                    maxVal = tmpVal;
                    row = i;
                    col = j;
                }
            }
        }
        Console.WriteLine($"{maxVal}");
        Console.WriteLine($"{row + 1} {col + 1}");
    }
}

 

Python

max_val = 0;
col = 0;
row = 0;
for i in range(9):
    arr = list(map(int, input().split()))
    for j in range(9):
        tmp_val = arr[j]
        if max_val < tmp_val:
            max_val = tmp_val
            row = i;
            col = j;
print(f"{max_val}\n{row + 1} {col + 1}")

 

Node.js

const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
let col = 0;
let row = 0;
let max_val = 0;
for (let i = 0; i < 9; i++){
    const arr = input[i].split(' ').map(Number);
    for (let j = 0; j < 9; j++){
        if (arr[j] > max_val){
            max_val = arr[j];
            row = i;
            col = j;
        }
    }
}
console.log(`${max_val}\n${row + 1} ${col + 1}`);

 

3번 세로 읽기

문제

아직 글을 모르는 영석이가 벽에 걸린 칠판에 자석이 붙어있는 글자들을 붙이는 장난감을 가지고 놀고 있다. 이 장난감에 있는 글자들은 영어 대문자 ‘A’부터 ‘Z’, 영어 소문자 ‘a’부터 ‘z’, 숫자 ‘0’부터 ‘9’이다. 영석이는 칠판에 글자들을 수평으로 일렬로 붙여서 단어를 만든다. 다시 그 아래쪽에 글자들을 붙여서 또 다른 단어를 만든다. 이런 식으로 다섯 개의 단어를 만든다. 아래 그림 1은 영석이가 칠판에 붙여 만든 단어들의 예이다.

A A B C D D
a f z z 
0 9 1 2 1
a 8 E W g 6
P 5 h 3 k x
<그림 1>

 

한 줄의 단어는 글자들을 빈칸 없이 연속으로 나열해서 최대 15개의 글자들로 이루어진다. 또한 만들어진 다섯 개의 단어들의 글자 개수는 서로 다를 수 있다. 심심해진 영석이는 칠판에 만들어진 다섯 개의 단어를 세로로 읽으려 한다. 세로로 읽을 때, 각 단어의 첫 번째 글자들을 위에서 아래로 세로로 읽는다. 다음에 두 번째 글자들을 세로로 읽는다. 이런 식으로 왼쪽에서 오른쪽으로 한 자리씩 이동하면서 동일한 자리의 글자들을 세로로 읽어 나간다. 위의 그림 1의 다섯 번째 자리를 보면 두 번째 줄의 다섯 번째 자리의 글자는 없다. 이런 경우처럼 세로로 읽을 때 해당 자리의 글자가 없으면, 읽지 않고 그다음 글자를 계속 읽는다. 그림 1의 다섯 번째 자리를 세로로 읽으면 D1gk로 읽는다. 그림 1에서 영석이가 세로로 읽은 순서대로 글자들을 공백 없이 출력하면 다음과 같다: Aa0aPAf985Bz1EhCz2W3D1gkD6x 칠판에 붙여진 단어들이 주어질 때, 영석이가 세로로 읽은 순서대로 글자들을 출력하는 프로그램을 작성하시오.

 

입력

총 다섯 줄의 입력이 주어진다. 각 줄에는 최소 1개, 최대 15개의 글자들이 빈칸 없이 연속으로 주어진다. 주어지는 글자는 영어 대문자 ‘A’부터 ‘Z’, 영어 소문자 ‘a’부터 ‘z’, 숫자 ‘0’부터 ‘9’ 중 하나이다. 각 줄의 시작과 마지막에 빈칸은 없다.

 

출력

영석이가 세로로 읽은 순서대로 글자들을 출력한다. 이때, 글자들을 공백 없이 연속해서 출력한다.

 

C++

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int main(){
    int max_col = 15;
    string line;
    vector<vector<char>> matrix;
    for (int i = 0; i < 5; i++){
        getline(cin, line);
        vector<char> row(line.begin(), line.end());
        matrix.push_back(row);
    }
    for (int col = 0; col < max_col; col++){
        for (int row = 0; row < matrix.size(); row++){
            if (col < matrix[row].size()){
                cout << matrix[row][col];
            }
        }
    }
    return 0;
}

 

C#

using System;
using System.Collections.Generic;
using System.Linq;
class Program{
    static void Main(string[] args){
        int max_col = 15;
        string line;
        List<List<char>> matrix = new List<List<char>>();
        
        while((line = Console.ReadLine()) != null){
            matrix.Add(line.ToList());
        }

        for (int col = 0; col < max_col; col++){
            for (int row = 0; row < matrix.Count; row++){
                if (matrix[row].Count > col){
                    Console.Write(matrix[row][col]);
                }
            }
        }
    }
}

 

Python

max_row = 5;
max_col = 15;
matrix = [list(input()) for _ in range(max_row)]
for col in range(max_col):
    for row in range(max_row):
        if col < len(matrix[row]):
            print(matrix[row][col], end="")

 

Node.js

const fs = require('fs');
const max_row = 5;
const max_col = 15;
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
for (let col = 0; col < max_col; col++){
    for (let row = 0; row < max_row; row++){
        if (col < input[row].length) {
            process.stdout.write(input[row][col]);    
        }
    }
}

 

4번 색종이

문제

가로, 세로의 크기가 각각 100인 정사각형 모양의 흰색 도화지가 있다. 이 도화지 위에 가로, 세로의 크기가 각각 10인 정사각형 모양의 검은색 색종이를 색종이의 변과 도화지의 변이 평행하도록 붙인다. 이러한 방식으로 색종이를 한 장 또는 여러 장 붙인 후 색종이가 붙은 검은 영역의 넓이를 구하는 프로그램을 작성하시오.

https://www.acmicpc.net/problem/2563

 

예를 들어 흰색 도화지 위에 세 장의 검은색 색종이를 그림과 같은 모양으로 붙였다면 검은색 영역의 넓이는 260이 된다.

 

입력

첫째 줄에 색종이의 수가 주어진다. 이어 둘째 줄부터 한 줄에 하나씩 색종이를 붙인 위치가 주어진다. 색종이를 붙인 위치는 두 개의 자연수로 주어지는데 첫 번째 자연수는 색종이의 왼쪽 변과 도화지의 왼쪽 변 사이의 거리이고, 두 번째 자연수는 색종이의 아래쪽 변과 도화지의 아래쪽 변 사이의 거리이다. 색종이의 수는 100 이하이며, 색종이가 도화지 밖으로 나가는 경우는 없다.

 

출력

첫째 줄에 색종이가 붙은 검은 영역의 넓이를 출력한다.

 

2차원 배열 카테고리의 마지막 문제라서 그런가 단순 값 처리가 아닌 응용이 필요한 문제이다.

100 x 100 크기의 도화지란 배열의 최대 크기를 정한 것으로 [100][100] 크기의 2차원 배열 안에서 이루어지는 작업이다.

색종이는 모두 동일한 크기로 최대 100개까지 붙인다. 색종이 크기는 모두 일정한 10 x 10이며 왼쪽 하단의 꼭짓점에 대한 좌표가 주어진다.

 

당장은 두 가지 방법이 떠오른다.

1. 도화지에서 색종이가 없는 영역 구하기

2. 전체 색종이 개수의 영역에서 겹치는 부분 제외하기

 

2차원 배열의 구조를 기준으로 생각해 본다.

100 x 100 크기에서 입력된 색종이 범위만큼 인덱스를 체크해 놓고 최종적으로 체크한 부분만 계산하면 색종이가 덮은 영역이 나온다.

C++

#include <iostream>
#include <vector>
using namespace std;
int main(){
    int count = 0;
    int matrix[100][100] = {0};
    cin >> count;
    for (int i = 0; i < count; i++){
        int x1, y1, x2, y2 = 0;
        cin >> x1;
        cin >> y1;
        x2 = x1 + 10;
        y2 = y1 + 10;
        for (int x = x1; x < x2; x++){
            for (int y = y1; y < y2; y++){
                matrix[x][y] = -1;
            }
        }
    }
    
    int area = 0;
    for (int row = 0; row < 100; row++){
        for (int col = 0; col < 100; col++){
            if (matrix[row][col] == -1){
                area++;
            }
        }
    }
    cout << area;
    return 0;
}

 

 

중복으로 겹쳐진 부분을 체크하는 부분을 조건을 주고 확인하는 걸 추가해도 될 것 같다.

 

C#

C++로 코드를 쓰면서 방식에 문제가 없으니 좀 더 정리해서 작성해 본다.

using System;
class Program{
    static void Main(string[] args){
        int[,] matrix = new int[100, 100];
        int count = int.Parse(Console.ReadLine()!);
        
        for (int i = 0; i < count; i++){
            string? input = Console.ReadLine();
            if (string.IsNullOrEmpty(input)) return;
            string[] xy = input.Split();
            int x = int.Parse(xy[0]);
            int y = int.Parse(xy[1]);
            
            for (int dx = x; dx < x + 10; dx++){
                for (int dy = y; dy < y + 10; dy++){
                    matrix[dx, dy] = -1;
                }
            }
        }
        
        int area = 0;
        for (int row = 0; row < 100; row++){
            for (int col = 0; col < 100; col++){
                if (matrix[row, col] == -1){
                    area++;
                }
            }
        }
        Console.WriteLine(area);
    }
}

 

Python

count = int(input());
matrix = [[0] * 100 for _ in range(100)]
for _ in range(count):
    x, y = map(int, input().split())
    for dx in range(x, x + 10):
        for dy in range(y, y + 10):
            matrix[dx][dy] = 1
area = sum(row.count(1) for row in matrix)
print(area)

 

Node.js

const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const count = parseInt(input[0]);
let matrix = Array.from({length : 100 }, () => Array(100).fill(0));
for (let i = 1; i <= count; i++){
    const xy = input[i].split(" ").map(Number);
    const x = xy[0];
    const y = xy[1];
    for (let dx = x; dx < x + 10; dx++){
        for (let dy = y; dy < y + 10; dy++){
            matrix[dx][dy] = 1;
        }
    }
}
let area = 0;
for (let row = 0; row < 100; row++){
    for (let col = 0; col < 100; col++){
        if (matrix[row][col] == 1){
            area++;
        }
    }
}
console.log(area);

 

728x90
반응형

PlayerInput

Input System은 Input Actions와 PlayerInput으로 구성된다. Input Actions는 입력과 행동의 연결을 정의하는 구조이고, PlayerInput은 그 정의를 바탕으로 실제 입력을 감지하고 동작을 실행하는 컴포넌트다.

 

동작의 주체가 되는 Player 게임 오브젝트에 PlayerInput 컴포넌트를 추가하고, Actions 필드에 사용할 Input Actions 에셋을 지정하면 PlayerInput을 사용할 준비가 완료된다.

 

unity - PlayerInput

 

Default Scheme

어떤 입력 장치(키보드마우스, 게임패드 등)에 대한 입력을 처리할지 지정할 수 있는 설정이다. 

Player Input - Scheme

 

기본값인 Any는 어떤 장치에서든 입력을 받을 수 있다. 특정 장치를 지정하더라도 Auto-Switch가 활성화되어 있다면, 다른 장치의 입력이 감지되었을 때 자동으로 해당 장치로 전환되어 입력을 처리한다. 반대로 Auto-Switch가 비활성화되어 있다면, 처음 감지된 장치만 계속 사용하게 된다. 

 

예를 들어 Default Scheme을 Any로 설정하면 먼저 사용된 키보드마우스로 Control Scheme이 잡힌다.

PlayerInput - Keyboard&Mouse

 

이 상태에서 게임패드를 연결하여 조작을 하면 Auto-Switch 덕분에 자동으로 게임패드로 입력이 감지되어 처리된다.

PlayerInput - GamePad detection

 

Default Map

Default Map은 Input Action Maps 중에서 기본으로 사용할 Map을 지정하는 설정이다.

Input Actions - Default Map

 

Input Action은 상황에 따라 다른 Map으로 전환할 수 있다.

예를 들어 캐릭터 조작 시에는 Player 맵을 사용하고, 메뉴를 열었을 때는 UI 맵으로 전환하여 입력을 UI 조작에만 반응하도록 만들 수 있다. 이러한 방식은 게임패드나 조이스틱처럼 여러 입력이 혼합되는 환경에서 특히 유용하다.

 

UI Input Module

UI와의 상호작용은 EventSystem에 연결된 Input System UI Input Module 컴포넌트를 통해 처리된다.

기본 Unity UI 시스템은 단일 입력만 처리하도록 설계되어 있지만 멀티플레이 게임(로컬)에서는 각 플레이어가 자신의 UI를 조작해야 하는 경우가 생기기 때문에 각 플레이어에게 UI 입력용 시스템도 별도로 연결해주어야 한다.

Event System

 

PlayerInput 컴포넌트가 사용하는 Input Action Asset은 UI Input Module에도 동일하게 적용되어, 동일한 동작과 디바이스 설정으로 UI와 게임 조작을 일관되게 제어할 수 있다.

 

멀티플레이 환경에서는 MultiplayerEventSystem 컴포넌트를 사용하여 화면에 여러 UI 인스턴스를 동시에 표시하고 각 UI를 서로 다른 플레이어가 독립적으로 제어할 수 있게 만들 수 있다.

MultiPlayer Event System

 

Camera

Camera 필드는 멀티 플레이어 상황에서, 플레이어 관리에 사용되는 PlayerInputManager 컴포넌트의 Split-Screen 기능이 활성화된 경우에 의미를 갖는다.

PlayerInputManager

 

이 기능이 켜지면 각 플레이어는 자신만의 카메라를 통해 분할된 화면을 보게 되며, 이때 PlayerInput의 Camera 필드에 각 플레이어의 카메라를 연결해주어야 한다.

 

Mario Kart 2P

 

이렇게 설정하면, UI의 입력 처리도 해당 카메라를 기준으로 이루어지므로 플레이어마다 올바른 UI 포커스 및 이벤트 처리가 가능해진다.

 

Behavior

이벤트가 발생했을 때 어떤 방식으로 처리할지 결정하는 옵션이다.

 

Send Messages

PlayerInpt - Behavior

 

Send Message는 Unity의 고전적인 메시지 전달 방식으로, SendMessage("함수명", 파라미터) 형태로 특정 메서드를 실행한다.

PlayerInput 컴포넌트는 Input Action이 발생했을 때, 해당 액션 이름을 기반으로 구성된 함수명을 자동으로 호출한다. 이 메서드는 GameObject에 연결된 MonoBehaviour 스크립트 내에 정확한 이름으로 존재해야 하며, 그렇지 않으면 호출되지 않고 무시된다.

예를 들어 Jump라는 액션이 정의되어 있다면, PlayerInput은 OnJump()라는 함수명을 찾아 호출한다.

 

이처럼 Input Action에서 정의된 Action 이름 앞에 On을 붙인 함수명이 호출 대상이 되며 위 이미지에서 텍스트로 사용가능한 함수명이 표시된다. 이 함수명 텍스트는 Input Actions 에셋에서 Action의 이름을 추가하거나 편집하고 Asset을 저장하면 자동으로 수정되어 보인다.

 

SendMessage 방식의 장점은 간단하고 빠르게 연결 가능하기 때문에 코드 구조가 가볍지만 메서드명이 정확히 일치해야 작동한다는 점과 동적 호출 방식이기 때문에 컴파일 타임에서 오류 체크가 불가능하며 함수의 파라미터가 InputValue만 전달되기 때문에 복합적인 처리나 Context 정보 전달, 다중 파라미터 기반 로직등의 처리가 어렵다.

 

public void OnMove(InputValue value)
{
	moveInput = value.Get<Vector2>();
}

 

 

Broadcast Message

Broadcast Message 방식도 Unity의 고전적인 메시지 전달 방식으로 Send Message와 동일한 형식을 따르지만, 차이점은 현재 GameObject 뿐만 아니라 모든 자식 오브젝트들에게도 메시지를 전파한다는 점이다.

 

예를 들어 계층 구조 내 여러 컴포넌트가 동일한 함수명을 가지고 있다면, 모두 호출되기 때문에 예상치 못한 중복 동작이 발생할 수 있다. 또한 자식 오브젝트가 많거나 계층이 깊을 경우, 성능 저하의 원인이 될 수 있어 주의가 필요하다.

 

Invoke Unity Events

이벤트 기반 정적 연결방식으로, 함수명을 신경 쓸 필요 없이 에디터에서 간편하게 메서드를 지정할 수 있다.

Behavior - Invoke Unity Events

 

Input Actions에서 정의한 Action들은 PlayerInput 컴포넌트 내에서 자동으로 이벤트로 생성되며, 인스펙터 창에서 해당 이벤트에 호출할 메서드를 직접 할당할 수 있다. 이러한 이벤트 - 리스너 구조는 코드 간의 결합도를 낮추고, 동작을 시각적으로 구성할 수 있어 직관적이고 유지보수도 용이하다.

 

Invoke C Sharp Events

Invoke C# Events는 코드 기반으로 입력을 처리하는 방식으로, PlayerInput이 제공하는 onActionTriggered 이벤트에 리스너를 등록하여 모든 입력 액션을 하나의 이벤트에서 감지할 수 있다.

void OnEnable()
{
    playerInput.onActionTriggered += OnActionTriggered;
}

void OnDisable()
{
    playerInput.onActionTriggered -= OnActionTriggered;
}

void OnActionTriggered(InputAction.CallbackContext context)
{
    if (context.action.name == "Jump")
    {
        Debug.Log("Jump triggered");
    }
}

 

action.name을 기준으로 원하는 액션을 구분해서 처리할 수 있으며, Unity Events 방식은 인스펙터에서 함수명을 문자열로 참조하기 때문에 함수명이 바뀌면 참조가 끊길 수 있는 반면 Invoke C# Events는 코드에서 직접 리스너를 등록하기 때문에 함수명을 변경하더라도 안전하게 리팩토링이 가능하며, 유지보수에 강점을 가진다.

 

정리

각 Behavior 방식은 특징이 다르기 때문에 상황에 따라 적절히 선택해서 사용하는 것이 중요하며 다음과 같이 요약할 수 있다.

- Send Message : 간단한 구현이 필요할 때 유용

- Broadcast Message : 하위 오브젝트까지 포함하여 입력 처리를 해야 하는 특수한 경우에 사용

- Invoke Unity Events : 비프로그래머나 디자이너가 에디터에서 직관적으로 연결할 때 적합

- Invoke C Sharp Events : 복잡한 입력 로직을 처리할 때 사용

728x90
반응형

'Develop > Unity' 카테고리의 다른 글

Input System 으로 플레이어 만들기  (0) 2025.04.12
Input System - Input Actions  (0) 2025.04.10
InputSystem 기본 사용법  (0) 2025.03.25
유니티 기본 물리 샘플  (0) 2025.03.21
구글 계정 연동  (1) 2025.02.28

Input System

2019 버전을 발표할 시점인 19년도에 새로운 Input System을 업데이트하면서 기존까지 입력 처리를 담당했던 Input Manager에 대해서 앞으로 추가 업데이트 사항은 없다고 언급을 했었다. 그 후 아직까지도 호환성은 유지한 채 사용할 수 있도록 제공하고 있지만 공식 문서에서도 legacy로 표현하며 Input System을 권장하고 있다. 

 

Input System을 권장하는 이유는 새로운 입력 처리 방식의 장점과 Input Manager의 오래된 기술로 인한 한계에 있다.

 

Input System 장점

- 다양한 입력 장치를 지원하며 사용자 정의가 가능하다. 이를 통해서 다양한 플랫폼에서 일관된 입력을 처리할 수 있다. 

- 비동기 입력 처리를 지원하기 때문에 입력 이벤트를 더 효율적으로 관리할 수 있어 게임 성능의 향상을 모색할 수 있다.

- Input System의 API는 더 직관적이어서 사용하기 쉬워 코드의 가독성을 높인다.

 

Input Manager 한계

- 다양한 입력 장치를 동시에 처리하는 등의 현대의 게임 개발에서 요구되는 사항을 충족하기  어렵다.

- 기본적인 입력 처리만 가능하기 때문에 복잡한 입력에 대한 요구 사항을 처리하기 부적합하다.

 

Unity 6 버전을 기준으로 진행한다.

 

Project Settings

기본 선택은 both로 되어있는데 Input System만 을 사용하기 위해서 Project Settings > Player > Active Input Handling에서 Input System Package를 선택한다. 

 

어느 버전부터인지 모르겠지만 이전에 사용했던 버전에서는 Input System을 선택 시 패키지 설치가 필요했는데 현재 버전에서는 패키지가 기본적으로 설치되어 있고 InputSystem_Actions 파일이 기본 생성되어 있다.

 

Input Actions

Input System - Input Actions


Input Actions Asset 파일을 열면 입력에 대한 처리를 맵핑과 세부 설정을 할 수 있는 Input Actions Editor 창이 열린다.

 

Action Maps

입력을 그룹으로 묶어 관리할 수 있는 단위이다. 기본적으로 Player, UI 맵이 제공된다.

각 Action Map은 관련된 여러 입력 동작인 Action들의 설정 묶음으로 하나의 Input Actions Asset에서 활성화되는 Map은 하나만 처리되므로 플레이어의 상태에 따라 Map을 전환하여 상황에 맞는 입력만 처리하도록 구성할 수 있어 게임 내 다양한 상태에 따라 입력 처리를 명확하게 구분하고 유지 보수 및 확장도 간편하다.

 

Actions

사용자 입력에 반응하여 수행될 동작을 정의하는 요소이다.

Input Actions - Actions

 

이동, 점프, 공격 등과 같이 플레이어의 입력 행동 단위이다. 각 Action은 하나 이상의 입력 Binding과 연결되고 키보드, 마우스, 게임 패드 등 다양한 장치의 입력을 조합하여 정의할 수 있다.

 

Action은 하나의 입력 동작에 대한 정의이며 Binding은 Action을 어떤 입력 장치에 매핑할지 설정한다.

 

각각 Properties에서 세부적인 옵션 설정을 할 수 있다.

 

Action Properties

각 Action의 동작 방식과 입력값의 처리 형태를 세부적으로 설정할 수 있는 항목이다.

입력의 종류, 처리 방식, 반환 값의 형태 등을 정의하여 다양한 입력 상황에 대응할 수 있도록 옵션이 제공된다.


각 옵션의 기본 설정은 Project Settings > Input System > Settings의 값을 따르며 개별 값을 조정하려면 Default 플래그를 끄고 직접 수정하거나 Settings Asset을 생성하여 값을 커스텀한다.

 

Action Type

입력 시스템의 동작 방식을 정의하는 요소로 각 타입마다 입력 이벤트를 처리하는 방식에 따라 다르게 동작한다.

Value, Button, Pass Through 세 가지 방식이 있다.

Action Properties - Action Type

 

Value

지속적인 입력 값을 반환한다. 입력이 활성화된 동안 현재 상태 값을 계속해서 업데이트한다. 주로 조이스틱의 위치나 슬러이더처럼 지속적으로 변화하는 값을 처리할 때 유용하다.

 

Button

버튼의 누름 상태를 감지한다. 점프, 공격, 상호작용처럼 단일 이벤트 트리거에 적합하다.

 

PassThrough

입력을 필터링 없이 그대로 전달한다. 가공되지 않고 호출 대상에 직접 전달되며 주로 복잡한 입력 로직, 멀티 컨트롤 입력, UI 입력 등에 활용한다.

 

Control Type

Action Type에 따라 달라지는데 Value인 경우 다양한 값 형식으로 가공해서 받을 수 있다.

Value - Control Type

 

Type 설명 반환 타입
Axis 단일 축(1차원)의 연속적인 값 (-1 ~ 1) float 조이스틱 축, 키보드 이동, 마우스 휠 등
Analog 0.0 ~ 1.0 범위의 연속 아날로그 값 float 게임패드의 트리거, 슬라이더 등
Integer 정수 값 int 타임라인, 메뉴 인덱스 등
Digital 이진 값 (0 또는 1, true/false) float or bool 버튼 입력, 온/오프 스위치 등
Double 높은 정밀도 실수 double 정밀 제어, 시뮬레이션 등
Vector2 2D 벡터 Vector2 2D 이동, 마우스/터치 위치, 텍스처 좌표 등
Vector3 3D 벡터 Vector3 3D 위치, 방향 등
Delta 변화량을 나타내는 값(차이값) Vector2 마우스 드래그, 터치 슬라이드 변화량
Quaternion 3D 회전을 나타내는 사원수 Quaternion 컨트롤러 회전, 장치 방향
Stick 아날로그 스틱 입력 StickControl 게임 패드의 조이스틱
Dpad 4방향 디지털 입력 DpadControl 게임 패드의 십자키
Touch 단일 터치 정보 TouchControl 터치 스크린
Pose 위치 + 회전 정보 PoseControl VR, AR 디바이스
Eyes 시선 추적 데이터 EyesControl VR HMD, 아이 트래킹 장비

 

PlayerInput의 Behavior 방식에 따라 다르지만 Stick, Dpad, Touch, Pose, Eyes와 같은 타입들은 내부적으로 해당 클래스 타입(StickControl, DpadControl 등)으로 처리되며, Callback 방식에서 해당 클래스를 직접 참조하여 세부 데이터를 추출할 수 있다.

 

Interactions

 Action의 세 가지 이벤트 상태(started, performed, canceled)에 도달하기 위한 조건을 세부적으로 정의하는 항목이다.

 

Action 상태

- started : 입력의 시작점

- performed : 입력이 성공적으로 완료된 시점

- canceled : 입력이 도중에 중단 또는 실패한 경우

 

Action Properties - Interactions

 

Hold

일정 시간 동안 누르고 있을 때만 유효한 입력으로 인식한다.

Interactions - Hold

 

Press Point : 입력 강도를 판정하는 것으로 어느 정도 눌렸을 때 Press로 보는지 판단하는 기준으로 트리거나 스틱 같이 값 변경이 미세한 경우에서 중요하게 판단된다.

 

Hold Time : Press Point를 넘은 후 이 시간만큼 입력이 유지됐을 때 performed 상태가 되고 그전에 입력이 중단되면 canceled 상태가 된다.

 

Multi Tap

일정 시간 안에 특정 횟수만큼의 입력이 발생했을 때 작동한다.

Interactions - Multi Tap

 

Tap Count : 몇 번을 입력해야 performed 되는지

Max Tap Spacing : 탭과 탭 사이 최대 허용 시간

Max Tap Duration : 각 탭이 유지될 수 있는 최대 시간

Press Point : 유효한 입력으로 판단할 최소 세기

 

입력 사이 간격이 Max Tap Spacing을 넘거나, 각 탭 유지 시간이 Max Tap Duration을 넘기면 canceled

 

Press

버튼을 누르거나 뗄 때 발생하는 단순 입력을 세밀하게 제어할 때 사용한다. 

단순 버튼의 입력에 대한 처리라면 Action Type의 Button으로 설정하면 되고 세부 제어가 필요한 경우 Press Interaction을 사용한다.

Interactions - Press

 

Trigger Behavior : Press Only, Release Only, Press and Release 세 가지 상태가 있으며 각 상태는 버튼을 눌리는 순간, 떼는 순간, 누를 때 started 뗄 때 performed로 처리한다.

 

Slow Tap

버튼을 누른 뒤 일정 시간 후에 뗄 때 performed 상태가 된다.

Min Tap Duration : 탭이 performed 되기 위한 최소 시간

 

Hold와 비슷하지만 hold는 시간 조건이 됐을 때 performed, slow tap은 시간을 채우고 뗄 때 performed 되는 차이가 있다. 하지만 겹치는 동작이 발생할 수 있기 때문에 동일한 Action에서 Hold와 Slow Tap 두 인터랙션을 동시에 사용할 때는 조건을 명확하게 하여 구분할 필요가 있다.

 

Tap

버튼을 빠르게 누르고 떼는 동작을 인식한다.

Max Tap Duration : 입력이 Press Point 이상으로 감지된 이후에 started 상태가 되고 Max Tap Duration 안에 입력을 뗄 때 performed 된다.

 

Processors

입력값을 처리하여 최종적으로 Action에 전달되는 값을 조정하는 기능이다.

컨트롤러의 조이스틱, 마우스, 트리거 등 다양한 입력 장치에서 입력된 원시 값을 자동으로 보정, 변환, 필터링할 수 있다.

Action Properties - Processors

 

Axis Deadzone, Stick Deadzone

Axis Deadzone은 축 단위(float)의 선형적인 입력값에 대해 deadzone을 적용한다.

입력값이 min 보다 작으면 0, max 보다 크면 1, 그 사이의 값은 0 ~ 1 사이로 정규화된 값

Processors - Deadzone

 

주로 게임패드의 트리거와 같이 선형적인 입력에서 의도치 않은 작은 입력을 무시하거나 거의 끝까지 입력된 경우 1로 보정한다.

Example - Trigger Axis Deadzone

 

Stick Deadzone은 2D Vector 입력에 대해서 방사형 범위의 deadzone을 적용한다.

입력된 벡터의 길이(magnitude)가 min 보다 작으면 0, max 보다 크면 1, 그 사이값은 정규화하여 보정한다.

 

Processor - Stick Deadzone

 

게임패드의 아날로그 스틱 입력에서 중앙 근처의 미세한 입력을 무시하고 가장자리에 가까우면 1, 범위 내의 값은 정규화하여 보정한다.

Example - Stick Deadzone

 

 

Clamp

입력값이 특정 범위를 벗어나지 않도록 제한해 주는 기능이다. 즉, 최소와 최댓값을 지정하고 그 범위를 벗어나는 값은 경곗값으로 보정한다.

Processors - Clamp

 

Min : 입력값의 최솟값

Max : 입력값의 최댓값

 

Invert, Invert Vector2, Invert Vector3

입력값의 부호를 반전시킨다. 

Processors - Invert

 

단일 수치(float) 뿐 아니라 Vector2, Vector3 타입의 입력에도 사용 가능하며, 각 축별로 플래그로 개별 설정할 수 있다.

 

Normalize, Normalize Vector2, Normalize Vector3

입력값을 -1 ~ 1 사이로 정규화한다.

Processors - Normalize

 

벡터의 크기는 무시하고 방향성만 필요한 경우에 사용한다.

 

Scale, Scale Vector2, Scale Vector3

입력 값에 곱셈 계수 Factor를 적용한다.

Processors - Scale

 

마우스 민감도 등 감도 조정에 사용할 수 있다.

 

Binding Properties

Binding

단일 입력 바인딩

Binding Properties - Binding

 

Path

이 Binding이 참조하는 입력 장치와 입력 경로를 지정하며 기본 입력 연결 포인트로, 해당 Action이 어떤 입력에서 데이터를 받을지 결정한다.

 

Show Drived Bdings : 입력 장치마다 자동으로 파생되는 바인딩 목록을 확인할 수 있다.

 

Use in control scheme

특정 Control Scheme에 이 Binding을 포함할지 여부를 정한다.

예를 들어서 Keyboard&Mouse, Gamepad, Touch 등으로 나누어진 스킴에서 이 바인딩이 어떤 스킴에서 사용되는지 지정한다.

 

Composite

복합 입력 바인딩

Binding Properties - Composite Binding

 

여러 입력 값을 조합해서 하나의 논리적 입력으로 만들 때 사용한다.

대표적으로 WASD 나 방향키 조합으로 Vector2 이동을 구현하는 것이 있다.

 

Composite Type

어떤 형태의 입력 조합인지 지정한다.

 

1D Axis 

두 개의 입력을 받아서 -1 ~ 1 사이의 float 값으로 반환한다.

1D Axis

Negative 입력 -1, Positive 입력 +1 두 입력이 동시에 있다면 Which Side Wins에 따라 결과가 달라진다.

 

Composite - 1D Axis

Neither : 두 입력이 상쇄되어 0

Positive : Positive 입력 우선, +1 반환

Negative : Negative 입력 우선, -1 반환

 

2D Vector

네 개의 입력을 받아 Vector2(x, y) 값으로 결합하여 반환

Composite - 2D Vector

Up : +y

Down : -y

Left : -x

Right : +x

2D Vector - Mode

 

Analog : 아날로그 입력 값을 그대로 사용

Digital Normalized : 디지털 입력의 정규화 값

Digital : 입력이 있는 방향에 대해 -1/0/1의 벡터 구성

 

3D Vector

여섯 개의 입력을 받아 Vector3(x, y, z) 값으로 결합

Composite - 3D Vector

2D Vector에서 

Forward : +z

Backward : -z

추가

 

모드는 동일하다.

 

Button With One/Two Modifiers

하나 또는 두 개의 Modifier 키와 Button의 조합이다.

Modifier의 입력이 있으며 Button이 눌려야 Action이 발생한다.

Composite - Button With Two Modifiers

 

일반적으로 shift, ctrl, alt 등의 키를 modifier 키지만 일반키를 Modifier로 사용할 수 있다.

 

Button With Two Modifiers

 

Override Modifiers Need To Be Pressed First : Modifier 키가 먼저 눌려야 Action이 발동되는지 결정하는 옵션

활성화 시 Modifier 키들이 먼저 입력된 상태에서 Button이 입력되어야 동작 비활성화 시 순서 상관없이 모두 눌려지면 동작

 

One/Two Modifiers

하나 또는 두 개의 Modifier 키의 조합이다.

Binding이 있지만 이 키의 입력은 상관없이 Modifier의 입력만으로 판단한다.

 

Composite - Two Modifiers

 

Button의 입력이 중요한 Button With ~ Modifier와 달리 Modifier의 입력만으로 동작의 조건이 충족된다.

Binding은 Modifier 입력을 기반으로 추가 입력을 받을 수 있는 여지를 만들어 주는 역할을 한다.

728x90
반응형

'Develop > Unity' 카테고리의 다른 글

Input System 으로 플레이어 만들기  (0) 2025.04.12
Input System - Player Input  (0) 2025.04.11
InputSystem 기본 사용법  (0) 2025.03.25
유니티 기본 물리 샘플  (0) 2025.03.21
구글 계정 연동  (1) 2025.02.28

Unity로 MMORPG를 개발하고 있을 때 발생한 일이다.

 

네트워크는 Mirror를 사용하였고 존 서버를 게임 서버에 띄워서 테스트를 진행하고 있었다.

 

에디터에서 Host로 실행했을 때 문제없이 API 통신이 성공하였지만 존 서버와 연결을 한 후에 통신을 한 경우 요청이 처리되지 않는 상황이 발생했다.

 

에러는 curl error 28 connection time out으로 요청이 시간 내에 처리되지 못해서 실패한 경우이다.

 

먼저 게임 서버와 통신을 다시 테스트하기 위해서 IP를 확인하고 방화벽의 Port 상태를 확인했지만 문제가 없었기 때문에 더욱 혼란스러웠다. 다행히 테스트용 URL를 만들고 브라우저에서 호출을 해서 API에는 문제가 없다는 것을 확인한 후에 혹시나 하는 생각에 게임 서버에 원격으로 접속하여 URL을 호출하여 테스트해 보니 time out 에러가 발생하는 것을 확인할 수 있었다.

 

즉 외부에서 서버의 Public IP로 접근하는 것은 허용이 되지만 내부에서 Public IP로의 접근은 막혀 있었고 LocalHost와 Private IP는 또 잘 동작했기에 이 부분에 대해서 좀 알아보기로 했다.

 

동작이 실패하는 과정은 다음으로 추정한다.

 

1. 내부에서 Public IP로 요청

2. 라우터/방화벽에 도달, NAT 설정은 내부에서 내부로 가능 경로를 인식하지 못함

3. 패킷이 처리되지 못하고 실패하게 됨

 

이러한 상황을 해결해 주는 방법이 Loopback NAT이다.

 

Loopback NAT

또는 Hairpin NAT으로도 부른다.

 

위 상황이 장황했지만 간단하게 말하면 내부 네트워크의 장치가 같은 네트워크의 다른 장치에 접근할 때 직접 내부 IP를 사용하는 경우 Loopback NAT이 없다면 내부 사용자는 외부로 나갔다가 다시 들어오는 비효율적인 경로를 통해 접근하거나 이 접근조차 완전히 차단되는 상황이 발생한다.

 

Loopback NAT은 내부 네트워크의 클라이언트가 공개 IP 주소를 통해 요청을 보낼 때, 라우터나 방화벽이 이 요청이 내부로 향하는 것을 인식하고 적절히 내부 IP로 변환하여 처리한다.

 

1. 라우터/ 방화벽에 내부에서 Public IP로 향하는 요청 감지

2. 해당 요청을 자동으로 내부 Private IP로 변환하여 처리

3. 응답 패킷을 처리하여 클라이언트 반환

 

이렇게 되면 내부 및 외부 사용자가 동일한 URL을 사용할 수 있고 내부 트래픽이 불필요하게 외부로 나가지 않고 효율적으로 라우팅이 된다. 또한 동일한 서비스에 대해서 내부용과 외부용 DNS 설정을 별도로 유지할 필요가 없어진다.

 

문제도 해결하고 새로운 사실도 알게 되었다. 일단은 내부에서 Private IP를 사용하도록 처리하고 다음 회의에서 Loopback에 대해서 언급을 하기로 한다.

 

NAT

여기서 NAT이란 무엇인지에 대해서 사고가 확장된다.

NAT은 Network Address Translation으로 "사설 IP와 공인 IP" 간의 주소를 변환하는 기술이다.

 

여러 대의 컴퓨터가 하나의 공인 IP로 인터넷을 공유, IPv4 주소 부족 문제를 해결, 외부에서 직접 내부 네트워크로 접근하기 어렵도록 보안의 목적 등으로 사용된다.

 

예를 들어 집에서 인터넷을 쓰는 경우 보통 핸드폰에서는 192.168.0.10과 같은 사설 IP를 사용하는데 외부 인터넷에 요청을 보내게 되면 NAT 장비가 그 요청을 중간에서 공인 IP로 바꿔서 보내고 응답이 오면 다시 원래의 사설 IP로 돌아오게 된다.

 

즉 여러 개의 장비로 인터넷을 나누어 쓰더라도 각각의 내부 IP는 다르지만 외부로 나가는 요청에서는 NAT 장비를 통해 동일한 공인 IP로 식별되게 된다.

 

이외에도 여러 NAT 방식이 존재한다.

 

SNAT(Source NAT) : 출발지 주소를 변경, 내부에서 외부 통신

DNAT(Destination NAT) : 목적지 주소를 변경, 외부에서 내부 통신

 

728x90
반응형

'Computer > Network' 카테고리의 다른 글

네트워크의 시작  (0) 2024.04.16

PlayerInput Component

빌트인 컴포넌트인 PlayerInput을 플레이어 오브젝트에 추가해서 키입력을 바로 받을 수 있다.

 

unity - PlayerInput

 

Actions에 등록된 InputSystem_Actions를 열어볼 수 있는데 일반적으로 사용되는 키로 바인딩되어 있는 걸 확인할 수 있다.

 

unity - input action

 

이 파일을 수정해서 바인딩 키나 값을 변경하여 처리할 수 있다.

 

플레이어 조작 스크립트에서 이 입력을 가져다 쓰는 방법은 다음과 같다.

 

private void Awake()
{
    rb = GetComponent<Rigidbody>();
}

private void FixedUpdate()
{
    if (currentInput != Vector2.zero)
    {
        // 방향 설정
        body.forward = new Vector3(currentInput.x, 0, currentInput.y).normalized;

        // 속도 계산
        float currentSpeed = walkSpeed;
        Vector3 moveVelocity = new Vector3(currentInput.x, 0, currentInput.y) * currentSpeed;

        // 물리 이동
        rb.linearVelocity = moveVelocity;

        // 애니메이션
        animator.SetFloat("Move", currentInput.magnitude);
    }
    else
    {
        // 정지 상태
        animator.SetFloat("Move", 0);
        rb.linearVelocity = Vector3.zero;
    }
}

public void OnMove(InputValue value)
{
	moveInput = value.Get<Vector2>();
}

 

업데이트 안에서 이동키 입력으로 변경되는 moveInput 값을 갱신해서 플레이어를 움직인다.

 

unity - player move

 

Action Properties 설정을 통해서 필요에 맞춰 수정해서 쓸 수 있다.

 

unity - sprint properties

 

쉬프트를 누르면 달리고, 떼면 걷도록 상태를 변경하는 기능을 추가해본다. 

 

기본 Action은 눌렀을 때만 처리하고 있는데 이 부분을 PressAndRelease로 변경한다. 

 

그리고 Initial State Check를 활성화 해준다.

 



private void FixedUpdate()
{
    currentInput = Vector2.SmoothDamp(
        currentInput,
        moveInput * (isSprint ? 1f : 0.5f),
        ref smoothVelocity,
        smoothTime
    );

    if (currentInput != Vector2.zero)
    {
        // 방향 설정
        body.forward = new Vector3(currentInput.x, 0, currentInput.y).normalized;

        // 속도 계산
        float currentSpeed = isSprint ? sprintSpeed : walkSpeed;
        Vector3 moveVelocity = new Vector3(currentInput.x, 0, currentInput.y) * currentSpeed;

        // 물리 이동
        rb.linearVelocity = moveVelocity;

        // 애니메이션
        animator.SetFloat("Move", currentInput.magnitude);
    }
    else
    {
        // 정지 상태
        animator.SetFloat("Move", 0);
        rb.linearVelocity = Vector3.zero;
    }
}

private void PlayerAnimation(float moveAmount)
{
    animator.SetFloat("Move", moveAmount);
}

public void OnMove(InputValue value)
{
    moveInput = value.Get<Vector2>();
}

public void OnSprint(InputValue value)
{
    isSprint = value.isPressed;
}

 

InputManager의 GetAxis처럼 입력이 서서히 -1 0 1 사이에서 움직이는 선택 없이 GetAxisRaw처럼 고정된 숫자로 값이 반환되는데 이 부분이 InputSystem에서 설정으로 제어 가능한 부분이 아닌 것으로 현재 판단되어서 일단 damp를 사용해서 임의로 값을 증가, 증감시켜 범위 내 변하는 값으로 움직임을 처리한다.

 

이 값이 필요한 이유는 애니메이션을 블렌딩으로 처리하기 때문에 자연스러운 애니메이션을 표현하기 위해서 시작-도착 값까지의 변화하는 값이 필요하다.

 

unity - move

728x90
반응형

'Develop > Unity' 카테고리의 다른 글

Input System - Player Input  (0) 2025.04.11
Input System - Input Actions  (0) 2025.04.10
유니티 기본 물리 샘플  (0) 2025.03.21
구글 계정 연동  (1) 2025.02.28
2D 애니메이션, 이펙트  (0) 2024.12.03

유니티의 물리엔진의 기본 기능들의 샘플 구현

 

중력

유니티는 물리적인 오브젝트에는 중력과 힘 등의 물리 작용들이 적용된다.

 

물리적인 오브젝트란 Rigidbody 컴포넌트가 붙어있는 것으로 이 컴포넌트에서 중력의 적용 여부나 질량, 마찰력 등의 설정을 제어할 수 있다.

 

유니티의 중력은 Physics.gravity로 접근하여 값을 가져오고 변경할 수 있다.

 

이 중력은 Vector3 값으로 기본값은 현실과 동일하게 y 축으로 -9.81으로 되어있다.

 

이 값을 변경하면 중력을 다양한 방식으로 적용할 수 있다.

 

unity - gravity

 

Rigidbody 컴포넌트의 drag 값은 항력을 제어한다. 하지만 이는 선형적인 값으로 현실적인 물리와는 차이가 있다.

 

angular drag는 회전 항력으로 회전력에 적용되는 저항력이다.

 

unity - drag
unity - angular drag

 

 

물리적인 상태의 오브젝트에는 힘을 가해서 움직이거나 회전 또는 범위 내 오브젝트에 거리 비례 힘을 가하는 물리 기능들을 사용할 수 있다.

 

AddForce

물체에 특정 방향으로 특정 크기만큼의 힘을 가한다.

unity - add force

 

AddTorque

물체에 회전력을 준다.

unity - add torque

 

AddExplosionForce

unity - add explosion force

 

AddExplosionForce는 이 함수만 호출한다고 주변에 영향을 주는 것이 아니라 특정한 객체를 기준으로 영향을 줄 주변 객체를 직접 탐색하면서 AddExpolsionForce를 적용시킨다.

 

public void ApplyExplosion(float force, float radius)
{
    Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
    foreach (Collider col in colliders)
    {
        Rigidbody rb = col.GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.AddExplosionForce(force, transform.position, radius);
        }
    }
}

 

 

샘플 프로젝트 Github 저장소

https://github.com/Bakcoding/unity-physics-sample.gi

728x90
반응형

'Develop > Unity' 카테고리의 다른 글

Input System - Input Actions  (0) 2025.04.10
InputSystem 기본 사용법  (0) 2025.03.25
구글 계정 연동  (1) 2025.02.28
2D 애니메이션, 이펙트  (0) 2024.12.03
Input System 사용시 UI 상호작용 안될때  (0) 2024.11.18

+ Recent posts