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

在游戏开发中,我们经常需要处理重复的动画序列,以增加游戏的真实感。例如,在海战游戏中,我们可能会有一组军舰停泊在港湾,它们的桅杆或甲板时不时地需要摇晃,以模拟海风的影响。实现这种效果有多种方法,例如完全随机选择某艘军舰播放动画,或者按照固定的顺序依次播放。亦或者让几个敌方小兵随机开火,。然而,这两种方法各有其缺点。本文将介绍如何利用排列序列(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 @   世纪末の魔术师  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2016-02-10 Unity初探之黑暗之光(1)
  1. 1 ありがとう··· KOKIA
ありがとう··· - KOKIA
00:00 / 00:00
An audio error has occurred.

作词 : KOKIA

作曲 : KOKIA

编曲 : 日向敏文

作词 : KOKIA

作曲 : KOKIA

誰もが気付かぬうちに

誰もが気付かぬうちに

何かを失っている

フッと気付けばあなたはいない

思い出だけを残して

せわしい時の中

言葉を失った人形達のように

街角に溢れたノラネコのように

声にならない叫びが聞こえてくる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じていたい

思い出はせめてもの慰め

いつまでもあなたはここにいる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じてたい

点击右上角即可分享
微信分享提示