paint-brush
Как да създадете 2D контролер на знаци в Unity: Част 2от@deniskondratev
653 показания
653 показания

Как да създадете 2D контролер на знаци в Unity: Част 2

от Denis Kondratev17m2024/12/08
Read on Terminal Reader

Твърде дълго; Чета

Тази статия показва как да подобрите Unity 2D контролер за символи, като обхваща нова настройка на системата за въвеждане, подобрена механика за скачане и безпроблемно проследяване на камерата.
featured image - Как да създадете 2D контролер на знаци в Unity: Част 2
Denis Kondratev HackerNoon profile picture

В тази статия ние продължаваме да разработваме контролер за символи за 2D платформинг в Unity, като подробно изследваме всяка стъпка от конфигурирането и оптимизирането на контролите.


В предишната статия, „ Как да създадете 2D контролер на персонажи в Unity: Част 1 “, обсъдихме подробно как да създадем основата на героя, включително неговото физическо поведение и основно движение. Сега е време да преминем към по-напреднали аспекти, като обработка на въвеждане и динамично проследяване на камерата.


В тази статия ще се задълбочим в настройването на новата система за въвеждане на Unity, създаване на активни действия за контрол на героя, разрешаване на скачане и осигуряване на правилни отговори на команди на играча.

Ако искате сами да приложите всички промени, описани в тази статия, можете да изтеглите клона на хранилището „ Тяло на символа “, който съдържа основата за тази статия. Като алтернатива можете да изтеглите клона „ Контролер на символи “ с крайния резултат.

Настройка на системата за въвеждане

Преди да започнем да пишем код за управление на нашия герой, трябва да конфигурираме системата за въвеждане в проекта. За нашия платформинг избрахме новата система за въвеждане на Unity, представена преди няколко години, която остава актуална поради предимствата си пред традиционната система.


Системата за въвеждане предлага по-модулен и гъвкав подход към обработката на въвеждане, което позволява на разработчиците лесно да настройват контроли за различни устройства и да поддържат по-сложни сценарии за въвеждане без допълнителни разходи за внедряване.


Първо инсталирайте пакета Input System. Отворете Package Manager от главното меню, като изберете Window → Package Manager. В секцията Unity Registry намерете пакета „Input System“ и щракнете върху „Install“.

След това отидете на настройките на проекта чрез менюто Редактиране → Настройки на проекта. Изберете раздела Player, намерете секцията Active Input Handling и я задайте на „Input System Package (New)“.

След като изпълните тези стъпки, Unity ще ви подкани да рестартирате. След рестартиране всичко ще бъде готово за конфигуриране на контроли за нашия капитан.

Създаване на входни действия

В папката Настройки създайте Действия за въвеждане чрез главното меню: Активи → Създаване → Действия за въвеждане . Наименувайте файла „Контроли“.

Input System на Unity е мощен и гъвкав инструмент за управление на въвеждане, който позволява на разработчиците да конфигурират контроли за герои и игрови елементи. Поддържа различни входни устройства. Действията за въвеждане, които създавате, осигуряват централизирано управление на въвеждането, опростявайки настройката и правейки интерфейса по-интуитивен.


Щракнете двукратно върху файла Controls , за да го отворите за редактиране, и добавете карта на действие за управление на знаци, наречена „Character“.

Карта на действие в Unity е колекция от действия, които могат да бъдат свързани с различни контролери и клавиши за изпълнение на конкретни задачи в играта. Това е ефективен начин за организиране на контроли, позволяващ на разработчиците да разпределят и коригират входове, без да пренаписват кода. За повече подробности вижте официалната документация на Input System .


Първото действие ще се нарича „Преместване“. Това действие ще определи посоката на движение на героя. Задайте типа действие на "Стойност" и типа контрол на "Вектор2", за да активирате движение в четири посоки.

Задайте обвързване на това действие, като изберете Добавяне нагоре/надолу/надясно/наляво Composite и присвоете познатите WASD клавиши към съответните им посоки.

Не забравяйте да запазите настройките си, като щракнете върху Запазване на актива . Тази настройка гарантира, че можете да преназначите обвързвания за действието „Преместване“, например на клавишите със стрелки или дори на джойстика на геймпада.


След това добавете ново действие — „Скок“. Запазете типа действие като „Бутон“, но добавете ново взаимодействие — „Натискане“ и задайте поведение на задействане на „Натискане и освобождаване“, тъй като трябва да уловим както натискането, така и освобождаването на бутона.

