Blender Python ボーンの親を変更する

やあ

いろんなキャラモデルを共通のアーマチュアで作ったけど、後からボーンの親を変更したいという状況が起こって、手動で全部書き換えるのはめんどうだなーって思って書いたよ。

注意

自分の環境ではこれで動きましたが、念のため、使用する際にはバックアップを取っておくことをお勧めします。
何か問題が起こっても僕の責任ではありません。

環境

Blender 2.90.0

コード

import bpy

target_armature_names = [
     "A", "B", "C"
]

reparents = [
    "Hand_R"
]

new_parent_name = "Wrist_R"

def get_object(name : str):
    return bpy.data.objects[name]

for target_armature_name in target_armature_names:    
    armature = get_object(target_armature_name)
    if armature is None:
        continue
    # アーマチュアを選択して編集モードにすることでedit_bonesからedit_boneを取得できる。
    armature.select_set(True)
    bpy.ops.object.mode_set(mode = "EDIT")
    new_parent = armature.data.edit_bones.get(new_parent_name)
    for reparent_target in reparents:
        target_edit_bone = armature.data.edit_bones.get(reparent_target)
        if target_edit_bone is None:
            continue
        target_edit_bone.parent = new_parent
    # 次に編集するアーマチュアを編集モードにできなくなるので、編集が終了したら、選択解除して、オブジェクトモードにしておく。
    armature.select_set(False) 
    bpy.ops.object.mode_set(mode = "OBJECT")

使い方

  1. target_armature_namesに編集したいアーマチュアの名前を詰め込む。

  2. reparentsにペアレントを編集したいボーンの名前を詰め込む。

  3. new_parent_nameに新しいペアレントの名前を設定する。

  4. スクリプトを走らせる。

ボーンごとに設定するペアレントを指定したい場合。

import bpy

target_armature_names = [
     "A", "B", "C"
]

reparents = [
    ("Hand_R", "Wrist_R"),
    ("Hand_L", "Wrist_L")
]

def get_object(name : str):
    return bpy.data.objects[name]

for target_armature_name in target_armature_names:    
    armature = get_object(target_armature_name)
    if armature is None:
        continue
    armature.select_set(True)
    bpy.ops.object.mode_set(mode = "EDIT")
    for reparent_target, new_parent_name in reparents:
        new_parent = armature.data.edit_bones.get(new_parent_name)
        target_edit_bone = armature.data.edit_bones.get(reparent_target)
        if target_edit_bone is None:
            continue
        target_edit_bone.parent = new_parent
    armature.select_set(False) 
    bpy.ops.object.mode_set(mode = "OBJECT")

こちらはテストしていないですが、これで問題なく動くはずです。

Blender Python チートシート

やあ

自分が必要になった時に更新するチートシートなので期待しないでください。

bpy.object

オブジェクトを取得

def get_object(name : str):
    return bpy.data.objects[name]

特定のオブジェクトを選択/選択解除する

obj = get_object("Cube")
# 選択
obj.select_set(True)
# 選択解除
obj.select_set(False)

オブジェクトの現在のアクション名を取得

obj = get_object("HumanArmature")
print(obj.animation_data.action)

オブジェクトからボーン一覧を取得

obj = get_object("HumanArmature")
for elem in obj.pose.bones:
    print(elem)

オブジェクトの特定のボーン名を変更

obj = get_object("HumanArmature")

pre_bone_name = "Arm"
pose_bone = obj.pose.bones.get(pre_bone_name)
if pose_bone is not None:
    pose_bone.name = pose_bone.name.replace(pre_bone_name, "LowerArm")

ボーン名を変更し、全アクションのボーン名も変更する

こちらにまとめています。
pto8913.hatenablog.com

オブジェクトの特定のボーンのペアレントを変更する

こちらにまとめています。
pto8913.hatenablog.com

ボーンを揃える

pto8913.hatenablog.com

ボーンを追加する

pto8913.hatenablog.com

bpy.data

オブジェクトの名前一覧を取得

for elem in bpy.data.objects:
    print(elem.name)

全アクションを取得

for anim in bpy.data.actions:
    print(anim)

特定の名前のアクションを削除

anim_name = "Jump"
bpy.data.actions.remove(bpy.data.actions.get(anim_name))

全アクションを削除

全部消えます

for anim_data in bpy.data.actions:
    bpy.data.actions.remove(anim_data)

全マテリアルを取得

for mat in bpy.data.materials:
    print(mat)

フェイクユーザーを使用していないマテリアルを削除

for mat in bpy.data.materials:
    if not mat.id_data.use_fake_user:
        bpy.data.materials.remove(mat)

UE4 OnOverlapイベントからHitResultを受け取りたいときに見るメモ

