【UTの底层代码】怪物动画(摆动)参数详解
毫无技术的鸽子
2026年04月11日 22:29

众所周知,怪物总是一个多动症。它们不会原地干站着:和站军姿一样。而是会因为呼吸、眨眼、位置轻微变化等而产生图片位移和变化。

好处也很明显:精致的角色像死物,而呼吸、重心位移等细微动态可以让角色更像活生生的声生命……巴拉巴拉的,剩下的不多说了,直接切入主题

为什么这个专栏会出现,答案是:如果你想做一个原作向同人,想要抄下来原版的运动逻辑让它更有原版味道的情况下你会怎么办?诚然,你可以选择抄代码,如果你的手头没有UNDERTALE和UTMT怎么办呢?又或者,如果你拿到了代码但是看不懂而无法复刻呢?

于是,这篇专栏就出现了,它更像是一个数据汇总文章,而不是一个说明性教程。

观前提醒,本专栏包含大量代码、数学讲解和复现,如有不适请停止观看

如果你对toby丝滑的动画怎么制作的感兴趣,也可以继续阅读,但不是读代码


一、组成拆解

一、遗迹组

1.Froggit(蛙吉特)

组成拆解:由 头部 + 身体(也可以叫腿部) 两部分组合而成。

头部:由Gamemaker Studio 1.4自带的Path系统进行画线循环控制,如图所示:

名称:path_froghead

没错,它确实是直接由Path系统完成位置移动的,我们仍然有办法复现它:

代码块
C
自动换行
复制代码
// 路径点展示
// 模式:循环模式,播放完重播
// 步长:0.5px
path_points = [
    [264, 300, 100],    // 点0 
    [272, 296, 100],    // 点1
    [280, 300, 100],    // 点2
    [272, 304, 100],    // 点3
    [272, 292, 100]     // 点4
];
复制成功

我写了什么?

path_points是记录所有点位的一个表(或者说数组),内部所有东西都围绕[x坐标,y坐标,速度]来逐个填入。至于怎么实现点位操作,需要自己写,因为每个引擎不一样,这里只提供原版的数值参考。上方给出了步长,每帧需要移动这么多的距离,但是请注意,这是30f的情况下,现阶段绝大部分ut游戏都是60f,这个数需要除以2.

哇,好头晕啊,居然有代码,不看了不看了。

当然,再次提醒,这只是开胃小菜,如果适应不了,请及时退出!

腿部:由于站位和怪物位置有关系,这里没有固定值(我会在下面贴部分图片,来看看为什么说是不固定的),但是y值确定是136.

最上方是froggit和whimsun的配合,然后是小模怪,然后是三个小模怪,观察可以发现,没有任何规律,全都是硬编码的(说人话就是被写死的)


2.Whimsun(忧郁虫虫)

组成拆解:单个实例移动 构成。

名称:path_whimsun(我请问了怎么这么长)

路径点复现:

代码块
C
自动换行
复制代码
// 路径点展示
// 模式:循环模式,播放完重播
// 步长:0.5px
path_points = [
    [176, 96, 100],    // 点0 
    [176, 88, 100],    // 点1
    [176, 104, 100],    // 点2
];
复制成功

3.Loox(卢克眼)

组成拆解:保持原地不动,只是切换眨眼动画(一共5帧图片)


4.Migosp(福音蟑螂)

组成拆解:不进行移动,只是原地的图片切换,切换样子像是随机无意识的动爪子。

下面是逻辑:

代码块
C
自动换行
复制代码
// Alarm8
image_speed = 0; // 停下动画切换

// Alarm7
image_speed = 0.5
if (mercymod < 0) // 如果还不可饶恕
{
    alarm[8] = 10

    // 随机30+U(0, 40)的时间再次执行这一片代码
    alarm[7] = 30 + random(40) 
}
else
    image_speed = 0.1 // 这里是福音蟑螂可饶恕时不断蹲起

// Alarm6,这里是福音蟑螂开心时不断蹲起
if (mercymod > 0)
{
    image_speed = 0.1
}
复制成功

主体解析:福音蟑螂会随机30 + U(0, 40)(U指的是在这段区间内均匀随机分布的一个数)的时间执行每0.5秒切换一次图片的动画进程。同时会在十帧后重新关闭动画静止站立。上述进程会不断循环,直到切换状态为happy


5.Vegetoid(蔬菜兽)

组成拆解:仅本身,不进行移动,只进行四张图片切换


6.Moldsmal(小模怪)

组成拆解:单个图片进行竖直方向的伸缩(image_yscale)。

代码块
C
自动换行
复制代码
if (image_yscale < 0.9)
    scalevalue = 0.01
if (image_yscale > 1.1)
    scalevalue = -0.01
image_yscale += scalevalue
y -= (102 * scalevalue)
复制成功

如果图片伸缩小于0.9倍,那么放大倍率为0.01;如果图片伸缩大于1.1倍,那么放大倍率为-0.01(或者说缩小倍率0.01)。y值随着scalevalue的变化有y = y - 102 * sv的变化公式。

实际制作的时候可以考虑把y设置在最下方,然后只改变yscale,就没必要改变y值了。


7.Dummy(训练人偶/假人)

组成拆解:它本身且无任何额外动作,除了砍击切换图片


8.Napstablook(纳普斯特幽灵)

组成拆解:它本身进行眼睛图片切换,仅在特定操作序列之后逐帧播放戴帽子的图片


下面是困难模式,事先声明,核心三人组在核心题目下,而不是这里,这里仅整理遗迹特有的

9.【Hardmode】Moldessa(终极模怪)

组成拆解:脸(face)+ 底座(stem)+ 头上的尖锐部分(headthing)+ 两个胳膊(arm)。

公式整理如下:

代码块
PlainText
自动换行
复制代码
底座:
x:x
y:y + 114
x伸缩公式:2 + 0.1 * sin(time / 7)
y伸缩公式:2 - 0.1 * sin(time / 7)

三个脸上的物品:
face[0]x:x + 13 * sin(time / 16)) + sin(time / 6)
face[0]y:y + 56 + 18 * cos(time / 16) + cos(time / 6)
face[0]角度:time * 3 + 4 * sin(time / 6)

