Управление космическим кораблем [3D]

На нашем ресурсе можно найти множество версий, скриптов управления персонажем, теперь, настала очередь и космических кораблей. Помимо просто управления движением космического аппарата, мы решили пойти дальше и добавить ряд полезных, так сказать, мелочей. Реализована функция стрельбы, физическими объектами, префабами, иначе говоря. Научили наш аппарат вращаться вокруг своей оси, влево или вправо, чтобы крутить различные финты. Есть возможность ускоряться, при условии, если одновременно удерживать «левый шифт и вперед». Если камера закреплена чуть выше, над кораблем, то будут ситуации, что корабль вверх тормашками, поэтому, добавлена функция выравнивания камеры и самого корабля по горизонтали.


Для начала нужно создать подготовить стрельбу и повреждения.

Ловить дамаг будет это маленький скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Target : MonoBehaviour {

    public void DoAction(float damage)
    {
        Debug.Log(this + " --> Damage: " + damage);
    }
}
Вешаем его на цель для нашего космического истребителя.

Теперь, создадим лазерный луч, или снаряд:

Управление космическим кораблем [3D]

Это может быть обычный цилиндр, выравненный по оси Z.

На снаряд цепляем скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour {

    public float damage = 15;
    public LayerMask ignoreMask;

    void Start()
    {
        Destroy(gameObject, 5);
    }

    bool IsIgnore(GameObject obj)
    {
        if (((1 << obj.layer) & ignoreMask) != 0)
        {
            return true;
        }

        return false;
    }

    void OnTriggerEnter(Collider coll)
    {
        if (!coll.isTrigger && !IsIgnore(coll.gameObject))
        {
            Target target = coll.GetComponent<Target>();
            if (target != null) target.DoAction(damage);
            Destroy(gameObject);
        }
    }
}
Не забываем, что на снаряде, кроме скрипта должен быть коллайдер в режиме триггера и Rigidbody (с отключенной гравитацией само собой).

И наш звездолет, со следующей иерархией:


Значит, на скриншоте мы видим, родителя SpaceShip, к которому прикреплен скрипт управления и Rigidbody. Дочерние объекты: Helper пустой объект, вспомогательный; Body пустой объект, содержащий модель model и точки откуда вылетают снаряды shotPoint (1) и shotPoint (2).

Модель может быть любая, но иерархия должна быть сохранена (более подробной см. в файлах проекта).

