UDN
Search public documentation:
DevelopmentKitGemsAddingOnScreenIndicatorsKR
English Translation
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
日本語訳
中国翻译
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
화면위 표지 추가하기
문서 변경내역: James Tan 작성. 홍성진 번역.
UDK 2011년 5월 버전으로 최종 테스팅, PC 호환
개요
디폴트로 언리얼 토너먼트 3 에는 카메라 앞에 플레이어의 이름을 표시하는 간단한 화면위 표지(on screen indicator)가 있습니다. 이 UDK 젬에서는 화면위에 타겟 위치를 가리키는 화면위 표지 만드는 법을 보여 드리겠습니다. 타겟이 카메라 뒤에 있어 화면을 벗어나도 화면위 표지는 화면 밑에 남아있을 것입니다. 타겟이 카메라 프러스텀 밖에 있어 화면을 벗어나도 화면의 한 쪽 가장자리에 남아 타겟 방향을 가리킵니다. 타겟이 현재 화면위에 렌더되지 않는다면 화면위 표지도 서서히 사라집니다. 마지막으로 타겟의 팀에 따라 화면위 표지색도 바뀝니다.
- 이 폰들은 화면 위에 보이며, 파란 팀입니다. 화면위 표지는 물론 그 화살표도 그에 맞게 왼쪽이나 오른쪽으로 약간 비껴 있습니다.
- 이 폰은 다리 뒤에 숨어 있기에, 화면위 표지는 투명해 집니다.
- 이 팀원은 카메라 프러스텀 시야에서 벗어나 있으며 보이지 않습니다.
- 이 폰들은 플레이어 뒤에 있으며 보이지 않습니다.
표지 머티리얼
화면위 표지 머티리얼은 로테이션, 불투명도, 팀 색 등 몇 가지를 담당합니다. 화면위 표지에 사용된 텍스처는 이렇습니다. 아래 이미지는 텍스처 개별 채널을 나타냅니다.
- 화면위 표지에 사용된 그레이스케일 디퓨즈입니다. 깨끗한 공간이 많이 있는데, 그렇지 않으면 텍스처가 회전할 때 텍스처 래핑(wrapping) 때문에 부작용이 생길 수 있습니다.
- 화면위 표지 중앙의 아이콘 불투명도를 설정하는 데 사용되는 마스크입니다.
- 화면위 표지에 대한 그림자를 만드는 데 사용되는 마스크입니다.
- 화면위 표지에 대한 불투명도로 사용되는 마스크입니다.
언리얼스크립트
SRadarInfo 구조체
각각의 화면위 표지에 대한 정보를 담는 데 구조체가 사용되었습니다.- UTPawn - 이 화면위 표지가 가리키는 폰으로의 참조입니다.
- MaterialInstanceConstant - 화면위 표지가 사용하는 머티리얼의 인스턴스입니다.
- DeleteMe - 삭제에 쓰이는 불리언입니다.
- Offset - 애니메이션에 쓰이는 화면위 표지의 오프셋입니다.
- Opacity - 화면위 표지의 현재 불투명도입니다.
struct SRadarInfo { var UTPawn UTPawn; var MaterialInstanceConstant MaterialInstanceConstant; var bool DeleteMe; var Vector2D Offset; var float Opacity; };
AddPostRenderedActor 함수
디폴트로 언리얼 토너먼트 3 는 기존의 이름 표지를 렌더하기 위해 포스트 렌더(나중에 렌더되는) 액터를 사용합니다. 이 함수는 딱 UTPawn 에 대해서만 그러한 행위를 제거합니다.function AddPostRenderedActor(Actor A) { // 이름표를 원하지 않으니 UTPawns 에 대한 포스트 렌더 콜 제거 if (UTPawn(A) != None) { return; } Super.AddPostRenderedActor(A); }
PostRender 이벤트
언리얼스크립트를 통해 텍스처나 머티리얼이나 텍스트를 화면 위에 렌더할 수 있도록 하기 위해, 언리얼스크립트에서 포스트 렌더 이벤트를 엔진이 호출합니다. 바로 이 곳이 UDK 젬의 로직 보석이 다량 함유되어 있는 곳으로, 좀 더 잘게 나눠 보도록 하겠습니다.업데이트 패스
이 패스에서, 먼저 DeleteMe 를 참으로 설정하여 레이더 정보를 모두 삭제 검사합니다. 그 다음 ForEach 를 사용하여 모든 UT Pawns 를 반복처리(iterate)합니다. 플레이어가 소유하지 않는 폰에 대해서는, 먼저 레이더 정보가 참조하고 있는지를, RadarInfo 배열에서 유효한 인덱스를 찾아보아 검사합니다. 인덱스도 유효하고 폰에 체력도 있으면 DeleteMe 를 거짓으로 설정하여 삭제 검사를 통과시킵니다. 인덱스는 무효하나 폰에 체력이 있으면, 레이더 인포를 새로 만듭니다. 새 레이더 인포가 생성되면, 그 레이더 인포에 대한 머티리얼 인스턴스도 새로 생성됩니다. 팀 컬러 역시 설정되며, DeleteMe 도 거짓으로 설정하여 삭제 검사를 통과시킵니다.// 찾지 못하면 모든 레이더 인포를 삭제하도록 설정 for (i = 0; i < RadarInfo.Length; ++i) { RadarInfo[i].DeleteMe = true; } // 레이더 인포를 업데이트한 후 더하거나 뺄 것이 있는지 확인 ForEach DynamicActors(class'UTPawn', UTPawn) { if (UTPawn != PlayerOwner.Pawn) { Index = RadarInfo.Find('UTPawn', UTPawn); // 이 폰은 레이더 인포에서 찾지 못했으니 추가 if (Index == INDEX_NONE && UTPawn.Health > 0) { i = RadarInfo.Length; RadarInfo.Length = RadarInfo.Length + 1; RadarInfo[i].UTPawn = UTPawn; RadarInfo[i].MaterialInstanceConstant = new () class'MaterialInstanceConstant'; if (RadarInfo[i].MaterialInstanceConstant != None) { RadarInfo[i].MaterialInstanceConstant.SetParent(Material'GemOnscreenRadarContent.PointerMaterial'); if (UTPawn.PlayerReplicationInfo != None && UTPawn.PlayerReplicationInfo.Team != None) { TeamLinearColor = (UTPawn.PlayerReplicationInfo.Team.TeamIndex == 0) ? Default.RedLinearColor : Default.BlueLinearColor; RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', TeamLinearColor); } else { RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', Default.DMLinearColor); } } RadarInfo[i].DeleteMe = false; } else if (UTPawn.Health > 0) { RadarInfo[Index].DeleteMe = false; } } }
렌더링 패스
패스 계산 전, 화면위 표지 크기를 먼저 계산한 다음 PointerSize 에 저장합니다. 여기 구현에서 이 작업은 화면 해상도 폭에 상대적입니다. 카메라가 보는 방향 역시 카메라 위치와 로테이션에서 구합니다. 카메라가 보는 방향은 폰이 플레이어 뒤에 있는지 아닌지를 알아내는 데 쓰입니다. 이 패스에서 DeleteMe 가 거짓이라면 레이더 인포가 렌더됩니다. 그렇지 않으면 레이더 인포 배열에서 제거됩니다. 화면위 표지가 렌더될 때, 최근 0.1초 이내에 연결된 폰이 렌더되었었는지를 먼저 검사합니다. 렌더되지 않았다면 그 불투명도를 40% 까지 선형 보간시켜 내립니다. 그 외의 경우는 100% 까지 선형 보간하여 올립니다. 불투명도가 계산되면, 머티리얼 인스턴스 콘스턴트에 설정됩니다. 플레이어의 폰과 폰 사이의 방향이 계산됩니다. 카메라 방향과 플레이어 폰에서 폰까지의 방향을 도트 곱(dot product)하여 플레이어가 현재 폰을 보고 있는지 아닌지를 결정합니다. 아래 이미지에서 빨강 화살표는 카메라 방향을 나타냅니다. 초록 화살표는 플레이어 폰이 폰을 보는 방향을 나타냅니다. 도트 곱을 하면 1.f 에서 0.f 사이의 값이 나올 테니, 카메라 앞에 있는 것입니다. 파랑 화살표는 플레이어 폰에서 다른 폰의 방향을 나타냅니다. 도트 곱은 0.f 와 -1.f 사이의 값이 될 테니, 카메라 뒤에 있는 것입니다.// 모든 레이더 인포 렌더링을 처리 PointerSize = Canvas.ClipX * 0.083f; PlayerOwner.GetPlayerViewPoint(CameraLocation, CameraRotation); CameraViewDirection = Vector(CameraRotation); for (i = 0; i < RadarInfo.Length; ++i) { if (!RadarInfo[i].DeleteMe) { if (RadarInfo[i].UTPawn != None && RadarInfo[i].MaterialInstanceConstant != None) { // 포인터의 불투명도 처리. 플레이어가 이 폰을 볼 수 없다면, // 절반 페이드 아웃. 볼 수 있다면, 페이드 인. if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f) { // 플레이어가 이 폰을 최근 0.1 초 동안 보지 못했음 RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f); } else { // 플레이어가 이 폰을 최근 0.1 초 내에 본 적이 있음 RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f); } // 불투명도 적용 RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity); // 플레이어 폰에서 폰으로의 방향 구하기 PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location); // 폰이 앞에 있는지 검사 if (PawnDirection dot CameraViewDirection >= 0.f) { // 액터가 카메라 앞에 있을 때 화면위 표지 렌더링 처리 } else { // 액터가 카메라 뒤에 있을 때 화면위 표지 렌더링 처리 } } } else { // 가비지 컬렉션이 가능하도록 기존 저장 변수 Null RadarInfo[i].UTPawn = None; RadarInfo[i].MaterialInstanceConstant = None; // 레이더 인포 배열에서 제거 RadarInfo.Remove(i, 1); // 스텝을 한 단계 낮춰 fot 루프 유지 --i; } }
카메라 앞 렌더링 패스
먼저 폰의 위치에다 콜리전 높이를 오프셋으로 더해서 WorldHUDLocation (월드 HUD 위치)를 계산합니다. 그런 다음 화면 좌표속으로 쏩(project)니다. 화면 좌표가 화면 왼편에 있으면 오프셋을 왼쪽으로, 오른편에 있으면 오프셋도 오른쪽으로 보간시켜, 화면위 표지에 약간의 애니메이션을 줍니다. 오프셋에도 제한(clamp)을 가하여 한 쪽으로 너무 크게 치우지지 않도록 합니다. 그런 다음 머티리얼 인스턴스의 로테이션을 계산한 다음 설정합니다. 마지막으로 화면위 표지를 화면 위에다 렌더합니다.// 월드 HUD 위치, 폰의 머리 바로 위의 위치를 구합니다. WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f)); // 월드 HUD 위치를 화면 HUD 위치속으로 쏩니다. ScreenHUDLocation = Canvas.Project(WorldHUDLocation); // 화면 HUD 위치가 조금 더 오른쪽이면, 왼쪽으로 돌립니다. if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f)) { RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f; } else { // 화면 HUD 위치가 조금 더 왼쪽이면, 오른쪽으로 돌립니다. RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f; } RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); // 머티리얼 아이콘의 로테이션을 설정합니다. ActualPointerLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8) + RadarInfo[i].Offset.X; ActualPointerLocation.Y = Clamp(ScreenHUDLocation.Y - PointerSize + RadarInfo[i].Offset.Y, 8, Canvas.ClipY - 8 - PointerSize) + (PointerSize * 0.5f); RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation)); // 머티리얼 포인터를 그립니다. Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f)); Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);
카메라 뒤 렌더링 패스
카메라 뒤에 있는 액터의 경우, 화면위 표지는 항상 화면 하단에 위치합니다. project 함수를 그대로 사용할 수는 있지만, 수정이 필요한 결과를 반환할 것입니다. 쏘아준 가로 좌표를 반전시키는 이유가 바로 그렇습니다. 가로 화면 좌표가 왼쪽 가장자리에 있으면 가로 오프셋은 오른쪽으로 밀어주고, 그 반대도 마찬가지입니다. 그렇게 하여 화면위 표지는 항상 화면위에 있는 것입니다. 비슷한 로테이션 계산을 하여 머티리얼 인스턴스에도 적용합니다. 마지막으로 화면위 표지를 화면 위에 렌더합니다.// 폰의 위치를 쏩니다. ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location); // 화면 HUD 위치를 반전시킵니다. ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X; // 화면 HUD 위치가 오른쪽 가장자리에 있으면, 왼쪽으로 돌립니다. if (ScreenHUDLocation.X > (Canvas.ClipX - 8)) { RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f; RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); } else if (ScreenHUDLocation.X < 8) { // 화면 HUD 위치가 왼쪽 가장자리에 있으면, 오른쪽으로 돌립니다. RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f; RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); } else { // 화면 HUD 위치가 가운데 어디쯤에 있으면, 쭉 폅니다. RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta); } // 화면 HUD 위치를 설정합니다. ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8); ScreenHUDLocation.Y = Canvas.ClipY - 8; // 실제 포인터 위치를 설정합니다. ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X; ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f); // 머티리얼 아이콘의 로테이션을 설정합니다. RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation)); // 머티리얼 포인터를 그립니다. Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f)); Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f);
각도 계산
이 각도 계산 메서드에서, 검정 화살표는 기준(reference) 벡터입니다. 두 벡터는 빨강선 아니면 녹색선을 이룹니다. 각 계산은 파랑 호로 표시됩니다. 머티리얼이 로테이션 값을 라디안으로 받기 때문에, 계산 역시도 라디안으로 합니다. 초기 검사를 약간 하는데, 0.f (0 도), Pi (180 도), Pi * 1.5f (270 도), Pi * 0.5f (90 도)와 같이 간단한 결과를 반환하도록 하기 위해서입니다. 이 메서드는 (SOH CAH TOA 를 사용하는) 4분면 검사 메서드나 다른 acos 메서드보다 빠릅니다.function float GetAngle(Vector PointB, Vector PointC) { // 위나 아래라서 각을 쉽게 결정할 수 있을지 검사 if (PointB.X == PointC.X) { return (PointB.Y < PointC.Y) ? Pi : 0.f; } // 왼쪽이나 오른쪽이라서 각을 쉽게 결정할 수 있을지 검사 if (PointB.Y == PointC.Y) { return (PointB.X < PointC.X) ? (Pi * 1.5f) : (Pi * 0.5f); } return (2.f * Pi) - atan2(PointB.X - PointC.X, PointB.Y - PointC.Y); }
완성된 언리얼스크립트 클래스
완성된 언리얼스크립트 클래스는 이러하며, 확실히 보여드리기 위해 모아놔 봤습니다.class UTRadarHUD extends UTTeamHUD; struct SRadarInfo { var UTPawn UTPawn; var MaterialInstanceConstant MaterialInstanceConstant; var bool DeleteMe; var Vector2D Offset; var float Opacity; }; var array<SRadarInfo> RadarInfo; function AddPostRenderedActor(Actor A) { // 이름표를 보이게 하고 싶지 않으니 UTPawns 에 대한 포스트 렌더 콜 제거 if (UTPawn(A) != None) { return; } Super.AddPostRenderedActor(A); } event PostRender() { local int i, Index; local Vector WorldHUDLocation, ScreenHUDLocation, ActualPointerLocation, CameraViewDirection, PawnDirection, CameraLocation; local Rotator CameraRotation; local UTPawn UTPawn; local LinearColor TeamLinearColor; local float PointerSize; if (PlayerOwner == None || PlayerOwner.Pawn == None) { return; } // 렌더 델타 셋업 RenderDelta = WorldInfo.TimeSeconds - LastHUDRenderTime; // 찾지 못한 레이더 인포를 모두 삭제 설정 for (i = 0; i < RadarInfo.Length; ++i) { RadarInfo[i].DeleteMe = true; } // 레이더 인포를 업데이트하고 더하거나 뺄 것이 있는지 확인 ForEach DynamicActors(class'UTPawn', UTPawn) { if (UTPawn != PlayerOwner.Pawn) { Index = RadarInfo.Find('UTPawn', UTPawn); // 레이더 인포에서 이 폰을 찾지 못했으니 추가 if (Index == INDEX_NONE && UTPawn.Health > 0) { i = RadarInfo.Length; RadarInfo.Length = RadarInfo.Length + 1; RadarInfo[i].UTPawn = UTPawn; RadarInfo[i].MaterialInstanceConstant = new () class'MaterialInstanceConstant'; if (RadarInfo[i].MaterialInstanceConstant != None) { RadarInfo[i].MaterialInstanceConstant.SetParent(Material'GemOnscreenRadarContent.PointerMaterial'); if (UTPawn.PlayerReplicationInfo != None && UTPawn.PlayerReplicationInfo.Team != None) { TeamLinearColor = (UTPawn.PlayerReplicationInfo.Team.TeamIndex == 0) ? Default.RedLinearColor : Default.BlueLinearColor; RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', TeamLinearColor); } else { RadarInfo[i].MaterialInstanceConstant.SetVectorParameterValue('TeamColor', Default.DMLinearColor); } } RadarInfo[i].DeleteMe = false; } else if (UTPawn.Health > 0) { RadarInfo[Index].DeleteMe = false; } } } // 모든 레이더 인포 렌더링 처리 PointerSize = Canvas.ClipX * 0.083f; PlayerOwner.GetPlayerViewPoint(CameraLocation, CameraRotation); CameraViewDirection = Vector(CameraRotation); for (i = 0; i < RadarInfo.Length; ++i) { if (!RadarInfo[i].DeleteMe) { if (RadarInfo[i].UTPawn != None && RadarInfo[i].MaterialInstanceConstant != None) { // 포인터의 불투명도 처리. 플레이어가 이 폰을 볼 수 없다면, // 절반 페이드 아웃, 볼 수 있다면 페이드 인. if (WorldInfo.TimeSeconds - RadarInfo[i].UTPawn.LastRenderTime > 0.1f) { // 플레이어가 0.1 초 안에 이 폰을 본 적이 없음 RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 0.4f, RenderDelta * 4.f); } else { // 플레이어가 0.1 초 안에 이 폰을 본 적이 있음 RadarInfo[i].Opacity = Lerp(RadarInfo[i].Opacity, 1.f, RenderDelta * 4.f); } // 불투명도 적용 RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Opacity', RadarInfo[i].Opacity); // 플레이어의 폰에서 폰으로의 방향 구하기 PawnDirection = Normal(RadarInfo[i].UTPawn.Location - PlayerOwner.Pawn.Location); // 폰이 내 앞에 있는지 검사 if (PawnDirection dot CameraViewDirection >= 0.f) { // 월드 HUD 위치, 폰의 머리 바로 위를 구함 WorldHUDLocation = RadarInfo[i].UTPawn.Location + (RadarInfo[i].UTPawn.GetCollisionHeight() * Vect(0.f, 0.f, 1.f)); // 월드 HUD 위치를 화면 HUD 위치 속으로 쏨 ScreenHUDLocation = Canvas.Project(WorldHUDLocation); // 화면 HUD 위치가 조금 더 오른쪽이면, 왼쪽으로 돌림 if (ScreenHUDLocation.X > (Canvas.ClipX * 0.5f)) { RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f; } else { // 화면 HUD 위치가 조금 더 왼쪽이면, 오른쪽으로 돌림 RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f; } RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); // 머티리얼 아이콘의 로테이션 설정 ActualPointerLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8) + RadarInfo[i].Offset.X; ActualPointerLocation.Y = Clamp(ScreenHUDLocation.Y - PointerSize + RadarInfo[i].Offset.Y, 8, Canvas.ClipY - 8 - PointerSize) + (PointerSize * 0.5f); RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation)); // 머티리얼 포인터 그림 Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f)); Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f); } else { // 액터가 카메라 뒤에 있을 때 화면위 표지 렌더링 처리 // 폰의 위치 쏘기 ScreenHUDLocation = Canvas.Project(RadarInfo[i].UTPawn.Location); // 화면 HUD 위치 반전 ScreenHUDLocation.X = Canvas.ClipX - ScreenHUDLocation.X; // 화면 HUD 위치가 오른쪽 가장자리에 있으면, 왼쪽으로 돌림 if (ScreenHUDLocation.X > (Canvas.ClipX - 8)) { RadarInfo[i].Offset.X -= PointerSize * RenderDelta * 4.f; RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); } else if (ScreenHUDLocation.X < 8) { // 화면 HUD 위치가 왼쪽 가장자리에 있으면, 오른쪽으로 돌림 RadarInfo[i].Offset.X += PointerSize * RenderDelta * 4.f; RadarInfo[i].Offset.X = FClamp(RadarInfo[i].Offset.X, PointerSize * -0.5f, PointerSize * 0.5f); } else { // 화면 HUD 위치가 가운데 어디쯤 있으면, 쭉 폄 RadarInfo[i].Offset.X = Lerp(RadarInfo[i].Offset.X, 0.f, 4.f * RenderDelta); } // 화면 HUD 위치 설정 ScreenHUDLocation.X = Clamp(ScreenHUDLocation.X, 8, Canvas.ClipX - 8); ScreenHUDLocation.Y = Canvas.ClipY - 8; // 실제 포인터 위치 설정 ActualPointerLocation.X = ScreenHUDLocation.X + RadarInfo[i].Offset.X; ActualPointerLocation.Y = ScreenHUDLocation.Y - (PointerSize * 0.5f); // 머티리얼 아이콘의 로테이션 설정 RadarInfo[i].MaterialInstanceConstant.SetScalarParameterValue('Rotation', GetAngle(ActualPointerLocation, ScreenHUDLocation)); // 머티리얼 포인터 그림 Canvas.SetPos(ActualPointerLocation.X - (PointerSize * 0.5f), ActualPointerLocation.Y - (PointerSize * 0.5f)); Canvas.DrawMaterialTile(RadarInfo[i].MaterialInstanceConstant, PointerSize, PointerSize, 0.f, 0.f, 1.f, 1.f); } } } else { // 가비지 컬렉팅 되도록 예전에 저장된 변수 Null RadarInfo[i].UTPawn = None; RadarInfo[i].MaterialInstanceConstant = None; // 레이더 인포 배열에서 제거 RadarInfo.Remove(i, 1); // 스텝을 하나 낮추고, for 루프 유지 --i; } } // 레이더 델타 셋업 LastHUDRenderTime = WorldInfo.TimeSeconds; Super.PostRender(); } function float GetAngle(Vector PointB, Vector PointC) { // 위나 아래여서 각을 쉽게 결정할 수 있을지 검사 if (PointB.X == PointC.X) { return (PointB.Y < PointC.Y) ? Pi : 0.f; } // 왼쪽이나 오른쪽이어서 각을 쉽게 결정할 수 있을지 검사 if (PointB.Y == PointC.Y) { return (PointB.X < PointC.X) ? (Pi * 1.5f) : (Pi * 0.5f); } return (2.f * Pi) - atan2(PointB.X - PointC.X, PointB.Y - PointC.Y); } defaultproperties { }
내려받기
- 이 UDK 젬에 사용된 콘텐츠와 소스 코드 OnScreenIndicator.zip 내려받기.