最终目标
文末有源码
本文讲的基础一点,可能有些啰嗦,大佬轻喷。
数学概念
下面要引入两个数学概念,一个是正弦余弦和正切,一个是角度制和弧度制
正弦 余弦 正切
如图,在Rt△ABC中,∠C = 90°,斜边为c,长的直角边为b,短的直角边为a。
正弦
我们把∠A的对边与斜边的比叫做∠A的正弦(sine),记作sinA,即
sinA = ∠A的对边 / ∠A的斜边 = a / c
余弦
我们把∠A的邻边与斜边的比叫做∠A的余弦(cosine),记作cosA,即
cosA = ∠A的邻边 / ∠A的斜边 = b / c
正切
我们把∠A的对边与邻边的比叫做∠A的正切(tangent),记作tanA,即
tanA = ∠A的对边 / ∠A的邻边 = a / b
对于tanA有一个公式
tanA = sinA / cosA
也就是
a / b = (a / c) / (b / c)
a / b = (a / c) * (c / b)
等号右边上下两个c约掉了
也就只剩下a / b = a / b
角度制和弧度制
每个角度都有一个对应的弧度值
既然已经有角度制了,为什么还要有弧度制呢
因为代码里Math中用的三角函数都是弧度制,代码只认弧度制,不认角度制
180°角的弧度值是π
由此可以得出公式
弧度 = 角度 / 180° * π
角度 = 弧度 / π * 180°
也可以写成
弧度 = π / 180° * 角度
角度 = 180° / π * 弧度
其实都是一样的,一模一样,只是数的位置变了
仔细思考思考公式是怎么得出来的
开始写代码
只要实现小球围绕着红色中心点转悠就可以了(UI部分代码省略)
新建一个脚本取名Main,挂载到canvas上面,在脚本中声明以下属性
@property({displayName: "小球", tooltip: "小球", type: Node})
ball: Node = null!;
@property({displayName: "中心红点", tooltip: "中心红点", type: Node})
point: Node = null!;
@property({displayName: "运动半径", tooltip: "运动半径", type: CCFloat})
r: number = 200;
@property({displayName: "每帧运动角度", tooltip: "每帧运动角度", type: CCFloat})
angle_nor: number = 1;
@property({displayName: "中心红点位置偏移量", tooltip: "中心红点位置偏移量"})
offset: Vec2 = new Vec2(0, 0);
// 下一帧需要运动的角度
angle: number = 0;
PS出两个圆,拖拽到场景,设置成一大一小,为了醒目容易区分还可以改下颜色,大的取名ball,小的取名center_point
绑定下节点
接下来一直撸代码,没编辑器什么事了
先封装两个函数供自己调用
分别是角度转弧度和弧度转角度
这里就用到了刚刚讲的公式
分别传入角度和弧度,经过代码计算之后返回弧度和角度
// 角度转弧度
angle_to_radian (angle: number): number {
// 角度转弧度公式
// π / 180 * 角度
// 计算出弧度
let radian = Math.PI / 180 * angle;
// 返回弧度
return(radian);
}
// 弧度转角度
radian_to_angle (radian: number): number {
// 弧度转角度公式
// 180 / π * 弧度
// 计算出角度
let angle = 180 / Math.PI * radian;
// 返回弧度
return(angle);
}
在游戏开始时,把ball(白色大球)的坐标设置到最右边
onLoad () {
// 将小球的坐标设置到最右边
this.ball.position = new Vec3(this.r, 0);
// 设置下一帧需要运动的角度为最开始的角度
this.angle = this.angle_nor;
}
接着就是最主要的逻辑,全部写在update里面
先解释一下angle属性和angle_nor属性的区别,angle属性是大白球下一帧要运动的角度,不可以在编辑器进行调整,只在代码中进行修改。而angle_nor是可以在编辑器修改的,每帧运动的角度,update每执行一次angle就加上一次angle_nor,angle的值会越来越大。
如果下一帧需要运动的角度大于等于了360°,就进行取余,计算出angle的值 / 360的余数,并且将这个余数重新赋值给angle,取余这里其实没有必要写,写了和没写效果是一样的,但是写上看起来更舒服一些(至少我这么认为)
update () {
// log(this.angle);
// 将每帧运动的角度计算成弧度
let radian = this.angle_to_radian(this.angle);
// 算出X和Y的坐标
let x = this.r * Math.cos(radian);
let y = this.r * Math.sin(radian);
// 设置小球的坐标
this.ball.position = new Vec3(x + this.offset.x, y + this.offset.y);
// 将下一帧需要运动的角度增加
this.angle += this.angle_nor;
// 如果下一帧需要运动的角度大于等于了360°
if (this.angle >= 360) {
// 取余360 也就是说angle的值不会超过360度
this.angle %= 360;
}
// 每帧更新中心红点位置
this.point.position = new Vec3(this.offset.x, this.offset.y);
}
update里面,首先把要旋转的角度计算成弧度,并且利用文章开头所讲的三角函数计算出大白球的坐标。
// 算出X和Y的坐标
let x = this.r * Math.cos(radian);
let y = this.r * Math.sin(radian);
这两句代码就是利用三角函数计算出了小球的坐标,画个图辅助理解
点A绕原点O逆时针旋转30°,求出旋转后的点A'的坐标
旋转前点A的坐标是(r, 0),r是旋转半径
旋转之后得到A',过A'向X轴做垂线交X轴于点B,连接OA',得到Rt△A'OB
∠A'OB暂时管它叫∠α
文章开头写的三角函数,现在就用得上了,我们已知的只有半径r和∠α的角度是30°,求点A'的坐标
说是求点A'的坐标,实际上求出对边和邻边的长就可以了
点A的坐标就是(邻边, 对边)
sin α = 对边 / r
cos α = 邻边 / r
等号右边再×一个r就得到了对边和邻边的值
也就是
对边 = sin α * r
邻边 = cos α * r
在代码中可以用Math.cos()和Math.sin()传入一个角的弧度值返回这个角的余弦或正弦
这两个方法传入的参数只能是角的弧度,不能是角度
大白球的最终坐标就是(邻边长度, 对边长度)
计算出大白球最终坐标之后还要加上位置偏移量
并且更新中心红点的位置
这样就实现了小球围着中心红点转悠的功能
其实这就是角度转平面向量的一个过程
平面向量是在二维平面内既有方向(direction)又有大小(magnitude)的量
平常写代码为了方便我还封装了两个方法
分别是角度转平面向量和平面向量转角度
这个在代码中用的更多
// 角度转向量
angle_to_vector (angle: number): Vec2 {
// tan = sin / cos
// 将传入的角度转为弧度
let radian = this.angle_to_radian(angle);
// 算出cos,sin和tan
let cos = Math.cos(radian);// 邻边 / 斜边
let sin = Math.sin(radian);// 对边 / 斜边
let tan = sin / cos;// 对边 / 邻边
// 结合在一起并归一化
let vec = new Vec2(cos, sin).normalize();
// 返回向量
return(vec);
}
// !!!!!!!!其实使用Math.atan2求出弧度再转角度一样的效果
// 向量转角度
vector_to_angle (vector: Vec2): number {
// 将传入的向量归一化
let dir = vector.normalize();
// 计算出目标角度的弧度
let radian = dir.signAngle(new Vec2(1, 0));
// 把弧度计算成角度
let angle = -this.radian_to_angle(radian);
// 返回角度
return(angle);
}
源代码:https://gitee.com/propertygame/cocos-creator3.x-demos/tree/master/TRF_Demo
技术交流Q群:1130122408
更多内容请关注微信公众号