利用排列序列提升游戏动画的真实感

在游戏开发中,我们经常需要处理重复的动画序列,以增加游戏的真实感。例如,在海战游戏中,我们可能会有一组军舰停泊在港湾,它们的桅杆或甲板时不时地需要摇晃,以模拟海风的影响。实现这种效果有多种方法,例如完全随机选择某艘军舰播放动画,或者按照固定的顺序依次播放。亦或者让几个敌方小兵随机开火,。然而,这两种方法各有其缺点。本文将介绍如何利用排列序列(Permutation Sequence)来构造一种确定性但不可预测的动画播放方式,从而提升游戏的真实感。

传统方法的不足

完全随机选择:

每次随机选择一艘军舰播放动画。

可能导致某些军舰很长时间都不会被选中,影响动画均衡性。

固定循环顺序:

按照 A → B → C → D → A → B → C → D 的顺序播放动画。

看上去过于机械,缺乏随机性,导致沉浸感下降。

利用排列序列的方案

为了克服上述问题,我们可以利用排列序列(Permutation Sequence)来生成军舰的动画播放顺序。排列序列指的是在一个List(逆天的敏感词,jihe都不行)的所有排列中,以一定规则获取某个特定的排列。
image-20250210172904381

如何生成排列序列? 假设有 4 艘军舰 {A, B, C, D},我们希望在每个回合中按照不同的顺序选择它们播放动画。例如:

第 1 轮: A → B → C → D

第 2 轮: A → B → D → C

第 3 轮: A → C → B → D

第 4 轮: A → C → D → B

……

我们可以按照字典序生成所有排列,然后在每个回合中依次选择一个新的排列。

字典序索引和获取排序 计算某个排列的字典序索引 字典序索引是指一个特定排列在所有可能排列中的排名。例如,对于 {A, B, C, D} 的所有排列,排列 ABCD 的索引是 0,排列 ACBD 的索引是 5。

🧮计算公式如下:

image-20250210164008969

其中:

  • t[j]t[j] 是排列中的元素。
  • AjA_j 是剩余未被选择的元素。
  • n!n! 代表阶乘。

根据索引获取特定排列 如果我们想获取某个特定索引下的排列(如索引 5),可以使用如下方法:

  1. 计算当前索引所在的阶乘范围。
  2. 确定排列中每个元素的位置。
  3. 逐步缩小问题规模,直到构造完整排列。

C# 代码实现

  1. 生成所有排列序列
class Program
    {
        static void Permute(List<char> elements, int start, List<string> result)
        {
            if (start == elements.Count - 1)
            {
                result.Add(new string(elements.ToArray()));
                return;
            }

            for (int i = start; i < elements.Count; i++)
            {
                (elements[start], elements[i]) = (elements[i], elements[start]);
                Permute(elements, start + 1, result);
                (elements[start], elements[i]) = (elements[i], elements[start]); // 回溯
            }
        }

        static void Main()
        {
            List<char> ships = new List<char> { 'A', 'B', 'C', 'D' };
            List<string> permutations = new List<string>();

            Permute(ships, 0, permutations);

            foreach (var p in permutations)
            {
                Console.WriteLine(p);
            }
        }
    }
  1. 计算字典序索引
using System;
using System.Collections.Generic;

class Program
    {
        static int Factorial(int n)
        {
            int result = 1;
            for (int i = 2; i <= n; i++)
                result *= i;
            return result;
        }

        static int GetPermutationIndex(List<char> elements, string target)
        {
            int index = 0;
            int n = elements.Count;
            List<char> available = new List<char>(elements);

            for (int i = 0; i < n; i++)
            {
                int rank = available.IndexOf(target[i]);
                index += rank * Factorial(n - i - 1);
                available.RemoveAt(rank);
            }

            return index;
        }

        static void Main()
        {
            List<char> ships = new List<char> { 'A', 'B', 'C', 'D' };
            string target = "ACBD";

            Console.WriteLine("Permutation Index: " + GetPermutationIndex(ships, target));
        }
    }

3.使用泛型集成

using System;
using System.Collections.Generic;
//        List<char> ships = new List<char> { 'A', 'B', 'C', 'D' };
//        PermutationSequence<char> sequence = new PermutationSequence<char>(ships);
//        List<char> permutation = sequence.GetPermutation(5);
//        Console.WriteLine("Permutation at index 5: " + string.Join("", permutation));
//        int index = sequence.GetPermutationIndex(new List<char> { 'A', 'C', 'B', 'D' });
//        Console.WriteLine("Index of ACB: " + index);
public class PermutationSequence<T>
{
    private List<T> elements;
    private List<List<T>> permutations;
    public PermutationSequence(List<T> elements)
    {
        this.elements = new List<T>(elements);
        this.permutations = new List<List<T>>();
        GeneratePermutations(this.elements, 0);
    }
    private void GeneratePermutations(List<T> list, int start)
    {
        if (start == list.Count - 1)
        {
            permutations.Add(new List<T>(list));
            return;
        }
        for (int i = start; i < list.Count; i++)
        {
            (list[start], list[i]) = (list[i], list[start]);
            GeneratePermutations(list, start + 1);
            (list[start], list[i]) = (list[i], list[start]); // 回溯
        }
    }
    public List<T> GetPermutation(int index)
    {
        if (index < 0 || index >= permutations.Count)
            throw new ArgumentOutOfRangeException(nameof(index), "Index out of range");
        return permutations[index];
    }
    public int GetPermutationIndex(List<T> target)
    {
        int index = 0;
        int n = elements.Count;
        List<T> available = new List<T>(elements);
        for (int i = 0; i < n; i++)
        {
            int rank = available.IndexOf(target[i]);
            index += rank * Factorial(n - i - 1);
            available.RemoveAt(rank);
        }
        return index;
    }
    private int Factorial(int n)
    {
        int result = 1;
        for (int i = 2; i <= n; i++)
            result *= i;
        return result;
    }
}

结论

使用排列序列来决定动画播放顺序,可以避免纯随机可能导致的不均衡,同时也能避免固定循环导致的机械感。这种方法在游戏动画、角色 AI 行为序列、战斗动作编排等场景中都有很好的应用价值。

通过计算字典序索引,我们可以对排列进行索引管理,使得动画播放有序且可控,而不会显得刻板或完全随机。这种“确定性混沌”的方法能显著提高游戏的沉浸感,使游戏世界更加真实。

参考 ——《游戏编程精粹6》-2.4 适用于游戏开发的序列索引技术

posted @ 2025-02-10 17:31  世纪末の魔术师  阅读(30)  评论(0)    收藏  举报