RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 936026
Accepted
Andrew_STOP_RU_AGRESSION_IN_UA
Andrew_STOP_RU_AGRESSION_IN_UA
Asked:2020-01-24 15:58:53 +0000 UTC2020-01-24 15:58:53 +0000 UTC 2020-01-24 15:58:53 +0000 UTC

正确执行角色移动

  • 772
  • 即使我在两个对象上都有对撞机,为什么一个对象会穿过另一个对象?

  • 为什么我的角色在移动时会穿过另一个物体然后反弹回来?

  • 如何在 Unity3d 中正确实现角色移动?

  • 为什么经常使用 transform.position 移动,为什么它是错误的?

  • 如果 FPS 下降,为什么我的角色会以不同的速度移动?

  • 为什么分配中使用transform.position乘数Time.deltaTime?

  • 为什么通过 shift 移动角色是错误的transform.position?

  • 为什么使用Velocityor时.AddForce()不使用乘数Time.deltaTime?

  • 如何从地板上跳下来,但让角色无法无休止地起飞

  • 为什么当平台移动时,站在平台上的角色会留在原地?

  • 为什么子弹不总是造成伤害?

事实上,所有这些问题都是初学者中太常见的一个问题。

同时创建了一个标签unity3d-faq

c# unity3d
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Andrew_STOP_RU_AGRESSION_IN_UA
    2020-01-24T15:58:53Z2020-01-24T15:58:53Z

    阅读前需要了解的重要信息

    1. 在任何情况下,角色都必须挂上 RigidBody - 一个负责角色物理(重力、摩擦力等)的脚本

    2. 虽然我在这里拆机,包括。非物理运动,我强烈推荐使用物理运动。只有在特殊情况下才切换到非物质。

    3. 代码中不应直接绑定到按钮。应该绑定到 Input Manager参数。可以在以下位置找到:Edit -> Project Settings -> Input。你需要接受这作为一个公理并且不要离开,尽管你在那里谷歌搜索。

    4. 我将在这里使用 2 个术语:“传送”和“平滑移动”。据我了解:

    • 平滑移动 - 在物理框架内重新计算对象的位置,或在调用时与物理并行FixedUpdate()。

    • Teleportation - 在大于 的时间间隔内重新计算对象的位置fixedDeltaTime。

    有不同意见的人。

    • 流体运动 - 纯粹的物理运动
    • 传送 - 手动或使用.Translate()方法改变位置。
    • UPD:顺便说一句,他们是对的-但我无法编辑整个文本-内部翻译在每一帧上都使用传送 https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Transform/ScriptBindings /Transform.bindings.cs

    请注意,下面写的所有内容均基于术语的上层含义,而不是这些含义。


    您可以通过以下方式在游戏引擎中移动对象:

    • 使用物理引擎(由游戏引擎的物理模型驱动的运动)

    • 运动不是物理的。错误的方法是在每一帧上传送的方法。(在Update())

    • 运动不是物理的。正确的做法是物体在帧之间的平滑运动(平行于每个物理误算)(还是建议不要用)

    • 通过CharacterController实现运动(这里暂不考虑,因为新人根本不看,也许我以后会签)

    在实践中,移动方法是为特定字符选择的[字符-不是字面意思。它也可能是一辆车。] 在一种情况下,身体运动会更好。另一种是非物质的。在第三种情况下,CharacterController 是最好的。了解在哪种情况下什么是最好的将随着实践而来。

    初学者经常在每一帧上都使用瞬移,这是一种严重错误的方法。然后在 SO 上有大量问题的克隆,例如“为什么角色在墙附近抽搐?” 或“为什么它会穿墙?” 或“为什么子弹不总是造成伤害?” 之类的。

    只有一条规则要记住:您不能在作业中移动/旋转。这会产生问题。transform.positiontransform.rotation无论如何,它会从侧面出现在你身上。

    所需要的只是传送到物体的另一个地方,而不是它的移动。

    正确实施运动的一个例子:

    (以物体球为例)

    using UnityEngine;
    
    //эта строчка гарантирует что наш скрипт не завалится 
    //ести на плеере будет отсутствовать компонент Rigidbody
    [RequireComponent(typeof(Rigidbody))]
    public class Movement : MonoBehaviour
    {
        public float Speed = 10f;
        public float JumpForce = 300f;
    
        //что бы эта переменная работала добавьте тэг "Ground" на вашу поверхность земли
        private bool _isGrounded;
        private Rigidbody _rb;
    
        void Start()
        {
            _rb = GetComponent<Rigidbody>();
        }
    
        // обратите внимание что все действия с физикой 
        // необходимо обрабатывать в FixedUpdate, а не в Update
        void FixedUpdate()
        {
            MovementLogic();
            JumpLogic();
        }
    
        private void MovementLogic()
        {
            float moveHorizontal = Input.GetAxis("Horizontal");
    
            float moveVertical = Input.GetAxis("Vertical");
    
            Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
    
            _rb.AddForce(movement * Speed);
        }
    
        private void JumpLogic()
        {
            if (Input.GetAxis("Jump") > 0)
            {
                if (_isGrounded)
                {
                    _rb.AddForce(Vector3.up * JumpForce);
    
                    // Обратите внимание что я делаю на основе Vector3.up 
                    // а не на основе transform.up. Если персонаж упал или 
                    // если персонаж -- шар, то его личный "верх" может 
                    // любое направление. Влево, вправо, вниз...
                    // Но нам нужен скачек только в абсолютный вверх, 
                    // потому и Vector3.up
                }
            }
        }
    
        void OnCollisionEnter(Collision collision)
        {
            IsGroundedUpate(collision, true);
        }
    
        void OnCollisionExit(Collision collision)
        {
            IsGroundedUpate(collision, false);
        }
    
        private void IsGroundedUpate(Collision collision, bool value)
        {
            if (collision.gameObject.tag == ("Ground"))
            {
                _isGrounded = value;
            }
        }
    }
    

    该脚本基于以下代码:https ://unity3d.com/learn/tutorials/projects/roll-ball-tutorial/moving-player

    请注意,单元上的官方文档/教程中的代码是作为基础的。如果关于一些小而常见的问题(例如,角色移动)有多个信息来源 - 选择官方文档!他们绝对不会建议废话,

    ...

    不像充满游戏的论坛,包括。统一问答服务。在那里,在这些主题中,答案往往是由那些不知道正确方法的人写的。

    结果示例:

    在此处输入图像描述


    相关概念:

    是Update()的——在每一帧绘图上调用这个方法。Time.DeltaTime是绘制两帧之间的估计时间。如果 FPS 在计算机上下降,则此参数与下降成比例增加。

    是FixedUpdate()的 - 这是重新计算物理时调用的方法。Time.FixedDeltaTime,你猜对了,是通话之间的时间FixedUpdate()。它可以通过设置手动更改,但取决于启动游戏的机器的物理功能。

    如果对象没有物理属性(没有 RigidBody),这些参数和方法可以用于非物理运动。

    例如,相机旋转。

    或者在天空中旋转的立方体。

    或者在您无法接近的遥远地方的火车的动态图像。物理学对这样的物体根本没用——只是浪费资源

    但是,即使在这种情况下,最好使用Transform.Translate,但稍后会详细介绍

    如果我们正确地执行非物理运动,我们在丢帧时不会得到抖动的图片:

    重要提示:!!!有害代码示例!!!!不要这样做!

    // код для Update()
    transform.position += transform.forward * speed * Time.DeltaTime;
    
    // или же код для FixedUpdate()
    transform.position += transform.forward * speed * Time.FixedDeltaTime;
    

    我们分配到一个新的位置:

    1. 旧职位
    2. 行进方向
    3. 移动速度乘以Time.deltaTime。

    因此,即使我们有 60 帧并且有多达 10 帧的下沉,物体的旋转/移动速度也不会改变。毕竟我们是和人员下沉一起考虑的。


    关于运动的物理特性。

    假设我们移动一个物体通过rb.Velocity或通过AddForce(),那么这就是物体的物理运动。也就是说,它可以在某些物理定律的影响下随时间变化。例如,我们决定做一个角色跳跃:

    if (IsGrounded && Input.GetButtonDown("Jump"))
    {
        rb.velocity = new Vector3(0, 100, 0);
    }
    

    我们设置一次跳转向量。只有一瞬间。但它会随着时间的变化在重力作用下自动均匀递减。直到它变为零(跳跃的上点),然后它变为负数Y(下降),然后它落到地面并反弹(再次加上Y)等等,直到物体的物理速度在地面完全停止。

    假设我们transform.Positon通过按空格键向前移动我们的播放器更改。在某些时候,我们停止按下按钮 - 运动会突然停止并冻结。这是因为我们的运动不是物理的。假设我们来到墙上并尝试爬上它。因为 我们正在对一个物体进行传送,然后我们的角色将首先到达墙壁,然后传送到它里面,然后Collider它会被推出自己。它向内传送的深度仅取决于我们每帧传送角色的距离。也就是说,实现角色的移动是“坏习惯” 。

    但同时,也有一种可以接受的非物质运动。这就是使用方法Transform.Translate()。这(有点)也是瞬移,但试图使物体平稳地进行非物理运动。但是使用这种方法并不能免除我们使用 deltaTime/fixedDeltaTime 的责任,例如of.documentation。

    (同样——如果你的对象有一个刚体——你可能无论如何都需要使用物理位移!)

    如果装有游戏的设备负载很重,调用方法 Update()/FixedUpdate()也会降低速度。如果在物理学中即使没有我们也考虑到这一点,那么现在我们不是在做物理运动,这就是为什么必须通过添加这个因素来考虑这一点。

    但即使不使用这个乘数,我们也不会有穿墙失败的问题。这只是一个速度修复。


    一个简单但良好的非物理实现的移动代码示例,用于角色示例。

    如果在前面的示例中我们移动了球,那么可以使用物理模型推动它。也就是说,我们用于AddForce()这些目的。

    假设我们有一个角色——一个人,而不是一个球。让我们创建一个他的肖像而不是一个人 - 一个高立方体 0.8x1.8x0.3 并尝试将我们的球运动脚本附加到它上面。会出来以下内容:

    在此处输入图像描述

    也就是说,当我们尝试移动时,我们的角色会摔倒(我们推他,这是合乎逻辑的!)。当他跌倒时,由于摩擦力,他无法移动。但是我们可以在一个跳跃中移动它。:)

    让我们为这个角色更新这个代码。我们将用物体在空间中的非物理但平滑的运动来代替物体的物理推动:

    using UnityEngine;
    
    //эта строчка гарантирует что наш скрипт не завалится ести на плеере будет отсутствовать компонент Rigidbody
    [RequireComponent(typeof(Rigidbody))]
    public class Movement : MonoBehaviour
    {
        // т.к. логика движения изменилась мы выставили меньшее и более стандартное значение
        public float Speed = 5f;
    
        public float JumpForce = 300f;
    
        //что бы эта переменная работала добавьте тэг "Ground" на вашу поверхность земли
        private bool _isGrounded;
        private Rigidbody _rb;
    
        void Start()
        {
            _rb = GetComponent<Rigidbody>();
        }
    
        
        void FixedUpdate()
        {
            //обратите внимание что все действия с физикой 
            //желательно делать в FixedUpdate, а не в Update
            JumpLogic();
    
            // в даном случае допустимо использовать это здесь, но можно и в Update.
            // но раз уж вызываем здесь, то 
            // двигать будем используя множитель fixedDeltaTimе 
            MovementLogic();
        }
        
        private void MovementLogic()
        {
            float moveHorizontal = Input.GetAxis("Horizontal");
    
            float moveVertical = Input.GetAxis("Vertical");
    
            Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
    
            // что бы скорость была стабильной в любом случае
            // и учитывая что мы вызываем из FixedUpdate мы умножаем на fixedDeltaTimе
            transform.Translate(movement * Speed * Time.fixedDeltaTime);
        }
    
        private void JumpLogic()
        {
            if (Input.GetAxis("Jump") > 0)
            {
                if (_isGrounded)
                {
                    // Обратите внимание что я делаю на основе Vector3.up а не на основе transform.up
                    // если наш персонаж это шар -- его up может быть в том числе и вниз и влево и вправо. 
                    // Но нам нужен скачек только вверх! Потому и Vector3.up
                    _rb.AddForce(Vector3.up * JumpForce);
                }
            }
        }
    
        void OnCollisionEnter(Collision collision)
        {
            IsGroundedUpate(collision, true);
        }
    
        void OnCollisionExit(Collision collision)
        {
            IsGroundedUpate(collision, false);
        }
    
        private void IsGroundedUpate(Collision collision, bool value)
        {
            if (collision.gameObject.tag == ("Ground"))
            {
                _isGrounded = value;
            }
        }
    }
    

    使用此代码,我们将得到以下结果:

    在此处输入图像描述

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

    Теперь мы можем занятся украшательствами -- например повороты тела. Довольно приятно реализованы повороты вот здесь: Как сделать управление, как в игре "Overcooked"?

    Так же можно добавить анимацию бега на нашего персонажа (ну если бы это был не куб).


    Но как же реализация на физике?

    Да, можно подобное реализовать и на физике.

    Наша прошлая версия скрипта имела несколько недостатков. А именно:

    • нужно было вручную отмечать каждый из предметов от которого мы можем прыгать. То есть добавив ящик на пол, нам нужно еще и его отметить тэгом Ground.
    • если поставить кучу ящиков вертикально, присвоить каждому из них тэг "Ground", то просто подойдя к вертикальной стене из ящиков мы сможем взлететь вверх). То есть нам не важно к чему мы дотрагиваемся -- к полу или к стене -- оно давало нам возможность прыгать.
    • наше движение все так же было НЕ физическим. То есть если мы начнем двигать игрока влево-вправо то он будет резко останавливатся а потом резко двигатся в противоположную сторону. В живом мире так не бывает.

    Вспомните уроки физкультуры, когда нужно было пробежать 30 метров вперед, взять палочку, пробежать 30 метров назад, положить палочку и еще раз 30 метров в другую сторону... Что случалось с бегуном в этот момент если посмотреть сбоку? Сначала скорость растет, потом достигает пика, а потом торможение, взятие палочки, бег в другую сторону -- снова возрастание скорости. Никаких резких скачков. Этого можно добится именно передвижением при помощи физики.

    Давайте поместим на наш куб CapsuleCollider (минимальное торможение из-за силы трения) и заблочим в rigidBody rotateX и rotateZ(что б наш персонаж не падал на бок).

    А потом нацепим на него вот этот скрипт:

    using UnityEngine;
    
    //эти строчки гарантирют что наш скрипт не завалится если на плеере будет отсутствовать нужные компоненты
    [RequireComponent(typeof(Rigidbody))]
    [RequireComponent(typeof(CapsuleCollider))]
    public class Movement : MonoBehaviour
    {
        public float Speed = 0.3f;
        public float JumpForce = 1f;
    
        //даем возможность выбрать тэг пола.
        //так же убедитесь что ваш Player сам не относится к даному слою. 
    
        //!!!!Нацепите на него нестандартный Layer, например Player!!!!
        public LayerMask GroundLayer = 1; // 1 == "Default"
    
        private Rigidbody _rb;
        private CapsuleCollider _collider; // теперь прийдется использовать CapsuleCollider
        //и удалите бокс коллайдер если он есть
    
        private bool _isGrounded
        {
            get {
                var bottomCenterPoint = new Vector3(_collider.bounds.center.x, _collider.bounds.min.y, _collider.bounds.center.z);
    
                //создаем невидимую физическую капсулу и проверяем не пересекает ли она обьект который относится к полу
    
                //_collider.bounds.size.x / 2 * 0.9f -- эта странная конструкция берет радиус обьекта.
                // был бы обязательно сферой -- брался бы радиус напрямую, а так пишем по-универсальнее
    
                return Physics.CheckCapsule(_collider.bounds.center, bottomCenterPoint, _collider.bounds.size.x / 2 * 0.9f, GroundLayer);
                // если можно будет прыгать в воздухе, то нужно будет изменить коэфициент 0.9 на меньший.
            }
        }
    
        private Vector3 _movementVector
        {
            get
            {
                var horizontal = Input.GetAxis("Horizontal");
                var vertical = Input.GetAxis("Vertical");
    
                return new Vector3(horizontal, 0.0f, vertical);
            }
        }
    
        void Start()
        {
            _rb = GetComponent<Rigidbody>();
            _collider = GetComponent<CapsuleCollider>();
    
            //т.к. нам не нужно что бы персонаж мог падать сам по-себе без нашего на то указания.
            //то нужно заблочить поворот по осях X и Z
            _rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
    
            //  Защита от дурака
            if (GroundLayer == gameObject.layer)
                Debug.LogError("Player SortingLayer must be different from Ground SourtingLayer!");
        }
    
        void FixedUpdate()
        {
            JumpLogic();
            MoveLogic();
        }
    
        private void MoveLogic()
        {
            // т.к. мы сейчас решили использовать физическое движение снова,
            // мы убрали и множитель Time.fixedDeltaTime
            _rb.AddForce(_movementVector * Speed, ForceMode.Impulse);
        }
    
        private void JumpLogic()
        {
            if (_isGrounded && (Input.GetAxis("Jump") > 0))
            {
                _rb.AddForce(Vector3.up * JumpForce, ForceMode.Impulse);
            }
        }
    }
    

    在此处输入图像描述

    Вы видите эту плавность, как будто человек бежит, останавливается, бежит в другую сторону? Красота!

    А теперь вернитесь к прошлой гифке и присмотритесь... Движение совсем не такое :) Там как буд-то рукой двигают шахматную фигуру по доске.

    Ну и описанные выше баги поведения были пофикшены с такой реализацией.

    Можно добавить еще физический материал нашему персонажу и откоректировать его поведение.

    Вообще улучшать реализацию можно до бесконечности. Но, думаю, основные проблемы СПОСОБОВ ПЕРЕДВИЖЕНИЯ с которыми вы столкнетесь, я затронул :)

    Оптимально использовать именно передвижение на базе физики.

    Пытайтесь использовать исключительно физическое передвижение.


    Реализация нестандартной физики движений.

    Одним из моих любимейших примеров нестандартной физики движения является игра Ori and the Blind Forest

    https://www.youtube.com/watch?v=aKLxJTvaVy0

    Такое перемещение/такие прыжки невозможно сделать на основе стандартной физики. Вероятнее всего, это делалось через физическое перемещение + костыли для получения нужных эфектов которые противоречат стандартной физике.

    Сначала разрабатываются концепты движения. Они делаются в любом видеоредакторе с примитивными фигурами. Вот пример (если станет недоступным искать можно по Ori and the blind forest Enemy Concepts ) :

    https://www.youtube.com/watch?v=A8cV-oJfsjk

    Обратите внимание на то, то здесь прорисовано не только перемещение обьекта, но и его вытягивания/сжатия. Изменения формы во время любого взаимодействия с внешним миром. В т.ч. выстрелы так же влияют на форму. А так же что указываются радиусы опознавания главного героя каждым отдельным врагом.

    Костыли для каждого персонажа/врага свои собственные. Это делается что бы каждый из них обладал своей уникальной физикой. Сделать это на общей физике навряд ли возможно.


    Движение реализовано "правильно" но предмет все равно пролетает сквозь стену

    ДАЖЕ если вы реализовали физическое передвижение вашего персонажа, все равно может случится такое, что просчет CollisionDetect может проходить с ошибками. Такое бывает.

    Для таких случаев есть настройки отвечающие за обработку CollisionDetect в настройках самого RigidBody.

    在此处输入图像描述

    Желательно такого не делать т.к. это негативно сказывается на производительности. Чем на большем количестве обьектов вы меняете эти настройки, тем более вероятно что вы делаете какую-то дичь, которую делать совсем не нужно. Считайте это спасательным кругом, а не панацеей. А если вы так будете делать, то рано или поздно вы прийдете на SO с вопросом почему игра тормозит, вас попросят показать код и ничего не найдут просто потому, что проблема тормозов не в коде. И намучаетесь вы с оптимизациями ой как сильно.

    Так делать - не является ошибкой(!). Но чем меньше вы так будете делать - тем лучше. Подходите к изменению этих настроек с умом!


    Информация для самостоятельного изучения:

    • NavMesh

    • Character Controller

    • 在这个视频(我强烈建议您阅读) https://www.youtube.com/watch?v=puPjNRJMmOc中,已经讲述了一些不同的事情。如果在我上面描述的示例中,我们的角色只在平坦的表面上移动(也就是说,在地形或倾斜的平坦空间上可能会出现困难),那么这里已经描述了角色应该如何在不平坦的表面上移动。

    • 50

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5