face[1]和face[2]所做操作均为改变了初相,face[1]为time + 36,face[2]为time + 65
face[1]和face[2]的角度均相对于face[0]加了180

头上的尖锐部分:
x公式:x
y公式:y - 4 + 2 * sin(time / 7)

两个胳膊:
x公式:x - 12 或 x + 14
y公式:y
角度公式:-5 + 5 * sin(time / 7) 或 5 - 5 * sin(time / 7)
复制成功

10.【Hardmode】Parsnik(欧防风兽)

组成拆解:蔬菜头部(head) + 头环(headwrap) + 五条单独的蛇(snake1~5)

公式整理:

代码块
PlainText
自动换行
复制代码
头部:
x:x + 24
y:y + 46

头环:
x:x + 14
y:y + 24 + 2 * sin(time / 5 + 1.5)

1号蛇(最左侧开始):
x:x + 32
y:y + 52 + 2 * sin(time / 5 + 1.5)
角度:6 * sin(time / 5)

2号蛇:
x:x + 38
y:y + 46 + 1.5 * sin(time / 5 + 0.5)
角度:2 * sin(time / 5)

3号蛇(中间)
x:x + 52
y:y + 50 + 1.5 * sin(time / 5 + 1)
角度:0

4号蛇
x:x + 60
y:y + 46 + 1.5 * sin(time / 5 + 1.5)
角度:-2 * sin(time / 5)

5号蛇(最右侧)
x:x + 74
y:y + 52 + 1.5 * sin(time / 5 + 2)
角度:-6 * sin(time / 5 + 2)
复制成功

11.【Hardmode】Migospel(哑剧蟑螂)

组成拆解:身体(body)(包括一整个外壳和两个缝隙)+ 腿部(leg)+ 嘴和手(mouthA/B/C)+ 长得像面罩的眼睛(eye)+ 大翅膀(wingA)+ 小翅膀(wingB)

公式整理:

代码块
PlainText
自动换行
复制代码
左大翅膀:
x:x + 2 * sin(time / 4)
y:y + 6 + 52 + 2 * sin(time / 4)

右大翅膀y和左大翅膀一样:
x:x + 52 - 2 * sin(time / 4)

左小翅膀x和左大翅膀一样:
y:y + 16 + 56 - 2 * sin(time / 4)

右小翅膀x与大翅膀一样,y与小翅膀一样

左腿:
x:x
y:y + 100
右腿:
x:x + 50
y:y + 100

身体:
x:x
y:floor(2 * sin(time / 6))
复制成功

二、雪域组

1.Snowdrake(雪铁龙)

组成拆解:头部(head)+ 身体(body)+ 腿部(legs)

公式整理:

代码块
PlainText
自动换行
复制代码
腿部:
x:x
y:y

身体:
x:x
y:y + (head的y - ys) / 3

头部:
x:x
y:
if (down == 1)
{
    y += (a / 2)
    a += 0.05
    if (a >= 1)
        down = 0
}
if (down == 0)
{
    y += (a / 2)
    a -= 0.05
    if (a <= -1)
        down = 1
}
复制成功

头部分段函数解释:如果要往下走,那么y每次增加a/2,a每次加0.05,如果a大于等于1,那么开始往上走;如果往上走,那么y每次减少a/2,a每次增加0.05.

总的来说,就是头部的增量是身体增量的三倍,这样制造出头比身体移动的快的感觉。


2.Chilldrake(小酷龙)

公式与雪铁龙完全一致。


3.Ice Cap(冰帽盖)

组成拆解:仅本身进行图片切换。


4.Gyftrot(礼物鹿)

组成拆解:身体(body)+ 眼睛和眼睛上半部分(head)+ 眼睛和眼镜下半部分(mouth)+ 云雾(cloud)+ 礼物(gift)

公式整理:

代码块
PlainText
自动换行
复制代码
礼物:
永远保持跟随头部的x, y

身体:
保持不动

眼睛和眼睛上半部分:
如果x比初始创建的位置+8要小,那么水平速度为1,反之为-1
同时在8 + U(0, 10)时间范围内将水平速度设置为0
开始跟随嘴巴的速度移动

眼睛和眼睛下半部分:
和上半部分相反
复制成功

这里说一下礼物鹿头和嘴巴的效果是什么情况:首先,礼物鹿的头分成三个节点,它会在这三个位置停下:x-8, x, x+8其次,礼物鹿在x到x+8这段范围是嘴巴跟随头部移动的;而在x-8到x这段区域,是头部跟随嘴巴移动的。二者互相在各自范围作用,形成了在三点之间往返运动的效果。


5.Doggo(遁狗)

组成拆解:身体(body)+ 头部(head)+ 双臂(arms)+ 尾巴(tail)

公式:

代码块
PlainText
自动换行
复制代码
身体,尾巴,头部均保持不动:

手臂:
若sin(time / 4.5) > 0,那么手臂y增加0.8;
反之减少0.8
复制成功

6.Dogi(狗夫妇)

组成拆解:mandog + womandog

这两位的图片都是循环播放的。


7.Lesser Dog(小犬汪)

组成拆解:全身(lesserdog)+ 尾巴(tail)+ 头(head)

公式整理:

代码块
PlainText
自动换行
复制代码
以防你不知道:
脖子的绘制其实是使用矩形完成的
尾巴会随着饶恕次数增加而加快图片切换速度

假设饶恕次数为mercymod / 900
头y:(mercymod - 500) / 4 + 40
复制成功

8.Greater Dog(大犬汪)

组成拆解:图片本身

公式整理:

代码块
PlainText
自动换行
复制代码
无视第一次,图片大小倍数为3倍
第二次为4倍
第三次为5倍
复制成功

9.Jerry(杰瑞)

组成拆解:仅图片切换

若想要进行复现,头部为随机触发的三角函数,振幅为1,震颤一次后归位。

我们视角的右手可以通过利用三角函数改变xscale来实现。


10.Glyde(老滑头)

组成拆解:身体(body)+ 左翅膀(leftwing)+ 右翅膀(rightwing)+ 尾巴上的圆圈天线(antenna)

