在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; } } }