"""
タイトル: 球体と三角錐の追尾・逃避アニメーション
説明:
三角錐(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()