以防你不知道,antenna的意思就是天线,这是一个常用单词。

公式整理:

代码块
PlainText
自动换行
复制代码
老滑头的计时器比较特殊,这里特地说明:
这是一个套了三角函数的特殊计时器
siner = 2 * cos(time / 24)

还有套了三角函数计时器的三角函数计时器
g = sin(siner / 6)
gg = sin(siner / 12)
---------------------------

身体:
x:不变
y:ys + 8 * sin(siner / 12)

右翅膀:
x:x + 46
y:y + 106 + 2 * g
xscale:2
yscale:1.7 - g * 0.3

左翅膀:
x:x + 82
y:y + 174
xscale:1.95 - g * 0.05
yscale:1.8 - g * 0.2

天线:
x:x + 54 + gg * 2
y:y + 4
角度:20 - gg * 20
复制成功

11.Mad Mew Mew(愤怒喵喵)

组成拆解:由于我这个版本没有愤怒喵喵,率先推测是单个图片构成


三、瀑布组

1.Aaron(亚伦)

组成拆解:单个图片

公式整理:

代码块
PlainText
自动换行
复制代码
y:ys + 9 * sin(timer / 30)
复制成功

2.Woshua(约刷亚)

组成拆解:身体外壳(body)+ 鸭子(duck)+ 头部(head)+ 脸(face)+ 尾巴摇杆(tail)+ 水(water)+ 玻璃罩(hanger)

公式整理:

代码块
PlainText
自动换行
复制代码
玻璃罩:保持不动
鸭子:
x:x + 46
y:每15帧切换上下状态,每次鸭子都会以每帧1个像素做上下移动

水1:
x:x + 40 - 4 * sin(time / 10)
水2:
x:x + 40 + 4 * sin(time / 10)
两个水的y相同

摇杆(发条):
x:x + 88
y:y + 50

头部和脸部很难直接写出公式,这里简单介绍一下
脸和头的运动方向相反,而且脸的速度是头的二倍
复制成功

如果我要写的话,那就太逆天了:


3.Moldbygg(大模怪)

组成拆解:果冻花(up)+ 底层(stem)

公式整理:

代码块
PlainText
自动换行
复制代码
底座:
x:x + 8 * sin(timer / 10)或x + 8 * cos(timer / 10)

果冻花:
x:x + 4 * cos(timer / 10)
复制成功

4.Temmie(提米)

有人曾说,提米的身体和脸是两个生物。现在,让我们从代码层面看看这两个生物的底层逻辑是什么

组成拆解:身体(body)+ 脸部(face)

公式整理:

代码块
PlainText
自动换行
复制代码
身体:
x:xs + random(r)
y:ys + random(r)

脸依旧是这个公式,加上random(r)
但是有一点,当时间大于10秒,而且脸小于身体x+100的位置
脸会以每帧0.01像素的速度向右偏移
如果你要看到脸跑到最大位置,这个时间大概是:
100 / 0.01 = 10000帧 = 10000/30 = 333.33秒
你需要挂机五分半才能看到脸停下了

当然,你不能忽略了身体也在乱动的事实

关于r:
当r小于2的时候,r会每帧增加0.01
如果攻击,r清零重新计时
复制成功

好吧,看来古老的传说是真的呢~


5.Mad Dummy(愤怒人偶)

组成拆解:头部(head)+ 上半躯干(torso)+ 下半躯干(legs)+ 底座(base)

公式整理:

代码块
PlainText
自动换行
复制代码
一样的,在此之前,我们先看看大表哥的计时器
rot = 30 * sin(time / 6)

-----------------------

头部:
x:x + 5
y:y + 75
角度:-rot

上半躯干:
x:x
y:y + 35 + rot / 4
角度:rot / 2

下半躯干:
x:x + 5
y:x + 65
角度:rot / 3

底座:
x:x - rot / 3
y:y + rot / 3
角度:rot
复制成功

大表哥越晃越快的操作也很简单,只需要增加rot的值即可,比如rot *= 3


6.Shyren(害羞塞壬)

组成拆解:底座(agent)+ 头部(hide/swim/sing)

公式整理:

代码块
PlainText
自动换行
复制代码
底座保持不动
头部:
y:sy + 4 * sin(time / 30)
复制成功

四、热域组

1.Vulkin(迷你火山)

组成拆解:火山外壳(body)+ 脸部(face)+ 溢出的岩浆(lava)+ 前脚(feetfront)+ 后脚(feetback)

公式整理:

代码块
PlainText
自动换行
复制代码
后脚:
x:x + 2 + 2 * cos(time / 6)
y:y + 102 + 2 * sin(time / 6)

前脚:
x:x + 2 + 2 * sin(time / 6)
y:y + 102 + 2 * cos(time / 6)

脸部:
x:x + 26 + 18 + 7 * sin(time / 12)
y:y + 50
xscale:2 - 0.2 * abs(sin(time / 12))

岩浆:
x:x + 42
y:y + 10

身体:
x:x
y:y
复制成功

2.Tsunderplane(傲娇飞机)

组成拆解:飞机本体(plane)+ 荷叶边洋帽(hat,疑似ZUN帽)+ “尾气”(cloud)+ 腮红(blush)

关于疑似ZUN帽:

我怎么看怎么像大小姐戴的那种,先不管了。

公式整理:

代码块
PlainText
自动换行
复制代码
身体:
x:x + 94
y:y + 76 + 3 * sin(time / 7)

帽子:
x:x + 154
y:y + 90 + 3 * sin(time / 7)

腮红:
x:x + 94 + 2 * sin(time / 7)
y:y + 76 + 4 * sin(time / 7)
透明度:0.7 + 0.1 * sin(time / 4)

尾气:
角度时刻+1
x持续加sin(time / 2)
y持续加cos(time / 2)
大小倍率持续加0.05
复制成功

3.Pyrope(火岩绳)

组成拆解:左脚(footl)+ 右脚(footr)+ 冒火的头(head/head2)+ 六个单层身体(body)

