Unity的场景(Sence)视图

  1. 在场景中移动
    Unity的坐标系统
    Unity使用笛卡尔坐标系来描述物体坐标信息,笛卡尔坐标系分为左手坐标系和右手坐标系两种。Unity使用的是笛卡尔左手坐标系。

    注意:这里与Blender前视图的坐标系不同

    之前测试将Blender中创建的模型导入Unity中也确实发生了问题,在场景中的显示是正常的,但是添加角色移动的脚本以后操作起来会有问题。
    看起来Blender的顶视图和Unity的坐标系是可以对应的。也确实在网络上看到了对类似问题的描述和解决办法。
    大体上是需要将模型的x轴旋转-90°,然后Ctrl + A将该属性应用。再在此应用的基础上延x轴旋转90°调整回来。

    后续会测试一下这种方法是否有效。
    场景视图坐标工具
    单击任何锥形轴臂会将 Scene 视图摄像机对齐到该锥形轴臂所代表的轴(例如:顶视图、左视图和前视图)。还可以右键单击该立方体来显示一个包含视角列表的菜单。要返回默认视角,请右键单击场景视图辅助图标,然后单击 Free。
    导航工具
    方向键:激活场景窗口后可以使用方向键在场景中移动,按住Shift可以加速
    单击Q切换到查看工具状态下:
    按住左键拖动,可以在场景中移动
    按住Alt再按住左键拖动,可以围绕当前轴心点旋转视角
    鼠标滚轮:可缩放
    在场景中按住鼠标右键:
    可使用WASD向左/向右/向前/向后移动,使用 Q 和 E 向上和向下移动
    聚焦指定对象:选中对象后,点 F。要将视图锁定到游戏对象(即使游戏对象正在移动),按 Shift+F。
  2. 选择游戏对象
    可以在场景中选择,也可以在层级中选择。
    可以设置对象是否允许被选取,也可以设置对象是否显示。但要注意设置为不显示的对象依然可以被选取,所以一般情况下在设置不显示的同时也会将其设置为不可选取。

Unity软件界面

2个视图:
场景视图(Sence):编辑场景的地方,基本上游戏中能看到的所有元素都包含在场景中,可以通过 场景视图来编辑、操作
游戏视图(Game):游戏执行的效果展示

3个窗口:
项目窗口(project):资源库
层级窗口(Hierachy):游戏对象按层次展示在该窗口中,可以快速定位我们要操作的对象,并清晰展示对象间关系
检查器窗口(Inspector):可以查看并配置选中对象的各个属性,选中的对象不同,布局和内容也不同

常用的Blender快捷键

快捷键功能
A全选
X删除
Shift + A添加
Shift + D复制
Shift + C3D游标回到原点
G随意移动
GX
GY
GZ

在指定坐标轴上移动
GX3在x轴上移动3格
R旋转
RX90沿x轴旋转90度
S缩放
/隔离模式
Tab进入编辑模式
编辑模式下
1,2,3

点,线,面
Ctrl + L关联
Ctrl + P创建父级
Alt + P清空父级
Ctrl + I反向选择
E挤出
Alt + E沿法向挤出
姿态模式下
Alt + G
Alt + R
Alt + S
骨架复位
位置
旋转
缩放
Ctrl + A应用(缩放等)
在选项上点击右键加入快速收藏夹
Q打开快速收藏夹
M添加集合
Alt + Z透明模式
Ctrl + R
滚动鼠标滚轮
插入循环边
调整循环边数量
编辑模式下2
Alt + 左键

选中循环边
Ctrl + B倒角
编辑模式下
U

UV映射

Ubuntu安装Gitlab

1.安装和配置必要的依赖项

sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates tzdata perl
sudo apt-get install -y postfix

2.添加GitLab包仓库并安装包

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
sudo apt-get install gitlab-ce

3.设置浏览器要访问的IP地址及端口号

编辑配置文件

vim /etc/gitlab/gitlab.rb

4.重新载入配置及启动服务

gitlab-ctl reconfigure

gitlab-ctl start

5.查找Gitlab默认root密码

vim /etc/gitlab/initial_root_password