Това завършва схемата за контрол на героите. Следващата стъпка е да напишете компонент, който да обработва тези действия.

Преместване на героя наляво и надясно

Време е да свържем входните действия, които създадохме за контрол на героите, с компонента CharacterBody , позволявайки на героя активно да се движи през сцената според нашите контролни команди.


За да направим това, ще създадем скрипт, отговарящ за контрола на движението, и ще го наречем CharacterController за яснота. В този скрипт първо ще дефинираме някои основни полета. Ще добавим препратка към компонента CharacterBody , _characterBody , който ще се контролира директно от скрипта.


Също така ще зададем параметри за скоростта на движение на героя ( _speed ) и височината на скока ( _jumpHeight ). Освен това ще дефинираме предназначението на полето _stopJumpFactor .


Може би сте забелязали, че в много 2D платформинги височината на скока може да се контролира. Колкото по-дълго се задържи бутонът за скок, толкова по-високо скача героят. По същество първоначалната скорост нагоре се прилага в началото на скока и тази скорост се намалява, когато бутонът се пусне. _stopJumpFactor определя с колко намалява скоростта нагоре при отпускане на бутона за скок.


Ето пример за кода, който ще напишем:


 // CharacterController.cs public class CharacterController : MonoBehaviour { [SerializeField] private CharacterBody _characterBody; [Min(0)] [SerializeField] private float _speed = 5; [Min(0)] [SerializeField] private float _jumpHeight = 2.5f; [Min(1)] [SerializeField] private float _stopJumpFactor = 2.5f; }


След това ще приложим възможността за преместване на героя наляво и надясно. Когато задържите натиснат бутона за движение, героят трябва да поддържа определената скорост на движение, независимо от препятствията. За да постигнем това, ще добавим променлива в скрипта за съхраняване на текущата скорост на движение по повърхността (или просто хоризонтално, когато героят е във въздуха):


 // CharacterController.cs private float _locomotionVelocity;


В компонента CharacterBody ще въведем метод за задаване на тази скорост:


 // CharacterBody.cs public void SetLocomotionVelocity(float locomotionVelocity) { Velocity = new Vector2(locomotionVelocity, _velocity.y); }


Тъй като нашата игра не разполага с наклонени повърхности, този метод е доста прост. В по-сложни сценарии ще трябва да отчетем състоянието на тялото и наклона на повърхността. Засега просто запазваме вертикалната компонента на скоростта, като променяме само хоризонталната x координата.


След това ще зададем тази стойност в метода Update за всеки кадър:


 // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); }


Ще дефинираме метод за обработка на сигнали от действието за въвеждане на Move :


 // CharacterController.cs public void OnMove(InputAction.CallbackContext context) { var value = context.ReadValue<Vector2>(); _locomotionVelocity = value.x * _speed; }


Тъй като действието Move е дефинирано като Vector2 , контекстът ще предостави векторна стойност в зависимост от това кои клавиши са натиснати или освободени. Например, натискането на клавиша D ще доведе до получаване на вектора (1, 0) от метода OnMove . Едновременното натискане на D и W ще доведе до (1, 1). Пускането на всички клавиши ще задейства OnMove със стойност (0, 0).


За клавиша A векторът ще бъде (-1, 0). В метода OnMove вземаме хоризонталния компонент на получения вектор и го умножаваме по зададената скорост на движение, _speed .

Обучение на героя да скача

Първо, трябва да научим компонента CharacterBody да се справя със скачането. За да направим това, ще добавим метод, отговорен за скока:


 // CharacterBody.cs public void Jump(float jumpSpeed) { Velocity = new Vector2(_velocity.x, jumpSpeed); State = CharacterState.Airborne; }


В нашия случай този метод е ясен: той задава вертикалната скорост и незабавно променя състоянието на героя на Airborne .


След това трябва да определим скоростта, с която героят трябва да скочи. Вече сме определили височината на скока и знаем, че гравитацията постоянно действа върху тялото. Въз основа на това първоначалната скорост на скок може да се изчисли по формулата:



Където h е височината на скок, а g е гравитационното ускорение. Ще вземем предвид и гравитационния множител, присъстващ в компонента CharacterBody . Ще добавим ново поле за определяне на началната скорост на скок и ще я изчислим, както следва:


 // CharacterController.cs private float _jumpSpeed; private void Awake() { _jumpSpeed = Mathf.Sqrt(2 * Physics2D.gravity.magnitude * _characterBody.GravityFactor * _jumpHeight); }