特地说明:六个单层身体的本质是toby把六个不同长度的环叠到了同一个贴图组,所以实际制作的过程中也确实是六个不同的图片,而不是单个图片完成放缩操作,如果想要复现,只需要修改xscale即可。

公式整理:

代码块
PlainText
自动换行
复制代码
首先还是喜闻乐见的特殊计时器声明时间:
nowy = y + 100 - 20 * abs(-sin(time / 14))

----------------------------

左脚:
x:x + 38
y:nowy + 20
角度:40 * abs(-sin(time / 14))

右脚:
x:x + 60
y:nowy + 20
角度:-40 * abs(-sin(time / 14))

身体层:
假设身体从上到下六层的编号(i)依次为0到5。
从最下方开始绘制:
x:x
y:nowy - i * (12 + 6 * abs(sin(time - i / 14)))

头部(正常):
x:x + 50
y:nowy - 30
角度:4 * sin(time / 6) + 12 * sin(hurttime)

以防你不知道,火岩绳被打了之后会换一套动画
如果攻击了火岩绳,那么hurttime开始+1,time定格为28

头部(受伤):
x:x + 50
y:nowy - 30
角度:12 * sin(hurttime)
复制成功

4.Muffet(玛菲特)

(光看这个动画就知道三角函数量多大了。)

组成拆解:中间两条胳膊(midarm1/midarm2)+ 手里拿的茶壶(teapot)+ 下面摆动的手(lowarm)+ 胸前的蝴蝶结(shirt)+ 两条腿(legs)+ 蝴蝶结下面部分的裤子(pants)+ 两条比较高的胳膊(upperarm)+ 肩膀(shoulder,这部分指的是比较高的胳膊的大臂到袖口)+ 晃动的短双马尾(hair)+ 头部(head,这部分不包括眼睛)+ 中间的眼睛(eyecen)+ 两侧的大眼睛(eyebig)+ 两边中间的小眼睛(eyemed)

(作者写完这一长串已经吓哭了)

公式整理:

代码块
PlainText
自动换行
复制代码
首先一如既往的计时器:
heady = y + 4 * sin(time / 5)

-----------------------

比较高的胳膊:
x:x + 14
y:y + 86 + 26 + cos(time / 5)
角度:-6 * sin(time / 5)
x:x + 78
y:y + 86 + 26 + cos(time / 5)
角度:6 * sin(time / 5)

肩膀和大臂:
x:x + 42
y:y + 86 + cos(time / 5)
x:x + 50
y:y + 86 + cos(time / 5)

短双马尾:
x:x + 80
y:heady * 1.02 + 18
角度:25 * sin(time / 5)
x:x + 12
y:heady * 1.02 + 18
角度:-25 * sin(time / 5)

头部:
x:x
y:heady

腿部:
x:x + 30
y:y + 162

最低的胳膊:
如果sin(time / 5) < 0,那么切换另一张图片
否则继续切换回来
x:x + 26
y:y + 130 + sin(time / 5)
角度:8 * sin(time / 5) - 8
x:x + 64
y:y + 130 + sin(time / 5)
角度:-8 * sin(time / 5) + 8

拿茶壶的胳膊(大臂):
x:x + 12
y:y + 116 + 2 * cos(time / 5)
x:x + 80
y:y + 116 + 2 * cos(time / 5)

拿茶壶的胳膊(小臂):
x:x + 12
y:y + 130 + 2 * cos(time / 5)
角度:3 * sin(time / 5)
x:x + 80
y:y + 130 + 2 * cos(time / 5)
角度:3 * sin(time / 5)

蝴蝶结:
x:x + 28
y:y + 92 + 2 * sin(time / 5)

裤子:
x:x + 20
y:y + 114 + sin(time / 5)

五只眼睛:
x:x + 24, 30, 42, 62, 68
y:heady + 26, 32, 42
复制成功

注:如果你想知道五只眼睛的动画,下面是源代码和解释:

代码块
PlainText
自动换行
复制代码
for (i = 0; i < 5; i += 1)
{
    if (anim > (0 + i * 5) && anim < (7 + i * 5))
        eye[i] += 0.5
    if (anim > (12 + i * 5) && anim < (16 + i * 5))
        eye[i] -= 1
    if (anim > 70 && anim < 77)
        eye[i] += 0.5
    if (anim > 88 && anim < 95)
        eye[i] -= 0.5
}
复制成功

第一个if提取公因式i * 5,所以每只眼睛享有0-7的区间睁眼

第二个if提取公因式i * 5,每只眼睛会在12-16帧闭眼

依次循环到70-77区间,所有眼睛睁眼;然后在88-95所有眼睛闭眼。

下面这个表格很清晰的展示了整个动画的时间线:


5.Royal Guards(皇家守卫01 02)

组成拆解:头部(head)+ 盔甲(armor)+ 盔甲手臂(baraball)+ 腿部(baralegs)+ 靴子(barashoes)+ 握拳的手(barafist)+ 拿剑的手,也包括剑(falchion)

是的,守卫的胳膊是通过两个球进行连接的,所以一共四个球都有自己的公式。

公式整理:

代码块
PlainText
自动换行
复制代码
胳膊:
x:x + 110/120
y:y + 40/60 + 3 * sin(timie / 4)
x:x - 10/20
y:y + 40/60 + 3 * sin(timie / 4)

拿剑的手:
x:x + 140
y:y + 80 + 16 + 4 * cos(time / 4)
角度:-30 + 4 * sin(time / 4)

握拳的手:
x:x - 10
y:y + 80 + 16 + 4 * cos(time / 4)
角度:4 * sin(time / 4)

腿部:
x:x + 64
y:y + 100 + sin(time / 4)
xscale:2 + 0.05 * sin(time / 4)
yscale:2 - 0.05 * sin(time / 4)

靴子:
x:x + 64
y:y + 142
xscale:2 + 0.1 * sin(time / 4)
yscale:2 - 0.05 * sin(time / 4)

盔甲:
x:x
y:y + 10 + 2 * sin(time / 4)

头部:
x:x
y:y - 30 + 4 * sin(time / 4)
复制成功

小知识:01和02用的是同一套动作。


