【Godot 4】SlopeSlider (坂道滑り) コンポーネントの作り方

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

Godot 4 でキャラクターの「坂道挙動」を作ろうとすると、けっこう面倒ですよね。
CharacterBody2D / CharacterBody3Dmove_and_slide() は便利ですが、

  • 急な坂だけ滑り落ちてほしい
  • でも緩い坂では普通に立ち止まりたい
  • キャラごとに「滑りやすさ」を変えたい

…といった細かい調整をしようとすると、

  • プレイヤーのスクリプトが「坂判定」「角度計算」「追加重力」などでパンパンになる
  • 敵キャラや動く床ごとにコピペしてカスタマイズ → どこを直せばいいか分からなくなる

という「継承地獄」「肥大化スクリプト」コースに入りがちです。

そこで今回は、「急な坂にいるときだけ、重力を強くして自動で滑り落ちる」挙動を、
どのキャラクターにもポン付けできる コンポーネント として切り出してみましょう。

プレイヤーでも敵でも、CharacterBody2D にこのコンポーネントを 1 個アタッチするだけで、
「急斜面でズルズル滑り落ちる」挙動を簡単に再利用できるようにします。

【Godot 4】急斜面でズルズル滑ろう!「SlopeSlider」コンポーネント

今回の SlopeSlider コンポーネントは、ざっくり言うと次のようなことをします。

  • 親ノードCharacterBody2D であることを前提にする
  • _physics_process() で「接地&坂の角度」を判定
  • 一定以上の角度の坂に乗っていたら、「滑りベクトル」を計算して velocity に加算
  • 「滑り出す角度」「滑りの強さ」「Y軸の重力増加量」などを @export で調整可能

プレイヤー本体のスクリプトは「入力処理」に集中させて、
坂の物理挙動はすべて SlopeSlider に丸投げする構成ですね。


フルコード: SlopeSlider.gd


extends Node
class_name SlopeSlider
## 急な坂にいるとき、重力を強くして滑り落ちるコンポーネント(2D用)
##
## 想定する親ノード:
##   - CharacterBody2D
##
## 使い方:
##   1. 親に CharacterBody2D を置く
##   2. 子としてこの SlopeSlider をアタッチ
##   3. プレイヤー側では「通常の移動と重力」だけ書く
##   4. 坂判定と滑り挙動はこのコンポーネントに任せる

@export_group("基本設定")
## 何度以上の坂を「滑る坂」とみなすか(度数法)
## 例: 30〜45 あたりがゲームらしい値になりやすいです。
@export_range(0.0, 89.0, 0.1)
var slide_angle_threshold_degrees: float = 40.0

## 坂に沿って滑る力の強さ(水平成分)
## 値を大きくすると、急斜面で一気に加速していく感じになります。
@export_range(0.0, 5000.0, 1.0)
var slide_force: float = 800.0

## 滑っているときに追加でかかる重力(垂直成分)
## 「坂から早く落ちてほしい」「吸い付くように張り付いてほしい」などの調整用。
@export_range(0.0, 5000.0, 1.0)
var extra_gravity_while_sliding: float = 400.0

## 滑り中の最大速度(坂に沿った方向)
## 0 以下にすると無制限になります。
@export_range(0.0, 10000.0, 1.0)
var max_slide_speed: float = 1200.0

@export_group("ブレーキ設定")
## 入力による「逆方向移動」がどれくらい滑りを打ち消すかの係数
## 1.0 で「プレイヤー入力をそのまま尊重」
## 0.0 で「滑りを完全に優先(入力では止めづらい)」
@export_range(0.0, 1.0, 0.05)
var input_respect_factor: float = 0.5

## 滑り中に少しだけ摩擦を入れて、無限に加速しないようにする係数
## 0.0 で摩擦なし、1.0 に近づくほどすぐ減速します。
@export_range(0.0, 1.0, 0.01)
var slide_friction: float = 0.05

@export_group("デバッグ")
## デバッグ用: 現在「滑り中」かどうか
@export var debug_is_sliding: bool = false : set = _set_debug_is_sliding

## デバッグ表示用に色を変えたい場合などに利用できます
@export var debug_print: bool = false

# 内部参照: 親の CharacterBody2D
var _body: CharacterBody2D

func _ready() -> void:
    # 親が CharacterBody2D かどうかをチェック
    _body = get_parent() as CharacterBody2D
    if _body == null:
        push_warning("SlopeSlider must be a child of CharacterBody2D. Current parent: %s" % [get_parent()])
        set_physics_process(false)
        return

