在mc中,有时会利用命令对玩家的视角进行一些控制,或者做一些必要的移动。但是,往往这样的运动都会非常的突兀或者说,不丝滑。而电影视角正如mc自带的电影视角功能一样,在20帧的空间内尽可能做到丝滑的移动。并且,这个数据包是如此的简单,以至于它能兼容所有的1.13+版本。

演示视频

使用方法

生成视角控制的函数

Svisual\data\svisual\functions文件夹下,你应该能找到一个exe文件,这个程序将用来生成视角控制的函数。程序生成要用到视角的配置文件,即一个储存在./visual文件夹下的json文件。这个文件的名字可以自定义。json文件的格式说明如下:

{
    //这是一个配置文件的格式说明
    "relative":true,    //是否相对运动,即设置一个运动的实体,然后运动坐标为相对这个实体的坐标。
    "o":"@e[tag=point]",    //如果是相对运动,则填写一个目标选择器,选择要相对哪个实体运动
    "start":[1,2,3,50,-30], //初始位置
    "time":333, //运动所用的总时间
    "events":[
        //关键帧的列表,包含了数个事件,用于控制坐标的变化
        {
            "by":false, //是否在原来的基础上运动(未实现,暂时没有作用)
            "time":128, //关键帧的开始时间(tick)
            "last":70,  //持续时间(tick)
            "moves":[
                //事件,每个坐标轴的事件分别列出。注意,一个坐标轴只能有一个事件,并且坐标轴的事件之间时间不能重合
                //但是即使重合程序也不会报错
                {
                    "axis":"y", //哪个坐标轴(rx ry为旋转轴)
                    "func":"line",  //缓动函数的名字,函数储存在func下,可以自定义
                    "to":3  //目标
                }

            ]
        }
    ]
}

根据这个json文件,程序将会生成一大坨函数文件,并放置在与json文件同名的文件夹下。这个文件夹在/output文件夹中。json文件中提到的函数实际上是一个python代码文件,因此可以高度编程化的自定义化。这个函数的基本格式要求如下:

# -*- coding: UTF-8 -*-
#这是一个样例,说明了怎么让脚本接入C++的程序
import math         #你可以导入一些库,没有关系的

def func(start,end,last,curr):          #注意这四个参数和函数的名字
    #start  ->起始的位置
    #end    ->结束的位置
    #last   ->事件持续时间
    #curr   ->现在的时间(相对事件开始的时间)
    
    #.....一些简单的处理

    return #something 返回一个值
    #事件函数的处理只处理了xyz或者rx ry中的一个 

数据包中自带了两个缓动函数,line和ease-in-out,后者实现了基本的平滑运动,即没有明显的速度突然改变。

进行视角控制

函数文件生成后,调用即tick执行svisual:visual/output/<名字>/tick函数,并增加计分板SV_timer中的$SV_timer_<名字>的值。建议一次加一,除非有特殊需要。并且,对分数的增加必须要在调用tick函数前进行。这是数据包中已经配置好了两个测试视角文件的tick函数。

#自己加控制分数增长的哦
#太懒了没有写
#test
execute if score $SV_timer_test SV_timer matches 0.. run scoreboard players add $SV_timer_test SV_timer 1
#test2
execute if score $SV_timer_test2 SV_timer matches 0.. run scoreboard players add $SV_timer_test2 SV_timer 1
#视角控制
function svisual:visual/output/test/tick
function svisual:visual/output/test2/tick

数据包对视角控制的基本原理就是穷举每个tick中的坐标,然后利用分块穷举的方式大幅缩减穷举时每tick执行的命令数,比如一个10000的穷举,则首先每十个tick为一组,生成1000个函数文件,然后再每十个函数文件为一组,生成100个函数文件分情况执行这1000个函数文件,以此类推。这样,即使最糟糕的情况,也只会执行50次命令,大大小于原来的10000次命令,从而大幅提高了命令执行的效率。

