윈도우 폼을 이용하여 화면을 캡쳐하는 프로그램을 만들어 보겠습니다.

System.Drawing에서 제공되는 동작들을 통해 쉽게 결과를 얻을 수 있습니다.

using System;
using System.Windows.Forms;
using System.Drawing.Imaging; //PixelFormat, ImageFormat
using System.Drawing; //Bitmap, Graphics
using System.IO; //Path

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        private void ScreenShot(int width, int height, int x, int y)
        {
            Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
            Graphics graphics = Graphics.FromImage(bitmap);

            graphics.CopyFromScreen(x, y, 0, 0, bitmap.Size);

            //현재 프로젝트 위치에 저장됩니다.
            string path = Environment.CurrentDirectory;
            string fileName = "image.png";

            //MessageBox.Show(Path.Combine(path + fileName));
            bitmap.Save(Path.Combine(path, fileName), ImageFormat.Png);
        }

        public Form1()
        {
            InitializeComponent();
            //Screen.PrimaryScreen : 1번 모니터
            //WorkingArea : 작업표시줄 제외한 범위
            ScreenShot(Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height, 0, 0);
        }
    }
}

 

스크린샷 결과 화면

 

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices; //DLL import

namespace WindowsFormsApp
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")] //C:\Windows\System32\user32.dll 참조
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, IntPtr hInstance, uint threadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hInstance);

        [DllImport("user32.dll")]
        private static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelKeyboardProc lowLevelKeyboardProc = HookProc;

        //WM_Keydown
        //https://wiki.winehq.org/List_Of_Windows_Messages
        const int WH_KEYBOARD_LL = 13;
        const int WM_KEYDOWN = 256;
        const int WM_SYSKEYDOWN = 260;

        private static IntPtr hookId = IntPtr.Zero;

        public void SetHook()
        {
            IntPtr hInstance = LoadLibrary("User32");
            hookId = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, hInstance, 0);
        }

        public void UnHook()
        {
            UnhookWindowsHookEx(hookId);
        }

        public static IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code >= 0)
            {
                if (wParam == (IntPtr)WM_SYSKEYDOWN) //알트 키 눌림
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    if (key == Keys.PrintScreen)
                    {
                        MessageBox.Show("Alt + PrintScreen 키가 눌렸습니다.");
                        return (IntPtr)1; //1을 리턴하여 처리를 끝냅니다.
                    }
                }
                else if (wParam == (IntPtr)WM_KEYDOWN) //일반 키 눌림
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);

                    switch (key)
                    {
                        case Keys.A:
                            MessageBox.Show("A키가 눌렸습니다.");
                            return (IntPtr)1;

                        default:
                            break;
                    }
                }
                
            }

            return CallNextHookEx(hookId, code, (int)wParam, lParam);
        }

        public Form1()
        {
            InitializeComponent();
            SetHook();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            UnHook();
        }
    }
}

 

4개의 윈도우 API 함수가 필요하고 하는 일은 함수명을 보시면 짐작이 가실겁니다 ㅎㅎ

callback 함수에서 키에 대한 처리를 해주고 있는데

wParam는 어떤 타입의 키인지 정보가 들어있고 lParam 은 해당 키 값이 들어있습니다.

키값에 대해선 해당 주석 사이트에 나와있습니당!

어떤 문자를 입력 받아 해당 구문에 대해 올바른 문자열인지 판별할 때 Regex를 사용할 수 있습니다.

기능들이 많지만 사실 다 필요한 건 아니고 간단하게 쓸거잖아요? ㅎㅎ

일단 훑어봅시다.


위키백과 정규표현식 (보기 싫게 생겼다)


개인적으로 필요한 기능은 한글과 영어 숫자에 대한 입력만을 확인하기 위한 것이었습니다.

a~z, A~Z, 가~히, ㄱ~ㅎ, ㅏ~ㅣ, 0~9

 

using System;
using System.Text.RegularExpressions; //Regex

