Làm cách nào để triển khai các tính năng trong một hệ thống thực thể?


31

Sau khi hỏi hai câu hỏi về hệ thống thực thể ( 1 , 2 ) và đọc một số bài viết về chúng, tôi nghĩ rằng tôi hiểu chúng hơn nhiều so với trước đây. Tôi vẫn còn một số điểm không chắc chắn, chủ yếu là về việc xây dựng bộ phát hạt, hệ thống đầu vào và máy ảnh. Tôi rõ ràng vẫn còn một số vấn đề trong việc hiểu các hệ thống thực thể và chúng có thể áp dụng cho toàn bộ các đối tượng khác, nhưng tôi đã chọn ba đối tượng này vì chúng là các khái niệm rất khác nhau, nên bao quát một nền tảng khá rộng và giúp tôi hiểu các hệ thống thực thể và cách xử lý các vấn đề như thế này, bản thân tôi, khi họ đi cùng.

Tôi đang xây dựng một công cụ bằng JavaScript và tôi đã triển khai hầu hết các tính năng cốt lõi, bao gồm: xử lý đầu vào, hệ thống hoạt hình linh hoạt, bộ phát hạt, các lớp và chức năng toán học, xử lý cảnh, máy ảnh và kết xuất và toàn bộ những thứ khác mà động cơ thường hỗ trợ. Tôi đọc câu trả lời của Byte56, điều đó khiến tôi hứng thú với việc biến động cơ thành một hệ thống thực thể. Nó vẫn sẽ là một công cụ trò chơi HTML5, với triết lý cảnh cơ bản, nhưng nó sẽ hỗ trợ việc tạo các thực thể động từ các thành phần.


Vấn đề tôi có, bây giờ, là phù hợp với khái niệm động cơ cũ của tôi vào mô hình lập trình mới này. Đây là một số định nghĩa từ các câu hỏi trước, được cập nhật:

  • Một thực thể là một định danh. Nó không có bất kỳ dữ liệu nào, nó không phải là một đối tượng, đó là một id đơn giản đại diện cho một chỉ mục trong danh sách cảnh của tất cả các thực thể (mà tôi thực sự có kế hoạch thực hiện như một ma trận thành phần).

  • Một phần là người nắm giữ dữ liệu, nhưng với phương pháp có thể hoạt động trên dữ liệu đó. Ví dụ tốt nhất là một Vector2Dthành phần hoặc "Vị trí". Nó có dữ liệu: xy, nhưng cũng có một số phương pháp mà làm cho hoạt động trên dữ liệu một chút dễ dàng hơn: add(), normalize(), và vân vân.

  • Một hệ thống là cái gì đó có thể hoạt động trên một tập hợp các thực thể đáp ứng các yêu cầu nhất định; thông thường các thực thể cần phải có một bộ các thành phần được chỉ định, sẽ được vận hành theo. Hệ thống là phần "logic", phần "thuật toán", tất cả các chức năng được cung cấp bởi các thành phần hoàn toàn để quản lý dữ liệu dễ dàng hơn.


Máy ảnh

Máy ảnh có thuộc tính Vector2Dvị trí, thuộc tính xoay và một số phương pháp để định tâm xung quanh một điểm. Mỗi khung hình, nó được đưa đến một trình kết xuất, cùng với một cảnh và tất cả các đối tượng được dịch theo vị trí của nó. Cảnh sau đó được kết xuất.

Làm thế nào tôi có thể đại diện cho loại đối tượng này trong một hệ thống thực thể? Máy ảnh sẽ là một thực thể, một thành phần hoặc kết hợp (theo câu trả lời của tôi )?

Phát xạ hạt

Vấn đề tôi gặp phải với bộ phát hạt của mình là, một lần nữa, cái gì sẽ là cái gì. Tôi khá chắc chắn rằng bản thân các hạt không nên là thực thể, vì tôi muốn hỗ trợ hơn 10.000 trong số chúng và tôi tin rằng việc tạo ra nhiều thực thể đó sẽ là một đòn nặng nề đối với hiệu suất của tôi.

