見出し画像

追いかけっこアニメーション、三角錐の方向を球体に向ける関数を追加

"""
タイトル: 球体と三角錐の追尾・逃避アニメーション
説明:
三角錐(A)が球体(B)を追いかけ、球体(B)は三角錐から逃げる。
球体は逃げる間に色が黄色になり、通常時は緑色。
三角錐は球体を追いかけながら移動し、方向ベクトルに応じて傾きます。

作成日: 2024年11月17日
バージョン: 2.0.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": 10.0,    # 三角錐から逃げるのをやめる距離
    "A_speed": 0.4,          # 三角錐の追尾速度
    "B_normal_speed": 0.25,   # 球体の通常移動速度
    "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": 12  # 球体の移動方向をランダムに変更するフレーム間隔
}

# 状態フラグ
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 calculate_rotation_from_direction(direction):
    """
    ベクトル方向から回転角度を計算します(XY軸の回転を適用)。
    Args:
        direction (list): 移動方向ベクトル [x, y, z]。
    Returns:
        tuple: オイラー角 (rotation_x, rotation_y, rotation_z)。
    """
    norm = math.sqrt(sum(d**2 for d in direction))
    if norm == 0:
        return (0.0, 0.0, 0.0)

    dx, dy, dz = direction[0] / norm, direction[1] / norm, direction[2] / norm

    # 回転角の計算(X軸とY軸の回転に基づく)
    rotation_x = math.atan2(dz, math.sqrt(dx**2 + dy**2))  # ピッチ(X軸回転)
    rotation_y = -math.atan2(dx, math.sqrt(dy**2 + dz**2))  # ロール(Y軸回転)
    return (rotation_x, rotation_y, 3.1)  # Z軸回転(ヨー)は固定


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"]

    # 回転を計算して適用
    rotation = calculate_rotation_from_direction(direction)
    chaser.rotation_euler = rotation

    # キーフレーム挿入
    chaser.keyframe_insert(data_path="location", frame=frame)
    chaser.keyframe_insert(data_path="rotation_euler", 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 move_B(obj, chaser, frame, escape_distance, safe_distance, direction, last_change_frame):
    """
    球体(B)の移動と色変化を制御します。
    """
    distance = calculate_distance(obj, chaser)

    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"]
            # ランダム方向を再設定
            direction = [random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)]
            last_change_frame = frame
        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:
            # 通常移動(ランダム)
            if frame - last_change_frame >= params["direction_change_interval"]:
                # ランダム方向の変更
                direction = [random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)]
                last_change_frame = frame
            # ランダム方向に移動
            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, direction

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 = [random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)]
    last_change_frame = 0

    for frame in range(frame_start, frame_end + 1):
        last_change_frame, direction = 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()

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