6.So Sorry(很抱歉/真抱歉)

组成拆解:身体(body,正反)+ 头部(head)+ 脸部(face)+ 手臂(arm)+ 帽子(hat)+ 腿部(leg)+ 尾巴(tail)+ 长耳朵(ear)

公式整理:

代码块
PlainText
自动换行
复制代码
尾巴:
x:x + 46 * sin(time / 12)
y:y + 88
xscale:2 * sin(time / 12)

身体:
x:x
y:y + 64 + 2 * sin(time / 6)

腿:
x:x ± (2 + 2 * sin(time / 6))
y:y + 128
yscale:2 + 0.1 * cos(time / 3)

头部:
x:x
y:y + 3 * sin(time / 6)

脸部:
x:x
y:y + 12 + 4 * sin(time / 6)

双耳:
x:x - 32; x + 32
y:y - 22 + sin(time / 6)

帽子(如果戴帽子了):
x:x
y:y - 20 + 5 * sin(time / 6)

胳膊:
由于有不同的状态,这里分开阐述
默认戳手:
x:x - 44 + 2 * sin(time / 3)
y:y + 32
x:x + 46 - 2 * sin(time / 3)
y:y + 32
手中抓画本时,和上述公式完全一致
双手在后面时,和上述公式完全一致
单手背后单手画画时:
x:x - 44 + 2 * sin(time / 3)
y:y + 32
x:x + 52 - 2 * sin(time / 3)
y:y + 28
复制成功

五、核心组

1.Final Froggit(终极蛙吉特)

组成拆解:头部(head)+ 身体(body)

公式整理:

代码块
PlainText
自动换行
复制代码
头部:
x:x + 60 + 10 * sin(time / 6)
y:y + 20 + 4 * sin(time / 3)
角度:-2 * sin(time / 6)

身体:
x:x + 60
y:y + 20 + 90
yscale:2 - 0.15 * sin(time / 3)
复制成功

2.Whimsalot(忧伤虫爵士)

组成拆解:身体(body)+ 头部(head)+ 翅膀(wing)

公式整理:

代码块
PlainText
自动换行
复制代码
特殊计时器:goof
goof = sin(time / 5)

---------------------

翅膀:
x:x + 60; x + 14
y:y + 40 - goof * 2
角度:±(15 - 30 * sin(time / 2.5))

身体:
x:x
y:y + 50 + goof * 6

头部:
x:x + 6
y:y + goof * 8
复制成功

3.Astigmatism(散光眼)

组成拆解:头部(anim2)+ 手臂(hand)+ 腿部(leg)

公式整理:

代码块
PlainText
自动换行
复制代码
由于这个计时器较为复杂
我们先说手臂和腿部的公式:

手臂:
x:x + 8 + 2 * sfactor * sin(time / 6)
y:y + 64 + 2 * sfactor * cos(time / 6)
yscale:1.8 + 0.2 * sfactor * cos(time / 6)
x:x + 92 - 2 * sfactor * sin(time / 6)
y:y + 64 - 2 * sfactor * cos(time / 6)
yscale:1.9 + 0.2 * sfactor * cos(time / 6)

腿部:
x:x + 30 + 2 * sfactor * sin(time / 6)
y:y + 84 + 2 * sfactor * sin(time / 6)
yscale:1.8 + 0.2 * sfactor * cos(time / 6)
x:x + 70 - 2 * sfactor * sin(time / 6)
y:y + 84 - 2 * sfactor * sin(time / 6)
yscale:1.8 - 0.2 * sfactor * cos(time / 6)
复制成功

接下来是神秘计时器部分,先上源码,再做解释:

代码块
PlainText
自动换行
复制代码
anim += 1
if (anim > 250)
{
    if (sfactor < 1)
        sfactor += 0.05
    siner += 1
}
if (anim > 140 && anim < 180)
{
    anim2 += 1
    if (anim2 > 18)
        anim = 200
}
if (anim > 220 && anim < 280)
{
    anim2 += 1
    if (anim2 >= 33)
        anim = 300
}
if (anim > 480)
{
    if (sfactor > 0)
        sfactor -= 0.07
    anim2 -= 1
    if (anim2 <= 0)
    {
        sfactor = 0
        anim2 = 0
        anim = 0
    }
}
复制成功

首先,sfactor这个变量,被规定在了0~1中间,这个数控制着散光眼的手臂和腿部晃动幅度,比如散光眼会在动画播放完之后,才开始晃动,本质上改变的就是sfactor的大小。

然后是anim和anim2变量,anim负责不断增加,anim2负责不断减少。执行anim > 480的时候,散光眼张嘴,同时anim2开始减小,经过33 + 250帧之后,重新执行行走动画,具体表格看这里:


4.Madjick(疯狂魔术师)

组成拆解:靴子(boot)+ 裙子(dress)+ 翻领(lapel)+ 头部(head)+ 巫师帽(hat)+ 宝珠、法球(orb)+ 法球放出来的十字架(orbsprinkle)

公式整理:

代码块
PlainText
自动换行
复制代码
计时器一览:
_sin = sin(time / 5)
_cos = cos(time / 5)
_dsin = sin(time / 2.5)

-----------------------

靴子:
x:x + 70 - 23 - 5 * _sin
y:y + 80 - 6 * _sin
角度:-40 - 10 * _sin
x:x + 70 + 19 + 5 * _sin
y:y + 80 - 6 * _sin
角度:40 + 10 * _sin

翻领:
x:x + 70 - 2
y:y + 52 + _sin
角度:-10 * _sin

裙子:
x:x + 70 - 34
y:y + 52 + _sin
yscale:1.8 - 0.2 * _sin

头部:
x:x + 70 - 6 + _sin
y:y + 2 + 8 * _sin

帽子:
x:x + 70 + 2 + _cos
y:y + 4 + 10 * _sin

法球:
x:x + 70 - 44
y:y + 16 + 2 * _dsin
x:x + 70 + 70
y:y + 6 + 5 * _dsin

十字架(每5帧创建一个):
x:x + 70 + 46 + random(18)
y:y - 10 + 4 * _dsin - random(6)
x:x + 70 - 66 + random(18)
y:y + 4 * _dsin - random(6)
复制成功

