반응형

다이내믹 섀도잉

다이내믹 오브젝트(, 이를테면 모빌리티가 무버블로 설정된 스태틱 메시 컴포넌트 및 스켈레탈 메시 컴포넌트)는, 디스턴스 필드 섀도맵에서 월드의 스태틱 섀도잉에 통합시켜야 합니다. 이는 Per Object 섀도로 가능합니다. 각 이동 오브젝트는 하나의 스테이셔너리 라이트에서 다이내믹 섀도를 두 개 만듭니다: 오브젝트에 드리워지는 정적인 월드에 대한 그림자와, 월드에 드리워지는 오브젝트에 대한 그림자입니다. 이런 구성에서 스테이셔너리 라이트에 드는 섀도잉 비용은, 영향을 끼치는 다이내믹 오브젝트에서만 발생합니다. 즉 다이내믹 오브젝트의 수에 따라 비용이 매우 조금 들 수도 많이 들 수도 있습니다. 다이내믹 오브젝트의 수가 일정 이상이라면, 무버블 라이트의 효율이 더 좋습니다.

 

 


섀도 맵 캐시

 

 

Movable Lights (무버블 라이트)는 완벽히 동적인 빛과 그림자를 드리우며, 위치나 방향, 색, 밝기, 감쇠, 반경 등 그 모든 프로퍼티를 변경할 수 있습니다. 여기서 나오는 라이트는 라이트 맵에 구워넣지 않으며, 현재 간접광을 받을 수 없습니다.

섀도잉

그림자를 드리우도록 설정된 무버블 라이트는 전체 씬 다이내믹 섀도를 사용하기에 퍼포먼스 비용이 엄청납니다. 퍼포먼스에 가장 큰 영향을 끼치는 부분은 라이트에 영향받는 메시의 갯수와, 해당 메시의 트라이앵글 수 입니다. 즉 커다란 반경에 그림자를 드리우는 무버블 라이트는 반경이 작은 무버블 라이트보다 비용이 몇 배가 될 수 있다는 뜻입니다.

사용법

어느 라이트든지 Transform (트랜스폼) 카테고리 아래 보면 Mobility (모빌리티) 이라는 풀다운 프로퍼티가 있습니다. 이 옵션을 Movable (무버블)로 바꿉니다. 이 프로퍼티는 블루프린트에 추가된 라이트 컴포넌트에도 나타납니다.

섀도 맵 캐시

포인트 / 스포트 라이트가 움직이지 않을 때, 그 라이트에 대한 섀도 맵을 저장한 뒤 다음 프레임에 재사용할 수 있습니다. 그러면 배경이 잘 움직이지 않는 게임에서 무버블 포인트 / 스포트 라이트의 그림자 비용을 크게 줄일 수 있습니다. 여기서는 언리얼 엔진 4 (UE4) 프로젝트에서 그 기능을 사용하는 방법을 알아보겠습니다.

섀도 맵 캐시 & 퍼포먼스

Shadow Map Caching (섀도 맵 캐시) 기능은 어느 UE4 프로젝트에서든 자동 활성화 가능합니다. 섀도 맵 캐시를 사용했을 때의 퍼포먼스를 확인해 보려면, 다음과 같은 방법으로 섀도 맵 캐시 기능을 껐다켰다 하면 됩니다:

다음 섀도 맵 캐시 데모에서는 Sun Temple 프로젝트가 사용되었습니다. 에픽 게임스 런처의 학습 탭에서 이 맵을 받으실 수 있습니다.

  1. 프로젝트의 레벨에서 다이내믹 섀도를 드리우도록 하고픈 라이트를 전부 선택합니다.

  2. 라이트의 모빌리티  무버블 로 설정하고 그림자 드리우기 옵션이 켜졌는지 확인합니다.

  3. 물결표 (`) 키를 눌러 콘솔 창을 열고 Stat Shadowrendering 이라 입력하면 현재 다이내믹 섀도 비용을 볼 수 있습니다.

  4. 다시 물결표 (\) 키를 눌러 <strong>콘솔</strong> 창을 열고 r.Shadow.CacheWholeSceneShadows 0` 이라 입력하여 다이내믹 섀도 캐시 기능을 끕니다.

    CallCount  InclusiveAug 부분의 숫자를 유심히 봐주세요.

  5. 이제 물결표 키를 한 번 더 눌러 콘솔 창을 열고 r.Shadow.CacheWholeSceneShadows 1 이라 입력하여 섀도 캐시 기능을 다시 켭니다. CallCount  InclusiveAug 숫자를 비교해 보면 이 기능이 다이내믹 섀도 퍼포먼스에 끼치는 영향을 확인할 수 있습니다.

    Shadow Caching On

    Shadow Caching Off

     

     

퍼포먼스

섀도 맵 캐시 기능은 퍼포먼스에 엄청난 영향을 끼칩니다. NVIDIA 970 GTX GPU 에서 1920x1200 해상도로 이 최적화를 테스트해 본 결과는 다음과 같습니다.

  • 이 기능을 켜기 전, 그림자를 드리우는 포인트 라이트가 셋 있는 환경에서 캐시가 없는 경우 섀도 뎁스를 렌더링하는 데 걸린 시간은 14.89ms 입니다.

  • Cached Shadow Maps (섀도 맵 캐시) 기능을 켠 상태에서, 똑같은 렌더링 작업에 걸린 시간은 .9ms, 약 16 배 빠른 것입니다!

    참고로 포인트 라이트 33 개의 기여 부분을 렌더링하는 데는 여전히 2ms 가 걸리는데, 이 부분은 다른 식으로 최적화시킬 수는 있지만 이러한 변화에는 영향받지 않습니다.

  • 섀도 맵 캐시에 사용되는 최대 메모리 양은 r.Shadow.WholeSceneShadowCacheMb 로 조절할 수 있습니다.

한계

섀도 맵 캐시 기능이 UE4 프로젝트의 다이내믹 섀도 비용을 낮춰주기는 하지만, 지원되지 않는 기능과 함께 썼을 때 렌더링 부작용을 일으키게 되는 한계점이 몇 가지 있습니다. 여기서는 섀도 맵 캐시의 한계는 무엇이고 그와 관련해서 무엇을 할 수 있는지 알아보겠습니다.

  • 기본적으로 오브젝트가 다음 요건을 충족했을 때만 캐시가 가능합니다:

    • 프리미티브의 모빌리티  스태틱 또는 스테이셔너리 여야 합니다.

    • 레벨에 사용되는 머티리얼이 World Position Offset 을 사용하지 않아야 합니다.

    • 라이트는 포인트 또는 스포트 라이트여야 하고, 그 모빌리티  무버블 로 설정되어 있으며, 그림자 드리우기 옵션이 켜져 있어야 합니다.

    • 라이트가 한 곳에 있어야 합니다.

    • 애니메이팅 Tessellation 또는 Pixel Depth Offset 을 사용하는 머티리얼은 그림자의 뎁스를 캐시에 저장할 때 부작용이 생길 수 있습니다.

 

 

 

 

 

 

ref : docs.unrealengine.com/ko/BuildingWorlds/LightingAndShadows/LightMobility/StationaryLights/index.html

반응형
반응형

 

두개 벡터 값을 곱한다 * 연산자가 모호해서 Scale 이 추가 된것이라 볼 수 있다

 

Multiplies two vectors component-wise.

Every component in the result is a component of a multiplied by the same component of b.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
// Calculate the two vectors generating a result.
// This will compute Vector3(2, 6, 12)
 
using UnityEngine;
using System.Collections;
 
public class ExampleClass : MonoBehaviour
{
    void Example()
    {
        print(Vector3.Scale(new Vector3(123), new Vector3(234)));
    }
}
 
 

 

 

ref : 

docs.unity3d.com/ScriptReference/Vector3.Scale.html

반응형
반응형

 

 

        GameObject rootObject = contentsRoot.transform.root.gameObject;

        var all = rootObject.GetComponentsInChildren<Transform>(true);

        foreach (var el in all)

        {

            if(PrefabUtility.IsPartOfRegularPrefab(el.gameObject))

            {

                PrefabUtility.UnpackPrefabInstance(el.gameObject, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);

            }

        }

Colored by Color Scripter

 

 

 

using UnityEngine;

using UnityEditor;

 

public class Example

{

    [MenuItem("Examples/Add BoxCollider to Prefab Asset")]

    static void AddBoxColliderToPrefab()

    {

        // Get the Prefab Asset root GameObject and its asset path.

        GameObject assetRoot = Selection.activeObject as GameObject;

        string assetPath = AssetDatabase.GetAssetPath(assetRoot);

 

        // Load the contents of the Prefab Asset.

        GameObject contentsRoot = PrefabUtility.LoadPrefabContents(assetPath);

 

        // Modify Prefab contents.

        contentsRoot.AddComponent<BoxCollider>();






        // Save contents back to Prefab Asset and unload contents.

        PrefabUtility.SaveAsPrefabAsset(contentsRoot, assetPath);

        PrefabUtility.UnloadPrefabContents(contentsRoot);

    }

 

    [MenuItem("Examples/Add BoxCollider to Prefab Asset"true)]

    static bool ValidateAddBoxColliderToPrefab()

    {

        GameObject go = Selection.activeObject as GameObject;

        if (go == null)

            return false;

 

        return PrefabUtility.IsPartOfPrefabAsset(go);

    }

}

 

 

 

 

 

 

ref : docs.unity3d.com/ScriptReference/PrefabUtility.LoadPrefabContents.html?_ga=2.99521374.1778878325.1608379497-1959443505.1601370427

 

반응형
반응형

/** * Gets the list of support fullscreen resolutions. * @return true if successfully queried the device for available resolutions. */

UFUNCTION(BlueprintCallable, Category="Rendering")

static bool GetSupportedFullscreenResolutions(TArray<FIntPoint>& Resolutions);

 

 

 

 

you can Use Bluerpinnt to change resolution

 

 

and Desktop resolution

 

 

 

FullScreen

 

 

Full screen C++ code

 

 

 

ref : www.youtube.com/watch?v=yc28rvQ6SNk

 

 

 

 

 

 

 

반응형
반응형

Hello, I try to make resolution settings, but when change resolution with console command with custom resolution ( r.setRes 600 x 400 ) it change the window size and if is full screen it doesn't make anything. How to make resolutions settings with this or other method?

 

 

The r.SetRes command is the right way to go, but you are missing one little feature to make it work with full screen:
Usage of r.SetRes r.SetRes 600x400, where n can either be "f" for fullscreen or "w" for windowed, so if you want to apply 600x400 at fullscreen, you do r.SetRes 600x400f

 

ConsoleCommand

 

 

forums.unrealengine.com/development-discussion/blueprint-visual-scripting/107699-resolution-change

반응형
반응형

ue4 플러그인 EditorScriptingUtilities 추가시 이것 을 사용 하려면

 

프로젝트명.Build.cs 에

 

 

PublicDependencyModuleNames.AddRange(new string[] {

"EditorScriptingUtilities" }

 

PrivateDependencyModuleNames.AddRange(new string[] {

"EditorScriptingUtilities" }

 

각각 추가해 주면 된다

 

 

반응형
반응형

절대축 기준..

 

 

GetWorld()->GetFirstPlayerController()->GetControlRotation() : 0~360 사이 각도 리턴

 

GetWorld()->GetFirstPlayerController()->GetControlRotation().Quaternion().Euler()  :  -180 ~ 180 사이의 각도를 리턴한다 

 

 

GetControlRotation 은 컨트롤러의 Rotation 을 리턴한다

 

FRotator APawn::GetControlRotation() const
{
return Controller ? Controller->GetControlRotation() : FRotator::ZeroRotator;
}

 

FRotator AController::GetControlRotation() const
{
ControlRotation.DiagnosticCheckNaN();
return ControlRotation;
}

 

 

회전 값을 행렬로 변환하기 FRotationMatrix()FRotationMatrix(GetControlRotation())

 

 

회전 방향에 대한 절대 축 얻기

 

GetControlRotation() 절대축 기준 회전 값

FRotationMatrix(GetControlRotation()).GetUnitAxis(EAxis::X)

FRotationMatrix(GetControlRotation()).GetUnitAxis(EAxis::Y)

 

vector3 방향으로 회전 행렬 생성 (X 축 기준으로 얼마나 회전이 되었는지 계산

FRotationMatrix::MakeFromX( vector3  ).Rotator()

 

 

 

1차 선형 보간

FMath::FInterpTo

 

회전 보간

FMath::RInterpTo

 

 

충돌 처리

 

도형을 특정 시작 위치에서 목표하는 지점까지 쓸면서(Sweep) 충돌한것이 있다면  반환 하는 함수

 

FHitResult hitResult;
//NAME_None 숫자 0
FCollisionQueryParams params(NAME_None, false, this);

 

bool bResult =

 GetWorld()->SweepSingleByChannel(hitResult, GetActorLocation(), GetActorLocation() + GetActorForwardVector() * 200.0f,
FQuat::Identity, ECollisionChannel::ECC_GameTraceChannel12, FCollisionShape::MakeSphere(50.0f), params);

 

ECollisionChannel::ECC_GameTraceChannel12 : 충돌한 채널

 

FCollisionShape::MakeSphere : 스피어를 만든다

 

 

 

 

FMatrix FRotationMatrix::MakeFromZ(FVector const& ZAxis)

 

ZAxis 이 벡터를 Z 축에 대한 기준으로 삼고 이를 토대로 x,y 축을 뽑아내어 회전 행렬을 만든다

ZAxis 의 방향이 나온 결과에 있어선 z 축이 된다

 

 

 

컴포넌트->SetupAttachment( 붙일 대상, 소켓에 붙일려면 소켓명기입 )

 

액터->AttachToComponent(붙일 대상,  컴포넌트를 붙일 규칙, 소켓에 붙일려면 소켓명기입 )

이 방식으로 하면 무기가 월드 아웃라이너에서 액터로 따로분리 되어 있는것을 볼 수 있다

 

+CreateDefaultSubobject에서 사용하는 문자열의 값은 액터에 속한
  컴포넌트를 구별하기 위한 Hash 값 생성에 사용되고 유일해야한다.

 

//바로 오브젝트를 바로 붙일수 있는건 아니고 Skeletal 이면 이를 관리하는 USkeletalMeshComponent 컴포넌트를 먼저 먼든 다음
//이 컴포넌트에서 관리할 메쉬포인터 쪽으로 무기를 넘겨주고
//실제 이 무기를 사용(캐릭텉에 부착하려면) 무기 컴포넌트를 뼈대에 붙여야 한다
FName weaponSocket(TEXT("hand_rSocket"));
if (GetMesh()->DoesSocketExist(weaponSocket))
{
        _weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WEAPON"));
        static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_WEAPON(TEXT("/Game/무기 경로.."));
        if (SK_WEAPON.Succeeded())
        {
            _weapon->SetSkeletalMesh(SK_WEAPON.Object);
        }
        _weapon->SetupAttachment(GetMesh(), weaponSocket);
    }


//액터를 붙일때의 코드 형태 (AABWeapon 클래스 안에서 웨폰을 생성하여 갖고 있는 상태)
FName weaponSocket(TEXT("hand_rSocket"));
auto currentWeapon = GetWorld()->SpawnActor<AABWeapon>(FVector::ZeroVector, FRotator::ZeroRotator);

if (currentWeapon != nullptr)
{
currentWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, weaponSocket);
}

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
void AABCharacter::SetWeapon(class AABWeapon* newWeapon)
{
    ABCHECK(nullptr != newWeapon && nullptr == _currentWeapon);
    FName weaponSocket(TEXT("hand_rSocket"));
    if (nullptr != newWeapon)
    {
        newWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, weaponSocket);
        newWeapon->SetOwner(this); //네트웍 리플리케이션을 위한 setOwner(this);
        _currentWeapon = newWeapon;
    }
}


* Set the owner of this Actor, used primarily for network replication. 
 * @param NewOwner The Actor whom takes over ownership of this Actor
 */
UFUNCTION(BlueprintCallable, Category=Actor)
virtual void SetOwner( AActor* NewOwner );
 
 

 

 

SetVisibility(bool)          : 인게임과 에디터에서 bool 에 따라 보이거나 안보이거나 한다

SetHiddenInGame(bool) :true 일때 에디터 에서 보이지만 인게임에선 안보인다

 

 

SetActorEnableCollision : 액터 전체의 콜리전 을 껐다 켰다 한다

SetCollisionProfileName("") :  콜리전 preset 을 변경 할 수 있다

 

 

 

컴포넌트  OnComponentBeginOverlap 델리게이트 설정 방법

 

//할당
_trigger->OnComponentBeginOverlap.AddDynamic(this&AABItemBox::OnCharacterOverlap);
 
 
 
//헤더 부분에 함수 선언
 
UFUNCTION() 
void OnCharacterOverlap(UPrimitiveComponent* OverlappedComponent, 
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, 
bool bFromSweep, const FHitResult& SweepResult);
 
 
//정의 
void AABItemBox::OnCharacterOverlap(UPrimitiveComponent* OverlappedComponent, 
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, 
bool bFromSweep, const FHitResult& SweepResult) 
///



 

 

 

 

 

 

//게임 인스턴스 구하기

auto abGameInstance = Cast<UABGameInstance>(UGameplayStatics::GetGameInstance(GetWorld()));


프로퍼티 

Transient

이 프로퍼티는 휘발성이라, 저장 또는 로드되지 않습니다. 이런 식의 지정자가 붙은 프로퍼티는 로드 시간에 0 으로 채워집니다.

https://docs.unrealengine.com/ko/Programming/UnrealArchitecture/Reference/Properties/index.html


액터 수명 주기

https://docs.unrealengine.com/ko/Programming/UnrealArchitecture/Actors/ActorLifecycle/index.html

 

액터의 수명 주기

액터가 로드 또는 스폰된 후 결국 죽을 때 벌어지는 일에 대해서입니다.

docs.unrealengine.com

 

게임 시작 과정 

 
만약 레벨에 이미 올라와 있는 액터가 있다면 
 
AActor::AActor() // 레벨에 올라가 있던것
 
AActor::PostLoad or AActor::PostActorCreated  // 레벨에 올라가 있던것
이 과정을 거친다음
 
UGameInstance::Init  : 상주하는 게임 인스턴스가 초기화
 
 
AActor::PostInitializeComponents // 레벨에 올라가 있던것
 
이후에는 GameMode 에 의해서 생성된것을 기준으로 보면
 
APlayerController::AABPlayerController         //컨트롤러 먼저 생성하고
 
APlayerController::PostInitializeComponents 
 
AGameMode::PostLogin(26)PostLogin Begin  //게임모드 PostLogin 이 실행하기 전(함수는 실행되나 
                                 Super::PostLogin(NewPlayer); 이 실행되기 이전임, 즉 이 함수 실행이 되면서
 
ACharacter::ACharacter(76)                      //게임 모드에서 생성되는 캐릭터
ACharacter::PostActorCreated(218)           //캐릭터가 생성됨
ACharacter::PostInitializeComponents(196)
 
APlayerController::OnPossess(15)OnPossess begin   //컨트롤러가 Pawn(캐릭터)를 시작 전
(즉 Super::OnPossess(InPawn); 실행전)
 
ACharacter::PossessedBy(223)   //캐릭터가 컨트롤러에 의해서 소유됨
 
APlayerController::OnPossess(18)OnPossess end //캐릭터 소유, Super::OnPossess(InPawn); 실행이후
 
AGameMode::PostLogin(28)PostLogin end //로그인 과정 끝


Actor::BeginPlay() //미리 로드되어 있던것

AABPlayerController::BeginPlay()

ACharacter::BeginPlay()

AGameMode::BeginPlay() //미리 로드되어 있던것



AGameMode::StartPlay(33)StartPlay() //게임 플레이 시작

블루프린트를 만들때 부모를 .cpp 로 만든다음 SkeletalMesh 값을 자식 BP 에서 변경 했을때

 

C++ 쪽에서 부모에서 default 로 기본이 되는 mesh 를 적용했고 이것을 상속 받은

BP 에서 열고 새롭게 할당한 Mesh 가 블루프린트에서 적용되는 C++ 지점은 PostLoad() 부분이다

PostLoad 를 레벨이 이미배치 됐을때 호출 되는것이며

SpawnActor 로 스폰하면 PostLoad가 아닌 PostActorCreated 이 호출 된다(즉 상호 배제적)

 

 

 

 

 

 

델리게이트

 

다이네믹 델리게이트

C++ 만의 델리게이트가 있고 C++ 과 블루프린트간에 연동되는 델리게이트가 있는데 이 연동 되는 델리게이트를

다이네믹 델리게이트라 한다, 다이네믹 델리게이트의 경우 UFUNCTION() 과 사용해야함으로 C++ 람다식을 사용할 수는 없다

 

DECLARE_MULTICAST_DELEGATE 는 람다 가능

 

 

FOnMontageEndedMCDelegate OnMontageEnded;  : 몽타주 애니가 끝날때(섹션 재생도중 중간에 끝나도) 이벤트가 발생

 

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnMontageEndedMCDelegate, UAnimMontage*, Montage, bool, bInterrupted);

//Animinstance.h 에 있는 내장형

UPROPERTY(BlueprintAssignable) 

FOnMontageEndedMCDelegate OnMontageEnded;

 

 

 

UFUNCTION() //다니네믹 멀티 라서 UFUNCTION 이 들어감

void OnAttackMontageEnded(UAnimMontage* montage, bool bInterrupted);

 

 

//UFUNCTION() c++ 과 블루프린트에서 모두에 호출되는 함수 바인딩시 AddDynamic

_aBAnim->OnMontageEnded.AddDynamic(this&AABCharacter::OnAttackMontageEnded);

 

 

 

 

멀티캐스트 델리게이트  : 등록된 모든 함수에게 알려주는 기능

 

람다를 델리게이트에 바인딩 시

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);

 

 