Скрипт управления кораблем:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class Spaceship : MonoBehaviour {

    [Header("Настройки управления:")]
    public float speed = 100; // скорость корабля (стартовое ускорение силы)
    public float speedMax = 200; // максимальная скорость движения
    public float acceleration = 3; // умножение (ускорение) силы
    public float shipRotationSpeed = 5; // вращение влево и вправо
    public float mouseSensitivity = 2; // чувствительность мышки
    public float cameraDistance = 10; // удаление камеры от корабля
    public float cameraHeight = 5; // высота камеры относительно корабля
    public Camera mainCamera;
    public float dampingCameraSpeed = 10; // сглаживание движения камеры
    public float dampingCameraRotation = 15f; // сглаживание вращения камеры
    public float cameraFrameCounter = 20; // сглаживание движения мыши
    public RectTransform weaponAim, cursor;
    public Transform spaceshipBody, helper;

    [Header("Настройки oружия:")]
    public Rigidbody bulletPrefab; // если префаба пули нет, то оружие будет стрелять рейкастом
    public float bulletSpeed = 300; // скорость префаба пули
    public Transform[] shotPoint; // точка оружия, откуда должны вылетать пули
    public float fireRate = 1; // скорострельность
    public int bulletCount = 500; // объем магазина
    public float reloadTime = 2.5f; // время перезарядки в секундах

    private Rigidbody body;
    private Vector3 position, direction;
    private float currentSpeed, currentSpeedMax, magnitude, rotY, rotX, rotAverageX, rotAverageY, h, v, timeout, reloadTimeout, shotDistance, invertAngle, invertAngleCam, invertY;
    private List<float> rotArrayX = new List<float>();
    private List<float> rotArrayY = new List<float>();
    private Quaternion xQuaternion, yQuaternion, quaternion, camQuaternion;
    private int layerMask, bulletCountInt, gunIndex;
    private bool reload, isFlip;

    void Start ()
    {
        body = GetComponent<Rigidbody>();
        body.useGravity = false;
        body.drag = 1;
        body.angularDrag = 1;
        layerMask = 1 << gameObject.layer | 1 << 2;
        layerMask = ~layerMask;
        bulletCountInt = bulletCount;
        camQuaternion = Quaternion.Euler(Vector3.forward * .1f);
        helper.localPosition = Vector3.zero;
        helper.localRotation = Quaternion.identity;
        invertY = 1;
        Cursor.lockState = CursorLockMode.Confined;
    }

    void FixedUpdate()
    {
        magnitude = body.velocity.magnitude;

        if (magnitude > currentSpeedMax)
        {
            currentSpeed = -(magnitude - currentSpeedMax);
        }

        MyUpdate();

        body.AddForce(transform.forward * v * currentSpeed, ForceMode.Acceleration);
    }

    Vector3 PositionCorrection(Vector3 target, Vector3 position) // коррекция позиции, если между камерой и кораблем, преграда
    {
        RaycastHit hit;
        Debug.DrawLine(target, position, Color.yellow);
        if (Physics.Linecast(target, position, out hit, layerMask))
        {
            float tempDistance = Vector3.Distance(target, hit.point);
            Vector3 pos = target + (quaternion * Vector3.back * tempDistance);
            position = pos + hit.normal * .1f;
        }
        return position;
    }

    void MyUpdate ()
    {
        if (Input.GetKeyDown(KeyCode.Z))
        {
            GetFlip();
        }

        shotDistance = 1000;

        RaycastHit hit;
        Ray ray = new Ray(transform.position, transform.forward);

        if (Physics.Raycast(ray, out hit, 1000, layerMask))
        {
            shotDistance = (hit.distance < cameraDistance * 2) ? 1000 : hit.distance;
        }

        h = Input.GetAxis("Horizontal");
        v = Input.GetAxis("Vertical");

        if (Input.GetKey(KeyCode.LeftShift) && v > 0)
        {
            currentSpeed = speed * acceleration;
            currentSpeedMax = speedMax * acceleration;
        }
        else
        {
            currentSpeed = (v < 0) ? speed / 2 : speed;
            currentSpeedMax = (v < 0) ? speedMax / 2 : speedMax;
        }

        rotAverageY = 0;
        rotAverageX = 0;

        rotX += Input.GetAxis("Mouse X") * mouseSensitivity * Mathf.Sign(transform.up.y);
        rotY += Input.GetAxis("Mouse Y") * mouseSensitivity * invertY;
        
        rotArrayY.Add(rotY);
        rotArrayX.Add(rotX);

        if (rotArrayY.Count >= cameraFrameCounter)
        {
            rotArrayY.RemoveAt(0);
        }
        if (rotArrayX.Count >= cameraFrameCounter)
        {
            rotArrayX.RemoveAt(0);
        }

        for (int j = 0; j < rotArrayY.Count; j++)
        {
            rotAverageY += rotArrayY[j];
        }
        for (int i = 0; i < rotArrayX.Count; i++)
        {
            rotAverageX += rotArrayX[i];
        }

        rotAverageY /= rotArrayY.Count;
        rotAverageX /= rotArrayX.Count;

        yQuaternion = Quaternion.AngleAxis(rotAverageY, Vector3.left);
        xQuaternion = Quaternion.AngleAxis(rotAverageX, Vector3.up);

        quaternion = xQuaternion * yQuaternion;

        position = transform.position + quaternion * Vector3.back * cameraDistance;
        position += transform.up * cameraHeight;
        position = PositionCorrection(transform.position, position);
       
        cursor.position = (Vector2)mainCamera.WorldToScreenPoint((transform.position + quaternion * Vector3.forward * cameraDistance) + transform.up * cameraHeight);
        weaponAim.position = (Vector2)mainCamera.WorldToScreenPoint((transform.position + transform.forward * shotDistance) + transform.up * cameraHeight);
        mainCamera.transform.position = Vector3.Lerp(mainCamera.transform.position, position, dampingCameraSpeed * Time.fixedDeltaTime);
        mainCamera.transform.rotation = Quaternion.Lerp(mainCamera.transform.rotation, quaternion * camQuaternion, dampingCameraRotation * Time.fixedDeltaTime);
        body.MoveRotation(mainCamera.transform.rotation);
        if (!isFlip) spaceshipBody.Rotate(Vector3.forward * h * -shipRotationSpeed);
    }

    void GetFlip()
    {
        invertAngle = (spaceshipBody.localEulerAngles.z < 180) ? 0 : 360;

        if (Mathf.Sign(helper.up.y) < 0)
        {
            invertAngleCam = (invertAngleCam > 0) ? 0 : 180;
        }

        invertY *= Mathf.Sign(helper.up.y);
        StartCoroutine(Flip());
        isFlip = true;
    }

    float Lerp(float from, float to, float t)
    {
        t = Mathf.Clamp01(t);
        return from + ((to - from) * t);
    }

    IEnumerator Flip() // разворот камеры и/или модели
    {
        while (true)
        {
            yield return null;

            bool reset = true;

            if (spaceshipBody.localEulerAngles.z > 1)
            {
                reset = false;
                float angle = Lerp(spaceshipBody.localEulerAngles.z, invertAngle, shipRotationSpeed * Time.fixedDeltaTime);
                spaceshipBody.localEulerAngles = new Vector3(0, 0, angle);
                if (spaceshipBody.localEulerAngles.z < 1) spaceshipBody.localEulerAngles = Vector3.zero;
            }

            if (camQuaternion.eulerAngles.z != invertAngleCam)
            {
                reset = false;
                camQuaternion = Quaternion.RotateTowards(camQuaternion, Quaternion.Euler(Vector3.forward * invertAngleCam), dampingCameraRotation * Time.fixedDeltaTime * 10);
            }

            if (reset)
            {
                spaceshipBody.localEulerAngles = Vector3.zero;
                isFlip = false;
                break;
            }
        }
    }

    IEnumerator WeaponReload()
    {
        reloadTimeout = 0;

        while (true)
        {
            yield return null;

            reloadTimeout += Time.deltaTime;

            if (reloadTimeout > reloadTime)
            {
                bulletCountInt = bulletCount;
                reload = false;
                break;
            }
        }
    }

    void GetReload()
    {
        bulletCountInt = 0;
        reload = true;
        StartCoroutine(WeaponReload());
    }

    void LateUpdate()
    {
        if (reload) return;

        if (Input.GetMouseButton(0)) // выстрел ПКМ
        {
            if (bulletCountInt <= 0)
            {
                GetReload();
                return;
            }

            timeout += Time.deltaTime;
            if (timeout > (60f / fireRate) / 100f)
            {
                timeout = 0;
                Shot();
            }
        }
        else
        {
            timeout += Time.deltaTime;
            if (timeout > fireRate)
            {
                timeout = Mathf.Infinity;
            }
        }

        if (Input.GetKeyDown(KeyCode.R)) // перезарядка "R"
        {
            if (bulletCountInt != bulletCount) GetReload();
        }
    }

    void BulletShot(Transform point)
    {
        Rigidbody clone = Instantiate(bulletPrefab, point.position, Quaternion.identity) as Rigidbody;
        clone.velocity = direction.normalized * (bulletSpeed + body.velocity.magnitude);
        clone.transform.forward = direction;
    }

    void Shot()
    {
        if (gunIndex > shotPoint.Length - 1) gunIndex = 0;
        direction = (transform.position + transform.forward * shotDistance) - shotPoint[gunIndex].position;
        BulletShot(shotPoint[gunIndex]);

        gunIndex++;
        bulletCountInt--;
    }
}
Сразу отметим, что скрипт долек от симулятора и находится на стадии беты, но тем не менее, он вполне рабочий и подойдет, чтобы начать разработку какой-нибудь аркады на космо тематику.

Скачать демо проект:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 2018.2.2

Комментариев 4

Офлайн
Stas Artemev 13 августа 2018
Как сделать управление как в этой игре ? Bouncing Over It with friends
https://store.steampowered.com/app/896880/Bouncing_Over_It_with_friends/
Офлайн
Stas Artemev 13 августа 2018
Stas Artemev,
Т.е что бы мышка взаимодействовала с шариком как в игре.
Офлайн
Light 13 августа 2018
Stas Artemev, не играл, поэтому не знаю, что там за управление.
Офлайн
Falkonio 14 августа 2018
Stas Artemev,
На шарике гравитация. Дальше ловить координаты клика мышкой. И по вектору от клика к объекту прикладывать импульс на объект - он будет как-бы отталкиваться от места клика. Можно в зависимости от расстояния ещё менять силу.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Дешевый хостинг
  • Яндекс.Метрика