func _physics_process(delta: float) -> void:
    if _body == null:
        return

    # 接地していないときは何もしない(自由落下中など)
    if not _body.is_on_floor():
        debug_is_sliding = false
        return

    # CharacterBody2D の floor_normal を使用して床の傾きを取得
    # 通常、上方向は (0, -1) なので、床が水平なら floor_normal ≒ (0, -1) になります。
    var floor_normal: Vector2 = _body.get_floor_normal()

    # 法線ベクトルから「床の角度」を計算
    # 上向きベクトル (0, -1) との角度差をとることで「どれくらい傾いているか」を算出します。
    var up: Vector2 = Vector2.UP
    var angle_radians := floor_normal.angle_to(-up)  # -up = (0, -1)
    var angle_degrees := rad_to_deg(angle_radians)

    # 閾値未満の坂では滑らない
    if angle_degrees < slide_angle_threshold_degrees:
        debug_is_sliding = false
        return

    # ここから「滑り坂」と判断
    debug_is_sliding = true

    # 床の法線から「坂に沿った接線方向」を計算
    # 法線を 90 度回転させることで接線ベクトルを得られます。
    var slope_tangent: Vector2 = Vector2(-floor_normal.y, floor_normal.x).normalized()

    # 坂の「下り方向」を決める(重力方向に近い方を下りとみなす)
    var gravity_dir: Vector2 = Vector2.DOWN
    if slope_tangent.dot(gravity_dir) < 0.0:
        slope_tangent = -slope_tangent

    # 現在の速度を取得
    var velocity: Vector2 = _body.velocity

    # 坂に沿った現在の速度成分(スカラー)を取得
    var current_speed_along_slope: float = velocity.dot(slope_tangent)

    # 坂に沿って加速させる(slide_force を delta でスケーリング)
    var added_speed: float = slide_force * delta
    var new_speed_along_slope: float = current_speed_along_slope + added_speed

    # 最大速度制限
    if max_slide_speed > 0.0:
        new_speed_along_slope = clamp(new_speed_along_slope, -max_slide_speed, max_slide_speed)

    # 坂に沿った速度ベクトルを再構築
    var slide_velocity: Vector2 = slope_tangent * new_speed_along_slope

    # 「もともとの速度」と「滑り速度」をブレンドする
    # - 水平方向: 坂に沿った速度を上書きしつつ、プレイヤー入力を少し残す
    # - 垂直方向: 追加重力をかけて落ちやすくする
    #
    # ここでは、
    #   velocity = velocity - (坂成分) + (slide_velocity * (1.0 - input_respect_factor))
    # のようなイメージで、プレイヤー入力を完全には殺さないようにします。

    # 現在の速度から「坂に沿った成分」を抜き出す
    var current_slope_component: Vector2 = slope_tangent * current_speed_along_slope
    var velocity_without_slope: Vector2 = velocity - current_slope_component

    # 入力をどれくらい尊重するかでブレンド
    var blended_slope_velocity: Vector2 = lerp(
        current_slope_component,
        slide_velocity,
        1.0 - input_respect_factor
    )

    velocity = velocity_without_slope + blended_slope_velocity

    # 垂直方向(Y軸)に追加重力を加える
    velocity.y += extra_gravity_while_sliding * delta

    # 摩擦による減速(坂に沿った方向)
    if slide_friction > 0.0:
        var friction_factor := 1.0 - slide_friction
        # 坂成分にだけ摩擦を適用
        var slope_only: Vector2 = slope_tangent * velocity.dot(slope_tangent)
        var non_slope: Vector2 = velocity - slope_only
        slope_only *= friction_factor
        velocity = non_slope + slope_only

    # 計算した速度を親の CharacterBody2D に反映
    _body.velocity = velocity

    if debug_print:
        print("SlopeSlider: angle=%.2f, sliding=%s, vel=%s" % [angle_degrees, str(debug_is_sliding), str(_body.velocity)])


func _set_debug_is_sliding(value: bool) -> void:
    debug_is_sliding = value
    # ここで Sprite の色を変えたり、エフェクトを出したりしても良い
    # 例:
    # var sprite := _body.get_node_or_null("Sprite2D") as Sprite2D
    # if sprite:
    #     sprite.modulate = value ? Color(1, 0.8, 0.8) : Color(1, 1, 1)