Làm thế nào tôi có thể đại diện cho loại đối tượng này trong một hệ thống thực thể?

Quản lý đầu vào

Điều cuối cùng tôi muốn nói là cách xử lý đầu vào. Trong phiên bản hiện tại của động cơ, có một lớp được gọi Input. Nó là một trình xử lý đăng ký các sự kiện của trình duyệt, chẳng hạn như nhấn phím và thay đổi vị trí chuột và cũng duy trì trạng thái bên trong. Sau đó, lớp trình phát có một react()phương thức, chấp nhận một đối tượng đầu vào làm đối số. Ưu điểm của việc này là đối tượng đầu vào có thể được tuần tự hóa thành .JSON, sau đó được chia sẻ qua mạng, cho phép mô phỏng nhiều người chơi mượt mà.

Làm thế nào điều này dịch vào một hệ thống thực thể?

Câu trả lời:


26
  • Camera: Làm cho thành phần này sẽ khá gọn gàng. Nó sẽ chỉ có mộtisRenderingcờ và phạm vi độ sâu như Sean nói. Ngoài "trường nhìn" (tôi đoán bạn có thể gọi nó là tỷ lệ trong 2D?) Và một vùng đầu ra. Vùng đầu ra có thể xác định phần cửa sổ trò chơi mà camera này được hiển thị. Nó sẽ không có vị trí / vòng quay riêng biệt như bạn đề cập. Thực thể bạn tạo có thành phần camera sẽ sử dụng các thành phần vị trí và xoay của thực thể đó. Sau đó, bạn sẽ có một hệ thống camera tìm kiếm các thực thể có các thành phần camera, vị trí và xoay. Hệ thống lấy thực thể đó và vẽ tất cả các thực thể mà nó có thể "nhìn thấy" từ vị trí, góc quay, độ sâu và trường nhìn của nó, đến phần được chỉ định của màn hình. Điều đó cung cấp cho bạn rất nhiều tùy chọn để mô phỏng nhiều cổng xem, cửa sổ "xem nhân vật", nhiều người chơi cục bộ,

  • Hạt phát ra: Điều này cũng chỉ nên là một thành phần. Hệ thống hạt sẽ tìm kiếm các thực thể có vị trí, góc quay và bộ phát hạt. Bộ phát có tất cả các thuộc tính cần thiết để tái tạo bộ phát hiện tại của bạn, tôi không chắc tất cả những thứ đó là gì, như: tốc độ, vận tốc ban đầu, thời gian phân rã, v.v. Bạn sẽ không phải thực hiện nhiều đường chuyền. Hệ thống hạt biết thực thể nào có thành phần đó. Tôi tưởng tượng bạn có thể sử dụng lại rất nhiều mã hiện có của bạn.

  • Đầu vào: Tôi phải nói rằng việc biến nó thành một thành phần có ý nghĩa nhất được đưa ra cho các đề xuất tôi đưa ra ở trên. Của bạninput systemsẽ được cập nhật mọi khung hình với các sự kiện đầu vào hiện tại. Sau đó, khi nó đi qua tất cả các thực thể có thành phần đầu vào, nó sẽ áp dụng các sự kiện đó. Thành phần đầu vào sẽ có một danh sách các sự kiện bàn phím và chuột tất cả các cuộc gọi lại phương thức liên quan. Tôi không thực sự chắc chắn nơi gọi lại phương thức sẽ sống. Có lẽ một số lớp điều khiển đầu vào? Bất cứ điều gì có ý nghĩa nhất để sửa đổi sau này bởi người dùng động cơ của bạn. Nhưng điều này sẽ cung cấp cho bạn sức mạnh để dễ dàng áp dụng điều khiển đầu vào cho các thực thể máy ảnh, thực thể người chơi hoặc bất cứ điều gì bạn cần. Bạn muốn đồng bộ hóa chuyển động của một loạt các thực thể với bàn phím? Chỉ cần cung cấp cho họ tất cả các thành phần đầu vào đáp ứng cùng một đầu vào và hệ thống đầu vào áp dụng các sự kiện di chuyển đó cho tất cả các thành phần yêu cầu chúng.