やあ

敵キャラを攻撃したときや、攻撃されたときに、攻撃された向きでリアクションを変えようと思っていたんだ。
だけど、OverlapイベントからHitResultを取得できるのは、オーバーラップしたアクターが動いてるもの(SweepがTrueの時)?だけみたいなんだ。
だから、それをかいくぐってヒットイベントを取得する方法をメモしておくよ。

やるだけです

敵キャラクターのコリジョンで呼び出した例だよ。
f:id:pto8913:20200905222003p:plain
blueprintue.com

youtu.be

UE4 C++ 特定の階層下のGameplayTagを判定する(ごり押し)

やあ

タイトルの通りだよ。
特定のタグ階層下のGameplayAbilityが呼び出されたときに、処理をしようと思ったんだけど、MatchesAnyMatchesAnyExactでは判定できなかった(使い方がわるいのかもですが)から、ごり押しで判定することにしたよ。
やるだけですが。

もっといい方法などありましたら教えていただけますと助かります。

やるだけです

bool AHoge::Huga(
    FGameplayTagContainer AbilityTag
)
{
    for (const FGameplayTag& Tag : AbilityTag)
    {
        TArray<FString> ParsedRes = {};
        FString TagName = Tag.GetTagName().ToString();
        TagName.ParseIntoArray(ParsedRes, TEXT("."));
        int32 IsMatched = 0;
        for (FString ParsedTag : ParsedRes)
        {
            if (ParsedTag == TEXT("Hierarchy1")) ++IsMatched;
            if (ParsedTag == TEXT("Hierarchy2"))
            {
                if (IsMatched > 0)
                {
                    UE_LOG(LogTemp, Log, TEXT("This ability is Hierarchy1.Hierarchy2"));
                    return true;
                }
                break;
            }
        }
    }
    return false;
};

一応、GameplayTagが引数の時のも。

bool AHoge::Huga(
    FGameplayTag AbilityTag
)
{
    TArray<FString> ParsedRes = {};
    AbilityTag.ParseIntoArray(ParsedRes, TEXT("."));
    int32 IsMatched = 0;
    for (FString ParsedTag : ParsedRes)
    {
        if (ParsedTag == TEXT("Hierarchy1")) ++bIsMatched;
        if (ParsedTag == TEXT("Hierarchy2"))
        {
            if (IsMatched > 0)
            {
                UE_LOG(LogTemp, Log, TEXT("This ability is Hierarchy1.Hierarchy2"));
                return true;
            }
            break;
        }       
    }
    return false;
};

汎用化?(引数にGameplayTagContainer)

bool AHoge::CheckGameplayTagContainerUnderHierarchy(
    const TArray<FString>& Hierarchy,
    const FGameplayTagContainer& TagContainerToCheck
) const
{
    int32 CheckedHierarchy = Hierarchy.Num() - 1;
    if (CheckedHierarchy < 0) return false;

    for (const FGameplayTag& Tag : TagContainerToCheck)
    {
        TArray<FString> ParsedRes = {};
        FString TagName = Tag.GetTagName().ToString();
        TagName.ParseIntoArray(ParsedRes, TEXT("."));
        int32 IsMatched = 0;
        for (const FString& ParsedTag : ParsedRes)
        {
            if (CheckedHierarchy < IsMatched) break;

            if (ParsedTag == Hierarchy[IsMatched]) ++IsMatched;
            if (IsMatched > CheckedHierarchy - 1)
            {
                return true;
            }
        }
    }
    return false;
}

引数Hierarchyに求めたい階層通りの順番に入れないといけないんだけど、これで十分だと思う。
みそからっぽで書いたから、間違ってたら教えてね。

汎用化?(引数にGamepalyTag)

bool AHoge::CheckGameplayTagUnderHierarchy(
    const TArray<FString>& Hierarchy,
    const FGameplayTag& TagToCheck
) const
{
    int32 CheckedHierarchy = Hierarchy.Num() - 1;
    if (CheckedHierarchy < 0) return false;

    TArray<FString> ParsedRes = {};
    FString TagName = TagToCheck.GetTagName().ToString();
    TagName.ParseIntoArray(ParsedRes, TEXT("."));
    int32 IsMatched = 0;
    for (const FString& ParsedTag : ParsedRes)
    {
        if (CheckedHierarchy < IsMatched) break;

        if (ParsedTag == Hierarchy[IsMatched]) ++IsMatched;
        if (IsMatched > CheckedHierarchy - 1)
        {
            return true;
        }
    }
    return false;
}

やってることは同じだよ。

UE4 SendGameplayEventToActorが動かない時に見るメモ

やあ

タイトルの通りだよ。
間違っているところなどありましたら教えてください。

イベントタグが揃っていますか?

f:id:pto8913:20200904163605p:plain
f:id:pto8913:20200904163702p:plain

blueprintue.com

UE4 どの方向から攻撃されたかを調べる方法

やあ

タイトルの通りだよ。
もっといい方法があれば教えてください。
BPで書いてもいいけどノードをつなぐのが面倒だったので、C++で書いたよ。

概要

f:id:pto8913:20200903161629p:plain
キャラクターのCapsuleComponentRightVectorDotProductしてDegAsinした結果と、同じくForwardVectorを処理した結果を合わせて、どの向きから攻撃されたかを調べるだけだよ。

実装

hoge.h

// どの方向かを伝えるためのenum
UENUM(Blueprintable)
enum class EDamageDirection : uint8
{
    Front = 0,
    Left = 1,
    Right = 2,
    Back = 3,
    None = 4,
};
ENUM_CLASS_FLAGS(EDamageDirection)

UCLASS()
class AMYPROJECT_API AHoge : ACharacter
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, Category = "Damage")
        EDamageDirection GetDamageDirection(
            FHitResult InHitResult
        );
};

hoge.cpp

EDamageDirection AHoge::GetDamageDirection(FHitResult InHitResult)
{
    FVector RightVector = GetCapsuleComponent()->GetRightVector();
    float RightDir = FVector::DotProduct(InHitResult.Normal, RightVector);
    RightDir = 180 / PI * FMath::Asin(RightDir);;

    FVector ForwardVector = GetCapsuleComponent()->GetForwardVector();
    float ForwardDir = FVector::DotProduct(InHitResult.Normal, ForwardVector);
    ForwardDir = 180 / PI * FMath::Asin(ForwardDir);
    
    //UE_LOG(LogTemp, Log, TEXT("RightDir %.f ForwardDir %.f"), RightDir, ForwardDir);

    if (
        (ForwardDir >= 45 && (RightDir <= 45 && RightDir >= 0)) ||
        (ForwardDir >= 45 && (RightDir >= -45 && RightDir <= 0))
       )
    {
        return EDamageDirection::Back;
    }
    
    if (
        (ForwardDir <= -45 && (RightDir <= 45 && RightDir >= 0)) ||
        (ForwardDir <= -45 && (RightDir >= -45 && RightDir <= 0))
       )
    {
        return EDamageDirection::Front;
    }

    if (
        ((ForwardDir <= 45 && ForwardDir >= 0) && RightDir >= 45) ||
        ((ForwardDir >= -45 && ForwardDir <= 0) && RightDir >= 45)
       )
    {
        return EDamageDirection::Left;
    }

    if (
        ((ForwardDir <= 45 && ForwardDir >= 0) && RightDir <= -45) ||
        ((ForwardDir >= -45 && ForwardDir <= 0) && RightDir <= -45)
       )
    {
        return EDamageDirection::Right;
    }
    return EDamageDirection::None;
}

おわり。

Blender to UE4 シェイプキーをアニメーションとしてUE4に持っていく

やあ

タイトルの通りだよ。
まばたきをする、などのシェイプキーを作ってそれをアニメーションとしてUE4に持っていくまでのメモだよ。

なおシェイプキーは自分で作ったものがあると思いますので、シェイプキーの使い方は省きます。

UE4に持っていった結果

f:id:pto8913:20200823001704g:plain

注意

シェイプキーを作る前に

シェイプキーを作る前にミラーモディファイア―を適用しておいてください。
シェイプキーを作った後に、ミラーモディファイアーの適用はできないです。
せっかく作ったシェイプキーが無駄になってしまうので、注意してください。

シェイプキーエディタでキーフレームを登録する

f:id:pto8913:20200823004219p:plain
ドープシート -> シェイプキーエディタ から開けます。
f:id:pto8913:20200823004331p:plain
キーフレームを登録したい位置で、シェイプキーの値をスライドしてキーを登録する。

シェイプキーアクションをNLAに追加する

f:id:pto8913:20210725135506p:plain

アクションのキーフレームを登録する

ドープシート -> アクション から開けます。
f:id:pto8913:20200823004509p:plain
シェイプキーと同じフレームにキーを登録する。

アクションをNLAに追加する

f:id:pto8913:20210725135548p:plain

NLAでフレームが同じか確認する

f:id:pto8913:20200823004640p:plain

エクスポートする

f:id:pto8913:20200823004700p:plain
f:id:pto8913:20210725135617p:plain

UE4でインポートする

f:id:pto8913:20200823004728p:plain
ここがかなり大事で、
Import Morph Targetsにチェックを必ず入れてください。

終わり

f:id:pto8913:20200823004827g:plain