Search Unity

Handling AI when fast forwarding time

Discussion in 'Game Design' started by D3Duck, Oct 7, 2017.

  1. D3Duck

    D3Duck

    Joined:
    Dec 25, 2014
    Posts:
    45
    Anyone else remember Settlers III-IV, where you could hit a button and all the building, fighting, carrying things around, etc. would fast forward immediately by one minute?

    This seems to be a very interesting game design point as it is not possible to just jump X amount of frames (nor to set Time.DeltaTime or the timeScale to 60f) if you are doing a lot of logic in Update(). All projects I've seen, including all of my own, use Time.DeltaTime to make everything move at correct speeds and look realistic in the Update function. AI that moves based on the NavMesh system misses all waypoints and in stead of working faster just flies around the world, unable to hit its target.

    Personally I have not found a way to properly handle this, and am curious if anyone else has run into this problem. I'd like to start a discussion about pro's and con's of different methods and what more experienced coders (I'm an internet/YouTube/self-taught hobbyist) would do to solve such a problem.
     
  2. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    I can be a very impatient person, especially when playtesting something I've already tested dozens of times, so I want to have that kind of feature as well if at all possible. Since the availability of a time-warp feature can be the deciding factor between me liking a game like Homeworld Cataclysm or disliking a game like the first Homeworld, I would understand if it makes a big difference for some players. The way I see it, giving a way for players to skip boring parts of just walking or waiting is a good thing. Ideally a game would just not have those by design, but it can't always be avoided.

    Since the question borders on being offtopic for the game design sub-forum, the game design implications should be discussed here as well, or else the thread will be moved. The time forwarding by exactly one minute sounds like something that could easily backfire. I'm more in favor of something that makes everything run faster by a factor like x8. You get a better feel for world-continuity when you see all actions still being played out, but faster, and you can better react to unforseen events. A jump ahead by exactly 1 minute imho only makes sense in games where nothing time critical could happen in that time, or where you have "events" that can interrupt the time-jump. A feature for "jump to next event" might make sense in certain strategy/economy games.


    Implementationwise I do the thing with changing .timeScale and that works with most things, but AI is an issue like you said. I have my own custom pathfinding, which is split in two parts. One takes so long that it had to be moved to its own thread, and during timewarp the update cycle of that process does not keep up, because it already runs @100% in normal time. This potentially could become an issue on very slow computers as well in normal timescale. The other part of the pathfinding takes long-ish, but still can be done on per-frame basis. The way I solve it at the moment is just scaling the timeDelta value by the factor of the time acceleration *), but that creates inconsistent behaviour. Quite frankly, I don't think there is a perfect solution in my case to solve this. To make the behaviour more consistent I'd need to try out ways to make the calculations so optimized that I can run them a multiple of times instead of just once with scaled timeDelta values, and I'm doubtful that is even possible to the extend needed. A workaround would be to just not allow time warping while enemies are near. Chances are players will only want to do it when there is nothing to shoot at anyway. If such a simple workaround gets you 90% of where you want to be, it's often the best choice imho.

    *) I try to avoid using Update() and instead implement my own ManualUpdate(float deltaTime) where I can pass in a deltaTime value scaled as I see fit. If you have hundreds of entities being updated each frame, it seems to make quite a bit of a difference, at least measured in the editor, which can be very different compared to results in an actual build (just mentioning it in general). With the manual updates you have more control and for example you could check if the deltaTime would go above a reasonable threshold and then instead call it x times with time/x as a delta parameter. That would make calculations slower, but behaviour more consistent. If consistency is more important than all else, I'd consider fixed timestep like the physics system uses. But be very very careful with that one, because it is super easy to create situations where computationlly intensive operations in a fixed update take so long that at the end of the operation you're already due one or two more fixed updates and that can quickly spiral out of control and crush your framerate to almost a halt. So make sure to have a failsafe to prevent that.

    I don't use the builtin pathfinding, so I can't say anything about it. But I wouldn't be surprised if there is no way to make that work reliably with accelerated time. If you want really complex and flexible things, you often need to make those systems yourself.
     
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    From an implementation side the key is to make the simulation deterministic. Basically meaning the same thing will happen every time, regardless.

    In general this means making your simulation entirely independent of Unity, rendering and the frame rate. Check out the Unite video on Kerball for one approach. There are also a few good blog posts around about lockstep in Age of Empires.
     
    wccrawford likes this.
  4. D3Duck

    D3Duck

    Joined:
    Dec 25, 2014
    Posts:
    45
    @Martin_H Thanks for the elaborate reply. I definitely agree with it being unworkable is some situations. For example in my Settlers example, I remember hitting the button 5 times in a row without checking what was going on and next thing I knew the screen said 'game over'.

    Part of what I am trying to implement here is that I have an idea to have multiple areas (villages) which are more or less separate and not loaded at the same time. But when the player leaves one village I DO want the AI to continue their working and whatever else. For example if it gets attacked the player will be warned and can quickly rush over to it to help out - or decide to let it go and hope the villagers handle it.

    Additionally, when the player returns the building and so on should have advanced the same amount it would have if the player is simply standing still in the middle of the village and not doing anything to help out. Whether this is going to be workable or not I don't know. It definitely will require major changes in AI code to account for not being rendered and basically not being allowed to take a lot of the processing power. As pointed out by @Kiwasi.

    Really it's kind of mixing two types of games, which really is above my skill level, but I was going to give it a try anyway and see how far I can get. It seems that my own AI code (as in, navigation etc.) will be needed as I don't see a way to use the standard NavMesh for this. That will be a whole challenge by itself.
     
  5. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    I always look for the easy solution rather then the direct blunt force one. Sometimes blunt force is necessary, but usually not. The most common mistake by new developers is going at a problem directly.

    So given that, to even be able to solve this you have to know why you are doing what you are doing. Because you need to question your assumptions from step 1 on.

    The first question when you hit a hard problem, should always be do I really need this? Second one should be what alternative ways that are simpler could this be done and still provide the value you want?

    Like I would not assume that visuals are necessarily linked to data here. As in the effects that the npc's have in the world doesn't need to be time based necessarily, or at a minimum you don't need to actually run all of the logic forward in a direct naive approach. You can probably just do some quick calculations to generate a minute's worth of activity.

    Start breaking the problem apart like that and questioning everything. You want to identify/isolate the actual problems to solve. Once you do that you usually just end up with a one or two hard problems max, and they are then easier to solve because they are now very specific, not completely vague.

    Off the top of my head I would break out the visuals and keep them separate, unlink them from data. Then I would just make a separate movement system for the fast motion stuff. Use the same logic for where you move, but just have a different controller for fast movement that uses a more appropriate approach. Easier if you have good abstractions here. Should be fairly simple actually.

    If the game is already time based, then if it's coded well you already have a base system that changes data based on time. So it should be very straight forward to fast forward that part.

    How hard all of this is, depends a lot on just having good abstractions. Like not having your waypoint logic mixed up in controllers, data handling abstracted out from controllers and visual stuff.