Menu 如何实现基于手势的菜单

Menu 如何实现基于手势的菜单,menu,Menu,我应该实现一个基于手势的菜单,在这个菜单中,你可以通过平移或投掷来滚动水平列表中的项目。这种菜单在智能手机游戏中非常常见。例如,在选择盒子(纸板盒、织物盒)的地方割断绳子,或者在选择关卡(水煮蛋、大骗局)的地方割断愤怒的小鸟 我想的是,我必须做一些复杂的物理计算,并根据手势给菜单项提供速度和加速度。有更好的解决办法吗?顺便说一句,我正在使用libgdx。我认为实现一个简单的菜单不需要经历所有这些!这一切都是关于为各种项目定义偏移量(我假设您想要剪切绳索样式菜单,在给定时刻只看到一个条目(不包括过

我应该实现一个基于手势的菜单,在这个菜单中,你可以通过平移或投掷来滚动水平列表中的项目。这种菜单在智能手机游戏中非常常见。例如,在选择盒子(纸板盒、织物盒)的地方割断绳子,或者在选择关卡(水煮蛋、大骗局)的地方割断愤怒的小鸟


我想的是,我必须做一些复杂的物理计算,并根据手势给菜单项提供速度和加速度。有更好的解决办法吗?顺便说一句,我正在使用libgdx。

我认为实现一个简单的菜单不需要经历所有这些!这一切都是关于为各种项目定义偏移量(我假设您想要剪切绳索样式菜单,在给定时刻只看到一个条目(不包括过渡)),然后在检测到轻弹时,在这些偏移量之间间隔

你的手势系统好像都连接好了,所以现在,我们只需要弄清楚如何显示菜单。为了简单起见,我们假设我们不想让菜单缠绕在一起

我们将从想象这个菜单在我们脑海中的样子开始。这就像是一部电影带,穿过手机,可以在屏幕上看到

                   phone ("stuff" is currently selected)
                  ========  
 |---------------|        |-------|
 | Start | About | Stuff  | Quit  |
 |---------------|        |-------|
                 |        |
                 |        |
                 |        |
                  ========
我们将假定屏幕宽度为
w
,因此,所有菜单项都正好是该宽度(请再次考虑切断绳索!)

现在,当显示“Start”时,我们应该在屏幕上以第一个元素“Start”开始渲染flimstrip,而其余元素理论上位于屏幕右侧。这将被视为基本情况,呈现偏移量为0的菜单

是的,是的,这个偏移量将是我们的小幻灯片菜单的关键!现在,很明显,当选择“关于”时,我们只需将“胶片带”向左偏移一个“帧”,这里offset=-1*frameWidth。我的精彩ASCII艺术展示了我们的示例案例,它选择了第三个菜单项,因为帧从0开始索引,所以我们只需减去帧宽度的两倍就可以得到所需的偏移量。我们将只渲染从偏移量=-2*帧宽开始的菜单

(显然,您只需提前计算frameWidth,方法是使用API获取屏幕宽度,然后以文本/图形为中心绘制菜单元素)

这很简单:

  • 用户向左扫掠,我们需要接近偏移量0的菜单,我们将所选实体的索引减少1,然后菜单跳到右侧位置

  • 用户向右扫掠,我们增加索引(显然,只要它不超过菜单元素的数量-1)

但是光滑的两个呢? 谢天谢地,Libgdx已经为可爱的小家伙们设置了所有的插值。我们只需要处理一些事情,这样我们就不会射中自己的腿。我会把它们列在这里

请注意: 割断绳索水平选择器的工作原理与我在这里所说的略有不同。它不只是对轻弹(预定义的手势)做出反应,它更敏感。通过使用偏移和跟踪手指在屏幕上的位置,您可能会获得类似的效果。(如果用户将菜单项向左/向右拖得太多,则自动切换到上一个/下一个)友好的建议:只需设置一个简单的工作菜单,并在最后留下这样的详细信息,因为它们可能会占用大量时间!:P

好了,回到正轨上

public class MenuEntry {


private String label;
// private RenderNode2D graphic;
private Action action;

public MenuEntry(String label, Action action) {
    this.label = label;
    this.action = action;
}

public void select() {
    this.action.execute();
}

public void draw(int x, int y, SpriteBatch spriteBatch, BitmapFont font) {
    font.drawMultiLine(spriteBatch, label, x, y, 400, HAlignment.CENTER);
}
我们现在拥有的是一种在偏移之间快速切换的方法。我们只需要之间。有一些额外的成员开始发挥作用,但我认为它们是不言自明的。当我们在两个元素之间转换时,我们会记住“旧”偏移量,以及我们正朝着的偏移量,以及我们从转换中剩下的时间,我们使用这四个变量来计算当前时刻的偏移量(在本例中使用libgdx插值exp10),从而生成平滑的动画

让我看看,我已经创建了一个快速的'n'dirty模型。我已经尽可能地对代码进行了注释,所以我希望下面的代码片段能够说明问题!:D

import java.util.ArrayList;

import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;

public class MenuManager {

// The list of the entries being iterated over
private ArrayList<MenuEntry> entries = new ArrayList<>();

// The current selected thingy
private int index;

// The menu offset
private float offset = 0.0f;

// Offset of the old menu position, before it started tweening to the new one
private float oldOffset = 0.0f;

// What we're tweening towards
private float targetOffset = 0.0f;

// Hardcoded, I know, you can set this in a smarter fashion to suit your
// needs - it's basically as wide as the screen in my case
private float entryWidth = 400.0f;

// Whether we've finished tweening
private boolean finished = true;

// How much time a single transition should take
private float transitionTimeTotal = 0.33f;

// How much time we have left from the current transition
private float transitionTimeLeft = 0.0f;

// libgdx helper to nicely interpolate between the current and the next 
// positions
private Interpolation interpolation = Interpolation.exp10;

public void addEntry(MenuEntry entry) {
    entries.add(entry);
}

// Called to initiate transition to the next element in the menu
public void selectNext() {
    // Don't do anything if we're still animationg
    if(!finished) return;

    if(index < entries.size() - 1) {
        index++;
        // We need to head towards the next "frame" of the "filmstrip"
        targetOffset = oldOffset + entryWidth;
        finished = false;
        transitionTimeLeft = transitionTimeTotal;
    } else {
        // (does nothing now, menu doesn't wrap around)
        System.out.println("Cannot go to menu entry > entries.size()!");
    }
}

// see selectNext()
public void selectPrevious() {
    if(!finished) return;

    if(index > 0) {
        index --;
        targetOffset = oldOffset - entryWidth;
        finished = false;
        transitionTimeLeft = transitionTimeTotal;
    } else {
        System.out.println("Cannot go to menu entry <0!");
    }
}

// Called when the user selects someting (taps the menu, presses a button, whatever)
public void selectCurrent() {
    if(!finished) {
        System.out.println("Still moving, hold yer pants!");
    } else {
        entries.get(index).select();
    }
}

public void update(float delta) {

    if(transitionTimeLeft > 0.0f) {
        // if we're still transitioning

        transitionTimeLeft -= delta;
        offset = interpolation.apply(oldOffset, targetOffset, 1 - transitionTimeLeft / transitionTimeTotal);
    } else {
        // Transition is over but we haven't handled it yet
        if(!finished) {
            transitionTimeLeft = 0.0f;
            finished = true;
            oldOffset = targetOffset;
        }
    }
}

// Todo make font belong to menu
public void draw(SpriteBatch spriteBatch, BitmapFont font) {
    if(!finished) {
        // We're animating, just iterate through everything and draw it,
        // it's not like we're wasting *too* much CPU power
        for(int i = 0; i < entries.size(); i++) {
            entries.get(i).draw((int)(i * entryWidth - offset), 100, spriteBatch, font);
        }
    } else {
        // We're not animating, just draw the active thingy
        entries.get(index).draw(0, 100, spriteBatch, font);
    }
}
}

噢,Action只是一个有执行方法的东西,它代表一个动作

public interface Action {
abstract void execute();
}

请随意在评论中提出任何相关问题,我会尽力澄清需要什么。
希望这有帮助

你可能会发现这个链接很有用:非常感谢你的回答!你太棒了!