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