Vì vậy, hầu hết những thứ này chỉ nằm trên đỉnh đầu của tôi, nên có lẽ nó sẽ không có ý nghĩa nếu không có lời giải thích thêm. Vì vậy, chỉ cần cho tôi biết những gì bạn không rõ ràng. Về cơ bản, tôi đã cho bạn rất nhiều việc để làm :)


Một câu trả lời tuyệt vời khác! Cảm ơn! Bây giờ, vấn đề duy nhất của tôi là lưu trữ và truy xuất các thực thể một cách nhanh chóng, vì vậy người dùng thực sự có thể thực hiện một vòng lặp / logic trò chơi ... Tôi sẽ cố gắng tự mình tìm ra nó, nhưng trước tiên tôi phải tìm hiểu cách Javascript xử lý các mảng, đối tượng và các giá trị không xác định trong bộ nhớ để đoán chính xác ... Đó sẽ là một vấn đề vì các trình duyệt khác nhau có thể thực hiện nó khác nhau.
jcora

Điều này cảm thấy thuần túy về mặt kiến ​​trúc nhưng làm thế nào để hệ thống kết xuất xác định máy ảnh hoạt động ngắn lặp lại thông qua tất cả các thực thể?
Pace

@Pace Vì tôi muốn tìm thấy camera hoạt động rất nhanh, tôi có thể cho phép hệ thống camera giữ tham chiếu đến các thực thể có camera hoạt động.
MichaelHouse

Nơi nào bạn đặt logic để điều khiển nhiều camera (nhìn, xoay, di chuyển, v.v.)? Làm thế nào để bạn kiểm soát nhiều máy ảnh?
plasmacel

@plasmacel Nếu bạn có nhiều đối tượng chia sẻ điều khiển, trách nhiệm của hệ thống điều khiển của bạn là xác định đối tượng nào nhận đầu vào.
MichaelHouse

13

Đây là cách tôi tiếp cận điều này:

Máy ảnh

Máy ảnh của tôi là một thực thể như mọi thứ khác, có các thành phần kèm theo:

  1. TransformTranslation, RotationScaletính chất, ngoài những thứ khác cho vận tốc, v.v.

  2. Pov(Point of view) đã FieldOfView, AspectRatio, Near, Far, và bất cứ thứ gì cần thiết để tạo ra một ma trận chiếu, ngoài một IsOrtholá cờ sử dụng để chuyển đổi giữa các quan điểm và dự đoán chính tả. Povcũng cung cấp một thuộc tính tải lười biếng ProjectionMatrixđược sử dụng bởi hệ thống kết xuất được tính toán nội bộ khi đọc và lưu vào bộ đệm cho đến khi bất kỳ thuộc tính nào khác được sửa đổi.

Không có hệ thống camera chuyên dụng. Hệ thống kết xuất duy trì một danh sách Povvà chứa logic để xác định cái nào sẽ sử dụng khi kết xuất.

Đầu vào

Một InputReceiverthành phần có thể được gắn vào bất kỳ thực thể. Điều này có một trình xử lý sự kiện đính kèm (hoặc lambda nếu ngôn ngữ của bạn hỗ trợ nó) được sử dụng để giữ xử lý đầu vào cụ thể theo thực thể, lấy tham số cho trạng thái khóa hiện tại và trước đó, vị trí nút hiện tại và trước đó, v.v. (Trên thực tế, có trình xử lý riêng cho chuột và bàn phím).

Ví dụ, trong một trò chơi thử nghiệm giống như tiểu hành tinh mà tôi đã tạo khi làm quen với Thực thể / Thành phần, tôi có hai phương thức lambda đầu vào. Một tay cầm điều hướng tàu bằng cách xử lý các phím mũi tên và thanh không gian (để bắn). Cái còn lại xử lý đầu vào bàn phím chung - các phím để thoát, tạm dừng, v.v., cấp độ khởi động lại, v.v. Tôi tạo hai thành phần, gắn mỗi lambda vào thành phần riêng của nó, sau đó gán thành phần máy thu điều hướng cho thực thể tàu, một thành phần khác cho thực thể bộ xử lý lệnh không nhìn thấy.

