C# 卡车装车算法2

发布时间 2023-04-28 08:48:36作者: 多见多闻

1. 创建一个货物类,包含长、宽、高、重量、颜色、标签等属性,并定义一个列表用于存储所有货物对象。

public class Cargo {
    public float length;
    public float width;
    public float height;
    public float weight;
    public Color color;
    public string label;

    public Cargo(float l, float w, float h, float m, Color c, string lbl) {
        length = l;
        width = w;
        height = h;
        weight = m;
        color = c;
        label = lbl;
    }
}

List<Cargo> cargoes = new List<Cargo>();

2. 在UI界面中添加输入框和按钮,让用户输入货物参数和算法选择。

float length = float.Parse(lengthInput.text);
float width = float.Parse(widthInput.text);
float height = float.Parse(heightInput.text);
float weight = float.Parse(weightInput.text);
string label = labelInput.text;
Color color = colorPicker.color;

// 添加新货物到列表中
Cargo newCargo = new Cargo(length, width, height, weight, color, label);
cargoes.Add(newCargo);

// 计算最优摆放方案,使用选择的算法
if (algorithmDropdown.value == 0) {
    // 贪心算法
} else if (algorithmDropdown.value == 1) {
    // 回溯算法
} else if (algorithmDropdown.value == 2) {
    // 遗传算法
}

3. 实现贪心算法来计算最优摆放方案,并在场景中绘制货物。

// 定义贪心算法函数
public void GreedyAlgorithm() {
    float maxWidth = 20f;
    float maxHeight = 10f;
    float maxLength = 50f;

    // 按照重量从大到小排序
    cargoes.Sort((a, b) => b.weight.CompareTo(a.weight));

    // 计算总体积和总重量,同时记录每个货物的位置和旋转角度
    Vector3 pos = Vector3.zero;
    Quaternion rot = Quaternion.identity;
    float totalWeight = 0f;
    float totalVolume = 0f;
    foreach (Cargo cargo in cargoes) {
        if (cargo.length <= maxLength && cargo.width <= maxWidth && cargo.height <= maxHeight) {
            // 判断是否可以放入卡车
            if (totalWeight + cargo.weight <= maxWeight && totalVolume + cargo.length * cargo.width * cargo.height <= maxVolume) {
                totalWeight += cargo.weight;
                totalVolume += cargo.length * cargo.width * cargo.height;

                // 计算当前货物的位置和旋转角度
                cargo.transform.position = pos;
                cargo.transform.rotation = rot;
                cargo.gameObject.SetActive(true);

                // 更新下一个货物的位置和旋转角度
                Vector3 nextPos = pos + new Vector3(cargo.length / 2f, cargo.height / 2f, -cargo.width / 2f);
                if (nextPos.y > maxHeight) {
                    nextPos.y = 0f;
                    nextPos.x += maxLength;
                }
                pos = nextPos;
            }
        }
    }
}

// 在场景中创建货物对象,设置位置和旋转角度,并添加到列表中
Cargo cargo1 = Instantiate(cargoPrefab).GetComponent<Cargo>();
cargo1.length = 5f;
cargo1.width = 2f;
cargo1.height = 3f;
cargo1.weight = 10f;
cargo1.color = Color.red;
cargo1.label = "Box 1";
cargo1.gameObject.SetActive(false);
cargoes.Add(cargo1);

// 绘制卡车模型,并计算最优摆放方案
CreateTruck();
GreedyAlgorithm();

4. 实现拖放事件和数据显示组件。

