我应该实现一个基于手势的菜单,您可以通过平移或扔过它们来滚动浏览水平项目列表。这种菜单在智能手机游戏中很常见。示例案例将是Cut the Rope,您可以在其中选择框(Cardboard box,Fabric box)或Angry Birds,您可以在其中选择一组级别(Poached Eggs,Mighty Hoax)。
我在想的是,我必须做一些复杂的物理计算,并根据手势给出菜单项的速度和加速度。更好的解决方案?我正在使用libgdx btw。
答案 0 :(得分:5)
我认为您不需要通过所有这些来实现简单的菜单!这都是关于为各种项目定义偏移量(我只是假设您想要切割绳索式菜单,在给定时刻只有一个条目(不包括过渡))然后在那些之间补间每当检测到轻弹时都会发生偏移!
您似乎已将所有手势系统连接起来,所以现在,我们只需要弄清楚如何显示菜单。 为简单起见,我们假设我们不希望菜单环绕。
我们首先想象一下这个菜单在我们脑海中的样子。它就像一部通过手机的幻灯片,可以在屏幕上看到。
phone ("stuff" is currently selected)
========
|---------------| |-------|
| Start | About | Stuff | Quit |
|---------------| |-------|
| |
| |
| |
========
我们假设屏幕宽度为 w
,因此,所有菜单条目都是该宽度(想想再次剪切绳索!)。
现在,当要显示“开始”时,我们应该在屏幕上以第一个元素“开始”开始渲染flimstrip,而理论上,其余部分将位于屏幕右侧。这将被视为基本情况,使用 offset = 0 。
呈现菜单是的,是的,这个偏移将是我们的小滑动滑动菜单的关键!现在,非常明显的是,当选择about时,我们只需要将“filmstrip”向左偏移一个“帧”,这里 offset = - 1 * frameWidth 。我的精彩ASCII艺术所示的示例案例选择了第三个菜单项,并且由于帧从0开始索引,我们只需减去frameWidth的两倍并获得所需的偏移量。我们只是从offset = -2 * frameWidth开始渲染菜单。
(显然你可以提前计算frameWidth,使用API获取屏幕宽度,然后只绘制菜单元素text / graphic居中)。
所以这很简单:
用户向左扫描,我们需要更接近偏移0的菜单,我们将所选实体的索引减一,然后菜单跳转到正确的位置
用户向右扫描,我们增加了索引(显然只要它没有超过菜单元素的数量 - 1)
但是补间顺畅呢? 谢天谢地,Libgdx已经为好的小补间设置了插值。我们只需要照顾好一些事情,这样我们就不会在腿上射击。我会在这里列出来的。
一个快速说明: Cut the Rope级别选择器的工作方式与我在这里所说的不同。它不仅对电影(预定义的手势)做出反应,而且更敏感。您可以通过播放偏移并跟踪手指在屏幕上的位置来实现类似的效果。 (如果用户将菜单条目向左/向右拖动太多,则自动转换到上一个/下一个)友好建议:只需设置一个简单的工作菜单,并将这样的详细信息留到最后,因为它们最终会结束花了很多时间 :P
好的,回到正轨!
我们现在拥有的是一种快速切换偏移的方法。我们只需要补间。还有一些其他成员发挥作用,但我认为他们是非常不言自明的。当我们在两个元素之间转换时,我们会记住“旧的”偏移,我们正在前进的那个,以及记住我们从转换中离开的时间,并且我们使用这四个变量来计算偏移量(在当前时刻使用libgdx插值,在这种情况下为exp10,从而产生平滑的动画。
让我们看看,我已经创建了一个快速的模拟。我尽可能地评论了代码,所以我希望下面的代码片段能说明一切! :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);
}
}
}
我相信一个简单的基于文本的菜单条目可以自己绘制就足够了! (请注意脏的硬编码文本包装宽度!)
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);
}
}
哦,行动只是一个有执行方法的东西,而且代表一个动作。
public interface Action {
abstract void execute();
}
随意在评论中提出任何相关问题,我会尝试澄清所需要的内容。 希望这有帮助!