Hệ thống RNG mở rộng


7

Câu hỏi của tôi là khá chung chung. Cách đơn giản nhất để mã hóa một hệ thống hoặc thuật toán RNG dễ dàng cung cấp các bổ sung và thay đổi là gì? Ví dụ: giả sử tôi có một hệ thống RNG sẽ sinh ra một vật phẩm dựa trên giá trị ngẫu nhiên được cuộn. Sau đó, tôi đoán một số giải pháp cơ bản, tát vào nhau có thể trông giống như

if (randomNumber > 0 && randomNumber <= 0.2)
{
    // spawn something
}

if (randomNumber > 0.2 && randomNumber <= 0.4)
{
    // spawn some other thing
}

hoặc để có cơ hội sinh ra nhiều vật phẩm, có thể

if (randomNumber < 0.4)
{
    // spawn something
}

if (randomNumber < 0.2)
{
    // spawn some other thing 
}

Bây giờ, giả sử tôi thêm rất nhiều vật phẩm có thể sinh ra trong trò chơi. Sẽ rất rắc rối khi quay lại và sửa đổi tất cả các giá trị này trong khi vẫn duy trì độ hiếm tương đối của chúng, v.v ... Làm cách nào để tạo một hệ thống RNG linh hoạt hơn?


4
Hãy xem các kỹ thuật lập trình hướng dữ liệu. Bạn không nên có các giá trị được mã hóa cứng như thế trong mã của mình.
MichaelHouse

Bạn có thể xây dựng các dấu ngoặc hiếm, cung cấp một lượng X trọng lượng cho các vật phẩm. Và sau đó từ đó cung cấp cho mọi thứ khác một giá trị cơ bản và để nó chọn từ đó. Chỉ cần làm cho các dấu ngoặc nhất định lớn hơn dựa trên độ hiếm để chúng ít được tìm thấy hơn. Bạn cũng sẽ muốn nhiều sự ngẫu nhiên hơn một rand cơ bản. Một cái gì đó nhiều dữ liệu được điều khiển như Byte đã nói sẽ tốt
n_plum

Không phải là một câu trả lời đầy đủ, nhưng có lẽ bạn muốn sử dụng một bảng tra cứu đơn giản. Điều quan trọng, bạn muốn làm theo đề xuất của @ Byte56 và khởi tạo các bảng này từ các tệp văn bản , không trực tiếp trong mã. Thậm chí tốt hơn, thêm chức năng để tải lại các giá trị từ các tệp trong khi chơi. Không phải biên dịch lại mã của bạn để thay đổi một vài biến là tuyệt vời.
Miles Rout

Câu trả lời:


7

Bạn có thể sử dụng một thuật toán mà tôi gọi là RNG Xác suất phân tán .

Nó trông như thế này:

public class Item {}

public class ExampleBucket
{
    private List<Item> bucket = new List<Item>();   

    public void Add (Item item, int count)
    {
        for (int i = 0; i < count; i++)
        {
            bucket.Add(item);
        }
    }

    public Item GetRandom (Random rng)
    {
        var index = rng.Next(0, bucket.Count);

        return bucket[index];
    }
}

Bạn có thể khởi tạo như thế này:

private void InitBucket()
{
    var itemA = new Item();
    var itemB = new Item();
    var itemC = new Item();

    var bucket = new ExampleBucket();

    bucket.Add(itemA, 1);
    bucket.Add(itemB, 2);
    bucket.Add(itemC, 3);
}

Bạn có thể nhận được các mục bằng cách gọi GetRandom(Random)vào ExampleBucketví dụ của bạn .


Làm thế nào nó hoạt động?

Chúng tôi có một danh sách có tên buckettrong ExampleBucketlớp. Trước hết nó là trống rỗng. Bằng Add(Item, int)phương thức gọi , bạn thêm một lượng Items cụ thể vào bucketdanh sách.

Vào cuối InitBucket()phương thức của chúng tôi , bucketdanh sách sẽ như thế này:

itemA // 1 x itemA
itemB // 2 x itemB
itemB
itemC // 3 x itemC
itemC
itemC

Sau đó, chúng tôi chỉ đơn giản là tạo ra một số ngẫu nhiên giữa 0bucket.Count, và trả lại hàng mà tương ứng với chỉ số trong bucketdanh sách.

Xác suất nhận được một mục cụ thể từ một danh sách là Item Count / Total Count. Bạn càng có nhiều mặt hàng của một loại cụ thể trong danh sách, càng có nhiều cơ hội mặt hàng đó được chọn.

Vì vậy, trong trường hợp này;

Probability of getting itemA = 1 / (1+2+3)
Probability of getting itemB = 2 / (1+2+3)
Probability of getting itemC = 3 / (1+2+3)

Khi bạn chạy GetRandom()100000 lần (bài kiểm tra ở đây trong Fiddle ), bạn có thể thấy các giá trị khá gần với kết quả hoàn hảo:

A count = 16690 | Expected = 16666
B count = 33519 | Expected = 33333
C count = 49791 | Expected = 50000

Điều tôi thích nhất trong thuật toán này là, bạn không cần chỉ định tổng số mục. Bạn chỉ có thể thêm bất kỳ mục nào bất cứ lúc nào bạn muốn và đừng lo lắng về phần còn lại.

Ví dụ: nếu bạn muốn tăng cơ hội nhận itemA từ danh sách, bạn chỉ cần gọi

bucket.Add (itemA, theAmountYouWant);

và bạn không cần phải làm gì khác.


Cảm ơn bạn rất nhiều vì đã trả lời hữu ích. Làm cách nào để lưu trữ một thể hiện của exampleBucket () trong một biến? var xô = new exampleBucket (); dường như không làm việc cho tôi.
hugacethefuture