FOnNextAttackCheckDelegate OnNextAttackCheck;

 

// UFUNCTION 을 연결하는 것이 아님

    _aBAnim->OnNextAttackCheck.AddLambda([this]()->void

    {

        //ABLOG(Warning, TEXT("OnNextAttackCheck"));

        _canNextCombo = false;

        if (_isComboInputOn)

        {

            AttackStartComboState();

            _aBAnim->JumpToAttackMontageSection(_currentCombo);

        }

    }

    );

 

// 연결된것 모두 호출

OnNextAttackCheck.Broadcast(); // 호출

 

 

C++ 블루프른트와 연동 되면서 등록된 함수모두에게 알려주는 기능 : 다이네믹 멀티캐스트 델리게이트

DECLARE_DYNAMIC_MULTICAST_DELEGATE

 

 

 

Montage_IsPlaying () : 몽타주가 재생중인지 확인

Montage_JumpToSection()  : 해당 섹션으로 몽타주 재생 아니 jump 하기

 

Primitive : 기본 도형

 

 

 

 

void AttackCheck();

 

일반 UObject 함수 바인딩시

_aBAnim->OnAttackHitCheck.AddUObject(this, &AABCharacter::AttackCheck);

 

 

이벤트

여러 함수에 바인딩하여 모두 한 번에 실행시킬 수 있는 델리게이트입니다.

이벤트(Event)는 멀티캐스트 델리게이트 와 매우 유사합니다.

그러나 어느 클래스도 이벤트 바인딩이 가능하지만, 이벤트를 선언한 클래스만이 이벤트의 Broadcast, IsBound, Clear 함수를 호출할 수 있습니다.

즉 이와 같이 민감한 함수에 대한 접근권을 외부 클래스에 주면 어떡하지 하는 걱정 없이,

이벤트 오브젝트는 퍼블릭 인터페이스에 노출시킬 수 있다는 뜻입니다. 이벤트가 사용되는 곳은, 순수 추상 클래스의 콜백 포함하기, 외부 클래스의 Broadcast, IsBound, Clear 함수 호출 제한시키기 입니다.

 

DECLARE_EVENT( OwningType, EventName )

이벤트를 생성합니다.

DECLARE_EVENT_OneParam( OwningType, EventName, Param1Type )

파라미터가 하나인 이벤트를 생성합니다.

...

DECLARE_EVENT 매크로의 첫 파라미터는 이 이벤트를 "소유"(own)하게 될 클래스,

 Broadcast() 함수를 호출할 수 있는 클래스입니다.

 

https://docs.unrealengine.com/ko/Programming/UnrealArchitecture/Delegates/Events/index.html

 

 

 

//액터에 UI 위젯을 달수 있는 컴포넌트, 액터가 이것을 달면 UI HP 같은 바를 나타낼 수 있음

//이걸 들고 있으면 됨

UWidgetComponent 

 

이것을 사용하려면 언리얼 엔진에서 UMG 모듈을 추가해주야 한다

기본적으로 이 모듈은 제외되어 있음

 

프로젝트명.Build.cs

 

파일에 보면 모듈들이 있는데 그 중 UMG 가 빠져 있는 것을 볼 수 있다

 

언리얼 엔진은 여러 모듈들이 합쳐진 형태로 구성되고 해당 프로젝트마다 모듈을 조합하여 하나의 프로젝트를 구성하게 된다

 

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore"});

 

"UMG" 부분 추가

 

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG" });

 

UE4 프로젝ㅌ에서 Source> runtime > UMG 폴더에 위치하게된다

 

이렇게 모듈을 추가하면 .h 는 추가해줘야 하지만 전체 경로를 몰라도 Public 폴더에 있는 헤더 파일을 게임 프로젝트(현재 게임을 만드는 프로젝트)에 이있는 헤더 파일처럼 자유롭게 참조 할 수 있다 

헤더는 Source>Runtime>UMG>Public>Components>WidgetComponent.h  에 있는데

 

WidgetComponent.h 이것을 포함하면 된다

#include "Components/WidgetComponent.h"

 

UPROPERTY(VisibleAnywhere, Category=UI)
class UWidgetComponent* _hpBarWidget;

 

_hpBarWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("HPBARWIDGET"));

 

_hpBarWidget->SetupAttachment(GetMesh());

 

 

UI 로직은 위젯 블루프린트는 애님인스턴스와 비슷하게 C++ 클래스에서 미리 만들어 제공할 수 있다

위젯 블루프린트가 사용하는 기반 C++ 클래스는 UserWidget 이다

 

 

 

위젯의 초기화 시점이 4.21 부터 PostInitializeComponents 에서 BeginPlay 로 변경 됨

 

 

UI 초기화 시점은 PlayerController의 BeginPlay 에서 UI 가 생성된다

생성시 NativeConstruct 함수가 호출된다

PlayerController::PostInitializeComponents() 함수에서 실행한 명령은 UI 에 반영 되지 않음으로

UI 시스템이 준비되면 호출 되는 함수 NativeConstruct 함수 에서 실행한다

여기에서 위젯 내용을 업데이트 한다

 

 

위젯에 있는것중에 이름으로 ProgressBar 를 얻어올 수 있음

_hPProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HPBar")));

 

void UABCharacterWidget::NativeConstruct()
{
Super::NativeConstruct();
_hPProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PB_HPBar")));
ABCHECK(_hPProgressBar!=nullptr);
UpdateHPWidget();
}

반응형
반응형

 

먼저 TPointerIsConvertibleFromTo 이 부분에 대해 알아보자

 

template <typename From, typename To>

struct TPointerIsConvertibleFromTo

{

private:

    static uint8  Test(...);

    static uint16 Test(To*);

 

public:
    //sizeof(Test((From*)nullptr)  해당 함수에 대한 리턴 타입 크기를 얻는다 즉 2 또는 1이 리턴 됨
    //함수 실행 없이 리턴 타입 크기를 얻어올 수 있다

    enum { Value  = sizeof(Test((From*)nullptr)) - 1 };

};

먼저 이건 From 타입에서 To 라는 타입으로 변환 가능하면 Value 값이 1 이 되며 만약 불가능한

포인터 간의 변환이라면 컴파일 타임에 enum 의 Value 값이 0 이 된다

 

static uint8  Test(...); 은     static uint16 Test(To*); 이 아닌 인자를 받기 위한 함수의 가변 형이다

즉 To 타입이 아닌 타입은 리턴 타입 크기를 1로 처리하겠다는 의미

 

ex)..
int printz(...); // valid, but the arguments cannot be accessed portably
보통 가변인자는 앞에 가변인자 개수를 알려주는 정수형이 오지만 위의 경우에는 단지 리턴 타입 사이즈를 얻기위함과 To 가 아닌 타입을 받기위한 처리로 가변인자로 처리한것

 

 