5.Knight Knight(晚安骑士)

组成拆解:拿矛的手(leftarm)+ 最顶上的牛角头盔(helmet)+ 龙一般的嘴(dragonmouth)+ 会动的胡须(dragonfur)+ 一整个外壳(body)+ 眼睛(dragoneyes)

公式整理:

代码块
PlainText
自动换行
复制代码
眼睛:
x:x - 60 + 94 + 6 * sin(time / 10)
y:y + 70
有人说你这只整理了一个啊?
热知识:眼睛的三种形态用的全是这个公式

身体:
x:x - 60
y:y

头盔:
x:x - 60 + 70
y:y + 2

嘴:
x:x - 60 + 98
y:y + 84

胡须:
x:x - 60 + 64
y:y + 96

手臂:
x:x - 60 + animl3
y:y + animl2
复制成功

toby的定时动画一如既往的稳定发挥。

晚安骑士的招牌动作就是手臂缓慢升起,然后捶地,捶地之后还有一段晃动期,这里仍旧给出表格:

一般人不要碰这个代码,听我的。甚至我看了这些代码都不想讲。


六、真实验室组

1.MemoryHead(记忆脑)

组成拆解:单个图片切换 + 扫描线(scanline)

扫描线主要用于构建一种画面挤压、扭曲的感觉,其本质其实是贴图的重新绘制:

代码块
PlainText
自动换行
复制代码
b = argument0
c = argument1
d = argument2
a += 1
for (i = 0; i < sprite_height; i += 1)
{
    a += 1
    draw_sprite_part_ext(sprite_index, image_index, 0, i, sprite_width, (1 * sin(a) * d), (x + (sin(a / b)) * c), (y + i * 2), 2, 2, c_white, image_alpha)
}
复制成功

2.Endogeny(融合犬)

组成拆解:下半部分身体(body)+ 头部(head)

公式整理:

代码块
PlainText
自动换行
复制代码
没摸的情况下,shaker为1
摸了一次的情况下,shaker为2
摸了三次的情况下,shaker为4

--------------------------

x, y = random(shaker) - random(shaker)
头部也是这样的
复制成功

3.Reaper Bird(死神鸟)

组成拆解:头部(head+bite)+ 身体(body)

还记得福音蟑螂的随机动手吗?死神鸟这里则是切换之后进行嘴巴咬合。

公式整理:

代码块
PlainText
自动换行
复制代码
身体:
x:x + 14 + 5 * sin(time / 10)
y:y + 90 + 6 * abs(cos(time / 8)) - 20 * mstretch
yscale:ystretch
复制成功

请输入文本(这是头部动画):

这里是身体拉伸的动画:


4.Lemon Bread(柠檬面包)

组成拆解:头部(head)+ 身体(body)

公式整理:无言了全是图片切换,不管,哼


5.Snowdrake's Mother(雪铁龙的母亲)

组成拆解:单个图片

雪铁龙母亲的图片并不是按顺序睁眼闭眼的,而是随机在四张图片里面选择一个表情,所以会有现在看到的抽动的效果。

不得不说,这种做法确实天才。


七、BOSS组

好吧,喜闻乐见的环节又来了,我知道你来这里是想看什么,对吧?

不要着急,我们会从羊妈Toriel开始慢慢往后延续

1.Toriel(托丽尔/羊妈)

组成拆解:仅单个图片


2.Papyrus(帕派瑞斯)

组成拆解:头部(head)+ 身体(body)

无公式整理,因为Papyrus在战斗中不会有任何晃动。


3.Undyne(安黛因)(中立路线)

组成拆解:头发(hair)+ 脸(face)+ 盔甲(armor)+ 腿部(legs)+ 裤子(pants)+ 拿矛的手(leftarm)+ 握拳的右手(rightarm)

公式整理:

代码块
PlainText
自动换行
复制代码
头发y:ys + 4 * sin(time / 6)
头部y:ys + 2 * sin(time / 6)
盔甲y:ys + 4 * sin(time / 6)
裤子y:ys + 2 * sin(time / 6)
左臂:
x:xs + 5 * sin(time / 6)
y:ys + 5 * sin(time / 6)
右臂:
x:xs - 2 * sin(time / 3)
y:ys + 6 * sin(time / 6) + 2 * sin(time / 3)
复制成功

4.Undyne(安黛因)(屠杀路线)

组成拆解:和中立路线一样,唯独多了一个眼睛放光(eyebeam)

公式整理:

代码块
PlainText
自动换行
复制代码
头发:
x:x + 85
y:y + 3 * sin(time / 6) + heady + 4
角度:70 - 15 * sin(time / 6)

腿部:
x:x + 100
y:y + 164

左臂:
x:x + 64 + 5 * sin(time / 6)
y:y + 78 + 5 * sin(time / 6)

右臂:
x:x + 136 + 3 * sin(time / 3)
y:y + 78 + 6 * sin(time / 6) + 2 * sin(time / 3)

盔甲:
x:x + 100
y:x + 78 + 4 * sin(time / 6)
角度:-4 * sin(time / 6)

裤子:
x:x + 100
y:y + 122 + 2 * sin(time / 6)
角度:2 * sin(time / 6)

头部:
x:x + 100
y:y + 28 + 2 * sin(time / 6) + heady

激光:
x:x + 110
y:y + 24 + 2 * sin(time / 6)
xscale:(etime - 10) / 4
yscale:2.5 - (etime - 10) / 20
角度:-32 * sin(time / 14)
透明度:1.5 - (etime - 10) / 20
复制成功

etime是一个持续增加的变量,每帧加1,如果大于等于40那么归零。


5.Mettaton(镁塔顿)(初始形态)

组成拆解:双臂(arms)+ 铁盒子外壳(nuts)

图片里所示的双臂是arms2,一共有五个不同的双臂图片。

公式整理:

代码块
PlainText
自动换行
复制代码
特殊计时器:
sinvalue = 2 * sin(time / 3)

----------------------

外壳:
x:x + sinvalue
y:y
角度:sinvalue