Ще ни трябва друго поле, за да проследим дали героят скача в момента, така че да можем да ограничим скоростта на скока в подходящия момент.


Освен това, ако играчът задържи бутона за скок до кацане, трябва сами да нулираме този флаг. Това ще бъде направено в метода Update :


 // CharacterController.cs private bool _isJumping; private void Update() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = false; } //... }


Сега нека напишем метода за обработка на действието Jump :


 // CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { Jump(); } else if (context.canceled) { StopJumping(); } }


Тъй като действието Jump е бутон, можем да определим от контекста дали натискането на бутона е започнало ( context.started ) или е приключило ( context.canceled ). Въз основа на това ние или започваме, или спираме скока.


Ето метода за изпълнение на скока:


 // CharacterController.cs private void Jump() { if (_characterBody.State == CharacterState.Grounded) { _isJumping = true; _characterBody.Jump(_jumpSpeed); } }


Преди да скочим, проверяваме дали героят е на земята. Ако е така, задаваме флага _isJumping и караме тялото да скочи с _jumpSpeed .

Сега нека внедрим поведението за спиране на скок:


 // CharacterController.cs private void StopJumping() { var velocity = _characterBody.Velocity; if (_isJumping && velocity.y > 0) { _isJumping = false; _characterBody.Velocity = new Vector2( velocity.x, velocity.y / _stopJumpFactor); } }


Спираме скока само ако флагът _isJumping е активен. Друго важно условие е персонажът да се движи нагоре. Това предотвратява ограничаване на скоростта на падане, ако бутонът за скок бъде освободен, докато се движите надолу. Ако всички условия са изпълнени, нулираме флага _isJumping и намаляваме вертикалната скорост с фактор _stopJumpFactor .

Настройка на героя

Сега, когато всички компоненти са готови, добавете компонентите PlayerInput и CharacterController към обекта Captain в сцената. Уверете се, че сте избрали компонента CharacterController , който създадохме, а не стандартния компонент Unity, предназначен за контролиране на 3D герои.


За CharacterController присвоете съществуващия компонент CharacterBody от знака. За PlayerInput задайте предварително създадените контроли в полето Actions .

След това конфигурирайте компонента PlayerInput да извиква подходящите методи от CharacterController. Разгънете секциите Събития и Знак в редактора и свържете съответните методи с действията Преместване и Прескачане.

Сега всичко е готово за стартиране на играта и тестване как всички конфигурирани компоненти работят заедно.


Движение на камерата

Сега трябва да накараме камерата да следва героя, където и да отиде. Unity предоставя мощен инструмент за управление на камерата — Cinemachine .


Cinemachine е революционно решение за управление на камерата в Unity, което предлага на разработчиците широка гама от възможности за създаване на динамични, добре настроени камерни системи, които се адаптират към нуждите на играта. Този инструмент улеснява прилагането на сложни техники на камерата, като проследяване на герои, автоматично регулиране на фокуса и много други, добавяйки жизненост и богатство към всяка сцена.


Първо, намерете обекта Main Camera в сцената и добавете компонента CinemachineBrain към него.

След това създайте нов обект в сцената с име CaptainCamera . Това ще бъде камерата, която следва капитана, точно като професионален оператор. Добавете към него компонента CinemachineVirtualCamera . Задайте полето Follow на капитана, изберете Framing Transposer за полето Body и задайте параметъра Lens Ortho Size на 4.


Освен това ще ни трябва още един компонент, за да дефинираме отместването на камерата спрямо героя — CinemachineCameraOffset . Задайте стойността Y на 1,5 и стойността Z на -15.

Сега нека тестваме как камерата следва нашия герой.



Мисля, че се получи доста добре. Забелязах, че камерата от време на време леко заеква. За да коригирам това, зададох полето Blend Update Method на обекта Main Camera на FixedUpdate.

Подобряване на скокове

Нека тестваме актуализираната механика. Опитайте да бягате и непрекъснато да скачате. Опитните геймъри може да забележат, че скоковете не винаги се регистрират. В повечето игри това не е проблем.


Оказва се, че е трудно да се предвиди точното време на кацане, за да се натисне отново бутона за скок. Трябва да направим играта по-прощаваща, като позволим на играчите да натиснат леко скок преди кацане и да накарат героя да скочи веднага след кацане. Това поведение е в съответствие с това, с което геймърите са свикнали.