실행 없이 함수의 리턴 타입 크기를 얻어온다( 그래서 int fun(char* a); 까지만 있어도 됨

It returns the size of the return type from that function (4 on my implementation since that's what an int takes up for me), which you would discover had you run it as is, then changed the return type to char (at which point it would give you 1).

The relevant part of the C99 standard is 6.5.3.4.The sizeof operator:

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

Keep in mind that bold bit, it means that the function itself is not called (hence the printf within it is not executed). In other words, the output is simply the size of your int type (followed by a newline, of course).

 

 

 

그다음 볼 구문은 TEnableIf

template <bool Predicate, typename Result = void>
class TEnableIf;

 

기본 적으로 봐야 하는 부분은 Predicate 부분인데 Predicate 로 넘어온 값이
1 즉 참이면 Type 이 정의 되고 그렇지 않다면 즉 0이 넘어온다면 Type은 정의 되지 않는다
Result=void 라는 것을 잊지 않고 이어서 나가보자

 

이 구문은 템플릿의 부분 특수화가 적용 되어 있는 구문이다

 

원래 아래와 같은 이러한 템플릿이 존재 했는데 

 

template <bool Predicate, typename Result = void>
class TEnableIf

{

}

 

 

여기서 Predicate 부분에 대해서만 부분적으로 특수화를 진행한다 

부분 특수화를 진행하려면 나머지 인자를 그대로 써주면 된다 그리하여

bool 타입은 true, 또는 false 임으로 이에 대해 부분 특수화를 진행하면 다음처럼 된다

 

template <typename Result>
class TEnableIf<true, Result>
{
public:

   typedef Result Type;   

   //이때는 Type 을 Result 로 정의 하겠다는 의미인데 별다른 타입 정의가 없다면 Result 는
     위의 기본 디폴 트 값으로 인하여 void 타입이 된다

};

template <typename Result>
class TEnableIf<false, Result>
{  

  //즉 이때는 Type 을 정의하지 않겠다, 존재 Type  존재자체가 없어서 Type 을 쓰면

    컴파일 에러를 발생시키겠다는 의미이다

};

 

 

이때

template <bool Predicate, typename Result = void> 
class TEnableIf

{

이 구문을 간략하게 {} 를 제거 하고

 

template <bool Predicate, typename Result = void> 
class TEnableIf;

 

이렇게 표현 하는 것이 가능하다, ture ,false 모두 다 정의했음으로

 

 

 

아래는 이 두개를 응용한 코드이다

#include <type_traits>

#include <iostream>

 

using namespace std;

 

 

template <bool Predicate, typename Result = void>

class TEnableIf;

 

template <typename Result>

class TEnableIf<true, Result>

{

public:

    typedef Result Type;

};

 

template <typename Result>

class TEnableIf<false, Result>

{ };

 

template<typename T>

class aaa {

public:

 

    //enum { Value = sizeof(unsigned short) - 1 };

    enum { Value = sizeof(unsigned short- 1 };

};

 

template<typename = TEnableIf<aaa<int>::Value>::Type>

void fn()

{

 

}

 

int main()

{

    fn();

    aaa<int> ai;

    auto ddddd= ai.Value;

 

    auto sdfsd=aaa<int>::Value;

 

    TEnableIf<aaa<int>::Value> sdf;

    

    //TEnableIf<aaa<int>::Value>::Type* sdfdd;

    //TEnableIf<true>::Type sdfd;

 

 

    return 0;

}

 

 

 

 

TSubclassOf (1)

다른 타입 변환 간의 안정적인 타입을 확인하고자 하는 템플릿 클래스

이 클래스는 UClass* 를 멤버 변수로 담는 템프릿 클래스이다

 

template<class TClass>
class TSubclassOf
{

....

private:
UClass* Class;

}

 

상세 구문..

template<class TClass>
class TSubclassOf
{
    template <class TClassA>
    friend class TSubclassOf;
public:
    /** Default Constructor, defaults to null */
    FORCEINLINE TSubclassOf() :
        Class(nullptr)
    {
    }
    /** Constructor that takes a UClass and does a runtime check to make sure this is a compatible class */
    FORCEINLINE TSubclassOf(UClass* From) :
        Class(From)
    {
    }
    /** Copy Constructor, will only compile if types are compatible */
    template <class TClassA, class = typename TEnableIf<TPointerIsConvertibleFromTo<TClassA, TClass>::Value>::Type>
    FORCEINLINE TSubclassOf(const TSubclassOf<TClassA>& From) :
        Class(*From)
    {
    }
    /** Assignment operator, will only compile if types are compatible */
    template <class TClassA, class = typename TEnableIf<TPointerIsConvertibleFromTo<TClassA, TClass>::Value>::Type>
    FORCEINLINE TSubclassOf& operator=(const TSubclassOf<TClassA>& From)
    {
        Class = *From;
        return *this;
    }
 
 
...중략...
 
private:
    UClass* Class;
};
 

 

이 중에서도 아래 구문을 보자

 

우선 아래 구문은 TSubclassOf 에 대한 대입 연산자 구문을 기반으로 진행된다

 

TClassA 이것은 자기 자신이 아닌 assign 될 대상 타입이 된다
TClass 가 자기 자신 타입, 즉 넘어온 타입이 자기 자신으로 포인터 변환이 가능한지 여부에 따라 컴파일 에러를 벹게 할것인지 아닌지를 결정한다

 

/** Copy Constructor, will only compile if types are compatible */

    template<class TClassA

    class = typename TEnableIf<TPointerIsConvertibleFromTo<TClassATClass>::Value>::Type>

    FORCEINLINE TSubclassOf& operator=(const TSubclassOf<TClassA>& From)

    {

        Class = *From;

        return *this;

    }

 

TEnableIf 과 TPointerIsConvertibleFromTo 의 조합임을 알 수 있다 

TPointerIsConvertibleFromTo 의 결과 값인 Value 값이 1 이면 => 변환 가능한 포인터라면

TEnableIf 에서 Type 을 typede 하게 되고 Type 은 void 처리 되어 class=void 가 되어

컴파일 에러가 발생하지 않지만 그렇지 않은 경우에는 컴파일 오류를 발생시킨다

class=   이렇게만 됨으로

 

Copy Constructor, 는 형식이 호환 되는 경우에만 컴파일 처리한다

 

 

//컴파일 가능

template<typename = void >
void fn3() {
}

fn3();

 ....

 

 

//컴파일 불가

template<typename =  >
void fn3() {
}

fn3();

 

 

 

TSubclassOf (2)

TSubclassOf 는 UClass 유형의 안전성을 보장해 주는 템플릿 클래스입니다. 예를 들어 디자이너가 대미지 유형을 지정하도록 해주는 프로젝타일 클래스를 제작중이라 가정합시다. 그냥 UPROPERTY 유형의 UClass 를 만든 다음 디자이너가 항상 UDamageType 파생 클래스만 할당하기를 바라거나, TSubclassOf 템플릿을 사용하여 선택지를 제한시킬 수도 있습니다. 그 차이점은 아래 코드와 같습니다:

 

두 번째 선언에서, 템플릿 클래스는 에디터의 프로퍼티 창에 UDamageType 파생 클래스만 선택되도록 합니다. 첫 번째 선언에서는 아무 UClass 나 선택할 수 있습니다. 아래 그림에서 확인됩니다.

 

 

이러한 UPROPERTY 안전성에 추가로, C++ 수준에서의 유형 안전성도 확보할 수 있습니다.

 

비호환 TSubclassOf 유형을 서로에게 할당하려는 순간, 컴파일 오류가 나게 됩니다.

(위에 언급한 이유로)

 

범용 UClass 를 할당하려는 경우, 할당이 가능한지 검증하는 실행시간 검사를 합니다.

실행시간 검사가 실패하면, 결과값은 nullptr 입니다.

 

UClass* ClassA = UDamageType::StaticClass();
 
TSubclassOf<UDamageType> ClassB;
 
ClassB = ClassA; // Performs a runtime check //런타임때 체크
 
TSubclassOf<UDamageType_Lava> ClassC;

//컴파일 타임때 체크
ClassB = ClassC; // Performs a compile time check

 

ref :  

https://stackoverflow.com/questions/12659486/what-does-sizeof-functionargument-return

https://docs.unrealengine.com/en-US/API/Runtime/Core/Templates/TPointerIsConvertibleFromTo/index.html

 

https://docs.unrealengine.com/ko/Programming/UnrealArchitecture/TSubclassOf/index.html

https://en.cppreference.com/w/cpp/language/variadic_arguments

반응형
반응형

 

 

투본 IK 는 3개의 조인트를 갖고 IK 를 구하는 방식이다
(언리얼에서의 정의 : 투 본 IK 를 사용해서 3 조인트 체인에 IK 를 적용하는 법입니다.
Two Bone IK (투 본 IK) 컨트롤은 캐릭터의 사지와 같은 3 조인트 체인에 Inverse Kinematics(역운동학, IK)를 적용합니다.)

 

투본 IK 의 경우 본 3개로 IK 를 구현 하는데 중간 조인트가 어느 방향쪽으로 걲일지에 대한 기준있게 되고

이 기준 위치를 따라 중간 본이 어떤 방향으로 꺽일지가 정해지게 된다

(무기/총 잡을 때 등에 쓰일 수 있다)

 

 

 

 

 

IK Bone : 컨트롤할 본 이름, IK 에 의해 컨트롤 되는 메인 본 체인 : 오른 손을 선택

 

Joint Target : 오른쪽 어깨를 선택 해 놓고

 

2본 IK(Two Bone IK) 노드엣 Joint Target Location 의 위치를 바꾸면 그 위치 기준으로 하여

꺽을 방향이 역으로 생기게 된다

 

아래 이미지는 Joint Target Location 을 캐릭터가 바라보는 앞쪽으로 뺏을때 팔이 역으로 꺽이는 형태이고 

 

 

 

Joint Target Location 를 음수로 하면 즉 캐릭터의 뒤쪽에 배치를 시키면

 

 

바르게 팔이 꺽이는 것을 볼 수 있다

 

 

이 상태에서 

 

Effector Location 위치를 바꾸면  right hand 가 대상을 따라간다

 

 

 

Analytic Two-Bone IK in 2D

December 29, 2008

Due to their complexity, inverse kinematics (IK) problems are often solved with iterative solutions. While iterative solutions can handle different IK poblems with a single algorithm, they can also become computationally expensive. If a specific IK problem needs to be computed often, it is worth considering an analytic solution.

 

If we limit the problem to a two bone chain in a two-dimensions, we can derive the anaytic solution without much complexity. This will generally be more efficient than its iterative alternatives. While this specific case lends itself to a 2D world, it can also be used in 3D as long as the kinematic chain's motion is limited to a single plane.

 

The goal of is to create a robust function with the following requirements.

  1. Let the user specify which direction to bend the bone chain (there can be up to two valid solutions, each bending in an opposite direction).
  2. Notify the user when there was no valid solution based on the input.
  3. When there is no valid solution, calcualte the closest alternative.
  4. Produce the expected results for every target position. Most implementations found online or in books only function properly when the target is in a certain quadrant or when it remains in a specific range.

 

If you aren't interested in the exciting math ahead, jump to the end of the article for sample code. You can also download the RJ_Demo_IK application and source code to see a running implementation. If you're the type that likes to know how things work (like me), we will walk through the derivation in detail. It might look extensive at first, but it's only because I've broken it down step by step. Hopefully you'll be able to follow along without much difficulty.

 

You might also be interested in the following articles on different IK solvers:

 

원점에 조인트 하나와 중간에 꺽인(θ2 앞에) 조인트 그리고 대상으로할 Target 지점 (x,y) 이렇게 3개가 주어지고

거리 d1, d2 가 주어진다

그리고 원점에서부터 target 까지의 거리 h 로 놓을 수 있는데

 

Two bone ik 는 figure1 의 도형을 보면 θ2, θ1 을 구하는 것으로 좁혀진다

아래의 유도 과정은 삼각함수들을 통해 구하는 과정으로 먼저 θ2 를 구한 다음 이를 토대로 θ1을 구해 나가는 방식이고

d1 과 d2 거리의 차의 거리까지가 target 으로 접근하는 최소 거리 d1+d2 의 거리가 target 으로 향할 수 있는 최대 거리이며 target 으로 향할수 있을때 target 점 까지의 각도를 두개의 각도  θ2, θ1 를 구할 수 있지만 그 외의서는

벌을 최대한 뻗어 Target 으로 향하는 방향을 가리키게 된다는 유도 과정이다

Defining the problem

Our problem domain consists of a two bone chain where bone1 is located at the origin and bone2 is a child of bone1. Each bone conistis of an angle θ and a distance dd. The goal is to get the end of bone2 to line up with the target point (x,y). Because we have set bone1 at the origin, you will need to transform your destination point relative to bone1. Figure 1 shows how our variables fit together. The distance from the orgin to the target point is defined as hh.

figure 1

 

The solvable domain

 

figure 2

 

Math review

Before we hop into the derivation, I want to review a few trigonometric identities that we will be using. For more information on the identities, Wikipedia has an extensive list and also has a page on the derivations.

 

 

Solving for θ2

figure 3

 

 

 

We now have our analytic equation for θ2, but there are a few nuances to discuss in more detail.

  1. theta2 기준으로 봤을 때 d1 과 d2 가 회전 되어서 구부러지는 것을 생각해보면 되는데 theta2가 -1이면 d1 과 d2 가 펴지고, theta2 가 1 이면 d1 과 d2 가 최대로 구부러지게 된다
    If cosθ2 is not within the range [-1,1], it is outside of the arccosθ domain. This will happen when our target point is a location the bones cannot reach. A value of -1 will give us an angle of 180° (i.e. bone2 is fully bent) and a value of 1 will give us an angle of 0° (i.e. bone2 is fully extended). When the value is less than -1, our bone is trying to bend father than is physically possible to reach a point that is too close to the origin. When the value is greater than 1, our bone is trying to extend farther than is physically possible to reach a point that is too far from the origin. This means that we can compute the best non-valid solution by clamping the value into the legal [-1,1] range.

  2. If d1 or d2 is zero, we will divide by zero when computing cos⁡θ2. When one or both bones have a zero length, we can set θ2 to any value we desire (I suggest zero or the value used on a previous frame). When we solve for θ1 it will correct itself according to our selected θ2. The solvable domain for this case has also been reduced to a single circle centered at the origin with a radius of d1+d2.
  3. When we can evaluate arccosθ, our result is limited to the range [0,π]. If we use the result as is, we will always find the solution where our second bone turns in the positive direction. If it is our intention to find the solution that bends in the negative direction, we need to negate the new θ2.

(theta2 기준으로 보면 

 

Solving for θ1

figure 4

 

 

여기서 tan 공식을 보면 

 

이런 형태가 있는데 이것을 이용하여 다음처럼 전개한다

Writing the code

We can now write our algorithm in code. The function will calculate a valid IK solution if possible. If there is no valid solution, it will calculate one that gets the end of bone2 as close to the target as possible. Check out the RJ_Demo_IK application for a functional implementation of this code.

 

These code samples are released under the following license.

 

/******************************************************************************

  Copyright (c) 2008-2009 Ryan Juckett

  http://www.ryanjuckett.com/

  

  This software is provided 'as-is', without any express or implied

  warranty. In no event will the authors be held liable for any damages

  arising from the use of this software.

  

  Permission is granted to anyone to use this software for any purpose,

  including commercial applications, and to alter it and redistribute it

  freely, subject to the following restrictions:

  

  1. The origin of this software must not be misrepresented; you must not

     claim that you wrote the original software. If you use this software

     in a product, an acknowledgment in the product documentation would be

     appreciated but is not required.

  

  2. Altered source versions must be plainly marked as such, and must not be

     misrepresented as being the original software.

  

  3. This notice may not be removed or altered from any source

     distribution.

******************************************************************************/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
///***************************************************************************************
/// CalcIK_2D_TwoBoneAnalytic
/// Given a two bone chain located at the origin (bone1 is the parent of bone2), this
/// function will compute the bone angles needed for the end of the chain to line up
/// with a target position. If there is no valid solution, the angles will be set to
/// get as close to the target as possible.
///  
/// returns: True when a valid solution was found.
///***************************************************************************************
public static bool CalcIK_2D_TwoBoneAnalytic
(
    out double angle1,   // Angle of bone 1
    out double angle2,   // Angle of bone 2
    bool solvePosAngle2, // Solve for positive angle 2 instead of negative angle 2
    double length1,      // Length of bone 1. Assumed to be >= zero
    double length2,      // Length of bone 2. Assumed to be >= zero
    double targetX,      // Target x position for the bones to reach
    double targetY       // Target y position for the bones to reach
)
{
    Debug.Assert(length1 >= 0);
    Debug.Assert(length2 >= 0);
  
    const double epsilon = 0.0001// used to prevent division by small numbers
  
    bool foundValidSolution = true;
  
    double targetDistSqr = (targetX*targetX + targetY*targetY);
  
    //===
    // Compute a new value for angle2 along with its cosine
    double sinAngle2;
    double cosAngle2;
  
    double cosAngle2_denom = 2*length1*length2;
    if( cosAngle2_denom > epsilon )
    {
        cosAngle2 =   (targetDistSqr - length1*length1 - length2*length2)
                    / (cosAngle2_denom);
  
        // if our result is not in the legal cosine range, we can not find a
        // legal solution for the target
        if( (cosAngle2 < -1.0|| (cosAngle2 > 1.0) )
            foundValidSolution = false;
  
        // clamp our value into range so we can calculate the best
        // solution when there are no valid ones
        cosAngle2 = Math.Max(-1, Math.Min( 1, cosAngle2 ) );
  
        // compute a new value for angle2
        angle2 = Math.Acos( cosAngle2 );
  
        // adjust for the desired bend direction
        if!solvePosAngle2 )
            angle2 = -angle2;
  
        // compute the sine of our angle
        sinAngle2 = Math.Sin( angle2 );
    }
    else
    {
        // At least one of the bones had a zero length. This means our
        // solvable domain is a circle around the origin with a radius
        // equal to the sum of our bone lengths.
        double totalLenSqr = (length1 + length2) * (length1 + length2);
        if(    targetDistSqr < (totalLenSqr-epsilon)
            || targetDistSqr > (totalLenSqr+epsilon) )
        {
            foundValidSolution = false;
        }
  
        // Only the value of angle1 matters at this point. We can just
        // set angle2 to zero. 
        angle2    = 0.0;
        cosAngle2 = 1.0;
        sinAngle2 = 0.0;
    }
  
    //===
    // Compute the value of angle1 based on the sine and cosine of angle2
    double triAdjacent = length1 + length2*cosAngle2;
    double triOpposite = length2*sinAngle2;
  
    double tanY = targetY*triAdjacent - targetX*triOpposite;
    double tanX = targetX*triAdjacent + targetY*triOpposite;
  
    // Note that it is safe to call Atan2(0,0) which will happen if targetX and
    // targetY are zero
    angle1 = Math.Atan2( tanY, tanX );
  
    return foundValidSolution;
}
 
 

 

 

 

ref : http://www.ryanjuckett.com/programming/analytic-two-bone-ik-in-2d/

ref : https://docs.unrealengine.com/ko/Engine/Animation/NodeReference/SkeletalControls/TwoBoneIK/index.html

반응형
반응형

 

chain 의 Root Node(=base)

각 chain 들의 노드(=link) 라 네이밍을 가정 하고 시작한다

 

root node 의 반대편 즉 chain 의 끝 노드(Chain End)에서부터 root node 까지 노드(Link)를 방문 하면서

Chain End 노드가 target 에 접근 하도록 각 노드(link)의 각도를 taret 방향으로 회전 시켜주는 방식이다

 

나중엔 Chain End 가 Target 과 같거나 가장 근접한 결과를 뽑아내는 알고리즘이다

FABRIK 와 달리 알고리즘의 해가 정확하지 존재하지 않을 수 있다

 

즉 CCD 는 가장 근사한 해를 찾는 알고리즘임으로 해를 찾지 못하는 경우를 대비하여

loop count 를 두고 그 횟수를 제한해야 하는 부분이 필요하다

 

 

 

CCD 영상

 

 

 

언리얼 에서의 CCD 영상

 

CCDIK (Cyclic Coordinate Descent Inverse Kinematics, 순환 좌표 하강 역운동학) 스켈레탈 컨트롤 노드는 경량 IK 솔버로 (FABRIK 과 비슷하며) 보통 본 체인 구동에 쓰입니다. 하지만 FABRIK 와 달리, CCDIK 는 솔브 도중 본의 회전을 제한하려는 경우 유용하게 쓰일 수 있는 각 컨스트레인트를 정의하는 기능을 제공합니다.

CCDIK 노드는 아래 그림처럼 절차적 애니메이션 구동에 쓰일 수 있습니다.

 

 

 

 

 

위 비디오에서 CCDIK 노드 프로퍼티의 Solver (솔버) 섹션을 사용하여 캐릭터의 왼쪽 어깨에 Root Bone (루트 본)을 설정했습니다. 그런 다음 Tip Bone (끝 본)을 캐릭터 검지 마지막 숫자로 설정합니다. Effector Location (이펙터 위치)를 조정할 때, IK 솔버가 인계하면, 체인의 본 각각이 따르고, 정의한 Rotation Limit Per Joints (조인트 당 회전 제한) 값을 기반으로 합니다. 이 각 제한은 본이 이상하게 회전하는 것을 방지하며 원하는 결과를 내기 위해서는 (본 단위로) 값을 조정해야 할 수 있습니다.

 

=> CCD는 조인트당 회전 되는 각도의 제한을 두어서 팔이 의도하지 않은 관절 형태로 회전 되는 것을 방지  할 수 있다

 

 

Tip bone : 본의 끝 노드로 오른손 검지 끝마디로 설정하고

Root bone 은 오른쪽 어깨 본으로 세팅 해놓으면 Root bone 부터 tip bone 까지에 걸처 본들에 대한 제한 각도들을 설정 할 수 있다(각 본에 대해 회전 제한 각도를 설정하기 위한 본들은 알아서 잡힌다, 본의 개수에 맞게끔) 가장 아래 쪽 7번 인덱스가 검지 끝 본을 말하며 6번은 검지 끝 본 이전 것노드를 말하며 각 숫자는 회저회전 할 수 있는 각도를 설정할 수 있다, 이 각도로 IK 도 중 본이 이상하게 꺽이는 현상을 방지할 수 있다

위 그림에서 보면 오른손 손가락이 타겟 즉 Effector Location 을 향하고 있는 것을 볼 수 있다, 하지만 각도를 0~2에 해당 하는 부분에 대해 0 으로 설정해 놓았기 때문에 회전 추적 되지 않고 어깨부터 3개 의 본들은 그자리에 있는 것을 볼 수 있다

 

 

나머지 3개에 대해서도 30도 정도의 회전각 반경을 주면 아래 그림 처럼 오른쪽 어깨부터 오른손 검지 손가락이 

타겟지점으로 향하는 것을 볼 수 있다

 

 

 

 

 

Using an iterative technique to determine orientations

for a multiple link IK chain

The way we will study here was first presented by Chris Welman in his

masters thesis on IK as an extension to work developed by Li-Chun

Tommy Wang and Chih Cheng Chen in an IEEE paper 'Transactions on

Robotics and Automation'. The technique is called Cyclic Coordinate

Descent. The principal algorithm is as follows:

 

1 Set a loop counter to zero.

2 Start with the last link in the chain.

3 Rotate this link to point towards the target.

4 Move down the chain and repeat step 2.

5 When the base object is reached, determine if the target has been

reached or a loop limit has been reached. If so exit, if not increment

loop counter and repeat from step 2.

This algorithm has the benefit of simplicity and can be easily

implemented.

To derive the angle

, we go back to the familiar methods of using

vectors. Remember that the dot product of vectors is given by

 

Unfortunately, the angle does not imply direction and we are interested in

both the angle and the direction. Since the cross product of two vectors

gives a perpendicular vector, we can use this to determine the direction.

By extending the vectors from two to three dimensions, we then have a z

value. The sign of this z value will give the direction in which to rotate.

 

 

defines the direction of rotation.

When using the algorithm, we start by iterating through the link chain to

find the last link. We then create unit vectors from the pivot point of the link

to the chain end and from the pivot point to the target. The inverse cosine

of the dot product of these two vectors gives the angle of rotation and the

third term of the result of the three-value vector cross product gives the

direction of rotation. We generate a rotation matrix using this information

and record the current chain end position.

 

 

We then move one link down the chain and determine new vectors from

the pivot point of the new link to the chain end and target and repeat the

dot product and cross product calculations. We then create a rotation

matrix and calculate the new location for the chain end. This procedure is

repeated until the base object is rotated. At this point we determine if we

are close enough to the target to exit the function. If not, we must repeat

the procedure. It is possible that the target cannot be reached. The

function needs to ensure that under such circumstances the program

does not enter an infinite loop. A simple technique to avoid this is to limit

the looping to a certain number of times; if this is exceeded then the

function exits.

If the total length of the links is less than the distance to the target, then

the goal can never be achieved and it can be useful to add this condition

to the code. Under these circumstances we return to the problem of

orientating a single link, the base link, and setting all other links to zero

rotation.

The code for the Cyclic Coordinate Descent method is as follows:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
void CIKDemoDoc::SolveMultiLink() 
{
    //links.next 시작 부분 base 에 해당 하는 노드
    CLink* link = links.next, * lastlink, * tmplink; 
    int loopcount = 0
    double x, y, totallength, linkslength = 0.0, dotprod;
 
    double mag, theta, sign, sqdist; VECTOR pe, pt;
    
    //Check solution is possible
    //시작 부분 base link 와 target 까지의 거리를 구한다
    x = target.x - link->pos.x;
    y = -target.y - link->pos.y;
    totallength = sqrt(x*+ y*y);
    
    //Find the last link and links length
    
    //link(chain)들의 총 길이를 구한다
    while (link->next)
    {
        linkslength += link->length;
        lastlink = link;
        link = link->next;
    }
 
    //lastlink : chain End 마지막 노드
 
    //체인의 총 길이가 타겟에 닿을 수 있는 범위에 있는지 판단 
    //즉 체인이 타겟을 추적할 수 있는 범위안에 있는지 판단
    if (totallength < linkslength)
    {
        //Calculate Cyclic Coordinate Descent solution
        //link 마지막 노드
        link = lastlink;
 
        //최대 20 번까지 접근을 한다 : 완전한 해를 구하지 못하는 경우는 도중에 끊어야 한다
        while(link && loopcount<20)
        {
            //end effector 따라가는 target에 해당, right 는 chain end 로 가는 단위 벡터인듯
            //Calculate current endeffector position
            //endeffector = lastlink->length * lastlink->right + lastlink->pos;
            //endeffector 현재 chain 의 끝 지점을 endeffector 로 칭하는 듯
            endeffector.x = lastlink->length * lastlink->right.x +lastlink->pos.x;
            endeffector.y = lastlink->length * lastlink->right.y +lastlink->pos.y;
            
            //chain end 와 target 사이의 거리를 구한다
            //Calculate the squared distance from end effector to//target
            x = target.x - endeffector.x;
            y = target.y - endeffector.y;
            
            //chain end 와 target 사이의 거리가 0.1 보다 작다면 추적을 중지한다
            sqdist = x*+ y*y;
            if (sqdist < 0.1
                break;
            
            //link : pivot 에서 target 까지의 거리를 구한다
            //Calculate pivot to target vector
            pt.x = target.x - link->pos.x;
            pt.y = target.y - link->pos.y;
            pt.z = 0;
            
            //Calculate pivot to end effector vector
            
            //pivot 위치에서 endeffector( 첫번째 실행할때는 endeffector 가 chain End) 
//까지의 거리를 구한다
            pe.x = endeffector.x - link->pos.x;
            pe.y = endeffector.y - link->pos.y;
            pe.z = 0;
 
            //Convert to unit vectors
            mag = sqrt(pt.x * pt.x + pt.y * pt.y);
            pt.x/=mag; 
            pt.y/=mag;
 
            mag = sqrt(pe.x * pe.x + pe.y * pe.y);
            pe.x/=mag; 
            pe.y/=mag;
            
            //Calculate dot product
            //endeffector 와 Target 의 내적을 구한다 => 1 * 1 * cos(theta)
            dotprod = pe.x * pt.x + pe.y * pt.y;
            
            //Calculate cross product for direction
            //외적 z 성분을 통해 회전 부호를 결정할 수 있다 
            sign = pe.x * pt.y - pe.y * pt.x;
            
            //각도를 구한다
            if (sign > 0.0)
            {
                theta = -acos(dotprod);
            }
            else
            {
                theta = acos(dotprod);
            }
            
            //각도를 구한다음 pivot 위치에서 end effector  가 target 로 까지 회전 
//되어야 하는 각도를 더해준다
            link->rot.z += theta;
            
            //Set matrices for current link and all children
            
            tmplink = link;
            while(link)
            {
                link->CreateMatrix();
                link = link->next;
            }
            
            link = tmplink;
            
            //Move on to next link
            //link 가 첫번째 link 와 같다면 loopcount 를 증가 시키고 
            //(chain end) 끝 노드 부터 다시 반복을 시도하여 해를 구하기 위한 시도를 한다
            if (link==links.next)
            {
                loopcount++;
                link = lastlink;        
            }
            else
            {
                //나음 link 로 이동한다
                link = link->parent;
            }
        }
    }
    else
    {
        //chain 범위 밖에 있는 경우 첫번째  link 에는 target 으로 향하는 각도를 넣어주고
        //나머지 자식에해당하는 노드들은 0 도로 채우준다(체인이 닿을 수 있는 범위 
//밖임으로 뻗는 형태가 된다)
        //Just use the single link solution
        link = links.next;
        x = target.x - link->pos.x;
        y = -target.y - link->pos.x;
 
        //첫번째 base  link 에서 target 까지의 거리를 구한다음
 
        //taret으로 향하는 절대 각을 구한다
        link->rot.z = atan(y/x);
 
        if (x<0
            link->rot.z = PI + link->rot.z;
 
        //나머지 
        while (link->next)
        { 
            link = link->next; 
            link->rot.z = 0.0
        }
    }
 
    link = links.next; 
 
    while (link) 
    {
        link->CreateMatrix(); 
        link = link->next; 
    }
}
 
 

 

 

 

 ref :

http://nedrilad.com/Tutorial/topic-73/Real-time-3D-Character-Animation-with-Visual-C-197.html

http://nedrilad.com/Tutorial/topic-73/Real-time-3D-Character-Animation-with-Visual-C-198.html

https://docs.unrealengine.com/ko/Engine/Animation/NodeReference/SkeletalControls/CCDIK/index.html

 

 

반응형
반응형

 

FABRIK Algorithm : IK

 

 

 

 

 

1. target 지점으로  reach 를  하면( Forward) 기준이 되는 끝 노드 base 노드가 원래 있던곳에서 떨어지게 된다

2. 이때문에 다시 역으로 target 으로 reach 했던 것을 다시 원래 최초의 끝 노드 지점을 target 으로 잡고
    reach 를 하는 (backward) 방식을 FABRIK ( Forward-and-Backward Reaching Inverse Kinematics ) 이라 한다

 

여기서 reach 는 각 노드들마다 target 을 기준으로 삼고 원래 해당 노드에서 target 기준으로 tail 노드를 끌어와서 target 지점을 목표로 각 노드들이 따라가는 것을 말한다

 

Tip bone : 끝 본에 해당하며  이것을 검지 손가락으로 설정하고

Root bone  은 어깨로 설정하여

타겟 : Effector Trasnform 위 이미지에서는 현재 붉은색 동그라미 부분의 좌표축의 위치

 

Effector 의 위치를 변경하면 어깨부터 검지까지 Effector 를 따라 가는것을 볼 수 있지만 관절마다의 각도 제한이 없기 때문에 뒷 부분으로 갈때 팔이 기이하게 꺽이는 현상을 볼 수 있다

 

 

Effector 위치를 FABRIIK 가 따라 갈수 있는 범위내 (즉 어깨부터 검지 손가락의 총 길이 내의 위치내)

에 있고 뼈가 꼬이지 않는 범위 부근에 위치한다면 다음 그림 처럼 정상적으로 보일순 있다

즉 FABRIK 은 인체 관절 보다는 자유스러운 형태에서의 IK 에 더 적합함을 알 수 있다

 

FABRIK 

FABRIK stands for Forward-and-Backward Reaching Inverse Kinematics, and it’s a very simple, fast, and clever solution to the inverse kinematics problem.

Let’s inspect this interesting algorithm in 2D.

Problem

First of all, what is Inverse Kinematics (IK)?

Suppose you have a robotic arm with three joints, whose job is to point to a location:

 

 

 

How do you calculate the correct angle of each joint in order to hit the target?

This seems stupid at first – we solve this problem all day long, inside our bodies, as we reach for objects and navigate the world. But there is surprising complexity in this problem.

In order to appreciate the FABRIK solution, watch the following video as different algorithms attempt to solve IK in 3D:

FABRIK Demonstration Video

Iterative Reaching

FABRIK’s solution to this problem is to iteratively reach for a target location.

Let’s start with the most basic setup.

Suppose there aren’t any joints, and you simply want a line of a fixed length to follow your mouse cursor:

 

 

Reaching, according to FABRIK, consists of:

  1. Given a line and a target…
  2. Set the line’s head point to the target, stretching the line
  3. Slide the line’s tail point along the new stretched line to fix the length

Let’s look at this visually.

 

 

 

Let’s look at some code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function reach(head, tail, tgt){
  // returns new head and tail in the format of:
  //   [new_head, new_tail]
  // where `new_head` has been moved to `tgt`
 
  // calculate the current length
  // (in practice, this should be calculated once and saved,
  //  not re-calculated every time `reach` is called)
  var c_dx = tail.x - head.x;
  var c_dy = tail.y - head.y;
  var c_dist = Math.sqrt(c_dx * c_dx + c_dy * c_dy);
 
  // calculate the stretched length
  var s_dx = tail.x - tgt.x;
  var s_dy = tail.y - tgt.y;
  var s_dist = Math.sqrt(s_dx * s_dx + s_dy * s_dy);
 
  // calculate how much to scale the stretched line
  var scale = c_dist / s_dist;
 
  // return the result
  return [
    // copy the target for the new head
    { x: tgt.x, y: tgt.y },
 
    // scale the new tail based on distance from target
    { x: tgt.x + s_dx * scale, y: tgt.y + s_dy * scale }
  ];
}
 
cs

 

This should be fairly straight-forward, but there is one important property to notice:

This function always succeeds. The line will always be moved so that it touches the target.

Making it Iterative

How can we make the process iterative? I.e., instead of pulling around a single line, how can we pull along a chain of lines?

 

It’s quite ingenious:

The act of sliding the tail along the line can itself be a reach.

Think about it. We have all the ingredients.

To perform a reach, we need a line (head + tail), and a target location. Well, in a chain, the tail of one segment is the head of another segment. For each segment, we simply perform a reach with the new target, until we run out of segments – then we slide the final tail to the target:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// initialize target
var tgt = mousePosition();
 
// for each segment (except the last one)
for (var i = 0; i < segments.length - 1; i++){
  // perform a reach for this segment
  var r = reach(segments[i], segments[i + 1], tgt);
 
  // update this node
  // (`r[0]` is guaranteed to be the same point as `tgt`)
  segments[i] = r[0];
 
  // update the target, so the next segment's head
  // targets this segments new tail
  tgt = r[1];
}
 
// for the last segment, move it to the target
segments[segments.length - 1= tgt;
 

 

 

 

Forward and Backward

We haven’t quite made a robot arm though, have we?

A robot arm doesn’t move around freely like a chain – it’s fixed at the base.

The last insight of FABRIK is that since we are guaranteed the head of the chain will always reach its target, we can perform the iterative reach in reverse to ensure that the base of the arm will always stay fixed.

Said another way:

First we perform the iterative reach like normal, so the head is guaranteed to be touching the target. Then we perform the iterative reach in reverse, so that the tail is guaranteed to be touching the base.

And that gives us an arm:

 

 

 

Here’s the code:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// save the fixed base location
var base = segments[segments.length - 1];
 
// perform the iterative reach in the forward direction
// (same as before)
var tgt = mousePosition();
for (var i = 0; i < segments.length - 1; i++){
  var r = reach(segments[i], segments[i + 1], tgt);
  segments[i] = r[0];
  tgt = r[1];
}
segments[segments.length - 1= tgt;
 
// at this point, our base has moved from its original
// location... so perform the iterative reach in reverse,
// with the target set to the initial base location
tgt = base;
for (var i = segments.length - 1; i > 0; i--){
  var r = reach(segments[i], segments[i - 1], tgt);
  segments[i] = r[0];
  tgt = r[1];
}
segments[0= tgt;
 
 
 

Conclusion

It’s quite amazing that such a simple concept – a stretched reach – has turned into solving the complex and puzzling inverse kinematics problem.

There are a lot of ways to extend this algorithm too.

Moving into 3D complicates it because orientation of the segments matter, but by tweaking the reach function, it can be made to work.

Adding constraints like fixed range of motion over a joint is possible too, but requires thought about where to enforce the constraints during the iterative process.

Moving objects other than lines is possible too (i.e., rigid triangles), if you can break down the objects into lines, and traverse the heirarchy correctly while iterating.

The important part is understanding the core idea, which I hope is clear now. Go forth, experiment, and have fun!

 

permanent link
back to top

posted 9 Jan 2018 by Sean
tags: 2d, algorithm, javascript, heuristic

 

 

ref : https://sean.cm/a/fabrik-algorithm-2d

 

 

 

언리얼에서도 FABRIK 이 사용 되는것을 볼 수 있다

 

 

FABRIK

Forward And Backward Reaching Inverse Kinematics, 앞뒤로 늘어나는 IK 를 사용하는 애니메이션 노드입니다.

 

 

Forward And Backward Reaching Inverse Kinematics (FABRIK) 은 (최소 2 링크 이상) 임의 길이의 본 체인에 작동하는 IK 솔버입니다.

우선 (위 그림과 같이) 애니메이션 블루프린트  애님 그래프  FABRIK 노드를 추가하는 것으로 FABRIK 을 사용할 수 있습니다.

추가하고 나면, 컴포넌트 포즈 에 추가로 노드는 이펙터 트랜스폼 을 받는데, 절대 트랜스폼 이 될 수도, (같은 스켈레톤의 다른 본을 기준으로 하는) 절대 트랜스폼이 될 수도 있습니다. 엔드 이펙터 세팅은 TwoBone_IK 노드와도 매우 유사합니다.

적용된 스켈레탈 컨트롤의 현재 세기를 결정하기 위하여 FABRIK 노드는 플로트 Alpha 를 받는데, 노드 자체에서 설정하거나 플로트 변수를 통해 설정 가능합니다.

FABRIK 노드에 대한 디테일 패널에서는 솔버 작동방식을 더욱 세밀히 조절하는 데 사용할 수 있는 추가 세팅을 확인할 수 있습니다:

ref : 

https://docs.unrealengine.com/ko/Engine/Animation/NodeReference/Fabrik/index.html

영상 : https://www.youtube.com/watch?v=Okq-mkEvcGU&t=82s

 

 

 

 

위 알고리즘하고 매칭해본다면 End Effector  가 따라가는 Target, Solver 가  base 에 해당한다고 보면 된다

Effector : 오른 손_r  (hand_r)

Solver 는 허리 쪽 Spine_02 

 

 

 

 

반응형
반응형

 

다음 처럼 fn 함수를 만들고 Call in Eidtor(에디터에서 호출) 을 체크해놓으면

 

 

 

아래 그림처럼 Default 부분에 fn 버튼이 만들어지고 클릭하면 해당 함수가 바로 실행되는 것을 볼수가 있다

 

 

 

 

===========================================================

 

 

 

 

 

You can call Blueprint events and functions on demand in the Unreal Editor. This can be especially useful any time you need to run the same Blueprint graph both at runtime and in the editor. For example, you can test or preview your runtime gameplay within the editor UI. However, it can also be a simple way to trigger Blueprints within the editor that require an Actor or a location in 3D space as a context.

Supported Blueprint Classes

Not all Blueprint classes allow their Custom Events and Functions to run in the Unreal Editor.

  • The steps described below work for any Blueprint class that you can place in a Level—that is, any class that derives directly or indirectly from Actor.

  • If you need access to editor-only features like working with Assets in the Content Browser, you can derive your Blueprint class from a placeable editor-only base class, such as EditorUtilityActor. However, keep in mind that you won't be able to trigger your Blueprint at runtime when you use an editor-only base class, because editor-only classes are not included in packaged Unreal Engine applications.

Editor Utility Blueprint classes that derive from the Actor base class don't expose buttons in the Details panel for any Functions or Custom Events that are marked as callable in the editor. If you absolutely need to use a button in the Details panel to drive your Blueprint logic, create your graphs in a normal Blueprint class and not in an Editor Utility Blueprint class. However, for a much more flexible and powerful approach to creating a custom UI to drive Blueprint logic in the Unreal Editor, consider using an Editor Utility Widget instead.

Steps

  1. Any time you use a Custom Event node in the Event Graph of your Blueprint class, you can set the Graph > Call in Editor option in the Details panel:

    Similarly, when you create a new function on your Blueprint class, you can select the node for your new function and set the same option in the Details panel:

  2. Add an instance of your Blueprint class into your Level if you haven't already.

  3. Select the Blueprint Actor in the Level Viewport or in the World Outliner. The Details panel shows a button for each of the Call in Editor events and functions you've set up. You'll typically find them in the Default section, where Blueprint classes also expose variables that are marked as Instance Editable.

    If your Custom Event or function has any inputs, it will not be shown in the Details panel.

  4. Click these buttons to trigger execution of the Event Graphs starting from your Custom Event nodes, or to trigger your custom functions.

https://docs.unrealengine.com/ko/Engine/Editor/ScriptingAndAutomation/Blueprints/CallInEditor/index.html

반응형
반응형

개요

기술이 발전하고 시간이 지남에 따라 그래픽카드 성능 역시 계속 발전해왔습니다. 그 덕에 더욱 놀랍고 사실적인 그래픽을 표현할 수 있게 되어왔습니다. 그 결과 현대의 PC 및 콘솔 게임의 그래픽은 현실과 그래픽을 분간하기 어려울 지경입니다. 이토록 그래픽 카드는 얼마나 더 멋진 그래픽을 얼마나 더 고속으로 처리할 수 있는 지가 가장 큰 이슈이고 이에 초점을 맞추어서 발전해왔습니다.

이미지 출처 : http://www.slideshare.net/ozlael/unitylightingslide-public

하지만 모바일 기기의 그래픽카드는 조금 다른 행보를 갖습니다.  모바일기기는 항상 전원이 연결되어있는 상황이 아니기때문에 전력 소모가 가장 큰 이슈가 됩니다. 또한 휴대가 용이하게 만들어야 하기 때문에 칩셋을 얼마나 물리적으로 작게 만드냐가 관건입니다. 게다가 물리적으로 작게 만드려면 쿨러를 장착할 수가 없기때문에 발열도 큰 문제가 됩니다. 이러한 모바일 기기의 특징들때문에 모바일에서는 Tile Based Rendering(타일 기반 렌더링)이라는 독특한 방식을 사용합니다. 이번 글에서는 Tile Based Rendering에 대해서 알아보고, 유니티에서 Tile Based Rendering을 고려시 주의점에 대해 다루고자 합니다.

 

Tile Based Rendering

우선 Tile Based Rendering에 대한 설명에 앞서 데스크톱의 렌더링 과정을 간단히 살펴보겠습니다. OpenGL에서 드로우콜을 날리면 지오메트리 데이터가 버텍스 쉐이더를 거쳐서 트랜스폼된 뒤 레스터화되고 픽셀쉐이더로 넘어가서 픽셀 컬러를 거칩니다. 픽셀쉐이더의 결과물은 바로 프레임 버퍼로 출력이 되면서 필요에따라 블렌딩 처리가 됩니다. 이처럼 드로우콜의 명령이 프레임버퍼까지 전달되는 과정이 한번의 패스로 이루어지고 매번 프레임버퍼 영역 전체가 갱신이 됩니다. 즉, 드로우콜 한번 당 한번에 바로 화면 전체에 렌더링합니다. (그래서 이러한 전통적인 렌더링 방식을 Immediate Mode Rendering이라 부르기도 합니다.)

이미지 출처 : http://www.ntu.edu.sg/home/ehchua/

하지만 모바일에서는 조금은 다른 방식을 사용합니다. 앞서 언급하였듯이 모바일에서는 전력 소모와 물리적 크기 등을 고려해야합니다. 이를 위해서 많은 고려사항들이 반영되며 설계가 됩니다만 그 중 가장 큰 고려 사항중 하나가 바로 대역폭입니다. 대역폭을 넉넉하게 쓰다보면 전력소모가 심해지고 물리적 칩셋 크기도 커집니다. 이는 발열로 이어지게 되는데 당연히 발열을 완화시킬 쿨러를 달 공간도 없습니다. 그래서 모바일에서는 대역폭을 줄이기위해 Tile Based Rendering(이하 TBR)이라는 아키텍쳐를 채용하고 있습니다.

앞서 말했던 것 처럼 전통적으로 데스크톱의 그래픽에서는 드로우콜마다 프레임 버퍼 전체를 갱신합니다. 하지만 높은 해상도의 프레임버퍼 전체를 매 번 갱신하는 것은 높은 메모리 대역폭을 요구하게 됩니다. 따라서 모바일에서는 프레임버퍼 전체를 매 번 갱신하는것이 아니라 타일 단위로 쪼개서 갱신을 하는 방식을 사용합니다. 드로우콜 발생 시 즉시 프레임버퍼에 기록하는 것이 아니라, 칩셋에 내장된 메모리에 존재하는 타일에 렌더링합니다. 이로 인해서 매번 화면 전체를 렌더링 하는 것이 아니라 실제 도형이 그려지는 타일만 렌더링 하게 됩니다.

이미지 출처 : Performance Tuning for Tile-Based Architectures

우선, 프레임버퍼를 일정 크기의 타일로 영역을 나눕니다. (이 타일 크기는 칩셋 벤더마다 차이가 있습니다.) 드로우콜이 발생하면 지오메트리 데이터가 버텍스쉐이더를 거쳐서 트랜스폼을 수행 후 레스터화됩니다. 여기까지는 전통적인 렌더링 방식과 동일합니다만 그 이후부터가 달라집니다. 버텍스 쉐이더의 결과가 바로 픽셀 쉐이더로 넘어가지 않고 타일을 선택하는 과정을 거칩니다. 그 후에 픽셀쉐이더가 수행되고 칩 내부 버퍼에 존재하는 타일에 그려집니다. 그 후 타일들이 완성되면 프레임버퍼에 그려집니다. 이런 식으로 타일 단위로 프레임버퍼를 갱신해주기때문에 적은 대역폭으로도 화면을 렌더링 할 수 있게됩니다.

이미지 출처 : http://wenku.baidu.com/view/85ea8fec998fcc22bcd10dcb.html

 

Tile Based Deferred Rendering

또한, TBR에서 변형되어 파생한 Tile Based Deferred Rendering(이하 TBDR) 라는 방식도 있습니다. 이 방식은 기본적으로는 TBR입니다. 다만 버텍스 쉐이더에서 트랜스폼 연산을 거치고나서 바로 픽셀 쉐이더로 넘기는 것이 아닙니다. 대신 버텍스 쉐이더의 결과를 중간 데이터를 담는 파라미터 버퍼에 담아둡니다. (이 버퍼를 ImgTec에서는 파라미터 버퍼라 부르고, ARM에서는 폴리곤 리스트라 부르는 등 여러 이름이 존재하지만 편의상 파라미터 버퍼로 통일하여 칭하겠습니다.) 이 파라미터 버퍼에 담은 후 픽셀 쉐이더로 바로 넘기는 것이 아니라, 매 드로우 콜 마다 버텍스 쉐이더의 결과를 계속 담아둡니다. 그 후 모든 드로우콜이 끝나면 그때서야 비로소 타일을 렌더링하고 프레임버퍼에 출력합니다. 그렇게되면 한 타일에 들어오는 모든 폴리곤을 한번에 처리 할 수가 있게됩니다. 이 과정에서 타일의 각 픽셀에는 은면제거가 처리되고 나서 도달하기 때문에 픽셀 오버드로우가 발생하지 않습니다. 이러한식으로 TBR을 지연해서 처리하기때문에 Tile Based Deferred Rendering(타일 기반 지연 렌더링)이라고 불립니다. 

이미지 출처 : Unity: iOS and Android - Cross Platform Challenges and Solutions

예전에는 TBDR 방식이 ImgTec의 PowerVR 즉 아이폰과 아이패드에서만 사용되었으나 최근들어서는 다른 칩셋들에서도 사용되고 있습니다. 하지만 여전히 안드로이드 기기는 TBDR보다는 TBR이 많이 사용되고 있습니다. 따라서 현 시점에서는 TBR을 사용하는 디바이스와 TBDR을 사용하는 디바이스가 공존하고 있는 상태입니다. (타일 기반이 아닌 전통적인 렌더링 기법을 쓰는 디바이스는 점유율이 매우 낮아서 논외로 합니다.)

이처럼 모바일에서는 타일 단위로 쪼개서 렌더링하는 방식을 사용하다보니 몇 가지 주의 사항이 존재합니다. 서론이 좀 길어지긴 했는데 결국 전달고자 하는 내용들은 다음과 같습니다.

 

 

 

 



앞선 글에서 설명드린 바와 같이 모바일에서는 타일 단위로 쪼개서 렌더링하는 방식을 사합니다. 그러다보니 전통적인 렌더링 방식에서와는 조금 다른 주의 사항이 몇 가지 존재합니다. 

 

알파블렌딩(Alpha Blending) VS 알파테스트(Alpha Test)

전통적으로 데스크톱 게임의 리소스에는 알파블렌딩보다 알파테스트의 사용이 권장되어왔습니다. 불투명한 철망이라든가 찢어진 옷감같은 경우는 불투명하기때문에 비싼 블렌딩 연산을 사용하기보다는 알파테스트를 사용함으로써 픽셀 연산도 절약하자는 의도였습니다. 하지만 모바일의 TBR(Tile Based Rendering, 타일 기반 렌더링)에서는 정반대로 알파테스트보다는 알파블렌딩의 사용이 권장됩니다. 

알파테스트처리를 하기 위해서는 픽셀쉐이더에서 동적분기(if문)가 사용됩니다. 데스크톱에서는 쉐이더의 동적 분기가 고속으로 처리되지만, 모바일에서는 동적 분기 성능이 취약합니다. 때문에 알파테스트는 쉐이더의 성능윽 하락시키는 원인이 됩니다.

게다가, TBDR(Tile Based Deferred Rendering, 타일 기반 지연 렌더링)에서는 픽셀 차폐의 고속 처리를 깨트립니다. 앞서 언급했다시피 TBDR에서는 여러 드로우콜의 버텍스 쉐이더 결과를 모아두었다가 은면 제거(Hidden Surface Removal)를 거친 뒤 실제 보이는 픽셀만 처리합니다. 하지만 이는 알파테스트를 사용하지 않는 완전한 불투명메시일 경우에만 해당됩니다. 알파테스트를 사용하면 버텍스 처리 단계에서는 해당 폴리곤이 차폐 되는지의 여부를 판단할 수 없기때문에 Deferred 처리를 깨트릴 수 밖에 없게됩니다.

유니티에서는 이를 방지하기위해서 완전 불투명 오브젝트들을 모두 렌더링처리한 후에 알파테스트 오브젝트들을 렌더링합니다. 따라서 알파 테스트를 제한적으로만 사용한다면 그렇게 치명적이지는 않습니다. 하지만 애초에 TBDR 칩셋의 구조가 알파테스트 처리에 적합하지 않기 때문에 알파테스트를 사용하지 않는 것이 좋습니다. 그런 이유로, 유니티의 내장 쉐이더 중 Mobile 카테고리에는 알파 테스트 쉐이더가 존재하지 않습니다.

반면에, 알파블렌딩은 데스크톱에 비해서 고속으로 처리가됩니다. 알파블렌딩과정은 출력 내부적으로 대상 버퍼의 읽기/쓰기가 발생합니다. 데스크톱에서는 DRAM에 존재하는 프레임버퍼 전체에 접근해야하기때문에 높은 대역폭을 잡아먹게됩니다. 하지만 TBR에서는 이 처리가 타일 단위로 이루어지고 칩 내부에 존재하는 메모리에서 이루어지므로 고속으로 처리됩니다.

 

오버드로우

다만 명심해야 할 것은 알파 블렌딩 처리 자체가 빠르다는 것일 뿐이지 오버드로우에서 자유로와진다는 것은 아닙니다. 예를 들어서 넓은 영역의 파티클을 높은 밀도로 뿌리는 것은 여전히 오버드로우 문제를 일으켜서 성능 저하로 직결됩니다. 모바일은 쉐이더 성능이 기종에 따라 천차만별이므로 오버드로우로 인해서 쉐이더 싸이클이 낭비되는 것은 치명적인 문제가 됩니다. 웬만하면 불투명 오브젝트 위주로 리소스를 만들기를 권장합니다. 알파 블렌딩 오브젝트 또는 파티클은 오버드로우를 최대한 피해서 사용하시기를 권장합니다.

 

로우 폴리곤

너무나 당연한 이야기라 뜬금없어 보일수도 있겠지만 많은 폴리곤을 처리하면 성능이 하락합니다. 게다가, TBDR에서는 많은 폴리곤 처리의 부담이 더욱 큽니다. 앞서 설명드린 버텍스 쉐이더의 결과물들을 담아두는 파라미터 버퍼(Parameter Buffer)의 크기는 당연하게도 무한하지 않습니다. 따라서 이 버퍼가 넘쳐버리면 더 이상의 버텍스 쉐이더 결과물을 받아들이지 못하고 버퍼를 비워줘야합니다. 이 버퍼를 비워주기 위해서는 타일의 픽셀 처리 후 프레임버퍼로 출력하는 사이클을 거쳐야합니다. 때문에, 이론상으로는 TBDR에서는 픽셀의 오버드로우가 발생하지 않아야하지만, 현실적으로는 폴리곤이 많을수록 오버드로우가 발생하게 발생하게 됩니다. 그러므로 오브젝트의 렌더링 퀄리티를 높여야한다면 버텍스를 늘리는 것 보다는 픽셀쪽 연산을 늘리는 것이 오히려 이득일 수도 있습니다.

 

렌더 텍스쳐(Render Texture)

렌더 텍스쳐를 사용하면 유니티 내부적으로 렌더 타겟(Render Target)을 변경하는 행위를 거치게됩니다. 이러한 렌더타겟을 변경하는 행위는 데스크톱에서도 성능을 잡아먹는 행위가 됩니다. 렌더 타겟을 바꾸기위해서는 CPU가 GPU를 대기하는 과정을 거치게되면서 CPU와 GPU의 병렬 관계가 잠기 깨지는 현상이 발생하기 때문입니다. 

게다가, TBDR에서는 더욱 치명적인 행위가 됩니다. 렌더 타겟을 변경할 시에는 현재 파라미터 버퍼에 쌓여있는 데이터들을 모두 처리해주고 프레임버퍼에 출력합니다. 그 후 다음 렌더 타겟을 위해서 파라미터 버퍼를 비워줍니다. 이런식으로 렌더 타겟을 바꿀 시 deferred 사이클을 추가적으로 처리해줘야 합니다. 때문에 유니티의 카메라에서 타겟 텍스쳐(Target Texture)로 렌더 텍스쳐를 사용하는 경우에는 TBDR의 효율이 떨어지게 됩니다.

이미지 후처리 효과(Image post process Effect)

최근 디바이스들은 컬러 그레이딩이나 블룸 효과 등 이미지 후처리들을 사용할 수 있을 만큼 성능이 좋아졌습니다. 하지만 이러한 이미지 후처리들을 너무 남발해서 사용하면 안되고 필요한 것만 선택적으로 사용해야 합니다. 

우선, 이미지 후처리들은 내부적으로 렌더 타겟을 변경하는 행위를 합니다. 하지만, 더 큰 문제는 대역폭입니다.  물론 픽셀 처리 능력도 관건이지만 대역폭이 더욱 큰 문제가 됩니다. 이미지 후처리들은 현재 렌더링 한 결과를 담고있는 렌더 타겟을 픽셀쉐이더의 입력 텍스쳐로 가져옵니다. 이 때 입력받는 텍스쳐는 칩 내부에 있는 타일이 아니라 공용 메모리에 있는 렌더 타겟을 가져오기때문에 엄청난 대역폭을 잡아먹게 됩니다. (예 : 1080p) 그러므로 이미지 후처리는 신중하게 사용해야 합니다.

 

카메라 클리어(Clear)

예전의 데스크톱 그래픽카드에서는 한 프레임의 렌더링을 시작하기 전 일부러 화면을 클리어해주지 않고 렌더링을 시작하는 경우도 있었습니다. 하지만 현대의 데스크톱 그래픽카드에서는 반드시 클리어를 해주어야만 하드웨어의 고속 처리를 지원받을 수 있습니다. 이는 모바일 기기의 TBR에서도 마찬가지입니다. 클리어를 수행하여 칩 내부의 버퍼들을 비워줘야만 이후 렌더링 과정을 고속으로 처리할 수 있습니다. 따라서, 유니티의 카메라에서 Clear Flag를 Don’t clear로 두는 것은 데스크톱에서나 모바일에서나 웬만해서는 권장되지 않습니다. 

 

MSAA

데스크톱에서는 MSAA가 매우 큰 부담이 됩니다. 역시 마찬가지로 대역폭이 가장 큰 원인입니다. 예를 들어 1080p해상도의 화면을 MSAA 2X로 처리하려면 2160p만큼의 대역폭이 필요해집니다. DRAM으로부터 그만큼의 대역폭을 요구한 다는 것은 매우 큰 부담이 되는것입니다. 하지만 TBR에서는 이 역시 칩 내부의 타일에서 이루어집니다. 16x16 혹은 32x32정도에 불과한 타일로 MSAA처리해주는 것은 그리 부담이 되지 않습니다. 

유니티에서 MSAA를 사용하기위해서는 퀄리티 셋팅에서 Anti Aliasing을 2 혹은 4로 선택해주면 됩니다. 다만 개인적으로는, 매우 높은 DPI를 자랑하는 대부분의 모바일 기기에서 안티 앨리어싱이 굳이 필요할 지는 모르겠습니다 :)

 

프로파일링

유니티5부터 프레임 디버거(Frame Debugger)가 추가되어서 프레임 별 렌더링 과정을 디버깅해볼 수 있게 되었습니다. 이를 통해서 오브젝트의 렌더링 과정이나 배칭 현황을 손쉽게 확인 해 볼 수 있게 되었습니다.  

이미지 출처 : http://docs.unity3d.com/Manual/FrameDebugger.html

하지만 애석하게도 유니티의 프레임 디버거만으로는 드로우콜 별 GPU 퍼포먼스나 세부 상태를 확인하기는 힘듭니다. 다행히도, 칩셋 벤더마다 렌더링 과정을 세부적으로 프로파일링을 해볼 수 있는 툴을 제공해주고 있습니다. 아드레노 칩셋은 아드레노 프로파일러를 통해서, 말리 칩셋은 말리 프로파일러나 DS-5를 통해서, 아이폰은 XCODE를 통해서 프로파일링을 해볼 수 있습니다.

이미지 출처 : http://www.slideshare.net/ozlael/graphics-opt-ndc

다만 문제가 하나 있습니다. TBR 방식을 사용하는 칩셋은 콜 별 설능을 확인하는데 어려움이 없습니다. 하지만 TBDR 방식을 사용하는 칩셋은 콜 별 성능을 직관적으로 확인하는게 사실상 불가능하다는 것입니다. TBDR은 앞서 언급했다시피, 드로우콜 발생 시 픽셀 쉐이더를 즉시 처리하는 것이 아니라 파라미터 버퍼에 결과를 담아둡니다. 그 후 모든 드로우콜을 마치고 나면 그때서야 실제 렌더링을 수행하기 때문에 콜 별 성능을 실질적으로 확인해 볼 수가 없는 것입니다. 따라서 X-code에서 아이폰의 렌더링을 프로파일링 해보면 성능 관련 숫자가 0으로 나오게 됩니다. 0이 아닌 숫자가 나오는 경우도 있지만 이 역시 신뢰할 수 없는 숫자입니다. 대신 프레임 전체에 걸린 성능을 확인해보거나 콜 당시의 사용 텍스쳐 등 주변 정보로 유추해보는 수 밖에 없습니다. 이처럼 아이폰의 프로파일링은 좀 까다로운 편입니다.

이미지 출처 : http://www.slideshare.net/ozlael/graphics-opt-ndc

 

화면 변화율

TBR에서는 렌더링을 칩 내부의 타일에다 하는 과정은 대역폭을 먹지 않지만, DRAM 영역에 존재하는 프레임 버퍼에 타일을 출력하는 과정에서는 어느 정도는 대역폭이 필요할 수 밖에 없게됩니다. 이러한 대역폭을 조금이나마 절약하기 위해서 말리에서는 트랜잭션 엘리미네이션(Transaction Elimination)이라는 기술을 사용합니다. 타일 별로, 이전 프레임과 화면 결과가 달라지지 않은 타일의 영역은 프레임버퍼를 갱신하지 않고 이전 프레임의 결과를 재활용 하는 것입니다. 그렇게 하면 칩 내부 메모리에서 시스템 메모리로 복사하는 양이 줄어드는 효과를 갖게됩니다. 아래 예시 이미지의 파란 글자 타일이 바로 그 부분에 해당합니다. 

이미지 출처 : http://community.arm.com/

따라서, 고정 카메라를 사용한다면 스카이박스 등의 배경에는 최대한 변화를 피하는 것도 좋은 방법이 될 수도 있습니다. 현실적으로는 3D 게임에는 이러한 조건에 해당하는 경우가 많지는 않을 것입니다. 하지만 2D 게임에는 적합하는 부분이 많은 것입니다.

 

마치며

TBR에 관한 내용이랑 TBDR에 관한 내용을 같이 언급하긴 하였습니다. 하지만 대부분은 아이폰이나 안드로이드폰만 타겟으로 설정하고 개발할 것이고, 플랫폼마다 데이터를 별도로 제작하지는 않을 것이라 예상합니다. 따라서 현실적으로는 TBR, TBDR 모두 고려대상으로 삼고 이러한 사항들을 인지하면서 개발하여야 할 것이라 생각합니다.

 

ref :https://ozlael.tistory.com/24

반응형
반응형

 

시작시 에디터 스크립트 실행

Unity 시작 직후에 사용자의 작업 없이 프로젝트 에디터 스크립트 코드를 실행하는 것이 유용한 경우가 종종 있습니다. 이것은 InitializeOnLoad 속성을 static constructor (정적 생성자)가 있는 클래스에 적용함으로써 얻을 수 있습니다. 정적 생성자는 클래스와 같은 이름의 함수로 정적으로 선언된 함수이며, 반환 값이나 인수가 없습니다(자세한 내용은 여기를 확인하시기 바랍니다) : -

 

1

2

3

4

5

6

7

8

9

using UnityEngine;

using UnityEditor;

[InitializeOnLoad]

public class Startup {

    static Startup()

    {

        Debug.Log("Up and running");

    }

}

 

 

 

 

 

정적 생성자는 어떤 정적 함수와 클래스의 인스턴스가 사용되는 것보다 먼저 호출되는 것이 보증되고 있지만, InitializeOnLoad 속성에 의해 에디터 시작할 때 호출되는 것을 보장합니다.

이 기술이 사용되는 예는 에디터에서 일반 콜백을 설정할 때(“프레임 업데이트” 등) 입니다. EditorApplication 클래스에는 update라는 대리자(Delegate)가 있고, 이것은 에디터가 실행되는 동안 초당 여러 번 호출됩니다. 프로젝트 시작할 때 이 대리자를 사용하려면 다음과 같은 코드를 사용합니다 : -

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

using UnityEditor;

using UnityEngine;

 

[InitializeOnLoad]

class MyClass

{

    static MyClass ()

    {

        EditorApplication.update += Update;

    }

 

    static void Update ()

    {

        Debug.Log("Updating");

    }

}

 

 

 

 

ref : https://docs.unity3d.com/kr/530/Manual/RunningEditorCodeOnLaunch.html

반응형
반응형

 

 

hat I am trying to do is in my Start() method of a prefab GameObject in my scene is I want to get the location of where this prefab is as a string but I cannot figure out how to do this.

Example of how I am using this: I put a Sword Prefab GameObject in my scene and on Start() I would like to get the file directory of where this GameObject is in my project.

 

 

 

Include using UnityEditor;

NOTE: This will only work if the prefab is to to public.

Then use AssetDatabase.GetAssetPath to get the path of the prefab.

public GameObject prefab; void Start(){ string prefabPath = AssetDatabase.GetAssetPath(prefab); Debug.Log("Path: " + prefabPath); }

Edit:

I did some experiment with PrefabUtility.GetPrefabType, PrefabUtility.GetPrefabObject and PrefabUtility.GetPrefabParent. PrefabUtility.GetPrefabParent solved the problem. You don't need to make the prefab public with PrefabUtility.GetPrefabParent.

void Start() { GameObject prefab = GameObject.Find("PrebabTest"); Object GameObject2 = PrefabUtility.GetPrefabParent(prefab); string prefabPath = AssetDatabase.GetAssetPath(GameObject2); Debug.Log("Path: " + prefabPath); }

 

 

ref  : https://stackoverflow.com/questions/36850296/get-a-prefabs-file-location-in-unity

 

Get a prefabs file location in Unity

What I am trying to do is in my Start() method of a prefab GameObject in my scene is I want to get the location of where this prefab is as a string but I cannot figure out how to do this. Example ...

stackoverflow.com

 

 

프리팹을 가져오는 함수는 AssetDatabase.LoadAssetAtPath() 이 함수로 에디터 상에서 얻어올 수 있다

반응형
반응형

MonoBehaviour.Reset()

Leave feedback

 

Description

Reset to default values.

Reset is called when the user hits the Reset button in the Inspector's context menu or when adding the component the first time. This function is only called in editor mode. Reset is most commonly used to give good default values in the Inspector.

// Sets target to a default value. // This could be used in a follow camera.

using UnityEngine;

public class Example : MonoBehaviour { public GameObject target;

void Reset() { //Output the message to the Console Debug.Log("Reset"); if (!target) target = GameObject.FindWithTag("Player"); } }

 

 

ref : https://docs.unity3d.com/ScriptReference/MonoBehaviour.Reset.html

반응형
반응형

Coroutine

C언어 등에서 일반적으로 사용하는 함수는 시작할 때 진입하는 지점이 하나 존재하고 함수가 모두 실행되거나, return 구문에 의해 종료되는 지점을 설정할 수있다.

이러한 함수를 Subroutine( 서브루틴 )이라 부르는데, 코루틴은 이를 더 일반화한 개념으로 진입하는 시점까지 여러 개를 가질 수 있는 함수를 의미한다. 개념적으로만 본다면 서브루틴의 한 종류라고 볼 수 있겠다.

 

C#, JavaScript, Python 등이 제공하는 Generator가 사실 코루틴의 한 형태이다.

 

caller가 함수를 call하고, 함수가 caller에게 값을 return하면서 종료하는 것에 더해 return하는 대신 suspend(혹은 yield)하면 caller가 나중에 esume하여 중단된 지점부터 실행을 이어갈 수 있다.

 

즉, 코루틴 함수가 호출되면 코루틴 함수 내부코드가 실행되고 있다가 suspend 혹은 yield에 의해 함수가 호출된 부분에 값이 반환되고 이후 다시 코루틴 함수를 호출하게 되면 코루틴 함수 내부에 suspend 혹은 yield 지점 이후 코드로 돌아와서 실행을 하게 된다는 말이다.

 

부분적으로, 그리고 특정한 상황이 맞아 떨어졌을 때 실행되는 함수.

 

- 장점

1. 성능

일반적으로 게임에서 코루틴을 사용하지 않는다면 매 프레임마다 Update구문에서 동작을 확인해야하는 상황이 발생한다.

하지만 코루틴을 사용하면 코루틴이 지정한 시간이 후에 자동으로 확인된다.

이러한 점 때문에 특히 모바일 기기에서 코루틴의 활용은 성능 향상에 큰 영향을 미친다.

2. 가독성

코드 가독성이 좋아진다. 이건 써보면 알 것 이다.

 

- 특성

* 특정 작업을 단계적으로 발생하게 한다.

* 시간 흐름에 따라 발생하는 루틴을 작성할 수 있다.

* 다른 연산이 완료될때까지 기다리는 루틴을 작성할 수 있다.

* 비동기가 아니다. = 동시에 발생하지 않는다.

 

 

 

 

Thread

현재에 와서 스레드의 경우는 멀티코어를 넘어서 many 코어 시대로 전환 되고 있기 때문에 이 자원들을 충분히 모두 이용하기 위해 병렬 프로그래밍을 하게 되는데 이 병렬 처리의 핵심도구가 스레드 이다.

 

멀티프로세스 기께에서, 스레드는 실제 다른 스레드와 함께 동시에 코드가 실행 될 수 있다. 하지만 여러 스레드를 사용하는 멀티스레드 프로그래밍은 코드를 이해하기 복잡하게 만든다.

다른 스레드가 특정 부분을 읽고 있는 동시에 그것을 변경할 수 있기 때문이다.

이 때문에, 공유 메모리영역을 만들지 않거나, 공유된 자원을 읽거나 변경시켜야 되는 경우라면, 공유된 자원으로 부터 다른 스레드를 잠궈버림으로써( Lock ) 이런 상황이 발생하지 않도록 하고 있다.

 

스레드를 이용하면 하나의 프로그램에서 한 번에 하나의 일을 처리하는 것이 아니라 동시에 많은 일을 처리할 수 있다.

 

- 특성

* 비동기 이다. = 동시에 발생한다.

 

 

 

 

 

 

ref : https://www.slideshare.net/QooJuice/coroutine-119750550

ref : https://poppy-leni.tistory.com/entry/%EC%BD%94%EB%A3%A8%ED%8B%B4-vs-%EC%93%B0%EB%A0%88%EB%93%9C

반응형
반응형

Why does Mathf.Sign(0.0f) return 1?

According to this wikipedia article it should return 0 if x == 0. Unity3D however returns 1 if x == 0.

Now I'm not sure if I'm blaming Unity3D correctly, maybe I misinterpreted the article. But is there a Math function which does work like Mathf.Sign() only returns 0 when x == 0?

For example I want this:

  1. Mathf.Sign(0.1f) == 1;
  2. Mathf.Sign(10.6f) == 1;
  3. Mathf.Sign(-0.2f) == -1;
  4. Mathf.Sign(0.0f) == 0;

 

 

Answer by whydoidoit · '12년 Jul월 11일 PM 01시 29분

Because 0 is considered a positive number by Unity it returns 1 you will have to test for 0 explicitly or write your own function.

  1. static function Sign(number : float) {
  2. return number < 0 ? -1 : (number > 0 ? 1 : 0);

 

 

https://answers.unity.com/questions/282813/why-does-mathfsign00f-return-1.html

 

Why does Mathf.Sign(0.0f) return 1? - Unity Answers

 

answers.unity.com

 

반응형
반응형

 

메카님의 Any State의 Can Transition To Self 옵션은 자기 자신의 State로도 전이가 가능한지를 체크하는 옵션이다.

 

유니티 메카님 이용할 때 캐릭터 이동 애니메이션 상태제어를 하기위해 아래와 같이 설정했었다.

Trigger로 상태값과 speed값에 따라 모든상태로 전이가 가능하다.

조건에 대해 설정하는데도 시간이 많이 걸렸다.

이 기능을 몰랐을때..하아..

 

 

 

처음엔 아래와 같이 설정했었는데.. 계속 자기 상태로도 전이가 되는것이다.

예를 들면 LeftUp 상태에서 다시 같은 LeftUp상태로 계속 전이가 된다.

8프레임짜리 스프라이트 애니메이션이었는데.. 2번프레임까지 플레이되고 다시 상태 전이로 2번 프레임까지만 플레이 되었다.

 

LeftUp 상태전이 0번 1번 프레임 플레이, LeftUp 상태전이 0번 1번 프레임 플레이

 

 

바로 이기능이다. Any State에서는 Settings에 Can Transition To Self 체크옵션을 빼주면 자기 자신한테 상태이전은 하지 않는다. 이로서 위처럼 깔끔하게 처리를 할수 있었다.



 

ref : https://bbangdeveloper.tistory.com/entry/%EB%A9%94%EC%B9%B4%EB%8B%98-Any-State-Can-Transition-To-Self-%EA%B8%B0%EB%8A%A5

반응형
반응형

Introduction

We wanted to have the enemies in Timefight Zone sort of “burn” into existence when they spawn, and burn away when they die. We began to look into how to do this, and quickly came across something called a dissolve shader.

We're going to assume you have a basic knowledge of Unity shaders for this tutorial, if you don’t, there are lots of excellent tutorials that can familiarize you with the basics just one Google search away.


Basic Dissolve Shader

So thanks to the Unity wiki page I linked above, we have a dissolve shader to work with. It looks like this:

  Shader "Dissolving" {
    Properties {
      _MainTex ("Texture (RGB)", 2D) = "white" {}
      _SliceGuide ("Slice Guide (RGB)", 2D) = "white" {}
      _SliceAmount ("Slice Amount", Range(0.0, 1.0)) = 0.5
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      Cull Off
      CGPROGRAM
      //if you're not planning on using shadows, remove "addshadow" for better performance
      #pragma surface surf Lambert addshadow
      struct Input {
          float2 uv_MainTex;
          float2 uv_SliceGuide;
          float _SliceAmount;
      };
      sampler2D _MainTex;
      sampler2D _SliceGuide;
      float _SliceAmount;
      void surf (Input IN, inout SurfaceOutput o) {
          clip(tex2D (_SliceGuide, IN.uv_SliceGuide).rgb - _SliceAmount);
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

This shader takes a value (_SliceAmount) and a texture (_SliceGuide), and uses a function called clip() to hide any pixels on _SliceGuide who's brightness is less than _SliceAmount.

Below is a gif showing the basic dissolve shader in action. I mapped the same texture to _SliceGuideand _MainTex to better illustrate how the brightness of the pixel in _SliceGuide determines at which point a pixel is hidden.

 

 

https://thumbs.gfycat.com/RegalWanIrishsetter-mobile.mp4

불러오는 중입니다...

 

Note how the darkest pixels are the first to dissolve when dissolving, and the last to re-appear when un-dissolving.

Adding Burnt Edges

Now for the interesting part, adding the burn effect around the areas that are about to dissolve. If you just want the code, here it is:

    Shader "Dissolving" {
    Properties {
      _MainTex ("Texture (RGB)", 2D) = "white" {}
      _SliceGuide ("Slice Guide (RGB)", 2D) = "white" {}
      _SliceAmount ("Slice Amount", Range(0.0, 1.0)) = 0.5


 _BurnSize ("Burn Size", Range(0.0, 1.0)) = 0.15
 _BurnRamp ("Burn Ramp (RGB)", 2D) = "white" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      Cull Off
      CGPROGRAM
      //if you're not planning on using shadows, remove "addshadow" for better performance
      #pragma surface surf Lambert addshadow
      struct Input {
          float2 uv_MainTex;
          float2 uv_SliceGuide;
          float _SliceAmount;
      };


      sampler2D _MainTex;
      sampler2D _SliceGuide;
      float _SliceAmount;
 sampler2D _BurnRamp;
 float _BurnSize;


      void surf (Input IN, inout SurfaceOutput o) {
          clip(tex2D (_SliceGuide, IN.uv_SliceGuide).rgb - _SliceAmount);
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
 
 half test = tex2D (_SliceGuide, IN.uv_MainTex).rgb - _SliceAmount;
 if(test < _BurnSize && _SliceAmount > 0 && _SliceAmount < 1){
    o.Emission = tex2D(_BurnRamp, float2(test *(1/_BurnSize), 0));
 o.Albedo *= o.Emission;
 }
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }


We have added a new value (_BurnSize), and a new texture (_BurnRamp). To recap, we are hiding parts of the model using clip(), which hides the pixel if the value passed into it is less than 0. We subtract_SliceAmount from the RGB value (brightness) of _SliceGuide, so if _SliceAmount is greater than the brightness of the given pixel of _SliceGuide, the pixel is hidden.

To add the burn effect, we again subtract _SliceAmount from the RGB value (brightness) of _SliceGuide, and this time if the resulting value is less than _BurnSize, we choose a pixel on _BurnRamp to color the pixel. This means that we can change _BurnSize to modify how far out from the dissolved area gets a burn effect. The higher _BurnSize is, the farther the burn effect extends.


The pixel on _BurnRamp is chosen based on how far past _BurnSize test is. At 0 you get the far left pixel, and as the value approaches _BurnSize you move farther to the right side of the image. So you're generally going to want to have the bright ember colors on the left, and the dimmer charred colors on the right on your _BurnRamp.

Below is a gif of _BurnSize being modified in real time to illustrate it's effect.

 

https://thumbs.gfycat.com/WarlikeKaleidoscopicAplomadofalcon-mobile.mp4

 

 

 

 

Note how increasing _BurnSize does not effect the areas that are dissolved, it merely changes how large the burned area extends.

That's about it. It's a relatively simple effect to achieve, but surprisingly hard to find concrete information on. Hopefully this post will help a few people add a cool effect to their game.

Notes

  • The way we are coloring the burnt edges is by setting the emission (o.Emission) to the color of the pixel we chose off _BurnRamp, as well as multiplying the base color (o.Albedo). This will cause the burnt areas to glow in the dark. If you want to achieve an effect with this shader where the edges shouldn't glow, you'll have to change o.Emission, to o.Albedo, and remove the next line down where we multiply o.Albedo by o.Emission.
  • Our _BurnRamp is designed for a cartoony art style. If your game has a more realistic art style, you may want a smoother gradient.
  • Feel free to use this shader and _BurnRamp in your game. We made this tutorial because we couldn't find any specific information on this type of shader outside paid Unity assets. Hopefully this has been informative, but even if it made absolutely no sense, we'd like for more people to be able to achieve this effect in their games, so just steal our version. :)

 

https://thumbs.gfycat.com/AnimatedUnderstatedJunebug-mobile.mp4

불러오는 중입니다...

https://thumbs.gfycat.com/WellinformedMadAlaskajingle-mobile.mp4

 

 

ref : http://www.codeavarice.com/dev-blog/tutorial-burning-edges-dissolve-shader-in-unity

반응형
반응형

 

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class testScript : MonoBehaviour
{
    [SerializeField]
    protected bool awoken = false;

    // Start is called before the first frame update
    void Start()
    {
             awoken = true;
    }

    // Update is called once per frame
    void Update()
    {
   
    }
}

 

이 스크립트를 Create Empty 를 만들어 붙인다

이후 Inspector 에서 awoken 의 값을 체크 해치를 한번 하고

이후 에디터 모드에서 실행한다음 중단 하면!

awoken 값이 true 로 남아 있는 것을 볼 수 있다

반응형
반응형

애니메이터 오버라이드 컨트롤러

애니메이터 오버라이드 컨트롤러는 원본의 애니메이터 컨트롤러를 확장하는 에셋으로 사용된 특정 애니메이션을 대체하지만 원본 구조, 파라미터와 로직을 유지합니다.

이를 통해 여러 배리언트의 동일한 기본 상태 머신을 만들 수 있습니다. 하지만 각각 다른 애니메이션 세트를 이용합니다. 예를 들어, 게임상에서 다양한 NPC 타입이 있지만 각 타입(고블린, 오거, 엘프 등)은 걷기, 대기, 앉기 등 동작에서 고유의 애니메이션을 가집니다.

모든 NPC 타입을 위한 로직을 가진 하나의 “베이스” 애니메이터 컨트롤러를 만들어서 각 타입의 오버라이드를 작성할 수 있으며 이를 각각의 애니메이션 파일에 연결할 수 있습니다.

이를 시연하기 위해 대표적인 애니메이터 컨트롤러 에셋을 살펴보겠습니다.

다음은 간단한 상태 머신을 담고 있는 애니메이터 컨트롤러를 나타내며 여기에는 다음과 같이 생긴 네 방향 블렌드 트리 조절 애니메이션과 대기 상태 에니메이션을 포함하고 있습니다.

이런 일반적인 NPC 상태 머신을 유니크한 애니메이션을 사용하는 오거 타입으로 확장하기 위해서는 애니메이터 오버라이드 컨트롤러를 만들어서 오거의 애니메이션 클립 안에 드롭함으로써 원본 애니메이션 클립을 대체할 수 있습니다. 오거는 더 느리고 무거우며 근육질적인 움직임 같이 다른 대기 방식과 움직임을 가질 수 있습니다. 그러나 애니메이터 오버라이드 컨트롤러를 사용하면 캐릭터의 움직임 상태 사이의 트랜지션과 블렌드가 어떻게 일어나는지에 대한 기본적인 로직이 다른 애니메이션 세트를 가진 캐릭터들 간에 공유될 수 있습니다. 그렇게 되면 상태 머신을 구축하고 변경하는 데 들어가는 작업을 줄일 수 있습니다.

새 애니메이터 오버라이드 컨트롤러를 만드려면 에셋(Assets) -> 생성(Create) 메뉴를 사용하거나 프로젝트 뷰의 생성(Create) 버튼에서 애니메이터 오버라이드 컨트롤러를 선택합니다.

애니메이터 오버라이드 컨트롤러는 애니메이터 컨트롤러와 비슷한 아이콘을 가지고 있지만, 전자는 아이콘 구석에 “플러스” 기호가 있고 후자는 “재생” 기호가 있습니다.

아이콘 비교: 애니메이터 컨트롤러와 애니메이터 오버라이드 컨트롤러 에셋 아이콘을 나란히 놓고 비교
아이콘 비교: 애니메이터 컨트롤러와 애니메이터 오버라이드 컨트롤러 에셋 아이콘을 나란히 놓고 비교

인스펙터에서 새 애니메이터 오버라이드 컨트롤러를 선택할 때, 처음에는 애니메이터 컨트롤러가 할당되지 않은 상태이며 아래와 같은 모습입니다.

애니메이터 컨트롤러가 할당되지 않은 애니메이터 오버라이드 컨트롤러
애니메이터 컨트롤러가 할당되지 않은 애니메이터 오버라이드 컨트롤러

오버라이드 컨트롤러를 사용하기 위해서는 원본 컨트롤러 에셋을 인스펙터의 새로운 오버라이드 컨트롤러에 할당해야 합니다. 이렇게 하면 원본 컨트롤러에 사용된 모든 애니메이션이 오버라이드 컨트롤러의 인스펙터 리스트상에 나타납니다.

기존 컨트롤러를 애니메이터 오버라이드 컨트롤러의 인스펙터로 드래그
기존 컨트롤러를 애니메이터 오버라이드 컨트롤러의 인스펙터로 드래그

다음으로 원본 애니메이션 클립을 오버라이드 하기 위해 새로운 애니메이션을 할당합니다. 이 예제에서 모든 애니메이션 클립은 “오거” 버전으로 오버라이드 되었습니다.

새로운 클립이 할당된 오버라이드 컨트롤러
새로운 클립이 할당된 오버라이드 컨트롤러

오버라이드 컨트롤러는 이제부터 애니메이터 컨트롤러로서 오거 캐릭터의 게임 오브젝트의 Animator 컴포넌트에 사용될 수 있습니다. 이는 원본 애니메이터 컨트롤러와 동일한 로직을 사용하지만 원본 대신 새롭게 할당된 애니메이션을 재생합니다.

Animator 컴포넌트에서 게임 오브젝트에 사용 중인 오버라이드 컨트롤러
Animator 컴포넌트에서 게임 오브젝트에 사용 중인 오버라이드 컨트롤러

https://docs.unity3d.com/kr/current/Manual/AnimatorOverrideController.html

반응형
반응형


애니메이터 뷰

처음 열린 애니메이터 뷰에는 3개의 스테이트(State)가 생성되어 있습니다. 각 스테이트의 역할은 다음과 같습니다.

스테이트 종류
역할
Entry
시작 스테이트로 최초의 진입점이다.
Exit
종료 스테이트로 모든 스테이트가 종료되는 마지막 스테이트다.
Any State
현재 어느 스테이트를 실행하고 있더라도 조건에 만족하면 분기시켜야 하는 스테이트가 있을 경우 사용한다.




Has Exit Time 속성은 수행중인 애니메이션이 다 끝난 후 전이가 되도록 하는 속성입니다. 바로 전이를 발생시키고자 할 때는 Has Exit Time 속성을 언체크합니다.









 유니티 메카님에서는 트랜지션이 일어나는 도중에 다른 트랜지션에 의해서 해당 트랜지션을 중단시킬 수 있는 기능이 있습니다. 이것은 4.x 버전에서는 atomic 설정의 on/off 를 통해서 트랜지션 중단의 가능 여부를 지정할 수 있었습니다. 유니티 5에서는 이 기능을 더 확장하여 더욱 상세한 설정이 가능한 Transition interruption source 옵션 항목이 추가되었습니다. (atomic 설정방식은 5 버전에서는 제거되었습니다.)


 트랜지션의 인스펙터 창을 열어봅니다:



[Settings] 설정 안에 [Interruption Source] 옵션이 있는 것을 볼 수 있습니다. 이 옵션을 통해서 사용자는 해당 트랜지션이 어떤 스테이트의 트랜지션들에 의해서 중단될 수 있는 지를 설정할 수 있습니다. 


 그럼 어떠한 옵션항목이 있는지 드롭다운 메뉴를 열어보겠습니다: 



위 그림에서와 같이 5가지 interruption source 옵션 항목이 있습니다. 그러면 각 항목들이 의미하는 것은 무엇인지 알아보겠습니다. 조금 더 이해를 쉽게 하기 위해서 아래 그림와 같은 상황을 가정한 상태에서 설명할까 합니다:


그림에서 파란색으로 표시된 트랜지션 t 의 Interruption Source를 설정한다고 했을 때 각 옵션 항목들을 아래와 같이 설명할 수 있습니다.


1) None: 트랜지션 t는 어떠한 상황에서도 간섭 받지 않습니다. 4.x 버전에서 atomic을 켰을 때와 동일합니다.


2) Current State: 트랜지션 t는 A 스테이트에서의 다른 트랜지션들에 의해 중단될 수 있습니다. 
그림 상에서 볼 때, 만약 해당 트랜지션이 일어나고 있는 도중에 A->B 또는 A->C의 트랜지션이 일어나는 조건이 만족하게 된다면 트랜지션 t는 중단되고 조건을 충족한 새로운 트랜지션이 일어나게 됩니다.

3) Next State트랜지션 t는 X 스테이트에서의 다른 트랜지션들에 의해 중단될 수 있습니다. 
그림 상에서 볼 때, 만약 해당 트랜지션이 일어나고 있는 도중에 X->Y 또는 X->Z의 트랜지션이 일어나는 조건이 만족하게 된다면 해당 트랜지션은 중단되어 버리고 조건을 충족한 새로운 트랜지션이 일어나게 됩니다.

4) Current State Then Next State트랜지션 t는 A와 X 스테이트의 다른 트렌지션들에 의해 중단될 수 있습니다. 단, A 스테이트가 우선순위가 높으므로, 양쪽 스테이트의 트랜지션들이 서로 동시에 발생할 수 있는 조건일 경우  A 스테이트에 속한 트랜지션이 먼저 선정됩니다.

5) Next State Then Current State트랜지션 t는 A와 X 스테이트의 다른 트렌지션들에 의해 중단될 수 있습니다. Current State Then Next State 와는 반대로 X 스테이트가 우선순위가 높습니다. 따라서 X 스테이트에 속한 트랜지션이 먼저 선정됩니다. 






ref : https://blog.naver.com/sasayakki/221357530573

ref : https://jinhomang.tistory.com/116

반응형
반응형


FYI I also had to use the updated PackageConversion files in order to get everything in my project converted.




Stephan_B

Unity Technologies


Importing the TMP Examples and Extras would have triggered a rebuild of the project solution which indirectly would have resolved the issue.

I meant these and potentially the Library folder.




반응형
반응형

Physics.BoxCastAll  을 사용하다보면(또는 유사 다른 cast) point 값이 0,0,0 으로 

나오거나 또는 normal 값이 이상한 형태로 나오는 것을 볼 수 있는데 이 이유에 대해 살펴봅시다



Physics.BoxCastAll



public static RaycastHit[] BoxCastAll(Vector3 centerVector3 halfExtentsVector3 directionQuaternion orientation = Quaternion.identity, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);

Parameters

centerCenter of the box.
halfExtentsHalf the size of the box in each dimension.
directionThe direction in which to cast the box.
orientationRotation of the box.
maxDistanceThe max length of the cast.
layermaskLayer mask that is used to selectively ignore colliders when casting a capsule.
queryTriggerInteractionSpecifies whether this query should hit Triggers.

Returns

RaycastHit[] All colliders that were hit.

Description

Like Physics.BoxCast, but returns all hits.

Notes: For colliders that overlap the box at the start of the sweep, RaycastHit.normal is set opposite to the direction of the sweep, RaycastHit.distance is set to zero, and the zero vector gets returned in RaycastHit.point. You might want to check whether this is the case in your particular query and perform additional queries to refine the result.



박스 단위로 충돌을 검사하는 함수입니다 



유효한 충돌 범위는


(대상 오브젝트의 centerhalfExtents 위치 기준=)maxDistance  > 충돌 가능 범위 >  centerhalfExtents


.99 그이하 미소 범위[e] 에 대해선 소수점 오차로 인해 충돌 판별 되는 양상을 띕니다 




가정


Cast 보낼 오브젝트 위치 정보

pos   : 0,0,-30

scale : 10,10,10


대상 오브젝트 정보 [1]

pos   : 0,0,-5

scale : 10,10,10


maxDistance 15 


그외 정보는 일반적으로 unit 한 정보라 가정합니다(normal 은 당연히 시작 오브젝트에서 대상을 바라본다 가정 합니다)



위의 경우 충돌이 되지만 


대상 오브젝트가 [2]

pos   : 0,0,-4.5

scale : 10,10,10


인 경우에는 충돌되지 않습니다





계산



충돌 시작점 + half

centerhalfExtents 의 위치에서부터 maxDistance 이 계산 됨으로


 -30 + 15 + 5 = -10


즉 대상체의 충돌 검출 시작점은 -10 (충돌 가능 범위 기준) 이내 범위어야합니다



그리하여 (대상 오브젝트가 [2])

-4.5 - 5 = -9.5  의 범위에선 충돌되지 않지만


그리하여 (대상 오브젝트가 [1])

-5.0 - 5 = -10  의 범위에선 충돌 검출을 하게 됩니다



만약 충돌될 대상 오브젝트가 충돌 시작위치의 halfExtents 을 침범하게 된다면?





주의


은 출돌검사를 시작하는 center 위치에서 halfExtents 까지의 범위 안에 충돌체가 들어왔고 이 충돌체와 충돌 하게 되면 pint 값이 0 이 되며

normal 값은 sweep 의 방향에 반대되는 (- 가 곱해진) normal 을 만듭니다






ref : https://docs.unity3d.com/ScriptReference/Physics.BoxCastAll.html


반응형
반응형


First, Download Custom Web Search of Visual Studio Extension program and select Options -> Environment-> keyboard,

and search Custom Web Search shortcut in 'Show commands containing' blank to go Unity web help page, and then assign your short key to 'Press shortcut keys'  








ref : https://forum.unity.com/threads/api-help-shortcut-in-visual-studio.344137/


반응형
반응형



AddListener to OnPointerDown of Button instead of onClick

I'd like to register a callback to the OnPointerDown and OnPointerUp events of the UnityEngine.UI.Button(s) in my game to trigger two different sounds for down and up click. However, only the onClick event is exposed. Is there any clean method to get these properties through code? I'd rather not add the UnityEngine.EventTrigger component to every button individually and set them up in the scene, because of the amount of buttons in my project and I might want to change behaviour while testing, and therefore rather do it from one central location in code.







I was expecting this to be built into the Unity button component, because it's such a common thing to ask for when assigning sounds to buttons and also the button must use it's OnPointerDown event internally to trigger sprite transitions etc. Why wouldn't they just expose these callback publicly like onClick already is?

Anyway, here is my code that got it working:

  1. EventTrigger trigger = buttons[i].gameObject.AddComponent<EventTrigger>();
  2. var pointerDown = new EventTrigger.Entry();
  3. pointerDown.eventID = EventTriggerType.PointerDown;
  4. pointerDown.callback.AddListener((e) => AkSoundEngine.PostEvent(downEvent, gameObject));
  5. trigger.triggers.Add(pointerDown);

For every button that needs additional listeners, I add an EventTrigger component and the appropriate PointerDown EventTriggerType. This has much more overhead than I wanted it to have, but it still works better than adding sound components to 20 buttons manually.

PS: Of course, I'd still be interested in seeing a better solution than mine.

Edit - Custom UI Button

I just figured, that I could also subclass the existing Unity Button to add my desired functionality. I keep forgetting that the UI source is extendable. This works very well and feels much cleaner. Just to spread community knowledge, here is my button extension, which I use to later add two different click sounds for down and up.

  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.Events;
  4. using UnityEngine.EventSystems;
  5. using System;
  6. // Button that raises onDown event when OnPointerDown is called.
  7. [AddComponentMenu("Aeronauts/AeButton")]
  8. public class AeButton : Button
  9. {
  10. // Event delegate triggered on mouse or touch down.
  11. [SerializeField]
  12. ButtonDownEvent _onDown = new ButtonDownEvent();
  13. protected AeButton() { }
  14. public override void OnPointerDown(PointerEventData eventData)
  15. {
  16. base.OnPointerDown(eventData);
  17. if (eventData.button != PointerEventData.InputButton.Left)
  18. return;
  19. _onDown.Invoke();
  20. }
  21. public ButtonDownEvent onDown
  22. {
  23. get { return _onDown; }
  24. set { _onDown = value; }
  25. }
  26. [Serializable]
  27. public class ButtonDownEvent : UnityEvent { }
  28. }

To make the ButtonDownEvent also show up in the inspector, you will need to subclass the according ButtonEditor as well.

  1. using UnityEditor;
  2. using UnityEditor.UI;
  3. [CustomEditor(typeof(AeButton), true)]
  4. public class AeButtonEditor : ButtonEditor
  5. {
  6. SerializedProperty _onDownProperty;
  7. protected override void OnEnable()
  8. {
  9. base.OnEnable();
  10. _onDownProperty = serializedObject.FindProperty("_onDown");
  11. }
  12. public override void OnInspectorGUI()
  13. {
  14. base.OnInspectorGUI();
  15. EditorGUILayout.Space();
  16. serializedObject.Update();
  17. EditorGUILayout.PropertyField(_onDownProperty);
  18. serializedObject.ApplyModifiedProperties();
  19. }
  20. }



ref : https://answers.unity.com/questions/1226851/addlistener-to-onpointerdown-of-button-instead-of.html

반응형
반응형


material color alpha doesn't seem to work.

I'm doing a simple box-add, and the alpha in the color doesn't seem to work. Code:

  1. var fieldBox : GameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
  2. fieldBox.transform.parent = this.transform;
  3. fieldBox.transform.renderer.material.color = Color.blue;
  4. fieldBox.transform.renderer.material.color.a = 0.05;

It just comes out solid blue. I'd expect something that was 95% transparent. What simple n00b mistake am I making?

Thanks!




Answer by Olie 

The immediate answer to the question that I asked is: the default shader does not do transparency. +1 to Eric5h5 for that.

However, the useful answer that I was looking for (and eventually found, but after a long-ish search!) was that I want to include this line:

  1. fieldBox.renderer.material.shader = Shader.Find( "Transparent/Diffuse" );

In the code above. So the whole thing looks like this:

  1. var fieldBox : GameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
  2. fieldBox.transform.parent = this.transform;
  3. fieldBox.transform.renderer.material.color = Color.blue;
  4. fieldBox.transform.renderer.material.color.a = 0.05;
  5. fieldBox.renderer.material.shader = Shader.Find( "Transparent/Diffuse" );

There are, of course, a variety of shaders and more efficient ways to get at them than Find(), but this is the answer for someone just looking to put a transparent cube on their early-on n00b project -- "how hard can it be?!"

I'm sorry you didn't find my answer sufficient, but I did say "You need to use a shader that has transparency." The second part ("The default diffuse shader doesn't") was merely a qualifier to the more important first part. I figured it would have been fairly obvious that you'd take another look at the shader drop-down list, see the "Transparent" section there, and say "Oh, I get it." Because that is exactly the reaction of several people in the past who've asked this question and gotten the same answer from me. I try to provide only the relevant info rather than wasting your time with fluff.

Also, if you find an answer to be unclear, just post a comment under it saying so, and I'll expand it.

Eric: yeah, good points all. Perhaps I wasn't clear that I was trying to do all of this in script, at which point "use a different shader" is, as I said, technically correct but not helpful. I'm not trying to "start up" with you -- I just noticed that a very many of the answers on this site are like that. I come from a strong StackOverflow background, where the answers are typically more overt, it all. No worries, I'll get the hang of things around here.




















ref : https://answers.unity.com/questions/33556/material-color-alpha-doesnt-seem-to-work.html

반응형
반응형



Suppose I have a class Foo and some member data that I wish to encapsulate in a struct Bar, like so:

Code (csharp):
  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. public class Foo : MonoBehaviour
  5. {  
  6.     public struct Bar { public int a; public float b; }
  7.     public Bar m_bar;
  8. }

Is there a way for me to make the members of m_bar editable through the Inspector of Foo?



---------------------------------------------------------



Want to re-resurrect, I have also gotten this to work, I got allot of errors, but I copy and pasted your code, swapped some stuff, deleted your code and put in my own and it works,





EDIT: I would also like to note that structs in structs work also, very good for organization!








ref : https://forum.unity.com/threads/how-do-i-make-member-structs-accessible-in-the-editor.36395/

반응형
반응형



박스로 캐스팅을 하는 것이고 인자 중 maxDistance 를 보면 알 수 있듯이 박스를 최대 던지는 거리(캐스팅 거리)가 있다는 것을 알 수 있습니다


인자 중 layermask 마스크 인자는 캐스팅(픽킹) 하고 싶은 레이어만을 선택 할 수 있습니다


All 은 모든 충돌체 박스 캐스팅 충돌체에 의한





충돌 범위는 center + halfExtention  ~   maxDistance + halfExtention 범위까지 체크됩니다





 



public static RaycastHit[] BoxCastAll(Vector3 centerVector3 halfExtentsVector3 directionQuaternion orientation = Quaternion.identity, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);

Parameters

centerCenter of the box.
halfExtentsHalf the size of the box in each dimension.
directionThe direction in which to cast the box.
orientationRotation of the box.
maxDistanceThe max length of the cast.
layermaskLayer mask that is used to selectively ignore colliders when casting a capsule.
queryTriggerInteractionSpecifies whether this query should hit Triggers.

Returns

RaycastHit[] All colliders that were hit.

Description

Like Physics.BoxCast, but returns all hits.

Notes: For colliders that overlap the box at the start of the sweep, RaycastHit.normal is set opposite to the direction of the sweep, RaycastHit.distance is set to zero, and the zero vector gets returned in RaycastHit.point. You might want to check whether this is the case in your particular query and perform additional queries to refine the result.


ref : https://docs.unity3d.com/ScriptReference/Physics.BoxCastAll.html


반응형

+ Recent posts