見出し画像

blenderのpythonを使ったアニメーション、追いかけっこする二つのオブジェクトの色を状態に合わせて変化させる

"""
タイトル: 球体と三角錐の追尾・逃避アニメーション
説明:
三角錐(A)が球体(B)を追いかけ、球体(B)は三角錐から逃げる。
球体は逃げる間に色が黄色になり、通常時は緑色。
三角錐は球体を追いかけながら移動し、距離に応じて色を変化させます。
球体は一定時間ごとにランダムに移動方向を変化させます。

作成日: 2024年11月17日
バージョン: 1.9.0
"""

import bpy
import math
import random

# パラメータ設定
params = {
    "A_color_close": (1.0, 0.0, 0.0, 1.0),  # 赤(最短距離時の色)
    "A_color_far": (0.0, 0.0, 1.0, 1.0),   # 青(最大距離時の色)
    "B_color_normal": (0.0, 1.0, 0.0, 1.0),  # 緑(通常時の色)
    "B_color_escape": (1.0, 0.0, 0.0, 1.0),  # 赤(逃げている間の色)
    "escape_distance": 2.5,  # 三角錐から逃げ始める距離
    "safe_distance": 8.0,    # 三角錐から逃げるのをやめる距離
    "A_speed": 0.3,          # 三角錐の追尾速度
    "B_normal_speed": 0.3,   # 球体の通常移動速度
    "B_boost_speed": 1.0,    # 球体の加速移動速度
    "initial_position_A": (0, 0, 0),  # 三角錐の初期位置
    "initial_position_B": (5, 5, 5),     # 球体の初期位置
    "min_distance": 3.0,     # 三角錐の最短距離
    "max_distance": 5.0,     # 三角錐の最大距離
    "direction_change_interval": 30  # 球体の移動方向をランダムに変更するフレーム間隔
}

# 状態フラグ
state_flags = {
    "B_is_escaping": False  # 球体が逃げている状態
}

def calculate_distance(obj1, obj2):
    """
    2つのオブジェクト間の距離を計算します。
    """
    return math.sqrt(
        (obj1.location.x - obj2.location.x) ** 2 +
        (obj1.location.y - obj2.location.y) ** 2 +
        (obj1.location.z - obj2.location.z) ** 2
    )

def random_unit_vector():
    """
    ランダムな単位ベクトルを生成します。
    """
    x, y, z = random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)
    norm = math.sqrt(x**2 + y**2 + z**2)
    return (x / norm, y / norm, z / norm) if norm > 0 else (1.0, 0.0, 0.0)

def move_B(obj, chaser, frame, escape_distance, safe_distance, direction, last_change_frame):
    """
    球体(B)の移動と色変化を制御します。
    """
    distance = calculate_distance(obj, chaser)

    # ランダム方向の更新
    if (frame - last_change_frame) >= params["direction_change_interval"]:
        direction[:] = random_unit_vector()
        last_change_frame = frame

    # 状態フラグに応じた処理
    if state_flags["B_is_escaping"]:
        if distance >= safe_distance:
            state_flags["B_is_escaping"] = False
            obj.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = params["B_color_normal"]
        else:
            escape_vector = [
                obj.location.x - chaser.location.x,
                obj.location.y - chaser.location.y,
                obj.location.z - chaser.location.z
            ]
            norm = math.sqrt(sum(d ** 2 for d in escape_vector))
            if norm > 0:
                escape_direction = [d / norm for d in escape_vector]
                obj.location.x += escape_direction[0] * params["B_boost_speed"]
                obj.location.y += escape_direction[1] * params["B_boost_speed"]
                obj.location.z += escape_direction[2] * params["B_boost_speed"]
            obj.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = params["B_color_escape"]
    else:
        if distance <= escape_distance:
            state_flags["B_is_escaping"] = True
            obj.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = params["B_color_escape"]
        else:
            obj.location.x += direction[0] * params["B_normal_speed"]
            obj.location.y += direction[1] * params["B_normal_speed"]
            obj.location.z += direction[2] * params["B_normal_speed"]
            obj.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = params["B_color_normal"]

    obj.keyframe_insert(data_path="location", frame=frame)
    obj.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].keyframe_insert(data_path="default_value", frame=frame)

    return last_change_frame

def move_A(chaser, target, frame):
    """
    三角錐(A)の追尾動作と色変化を制御します。
    """
    distance = calculate_distance(chaser, target)

    # 色変化の計算
    if distance <= params["min_distance"]:
        color_ratio = 0.0
    elif distance >= params["max_distance"]:
        color_ratio = 1.0
    else:
        color_ratio = (distance - params["min_distance"]) / (params["max_distance"] - params["min_distance"])

    chaser.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (
        params["A_color_close"][0] * (1 - color_ratio) + params["A_color_far"][0] * color_ratio,
        params["A_color_close"][1] * (1 - color_ratio) + params["A_color_far"][1] * color_ratio,
        params["A_color_close"][2] * (1 - color_ratio) + params["A_color_far"][2] * color_ratio,
        1.0
    )
    chaser.active_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].keyframe_insert(data_path="default_value", frame=frame)

    direction = [
        target.location.x - chaser.location.x,
        target.location.y - chaser.location.y,
        target.location.z - chaser.location.z
    ]
    norm = math.sqrt(sum(d ** 2 for d in direction))
    if norm > 0:
        direction = [d / norm for d in direction]
        chaser.location.x += direction[0] * params["A_speed"]
        chaser.location.y += direction[1] * params["A_speed"]
        chaser.location.z += direction[2] * params["A_speed"]

    chaser.keyframe_insert(data_path="location", frame=frame)

def create_material(name, color):
    """
    指定されたベースカラーのマテリアルを作成します。
    """
    mat = bpy.data.materials.get(name)
    if mat is None:
        mat = bpy.data.materials.new(name=name)
        mat.use_nodes = True
        bsdf = mat.node_tree.nodes.get("Principled BSDF")
        if bsdf:
            bsdf.inputs["Base Color"].default_value = color
    return mat

def main():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)
    for mat in bpy.data.materials:
        bpy.data.materials.remove(mat, do_unlink=True)

    bpy.ops.mesh.primitive_cone_add(vertices=3, radius1=1, depth=2, location=params["initial_position_A"])
    A = bpy.context.object
    A.name = "A"
    A.data.materials.append(create_material("A_Material", params["A_color_far"]))

    bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=params["initial_position_B"])
    B = bpy.context.object
    B.name = "B"
    B.data.materials.append(create_material("B_Material", params["B_color_normal"]))

    frame_start = 1
    frame_end = 250

    direction = list(random_unit_vector())  # 初期移動方向
    last_change_frame = 0  # 最後に移動方向を変更したフレーム

    for frame in range(frame_start, frame_end + 1):
        last_change_frame = move_B(B, A, frame, params["escape_distance"], params["safe_distance"], direction, last_change_frame)
        move_A(A, B, frame)

    bpy.context.scene.frame_set(frame_start)

main()

いいなと思ったら応援しよう!