Форум программистов, компьютерный форум, киберфорум
alecss131
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Ввод в Unreal Engine

Запись от alecss131 размещена 08.07.2024 в 12:49
Обновил(-а) alecss131 08.07.2024 в 12:36

Ввод в Unreal Engine

Небольшой гайд по созданию ввода в UE с нуля, как в С++ кода так и в Blueprints (далее для краткости BP), старая и новая (EnhancedInput) системы ввода. Пишу больше как заметку для себя.

Создание проекта и подготовка окружения

Создадим пустой проект на С++, с максимальным качеством и без стартер контента, назову ForAll

После создания проекта в корне с проектом создается файл `.vsconfig` представляющий собой конфигурацию установщика студии, у меня его содержимое вот такое
JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "version": "1.0",
  "components": [
    "Microsoft.Net.Component.4.6.2.TargetingPack",
    "Microsoft.VisualStudio.Component.VC.14.38.17.8.x86.x64",
    "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
    "Microsoft.VisualStudio.Component.Windows10SDK.22621",
    "Microsoft.VisualStudio.Workload.CoreEditor",
    "Microsoft.VisualStudio.Workload.ManagedDesktop",
    "Microsoft.VisualStudio.Workload.NativeDesktop",
    "Microsoft.VisualStudio.Workload.NativeGame"
  ]
}
Сейчас самая свежая версия студии это 17.10.3, а в этом файле указан компилятор версии 17.8, движок версии 5.4.2 работает и с 17.10, но при сборке выдает предупреждение 11>EXEC: Warning : Visual Studio 2022 compiler is not a preferred version, На сколько помню версия 5.3 была несовместима с версией 17.9, в 5.4 могло остаться то же самое.

После создания откроется главное окно редактора. Проект плюсовый и должен быть каталог с с++ классами, но его нету, возможно баг, но не важно.

Создадим новый уровень File -> New Level, выберем пресет Empty level

Окно вьюпорта изменится, оно станет черным
Нажмите на изображение для увеличения
Название: 3.png
Просмотров: 22
Размер:	156.2 Кб
ID:	8852
В папке Content создадим папку Input и в нее сохраним наш уровень (Ctrl + S) под именем InputMap

Теперь создадим GameMode в той же папке, для этого правой кнопкой мыши и создать Blueprint class и в списке выбираем GameModeBase. Теперь включим вкладку настроек мира через Window -> World Settings и в GameMode Override выберем наш созданным GameMode.

Теперь добавим стандартное небо, для этого откроем окно Env. Light Mixer с помощью Window -> Env. Light Mixer
Нажмите на изображение для увеличения
Название: 6.png
Просмотров: 20
Размер:	16.5 Кб
ID:	8853
В котором сверху поочередно нажмем все имеющиеся кнопки Create Sky Light -> Create Atmospheric Fog -> Create Sky Atmosphere -> Create Volumetric Cloud -> Create Height Fog. После этого закроем окно и увидим во вьюпорте небо.
Название: 7.jpg
Просмотров: 76

Размер: 43.9 Кб
Добавим на уровень поверхность где будем ходить в виде простого кубика с измененным масштабом.
Масштаб настроим так 10, 10 и 0.1 по осям X, Y и Z. И последним шагом добавим Player Start.
Поместим его в центр нашей платформы установив координаты в 0, потом поднимем над поверхностью и нажмем End, объект опустится на поверхность.
Название: 10.jpg
Просмотров: 68

Размер: 67.9 Кб
Теперь настроим чтобы при запуске редактора и упаковке игры открывалась наша карта, для этого по пути Edit -> Project Settings -> Maps & Modes выбираем наш уровень в Editor Startup Map и Game Default Map

Старое управление

В последних версиях движка оно устарело (deprecated) и не желательно к использованию. Из плюсов у него простота и быстрота создания и настройки, а из минусов невозможность изменить управление в игре. Для примера буду использовать управление персонажем от первого лица, для третьего лица будет все аналогично.

Первая часть настроек производится в настройках проекта по пути Edit -> Project Settings -> Input
Нажмите на изображение для увеличения
Название: 12.png
Просмотров: 27
Размер:	103.8 Кб
ID:	8856
Где редактор предупреждает нас о том что этот способ deprecated. Создадим тут привязки для кнопок. Action Mapping для единоразовых нажатий, например прыжки, стрельба и Axis Mappings для продолжительных нажатий, например передвижение персонажа и взгляд. Приведу сразу готовые настройки для передвижения по кнопкам WASD, осмотру мышкой и прыжку на Space. Запомним названия действий, они нам потом понадобятся.

Начнем создание с С++ версии.