namespace ConsoleApp
{
    class Program
    {
        private static bool IsValidString (string input)
        {
            //^는 시작 의미
            //+는 최소 1개 이상을 의미
            //$는 끝을 의미
            //@" " 로 쓰는 이유는 \를 문자로 다루기 위해서입니다.
            //"c:\\" == @"c:\"
            return Regex.IsMatch(input, @"^[a-zA-Z0-9가-히ㄱ-ㅎㅏ-ㅣ]+$");

            //아래 코드와 같은 동작을 합니다..
            //for (int i = 0; i < input.Length; ++i)
            //{
            //    char ch = input[i];
            //    if (('a' <= ch && ch <= 'z') ||
            //        ('A' <= ch && ch <= 'Z') ||
            //        ('0' <= ch && ch <= '9') ||
            //        ('ㄱ' <= ch && ch <= 'ㅎ') ||
            //        ('ㅏ' <= ch && ch <= 'ㅣ') ||
            //        ('가' <= ch && ch <= '힣') ||
            //        ch == '_')
            //    {
            //        //ok
            //    }
            //    else
            //    {
            //        return false;
            //    }
            //}

            //return true;
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 5; ++i)
            {
                Console.Write("input : ");
                string input = Console.ReadLine();

                if (IsValidString(input))
                {
                    Console.WriteLine("유효한 문자열입니다.");
                }
                else
                {
                    Console.WriteLine("유효하지 않은 문자열입니다.");
                }
                Console.WriteLine();
            }
        }
    }
}

 

 

추가적으로 알아야 할 것은 input 길이가 더 길어도 패턴에 매칭되면 무조건 true를 반환한다는 것입니다.

 

. : 1개의 문자를 의미

 ex) Regex.IsMatch("123", @"...") == true     (3개가 매칭됩니다.)

      Regex.IsMatch("12344", @"...") == true  (3개가 매칭되면 true가 됩니다.)

      Regex.IsMatch("12", @"...")  == false     (2개만 매칭되어 false가 됩니다.)

 

[ ] : 내부의 모든 문자를 의미

   [a-z]  - 표시를 통해 a ~ z 까지의 의미를 표현합니다.

   [a-z] == [abcd...xyz]

   

   [^a-z] 내부에 ^문자가 들어가면 부정형태 즉 a~z까지 들어가면 false가 됩니다.

 

 ex) Regex.IsMatch("z", @"[a-z]")  == true

      Regex.IsMatch("z25", @"[a-z]" == true (앞의 1개가 매칭되어 true가 됩니다.)

      Regex.IsMatch("1", @"[a-z]" == false

using System;
using System.Diagnostics; //Process

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Process[] process = Process.GetProcesses();
            for (int i = 0; i < process.Length; ++i)
            {
                Console.WriteLine(process[i].Id + " " + process[i].ProcessName);
            }
        }
    }
}

위의 코드로 현재 내 컴퓨터에서 실행 중인 모든 프로세스 정보를 볼 수 있습니다.

실행 결과 화면

 

그리고 현재 포커싱 되어있는 프로세스에 접근하기 위해선 윈도우 API가 필요합니다.

 

C:\Windows\System32\user32.dll (dynamic link library) 요기에 있는 함수

 

IntPtr GetForegroundWindow() : 현재 맨 앞의 윈도우 핸들(hWnd) 가져오기 (최상위 프로세스)

int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId) : 핸들을 이용하여 프로세스 아이디 가져오기

 

2개가 필요합니다.

실행 시켜준 후 다른 프로세스로(실행 중인 프로그램) 포커스를 옮겨보았습니다.

using System;
using System.Diagnostics; //Process
using System.Runtime.InteropServices; //DLL Import
using System.Threading; //Thread

namespace ConsoleApp
{
    class Program
    {
        [DllImport("user32.dll")]
        public static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        private static void CheckCurrentProcess ()
        {
            for (int i = 0; i < 5; ++i)
            {
                IntPtr hWnd = GetForegroundWindow(); //맨 앞의 프로그램 핸들 반환
                uint processId;

                GetWindowThreadProcessId(hWnd, out processId); //해당 프로세스 id 반환
                Process currentProcess = Process.GetProcessById((int)processId);

                Console.WriteLine(currentProcess.Id + " " + currentProcess.ProcessName);

                Thread.Sleep(3000); //3초
            }
        }