下载

程序源码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;

namespace DLMCviewer
{
    class Program
    {
        //struct Pos
        //{
        //    public double[5] pos;
        //    public double x, y, z; //坐标
        //    public double rx, ry;  //旋转角度

        //    public Pos(double x, double y, double z, double rx, double ry)
        //    {
        //        this.x = x; this.y = y; this.z = z;
        //        this.rx = rx; this.ry = ry;
        //    }
        //}

        struct Event
        {
            //关键帧
            public bool by;
            public int time, last;
            public List<Move> moves;

            public Event(bool by, int time, int last, List<JToken> moves)
            {
                this.by = by;
                this.time = time;
                this.last = last;
                this.moves = new List<Move>();
                //处理移动
                foreach (JToken single in moves)
                {
                    this.moves.Add(new Move(single["axis"].ToString(), time, last, (double)single["to"], single["func"].ToString()));
                }
            }
        }

        struct Move
        {
            //移动事件
            public string axis;   //移动的轴
            public int time, last;  //触发时间和持续时间
            public double to;     //目标
            public dynamic ptfuncs;

            public Move(string axis, int time, int last, double to, string ptfuncs)
            {
                this.axis = axis;
                this.time = time;
                this.last = last;
                this.to = to;
                this.ptfuncs = pyEngine.ExecuteFile(@"visual/funcs/" + ptfuncs + ".py");//读取脚本文件
            }
        }

        static ScriptEngine pyEngine = Python.CreateEngine();  //创建Python解释器对象
        static List<Event> events = new List<Event>();          //事件
        static List<List<Move>> moves = new List<List<Move>>();    //五条轴的移动