注:该文件为临时文件,将于reconfigure后的24小时后自动删除,注意尽早保存。

静态链接DLL与动态调用DLL

静态链接DLL:

需要设置“附加包含目录”,“附加库目录”以及“附加依赖项”。

然后通过外部引用函数定义即可。

#include <stdio.h>
#include <Windows.h>

extern int add(int a, int b);

int main(int argc, char* argv[])
{
	MessageBoxW(NULL, L"Test", L"Test", MB_OK);

	printf("5 + 3 = %d", add(5, 3));

	return 0;
}

动态调用DLL:

即通过函数调用的方式获得所需函数的地址,然后通过函数指针的方式调用函数。

#include <stdio.h>
#include <Windows.h>

int main(int argc, char* argv[])
{
	HMODULE load_lib = NULL;
	FARPROC get_proc_add = 0;

	MessageBoxW(NULL, L"Test", L"Test", MB_OK);

	do
	{
		load_lib = LoadLibraryW(L"C:\\Users\\Raiseki\\source\\repos\\dll_learn\\x64\\Debug\\dll_learn.dll");
		if (load_lib == NULL)
		{
			break;
		}
		get_proc_add = GetProcAddress(load_lib, "add");
		if (get_proc_add == NULL)
		{
			break;
		}
		printf("5 + 3 = %lld", get_proc_add(5, 3));
	} while (0);

	return 0;
}

注意:

当找不到DLL文件时,采用静态链接方式的程序系统会报错且无法运行。而采用动态调用方式的程序虽然无法调用到所需函数,但系统不会报错。

C语言控制台程序编写Windows服务(二)

C语言控制台程序编写Windows服务(一)的基础上进行了改动。对比上次,本次用到了Event(事件)和父子进程的概念,解决了服务在系统重启后无法随系统自动启动的问题。

目标回顾:编写一个服务,能够正常添加到Windows服务列表并能随系统启动,可正常手动启停。暂时不需要通过服务实现任何实际的功能。

修改过程中明确了以下几点:
1. 明确原理,仔细阅读文档说明。
以此方式写exe服务在exe文件启动时会存在两个进程。一个控制台程序的进程以及一个服务进程。
2. 明确需要做的工作。同时满足以下两件事。
(1)判断服务进程是否已经存在。如果不存在则通过控制台程序进程创建服务并启动,否则退出,不要让程序运行到StartServiceCtrlDispatcherW。
(2)要避免服务进程重复创建进程导致程序出错。这就需要能够判断当前进程是控制台程序进程还是服务进程。服务进程的特点是它们都是services.exe的子进程,所以可以通过判断当前进程的父进程是否为service.exe的方式来对两种进程进行区分。方法如下:
获取进程pid,ppid以及进程名
3.今后编写代码要注意清晰简洁。大量无意义的代码有时会让自己陷入混乱。

以下为修改后的代码:

#include <stdio.h>
#include <Windows.h>
#include <tlhelp32.h>

SERVICE_STATUS			service_status;
SERVICE_STATUS_HANDLE	service_status_handle;

BOOL GetProcessInfoWithPID(DWORD pid, DWORD* ppid, WCHAR* process_name)
{
	BOOL			result = FALSE;
	HANDLE			snapshot_handle = NULL;
	PROCESSENTRY32W	process_entry32;
	BOOL			process_return = FALSE;

	do
	{
		snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
		if (snapshot_handle == INVALID_HANDLE_VALUE)
		{
			break;
		}
		process_entry32.dwSize = sizeof(process_entry32);
		process_return = Process32FirstW(snapshot_handle, &process_entry32);

		while (process_return)
		{
			if (process_entry32.th32ProcessID == pid)
			{
				if (ppid != NULL)
				{
					*ppid = process_entry32.th32ParentProcessID;
				}
				if (process_name != NULL)
				{
					lstrcpyW(process_name, process_entry32.szExeFile);
				}
				result = TRUE;
				break;
			}
			process_entry32.dwSize = sizeof(process_entry32);
			process_return = Process32NextW(snapshot_handle, &process_entry32);
		}

	} while (0);

	if (snapshot_handle)
	{
		CloseHandle(snapshot_handle);
	}
	return result;
}

