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)
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();
}
请随意在评论中提出任何相关问题,我会尽力澄清需要什么。
希望这有帮助 你可能会发现这个链接很有用:非常感谢你的回答!你太棒了!