        static void Main(string[] args)
        {
            Thread thread = new Thread(CheckCurrentProcess);
            thread.Start();
            thread.Join();
        }
    }
}

결과 화면

스타크래프트 키는데 6초 걸렸네요 :)

저는 개인용 캡쳐 프로그램을 만들기 위해 해당 기능들을 사용할 생각입니다.

찾아보면 윈도우 API 기능들이 유용한 것들이 많이 있네요!

네이버 지식인에서 어떤 사람이 올렸었는데 재밌어 보여서 완성해본 코드입니다.

목적지에 물건을 옮기는 게임인데 간단하게 만들어봤습니다.

 

#include <stdio.h>
#include <conio.h>  
#include <windows.h> 
#include <string.h>

enum Map { Blank = 0, Wall = 1, Block = 2, Goal = 3, Player = 4 };
enum KeyBoard { RIGHT = 77, LEFT = 75, UP = 72, DOWN = 80 };
const char icon[5][5] = { "..", "■", "★", "_G", "♬" };
const int StageCount = 1;
const int MaxRow = 10;
const int MaxCol = 10;

int gameOver = 0;
int currentStage[MaxRow][MaxCol];
int round;
int preIcon = 0;
COORD playerCoord;

int MAP[StageCount][MaxRow][MaxCol] = 
{
//1round
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,3,1},
{1,0,1,1,0,0,0,0,0,1},
{1,0,0,1,0,0,0,1,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,1,0,0,1},
{1,0,2,0,1,0,4,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
}
//next..
};

void gotoxy(int row, int col, const char* str)
{
	COORD Pos = { col * 2 + 1, row };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
	printf("%s", str);
}

void ShowMainMessage()
{
	for (int i = 0; i < 10; ++i)
	{
		gotoxy(5, 20 + i, "-");
	}
	for (int i = 0; i < 10; ++i)
	{
		gotoxy(10, 20 + i, "-");
	}

	gotoxy(7, 18, "게임 시작! (Press Any Key)");
}

void drawMap()
{
	for (int row = 0; row < MaxRow; row++)
	{
		for (int col = 0; col < MaxCol; col++)
		{
			int index = currentStage[row][col];

			if (index > Player)
			{
				gotoxy(row, col, "  ");
				continue;
			}

			gotoxy(row, col, icon[index]);
		}
	}
}

void initStageInfo()
{
	for (int row = 0; row < MaxRow; ++row)
	{
		for (int col = 0; col < MaxCol; ++col)
		{
			currentStage[row][col] = MAP[round][row][col];

			if (currentStage[row][col] == Player) 
			{
				playerCoord.Y = row;
				playerCoord.X = col;
			}
		}
	}
}

int CanMove(int row, int col)
{
	return currentStage[playerCoord.Y + row][playerCoord.X + col] != Wall;
}

int IsExistBlock(int row, int col)
{
	return currentStage[playerCoord.Y + row][playerCoord.X + col] == Block;
}

int IsGoal(int row, int col)
{
	return currentStage[playerCoord.Y + row][playerCoord.X + col] == Goal;
}

int CanPush(int row, int col)
{
	return currentStage[playerCoord.Y + row][playerCoord.X + col] == Blank || IsGoal(row, col);
}

void PrintState()
{
	gotoxy(15, 0, "현재 상태\n");
	for (int i = 0; i < MaxRow; ++i)
	{
		for (int j = 0; j < MaxCol; ++j)
		{
			printf("%d ", currentStage[i][j]);
		}
		printf("\n");
	}
}