Đây là trình xử lý sự kiện để xử lý các khóa được giữ giữa các khung được gắn vào InputReceiverthành phần của tàu (C #):

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

Nếu máy ảnh của bạn là thiết bị di động, hãy cung cấp cho nó thành phần InputReceivervà thiết bị riêng Transform, gắn một lambda hoặc trình xử lý thực hiện bất kỳ loại điều khiển nào bạn muốn và bạn đã hoàn thành.

Đây là một cách gọn gàng ở chỗ bạn có thể di chuyển InputReceiverthành phần với bộ xử lý điều hướng được gắn từ tàu đến một tiểu hành tinh, hoặc bất cứ thứ gì khác cho vấn đề đó, và thay vào đó bay xung quanh nó. Hoặc, bằng cách chỉ định một Povthành phần cho bất kỳ thứ gì khác trong cảnh của bạn - một tiểu hành tinh, đèn đường, v.v. - bạn có thể xem cảnh của mình từ phối cảnh của thực thể đó.

Một InputSystemlớp duy trì trạng thái bên trong cho bàn phím, chuột, v.v. InputSystemlọc bộ sưu tập thực thể bên trong của nó thành các thực thể có một InputReceiverthành phần. Trong Update()phương thức của nó , nó lặp qua bộ sưu tập đó và gọi các trình xử lý đầu vào được gắn vào mỗi thành phần đó theo cùng cách mà hệ thống kết xuất vẽ mỗi thực thể với một Renderablethành phần.

Các hạt

Điều này thực sự phụ thuộc vào cách bạn lên kế hoạch tương tác với các hạt. Nếu bạn chỉ cần một hệ thống hạt hoạt động giống như một đối tượng - giả sử, pháo hoa cho thấy người chơi không thể chạm hoặc đánh - thì tôi sẽ tạo một thực thể duy nhất và một ParticleRenderGroupthành phần có chứa bất kỳ thông tin nào bạn cần cho các hạt - phân rã, vv - không được bao phủ bởi Renderablethành phần của bạn . Khi kết xuất, hệ thống kết xuất sẽ xem liệu một thực thể có RenderParticleGroupđính kèm hay không và xử lý nó phù hợp.

Nếu bạn cần các hạt riêng lẻ tham gia phát hiện va chạm, phản hồi đầu vào, v.v., nhưng bạn chỉ muốn kết xuất chúng theo lô, tôi sẽ tạo một Particlethành phần chứa thông tin đó trên cơ sở từng hạt và tạo chúng như Các thực thể riêng biệt. Hệ thống kết xuất vẫn có thể bó chúng, nhưng chúng sẽ được coi là các đối tượng riêng biệt bởi các hệ thống khác. (Điều này hoạt động rất tốt với inst instance.)

Sau đó, trong MotionSystem(hoặc bất cứ điều gì bạn sử dụng để xử lý cập nhật vị trí thực thể, v.v.) hoặc chuyên dụng ParticleSystem, thực hiện bất kỳ xử lý nào được yêu cầu cho mỗi hạt trên mỗi khung. Công ty RenderSystemsẽ chịu trách nhiệm xây dựng / tạo khối và lưu trữ các bộ sưu tập hạt khi chúng được tạo và hủy, và hiển thị chúng theo yêu cầu.

Một điều thú vị về phương pháp này là bạn không cần phải có bất kỳ trường hợp đặc biệt nào về va chạm, loại bỏ, v.v. đối với các hạt; họ viết mã cho mọi loại thực thể khác vẫn có thể được sử dụng.

Phần kết luận

Nếu bạn đang xem xét việc sử dụng đa nền tảng - không thể áp dụng được với JavaScript - tất cả các mã dành riêng cho nền tảng của bạn (cụ thể là kết xuất và nhập liệu) được tách thành hai hệ thống. Logic trò chơi của bạn vẫn ở trong các lớp học nền tảng (chuyển động, va chạm, v.v.) vì vậy bạn không cần phải chạm vào chúng khi chuyển.

Tôi hiểu và đồng ý với quan điểm của Sean rằng những thứ kinh khủng trong giày thành một mẫu để tuân thủ nghiêm ngặt mẫu, thay vì điều chỉnh mẫu để đáp ứng nhu cầu của ứng dụng của bạn, là điều tồi tệ. Tôi chỉ không thấy bất cứ điều gì trong Đầu vào, Máy ảnh hoặc Hạt yêu cầu xử lý đó.


Nơi nào bạn đặt logic để điều khiển nhiều camera (nhìn, xoay, di chuyển, v.v.)?
plasmacel

7

Logic đầu vào và trò chơi có thể sẽ được xử lý trong một đoạn mã chuyên dụng bên ngoài hệ thống thành phần thực thể. Về mặt kỹ thuật có thể đưa nó vào thiết kế, nhưng có rất ít lợi ích - logic trò chơi và giao diện người dùng bị hack và đầy trừu tượng bất kể bạn làm gì, và cố gắng buộc cái chốt vuông vào một lỗ tròn chỉ vì độ tinh khiết của kiến ​​trúc là một sự lãng phí của thời gian

Tương tự như vậy, các trình phát hạt là những con thú đặc biệt, đặc biệt nếu bạn quan tâm đến hiệu suất. Một thành phần phát có ý nghĩa, nhưng đồ họa sẽ thực hiện một số phép thuật đặc biệt với các thành phần đó, xen kẽ với phép thuật cho phần còn lại của kết xuất.

Về máy ảnh của bạn, chỉ cần cung cấp cho máy ảnh một cờ hoạt động và có thể là chỉ số "độ sâu" và để hệ thống đồ họa hiển thị tất cả chúng được bật. Điều này thực sự hữu ích cho nhiều thủ thuật, bao gồm GUI (muốn GUI của bạn được hiển thị ở chế độ chính tả trên thế giới trò chơi? Không vấn đề gì, chúng chỉ là hai máy ảnh với mặt nạ đối tượng khác nhau và GUI được đặt ở lớp cao hơn). Nó cũng hữu ích cho các lớp hiệu ứng đặc biệt và như vậy.


4

Máy ảnh sẽ là một thực thể hay chỉ đơn giản là một thành phần?

Tôi không chắc câu hỏi này thực sự là gì. Cho rằng những thứ duy nhất bạn có trong trò chơi là các thực thể, sau đó máy ảnh phải là các thực thể. Các chức năng camera được thực hiện thông qua một số loại thành phần máy ảnh. Không có các thành phần "Vị trí" và "Xoay" riêng biệt - đó là mức quá thấp. Chúng nên được kết hợp thành một số loại thành phần WorldPocation sẽ áp dụng cho bất kỳ thực thể nào trên thế giới. Đối với cái nào sẽ sử dụng ... bạn phải đưa logic vào hệ thống bằng cách nào đó. Hoặc bạn mã hóa nó vào hệ thống xử lý máy ảnh của bạn, hoặc bạn đính kèm tập lệnh, hoặc một cái gì đó. Bạn có thể có một cờ được bật / tắt trên một thành phần máy ảnh nếu nó giúp.

Tôi khá chắc chắn rằng bản thân các hạt không nên là thực thể

Tôi cũng vậy. Một bộ phát hạt sẽ là một thực thể và hệ thống hạt sẽ theo dõi các hạt liên kết với một thực thể nhất định. Những thứ như thế này là nơi bạn nhận ra rằng "mọi thứ là một thực thể" là không thực tế. Trong thực tế, những thứ duy nhất là các thực thể là các đối tượng tương đối phức tạp được hưởng lợi từ sự kết hợp của các thành phần.

Đối với Đầu vào: đầu vào không tồn tại trong thế giới trò chơi, do đó, được xử lý bởi một hệ thống. Không nhất thiết phải là một 'hệ thống thành phần' bởi vì không phải mọi thứ trong trò chơi của bạn sẽ xoay quanh các thành phần. Nhưng sẽ có một hệ thống đầu vào. Bạn có thể muốn đánh dấu thực thể phản hồi đầu vào bằng một số loại thành phần Người chơi, nhưng đầu vào sẽ phức tạp và hoàn toàn dành riêng cho trò chơi nên có rất ít điểm cố gắng tạo thành phần cho việc này.


1

Dưới đây là một số ý tưởng của tôi để giải quyết những vấn đề này. Họ có thể sẽ có điều gì đó không ổn với họ, và có lẽ sẽ có một cách tiếp cận tốt hơn, vì vậy xin vui lòng, hướng tôi đến những người trong câu trả lời của bạn!

Camera :

Có một thành phần "Camera", có thể được thêm vào bất kỳ thực thể nào. Tôi thực sự không thể tìm ra dữ liệu nào tôi nên đặt trong thành phần này, mặc dù: Tôi có thể có các thành phần "Vị trí" và "Xoay" riêng biệt! Các followphương pháp không cần phải được thực hiện, bởi vì nó đã theo dõi những thực thể nó được gắn vào! Và tôi tự do di chuyển nó xung quanh. Vấn đề với hệ thống này sẽ là nhiều đối tượng máy ảnh khác nhau: làm thế nào để RendererSystembiết cái nào sẽ sử dụng? Ngoài ra, tôi đã từng vượt qua các đối tượng máy ảnh xung quanh, nhưng bây giờ có vẻ như RendererSystemsẽ cần phải lặp lại hai lần trên tất cả các thực thể: đầu tiên để tìm những đối tượng hoạt động như máy ảnh và thứ hai, để thực sự hiển thị mọi thứ.

Hạt :

Sẽ có một ParticleSystembản cập nhật tất cả các thực thể có thành phần "Emitter". Các hạt là các đối tượng câm trong một không gian tọa độ tương đối, bên trong thành phần đó. Có một vấn đề về kết xuất ở đây: tôi sẽ cần phải tạo một ParticleRendererhệ thống, hoặc mở rộng chức năng của hệ thống hiện có.

Hệ thống đầu vào :

Mối quan tâm chính đối với tôi ở đây là logic, hoặc react()phương pháp. Giải pháp duy nhất tôi đưa ra là một hệ thống riêng cho điều đó và một thành phần cho mỗi hệ thống, sẽ chỉ ra cái nào sẽ sử dụng. Điều này thực sự có vẻ quá hack, và tôi không biết làm thế nào để xử lý nó tốt. Một điều là, miễn là tôi quan tâm, việc Inputcó thể được triển khai như một lớp, nhưng tôi không thấy làm thế nào tôi có thể tích hợp nó với phần còn lại của trò chơi.


Thực sự không có lý do nào để RendererSystem lặp đi lặp lại trên tất cả các thực thể - nó phải có một danh sách các vật có thể vẽ được (và máy ảnh và đèn (trừ khi đèn có thể vẽ được)) hoặc biết các danh sách đó ở đâu. Ngoài ra, bạn có thể muốn loại bỏ các máy ảnh bạn muốn kết xuất, vì vậy có thể máy ảnh của bạn có thể chứa một danh sách ID thực thể có thể vẽ được hiển thị cho nó. Bạn có thể có nhiều máy ảnh và một máy ảnh hoạt động hoặc một máy ảnh được gắn vào các POV khác nhau, cả hai đều có thể được kiểm soát bởi bất kỳ số lượng nào, như tập lệnh và trình kích hoạt và đầu vào

@ melak47, điều đó đúng, tôi cũng nghĩ về nó, nhưng tôi muốn thực hiện nó theo cách Aremis làm. Nhưng "hệ thống lưu trữ tài liệu tham khảo cho các thực thể có liên quan" dường như ngày càng thiếu sót ...
jcora

Không phải Artemis lưu trữ từng loại Thành phần trong danh sách riêng của nó? Vì vậy, bạn sẽ có chính xác những danh sách các thành phần có thể vẽ, các thành phần máy ảnh, ánh sáng và những gì không phải ở đâu đó?
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.