Kịch bản thân thiện với người dùng khi sử dụng ECS?


8

Tôi hiện đang tạo một dự án sở thích nhỏ để quay lại phát triển trò chơi và tôi đã quyết định cấu trúc các thực thể của mình bằng ECS ​​(Hệ thống thành phần thực thể). Việc triển khai ECS này có cấu trúc như vậy:

  • Thực thể : Trong trường hợp của tôi, đó là một mã intđịnh danh duy nhất được sử dụng làm chìa khóa cho danh sách các thành phần.
  • Thành phần : Chỉ giữ dữ liệu, ví dụ: Positionthành phần giữ một xytọa độ, và Movementthành phần giữ một speeddirectionbiến.
  • Hệ thống : Xử lý các thành phần, ví dụ: nó lấy PositionMovementcác thành phần và thêm speeddirectionvào vị trí xytọa độ.

Điều này hoạt động tốt, nhưng bây giờ tôi muốn triển khai kịch bản vào các trò chơi của mình, dưới dạng ngôn ngữ kịch bản. Trong các dự án trước đây, tôi đã sử dụng triển khai OOP cho các đối tượng trò chơi, điều đó có nghĩa là kịch bản chuyển tiếp khá đơn giản. Ví dụ, một tập lệnh đơn giản có thể trông giống như thế này:

function start()
    local future = entity:moveTo(pos1)
    wait(future)

    local response = entity:showDialog(dialog1)
    if wait(response) == 1 then
        local itemStack = entity:getInventory():removeItemByName("apple", 1)
        world:getPlayer():getInventory():addItemStack(itemStack)
    else
        entity:setBehavior(world:getPlayer(), BEHAVIOR_HOSTILE)
    end
end

Tuy nhiên, khi sử dụng một ECS, thực thể chính nó không có bất kỳ chức năng như moveTohoặc getInventory, thay vào đó là kịch bản trên được viết theo phong cách ECS sẽ giống như thế này:

 function start()
    local movement = world:getComponent(MOVEMENT, entity)
    movement:moveTo(pos1)

    local position = world:getComponent(POSITION, entity)
    local future = Future:untilEquals(position.pos, pos1)
    wait(future)

    local dialogComp = world:getComponent(DIALOG, entity)
    local response = dialogComp:showDialog(dialog1)

    if wait(response) == 1 then
        local entityInventory = world:getComponent(INVENTORY, entity)
        local playerInventory = world:getComponent(INVENTORY, world:getPlayer())
        local itemStack = entityInventory:removeItemByName("apple", 1)
        playerInventory:addItemStack(itemStack)
    else
        local entityBehavior = world:getComponent(BEHAVIOR, entity)
        local playerBehavior = world:getComponent(BEHAVIOR, world:getPlayer())
        entityBehavior:set(playerBehavior, BEHAVIOR_HOSTILE)
    end
end

Đây là rất nhiều tiết hơn so với phiên bản OOP, mà không phải là mong muốn khi kịch bản được nhằm hướng tới chủ yếu không phải lập trình (cầu thủ của trò chơi).

Một giải pháp sẽ là có một số loại đối tượng trình bao bọc đóng gói Entityvà cung cấp các chức năng như moveTotrực tiếp và xử lý phần còn lại bên trong, mặc dù giải pháp đó có vẻ không tối ưu vì phải mất rất nhiều công việc để bao gồm tất cả các thành phần và mọi khi một thành phần mới được thêm vào, bạn sẽ cần thay đổi đối tượng trình bao bọc với các hàm mới.

Đối với tất cả các nhà phát triển trò chơi đã triển khai kịch bản trong ECS ​​trước đây - bạn đã làm như thế nào? Trọng tâm chính ở đây là khả năng sử dụng cho người dùng cuối, với chi phí "bảo trì" càng ít càng tốt (tốt nhất là bạn không cần thay đổi nó mỗi khi bạn thêm một thành phần).