void tryMove(int row, int col)
{
	if (CanMove(row, col))
	{		
		if (IsExistBlock(row, col))
		{
			//블록을 플레이어가 이동한 방향 + 1 만큼 밀어주기 위해 * 2를 해줌

			if (IsGoal(row * 2, col * 2))
			{
				gameOver = 1;
			}

			if (CanPush(row * 2, col * 2))
			{
				//민 블록을 그려준다.
				currentStage[playerCoord.Y + row * 2][playerCoord.X + col * 2] = Block;
				gotoxy(playerCoord.Y + row * 2, playerCoord.X + col * 2, icon[Block]);

				//현재 위치를 최근 아이콘으로 교체해준다.
				currentStage[playerCoord.Y][playerCoord.X] = preIcon;
				gotoxy(playerCoord.Y, playerCoord.X, icon[preIcon]);
				
				//플레이어 이동 및 갱신
				playerCoord.Y += row;
				playerCoord.X += col;
				currentStage[playerCoord.Y][playerCoord.X] = Player;
				gotoxy(playerCoord.Y, playerCoord.X, icon[Player]);
			}

			//테스트용
			PrintState();
			return;
		}

		//이동 전 기존 아이콘으로 현재 자리를 갱신한다.
		currentStage[playerCoord.Y][playerCoord.X] = preIcon;
		gotoxy(playerCoord.Y, playerCoord.X, icon[preIcon]);

		//최근 아이콘은 앞 칸의 아이콘
		preIcon = currentStage[playerCoord.Y + row][playerCoord.X + col];

		//플레이어 이동 및 갱신
		playerCoord.Y += row;
		playerCoord.X += col;
		currentStage[playerCoord.Y][playerCoord.X] = Player;
		gotoxy(playerCoord.Y, playerCoord.X, icon[Player]);

		//테스트용
		PrintState();
	}
}

int main()
{
	ShowMainMessage();

	//콘솔 버퍼 안보이게 설정
	CONSOLE_CURSOR_INFO ci = { 1, FALSE };
	SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &ci);
	getch();
	
	initStageInfo();
	drawMap();

	while (gameOver == 0)
	{
		int key = getch();
		if (key == 0xe0 || key == 0) //방향키는 두번의 입력으로 인식됩니다.
		{
			key = getch();

			switch (key)
			{
				case RIGHT:
					tryMove(0, 1);
					break;
				case LEFT:
					tryMove(0, -1);
					break;
				case UP:
					tryMove(-1, 0);
					break;
				case DOWN:
					tryMove(1, 0);
					break;
			}
		}
	}

	gotoxy(12, 0, "승리! (키를 눌러 종료해주세요.)");
	Sleep(1000);
	getch();
	return 0;
}

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

 

2618번: 경찰차

첫째 줄에는 동서방향 도로의 개수를 나타내는 정수 N(5 ≤ N ≤ 1,000)이 주어진다. 둘째 줄에는 처리해야 하는 사건의 개수를 나타내는 정수 W(1 ≤ W ≤ 1,000)가 주어진다. 셋째 줄부터 (W+2)번째 줄

www.acmicpc.net

 

dp에 어떻게 저장해야 될까? 생각이 안나서

구글링으로.. 다른 사람의 코드를 참고했고 이제 좀 이해가 되었다. (나중에 까먹을까봐 적어둔다 ㅠ)

 

사건을 중심으로 전개를 해야되는 방법이다.

최대 1000 * 1000 의 상태로 문제를 바꿔서 바라보는 방법이 필요했다. (이게 어려움..)

 

[ 1번 경찰차 최근 해결 사건 번호 ] [ 2번 경찰차 최근 해결 사건 번호 ]

 

DFS를 통해 사건 담당 상태에 따라 값을 갱신시켜준다.

겹치는 부분은 dp 값을 이용하여 대체가 가능해진다.

 

아래는 사건이 4개인 경우 DFS를 진행한 결과이다.

크기가 크지 않아 겹치는 주황 부분이 많지 않지만 사건의 갯수가 많아지면

생략되는 탐색이 많아지기에 효과를 볼 수 있다.

 

FindMinPath 가 깊이 우선 탐색을 하며 dp를 갱신하는 핵심 함수이고,