双臂:
x:x + sinvalue
y:y - 5 * sin(time / 4)
角度:sinvalue
复制成功

6.Mettaton EX(镁塔顿)(EX形态)

组成拆解:腿部(leg)+ 身体(body)+ 肚子上的心(heart)+ 手臂(arm)+ 脸部(mettface)

公式整理:

代码块
PlainText
自动换行
复制代码
特殊计时器:
ds1, ds2 = random(dsf * 2) - dsf

腿部:
x:x + 90 + xoffr
y:y + 120 + yoffr - legh - 0.05 * sin(time / 2)
yscale:2 - 0.05 * sin(time / 3.5)
角度:10 * sin(time / 7) - offangle
x:x + 90 - xoffl - 32
y:y + 120 + yoffl - legh - 0.05 * sin(time / 2)
yscale:2 - 0.05 * sin(time / 3.5)
角度:10 * sin(time / 7)

手臂:
x:x + 36 + sin(time / 3.5)
y:y - legh + 80 + 2 * cos(time / 3.5)
x:x + 110 + sin(time / 3.5)
y:y - legh + 80 + 2 * cos(time / 3.5)

身体:
x:x + 72 + sin(time / 3.5) + ds1
y:y + legh + 134 + 2 * cos(time / 3.5) + ds2

心:
x:x + 72 + sin(time / 3.5) + ds1 + 66
y:y + legh + 134 + 2 * cos(time / 3.5) + 108 + ds2

脸部:
x:x + 68
y:y + 40 - legh + 2 * cos(time / 3.5)
复制成功

由于mtt ex腿部和手部动作十分丰富,这里进行阐述:

代码块
PlainText
自动换行
复制代码
if (noleg == 0)
{
    if (legr == 0)
    {
        legrsprite = spr_mettleg1
        xoffr = -14
        yoffr = 10
        legrh = 36
    }
    if (legr == 1)
    {
        legrsprite = spr_mettleg2
        xoffr = -16
        yoffr = 6
        legrh = 8
    }
    if (legr == 2)
    {
        legrsprite = spr_mettleg3
        xoffr = -10
        yoffr = 14
        legrh = 60
    }
    if (legr == 3)
    {
        legrsprite = spr_mettleg4
        xoffr = -10
        yoffr = 14
        legrh = 30
    }
    if (legr == 4)
    {
        legrsprite = spr_mettleg5
        xoffr = -18
        yoffr = 2
        legrh = 42
    }
    if (legr == 9)
    {
        legrsprite = spr_mettleg2_flip
        xoffr = 20
        yoffr = 6
        legrh = 8
    }
    if (legr == 10)
    {
        legrsprite = spr_mettleg2_flip_v
        xoffr = 15
        yoffr = 2
        legrh = 0
    }
    if (legl == 0)
    {
        leglsprite = spr_mettleg1
        xoffl = -14
        yoffl = 10
        leglh = 36
    }
    if (legl == 1)
    {
        leglsprite = spr_mettleg2
        xoffl = -16
        yoffl = 6
        leglh = 8
    }
    if (legl == 2)
    {
        leglsprite = spr_mettleg3
        xoffl = -10
        yoffl = 14
        leglh = 60
    }
    if (legl == 3)
    {
        leglsprite = spr_mettleg4
        xoffl = -10
        yoffl = 14
        leglh = 30
    }
    if (legl == 4)
    {
        leglsprite = spr_mettleg5
        xoffl = -18
        yoffl = 2
        leglh = 42
    }
    if (legl == 9)
    {
        leglsprite = spr_mettleg2_unflip
        xoffl = -5
        yoffl = 2
        leglh = 6
    }
    if (legl == 10)
    {
        leglsprite = spr_mettleg2_unflip_v
        xoffl = 0
        yoffl = 5
        leglh = 2
    }
}
复制成功

大概意思就是:每个不同贴图的腿,它的x, y都是不一样的。

然后接下来是胳膊:

代码块
PlainText
自动换行
复制代码
if (arml == 0)
    armlsprite = spr_mettarm1
if (arml == 1)
    armlsprite = spr_mettarm2
if (arml == 2)
    armlsprite = spr_mettarm3
if (arml == 3)
    armlsprite = spr_mettarm4
if (arml == 4)
    armlsprite = spr_mettarm5
if (arml == 5)
    armlsprite = spr_mettarm6
if (arml == 6)
    armlsprite = spr_mettarm7
if (arml == 7)
    armlsprite = spr_mettarm8
if (armr == 0)
    armrsprite = spr_mettarm1
if (armr == 1)
    armrsprite = spr_mettarm2
if (armr == 2)
    armrsprite = spr_mettarm3
if (armr == 3)
    armrsprite = spr_mettarm4
if (armr == 4)
    armrsprite = spr_mettarm5
if (armr == 5)
    armrsprite = spr_mettarm6
if (armr == 6)
    armrsprite = spr_mettarm7
if (armr == 7)
    armrsprite = spr_mettarm8
复制成功

虽然我不知道toby这么列举下来是何意味,但是很容易看懂就是了。


7.Mettaton NEO(镁塔顿)(NEO形态)

组成拆解:头部(face)+ 身体(body)+ 炮筒手臂(arml)+ 另一个手(armr)+ 背后的翅膀(burst,也许也是灯光)+ 腿部(legs)

公式整理:

代码块
PlainText
自动换行
复制代码
翅膀:
x:x - 24或x + 28
y:y + 18 + sin(time / 3)
角度:±2 * sin(time / 6)
透明度:0.4 + 0.5 * abs(sin(time * 0.3))

腿部:
x:x
y:x + 84 + 112
yscale:2 - 0.05 * sin(time / 3)

炮筒手(左边的手):
x:x - 26 - 2 * sin(time / 3)
y:y + 40
角度:-2 * sin(time / 6)

右边的手:
x:x + 40 + 2 * sin(time / 3)
y:y + 40
角度:2 * sin(time / 6)

身体:
x:x + 4
y:y + 36 + 2 * sin(time / 3)

头部:
x:x
y:y + 3 * sin(time / 3)
复制成功

8.Sans(杉斯)