Tôi sẽ viết nhận xét này dưới dạng nhận xét vì tôi hơi mơ hồ về cách triển khai chính xác của bạn (tôi giả sử C ++). Bạn không thể sử dụng các mẫu ở đâu đó ở đây? Để áp dụng thành phần X chống lại thành phần Y? Tôi tưởng tượng rằng các thành phần sẽ cần ghi đè lên phương thức "áp dụng" cơ bản và chuyên môn hóa nó cho các loại thành phần có thể được áp dụng cho nó. Dựa vào SFINAE sẽ đảm bảo rằng nó hoạt động khi có ý nghĩa. Hoặc nó có thể được chuyên môn hóa trong các Systemlớp để cho phép các thành phần duy trì cấu trúc dữ liệu.
NeomerArcana

Tại sao không hiển thị moveTophương thức như một phần của hệ thống cơ bản trong trường hợp sử dụng của bạn, ví dụ MovementSystem? Cách này không chỉ sau đó bạn có thể sử dụng nó trong các tập lệnh bạn viết mà sau đó bạn cũng có thể sử dụng nó như một phần của mã C ++, nơi bạn cần nó. Vì vậy, có, bạn sẽ phải đưa ra các phương thức mới khi các hệ thống mới được thêm vào, nhưng đó là điều được mong đợi vì hành vi hoàn toàn mới của nó mà các hệ thống này vẫn giới thiệu.
Naros

Tôi chưa có cơ hội để làm điều đó, nhưng liệu có khả thi khi chỉ thêm "phím tắt" cho những thao tác được thực hiện phổ biến hơn không?
Vaillancourt

Câu trả lời:


1

Bạn có thể tạo một hệ thống ScriptExecutSystem hoạt động trên tất cả các thực thể có thành phần Script. Nó có được tất cả các thành phần của thực thể có thể hữu ích để hiển thị cho hệ thống kịch bản lệnh và chuyển chúng đến chức năng kịch bản.

Một cách tiếp cận khác là khiến người dùng của bạn cũng nắm lấy ECS và cho phép họ xác định các thành phần của riêng họ và thực hiện các hệ thống của riêng họ bằng ngôn ngữ kịch bản.


0

ECS có ưu và nhược điểm của nó. Kịch bản thân thiện với người dùng không phải là một trong những ưu điểm của nó.

Vấn đề ECS giải quyết là khả năng có một số lượng lớn những thứ tương tự trong trò chơi của bạn cùng một lúc trong khi vẫn giữ được hiệu suất. Nhưng giải pháp này có chi phí - chi phí của một kiến ​​trúc dễ sử dụng. Nó không phải là kiến ​​trúc tốt nhất cho mọi trò chơi.

Ví dụ, ECS sẽ là một lựa chọn tốt cho Kẻ xâm lược không gian , nhưng không nhiều cho PacMan .

Vì vậy, không chính xác câu trả lời bạn đang tìm kiếm, nhưng có thể ECS không phải là công cụ phù hợp cho công việc của bạn.

Nếu bạn thêm một trình bao bọc, xem chi phí trên không. Nếu bạn kết thúc loại bỏ tăng hiệu suất của ECS trong trình bao bọc của bạn, thì bạn có điều tồi tệ nhất của cả hai thế giới.


Nhưng để trả lời trực tiếp câu hỏi của bạn - "Gửi tới tất cả các nhà phát triển trò chơi đã triển khai kịch bản trong ECS ​​trước đây - bạn đã làm như thế nào?"

Khá chính xác như bạn đang làm nó, mà không cần một trình bao bọc. Các thực thể không có gì ngoài một định danh. Các thành phần không có gì ngoài dữ liệu. Hệ thống không có gì ngoài logic. Các hệ thống chấp nhận các thực thể với các thành phần cần thiết chạy qua. Thêm hệ thống, thực thể và các thành phần một cách tự do.

Tôi đã từng sử dụng một khung với một khía cạnh thứ tư, được gọi là bảng đen. Về cơ bản, đó là cách để các hệ thống giao tiếp với nhau. Nó tạo ra nhiều vấn đề hơn nó giải quyết.


Liên quan: Tôi có nên triển khai Hệ thống thành phần thực thể trong tất cả các dự án của mình không?


0

Với ECS, bạn có thể phân chia thành một trách nhiệm duy nhất, do đó, bất kỳ thực thể nào di chuyển đều muốn có hai thành phần dữ liệu: MoveComponent và MoveSpeedComponent.

using System;
using Unity.Entities;