ShowPath에선 p1, p2의 현재 위치에서 탐색 비용과 이미 dp에 저장된 결과값의 최소값을

따라가며 경로를 알 수 있게 된다.

ex) (p1 : 경찰차1 , p2 : 경찰차2)

[p1][p2] 에 대한 최적의 루트는 이미 FindMinPath를 통해 갱신되었기에

경찰차1 목적지의 비용 + dp[목적지][p2]

경찰차2 목적지의 비용 + dp[p1][목적지]

중 어디가 더 비용이 적은지 탐색하며 p1과 p2를 바꿔주면 된다.

 

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef struct Position
{
	int x, y;
}Position;

vector<Position> pos;
int dp[1002][1002];
int n, w;

int GetDistance(Position pos1, Position pos2)
{
	return abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y);
}

int FindMinPath(int p1, int p2, int next)
{
	if (next > w) //사건의 수가 w개를 넘으면 종료합니다.
	{
		return 0;
	}

	if (dp[p1][p2] != 0)
	{
		return dp[p1][p2];
	}
        //GetDistance를 통해 해밍턴 거리를 구하고 DFS 탐색을 이어나갑니다.
	int p1Cost = GetDistance(pos[p1], pos[next]) + FindMinPath(next, p2, next + 1);
	int p2Cost = GetDistance(pos[p2], pos[next]) + FindMinPath(p1, next, next + 1);

	return dp[p1][p2] = min(p1Cost, p2Cost);
}

void ShowPath(int p1, int p2)
{
        //dp를 통해 경로를 역추적합니다.
	for (int next = 1; next <= w; ++next)
	{
		int p1Cost = GetDistance(pos[p1], pos[next]) + dp[next][p2];
		int p2Cost = GetDistance(pos[p2], pos[next]) + dp[p1][next];

		if (p1Cost < p2Cost)
		{
			cout << 1 << endl;
			p1 = next;
		}
		else
		{
			cout << 2 << endl;
			p2 = next;
		}
	}
}

int main()
{
	cin.tie(0);
	cin >> n >> w;

        //편의를 위해 미리 경찰차1, 경찰차2 위치를 저장해줍니다.
	pos.resize(w + 2);
	pos[0] = Position{ 1, 1 };     //0 을 경찰차1 위치로 정해줍니다.
	pos[w + 1] = Position{ n, n }; //w + 1 을 경찰차2 위치로 정해줍니다.

	for (int i = 1; i <= w; ++i) //1 ~ w 까지만 입력을 받습니다.
	{
		cin >> pos[i].x >> pos[i].y;
	}

        //첫번째 경찰차 인덱스[0], 두번째 경찰차[w + 1], 첫 사건 번호[1]
	int cost = FindMinPath(0, w + 1, 1);
	cout << cost << endl;
        //첫번째 경찰차는 0, 두번째 경찰차는 w + 1
	ShowPath(0, w + 1);
}

카메라에 붙여서 사용할 수 있는 코드입니다.

카메라가 보는 방향으로 이동 할 수 있게 만들었고

( transform.forward * vertical + transform.right * horizontal )

 

마우스 오른쪽 클릭을 통해 회전을 할 수 있는 코드입니다.

회전은 마우스 가로 움직임이 세로축에 영향을 주고

마우스 세로 움직임이 가로축에 영향을 줍니다.

MouseX => rotation.y에 영향

MouseY => rotation.x에 영향

( Quaternion.Euler(transform.rotation.x - mouseY, transform.rotation.y + mouseX, 0.0f) )

 

private new Transform transform; 를 선언하고 transform = GetComponent<Transform> 를 해주었는데

transform을 캐싱하여 성능을 높일 수 있다고 해서 사용했습니다! ㅎㅎ

using UnityEngine;

public class CameraController : MonoBehaviour
{
    public float rotateSpeed = 5.0f;
    public float moveSpeed = 3.5f;
    public float limitAngle = 70.0f;

    private new Transform transform;
    private bool isRotate;
    private float mouseX;
    private float mouseY;