Теперь создадим С++ класс нашего персонажа. Если отсутствует раздел с С++ классами, то сделать это можно с помощью Tools -> New C++ Class..., дадим ему имя OldInputCharacter и наследуемся от Character.
После создания изменим наш GameMode на уровне, указав только что созданного персонажа в строке Default Pawn Class.

Теперь перейдем к созданию играбельного персонажа. Начнем с h файла. После всех include-ов добавим forward declaration класса камеры
C++
1
class UCameraComponent;
И в тело (после конструктора в раздел Public) добавим компонент камеры
C++
1
2
UPROPERTY(VisibleAnywhere, Category = "Components")
UCameraComponent* CameraComponent;
И в конец класса в public добавим объявления для callback функций для управления.
C++
1
2
void MoveForward(float Axis);
void MoveRight(float Axis);
Теперь перейдем к реализации, подключим необходимые заголовки
C++
1
2
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
В конструкторе зададим размеры коллизиям (стандартному компоненту класса Character c именем CapsuleComponent). Создадим компонент камеры, зададим ей иерархию (дочерняя от капсулы), координаты и включим возможность вращать персонажа камерой.
C++
1
2
3
4
5
GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
CameraComponent->SetupAttachment(GetCapsuleComponent());
CameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 64.0f));
CameraComponent->bUsePawnControlRotation = true;
Теперь в методе SetupPlayerInputComponent зададим управление с помощью вызова методов BindAxis и BindAction, где первым параметром идет имя действия которое мы задавали в настройках проекта. Последним параметром идет ссылка на функцию, отвечающую за действие. Для движения параметры у функций не совпадают и поэтому используем созданные нами методы.
C++
1
2
3
4
5
6
PlayerInputComponent->BindAxis("MoveForwardOld", this, &AOldInputCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRightOld", this, &AOldInputCharacter::MoveRight);
PlayerInputComponent->BindAxis("LookHorizontalOld", this, &ACharacter::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookVerticalOld", this, &ACharacter::AddControllerPitchInput);
PlayerInputComponent->BindAction("JumpOld", IE_Pressed, this, &ACharacter::Jump);
PlayerInputComponent->BindAction("JumpOld", IE_Released, this, &ACharacter::StopJumping);
Реализуем объявленные нами методы для передвижения, у функции AddMovementInput первым параметром идет вектор направления вдоль которого будет движение.
C++
1
2
3
4
5
6
7
8
9
void AOldInputCharacter::MoveForward(float Axis)
{
    AddMovementInput(GetActorForwardVector(), Axis);
}
 
void AOldInputCharacter::MoveRight(float Axis)
{
    AddMovementInput(GetActorRightVector(), Axis);
}
И теперь полностью код
OldInputCharacter.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "OldInputCharacter.generated.h"
 
class UCameraComponent;
 
UCLASS()
class FORALL_API AOldInputCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    // Sets default values for this character's properties
    AOldInputCharacter();
 
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UCameraComponent* CameraComponent;
 
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
 
public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;
 
    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
    void MoveForward(float Axis);
    
    void MoveRight(float Axis);
 
};
OldInputCharacter.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "OldInputCharacter.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
 
// Sets default values
AOldInputCharacter::AOldInputCharacter()
{
    // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(GetCapsuleComponent());
    CameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 64.0f));
    CameraComponent->bUsePawnControlRotation = true;
}
 
// Called when the game starts or when spawned
void AOldInputCharacter::BeginPlay()
{
    Super::BeginPlay();
    
}
 
// Called every frame
void AOldInputCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
}
 
