Управление персонажем в 2D

Общие вопросы о Unity3D

Управление персонажем в 2D

Сообщение Opts 12 сен 2012, 15:32

Использую CharacterController у которого капсула. Натянул её на бокс добавил немного управления и получил такую ситуацию
box-air.avi 137кб
Теоретически всё правильно капсула "задевает" бокс коллайдер платформы
Screenshot-1107.png
и поэтому ящик не падает. Но в игре то не должно быть так.
Пробовал через OnControllerColliderHit рассчитывал угол и если он попадает в какие-то пределы, то толкал персонаж в сторону. Это работает для конкретного случая, но сводит полезность в других случаях, например когда толкаешь ящик или прыгаешь по платформам, персонажа отбрасывает в сторону тогда когда его не должно откидывать. Может есть какое-то универсальное решение без глюков?
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22

Re: Управление персонажем в 2D

Сообщение seaman 12 сен 2012, 15:45

Убрать CharacterController. Добавить BoxCollider и свои скрипты для управления боксом.
seaman
Адепт
 
Сообщения: 8352
Зарегистрирован: 24 янв 2011, 12:32
Откуда: Самара

Re: Управление персонажем в 2D

Сообщение Opts 12 сен 2012, 15:52

Это первое что мне в голову пришло, но не всё так просто.
У charactercontroller-а есть гравитация причём она действует не всегда не смотря на то что в коде я гравитацию применяю.
В итоге пробовал добавлять box collider и выкидывал charactercontroller. В итоге гравитация действует всегда при этом персонаж начинает дико прыгать пытаясь продолбить пол. Через Phisycs.Raycast определяю что чел стоит на полу и вот мне не удалось добиться нормальной работы этого метода, персонажа постоянно кидало из стороны в сторону, он висел в воздухе а функция показывала что стоял на полу. На ровном месте при хотьбе подпрыгивал.
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22

Re: Управление персонажем в 2D

Сообщение seaman 12 сен 2012, 15:55

и свои скрипты для управления боксом.

Добавить гравитацию проще простого.
seaman
Адепт
 
Сообщения: 8352
Зарегистрирован: 24 янв 2011, 12:32
Откуда: Самара

Re: Управление персонажем в 2D

Сообщение Opts 12 сен 2012, 16:00

да гравитацию добавить не проблема, но заставить её работать адекватно у меня не получилось.
Ладно, вообщем сейчас попробую ещё раз сделать, и буду выкладывать сюда результат.
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22

Re: Управление персонажем в 2D

Сообщение Opts 12 сен 2012, 17:03

Закодил. Это просто ужас.
Видео того, что получилось.

Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;

public class PlayerControl : MonoBehaviour {
        private float walkSpeed = 3.0f;
    private float jumpSpeed = 5.0f;
        private float edgeSlopeSpeed = 10.0f;
    private Vector3 velocity = Vector3.zero;
    private bool isGround = false;
    private float gravity = 6.0f;
    private float fallSpeed = 3.0f;
    float afterHitForceDown = 2.0f;
    float pushPower = 2.0f;
       
       
        tk2dAnimatedSprite animation = null;
       
        void Start() {
                animation = GetComponent<tk2dAnimatedSprite>();
        }
       
    void OnCollisionEnter(Collision collision) {
                print("collision enter");
        if(collision.gameObject.tag == "ground") {
                        isGround = true;
        }
    }

    void OnCollisionExit(Collision collision) {
                print("collision exit");
        if (collision.gameObject.tag == "ground") {
                        isGround = false;
        }
    }
       
        void PlayerMoveUpdate() {
                bool ground = isOnGround(); //isGround;
                print(ground);
                if (ground) {
                        velocity = new Vector3(Input.GetAxisRaw("Horizontal"), 0, 0);
            velocity = transform.TransformDirection(velocity);
            velocity *= walkSpeed;
            if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.Space))
                velocity.y = jumpSpeed;
           
                }
                if (!ground) {
                        velocity.x = Input.GetAxisRaw("Horizontal");
                        velocity.x *= walkSpeed;
                        velocity.y -= gravity * Time.deltaTime;
                }
        transform.Translate(velocity * Time.deltaTime);
        }
       
        bool isOnGround() {
                RaycastHit hit;
                Physics.Raycast(transform.position, -Vector3.up, out hit);
        if (hit.distance < 1.0f) {
                        return true;
                }
        return false;
        }
       
        void Update() {
                PlayerMoveUpdate();
        }
}
 


пробовал как на OnCollisionEnter так и на Raycast в обоих случаях ящик прыгает по поверхности и нет однозначного результата, что он стоит на земле или в воздухе. На видео пару раз попрыгал и вообще он начал сам прыгать при этом он уже стал неуправляемый.
Мало того, на OnCollisionEnter ещё и движение затруднено. Прыжки вообще через раз срабатывали. Тот вариант что привёл выше это самый лучший что удалось сделать. Ума не приложу, как ещё контроллер нужно правильно кодить. Подскажите пожалуйста.
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22

Re: Управление персонажем в 2D

Сообщение Opts 14 сен 2012, 09:33