    private void Start()
    {
        transform = GetComponent<Transform>();

        mouseX = transform.rotation.eulerAngles.y;  //마우스 가로(x)는 세로축(y) 이 중심
        mouseY = -transform.rotation.eulerAngles.x; //마우스 세로(y)는 가로축(-x) 이 중심
    }

    private void Update()
    {
        float horizontal = Input.GetAxisRaw("Horizontal");
        float vertical = Input.GetAxisRaw("Vertical");

        //카메라를 기준으로 앞, 옆으로 이동시킵니다.
        Vector3 movement = transform.forward * vertical + transform.right * horizontal;
        movement = movement.normalized * (Time.deltaTime * moveSpeed);
        transform.position += movement;

        //마우스 오른쪽 클릭 시 회전시킴
        if (Input.GetMouseButtonDown(1))
        {
            isRotate = true;
        }
        if (Input.GetMouseButtonUp(1))
        {
            isRotate = false;
        }

        if (isRotate)
        {
            Rotation();
        }
    }

    public void Rotation()
    {
        mouseX += Input.GetAxis("Mouse X") * rotateSpeed; // AxisX = Mouse Y
        mouseY = Mathf.Clamp(mouseY + Input.GetAxis("Mouse Y") * rotateSpeed, -limitAngle, limitAngle);

        //mouseX (가로 움직임) 은 Y축에 영향을 줌
        //mouseY (세로 움직임) 은 X축에 영향을 줌
        transform.rotation = Quaternion.Euler(transform.rotation.x - mouseY, transform.rotation.y + mouseX, 0.0f);
    }
}

 

1. Box Collider

박스 콜라이더는 위와 같이 x좌표, y좌표가 박스 내부에 있는지 판별을 하게 됩니다.

x좌표와 y좌표 모두 내부에 속해있으면 충돌로 판정하게 됩니다.

(만약 3차원이라면 z좌표까지도 내부인지 확인해주면 되겠습니다.)

public bool IsBoxInner ()
{
    float x = clickPosition.x;
    float y = clickPosition.y;

    return xMin <= x && x <= xMax &&
            yMin <= y && y <= yMax;
}

 

2. Circle Collider

원형 콜라이더는 거리를 구해서 반지름 값보다 같거나 작은지를 확인하면 됩니다.

간단한 계산 덕분에 가장 빠른 충돌체 처리로 알려져 있습니다.

피타고라스 정리를 이용하여 거리를 계산합니다.

가로^2 + 세로^2 = 빗변^2

위와 같이 빗변을 구하고 루트를 씌워주면 원하는 빗변값을 구할 수 있고,

빗변이 원의 반지름 이내라면 원과 접촉했다고 판별할 수 있습니다.

최적화는 빗변에 루트를 씌우지 않고 반지름을 제곱해주어 판별하면 되겠습니다.

//방법1
public bool IsCircleInner ()
{
    float distance = 
    Mathf.Sqrt((clickPosition.x - colliderPosition.x) * (clickPosition.x - colliderPosition.x)
             + (clickPosition.y - colliderPosition.y) * (clickPosition.y - colliderPosition.y));
    
    //float distance = Vector2.Distance(colliderPosition, clickPosition);

    return distance <= circleRadius;
}

//방법2
public bool IsCircleInner ()
{
    float distance = 
               (clickPosition.x - colliderPosition.x) * (clickPosition.x - colliderPosition.x)
             + (clickPosition.y - colliderPosition.y) * (clickPosition.y - colliderPosition.y);
    
    return distance <= circleRadius * circleRadius;
}

 

3. Triangle Collider

예전에 프로그래밍 고수형이 넥슨 회사 면접 볼 때 나왔던 문제라고 저한테 풀어보라고 했었습니다.

점이 삼각형 내부(충돌)인지 아닌지 아닌지 어떻게 판별을 해야될까요?

처음엔 삼각형의 기울기를 생각했었는데 선형대수학에 나오는 벡터의 외적을 이용하면 쉽게 풀 수 있습니다.