// Called to bind functionality to input
void AOldInputCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    PlayerInputComponent->BindAxis("MoveForwardOld", this, &AOldInputCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRightOld", this, &AOldInputCharacter::MoveRight);
    PlayerInputComponent->BindAxis("LookHorizontalOld", this, &ACharacter::AddControllerYawInput);
    PlayerInputComponent->BindAxis("LookVerticalOld", this, &ACharacter::AddControllerPitchInput);
    PlayerInputComponent->BindAction("JumpOld", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("JumpOld", IE_Released, this, &ACharacter::StopJumping);
}
 
void AOldInputCharacter::MoveForward(float Axis)
{
    AddMovementInput(GetActorForwardVector(), Axis);
}
 
void AOldInputCharacter::MoveRight(float Axis)
{
    AddMovementInput(GetActorRightVector(), Axis);
}
После запуска мы увидим следующую картинку.
Нажмите на изображение для увеличения
Название: 15.png
Просмотров: 40
Размер:	810.9 Кб
ID:	8857
Управление готово.

Теперь перейдем к BP версии.
Создадим BP класс унаслоедовав его от Character, назовем его BP_OldInputCharacter и поместим в каталог Input/Old.
Так же как и в С++ создадим ему камеру и настроим остальные параметры.
Поместим камеру в качестве дочернего элемента капсулы и высоту по Z на 64
Настроим размеры капсулы
И у камеры включим параметр Use Pawn Control Rotation
И в Event Graph зададим управление аналогично
Нажмите на изображение для увеличения
Название: 21.png
Просмотров: 39
Размер:	268.2 Кб
ID:	8858
И для проверки поменяем персонажа в GameMode нашего уровня.

Новое управление

В экспериментальном виде в виде плагина это доступно еще на версии 4.27. И только в 5 (точно не помню, но вроде 5.1 или 5.2) версии его сделали стандартным. Оно делается чуть дольше и сложнее, но зато более гибкое и поддерживает переназначение кнопок прямо в игре (в теории, не пробовал).
Управление в простейшем случае состоит из Input Action и Input Mapping Context, первые отвечают за действия, а вторые за привязку кнопок. Action и Context можно размещать не только у игрока, а у тех объектов которым нужен особый ввод. Контекстов может быть несколько и их можно размещать в контроллере игрока.

В проекте создадим папку Input/New и внутри папку Actions в которой создадим BP типа Input Action (раздел Input) с именами IA_Move, IA_Look и IA_Jump
Сменим тип у IA_Move и IA_Look на Axis2D (Vector2D)
Нажмите на изображение для увеличения
Название: 24.png
Просмотров: 25
Размер:	28.5 Кб
ID:	8859
Внутри Input/New создадим BP типа Input Mapping Context (раздел Input) с именем IMC_NewInput и откроем его редактирование
Сначала добавим все Mapping созданные ранее с помощью + около слова Mappings и выберем то что создали. Потом у каждого mapping создадим привязки клавиш, нажав + у каждого mapping. Начнем с Jump, нужна всего 1 клавиша Space, без дополнительных настроек. С Move будет посложнее, надо создать 4 клавиши и разным клавишам добавить разные модификаторы, что добавлять зависит от порядка следования, в данном случае W - Swizzle Input Axis Value, S - Swizzle Input Axis Value и Negate, A - Negate и D без модификаторов, выглядеть должно так
Название: 25.jpg
Просмотров: 68

Размер: 69.3 Кб
Выглядит сложно и непонятно, но это только на первый взгляд. Логика тут простая. Move у нас двумерный ввод, то есть по двум осям, а добавляемые кнопки добавляют ввод только по одному направлению оси, по умолчанию это ось Х. Поэтому для ввода W добавляем модификатор смены оси Swizzle Input Axis Value так как для движения вперед нужна ось Y, а для движения назад (кнопка S) нужен еще и модификатор `Negate`.
Теперь Look, добавляем всего 1 "кнопку", а точнее перемещение мышки и к ней один модификатор Negate по оси Y
Нажмите на изображение для увеличения
Название: 26.png
Просмотров: 28
Размер:	30.3 Кб
ID:	8861
Теперь перейдем к С++ варианту, настройка произведенная до этого общая, создадим класс для персонажа с именем NewInputCharacter. Как и со старым управлением добавим камеру и настройки в конструкторе

После всех include-ов добавим forward declaration для всех используемых элементов
C++
1
2
3
4
5
class UCameraComponent;
class UInputComponent;
struct FInputActionValue;
class UInputAction;
class UInputMappingContext;
А так же свойства для камеры, каждого Input Action и Input Mapping Context
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
UPROPERTY(VisibleAnywhere, Category = "Components")
UCameraComponent* CameraComponent;
 
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input)
UInputAction* JumpAction;
 
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input)
UInputAction* MoveAction;
 
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputAction* LookAction;
 
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
UInputMappingContext* MappingContext;
И объявим пару функций для передвижения
C++
1
2
3
void Move(const FInputActionValue& Value);
    
