Убивство гравця

Ми можемо убивати ворогів просто стрибнувши на них, проте гравець залишається безсмертним. Давайте пофіксимо це.

Успішний удар має надходити від ворога, який не підлягає розчавленню. Ми хочемо, щоб гравець помирав при зіткненні з монстрами на підлозі, але не коли знаходиться в повітрі. Ми могли б використовувати векторну математику, щоб розрізняти два типи зіткнень. Замість цього, однак, ми будемо використовувати вузол Area, який добре підходить для хітбоксів (hit - удар, boxes - коробки).

Хітбокс з вузла Area

Поверніться до сцени гравця Player та додайте новий вузол Area. Назвіть його MobDetector. Додайте вузол CollisionShape в якості нащадка.

image0

В Інспекторі призначте йому форму циліндра.

image1

Ось трюк, який ви можете використовувати, щоб відбирати тільки ті зіткнення, що коли гравець знаходиться на землі, або близько до неї. Ви можете зменшити висоту циліндра і перемістити його до верхньої частини персонажа. Таким чином, коли гравець стрибає, форма буде занадто високо, щоб вороги зіткнулися з нею.

image2

Окрім цього циліндр має бути ширше сфери. Таким чином, гравець буде отримувати удар до того як сам нанесе його монстру.

Чим ширше циліндр, тим легше буде убивати гравця.

Далі знову виберіть вузол MobDetector і в Інспекторі вимкніть його властивість Monitorable. Таким чином інші фізичні вузли не зможуть виявити область. Додаткова властивість Monitoring дозволяє йому виявляти зіткнення. Потім зніміть шар зіткнення Collision -> Layer і встановіть маску на шар "enemies".

image3

Коли області (вузли Аrea) виявляють зіткнення, вони випромінюють сигнали. Ми збираємося підключити один до вузла гравця Player. На панелі Вузол двічі клацніть сигнал body_entered і підключіть його до Player.

image4

MobDetector буде випромінювати body_entered, коли в нього входить вузол KinematicBody, або RigidBody. Оскільки він реагує лише на фізичні шари "enemies", він буде виявляти лише вузли Mob.

З точки зору коду, ми збираємося зробити дві речі: випромінюваний сигнал ми пізніше будемо використовувати для завершення гри і знищення гравця. Ми можемо обернути ці операції в функцію die(), яка допоможе нам поставити описову мітку на коді.

# Emitted when the player was hit by a mob.
# Put this at the top of the script.
signal hit


# And this function at the bottom.
func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

Спробуйте гру ще раз, натиснувши F5. Якщо все налаштовано правильно, персонаж повинен загинути при зіткненні з ворогом.

Однак зверніть увагу, що це повністю залежить від розміру та положення гравця та форм зіткнення Mob. Можливо, вам доведеться перемістити їх і змінити розмір, щоб досягти жорсткого ігрового відчуття.

Завершення гри

Ми можемо використовувати сигнал гравця Player hit, для завершення гри. Все, що нам потрібно зробити, це підключити його до головного вузла Main і запрограмувати зупинку MobTimer при його надходженні.

Відкрийте Main.tscn, виберіть вузол Player, а на панелі Вузол підключіть його сигнал hit до головного вузла Main.

image5

Функція _on_Player_hit() отримує сигнал і зупиняє таймер.

func _on_Player_hit():
    $MobTimer.stop()

Якщо ви спробуєте гру зараз, монстри перестануть появлятися, коли ви помрете, а інші залишать екран.

Можете погладити себе по голові: ви створили прототип повної 3D-гри, хай, навіть, вона все ще трохи груба.

Звідси ми додамо рахунок, можливість повторити гру, і ви побачите, як можна трохи оживити гру за допомогою мінімалістичних анімацій.

Перевірка коду

Ось повні скрипти для вузлів Main, Mob та Player, для довідки. Ви можете використовувати їх для порівняння та перевірки вашого коду.

Починаючи з Main.gd.

extends Node

export(PackedScene) var mob_scene


func _ready():
    randomize()


func _on_MobTimer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instance()

    # Choose a random location on the SpawnPath.
    var mob_spawn_location = get_node("SpawnPath/SpawnLocation")
    # And give it a random offset.
    mob_spawn_location.unit_offset = randf()

    # Communicate the spawn location and the player's location to the mob.
    var player_position = $Player.transform.origin
    mob.initialize(mob_spawn_location.translation, player_position)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)


func _on_Player_hit():
    $MobTimer.stop()

Продовжуючи Mob.gd.

extends KinematicBody

# Emitted when the player jumped on the mob.
signal squashed

# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18

var velocity = Vector3.ZERO


func _physics_process(_delta):
    move_and_slide(velocity)


func initialize(start_position, player_position):
    look_at_from_position(start_position, player_position, Vector3.UP)
    rotate_y(rand_range(-PI / 4, PI / 4))

    var random_speed = rand_range(min_speed, max_speed)
    velocity = Vector3.FORWARD * random_speed
    velocity = velocity.rotated(Vector3.UP, rotation.y)


 func squash():
    emit_signal("squashed")
    queue_free()


func _on_VisibilityNotifier_screen_exited():
    queue_free()

І завершуючи найдовшим скриптом , Player.gd.

extends KinematicBody

# Emitted when a mob hit the player.
signal hit

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20
# Vertical impulse applied to the character upon bouncing over a mob in meters per second.
export var bounce_impulse = 16

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y += jump_impulse

    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

    for index in range(get_slide_count()):
        var collision = get_slide_collision(index)
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            if Vector3.UP.dot(collision.normal) > 0.1:
                mob.squash()
                velocity.y = bounce_impulse


func die():
    emit_signal("hit")
    queue_free()


func _on_MobDetector_body_entered(_body):
    die()

До зустрічі на наступному уроці, щоб додати рахунок та можливість повторити спробу.