как-то совсем всё печально, ну ладно, буду сам ковырять...
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22

Re: Управление персонажем в 2D

Сообщение AndreyMust19 14 сен 2012, 11:31

Могу поделиться с вами своим скриптом.

Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;


public class CharacterPhysics : MonoBehaviour {

        public Transform[] child_pos;
        public GUIText debug_text1;
       
        private CharacterController controller;
        private CapsuleCollider capsule;
        // гравитация
        public bool gravityOn {
                get { return isChar ? _gravityOn : rigidbody.useGravity; }
                set { if (isChar) _gravityOn = value; else rigidbody.useGravity = value; }
        }
        private bool _gravityOn = true;
        public float gravity = 9.81f;   // искусственная гравитация
        // физика
        private CollisionFlags flags;
        public bool grounded, upcollide, colside;
        public bool i_stick;
       
        // контроллер
        private bool isChar;    // используется CharacterController иначе - Rigidbody 
        public bool IsChar {
                get { return isChar; }
        }
        public float cRadius {
                get { return isChar ? controller.radius : capsule.radius; }
        }
        public float cHeight {
                get { return isChar ? controller.height : capsule.height; }
                set { if (isChar) controller.height = value; else capsule.height = value; }
        }
        public Vector3 cCenter {
                get { return isChar ? controller.center : capsule.center; }
                set { if (isChar) controller.center = value; else capsule.center = value; }
        }
        public float cCenterY {
                get { return (isChar ? controller.center.y : capsule.center.y); }
                set {
                        if (isChar) controller.center = new Vector3(controller.center.x, value, controller.center.z);
                        else capsule.center = new Vector3(capsule.center.x, value, capsule.center.z);
                }
        }
       
        // состояние персонажа
        public Vector3 velocity {
                get { return isChar ? (side*rightDirection + vert*Vector3.up) : (rigidbody.velocity); }
        }
        public float vert {
                set {
                        if (isChar) _vert = value; else rigidbody.velocity = new Vector3(rigidbody.velocity.x, value, rigidbody.velocity.z);
                }
                get { return isChar ? _vert : rigidbody.velocity.y; }
        }
        public float _vert, side;
        //[NonSerialized]
        public Vector3 rightDirection { // прямое направление движения
                get { return _rightDirection; }
                set { _rightDirection = value.normalized; }
        }
        private Vector3 _rightDirection;
       
        // Запрет перемещения
        private bool freeze;
        public bool IsFreeze {
                get { return freeze; }
        }
       
        public void Freeze()
        {
                freeze = true; Stop();
        }

        public void Unfreeze()
        {
                freeze = false;
        }
       
        public void Stop() {
                if (isChar) { vert = 0.0f; side = 0.0f; } else rigidbody.velocity = Vector3.zero;
        }
       
// = = = = = = = = = = = = = = =
       
        uint i, child_num;
        private Vector3[] child_offset;
       
        void Awake() {
                // начальное положение
                rightDirection = transform.forward;
                CheckIsChar(); // определяем тип контроллера
                //
                if (!isChar) child_num = (uint)child_pos.Length;
                child_offset = new Vector3[child_num];
                for (i = 0; i < child_num; i++) child_offset[i] = child_pos[i].localPosition;
        }

        public void CheckIsChar() {
                controller = GetComponent<CharacterController>();
                capsule = GetComponent<CapsuleCollider>();
                if (controller) { isChar = true; return; };
                if (rigidbody && capsule) { isChar = false; return; }
                Debug.LogError("Object no have CharacterController or Capsule+Rigidbody");
        }
       
        void Start() {
                if (!isChar) rigidbody.freezeRotation = true; // отключаем вращение Rigidbody
        }

        void Update() {
                // тащим детей за собой
                if (!isChar) {
                        for (i = 0; i < child_num; i++) child_pos[i].position = transform.position + child_offset[i];
                }
               
                // ПЕРЕМЕЩЕНИЕ
                if (!freeze && (side != 0.000f || vert != 0.000f)) {
                        if (isChar)
                        // с контроллером
                        {
                                Vector3 transit = Vector3.zero;
                                if (side != 0.0f) transit += rightDirection * side * Time.deltaTime;
                                if (vert != 0.0f) transit += transform.up * vert * Time.deltaTime;
                                controller.Move(transit);
                        }
                        // без контроллера
                        else {
                                if (!i_stick) rigidbody.velocity = side*rightDirection+Vector3.up*vert;
                                else rigidbody.velocity = Vector3.up*vert; // если застряли, то не двигаемся по горизонтали
                        }
                }
               
                // применяем гравитацию
                if (isChar && gravityOn) {
                        if (!grounded) vert -= gravity * Time.deltaTime;
                        //else vert = -gravity * Time.deltaTime; // прижимаем к земле, чтобы поднялся флаг isGrounded
                        else vert = -0.05f;
                }
               
                if (isChar) {
                        flags = controller.collisionFlags;
                        grounded = controller.isGrounded;
                        upcollide = (flags & CollisionFlags.Above) != 0;
                        colside = (flags & CollisionFlags.Sides) != 0;
                }
               
                // отладочная информация
                if (debug_text1 != null) debug_text1.text = DebugText(false);
        }