void Look(const FInputActionValue& Value);
Теперь cpp файл. Все необходимые заголовки
C++
1
2
3
4
5
#include "EnhancedInputSubsystems.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "EnhancedInputComponent.h"
#include "Engine/LocalPlayer.h"
Создание и настройка камеры и капсулы
C++
1
2
3
4
5
GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
CameraComponent->SetupAttachment(GetCapsuleComponent());
CameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 64.0f));
CameraComponent->bUsePawnControlRotation = true;
В методе BeginPlay добавляем наш MappingContext с индексом 0
C++
1
2
3
4
5
6
7
if (const auto PlayerController = Cast<APlayerController>(Controller))
{
    if (const auto Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
    {
        Subsystem->AddMappingContext(MappingContext, 0);
    }
}
А в SetupPlayerInputComponent назначаем для каждого Input Action свое действие-функцию
C++
1
2
3
4
5
6
7
if (const auto EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
    EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ANewInputCharacter::Move);
    EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ANewInputCharacter::Look);
    EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
    EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
}
И реализуем функции движение и просмотра
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ANewInputCharacter::Move(const FInputActionValue& Value)
{
    const FVector2D MovementVector = Value.Get<FVector2D>();
    if (Controller)
    {
        AddMovementInput(GetActorForwardVector(), MovementVector.Y);
        AddMovementInput(GetActorRightVector(), MovementVector.X);
    }
}
 
void ANewInputCharacter::Look(const FInputActionValue& Value)
{
    const FVector2D LookAxisVector = Value.Get<FVector2D>();
    if (Controller)
    {
        AddControllerYawInput(LookAxisVector.X);
        AddControllerPitchInput(LookAxisVector.Y);
    }
}
И код целиком
NewInputCharacter.h
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "NewInputCharacter.generated.h"
 
class UCameraComponent;
class UInputComponent;
struct FInputActionValue;
class UInputAction;
class UInputMappingContext;
 
UCLASS()
class FORALL_API ANewInputCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    // Sets default values for this character's properties
    ANewInputCharacter();
 
    UPROPERTY(VisibleAnywhere, Category = "Components")
    UCameraComponent* CameraComponent;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input)
    UInputAction* JumpAction;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input)
    UInputAction* MoveAction;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
    UInputAction* LookAction;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
    UInputMappingContext* MappingContext;
    
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
 
public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;
 
    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    
    void Move(const FInputActionValue& Value);
    
    void Look(const FInputActionValue& Value);
 
};
NewInputCharacter.cpp
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "NewInputCharacter.h"
#include "EnhancedInputSubsystems.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "EnhancedInputComponent.h"
#include "Engine/LocalPlayer.h"
 
// Sets default values
ANewInputCharacter::ANewInputCharacter()
{
    // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    GetCapsuleComponent()->InitCapsuleSize(35.0f, 90.0f);
    CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
    CameraComponent->SetupAttachment(GetCapsuleComponent());
    CameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 64.0f));
    CameraComponent->bUsePawnControlRotation = true;
}
 
// Called when the game starts or when spawned
void ANewInputCharacter::BeginPlay()
{
    Super::BeginPlay();
    if (const auto PlayerController = Cast<APlayerController>(Controller))
    {
        if (const auto Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
        {
            Subsystem->AddMappingContext(MappingContext, 0);
        }
    }
}
 
// Called every frame
void ANewInputCharacter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
}
 
// Called to bind functionality to input
void ANewInputCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    if (const auto EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ANewInputCharacter::Move);
        EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ANewInputCharacter::Look);
        EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
        EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
    }
}
 
void ANewInputCharacter::Move(const FInputActionValue& Value)
{
    const FVector2D MovementVector = Value.Get<FVector2D>();
    if (Controller)
    {
        AddMovementInput(GetActorForwardVector(), MovementVector.Y);
        AddMovementInput(GetActorRightVector(), MovementVector.X);
    }
}
 
void ANewInputCharacter::Look(const FInputActionValue& Value)
{
    const FVector2D LookAxisVector = Value.Get<FVector2D>();
    if (Controller)
    {
        AddControllerYawInput(LookAxisVector.X);
        AddControllerPitchInput(LookAxisVector.Y);
    }
}
Теперь унаследуемся BP классом с именем BP_CppNewInputCharacter (сохранив в каталоге Input/New) от нашего С++ класса и настроим наши Input компоненты
И как всегда поменяем игрока на новый класс в GameMode

И сделаем то же самое в BP, для простоты и чтобы не повторяться, в качестве основы скопируем переместим и переименуем BP от старого ввода, поместим его по адресу Input/New с именем BP_NewInputCharacter. У нас уже будет камера и остальные настройки, только удалим все ноды из Event Graph. Теперь настроим управление
Нажмите на изображение для увеличения
Название: 29.png
Просмотров: 29
Размер:	254.1 Кб
ID:	8862
И в GameMode поставим нашего нового игрока
Запустим игру и убедимся что все работает
Нажмите на изображение для увеличения
Название: 15.png
Просмотров: 40
Размер:	810.9 Кб
ID:	8857

И упакуем исходники проекта, только то что нужно, это в движке делается автоматически с помощью File -> Zip Project. Приложу то, что запаковалось в архив ForAll.zip
Размещено в Без категории
Показов 199 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru