camera lag

Hi again, I'm implementing a simple type of camera lag so that my character-following camera cannot teleport instantly even if the character occasionally does (like the way I've implemented auto-stepping up hills transports the player up to the next step instantaneously). I'm not going for any fancy lag with acceleration, etc, but just am giving my camera a maximum speed and updating it's position towards its desired target position. I think I can make all that work as needed.

I was wondering if there is any cool orx-config style way of doing camera lag in general. It can sometimes be a nice effect for certain kinds of games....I can imagine having a maximum velocity.x/y options for child objects that allow them to catch up to their parents rather that adopt their positions instantly. A max lag distance could also be set so that the child can't get more than a certain distance away from its parent position. I didn't see anything like this so far, so curious if there is already some way to do this that I'm not seeing (other than code it myself, which is fine).

thanks

Comments

  • edited April 2020

    There's unfortunately no such convenience in orx at the moment.

    The only thing I encourage people to do is to have an object and set the camera as child of the object, and then only interact with the object (note that the camera's zoom is the reciprocal of the object's scale).

    Having a camera system could be interesting for the features your mentioned and others, such as constraining it inside a volume. However it can't be achieved by the parenting system itself as this one is completely unaware of velocities/speeds: it only handles current space transformation through the orxFRAME structure.

    On that topic, if you haven't seen it already, I recommend checking:

    for some interesting insights. :)

    If you end up making a generic camera system, it's definitely something we could add to the init script as an option for people to select when starting a new project.

  • Thanks for the video, very interesting...it certainly would be nice to have a generic camera system with target influence, some visual debugging like that, etc. I was able to make an extremely simple little system today that satisfied my immediate need, so walking up some steps doesn't jerk the world to pieces :smile: The idea of using a single control object that the cameras (mine uses a couple) are children of makes a lot of sense.

    If I were to have the courage to think about making a system we could add as an option (really not sure I'm qualified), is there documentation or examples I should look at first? I'm not sure what form an add-on option would need to be in.

  • Ah don't worry about the extension part of init, as it's very recent (I coded it in the past couple of weeks).
    It's a simple system but until I document it I don't expect anyone to add content to it. However if you were to make a simple project with just the camera system, I'll be happy to extract it myself and add it as an init extension.

    Regarding the single parent, you can also re-parent that object on the fly when need be. It's just that it's your sole point of contact for dealing with the camera itself as a parent object abstracts most of the camera features (though granted, you can't change its frustum or its group list through the object).

  • Ok, sounds good. I'll put some thought into it when I have time and see if bears fruit.

  • I've made a pretty good start on this...camera lag, easy main target switching, and a list of "objects of interest" with camera influence radius settings is all working sort of nicely! I've been trying to make as much of it as possible configurable in the ini files. The main entry for the camera positioner and debug crosshairs looks like this:

    ;obejcts for for the camera postioning system
    [CameraPositionerObject]
    Pivot = Center
    MaxVelocity = 600.0 ; maximum speed the camera is allowed to move
    CameraLagFactor = .2 ; 0 = extremely laggy, 1 = very sticky
    CameraList = MainCamera ; list of cameras that become children of the positioner
    
    [CameraPositionerCrossHairsObject] ; for showing debug crosshairs
    Graphic = @
    Texture = CameraPositionerDebugGraphic.png
    Pivot = Center
    Position = (0, 0, -0.5)
    Alpha = 0.25
    

    An orxobject that you designate as an object of interest looks something like this:

    [BeaconObject]
    Graphic = @
    Texture = Beacon.png
    Smoothing = true
    Position = (800, 125, -.01)
    Pivot = Center
    FullCameraInfluenceRadius = 100
    PartialCameraInfluenceRadius = 500
    

    On the todo list is to add some camera bounds like a minX, maxX, minY and maxY values that the camera itself can never go past, if set. I'd also like to add the ability to give the camera an x or y offset from the main target object that can be changed at will.

    The code is contained in a class, which may not be called for but is the way I usually do things. In this case you only ever make once instance of the class so may not be the right way to do it.

    Any suggestion for additional functionality? I can't think of anything at the moment but there are probably other cool things that could be done with this.

  • That sounds like a good set of initial features to me.

    How does it work on the code side though? Are the beacons automatically discovered upon their creation based on their config properties?

  • At the moment, you add them yourself like this:

    beacon = orxObject_CreateFromConfig("BeaconObject");
    CamController->AddObjectOfInterest(beacon);
    

    My class stores a std::vector of structures - each contains a pointer to the orxObject as well as its radius values. (AddObjectOfInterest looks up the radius values from config, based on the object's name)

    I intended to make an ObjectsOfInterestList = option in the ini file. This works for the camera list because I can easily get a camera with a specific name. But for objects, I wasn't sure of how to do it, since you could have multiple objects in existence with the same name, correct? Maybe that doesn't matter and I should loop through all existing objects and any with the name matching an entry in the list gets added? That would eliminate the need to explicitly create the object and add it. Probably there is a good way to make this simpler through the config file, I'm all ears for suggestions.

  • Here's a possible approach (keep in mind I didn't think about it for more than a minute or two, so it might be deeply flawed):

    • I'd have a single "entry" point for your camera system, like a "init" function that will then register an update function on the core clock
    • it'll also register an event handler for orxEVENT_TYPE_OBJECT/orxOBJECT_EVENT_CREATE
    • the event handler will check the created objects' config section in search of a specific config property, something like "IsCameraBeacon", and register them when it's found

    I'd recommend to either also listen for the orxOBJECT_EVENT_DELETE in order to clean your internal list or use GUIDs instead of pointers (which you can them manually clean when orxStructure_Get returns orxNULL for a given GUID).

    Another option would be to depend on Scroll and not just orx: you simply need to bind your CameraBeacon class to the Beacon config section and use the OnCreate/OnDelete to do your registration/cleanup.
    Now in config, all you have to do for an object to be a beacon is to inherit from the Beacon config section. You also don't need to keep an internal list as you can simply iterate over them with Scroll:GetNextObject<CameraBeacon>.

    (You could also do something similar with orx to prevent from having to keep an internal list, by creating a Beacon child programmatically with a specific group that allows you to easily iterate over them.)

  • Thanks, these look like good ideas. Dealing with object deletion was on my list as well, but I always forget about setting up event handlers.

    I'll make a pass at improvements using these ideas when there is time.

  • This is a lot simpler...I first removed the class construct since there is no need for it and it also complicates using the event handlers. Now there is a single call to cameraControl_Init() that is needed.

    For the ini file, the entries look like:

    [CameraControl_Target] ; inherit from this if your object should be the camera target
                          ;- only one object at a time can be the main target
    CameraControlType = MainTarget
    
    [CameraControl_ObjectOfInterest] ; inherit from this if your want to define an object that influences the camera controol
    CameraControlType = ObjectOfInterest  
    FullCameraInfluenceRadius = 300 ;default - override in your object
    PartialCameraInfluenceRadius = 800 ; default - override in your object
    
    ;objects for for the camera postioning system
    [CameraControl_Positioner]
    Pivot = Center
    MaxVelocity = 600.0 ; maximum speed the camera is allowed to move
    CameraLagFactor = .2 ; 0 = extremely laggy, 1 = very sticky
    CameraList = MainCamera ; list of cameras that become children of the positioner
    ShowDebug = true ; defaults to false
    
    [CameraControl_CrossHairs] ; for showing debug crosshairs
    Graphic = @
    Texture = CameraControlCrossHairs.png
    Pivot = Center
    Position = (0, 0, -0.5)
    Alpha = 0.25 
    

    A couple of things I'm not sure of. One is the last two ini entries. I need somewhere to set the various options for the system, but currently the system relies on this entry being in the ini file with this name (CameraControl_Positioner). I can just print out a warning if it isn't found. It could also be any object with a CameraControlType = Positioner line in it, but really there should only be one of these objects since there is really only one set of options for the system. I may be missing the obvious way that one should work. And for the cross hairs, really that might make more sense to create internally without an ini entry, but I wasn't sure how to create an object directly without the ini entry.

    So I'm using the object create event to catch new objects and check if they are the main target or an object of interest for the system, and it works well...I just wonder if this adds any performance issues considering it will be catching every object create event throughout the game and reading their config settings, though probably most of them will not be relevant. Should that be a concern?

  • edited April 2020

    Sounds like a good initial setup. I'd probably recommend to revisit the terms in a second step to simplify them in a future step.
    Regarding the position section, you could either warn and return orxSTATUS_FAILURE or simply go with default settings (I'd keep the warnings in debug though). In any case, you do not need an object per se for this one, just need to read the config values directly from a predefined section name.

    As for creating the crosshair object programmatically, you could of course do it manually by creating all the bits and pieces yourself but I'd simply recommend setting the config values you want with orxConfig_Set*, then call orxObject_CreateFromConfig() with the values you've just set in code. This way you could also only overrides values if they were not already present, giving a default behavior that could be overridden in config by the user if they feel like it.
    Even the default crosshair texture can be created at runtime without the need for any external files.

    As for the performance hit, it's probably not going to be a bit concern except for high volume of objects, like particle systems. But even with those, there are some ways to mitigate any potential performance impact. I wouldn't worry about it for the time being.

  • Thanks for the feedback. Yes, particles is what I was a little worried about for the create and delete events (planning to use delete events to clean my list), but I won't worry about it for now as you suggest.

    Regarding revisiting the terms, I wasn't sure what exactly that meant...do you mean the names like "CameraControl_Positioner" and "FullCameraInfluenceRadius"? If so, I'm open to suggestions for improvement. I have an allergy to cryptic looking abbreviations though and I usually end up with ridiculously long variable names, for example. At a minimum the "Camera" could be removed from some of them since it should be implicit. But, not sure if that is what you referred to.

    The positioner section does actually define an orxobject, because that is the object the camera(s) get parented to. Probably easier to see that when I can make a copy of the code available.

    I'll check out using the orxConfig_Set to setup the crosshair. I suppose the section itself still has to exist in the actual .ini file? I didn't find an orxConfig_CreateSection or something similar after a quick look.

    I think all the functionality is working at this point, except handling object deletions. I'll see if I can squeeze in some more time today..this has been hugely fun to work on.

  • edited April 2020

    Quick partial reply:

    @funemaker said:
    I'll check out using the orxConfig_Set to setup the crosshair. I suppose the section itself still has to exist in the actual .ini file? I didn't find an orxConfig_CreateSection or something similar after a quick look.

    The section will be created automatically if it doesn't exist when you push or select it, no need to set it in a .ini file.

  • Great, thanks.

  • I got rid of the crosshair section and trimmed down some of the ini labels:

        [CameraTarget] ; inherit from this if your object should be the camera target
                              ;- only one object at a time can be the main target
        Control = MainTarget
        CameraOffsetX = 0
        CameraOffsetY = 0
    
        [ObjectOfInterest] ; inherit from this if your want to define an object that influences the camera control
                            ; define as many objects of interest as you want
        Control = ObjectOfInterest  
        FullInfluenceRadius = 200 ; default - override in your object
        PartialInfluenceRadius = 400 ; default - override in your object
    
        ;object for the camera postioning system
        [CameraControl]
        MaxVelocity = 600.0 ; maximum speed the camera is allowed to move
        LagFactor = 0.8 ; 0 = no lag, 1 = extreme lag
        CameraList = MainCamera ; list of cameras that become children of the positioner
        CameraMinX = -1400; //for min/max, if entry not found, no limit applied
        CameraMinY = -500;
        CameraMaxX = 1400;
        CameraMaxY = 600;
        ShowDebug = true ; defaults to false
    

    Also am cleaning my objects of interest list on object deletions.

    I'm sure there is a lot of room for improvement.

    If you feel like looking closer, try

    https://bitbucket.org/funemaker/orxcamerasystem/downloads/

  • Nice, that does look nicer from a user standpoint already. I'll have a look at your repo in the coming days.

  • Well, tonight I tried to add this to a "real" project in progress and can see it has quite a ways to go. It works fine as setup in a sample project but trying to add it to a more complicated project broke it really easily. Needs to be quite a bit more robust at this point.

  • Ah that's often the case with everything, I guess. I'm curious though, in which ways did it break?

  • There were a couple of things easy enough to fix so far.

    If there were no objects of interest specified, then the camera won't follow the main target...this was just a logic error in one of the functions and is fixed.

    My object delete event was handling object of interest deletions, but didn't account for deleting the main camera target.
    In this project I get rid of the camera target when the level is closed or returned to the main menu. That is also corrected now so that there is no crash when you get rid of the target :)

    There are some order of operation considerations that I'm not sure how to best handle yet. The CameraControl_Init function needs to be run before the creation of target or objects of interest, since it catches them on object creation. I suppose I could also loop through any currently existing objects when Init is run to catch them if the target or objects of interest had already been created. Also, if I create my main target and then set the location of that object to somewhere far away from (0, 0), which is the case in my "real" project, the camera system dutifully "follows" over to the starting location when you start playing...not ideal. So I added "CameraControl_SetCurrentPosition" function so you can immediately set the position of the camera positioner wherever you want. You just have to know to use that if you move your player somewhere far away (like spawning at startup, or teleporting for example). Not sure if that is the best way to handle it, but it works if you know to use it.

    I'll give it some more kicking around to see what else I can break. The code isn't up to date in the repo at the moment.

  • Regarding the crash when the target was removed, more generally speaking I'd recommend using GUIDs when referencing any orx structure, such as objects. Calling orxOBJECT(orxStructure_Get(MyGUID)) will return orxNULL if the object has been deleted or even if another one has been relocated in the same place (which a naked pointer wouldn't be able to track).
    In any, it sounds like it's shaping up nicely, congrats. =)

  • Sounds good, I'll try to switch over to the GUID check instead. I'm working on something else at the moment, trying to track down some screen "jerks" with the profiler...I may have to hassle you again in another thread if I can't figure out what is happening.

Sign In or Register to comment.