За да приложим това, ще въведем нова променлива, _jumpActionTime , която представлява прозореца от време, през който скок все още може да бъде задействан, ако възникне възможност.


 // CharacterController.cs [Min(0)] [SerializeField] private float _jumpActionTime = 0.1f;


Добавих поле _jumpActionEndTime , което маркира края на прозореца за действие за прескачане. С други думи, докато се достигне _jumpActionEndTime , персонажът ще скочи, ако се появи възможност. Нека също да актуализираме манипулатора на действие Jump .


 // CharacterController.cs private float _jumpActionEndTime; public void OnJump(InputAction.CallbackContext context) { if (context.started) { if (_characterBody.State == CharacterState.Grounded) { Jump(); } else { _jumpActionEndTime = Time.unscaledTime + _jumpActionTime; } } else if (context.canceled) { StopJumping(); } }


При натискане на бутона за скок, ако героят е на земята, той скача веднага. В противен случай ние съхраняваме времевия прозорец, през който скокът все още може да бъде извършен.


Нека премахнем проверката Grounded state от самия метод Jump .


 // CharacterController.cs private void Jump() { _isJumping = true; _characterBody.Jump(_jumpSpeed); }


Ще адаптираме и метода за спиране. Ако бутонът е бил освободен преди кацане, не трябва да има скок, така че нулираме _jumpActionEndTime .


 // CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; //... }


Кога трябва да проверим дали героят е кацнал и да задействаме скок? Състоянието CharacterBody се обработва във FixedUpdate , докато обработката на действие се извършва по-късно. Независимо дали е Update или FixedUpdate , може да възникне забавяне от един кадър между кацане и скок, което е забележимо.


Ще добавим събитие StateChanged към CharacterBody , за да реагира незабавно на кацане. Първият аргумент ще бъде предишното състояние, а вторият ще бъде текущото състояние.


 // CharacterBody.cs public event Action<CharacterState, CharacterState> StateChanged;


Ще коригираме управлението на състоянието, за да задействаме събитието за промяна на състоянието и ще пренапишем FixedUpdate .


 // CharacterBody.cs [field: SerializeField] private CharacterState _state; public CharacterState State { get => _state; private set { if (_state != value) { var previousState = _state; _state = value; StateChanged?.Invoke(previousState, value); } } }


Също така усъвършенствах как се обработва surfaceHit във FixedUpdate .


 // CharacterBody.cs private void FixedUpdate() { //... if (_velocity.y <= 0 && slideResults.surfaceHit) { var surfaceHit = slideResults.surfaceHit; Velocity = ClipVector(_velocity, surfaceHit.normal); if (surfaceHit.normal.y >= _minGroundVertical) { State = CharacterState.Grounded; return; } } State = CharacterState.Airborne; }


В CharacterController ще се абонираме за събитието StateChanged и ще добавим манипулатор.


 // CharacterController.cs private void OnEnable() { _characterBody.StateChanged += OnStateChanged; } private void OnDisable() { _characterBody.StateChanged -= OnStateChanged; } private void OnStateChanged(CharacterState previousState, CharacterState state) { if (state == CharacterState.Grounded) { OnGrounded(); } }


Ще премахнем проверката на Grounded state от Update и ще я преместим в OnGrounded .


 // CharacterController.cs private void Update() { _characterBody.SetLocomotionVelocity(_locomotionVelocity); } private void OnGrounded() { _isJumping = false; }


Сега добавете кода, за да проверите дали трябва да се задейства скок.


 // CharacterController.cs private void OnGrounded() { _isJumping = false; if (_jumpActionEndTime > Time.unscaledTime) { _jumpActionEndTime = 0; Jump(); } }


Ако _jumpActionEndTime е по-голямо от текущото време, това означава, че бутонът за прескачане е натиснат наскоро, така че нулираме _jumpActionEndTime и изпълняваме прескачането.


Сега опитайте непрекъснато да скачате с героя. Ще забележите, че бутонът за скок се чувства по-отзивчив и контролирането на героя става по-плавно. Въпреки това забелязах, че в определени ситуации, като ъгъла, показан на илюстрацията по-долу, Grounded състояние изпитва леко забавяне, прекъсвайки веригата за прескачане.

За да се справя с това, зададох полето Surface Anchor в компонента CharacterBody на 0,05 вместо на 0,01. Тази стойност представлява минималното разстояние до повърхността, за да може тялото да влезе в Grounded състояние.

Скачане от скала

Може би сте забелязали, че опитът да скочите, докато бягате от вертикални повърхности, не винаги работи. Може да се почувствате сякаш бутонът за прескачане понякога не реагира.


Това е една от тънкостите на разработването на Character Controller за 2D платформъри. Играчите се нуждаят от способността да скачат, дори когато са закъснели с натискането на бутона за скок. Въпреки че тази концепция може да изглежда странна, така функционират повечето платформинги. Резултатът е герой, който се изтласква от въздуха, както е показано в анимацията по-долу.



Нека внедрим тази механика. Ще въведем ново поле за съхраняване на времевия прозорец (в секунди), през който персонажът все още може да скача, след като загуби състоянието Grounded .


 // CharacterController.cs [Min(0)] [SerializeField] private float _rememberGroundTime = 0.1f;


Също така ще добавим още едно поле за съхраняване на клеймото за време, след което Grounded състояние се „забравя“.


 // CharacterController.cs private float _lostGroundTime;


Това състояние ще бъде проследено с помощта на събитието CharacterBody . Ще коригираме манипулатора OnStateChanged за тази цел.


 // CharacterController.cs private void OnStateChanged(CharacterState previousState, CharacterState state) { if (state == CharacterState.Grounded) { OnGrounded(); } else if (previousState == CharacterState.Grounded) { _lostGroundTime = Time.unscaledTime + _rememberGroundTime; } }


Важно е да се разграничи дали персонажът е загубил състоянието Grounded поради умишлен скок или по друга причина. Вече имаме флага _isJumping , който се дезактивира при всяко извикване на StopJumping , за да предотврати излишни действия.


Реших да не въвеждам друг флаг, тъй като отмяната на излишния скок не влияе на играта. Чувствайте се свободни да експериментирате. Флагът _isJumping сега ще бъде изчистен само когато персонажът се приземи след скок. Нека актуализираме съответно кода.


 // CharacterController.cs private void StopJumping() { _jumpActionEndTime = 0; var velocity = _characterBody.Velocity; if (_isJumping && velocity.y > 0) { _characterBody.Velocity = new Vector2( velocity.x, velocity.y / _stopJumpFactor); } }


И накрая, ще преразгледаме метода OnJump .


 // CharacterController.cs public void OnJump(InputAction.CallbackContext context) { if (context.started) { if (_characterBody.State == CharacterState.Grounded || (!_isJumping && _lostGroundTime > Time.unscaledTime)) { Jump(); } else { _jumpActionEndTime = Time.unscaledTime + _jumpActionTime; } } else if (context.canceled) { StopJumping(); } }


Сега скачането от вертикални повърхности вече не нарушава ритъма на играта и се чувства много по-естествено, въпреки очевидната си абсурдност. Героят може буквално да отблъсне въздуха, отивайки по-далеч, отколкото изглежда логично. Но това е точно това, което е необходимо за нашия платформинг.

Обръщане на персонажа

Последният щрих кара героя да се изправи срещу посоката на движение. Ще приложим това по най-простия начин — като променим мащаба на знака по оста x. Задаването на отрицателна стойност ще накара нашия капитан да се изправи в обратната посока.

Първо, нека съхраним оригиналната скала, в случай че се различава от 1.


 // CharacterController.cs public class CharacterController : MonoBehaviour { //... private Vector3 _originalScale; private void Awake() { //... _originalScale = transform.localScale; } }


Сега, когато се движим наляво или надясно, ще приложим положителна или отрицателна скала.


 // CharacterController.cs public class CharacterController : MonoBehaviour { public void OnMove(InputAction.CallbackContext context) { //... // Change character's direction. if (value.x != 0) { var scale = _originalScale; scale.x = value.x > 0 ? _originalScale.x : -_originalScale.x; transform.localScale = scale; } } }


Нека тестваме резултата.


Завършване

Тази статия се оказа доста подробна, но успяхме да покрием всички съществени аспекти на контрола на героите в 2D платформинг. Като напомняне, можете да проверите крайния резултат в клона „ Контролер на символи “ на хранилището.


Ако сте харесали или сте намерили тази и предишната статия за полезни, ще се радвам на харесвания и звезди в GitHub. Не се колебайте да се свържете с нас, ако срещнете проблеми или откриете грешки. Благодаря ви за вниманието!

L O A D I N G
. . . comments & more!

About Author

Denis Kondratev HackerNoon profile picture
Denis Kondratev@deniskondratev
Software Engineer / С++ / C# / Unity / Game Developer

ЗАКАЧВАЙТЕ ЕТИКЕТИ

ТАЗИ СТАТИЯ Е ПРЕДСТАВЕНА В...