2차원 좌표이기에 외적을 구해보면 z값에 대해서만 변화가 있게 됩니다.

이 z값이 양수인지 음수인지를 따져보면 현재 위치에서 방향을 알 수 있게 됩니다.

먼저 상대적인 좌표로 만들어줍니다. 기준점 (-2, -2)으로부터의 거리로 만들어주고

두 점의 외적을 구해봅니다. 노란 화살표가 기준이 되고, 보라색 화살표가 왼쪽인지 오른쪽인지 판별을 합니다.

이렇게 3개의 꼭지점을 기준으로 위치를 판별해줍니다.

결과는 왼쪽, 오른쪽, 왼쪽 이 나왔고 점은 밖에 있다는 것이 판별되었습니다.

삼각형 내부에 있는 경우는 모두 동일한 방향에 있는 경우입니다.

아래는 이미 포지션 값을 정해놓고, 시뮬레이션 단계마다 판별을 하는 코드입니다.

전체 코드는 깃허브에 유니티 프로젝트와 함께 올려두었습니다.

private void DeterminePosition ()
{
    Vector3[] positions = triangle_clickPoint.GetTrianglePositions();

    Vector3 startPosition = positions[simulationStep - 1]; //기준 위치
    Vector3 endPosition = positions[simulationStep];       //기준 라인
    Vector3 basePoint = endPosition - startPosition;       //1번 화살표 구하기
        
    endPosition = triangle_clickPoint.GetClickPosition();  //(노란점)클릭 위치
    Vector3 clickPoint = endPosition - startPosition;      //2번 화살표 구하기

    //벡터의 외적을 통해 z값을 구합니다. 아래코드와 동일합니다.
    //float cross = Vector3.Cross(basePoint, clickPoint).z;
    
    float cross = basePoint.x * clickPoint.y - basePoint.y * clickPoint.x;
    
    if (cross >= 0)
    {
        //Is Left
    }
    else
    {
        //Is Right
    }
}

전체 코드와 유니티 프로젝트는 깃허브에 따로 올려두겠습니다!

https://github.com/dlaehdeod/Practice

 

GitHub - dlaehdeod/Practice: Play File and Sources

Play File and Sources. Contribute to dlaehdeod/Practice development by creating an account on GitHub.

github.com

 

버튼을 누르면 무언가 동작을 하게 해봅시다.

2가지 방법으로 연결이 가능합니다.

 

1. 코드로 연결하기

using UnityEngine;
using UnityEngine.UI;

public class UI_Script : MonoBehaviour
{
    public Button testButton;

    private void Start()
    {
        //방법1. 무명 메소드 람다식을 통해 적용
        testButton.onClick.AddListener(() => TestButtonDown());
        
        //방법2. delegate를 이용해 적용
        testButton.onClick.AddListener(delegate { TestButtonDown(); });
        
        //방법3. (방법1 에서 UnityAction으로 함수를 갖고 있는 방법입니다.)
        UnityEngine.Events.UnityAction action = () => TestButtonDown();
        testButton.onClick.AddListener(action);
        
        //방법4. (방법2 에서 UnityAction으로 함수를 갖고 있는 방법입니다.)
        UnityEngine.Events.UnityAction action2 = (delegate { TestButtonDown(); });
        testButton.onClick.AddListener(action2);
    }

    public void TestButtonDown ()
    {
        print("Button Down!");
    }
}

방법1~4 중에 편한 방법으로 써주면 되겠습니당. (모두 동일한 기능을 합니다.)

버튼이 많거나 동적으로 생성해서 사용해줄 때 AddListener를 이용하여 메소드를 등록해줄 수 있습니다.

(또는 버튼을 누른 다음에 다른 메소드로 변경하는데도 사용될 수 있겠죠?)

 

2. 화면에서 적용시켜주기.

  먼저 스크립트가 있어야 하고, 스크립트 내부에 public으로 선언 된 함수가 있어야 됩니다.

  UI_Script 이름의 스크립트를 생성해줍니다.

using UnityEngine;