BOOL ServiceStart(HANDLE handle)
{
	BOOL					result = FALSE;
	SERVICE_STATUS_PROCESS	status;
	DWORD					bytes_need = 0;

	SecureZeroMemory(&status, sizeof(status));
	do
	{
		result = QueryServiceStatusEx(handle, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), &bytes_need);
		if (result == FALSE)
		{
			break;
		}
		else if ((status.dwCurrentState == SERVICE_RUNNING) || (status.dwCurrentState == SERVICE_START_PENDING))
		{
			service_status.dwCurrentState = SERVICE_RUNNING;
			result = TRUE;
			break;
		}
		result = StartServiceW(handle, 0, NULL);
		if (result == FALSE)
		{
			break;
		}
		printf("please wait...\n");
		do
		{
			result = FALSE;
			Sleep(500);
			result = QueryServiceStatusEx(handle, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(SERVICE_STATUS_PROCESS), &bytes_need);
			if (result == FALSE)
			{
				break;
			}
		} while (status.dwCurrentState == SERVICE_START_PENDING);
		result = FALSE;
		if (status.dwCurrentState == SERVICE_RUNNING)
		{
			result = TRUE;
			break;
		}
	} while (0);

	return result;
}

BOOL InstallService()
{
	BOOL		result = FALSE;
	SC_HANDLE	SCM_handle = NULL;
	SC_HANDLE	service_handle = NULL;
	DWORD		return_value = 0;
	TCHAR		Path[MAX_PATH];

	do
	{
		return_value = GetModuleFileNameW(NULL, Path, MAX_PATH);
		if (return_value == 0)
		{
			break;
		}
		SCM_handle = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (SCM_handle == NULL)
		{
			break;
		}
		service_handle = CreateServiceW(
			SCM_handle,
			L"TestService",
			L"TestService",
			SC_MANAGER_ALL_ACCESS,
			SERVICE_WIN32_OWN_PROCESS,
			SERVICE_AUTO_START,
			SERVICE_ERROR_IGNORE,
			Path,
			NULL,
			NULL,
			NULL,
			NULL,
			NULL
		);
		if (service_handle == NULL)
		{
			break;
		}
		result = ServiceStart(service_handle);
		if (result == FALSE)
		{
			break;
		}
		result = TRUE;
	} while (0);

	if (SCM_handle != NULL)
	{
		CloseServiceHandle(SCM_handle);
	}
	if (service_handle != NULL)
	{
		CloseServiceHandle(service_handle);
	}

	return result;
}

VOID WINAPI ServiceControlHandler(DWORD parameter)
{
	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	service_status.dwCurrentState = SERVICE_RUNNING;
	service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	service_status.dwWin32ExitCode = 0;
	service_status.dwServiceSpecificExitCode = 0;
	service_status.dwCheckPoint = 0;
	service_status.dwWaitHint = 0;

	if (parameter == SERVICE_CONTROL_STOP)
	{
		service_status.dwCurrentState = SERVICE_STOPPED;
	}
	SetServiceStatus(service_status_handle, &service_status);

	return;
}

VOID WINAPI ServiceMain(DWORD argc, LPWSTR* argv)
{
	service_status_handle = RegisterServiceCtrlHandlerW(L"TestService", (LPHANDLER_FUNCTION)ServiceControlHandler);

	service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	service_status.dwCurrentState = SERVICE_RUNNING;
	service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	service_status.dwWin32ExitCode = 0;
	service_status.dwServiceSpecificExitCode = 0;
	service_status.dwCheckPoint = 0;
	service_status.dwWaitHint = 0;

	SetServiceStatus(service_status_handle, &service_status);

	while (1)
	{
		Sleep(10000);
		OutputDebugStringA("TestService Running");
	}

	return;
}

int main(int argc, char* argv[])
{
	BOOL					result = 0;
	HANDLE					event_handle = NULL;
	DWORD					pid = 0;
	DWORD					ppid = 0;
	WCHAR					process_name[MAX_PATH];
	int						wcscmp_return;
	SERVICE_TABLE_ENTRY		service_table[2];

	SecureZeroMemory(process_name, sizeof(process_name));

	do
	{
		event_handle = CreateEventW(NULL, FALSE, TRUE, L"Global\\Test");
		if (event_handle == NULL)
		{
			break;
		}
		pid = GetCurrentProcessId();
		result = GetProcessInfoWithPID(pid, &ppid, process_name);
		if (result == FALSE)
		{
			break;
		}
		result = GetProcessInfoWithPID(ppid, 0, process_name);
		if (result == FALSE)
		{
			break;
		}
		wcscmp_return = lstrcmpiW(process_name, L"services.exe");
		if (wcscmp_return != 0)
		{
			if (GetLastError() == ERROR_ALREADY_EXISTS)
			{
				break;
			}
			result = InstallService();
			if (result == 0)
			{
				break;
			}
			break;
		}

		service_table[0].lpServiceName = L"TestService";
		service_table[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
		service_table[1].lpServiceName = NULL;
		service_table[1].lpServiceProc = NULL;

		result = StartServiceCtrlDispatcherW(service_table);
		if (result == 0)
		{
			break;
		}

	} while (0);
	if (event_handle != NULL)
	{
		CloseHandle(event_handle);
	}
	return 0;
}

获取进程pid,ppid以及进程名

获取pid:

pid = GetCurrentProcessId();

获取ppid和进程名:

BOOL GetProcessInfoWithPID(DWORD pid, DWORD* ppid, WCHAR* process_name)
{
	BOOL			result = FALSE;
	HANDLE			snapshot_handle = NULL;
	PROCESSENTRY32W	process_entry32;
	BOOL			process_return = FALSE;

	do
	{
		snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
		if (snapshot_handle == INVALID_HANDLE_VALUE)
		{
			break;
		}
		process_entry32.dwSize = sizeof(process_entry32);
		process_return = Process32FirstW(snapshot_handle, &process_entry32);

		while (process_return)
		{
			if (process_entry32.th32ProcessID == pid)
			{
				if (ppid != NULL)
				{
					*ppid = process_entry32.th32ParentProcessID;
				}
				if (process_name != NULL)
				{
					lstrcpyW(process_name, process_entry32.szExeFile);
				}
				result = TRUE;
				break;
			}
			process_entry32.dwSize = sizeof(process_entry32);
			process_return = Process32NextW(snapshot_handle, &process_entry32);
		}

	} while (0);

	if (snapshot_handle)
	{
		CloseHandle(snapshot_handle);
	}
	return result;
}

该函数的第二个或第三个参数如不需要可以填零。

创建事件CreateEventW

函数原型:

CreateEventW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_ BOOL bManualReset,
    _In_ BOOL bInitialState,
    _In_opt_ LPCWSTR lpName
    );

参数1:安全描述符

参数2:手动复位
TRUE为手动,FALSE为自动。影响等待线程释放后,线程是手动还是自动的回到复位状态(即无信号状态)。

参数3:初始状态
TRUE为置位(有信号),FALSE为复位(无信号)。

参数4:事件对象名称
可设置一个最长为MAX_PATH的时间对象名称。
可以选择本地或全局(Local或Global),
如:CreateEvent( NULL, FALSE, FALSE, “Global\\CSAPP” );
利用全局内核对象命名空间可以做到进程间的同步。
也可以活用此功能避免运行重复的进程。

返回值:
如果函数成功,则返回值是事件对象的句柄。如果命名事件对象在函数调用之前存在,则该函数返回现有对象的句柄,而 GetLastError返回 ERROR_ALREADY_EXISTS。
如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用 GetLastError。

事件(Event)

多用于线程同步。

可能用到的函数:

1.CreateEventW

HANDLE CreateEventW(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCWSTR               lpName
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventw

2.WaitForSingleObject

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject

3.SetEvent

BOOL SetEvent(
  HANDLE hEvent
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setevent

4.ResetEvent

BOOL ResetEvent(
  HANDLE hEvent
);

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-resetevent

5.SetEventWhenCallbackReturns

void SetEventWhenCallbackReturns(
  PTP_CALLBACK_INSTANCE pci,
  HANDLE                evt
);

https://docs.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-seteventwhencallbackreturns