如何在多个不同的对象上调用相同的tick方法?

时间:2018-01-31 20:40:21

标签: java loops interface game-loop

尝试制作JavaFX游戏但不能围绕如何在不同对象上使用tick方法,同时能够调用接口方法之外的其他方法。为您的快乐制作了这个简化的代码:

interface TickInterface {
    public void tick(); // i.e to move the object or to check for collision.
}

class Car implements TickInterface {
    void tick(){
        // run on every tick
    }

    void refuel(){
        /* 
        could be also any other method which is not run
        in every tick, like unlocking the car or getLocation()
        */
    }
}

class Bicycle implements TickInterface {
    void tick(){
        // run on every tick
    }
}

class LoopClass(){
    ...
    tickInterface car = new Car();
    tickInterface bicycle = new Bicycle();

    LoopClass(){
        ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
        rides.add(car);
        rides.add(bicycle);

    void thisLoopsEveryFrame(){
        for(TickInterface ride : rides){
            ride.tick();
        }
    }

    void refuelCar(){
        car.refuel(); //not possible because of the interface object type
    }
}

我想在具有相同接口的两个不同对象上调用tick()但这导致我无法从Car对象调用refuelCar()。当然,你不能给自行车加油。执行更新循环(tick)功能的标准方法是什么?令我感到沮丧,我无法找到解决方案。

4 个答案:

答案 0 :(得分:1)

您在需要逻辑的类中执行逻辑。

class Car implements TickInterface {
    void tick(){
        if (lowOnFuel) {
          refuel();
        {

    }
    void refuel(){ 

    }
}

-edit -

我显然不知道你在做什么,但引入一名球员改变了一切。

我会更新/打勾你的玩家课程,让他知道他驾驶的是什么,因为这是有道理的。因此,如果他正在驾驶Car通过Car playerCar = new Car()实例化它,或者如果你真的想要编程到界面(在大多数情况下这是一个好习惯),你可以这样做。

  interface Vehicle {
    void accelerate();
    void steerLeft();
    //...
  }

  If (forwardIsPressed) {
    vehicle.accelerate();
  }
  if (leftIsPressed) {
    myCar.steerLeft();
  }   

  if (playerWantsToRefuel) {
    if (vehicle instanceof Car) {
        // safe to cast into a car object. 
        Car myCar = (Car) vehicle;
        myCar.refuel;
    } else if (vehicle instanceof Bike)
    {
        UI.ShowDialogueBox("You cannot refuel a bike, go eat a something to refuel your energy.");
    }
  }

正如你所看到的,我摆脱了TickInterface,因为这已经没有意义了。玩家和人工智能正在驾驶汽车,所以也许让它们具有界面&#39; Driver&#39;具有勾选或更新功能。然后让他们控制他们驾驶的车辆。在玩家的情况下,如果按下某个键,则在游戏循环中调用的更新/刻度方法中调用他正在驾驶的汽车的该功能。我希望这是有道理的。

您仍然可以使用类似Drive()的车辆界面来降低汽车的燃油量。您的自行车的燃油问题仍然存在,玩家需要知道他骑的是什么,以便利用它的功能。以侠盗猎车手为例,所有车辆都可以拥有相同的界面,只需改变行为。但是,如果GTA汽车需要加油而自行车不会那么自行车会比汽车明显不同。仍然两者都可以从界面继承加油方法,但是自行车会显示一条消息,表示它无法加油,如果那样能为你完成工作那么好但是如果它没有意义那么它可能更糟糕的设计

我还建议您阅读有关接口的更多信息,以便更好地理解它们。 Here is a great answer already

答案 1 :(得分:1)

TL; DR:你可以做到

class LoopClass(){
    ...
    Car car = new Car();
    Bicycle bicycle = new Bicycle();

    LoopClass(){
        ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
        rides.add(car);
        rides.add(bicycle);
    }

    void thisLoopsEveryFrame(){
        for(TickInterface ride : rides){
            ride.tick();
        }
    }

    void refuelCar(){
        car.refuel(); // possible now car has compile-time type of Car
    }
}

说明:

你感到困惑&#34;编译时类型&#34;和&#34;运行时类型&#34;:语句

  

我想在具有相同界面的两个不同对象上调用tick()   但这导致我无法从refuelCar()对象调用Car

不是真的。

对象实际具有的方法,即对象的成员,由运行时内存中对象的实际类型决定(&#34;运行时类型&#34;) 。这又由用于创建对象的构造函数决定。

所以当你写

TickInterface car = new Car();

然后在运行时执行此代码时,它会在类型为Car的内存(在堆上)中创建一个对象。您可以将此对象视为同时使用tick()方法和refuel()方法。

另一方面,编译器允许您调用的方法由编译时类型确定:即使用的引用变量的类型引用一个物体。

写作

TickInterface car ;

您创建编译时类型car的引用(称为TickInterface)。这意味着编译器只允许您调用

car.tick();

(因为编译器知道car类型为TickInterface,并且它知道TickInterface声明了一个名为tick()的方法,但它不会让你这样做

car.refuel();

因为并非每个TickInterface实例都有一个名为refuel()的方法。

使用

car分配值时
car = new Car();

您正在执行 upcast =右侧的表达式类型为Car,而左侧的表达式类型为TickInterface。由于编译器确保每个Car实例也是TickInterface实例,因此这是完全合法的。

car添加到列表中时:

rides.add(car);

您有效地创建了对您创建的Car对象的第二个引用。第二个引用保存在List内部。由于您使用

将列表声明为TickInterface类型
List<TickInterface> rides = new ArrayList<TickInterface>();

您可以将隐藏的内部引用视为编译时类型TickInterface

但是,这两个引用都没有理由是同一类型。你可以做到

Car car = new Car();
Bicycle bicycle = new Bicycle();

LoopClass(){
    ArrayList<TickInterface> rides = new ArrayList<TickInterface>();
    rides.add(car);
    rides.add(bicycle);

void thisLoopsEveryFrame(){
    for(TickInterface ride : rides){
        ride.tick();
    }
}

现在car编译时类型Carbicycle具有编译时类型Bicycle)。电话

rides.add(car);

完全合法:rides.add(...)期待TickInterface类型的某些内容,并且您给它一个Car:再次确保编译器每个Car实例也是TickInterface的一个实例。在此版本中,您已将upcast移至代码中的此点,而不是分配给car

现在,因为car的编译时类型是Car,所以你要写的方法是:

void refuelCar(){
    car.refuel(); 
}

将编译并执行得很好。

答案 2 :(得分:0)

糟糕方法,但您可以:

 ((Car) car).refuel();

我认为您必须创建名称为Refuelvoid refuel(); - 方法的界面。

你必须决定:

继承 - &gt; interface Refuel extends TickInterfaceclass Car implements Refuel

实施 - &gt; class Car implements Refuel,TickInterface

这取决于您的任务和应用程序架构。

答案 3 :(得分:0)

如前所述,如果您只想保留当前的体系结构,则可以始终将对象明确地类型化为其子类,并以这种方式轻松访问其所有专用的公共方法。您还可以使用instanceof运算符首先检查对象的实例类型,以确保您可以应用该方法。但是,我也认为在您的上下文中使用它是不好的做法。继承/接口通常用于抽象子类共享的公共属性,与其具体实现无关。因此,分享&#34; tick&#34; -property是有意义的,但是你的LoopClass不应该关心每个tick中发生的事情。

我建议您将控制逻辑外包在单独的类中(或者如果需要,将其包含在游戏对象实例类中)并使用对象的显式子类型。如果您的控制类需要修改两个车辆,那么它有两个知识是有意义的。所以例如您的Car类可以使用对Bicycle对象的引用(可能由自己的属性提供),并在发生碰撞时在其tick()方法中操纵这两个对象。