        static void Main(string[] args)
        {
            Console.Write("Visual generator\n");
            while (true)
            {
                Console.Write(">");
                //逻辑循环
                string command = Console.ReadLine();
                switch (command)
                {
                    case "/debug":
                        {
                            string a = Console.ReadLine();
                            Console.ForegroundColor = ConsoleColor.Yellow;
                            pyEngine = Python.CreateEngine();  //创建Python解释器对象
                            dynamic ptfuncs = pyEngine.ExecuteFile(@"visual/funcs/"+a+".py");//读取脚本文件
                            Console.WriteLine(ptfuncs.func(50.0, 80.0, 72.0, 10.0));
                            Console.ResetColor();
                            break;
                        }

                    default:
                        {
                            try
                            {
                                pyEngine = Python.CreateEngine();  //创建Python解释器对象
                                events = new List<Event>();          //事件
                                moves = new List<List<Move>>
                                {
                                    new List<Move>(),
                                    new List<Move>(),
                                    new List<Move>(),
                                    new List<Move>(),
                                    new List<Move>()
                                };    //五条轴的移动

                                Console.ForegroundColor = ConsoleColor.Blue;
                                Console.WriteLine("尝试读取文件" + "visual/" + command + ".json");
                                //文件读取
                                JObject jo = JObject.Parse(File.ReadAllText(@"visual/" + command + ".json"));
                                List<JToken> joevents = jo["events"].ToList();
                                /*
                                 * 事件格式
                                {
                                    "by":false,
                                    "time":128,
                                    "last":70,
                                    "func":"line",
                                    "to":{
                                        "pos":[1,3,8],
                                        "rot":[50,-40]
                                    }
                                }
                                */
                                Console.WriteLine("解析JSON中");
                                //如果是相对运动
                                //获取事件
                                foreach (JToken j in joevents)
                                {
                                    events.Add(new Event((bool)j["by"], (int)j["time"], (int)j["last"], j["moves"].ToList()));
                                }
                                //对五条轴分别处理事件
                                foreach (Event e in events)
                                {
                                    foreach (Move m in e.moves)
                                    {
                                        switch (m.axis)
                                        {
                                            case "x":
                                                {
                                                    moves[0].Add(m);
                                                    break;
                                                }
                                            case "y":
                                                {
                                                    moves[1].Add(m);
                                                    break;
                                                }
                                            case "z":
                                                {
                                                    moves[2].Add(m);
                                                    break;
                                                }
                                            case "rx":
                                                {
                                                    moves[3].Add(m);
                                                    break;
                                                }
                                            case "ry":
                                                {
                                                    moves[4].Add(m);
                                                    break;
                                                }
                                        }
                                    }
                                }
                                //生成开始
                                List<double[]> poslist = new List<double[]>();
                                //初始化坐标序列
                                for (int i = 0; i <= (int)jo["time"]; i++)
                                {
                                    poslist.Add(new double[5]);
                                }
                                //Pos start = new Pos((int)jo["start"]["pos"][0], (int)jo["start"]["pos"][1], (int)jo["start"]["pos"][2], (int)jo["start"]["rot"][0], (int)jo["start"]["rot"][1]);
                                //poslist.Add(start);
                                //分别处理五条轴的变化
                                for (int i = 0; i < 5; i++)
                                {
                                    double start = (double)jo["start"][i];
                                    double movestart = 0;   //移动起始的位置
                                    poslist[0][i] = start;
                                    for (int t = 1; t <= (int)jo["time"]; t++)
                                    {
                                        if (HasMove(t, out Move m, i))
                                        {
                                            //如果有移动事件发生
                                            if (m.time == t)
                                            {
                                                //特别的,是移动开始的时候
                                                movestart = poslist[t - 1][i];
                                                poslist[t][i] = poslist[t - 1][i];
                                            }
                                            else
                                            {
                                                //事件进行中~调用脚本开始计算
                                                poslist[t][i] = m.ptfuncs.func((double)movestart, (double)m.to, (double)m.last, t - m.time);
                                            }
                                        }
                                        else
                                        {
                                            //没有移动事件的发生
                                            poslist[t][i] = poslist[t - 1][i];
                                        }
                                    }
                                }
                                Console.Write("尝试生成函数文件\n|");
                                //生成命令
                                //0~4不嵌套 5~11 12~31 31~86 87~237 238~
                                int each = (int)(Math.Pow((int)jo["time"], 0.2) + 1);     //函数分片
                                                                                          //函数被压缩为5片
                                Directory.CreateDirectory("visual/output/" + command);
                                if ((bool)jo["relative"])
                                {
                                    string o = jo["o"].ToString();     //对齐的中心点
                                    int nextlevel = -1;  //文件个数
                                    FileStream fileStream;
                                    StreamWriter writer = null;
                                    for (int i = 0; i < poslist.Count; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/0_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" DL_camara matches 0 at @e[tag=DL_camara] run tp @p ~ ~ ~ ~ ~
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i + " at "+o+" run tp @a[tag=SV_ctred] ~" + poslist[i][0] + " ~" + poslist[i][1] + " ~" + poslist[i][2] + " ~" + poslist[i][3] + " ~" + poslist[i][4];
                                        writer.Write(line + "\n");
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第二层
                                    //每{each}个函数文件为一组
                                    int thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/1_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each + ".." + ((i + 1) * each - 1) + " run function svisual:visual/output/"+command+"/0_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第三层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/2_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each + ".." + ((i + 1) * each * each - 1) + " run function svisual:visual/output/"+command+"/1_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第四层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/3_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each * each + ".." + ((i + 1) * each * each * each - 1) + " run function svisual:visual/output/"+command+"/2_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第五层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/4_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each * each * each + ".." + ((i + 1) * each * each * each * each - 1) + " run function svisual:visual/output/"+command+"/3_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    //tick层
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        fileStream = new FileStream("visual/output/" + command + "/tick.mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                        writer = new StreamWriter(fileStream);
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_" + command + " SV_timer matches " + i * each * each * each * each * each + ".." + ((i + 1) * each * each * each * each * each - 1) + " run function svisual:visual/output/"+command+"/4_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.WriteLine("execute if score $SV_timer_" + command + " SV_timer matches " + (thislevel + 1) * each * each * each * each * each + ".. run scoreboard players set $SV_timer_" + command + " SV_timer -1");
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|\n");
                                }
                                else
                                {
                                    //绝对距离
                                    int nextlevel = -1;  //文件个数
                                    FileStream fileStream;
                                    StreamWriter writer = null;
                                    for (int i = 0; i < poslist.Count; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/0_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" DL_camara matches 0 at @e[tag=DL_camara] run tp @p ~ ~ ~ ~ ~
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i + " run tp @a[tag=SV_ctred] " + poslist[i][0] + " " + poslist[i][1] + " " + poslist[i][2] + " " + poslist[i][3] + " " + poslist[i][4];
                                        writer.Write(line + "\n");
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第二层
                                    //每{each}个函数文件为一组
                                    int thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/1_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each + ".." + ((i + 1) * each - 1) + " run function svisual:visual/output/"+command+"/0_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第三层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/2_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each + ".." + ((i + 1) * each * each - 1) + " run function svisual:visual/output/"+command+"/1_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第四层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/3_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each * each + ".." + ((i + 1) * each * each * each - 1) + " run function svisual:visual/output/"+command+"/2_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|");
                                    //第五层
                                    //每{each}个函数文件为一组
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        if (i % each == 0)
                                        {
                                            if (!(writer == null))
                                            {
                                                writer.Flush();
                                                writer.Close();
                                            }
                                            //生成文件
                                            nextlevel++;
                                            fileStream = new FileStream("visual/output/" + command + "/4_" + nextlevel + ".mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                            writer = new StreamWriter(fileStream);
                                        }
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_"+command+" SV_timer matches " + i * each * each * each * each + ".." + ((i + 1) * each * each * each * each - 1) + " run function svisual:visual/output/"+command+"/3_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    //tick层
                                    thislevel = nextlevel;
                                    nextlevel = -1;
                                    for (int i = 0; i <= thislevel; i++)
                                    {
                                        fileStream = new FileStream("visual/output/" + command + "/tick.mcfunction", FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                                        writer = new StreamWriter(fileStream);
                                        //写入
                                        //execute if score $SV_timer_"+command+" SV_timer matches 0..5 run function svisual:visual/output/test/0_0
                                        string line = "execute if score $SV_timer_" + command + " SV_timer matches " + i * each * each * each * each * each+ ".." + ((i + 1) * each * each * each * each * each - 1) + " run function svisual:visual/output/"+command+"/4_" + i;
                                        writer.WriteLine(line);
                                    }
                                    writer.WriteLine("execute if score $SV_timer_" + command + " SV_timer matches " + (thislevel+1) * each * each * each * each * each + ".. run scoreboard players set $SV_timer_" + command + " SV_timer -1");
                                    writer.Flush();
                                    writer.Close();
                                    writer = null;
                                    Console.Write("===|\n");
                                }
                                Console.ForegroundColor = ConsoleColor.Green;
                                Console.WriteLine("完成");
                                Console.ResetColor();
                            }
                            catch (Exception e)
                            {
                                Console.ForegroundColor = ConsoleColor.Red;
                                if (e.GetType() == typeof(NullReferenceException))
                                {
                                    Console.WriteLine("JSON格式错误");
                                }
                                else
                                {
                                    Console.WriteLine(e.Message);
                                }
                                Console.ResetColor();
                            }
                            break;
                        }
                }
            }
        }

        static bool HasMove(int time, out Move @move, int axis)
        {
            foreach (Move e in moves[axis])
            {
                if (e.time <= time && time <= e.time + e.last)
                {
                    //如果时间线上有事件
                    @move = e;
                    return true;
                }
            }
            @move = new Move();
            return false;
        }
    }
}

发表回复