diff --git a/_docs/api.md b/_docs/api.md
index cdb7332a..75f925c3 100644
--- a/_docs/api.md
+++ b/_docs/api.md
@@ -1013,12 +1013,13 @@ lose: fn(reason?: string)
moveEnemyOnPoint: fn(fromX: number, fromY: number, toX: number, toY: number, floorId?: string)
将某个点已经设置的敌人属性移动到其他点
-moveImage: fn(code: number, to?: [number], opacityVal?: number, time?: number, callback?: fn())
+moveImage: fn(code: number, to?: [number], opacityVal?: number, moveMode?: string, time?: number, callback?: fn())
移动一张图片并/或改变其透明度
例如:core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明
code: 图片编号
to: 新的左上角坐标,省略表示原地改变透明度
opacityVal: 新的透明度,省略表示不变
+moveMode: 移动模式
time: 移动用时,单位为毫秒。不填视为1秒
callback: 图片移动完毕后的回调函数,可选
@@ -2012,6 +2013,9 @@ textImage: fn(content: string, lineHeight?: number) -> image
工具函数库,里面有各个样板中使用到的工具函数。
```text
+applyEasing: fn(mode?: string) -> fn(t: number) -> number
+获得变速移动曲线
+
arrayToRGB: fn(color: [number]) -> string
颜色数组转字符串
例如:core.arrayToRGB([102, 204, 255]); // "#66ccff"
diff --git a/_docs/instruction.md b/_docs/instruction.md
index d002cd5c..a19e1659 100644
--- a/_docs/instruction.md
+++ b/_docs/instruction.md
@@ -119,7 +119,7 @@ data为整个指令对象,x和y为当前点坐标,prefix为独立开关的
* 此指令对应`core.showImage()`函数,编号真正的意义,详见[个性化](personalization)
2. **清除图片:**如题,需要指定要清除的图片编号和淡出时间。
* 此指令对应`core.hideImage(code, time, callback)`
-3. **图片移动:**其实还包括了透明度渐变,“终点像素位置”指移动结束后的图片在视野中的左上角像素坐标(不填则表示单纯的透明度渐变),“不透明度”指渐变结束后的新的不透明度(不填表示单纯的移动),移动和透明度渐变都是匀速直线进行的。对应`core.moveImage(code, to, opacityVal, time, callback)`
+3. **图片移动:**其实还包括了透明度渐变,“终点像素位置”指移动结束后的图片在视野中的左上角像素坐标(不填则表示单纯的透明度渐变),“不透明度”指渐变结束后的新的不透明度(不填表示单纯的移动)。对应`core.moveImage(code, to, opacityVal, moveMode, time, callback)`
4. **显示或清除动图:**需要填写动图的文件名(带.gif后缀),“起点像素位置”含义如前且必须填写,可以双击指令块来预览第一帧的效果。
* 动图不支持淡入淡出和伸缩移动,如果不填任何参数则清除所有动图。
* 该指令对应`core.showGif(name, x, y)`函数。
@@ -462,7 +462,7 @@ core.insertAction({"type": "changeFloor", "floorId": "MT" + core.rand2(20)})
3. **设置和移动视角:**设置视角支持双击从地图选点,不填坐标则重置视角。
* 移动视角的动画时间为每动一格的时间,步伐数组只支持“上下左右”。
* 请注意,勇士重绘时(`core.drawHero()`函数)视角也会随之重置。所以视角变化后勇士的坐标、朝向、显隐、行走图(事件和API都提供了不重绘参数)和跟随者情况暂时都不要动。
- * 实际调用`core.setViewport(x, y)`和`core.moveViewport(steps, time, callback)`,其中前者的自变量为【像素坐标】且不必为32的倍数,必要时可作为原生js使用来实现特殊的演出。
+ * 实际调用`core.setViewport(x, y)`和`core.moveViewport(x, y, moveMode, time, callback)`,其中前者的自变量为【像素坐标】且不必为32的倍数,必要时可作为原生js使用来实现特殊的演出。
4. **显隐状态栏:**如题,如果隐藏状态栏期间勇士需要恢复行动,则建议不隐藏竖屏工具栏以方便手机玩家。
* 实际调用`core.showStatusBar()`和`core.hideStatusBar(showToolbox)`
5. **显隐勇士:**如题,动画时间为淡入淡出时间,异步勾选框用法如前。
diff --git a/_server/CodeMirror/defs.js b/_server/CodeMirror/defs.js
index faabcec7..201ebd6e 100644
--- a/_server/CodeMirror/defs.js
+++ b/_server/CodeMirror/defs.js
@@ -2530,7 +2530,7 @@ var terndefs_f6783a0a_522d_417e_8407_94c67b692e50 = [
},
"moveViewport": {
"!doc": "移动视野范围",
- "!type": "fn(x: number, y: number, time?: number, callback?: fn())"
+ "!type": "fn(x: number, y: number, moveMode?: string, time?: number, callback?: fn())"
},
"syncLoad": {
"!doc": "从服务器加载存档",
@@ -2740,6 +2740,10 @@ var terndefs_f6783a0a_522d_417e_8407_94c67b692e50 = [
"y": "number"
}
},
+ "applyEasing": {
+ "!doc": "获得变速移动曲线",
+ "!type": "fn(mode?: string) -> fn(t: number) -> number"
+ },
"clamp": {
"!doc": "将x限定在[a,b]区间内,注意a和b可交换
例如:core.clamp(1200, 1, 1000); // 1000
x: 原始值,!x为true时x一律视为0
a: 下限值,大于b将导致与b交换
b: 上限值,小于a将导致与a交换",
"!type": "fn(x: number, a: number, b: number) -> number"
@@ -3802,7 +3806,7 @@ var terndefs_f6783a0a_522d_417e_8407_94c67b692e50 = [
},
"moveImage": {
"!doc": "移动一张图片并/或改变其透明度
例如:core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明
code: 图片编号
to: 新的左上角坐标,省略表示原地改变透明度
opacityVal: 新的透明度,省略表示不变
time: 移动用时,单位为毫秒。不填视为1秒
callback: 图片移动完毕后的回调函数,可选",
- "!type": "fn(code: number, to?: [number], opacityVal?: number, time?: number, callback?: fn())"
+ "!type": "fn(code: number, to?: [number], opacityVal?: number, moveMode?: string, time?: number, callback?: fn())"
},
"openSettings": {
"!doc": "点击设置按钮时的操作",
diff --git a/_server/MotaAction.g4 b/_server/MotaAction.g4
index 8e1c52f2..61e0ff03 100644
--- a/_server/MotaAction.g4
+++ b/_server/MotaAction.g4
@@ -2053,13 +2053,13 @@ return code;
*/;
setViewport_s
- : '设置视角' '左上角坐标' 'x' PosString? ',' 'y' PosString? '动画时间' Int '不等待执行完毕' Bool Newline
+ : '设置视角' '左上角坐标' 'x' PosString? ',' 'y' PosString? '移动方式' MoveMode_List '动画时间' Int '不等待执行完毕' Bool Newline
/* setViewport_s
tooltip : setViewport: 设置视角
helpUrl : /_docs/#/instruction
-default : ["","",0,false]
+default : ["","","",0,false]
selectPoint : ["PosString_0", "PosString_1"]
colour : this.soundColor
var loc = '';
@@ -2068,18 +2068,19 @@ if (PosString_0 && PosString_1) {
}
Int_0 = Int_0 ?(', "time": '+Int_0):'';
Bool_0 = Bool_0?', "async": true':'';
-var code = '{"type": "setViewport"'+loc+Int_0+Bool_0+'},\n';
+MoveMode_List_0 = (MoveMode_List_0!=='') ? (', "moveMode": "'+MoveMode_List_0+'"'):'';
+var code = '{"type": "setViewport"'+loc+MoveMode_List_0+Int_0+Bool_0+'},\n';
return code;
*/;
setViewport_1_s
- : '设置视角' '增量坐标' 'dx' PosString? ',' 'dy' PosString? '动画时间' Int '不等待执行完毕' Bool Newline
+ : '设置视角' '增量坐标' 'dx' PosString? ',' 'dy' PosString? '移动方式' MoveMode_List '动画时间' Int '不等待执行完毕' Bool Newline
/* setViewport_1_s
tooltip : setViewport: 设置视角
helpUrl : /_docs/#/instruction
-default : ["0","0",0,false]
+default : ["0","0","",0,false]
colour : this.soundColor
var loc = '';
if (PosString_0 && PosString_1) {
@@ -2087,7 +2088,8 @@ if (PosString_0 && PosString_1) {
}
Int_0 = Int_0 ?(', "time": '+Int_0):'';
Bool_0 = Bool_0?', "async": true':'';
-var code = '{"type": "setViewport"'+loc+Int_0+Bool_0+'},\n';
+MoveMode_List_0 = (MoveMode_List_0!=='') ? (', "moveMode": "'+MoveMode_List_0+'"'):'';
+var code = '{"type": "setViewport"'+loc+MoveMode_List_0+Int_0+Bool_0+'},\n';
return code;
*/;
@@ -2182,19 +2184,20 @@ return code;
moveImage_s
: '图片移动' '图片编号' NInt '终点像素位置' 'x' PosString? 'y' PosString? BGNL?
- '不透明度' EvalString? '移动时间' Int '不等待执行完毕' Bool Newline
+ '不透明度' EvalString? '移动方式' MoveMode_List '移动时间' Int '不等待执行完毕' Bool Newline
/* moveImage_s
tooltip : moveImage:图片移动
helpUrl : /_docs/#/instruction
-default : [1,'','','',500,false]
+default : [1,'','','','',500,false]
var toloc = '';
if (PosString_0 && PosString_1)
toloc = ', "to": ['+PosString_0+','+PosString_1+']';
EvalString_0 = (EvalString_0!=='') ? (', "opacity": '+EvalString_0):'';
+MoveMode_List_0 = (MoveMode_List_0!=='') ? (', "moveMode": "'+MoveMode_List_0+'"'):'';
var async = Bool_0?', "async": true':'';
-var code = '{"type": "moveImage", "code": '+NInt_0+toloc+EvalString_0+',"time": '+Int_0+async+'},\n';
+var code = '{"type": "moveImage", "code": '+NInt_0+toloc+MoveMode_List_0+EvalString_0+',"time": '+Int_0+async+'},\n';
return code;
*/;
@@ -3962,6 +3965,10 @@ Move_List
: '上'|'下'|'左'|'右'|'前'|'后'|'左上'|'左下'|'右上'|'右下'|'设置速度'
/*Move_List ['up','down','left','right','forward','backward','leftup','leftdown','rightup','rightdown','speed']*/;
+MoveMode_List
+ : '匀速移动'|'缓入快出'|'快入缓出'|'缓入缓出'
+ /*MoveMode_List ['', 'easeIn', 'easeOut', 'easeInOut']*/;
+
NameMap_List
: '确定'|'取消'|'操作失败'|'光标移动'|'打开界面'|'读档'|'存档'|'获得道具'|'回血'|'炸弹'|'飞行器'|'开关门'|'上下楼'|'跳跃'|'破墙镐'|'阻激夹域'|'穿脱装备'
/*NameMap_List ['确定','取消','操作失败','光标移动','打开界面','读档','存档','获得道具','回血','炸弹','飞行器','开关门','上下楼','跳跃','破墙镐','阻激夹域','穿脱装备']*/;
diff --git a/_server/MotaActionParser.js b/_server/MotaActionParser.js
index 392353c5..7fbe855f 100644
--- a/_server/MotaActionParser.js
+++ b/_server/MotaActionParser.js
@@ -516,11 +516,11 @@ ActionParser.prototype.parseAction = function() {
case "setViewport": // 设置视角
if (data.dxy) {
this.next = MotaActionBlocks['setViewport_1_s'].xmlText([
- data.dxy[0],data.dxy[1],data.time||0,data.async||false,this.next]);
+ data.dxy[0],data.dxy[1],data.moveMode||'', data.time||0,data.async||false,this.next]);
} else {
data.loc = data.loc||['',''];
this.next = MotaActionBlocks['setViewport_s'].xmlText([
- data.loc[0],data.loc[1],data.time||0,data.async||false,this.next]);
+ data.loc[0],data.loc[1],data.moveMode||'', data.time||0,data.async||false,this.next]);
}
break;
case "vibrate": // 画面震动
@@ -551,7 +551,7 @@ ActionParser.prototype.parseAction = function() {
case "moveImage": // 移动图片
data.to=data.to||['','']
this.next = MotaActionBlocks['moveImage_s'].xmlText([
- data.code, data.to[0], data.to[1], data.opacity, data.time||0, data.async||false, this.next]);
+ data.code, data.to[0], data.to[1], data.opacity, data.moveMode||'', data.time||0, data.async||false, this.next]);
break;
case "showGif": // 显示动图
data.loc=data.loc||['','']
diff --git a/libs/control.js b/libs/control.js
index f064cd1b..f80eb677 100644
--- a/libs/control.js
+++ b/libs/control.js
@@ -968,23 +968,25 @@ control.prototype.setViewport = function (px, py) {
}
////// 移动视野范围 //////
-control.prototype.moveViewport = function (x, y, time, callback) {
+control.prototype.moveViewport = function (x, y, moveMode, time, callback) {
time = time || 0;
time /= Math.max(core.status.replay.speed, 1)
- var per_time = 10, step = parseInt(time / per_time);
- if (step <= 0) {
+ var per_time = 10, step = 0, steps = parseInt(time / per_time);
+ if (steps <= 0) {
this.setViewport(32 * x, 32 * y);
if (callback) callback();
return;
- }
+ }
var px = core.clamp(32 * x, 0, 32 * core.bigmap.width - core.__PIXELS__);
var py = core.clamp(32 * y, 0, 32 * core.bigmap.height - core.__PIXELS__);
- var dx = (px - core.bigmap.offsetX) / step, dy = (py - core.bigmap.offsetY) / step;
+ var cx = core.bigmap.offsetX;
+ var cy = core.bigmap.offsetY;
+ var moveFunc = core.applyEasing(moveMode);
var animate=window.setInterval(function() {
- core.setViewport(core.bigmap.offsetX + dx, core.bigmap.offsetY + dy);
- step--;
- if (step <= 0) {
+ step++;
+ core.setViewport(cx + moveFunc(step / steps) * (px - cx), cy + moveFunc(step / steps) * (py - cy));
+ if (step == steps) {
delete core.animateFrame.asyncId[animate];
clearInterval(animate);
core.setViewport(px, py);
diff --git a/libs/events.js b/libs/events.js
index 2cd5cfc3..8b2952c3 100644
--- a/libs/events.js
+++ b/libs/events.js
@@ -1449,7 +1449,7 @@ events.prototype._action_setViewport = function (data, x, y, prefix) {
data.loc = this.__action_getLoc(data.loc, x, y, prefix);
}
console.log(data.loc);
- this.__action_doAsyncFunc(data.async, core.moveViewport, data.loc[0], data.loc[1], data.time);
+ this.__action_doAsyncFunc(data.async, core.moveViewport, data.loc[0], data.loc[1], data.moveMode, data.time);
}
events.prototype._action_move = function (data, x, y, prefix) {
@@ -1567,7 +1567,7 @@ events.prototype._action_showGif = function (data, x, y, prefix) {
events.prototype._action_moveImage = function (data, x, y, prefix) {
if (this.__action_checkReplaying()) return;
- this.__action_doAsyncFunc(data.async, core.moveImage, data.code, data.to, data.opacity, data.time);
+ this.__action_doAsyncFunc(data.async, core.moveImage, data.code, data.to, data.opacity, data.moveMode, data.time);
}
events.prototype._precompile_moveImage = function (data) {
@@ -2988,7 +2988,7 @@ events.prototype.showImage = function (code, image, sloc, loc, opacityVal, time,
return;
}
core.setOpacity(name, 0);
- this.moveImage(code, null, opacityVal, time, callback);
+ this.moveImage(code, null, opacityVal, null, time, callback);
}
////// 隐藏图片 //////
@@ -3000,14 +3000,14 @@ events.prototype.hideImage = function (code, time, callback) {
if (callback) callback();
return;
}
- this.moveImage(code, null, 0, time, function () {
+ this.moveImage(code, null, 0, null, time, function () {
core.deleteCanvas(name);
if (callback) callback();
});
}
////// 移动图片 //////
-events.prototype.moveImage = function (code, to, opacityVal, time, callback) {
+events.prototype.moveImage = function (code, to, opacityVal, moveMode, time, callback) {
time = time || 1000;
to = to || [];
var name = "image" + (code + 100);
@@ -3028,21 +3028,22 @@ events.prototype.moveImage = function (code, to, opacityVal, time, callback) {
this._moveImage_moving(name, {
fromX: fromX, fromY: fromY, toX: toX, toY: toY, opacity: opacity, toOpacity: toOpacity,
- time: time / Math.max(core.status.replay.speed, 1)
+ moveMode: moveMode, time: time / Math.max(core.status.replay.speed, 1)
}, callback)
}
events.prototype._moveImage_moving = function (name, moveInfo, callback) {
- var per_time = 10, step = 0, steps = parseInt(moveInfo.time / 10);
+ var per_time = 10, step = 0, steps = parseInt(moveInfo.time / per_time);
if (steps <= 0) steps = 1;
var fromX = moveInfo.fromX, fromY = moveInfo.fromY, toX = moveInfo.toX, toY = moveInfo.toY,
opacity = moveInfo.opacity, toOpacity = moveInfo.toOpacity;
var currX = fromX, currY = fromY, currOpacity = opacity;
+ var moveFunc = core.applyEasing(moveInfo.moveMode);
var animate = setInterval(function () {
step++;
currOpacity = opacity + (toOpacity - opacity) * step / steps;
- currX = parseInt(fromX + (toX - fromX) * step / steps);
- currY = parseInt(fromY + (toY - fromY) * step / steps);
+ currX = parseInt(fromX + (toX - fromX) * moveFunc(step / steps));
+ currY = parseInt(fromY + (toY - fromY) * moveFunc(step / steps));
core.setOpacity(name, currOpacity);
core.relocateCanvas(name, currX, currY);
if (step == steps) {
diff --git a/libs/utils.js b/libs/utils.js
index bc70f8bd..671fcadf 100644
--- a/libs/utils.js
+++ b/libs/utils.js
@@ -480,6 +480,27 @@ utils.prototype.formatBigNumber = function (x, onMap) {
return c + x;
}
+////// 变速移动 //////
+utils.prototype.applyEasing = function(name) {
+ var list = {
+ "easeIn": function(t) {
+ return Math.pow(t, 3);
+ },
+ "easeOut": function(t) {
+ return 1 - Math.pow(1 - t, 3);
+ },
+ "easeInOut": function(t) {
+ // easeInOut试了一下感觉二次方效果明显点
+ if (t < 0.5) return Math.pow(t, 2) * 2;
+ else return 1 - Math.pow(1 - t, 2) * 2;
+ },
+ "linear": function(t) {
+ return t
+ }
+ }
+ return list[name] || list.linear;
+}
+
////// 数组转RGB //////
utils.prototype.arrayToRGB = function (color) {
if (!(color instanceof Array)) return color;
diff --git a/runtime.d.ts b/runtime.d.ts
index e0a6c239..941f0eec 100644
--- a/runtime.d.ts
+++ b/runtime.d.ts
@@ -646,7 +646,7 @@ declare class control {
setViewport(px?: number, py?: number): void
/** 移动视野范围 */
- moveViewport(x: number, y: number, time?: number, callback?: () => any): void
+ moveViewport(x: number, y: number, moveMode?: string, time?: number, callback?: () => any): void
/** 更新跟随者坐标 */
updateFollowers(): void
@@ -976,14 +976,15 @@ declare class events {
/**
* 移动一张图片并/或改变其透明度
- * @example core.moveImage(1, undefined, 0.5); // 1秒内把1号图片变为50%透明
+ * @example core.moveImage(1, null, 0.5); // 1秒内把1号图片变为50%透明
* @param code 图片编号
* @param to 新的左上角坐标,省略表示原地改变透明度
* @param opacityVal 新的透明度,省略表示不变
+ * @param moveMode 移动模式
* @param time 移动用时,单位为毫秒。不填视为1秒
* @param callback 图片移动完毕后的回调函数,可选
*/
- moveImage(code: number, to?: [number?, number?], opacityVal?: number, time?: number, callback?: () => void): void
+ moveImage(code: number, to?: [number?, number?], opacityVal?: number, moveMode?: string, time?: number, callback?: () => void): void
/**
* 绘制一张动图或擦除所有动图
@@ -2381,6 +2382,9 @@ declare class utils {
*/
formatBigNumber(x: number, onMap?: boolean): string
+ /** 变速移动 */
+ applyEasing(mode?: string): (number) => number;
+
/**
* 颜色数组转十六进制
* @example core.arrayToRGB([102, 204, 255]); // "#66ccff",加载画面的宣传色