public class Cargo : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler {
    public void OnBeginDrag(PointerEventData eventData) {
        // 开始拖动货物时,记录初始位置和旋转角度
        startPos = transform.position;
        startRot = transform.rotation;

5. 实现摆放到卡车功能,并在UI界面中添加“装载”按钮。

public class Cargo : MonoBehaviour {
    public void LoadToTruck() {
        // 将货物设置为静态,禁止再次拖放
        GetComponent<Rigidbody>().isKinematic = true;

        // 设置货物父节点为卡车对象
        transform.SetParent(truck.transform);

        // 将货物加入已装载货物列表中
        loadedCargoes.Add(this);
    }
}

public void LoadCargoesToTruck() {
    foreach (Cargo cargo in selectedCargoes) {
        cargo.LoadToTruck();
    }

    // 计算已装载货物的总重量和总体积
    float totalWeight = 0f;
    float totalVolume = 0f;
    foreach (Cargo cargo in loadedCargoes) {
        totalWeight += cargo.weight;
        totalVolume += cargo.length * cargo.width * cargo.height;
    }

    // 显示已装载货物的总重量和总体积
    weightText.text = "Total weight: " + totalWeight.ToString("F2");
    volumeText.text = "Total volume: " + totalVolume.ToString("F2");
}

6. 实现场景旋转、放大、拖动功能,并使用Unity自带的Camera类实现视角控制。

public class CameraController : MonoBehaviour {
    public float moveSpeed = 10f;
    public float rotateSpeed = 100f;
    public float zoomSpeed = 10f;

    private Camera camera;

    void Start() {
        camera = GetComponent<Camera>();
    }

    void Update() {
        // 控制场景移动
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        float depth = Input.GetAxis("Depth");
        transform.position += transform.right * horizontal * moveSpeed * Time.deltaTime;
        transform.position += transform.up * vertical * moveSpeed * Time.deltaTime;
        transform.position += transform.forward * depth * moveSpeed * Time.deltaTime;

        // 控制场景旋转
        float rotateHorizontal = Input.GetAxis("Mouse X");
        float rotateVertical = Input.GetAxis("Mouse Y");
        transform.Rotate(Vector3.up, rotateHorizontal * rotateSpeed * Time.deltaTime, Space.World);
        transform.Rotate(Vector3.left, rotateVertical * rotateSpeed * Time.deltaTime, Space.World);

        // 控制摄像机缩放
        float zoom = Input.GetAxis("Mouse ScrollWheel");
        camera.fieldOfView -= zoom * zoomSpeed;
        camera.fieldOfView = Mathf.Clamp(camera.fieldOfView, 1f, 100f);
    }
}

7. 在UI界面中添加“保存场景”按钮,并使用Unity自带的JsonUtility类将货物列表保存到本地文件。

public void SaveScene() {
    // 将货物列表转换为JSON格式字符串
    string json = JsonUtility.ToJson(cargoes);

    // 将JSON字符串写入本地文件
    string filePath = Application.dataPath + "/cargoes.json";
    File.WriteAllText(filePath, json);
}

8. 在UI界面中添加“加载场景”按钮,并使用Unity自带的JsonUtility类从本地文件中读取货物列表。

public void LoadScene() {
    // 从本地文件读取JSON字符串
    string filePath = Application.dataPath + "/cargoes.json";
    string json = File.ReadAllText(filePath);

    // 将JSON字符串转换为货物对象列表
    cargoes = JsonUtility.FromJson<List<Cargo>>(json);

    // 在场景中创建每个货物对象,并设置其位置和旋转角度
    foreach (Cargo cargo in cargoes) {
        Cargo newCargo = Instantiate(cargoPrefab).GetComponent<Cargo>();
        newCargo.length = cargo.length;
        newCargo.width = cargo.width;
        newCargo.height = cargo.height;
        newCargo.weight = cargo.weight;
        newCargo.color = cargo.color;
        newCargo.label = cargo.label;
        newCargo.transform.position = cargo.position;
        newCargo.transform.rotation = cargo.rotation;
    }
}

9. 在货物类中添加序列化属性,以便将货物对象保存到本地文件。

[System.Serializable]
public class Cargo {
    public float length;
    public float width;
    public float height;
    public float weight;
    public Color color;
    public string label;

    // 序列化货物位置和旋转角度
    public Vector3 position;
    public Quaternion rotation;

    public Cargo(float l, float w, float h, float m, Color c, string lbl) {
        length = l;
        width = w;
        height = h;
        weight = m;
        color = c;
        label = lbl;
    }
}

10. 在货物类中添加Equals方法和GetHashCode方法,以便将相同属性的货物对象视为相等。

public override bool Equals(object obj) {
    if (obj == null) return false;
    Cargo other = obj as Cargo;
    if (other == null) return false;
    return length == other.length && width == other.width && height == other.height && weight == other.weight && color.Equals(other.color) && label.Equals(other.label);
}

public override int GetHashCode() {
    return length.GetHashCode() ^ width.GetHashCode() ^ height.GetHashCode() ^ weight.GetHashCode() ^ color.GetHashCode() ^ label.GetHashCode();
}

11. 在摆放算法中实现相同货物尽量放置在一起的要求,并使用字典记录已放置的货物类型和数量。

// 定义贪心算法函数
public void GreedyAlgorithm() {
    float maxWidth = 20f;
    float maxHeight = 10f;
    float maxLength = 50f;

    // 按照重量从大到小排序
    cargoes.Sort((a, b) => b.weight.CompareTo(a.weight));

    // 计算总体积和总重量,同时记录每个货物的位置和旋转角度
    Vector3 pos = Vector3.zero;
    Quaternion rot = Quaternion.identity;
    float totalWeight = 0f;
    float totalVolume = 0f;
    Dictionary<Cargo, int> cargoCount = new Dictionary<Cargo, int>();
    foreach (Cargo cargo in cargoes) {
        if (cargo.length <= maxLength && cargo.width <= maxWidth && cargo.height <= maxHeight) {
            // 判断是否可以放入卡车
            if (totalWeight + cargo.weight <= maxWeight && totalVolume + cargo.length * cargo.width * cargo.height <= maxVolume) {
                // 判断是否需要新建一层
                if (pos.y + cargo.height / 2f > maxHeight) {
                    pos.y = 0f;
                    pos.x += maxLength;
                }

                // 判断是否与上一个货物相同
                bool sameCargo = false;
                foreach (KeyValuePair<Cargo, int> item in cargoCount) {
                    if (item.Key.Equals(cargo)) {
                        sameCargo = true;
                        pos.z += item.Key.length / 2f + cargo.length / 2f;
                        break;
                    }
                }
                if (!sameCargo) {
                    cargoCount[cargo] = 1;
                } else {
                    cargoCount[cargo]++;
                }

                // 更新当前货物的位置和旋转角度
                cargo.transform.position = pos;
                cargo.transform.rotation = rot;
                cargo.gameObject.SetActive(true);

                // 更新下一个货物的位置和旋转角度
                pos += new Vector3(0f, cargo.height / 2f, cargo.width / 2f);
                totalWeight += cargo.weight;
                totalVolume += cargo.length * cargo.width * cargo.height;
            }
        }
    }
}

12. 在摆放算法中实现最大化利用空间要求,根据卡车尺寸计算每层最大可用空间,并动态调整货物高度来最大化利用空间。

// 定义贪心算法函数
public void GreedyAlgorithm() {
    float maxWidth = 20f;
    float maxHeight = 10f;
    float maxLength = 50f;

    // 按照重量从大到小排序
    cargoes.Sort((a, b) => b.weight.CompareTo(a.weight));

    // 计算总体积和总重量,同时记录每个货物的位置和旋转角度
    Vector3 pos = Vector3.zero;
    Quaternion rot = Quaternion.identity;
    float totalWeight = 0f;
    float totalVolume = 0f;
    Dictionary<Cargo, int> cargoCount = new Dictionary<Cargo, int>();
    foreach (Cargo cargo in cargoes) {
        if (cargo.length <= maxLength && cargo.width <= maxWidth && cargo.height <= maxHeight) {
            // 判断是否可以放入卡车
            if (totalWeight + cargo.weight <= maxWeight && totalVolume + cargo.length * cargo.width * cargo.height <= maxVolume) {
                // 判断是否需要新建一层
                if (pos.y + cargo.height / 2f > maxHeight) {
                    pos.y = 0f;
                    pos.x += maxLength;
                }

                // 判断是否与上一个货物相同
                bool sameCargo = false;
                foreach (KeyValuePair<Cargo, int> item in cargoCount) {
                    if (item.Key.Equals(cargo)) {
                        sameCargo = true;
                        pos.z += item.Key.length / 2f + cargo.length / 2f;
                        break;
                    }
                }
                if (!sameCargo) {
                    cargoCount[cargo] = 1;
                } else {
                    cargoCount[cargo]++;
                }

                // 动态调整货物高度来最大化利用空间
                float availableHeight = maxHeight - pos.y;
                if (availableHeight < cargo.height) {
                    cargo.transform.localScale *= availableHeight / cargo.height;
                    cargo.height = availableHeight;
                }

                // 更新当前货物的位置和旋转角度
                cargo.transform.position = pos;
                cargo.transform.rotation = rot;
                cargo.gameObject.SetActive(true);

                // 更新下一个货物的位置和旋转角度
                pos += new Vector3(0f, cargo.height / 2f, cargo.width / 2f);
                totalWeight += cargo.weight;
                totalVolume += cargo.length * cargo.width * cargo.height;
            }
        }
    }
}

13. 在UI界面中添加“清空场景”按钮,并在点击后移除所有已放置的货物,并重置数据显示。

public void ClearScene() {
    foreach (Cargo cargo in loadedCargoes) {
        Destroy(cargo.gameObject);
    }
    loadedCargoes.Clear();
    weightText.text = "Total weight: ";
    volumeText.text = "Total volume: ";
}

14. 在UI界面中添加“退出应用程序”按钮,并在点击后退出应用程序。

public void QuitApplication() {
    Application.Quit();
}

15. 在UI界面中添加“选择算法”下拉框,并在其中添加各种摆放算法选项。

public Dropdown algorithmDropdown;

void Start() {
    // 在下拉框中添加选项
    List<string> algorithms = new List<string>() { "Greedy Algorithm", "Genetic Algorithm", "Simulated Annealing" };
    algorithmDropdown.AddOptions(algorithms);
}

16. 在UI界面中添加“摆放货物”按钮,并在点击后根据选定的算法进行货物摆放。

public void LoadCargoes() {
    // 移除之前的货物
    ClearScene();

    // 根据选定的算法进行货物摆放
    switch (algorithmDropdown.value) {
        case 0:
            GreedyAlgorithm();
            break;
        case 1:
            GeneticAlgorithm();
            break;
        case 2:
            SimulatedAnnealing();
            break;
    }

    // 更新数据显示
    weightText.text = "Total weight: " + loadedWeight.ToString("F2") + " tons";
    volumeText.text = "Total volume: " + loadedVolume.ToString("F2") + " cubic meters";
}

17. 在UI界面中添加“摆放到卡车”按钮,并在点击后将货物摆放到卡车上。

public void PlaceCargoes() {
    // 将货物摆放到卡车上
    foreach (Cargo cargo in loadedCargoes) {
        cargo.transform.SetParent(truck.transform);
    }
}

18. 在UI界面中添加“拖动场景”按钮,并在点击后启用场景拖放功能。

public void EnableDrag() {
    dragManager.enabled = true;
}

19. 在UI界面中添加“缩放场景”选框,并在选中后开启场景缩放功能。

public Toggle zoomToggle;

void Update() {
    // 如果选中了缩放场景选框,则开启场景缩放功能
    if (zoomToggle.isOn) {
        float zoom = Input.GetAxis("Mouse ScrollWheel");
        Camera.main.transform.position += Camera.main.transform.forward * zoom * zoomSpeed;
    }
}

20. 在UI界面中添加“显示/隐藏方案”选框,并在选中后显示或隐藏货物摆放方案。

public Toggle planToggle;

void Update() {
    // 如果选中了显示/隐藏方案选框,则显示或隐藏货物摆放方案
    foreach (Cargo cargo in loadedCargoes) {
        cargo.plan.SetActive(planToggle.isOn);
    }
}

21. 在货物类中添加plan属性,并在创建货物时同时创建其对应的货物摆放方案。

public GameObject planPrefab;
public GameObject plan { get; private set; }

void Awake() {
    plan = Instantiate(planPrefab, transform.position, transform.rotation, transform);
    plan.SetActive(false);
}

22. 在卡车类中添加bounds属性,表示卡车所占用的空间范围,并在添加货物时更新该属性。

public Bounds bounds { get; private set; }

void Awake() {
    bounds = new Bounds(transform.position, new Vector3(maxLength, maxHeight, maxWidth));
}

public bool AddCargo(Cargo cargo) {
    // 判断是否可以放入卡车
    if (loadedWeight + cargo.weight <= maxWeight && loadedVolume + cargo.length * cargo.width * cargo.height <= maxVolume) {
        // 判断是否与上一个货物相同
        bool sameCargo = false;
        foreach (KeyValuePair<Cargo, int> item in cargoCount) {
            if (item.Key.Equals(cargo)) {
                sameCargo = true;
                pos.z += item.Key.length / 2f + cargo.length / 2f;
                break;
            }
        }
        if (!sameCargo) {
            cargoCount[cargo] = 1;
        } else {
            cargoCount[cargo]++;
        }

        // 更新当前货物的位置和旋转角度
        cargo.transform.position = pos;
        cargo.transform.rotation = rot;
        cargo.gameObject.SetActive(true);

        // 更新卡车的属性和下一个货物的位置和旋转角度
        loadedCargoes.Add(cargo);
        loadedWeight += cargo.weight;
        loadedVolume += cargo.length * cargo.width * cargo.height;
        bounds.Encapsulate(new Bounds(cargo.transform.position, new Vector3(cargo.length, cargo.height, cargo.width)));
        pos += new Vector3(0f, cargo.height / 2f, cargo.width / 2f);
        return true;
    } else {
        return false;
    }
}

23. 在UI界面中添加“拖动货物”选框,并在选中后启用货物拖放功能。

public Toggle cargoDragToggle;

void Update() {
    // 如果选中了拖动货物选框,则启用货物拖放功能
    if (cargoDragToggle.isOn) {
        foreach (Cargo cargo in loadedCargoes) {
            cargo.gameObject.AddComponent<DragManager>();
        }
    }
}