@embracethefuture Ý bạn là gì khi "dường như không hoạt động", bạn có đang gặp lỗi không?
S. Tarık Çetin

Oh lạ, nó làm việc. Ví dụBucket xô = new exampleBucket () Ý tôi là ... vì tôi đang sử dụng C #. Không thực sự chắc chắn tại sao nó không hoạt động sớm hơn. Tôi nghĩ đó là bởi vì tôi đã cố gắng thực hiện nó trong một kịch bản khác mà không thiết lập một tài liệu tham khảo. InitBucket () nên là một phần của lớp Item hay nó nên tồn tại trong tập lệnh MonoBehaviour ở đâu đó? Tôi chỉ đang cố gắng quấn đầu quanh kiến ​​trúc.
hugacethefuture

1
InitBucket()Phương thức @embracethefuture là một ví dụ sử dụng, đó không phải là một phần của mã thực tế. Bạn nên chuyên môn hóa nó cho cơ sở mã của riêng bạn. Nhưng nó không nên ở trong Itemlớp.
S. Tarık Çetin

Phải, cảm ơn bạn. Đó là những gì tôi nghĩ. Xin lỗi, tôi khá mới với lập trình trò chơi. Đó là lý do tại sao tôi gặp vấn đề sớm hơn, tôi không biết làm thế nào để tham khảo một lớp con không phải là monobehaviour trong các kịch bản monobehaviour của mình. Tôi không thể sử dụng GetComponent và tôi không thể thực hiện xô Ví dụBucket = mới ... bởi vì tập lệnh monobehaviour của tôi không biết rằng có một loại Ví dụBucket tồn tại.
hugacethefuture

9

Randoms có trọng số

Thay vì nhồi nhiều bản sao của một mục vào danh sách, chúng ta có thể làm điều này thay vào đó:

private class WeightedItem {
    public readonly int weight;
    public readonly Item drop;
    //constructor omitted
}

Sau đó chúng tôi đưa những thứ này vào một danh sách:

private List<WeightedItem> bucket = new List<WeightedItem>();
private totalWeight = 0;
public void Add (Item item, int count) {
    if(count <= 0) throw new Exception("Invalid random weight");
    bucket.Add(new WeightedItem(item, count));
    totalWeight += count;
}

Và sau đó chúng tôi nhận được kết quả của chúng tôi như thế này:

public Item GetRandom (Random rng) {
    int randomVal = rng.Next(0, totalWeight);
    foreach(WeightedItem item in bucket) {
        randomVal -= item.weight;
        if(randomVal <= 0) {
            return item.drop;
        }
    }
}

Phương pháp này linh hoạt hơn là chỉ đưa các mục vào danh sách vì chúng ta có thể làm cho lớp trình bao bọc chịu trách nhiệm biến Mục thành ItemStack và áp dụng dữ liệu bổ sung (như kích thước ngăn xếp hoặc dữ liệu NBT - nếu chúng ta nghĩ về các Mục trong Minecraft có nghĩa Itemlà định nghĩa nguyên mẫu và ItemStacklà thứ thực sự xuất hiện trong kho của người chơi: kích thước và dữ liệu NBT chỉ là các bit dữ liệu bổ sung trên định nghĩa nguyên mẫu cho chúng tôi biết về nhóm này cụ thể, dự án của bạn thực sự có gì và nó như thế nào đại diện là tùy thuộc vào bạn).

Điều này có nghĩa là chúng ta có thể có nhiều bộ tạo ngẫu nhiên, mỗi bộ có danh sách trọng số riêng và bất kỳ mục nhập nào trong danh sách đó (giả sử, Táo) có thể tạo ra một số lượng duy nhất tùy thuộc vào máy phát điện nào. một trình bao bọc như thế này:

public class RandomSizeStackDrop extends WeightedItem {
    public readonly int weight;
    public readonly Item drop;
    public WeightedItem(Item i, int w) {
        drop = i;
        weight = w;
    }

    public ItemStack GetStackFromDrop() {
        //Creates a stack with a size from 1 to 5
        return new ItemStack(drop, Random.Next(1,5));
    }
}

Và thay vì quay lại Itemtrong phương thức GetRandom (), thay vào đó chúng tôi sẽ quay lạiitem.GetStackFromDrop()

Vì vậy, chúng ta có thể có một cái xô cho các vật phẩm được tạo ra trong rương gần trang trại và có các ngăn từ 1 đến 5 quả táo xuất hiện, nhưng trong một rương khác, trong một hầm mỏ, chỉ tạo ra 1 quả táo mỗi khi quả táo xuất hiện. Ngay cả khi xác suất cho cả hai là như nhau, kết quả là rương gần trang trại sẽ tạo ra tổng số táo nhiều hơn .


2

Hầu hết các điều kiện của bạn là vô ích khiến mã của bạn dài hơn mà không có gì. Đây là cách làm bánh mì và bơ.

if (randomNumber <= 0.2)
{
    // 20% chance
}else if (randomNumber <= 0.4)
{
    // 20% chance
}else{
    // 60% chance
}

Nếu bạn muốn làm cho nó thuận tiện hơn nữa, bạn có thể thử với trọng số.

Giả sử bạn có một cấu trúc dữ liệu cần một Action. Đây là một số mã giả.

WeightedRandomizer wr = new WeightedRandomizer();

wr.addAction(Action.MOVE, 1); // 1 in 7 chances of happening
wr.addAction(Action.JUMP, 2); // 2 in 7 chances of happening
wr.addAction(Action.FLY, 4); // 4 in 7 chances of happening

Action action = wr.getAction();

Bạn có ít quyền kiểm soát hơn về% chính xác, nhưng đó chắc chắn là một cách tiếp cận gọn gàng hơn nhiề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.