        Vector3 collideDirection;
        float vel_magnitude;
       
        void OnCollisionEnter(Collision other) {
                if (isChar) return;
                collideDirection = other.contacts[0].normal;
                if (Vector3.Project(Vector3.up, collideDirection).magnitude >= 0.975) { grounded = true; i_stick = false; }
                //upcollide = (Vector3.Project(-Vector3.up, other.contacts[0].normal).magnitude >= 0.85);
                if (Vector3.Project(transform.forward, collideDirection).magnitude >= 0.85) colside = true;
        }
       
        void OnCollisionExit(Collision other) {
                if (isChar) return;
                collideDirection = other.contacts[0].normal;
                if (Vector3.Project(Vector3.up, collideDirection).magnitude >= 0.975) grounded = false;
                //upcollide = (Vector3.Project(-Vector3.up, other.contacts[0].normal).magnitude >= 0.85);
                if (Vector3.Project(transform.forward, collideDirection).magnitude >= 0.85) colside = false;
        }

        void OnCollisionStay(Collision other) {
                //Debug.Log("Contacts = "+other.contacts.Length);
                if (other.contacts.Length == 0) return;
                collideDirection = other.contacts[0].normal;
                if (!grounded && Vector3.Project(transform.forward, collideDirection).magnitude >= 0.85) {
                        transform.position += collideDirection * Mathf.Clamp(0.5f*other.relativeVelocity.magnitude*Time.fixedDeltaTime, 0.05f, 0.5f);
                        i_stick = true; // застряли
                        Debug.Log("OnCollisionStay_colside");
                }
        }
// -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -  

        // вывод на экран инфы о состоянии персонажа
        string DebugText(bool inline)
        {
                char endl = inline ? ' ' : '\n';
                string text = "";
                text += (grounded ? "ground" : "inair");
                text += endl+"gravity: "+(gravityOn ? "ON" : "OFF");
                text += endl+"velocity = "+(isChar ? controller.velocity : rigidbody.velocity);
                return text;
        }

        public void OnDrawGizmos() {
                Gizmos.color = Color.red;
                Gizmos.DrawLine(transform.position, transform.position+rightDirection);
        }


/* Расширение редактора - смена CC на Rigidbody+Capsule и наоборот */
       
        // непереносимые параметры
        private float oldCCSkin, oldSOffset;
        // переносимые параметры
        float _radius, _height; Vector3 _center;
       
        [ExecuteInEditMode]
        [ContextMenu ("Switch CC <-> Rigidbody")]
        public void SwitchCharMode()
        {
                CheckIsChar();
                if (!isChar) { // CC
                        _radius = capsule.radius; _height = capsule.height; _center = capsule.center;
                        controller = gameObject.AddComponent<CharacterController>();
                        DestroyImmediate (capsule); capsule = null;
                        DestroyImmediate (rigidbody);
                        controller.radius = _radius; controller.height = _height; controller.center = _center;
                }
                else { // Rigidbody
                        _radius = controller.radius; _height = controller.height; _center = controller.center;
                        DestroyImmediate (controller); controller = null;
                        capsule = gameObject.AddComponent<CapsuleCollider>();
                        gameObject.AddComponent<Rigidbody>();
                        capsule.radius = _radius; capsule.height = _height; capsule.center = _center;
                }
                CheckIsChar();
        }

}


Он работает как с CharacterController, так и с CapsuleCollider+Rigidbody.
Единственное отличие - второй вариант может передвигаться по движ. платформам, но иногда при сильном прыжке все-таки застревает в препятствиях.
Что вам надо знать:
rightDirection - прямое направление движения, куда перс будет двигаться при положительном значении side. Нужно установить этот вектор в в инспекторе или в Awake другим скриптом.
vert, side - вертикальная и горизонтальная скорость движения. Меняйте их другим скриптом для управления движением персонажем.
velocity - текущая скорость движения. Предназначено для чтения
gravityOn - вкл/выкл. гравитацию.
Freeze(), Unfreeze() - отключает работу контроллера и включает обратно.
В контекстном меню (шестеренка в строке имени компонента) есть пункт "Switch CC <-> Rigidbody", к-й худо-бедно заменяет CC на Rididbody и Capsule Collider с теми же параметрами и обратно.

Кстати, при работе с CharacterController заметил такую особенность. Если внутри OnControllerColliderHit двигать коллайдер через Move, то игра зависает в бесконечном цикле и падает от переполнения стека. Поэтому так делать нельзя.
Нужна помощь? Сами, сами, сами, сами, сами... делаем все сами
AndreyMust19
Адепт
 
Сообщения: 1119
Зарегистрирован: 07 июн 2011, 13:19

Re: Управление персонажем в 2D

Сообщение Opts 14 сен 2012, 12:45

ОООООО, большущее спасибо!!! Я уже почти неделю маюсь с управлением персонажа, и тут такая благодать. Буду подключать и разбираться, ещё раз спасибо.
Opts
UNIт
 
Сообщения: 64
Зарегистрирован: 12 сен 2012, 15:22


Вернуться в Общие вопросы

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 15