Godot キャラコン 10 「障害物とカメラ めんどくさいけど4本RayCast」
結局4本RayCast。そろそろカプセルを人型キャラに置き換えたいんだけどな。
人型3DモデルはblenderのアドオンであるManuelbastioniLABを利用させてもらった。ManuelbastioniLABの利用は、「1.使ったよ」ということと「2.HPアドレス」のリンクを載せればokだと思う。
「RayCast」ノード と プロパティ
4本にしたって事と、上の写真を見れば何をどうすればいいか分かると思うけど、俺は親切なので一応書いておく。
[1]
「RayCast」ノードを4つに。上下左右用。
[2]
プロパティはこんな感じに。
- 左用。
- Enabledを"On"にするのを忘れない。
- Cast Toはちょっと短め、5mにしてみた。(もちろんz方向に5m)
- Translationは左用は、左に22cmずらしてる。前と一緒。
同様に、右用(RayCast_R)、上用(RayCast_U)、下用(RayCast_D)も設定するが、Translationは
- 右用: (0.22, 0, 0)
- 上用: (0, 0.22, 0)
- 下用: (0, -0.22, 0)
にする。あとは一緒。
スクリプトへの追加と変更
[1]
カメラ用変数部分の変更。
L13
- 4つの「RayCast」ノード操作用の変数を配列(Array)で用意する。
- 中身はなんでもイイ。とにかく、要素が4つあればイイ。([1, 1, 1, 1]でも、["a", "b", "c", "d"]でもイイってこと。)
L14
- ヒットした場合の距離をしまっておく変数も上下左右の4つ必要なので、配列で用意。
L15
- 4本の内で一番短いのを入れる用。
[2]
レディー関数のボディー。
L30~33
- "my_ray[0]"~"my_ray[3]"に、左・右・上・下の「RayCast」ノードを
get_node()
する。
[3]
障害物に当たってたらカメラを前に、当たってなければ標準距離に、の部分。バッサリ変更。
L76~79
- RayCast2本の時は同じ処理を2回書いていた。だが今回は4本で、4回書くのはアホらしい。というわけで、自作関数を作って外注する。
- 大抵、似たような処理をするときは、自作関数を作って見た目をスッキリさせた方がイイ。後で手直ししたくなった時、何ヶ所も直さなければイケナイかわりに、外注先の1ヶ所を直せばいいだけなので、こうしておく方が断然楽。
- 自作関数
my_ray_hit(i)
はレイヒットの場所までの距離を"my_ray_dis[i]"に入れる仕事をする。解説は後ほど。
L81
- まずは、"my_ray_dis[0]"を最小値と考える。
L82~83
min()
関数は、パラメータが2つしか取れない。つまり2つの内の小さい方しか取ってこれない。- 今回は"my_ray_dis[0]"~"my_ray_dis[3]"の4つの中での最小値を取得したい。そのため、
for
ステートメントで回して、2つづつ比べて小さい方を残す方法を取っている。めんどくさ。 - もっと早くて簡単な方法ないかな~。
L85
- 最小距離に合わせてカメラ位置をセット。(カメラはヘッドの子だから、ローカル位置で指定してok。)
[4]
自作関数部分追加。
L87
my_ray_hit(i)
の"i"で、数値を受け取ってる。- L76で"my_ray_hit(0)"なので、
my_ray_hit(i)
関数内で"i"は"0"として機能する。同様、"my_ray_hit(1)"なら、"i"は"1"として機能する。 - もちろん"5"とか"ASDF"とか入れてこの関数を呼んだらバグる。0~3の整数で指定。
- "0"は左、"1"は右、"2"は上、"3"は下のRayCastとその距離。
L88~91
- 配列になっているだけで、やっていることは前回と一緒。
おまけ
カメラのFOVをちょっと狭く、60°にしてみた。(デフォ70°)
結果
床も透けがなくなったハズ。
おじさん付けてみたけど、まだやり方がイマイチ分からなくって、ずっとこっち見てるんだよねー。なんとかしないと。移動方向に向かせなくちゃいけないし、歩くモーション付けなきゃいけないし、先は長いな~~~。
ジャンプ、クラウチ、スイム、ウォーク・ランのトグル、階段、はしご、扉の開け閉め、、、あー。
もしかしてスカイリムのカメラって、前が見えるように、少し右にズレてたっけ? あと、マウスころころで、前後に動いたような。。。
しまったな。
こぴぺ
extends KinematicBody # マウス var my_mou_sen = 0.003 # マウスセンシ (mouse sensitivity) var my_mou_rel = Vector2(0, 0) # マウスが動いた差分 (mouse relative) # ヘッド (カメラピボット) var my_head # ヘッドノード入れる用 var my_head_ang = 0.0 # ヘッドのうなづき角度(ラジアン) (head angle) var my_head_ang_max = PI / 3.0 # ヘッドのうなづき限界角度(60°) (head angle MAX) # カメラ var my_ray = [0, 1, 2, 3] # RayCast_L, _R, _U, _Dノード入れる用 var my_ray_dis = [5.0, 5.0, 5.0, 5.0] # RayCast_@がヒットした時その場所までの距離 (distance) var my_ray_dis_min = 5.0 # 距離の最小値を入れる用 var my_cam # カメラノード入れる用 var my_cam_dis = 5.0 # ヘッドとカメラまでの標準距離 (camera distance) # プレイヤ var my_dir = Vector3(0, 0, 0) # キー入力での移動方向 (direction) var my_vel = Vector3(0, 0, 0) # 目標移動地点 (velocity) var my_spd = 10.0 # 移動速度 秒速10m (run speed) var my_acl = 0.03 # 加速率 (acceleration) var my_dcl = 0.08 # 減速率 (deceleration) var my_gra = -10.0 # 重力 (gravity) var my_yyy = 0.0 # 現在のy方向の大きさ保管用 func _ready(): my_head = get_node("Spatial_Head") my_ray[0] = get_node("Spatial_Head/RayCast_L") my_ray[1] = get_node("Spatial_Head/RayCast_R") my_ray[2] = get_node("Spatial_Head/RayCast_U") my_ray[3] = get_node("Spatial_Head/RayCast_D") my_cam = get_node("Spatial_Head/Camera_Main") func _input(event): if event is InputEventMouseMotion: my_mou_rel += event.relative func _physics_process(delta): # プレイヤ動かす # キー入力により移動方向をセットする my_dir = Vector3(0, 0, 0) if Input.is_action_pressed("my_forward"): my_dir = my_dir - my_head.global_transform.basis.z if Input.is_action_pressed("my_backward"): my_dir = my_dir + my_head.global_transform.basis.z if Input.is_action_pressed("my_right"): my_dir = my_dir + my_head.global_transform.basis.x if Input.is_action_pressed("my_left"): my_dir = my_dir - my_head.global_transform.basis.x my_dir.y = 0.0 my_dir = my_dir.normalized() # 加速・減速 if my_dir == Vector3(0, 0, 0): my_vel = my_vel.linear_interpolate(Vector3(0, 0, 0), my_dcl) else: my_vel = my_vel.linear_interpolate(my_dir * my_spd, my_acl) # 重力 my_vel.y = my_yyy + my_gra * delta # 移動とy保持 my_yyy = move_and_slide(my_vel).y # ヘッド動かす # 横回転 my_head.global_rotate(Vector3(0, 1, 0), -my_mou_rel.x * my_mou_sen) # 縦回転 my_head_ang = my_head_ang - my_mou_rel.y * my_mou_sen my_head_ang = clamp(my_head_ang, -my_head_ang_max, my_head_ang_max) my_head.rotation.x = my_head_ang # マウス移動がない時に勝手に動かない様、0に my_mou_rel = Vector2(0, 0) # 障害物に当たってたらカメラを前に、当たってなければ標準距離に # 当たり判定と距離取得 my_ray_hit(0) # 左レイ判定 my_ray_hit(1) # 右レイ判定 my_ray_hit(2) # 上レイ判定 my_ray_hit(3) # 下レイ判定 # 最小値取得 my_ray_dis_min = my_ray_dis[0] for i in [1, 2, 3]: my_ray_dis_min = min(my_ray_dis_min, my_ray_dis[i]) # カメラ位置セット my_cam.translation = Vector3(0, 0, my_ray_dis_min) func my_ray_hit(i): if my_ray[i].is_colliding(): my_ray_dis[i] = my_ray[i].global_transform.origin.distance_to(my_ray[i].get_collision_point()) else: my_ray_dis[i] = my_cam_dis