[Serializable]
public struct MoveForward : IComponentData{}
////////////////////////////////////////////
using System;
using Unity.Entities;

[Serializable]
public struct MoveSpeed : IComponentData
{
public float Value;
}
///////////////////////////////////////////

vì vậy bây giờ trong chuyển đổi của bạn, bạn thêm các thành phần đó vào thực thể của mình

public class MoveForwardConversion : MonoBehaviour, IConvertGameObjectToEntity
{
public float speed = 50f;

public void Convert(Entity entity, EntityManager manager,       GameObjectConversionSystem conversionSystem)
{
    manager.AddComponent(entity, typeof(MoveForward));

    MoveSpeed moveSpeed = new MoveSpeed { Value = speed };
    manager.AddComponentData(entity, moveSpeed);     
}

Bây giờ chúng tôi có chuyển đổi và dữ liệu chúng tôi có thể chuyển sang hệ thống, tôi đã xóa hệ thống đầu vào để dễ đọc nhưng nếu bạn muốn tìm hiểu thêm về hệ thống đầu vào, tôi sẽ có tất cả được liệt kê trong bài viết của tôi vào tuần tới về kết nối thống nhất.

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

namespace Unity.Transforms
{
public class MoveForwardSystem : JobComponentSystem
{
    [BurstCompile]
    [RequireComponentTag(typeof(MoveForward))]
    struct MoveForwardRotation : IJobForEach<Translation, MoveSpeed>
    {
        public float dt;

        public void Execute(ref Translation pos, [ReadOnly] ref MoveSpeed speed)
        {
            pos.Value = pos.Value + (dt * speed.Value);            
           // pos.Value.z += playerInput.Horizontal;
        }
    }
}

lưu ý rằng lớp trên đang sử dụng Unity.Mathmatics. Điều này thật tuyệt vời khi có thể sử dụng các hàm toán học khác nhau mà bạn đã quen làm việc với các hệ thống bình thường. Với tất cả những gì phù hợp, bây giờ bạn có thể làm việc với hành vi của các thực thể - một lần nữa tôi đã xóa đầu vào ở đây nhưng điều này được giải thích tốt hơn nhiều trong bài viết.

using Unity.Entities;
using UnityEngine;

public class EntityBehaviour : MonoBehaviour, IConvertGameObjectToEntity
{
public float speed = 22f;

void Update()
{
    Vector3 movement = transform.forward * speed * Time.deltaTime;
}
public void Convert(Entity entity, EntityManager manager, GameObjectConversionSystem conversionSystem)
{   
    //set speed of the entity
    MoveSpeed moveSpeed = new MoveSpeed { Value = speed };
    //take horizontal inputs for entites
    //PlayerInput horizontalinput = new PlayerInput { Horizontal = Input.GetAxis("Horizontal") };

    //add move component to entity
    manager.AddComponent(entity, typeof(MoveForward));
    //add component data  
    manager.AddComponentData(entity, moveSpeed);
   // manager.AddComponentData(entity, horizontalinput);
}
}

Bây giờ bạn có thể giới thiệu các thực thể sẽ tiến về phía trước với tốc độ.

Nhưng điều này cũng sẽ di chuyển mọi thực thể có hành vi này để bạn có thể giới thiệu các thẻ, ví dụ nếu bạn đã thêm PlayerTag, thì chỉ có thực thể với playerTag IComponentData mới có thể thực hiện MoveForward nếu tôi chỉ muốn di chuyển trình phát như ví dụ phía dưới.

Tôi sẽ đi sâu hơn vào điều đó cũng trong bài viết nhưng có vẻ như thế này trong một Hệ thống thành phần điển hình

    Entities.WithAll<PlayerTag>().ForEach((ref Translation pos) =>
    {
        pos = new Translation { Value =  /*PlayerPosition*/ };
    });

Phần lớn điều này được giải thích khá tốt trong bài thuyết trình Angry Dots với Mike Geig, nếu bạn chưa thấy thì tôi khuyên bạn nên kiểm tra nó. Tôi cũng sẽ chỉ đến bài viết của tôi sau khi nó lên. Nên thực sự hữu ích để có được một vài trong số những điều bạn đã quen làm việc với cách làm việc như bạn muốn trong ECS.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.