组成拆解:头部(head)+ 身子(body)+ 腿部(legs)

我说圈内制作者做了这么多年sans早知道sans的组成拆解了。

公式整理:

代码块
PlainText
自动换行
复制代码
核心摆动控制

// 摆动控制变量
y = global.idealborder[2] - 130  // 基础Y坐标
xoff = 0  // X轴摆动偏移
yoff = 0  // Y轴摆动偏移
siner = 0  // 正弦计时器
headx = 0  // 头部X偏移(用于攻击动画)
heady = 0  // 头部Y偏移(用于攻击动画)

// 摆动模式选择(bounce = 0-3)
if (bounce == 3) {
    // 模式3:大幅垂直摆动(疲惫状态)
    siner += 1
    yoff = (sin(siner / 18)) * 2
    xoff = 0
}
if (bounce == 2) {
    // 模式2:中等幅度垂直摆动
    siner += 1
    yoff = (sin(siner / 15)) * 4
    xoff = 0
}
if (bounce == 1) {
    // 模式1:轻微8字形摆动(基础闲晃)
    siner += 1
    yoff = sin(siner / 3)  // 快速垂直摆动
    xoff = cos(siner / 6)  // 慢速水平摆动,形成8字形轨迹
}
if (bounce == 0) {
    // 模式0:静止
    siner = 0
    yoff = 0
    xoff = 0
}

身体绘制

// 绘制躯干(摆动影响)
if (movearm == 0)  // 仅在非手臂动画时
    draw_sprite_ext(spr_sansb_torso, global.flag[20], 
                   (x + xoff),  // X坐标:基础X + 摆动偏移
                   (y + 42 + yoff / 1.5),  // Y坐标:基础Y + 42 + 1/1.5的摆动偏移
                   2, 2, 0, c_white, 1)

脸

// 绘制面部(有多种状态)
if (facetype == 0)  // 正常面部
    draw_sprite_ext(spr_sansb_face, global.faceemotion, 
                   (x + xoff + headx),  // X坐标:基础X + 摆动偏移 + 头部攻击偏移
                   (y + yoff + heady),  // Y坐标:基础Y + 摆动偏移 + 头部攻击偏移
                   2, 2, 0, c_white, 1)

// 流汗动画(3种等级)
if (sweat == 1)
    draw_sprite_ext(spr_sansb_face_sweat, 0, (x + xoff + headx), (y + yoff + heady), 2, 2, 0, c_white, 1)
if (sweat == 2)
    draw_sprite_ext(spr_sansb_face_sweat, 1, (x + xoff + headx), (y + yoff + heady), 2, 2, 0, c_white, 1)
if (sweat == 3)
    draw_sprite_ext(spr_sansb_face_sweat, 2, (x + xoff + headx), (y + yoff + heady), 2, 2, 0, c_white, 1)

// 蓝眼特效(特殊攻击时)
if (facetype == 1) {
    f_i += 1
    draw_sprite_ext(spr_sansb_blueeye, floor(f_i / 2), 
                   (x + xoff + headx), (y + yoff + heady), 2, 2, 0, c_white, 1)
}
复制成功

9.Asgore(艾斯戈尔/羊爸)

组成拆解:披风/斗篷(cape)+ 胳膊(ballarm)+ 左边的胳膊(arml)+ 右边的胳膊(armr)+ 头部(head1)+ 带有符文标志的盔甲(armor)+ 下面三个角的裙子(dress)+ 腿部(legs)+ 脚(feet)

我们先来看点位图解(这个是我用Python模拟的)

Asgore的绘制分为两部分,一部分绘制头、身子、腿、脚;另一部分由三叉戟控制,绘制手臂、手、三叉戟

公式整理(三叉戟):

代码块
PlainText
自动换行
复制代码
三叉戟:
x:x
y:y + 0.3 * sin(time / 15)
角度:0.02 * sin(time / 15)

手臂:
x:part[5].x + 14 + body.x
y:part[5].y + 64 + body.y
角度:由自身(x, y)指向(x - 55 * 0.02 * cos(time / 15), y - 55 * 0.02 * sin(time / 15))
xscale:和角度目标点进行勾股定理距离计算后除以40
x:part[4].x + 34 + body.x
y:part[4].y + 64 + body.y
设rdistx, rdisty, armlen
令rdistx = x + 110 * cos(0.02 * sin(time / 15))
rdisty = y + 110 * sin(0.02 * sin(time / 15))
armlen = (x, y)和(rdistx, rdisty)的距离
若armlen > 100
那么令armoff = (armlen - 100) / 2
rdistx = x + (110 - 2 * armoff) * cos(0.02 * sin(time / 15))
rdisty = y + (110 - 2 * armoff) * sin(0.02 * sin(time / 15))
随后角度和长度计算使用(x, y)和(rdistx, rdisty)

右手:
x:rdistx
y:rdisty
角度:sin(time / 15)

左手:
x:x - 55 * 0.02 * cos(time / 15)
y:y - 55 * 0.02 * sin(time / 15)
角度:sin(time / 15)
复制成功

剩下的部分:

代码块
PlainText
自动换行
复制代码
脚:依旧静止不动
腿:仍然静止不动

下面为了方便就不写xy了,xy在上面的python图里,直接看幅度:
头部:0.3
身体:0.2
裙子:0.1
双手:再加0.1
披风:0.05
复制成功

二、砍击颤动

敌人的砍击震动也是有公式的:

首先敌人有一个特殊变量shudder,这个变量就是总共的幅度,会被固定设置为16(前提是小怪:sans直接跑、羊妈剧烈震动,完全不符合这个)。

然后每次震颤会固定减小2,然后左右对换:

代码块
PlainText
自动换行
复制代码
x += shudder
if (shudder < 0)
    shudder = (-((shudder + 2)))
else
    shudder = (-shudder)
if (shudder == 0)
{
    global.hurtanim[myself] = 2
    exit
}
alarm = 2
复制成功

比如一开始是16,先往右移16

随后因为shudder > 0,取负数-16

然后由于shudder < 0,取负数并且减小2,变成14

再取负数-14……

直到0,动画结束。