[OpenGL] 制作一个有旋转动画的按钮

注:以下以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 软导作业

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值