public class UI_Script : MonoBehaviour
{
    public void TestButtonDown ()
    {
        print("Button Down!");
    }
}

 

 이제 게임오브젝트를 하나 생성해주고 UI_Script를 갖고 있게 해줍니다.

오브젝트 이름도 동일하게 UI_Script 로 해주겠습니다.

 

 

버튼을 생성해줍니다. 그러면 버튼에 옵션이 나오게 되고 스크립트를 On Click () 으로 넣어줍니다.

 

빨간 네모 부분을 눌러주면 메뉴들이 나옵니다.

UI_Script -> TestButtonDown

을 찾아서 연결해주면 버튼이 눌렸을 때 해당 함수가 호출이 됩니다.

 

Scriptable Object Instances


유니티에서 스크립터블 오브젝트를 통해 게임내의 필요한 것들을 관리할 수 있다.

위의 Scriptable_Skill은 아래와 같이 구성되어 있다.

 

using UnityEngine;

[CreateAssetMenu(fileName = "Scriptable_Skill", order = 1004)]
public class Scriptable_Skill : ScriptableObject
{
    public Skill skill;
    public Sprite sprite;
    public float hitPower;
    public float sensorDistance;
    public float sensorRadius;
}

CreateAssetMenu 를 해당 파일을 생성할 수 있게 만들 수 있다.

생성되는 파일명, 보여질 순서이다. (order를 생략하면 Folder 윗쪽으로 생기게 된다.)

프로젝트 뷰에서 마우스 오른쪽 클릭 후 스크립터블 오브젝트 생성이 가능하다.

 

충분히 편리하지만 이미지가 너무 작다는 것이 아쉽기에

인터넷 검색을 해보았고 결국 찾아냈다. 원하는 결과물은 다음과 같다.

 

Sprite = Source Image 와 같다.

 

새로운 스크립트를 작성해주어 Scriptable_Skill에 대한 에디터를 정의해주어야 된다. 최종적으로

2개의 스크립트를 작성해준다.

using UnityEngine;

[CreateAssetMenu(fileName = "Scriptable_Skill", order = 1004)]
public class Scriptable_Skill : ScriptableObject
{
    public Skill skill;
    public float hitPower;
    public float sensorDistance;
    public float sensorRadius;
    public Sprite sprite;
}
using UnityEngine;
using UnityEditor; //Editor 상속 받기위해 써줌

[CustomEditor(typeof(Scriptable_Skill))] //해당 타입에 대해 적용을 해준다. (반드시 써줘야됨)
public class Scriptable_SkillEditor : Editor //에디터를 상속 받는다. OnInspectorGUI 재정의 가능
{
    private Scriptable_Skill scriptable_Skill;
    private Sprite sprite;
    private GUILayoutOption[] options;

    private void OnEnable()
    {
        //GUI 옵션 설정. 크기를 위해 설정해줌
        options = new GUILayoutOption[] { GUILayout.Width(128), GUILayout.Height(128) };
        
        //Editor에선 serializedObject 를 통해 해당 스크립트에 접근이 가능하다.
        //serializedObject.target의 형 변환을 통해 해당 값에 접근이 가능해진다.
        scriptable_Skill = serializedObject.targetObject as Scriptable_Skill;
        
        //기존 sprite를 기본으로 해줌.
        sprite = scriptable_Skill.sprite;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        EditorGUILayout.BeginHorizontal(); //줄 맞춤을 위해 설정
        
        GUILayout.Label("Source Image"); //라벨 설정. 이 후 값들이 오른쪽으로 정렬 된다.
        //EditorGUILayout.PrefixLabel("Source Image"); //라벨 설정. 왼쪽으로 정렬 된다.
        
        //EditorGUILayout.ObjectField 를 통해 이미지를 보이게 할 수 있다.
        //(Object, type, allowSceneObject (수정 가능 여부), options)
        EditorGUILayout.ObjectField(sprite, typeof(Sprite), false, options);
        
        EditorGUILayout.EndHorizontal();  //줄 마무리
    }
}

 

 

+ Recent posts