注:以下以Java代码为例(依赖LWJGL库,即只会使用基础的OpenGL的API)
准备
您至少得准备一张图片。。
然后我们要设计按钮的动画效果,本文要做的动画效果是:鼠标放到按钮上时,按钮会顺时针旋转350°,同时放大(当然是有动画效果的旋转),鼠标离开按钮时,按钮会逆时针旋转复原。
打码
动画控制代码
对于每帧,我们都要计算当前的时间,以确保动画节奏,假设我们每250ns重新计算一次按钮的状态:
long lastTime = Long.MIN_VALUE;
void onTick() {
long now = System.nanoTime();
if (now - lastTime >= 250L || lastTime == Long.MIN_VALUE) {
// Do something
}
}
必须要提醒的是,由于我们要确保动画流畅,我们需要比较快的反应速度,跨度达1ms的System.currentTimeMillis()
不能满足我们的需要,我们必须采用System.nanoTime()
。但是System.nanoTime()
返回值可正可负,在很短的一段时间内,我们可以认为System.nanoTime()
的Delta是正的,但是时间足够长,代码就不能保证正确性了。当然我们这里不存在这样的问题。
回到代码,lastTime初始化为Long.MIN_VALUE
,我这里偷了懒没有另外开个标记。开个标记会比较稳,毕竟System.nanoTime()
返回值可正可负,Long.MIN_VALUE
完全可以取,但是。。几率比较低,我就偷个懒(不要学我)。
然后If语句判断了时间差或初始化状态,比较好理解。
接着我们要在鼠标放到按钮上时启动动画,假设我们有一个表示现在按钮的状态,鼠标是否在按钮上的变量isHovering
。还有一个表示当前动画最长时间的duration
,调整duration
就可以放慢或加快动画的速度。那么//Do something
就可以替换成:
if (isHovering) {
if (counter < duration) {
lastTime = now;
++counter;
} else {
if (counter > 0) { // 确保counter不会无限减小
lastTime = now;
--counter;
}
}
}
其中,counter
表示当前动画的进度,每一帧我们判断时间是否到了,到了我们就改变动画的进度,如果鼠标在按钮上,动画进度就前进1帧,否则后退1帧。
动画绘制代码
我们已经有了counter
表示当前的动画进度,我们就可以依据这个进度计算图片的旋转角度:
bindYourTexture();
glEnable(GL_BLEND);
glDisable(GL_LIGHTING);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(1, 1, 1, 0.5f);
float sineValue = duration == 0 ? 0 : (float) Math.sin(Math.PI * counter / duration / 2);
float scaleRatio = 1 + sineValue / 2;
glTranslatef(x, y, 0);
glTranslatef(width / 2, height / 2, 0);
glScalef(scaleRatio, scaleRatio, 1);
glRotatef(sineValue * 350, 0, 0, 1);
glTranslatef(-width / 2, -height / 2, 0);
drawYourTexture();
glTranslatef(width / 2, height / 2, 0);
glRotatef(-sineValue * 350, 0, 0, 1);
glScalef(1.0f / scaleRatio, 1.0f / scaleRatio);
glTranslatef(-width / 2, -height / 2, 0);
glTranslatef(-x, -y, 0);
代码的第一部分调整绘制参数。
bindYourTexture
函数中需要调用glBindTexture
等函数绑定纹理。
代码的第二部分计算当前的旋转角,为什么要用Math.sin
呢?因为我们的动画显然是速度从0慢慢加速,最后慢慢停下来的效果会舒服一些。全过程匀速的效果也不好看。从Math.sin
的图像来看,
[0,π/2]
区间内的y值变化满足我们的需求。通过Math.sin
变换后的动画效果就是我们要的。
代码的第三部分在处理偏移、旋转和缩放。
因为glRotatef
是绕
(x,y,z)
向量旋转,所以我们要旋转按钮,必定使向量垂直于
xOy
平面。所以向量令为
(0,0,1)
,然后我们移动原点的位置(就是glTranslatef(width / 2, height / 2)
那段,确保原点在按钮图片的中心,我们旋转的时候就绕图片中心旋转)最后恢复原点的位置即可。
代码的第四部分绘图,一个可行的代码示例如下:
def drawYourTexutre() = drawTexture(0, 0, width, height)
public static void drawTexture(int x1, int y1, int x2, int y2) {
GL11.glColor3f(255, 255, 255);
GL11.glBegin(GL11.GL_QUADS);
GL11.glTexCoord2d(0, 0);
GL11.glVertex2i(x1, y1);
GL11.glTexCoord2d(0, 1);
GL11.glVertex2i(x1, y2);
GL11.glTexCoord2d(1, 1);
GL11.glVertex2i(x2, y2);
GL11.glTexCoord2d(1, 0);
GL11.glVertex2i(x2, y1);
GL11.glEnd();
}
代码的最后一部分与第三部分相反(函数调用顺序相反,参数相反),表示恢复坐标矩阵。
完整代码
以下完整代码基于Minecraft编写。
import org.lwjgl.opengl.GL11;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.util.ResourceLocation;
public class GuiAnimationImage extends Gui {
ResourceLocation image = null;
boolean hovering = false;
Minecraft mc = Minecraft.getMinecraft();
int x, y, width, height, counter = 0, duration;
long lastTime = Long.MIN_VALUE;
public GuiAnimationImage(int x, int y, int width, int height, int duration) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.duration = duration;
}
boolean isMouseIn(int mouseX, int mouseY) {
return x <= mouseX && y <= mouseY && mouseX <= x + width && mouseY <= y + height;
}
public void draw(int mouseX, int mouseY) {
hovering = isMouseIn(mouseX, mouseY);
long now = System.nanoTime();
if (now - lastTime >= 250L || lastTime == Long.MIN_VALUE) {
if (hovering) {
if (counter < duration) {
lastTime = now;
++counter;
}
} else {
if (counter > 0) {
--counter;
lastTime = now;
}
}
}
if (image == null)
return;
mc.getTextureManager().bindTexture(image);
GL11.glEnable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glColor4f(1.0F, 1.0F, 1.0F, 0.5F);
float sineValue = duration==0?0.0f:(float) Math.sin(Math.PI*counter/duration/2);
GL11.glTranslatef(x, y, 0);
GL11.glTranslatef(+width/2, +height/2, 0);
GL11.glScalef(1+sineValue/2, 1+sineValue/2, 1);
GL11.glRotatef(sineValue*350, 0, 0, 1);
GL11.glTranslatef(-width/2, -height/2, 0);
GLUtil.drawTexture(0, 0, this.width, this.height);
GL11.glTranslatef(+width/2, +height/2, 0);
GL11.glRotatef(-sineValue*350, 0, 0, 1);
GL11.glScalef(1.0f/(1+sineValue/2), 1.0f/(1+sineValue/2), 1);
GL11.glTranslatef(-width/2, -height/2, 0);
GL11.glTranslatef(-x, -y, 0);
}
}
for SYSU 软导作业