使い方の手順

  1. スクリプトをプロジェクトに追加
    上記コードを res://components/SlopeSlider.gd などのパスで保存します。
    class_name SlopeSlider を定義しているので、エディタの「ノード追加」から直接追加できます。
  2. プレイヤーシーンにアタッチ
    例として、2D 横スクロールのプレイヤーを考えます。
    Player (CharacterBody2D)
     ├── Sprite2D
     ├── CollisionShape2D
     └── SlopeSlider (Node)
        
    • Player は通常どおり CharacterBody2D として作成
    • 子ノードとして SlopeSlider(Nodeベース)を追加
    • SlopeSlider のスクリプトに、先ほどの SlopeSlider.gd を指定
  3. プレイヤー側の移動スクリプトを書く(シンプルでOK)
    プレイヤーのスクリプトは「入力と基礎重力」だけに集中させます。
    
    # Player.gd
    extends CharacterBody2D
    
    @export var move_speed: float = 220.0
    @export var base_gravity: float = 900.0
    @export var jump_speed: float = -360.0
    
    func _physics_process(delta: float) -> void:
        var input_dir := Input.get_axis("ui_left", "ui_right")
    
        # 水平移動
        velocity.x = move_speed * input_dir
    
        # 基本の重力
        if not is_on_floor():
            velocity.y += base_gravity * delta
    
        # ジャンプ
        if Input.is_action_just_pressed("ui_accept") and is_on_floor():
            velocity.y = jump_speed
    
        # 実際の移動
        move_and_slide()
    

    坂の角度判定・滑り処理はすべて SlopeSlider が裏で velocity をいじってくれるので、
    プレイヤー側のコードはかなりスッキリします。

  4. パラメータを調整して好みの挙動にする
    エディタで SlopeSlider ノードを選ぶと、インスペクタに以下の項目が出ます。
    • slide_angle_threshold_degrees … この角度以上の坂で滑る(例: 35〜45)
    • slide_force … 坂に沿った滑りの強さ(例: 600〜1000)
    • extra_gravity_while_sliding … 滑り中の追加重力(例: 300〜600)
    • max_slide_speed … 滑りの最高速度
    • input_respect_factor … プレイヤー入力をどれだけ尊重するか
    • slide_friction … 滑り中の摩擦(ブレーキ)

    レベル内の坂を実際に歩かせながら、
    「ここはギリギリ登れる」「ここからはズルズル落ちる」といった気持ちよさを探っていきましょう。

別の使用例: 敵キャラや動く床にも

同じコンポーネントを、敵キャラや特殊なオブジェクトにもそのまま付けられます。

Enemy (CharacterBody2D)
 ├── Sprite2D
 ├── CollisionShape2D
 └── SlopeSlider (Node)

この場合、敵の移動ロジック(パトロール・プレイヤー追跡など)は Enemy.gd に書き、
坂で滑る挙動はすべて SlopeSlider に任せる、という分担になります。

「動く氷床」のようなものを作る場合も、CharacterBody2D ベースで坂に沿って動かしつつ、
このコンポーネントを付けて「自重でずれていく床」なども表現できます。


メリットと応用

この SlopeSlider コンポーネントを使うメリットを整理すると:

  • プレイヤー/敵スクリプトから「坂ロジック」を完全に追い出せる
    → 移動コードがシンプルになり、デバッグしやすいです。
  • シーン構造は浅いまま、「挙動」をコンポーネントで足していける
    → Godot でありがちな「ノード継承ツリーが深くなりすぎる」問題を避けられます。
  • パラメータだけでキャラごとの個性を出せる
    → 同じ SlopeSlider を使いながら、「重い敵は滑りにくい」「氷の精霊はめちゃ滑る」といった差別化が簡単です。
  • レベルデザインの調整がやりやすい
    → 「このステージは全体的に滑りやすくしたい」なら、シーン内の SlopeSlider のパラメータを一括調整するだけでOKです。

「継承で PlayerSlopeWalker, EnemySlopeWalker… を作りまくる」のではなく、
「共通の SlopeSlider をコンポーネントとしてアタッチする」構成にすることで、
プロジェクト全体の見通しがかなり良くなりますね。

改造案: 「滑り中だけアニメーションを切り替える」

例えば、「滑っているときだけアニメーションを変えたい」場合は、
SlopeSlider にコールバックを 1 個足すだけで簡単に対応できます。


# SlopeSlider.gd 内に追加(例)

signal sliding_state_changed(is_sliding: bool)

var _prev_sliding: bool = false

func _physics_process(delta: float) -> void:
    # ...(中略)坂判定と debug_is_sliding の更新まで終わった後

    if debug_is_sliding != _prev_sliding:
        _prev_sliding = debug_is_sliding
        emit_signal("sliding_state_changed", debug_is_sliding)

    # 以降、velocity 計算などの処理...

そしてプレイヤー側では:


# Player.gd

func _ready() -> void:
    var slider := get_node("SlopeSlider") as SlopeSlider
    slider.sliding_state_changed.connect(_on_sliding_state_changed)

func _on_sliding_state_changed(is_sliding: bool) -> void:
    var anim := $AnimatedSprite2D
    if is_sliding:
        anim.play("slide")
    else:
        anim.play("idle")

こんな感じで、「坂滑り」という 1 つの振る舞いをコンポーネントとして独立させておくと、
アニメーションやエフェクトとの連携もシグナルベースで気持ちよく組めるようになります。
継承より合成、どんどん進めていきましょう。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!