更传统的蛇游戏中,每个分段都会取代前一个位置前的分段。问题是,我正在编程一个"平滑"的蛇游戏,因为每帧的头部都在以分钟为单位移动。
以前,我有turningPoint
,它们是在头部移动时创建的。当前面的每个分段都到达该点时,它们将沿存储在turningPoint
中的方向旋转。这样做的问题是,我必须不断地为每个turningPoint
的每个片段绑定,这是一个累积的错误,因为移动不是完全平滑的(总是有可能片段完全错过了点,没有转向),而且片段不是"连接的",而是很多碰巧挨着移动的物体。
我的问题是:我该如何对一条能有效转弯的平滑蛇进行编程?
我假设您对模型(即位置/旋转数据的后端表示)有问题,而不是视图的问题(屏幕上显示的内容),因为通常平滑的模型移动可以很好地转换为平滑的视图移动。
事实上,这听起来像是函数/懒惰评估编程的完美场所。。。
首先,位置/方向:
(请注意,这里的Vector
不是集合类,而是与图形编程中大量使用的数学概念有关)
public final class PositionAndOrientation {
public final Vector position;
public final Vector orientation;
public PositionAndOrientation(Vector position, Vector orientation) {
this.position = position;
this.orientation = orientation;
}
public PositionAndOrientation move(Vector direction) {
return new PositionAndOrientation(position.add(direction), orientation);
}
public PositionAndOrientation rotate(Vector degrees) {
return new PositionAndOrientation(position, orientation.add(degrees));
}
}
调整/更新界面:
public interface AdjustPositionAndOrientation {
PositionAndOrientation adjust(long stamp);
}
起始位置:
public final class StartingPositionAndOrientation
implements AdjustPositionAndOrientation {
private final PositionAndOrientation starting;
public StartingPositionAndOrientation(final PositionAndOrientation start) {
starting = start;
}
public final PositionAndOrientation adjust(long stamp) {
return starting;
}
}
移动段:
public final class MovePosition implements AdjustPositionAndOrientation {
private final AdjustPositionAndOrientation previous;
private final Vector direction;
private final long start;
public MovePosition(long start, Vector direction,
AdjustPositionAndOrientation previous) {
this.start = start;
this.direction = direction;
this.previous = previous;
}
public final PositionAndOrientation adjust(long stamp) {
return previous.adjust(Math.min(stamp, start)).move(
direction.multiply(Math.max(stamp - start, 0)));
}
}
旋转段:
public final class RotatePosition implements AdjustPositionAndOrientation {
private final AdjustPositionAndOrientation previous;
private final Vector degrees;
private final long start;
public MovePosition(long start, Vector degrees,
AdjustPositionAndOrientation previous) {
this.start = start;
this.degrees = degrees;
this.previous = previous;
}
public final PositionAndOrientation adjust(long stamp) {
return previous.adjust(Math.min(stamp, start)).rotate(
degrees.multiply(Math.min(Math.max(stamp - start, 0), 90)));
}
}
最后是细分市场本身:
public final class Segment {
private final Segment parent;
private final long offset;
private final AdjustPositionAndOrientation place;
private Segment (Segment parent, long offset, AdjustPositionAndOrientation place) {
this.parent = parent;
this.offset = offset;
this.place = place;
}
public static Segment startingAt(AdjustPositionAndOrientation place) {
return new Segment(null, 0, place);
}
public static Segment connectTo(Segment parent, long offset) {
return new Segment(parent, offset, null);
}
public final PositionAndOrientation getPositionAndOrientation(long elapsed) {
if (parent != null) {
return parent.getPositionAndOrientation(elapsed - offset);
} else {
return place.adjust(elapsed);
}
}
}
当然,这只是一个粗略的草图(没有经过测试),但基本的想法应该会让你开始。值得注意的是,无论何时向链中添加新的Adjust
,都需要一个复制构造函数/工厂来重建段。然而,这种方法的美妙之处在于,我可以通过向父段传递最终(结束)Adjust
实例来创建"重播"。
(注意:我使用了long作为游戏时钟,因为Date
就是这样使用的。但是,考虑到int
可以在周内保持足够的毫秒数,您可以将其交换)。
编辑:
要想"移动"蛇,你实际上需要复制。在Segment
中添加这样的方法就可以了:
public final Segment addAdjutment(AdjustPositionAndOrientation place) {
if (parent != null) {
return connectTo(parent.addAdjustment(place), offset);
} else {
return startingAt(place);
}
}
并在尾部调用该方法,而不是在头部调用(顺便说一句,你只需要维护对蛇尾部的外部引用,这使得添加额外的段变得微不足道——尽管我认为这可以逆转)。请注意,无论你对方法进行什么调整,都应该已经封装了"驱动"蛇的方法,否则你需要在接口中添加某种copy(..)
方法。这意味着您需要保留对当前调整的外部引用,或者向snake添加递归get()
方法(实现起来很简单)。
根据事物的声音,你预计每个片段都会在几帧后回溯头部所经历的点。在这种情况下,只需让你的蛇数据结构成为一个头部所在点的列表……然后要绘制蛇,在该列表中循环,跳过每10个点,然后在那里绘制一段。