SOLVED Is it possible to control Camera with keyboard WASD like a FPS game?

Hi everybody. I find when edit a interior scene or a huge map, it is hard to navigate through only mouse. The best way to control the camera is like FPS games or UE4.

There was vitual walkthrough in cinema 4d however it performs laggy and it's not a flying camera,then it was removed.

My friend write a plugin which make camera read keyboard, however it can only move along the world axis. Besides, it moves about 5 times per sec, laggy too. If anybody knows how to correctly do it, pls tell me thanks.

I used to take in game footage with a xbox controller, find it works perfectly.
Desktop 2021.11.04 - 23.01.40.082021114236338.gif

So if a combine gamepad and real time keying(cappuccino in c4d), animating camera would be much easier.

These 2 videos demonstrates real time keying with mouse and keyboard to make camera movement and mechanical animations.
https://www.youtube.com/watch?v=eoSRMxMDYsk&t=307s
https://www.youtube.com/watch?v=a7qyW1G350g

But with a gamepad, we can even achieve this level of control inside the soft ware. Follow character movement, focus on the face, camera shake you name it.

https://www.youtube.com/watch?v=MAFVglxzkhI

3D Connexion SpaceMouse can navigate like a gamepad, but not everybody afford that, and using 1 stick simultaneously to move and rotate is not that accurate.

If you knows how to get the right API, pls leave a comment. That would be really appriciated!

Hello @aimidi,

without wanting to be rude: Please do not go shopping for answers in other topics. As lined out in the Forum Guidelines we talk internally about every posting marked as 'As-as-Question' anyways, so you will always get the input of the whole team (with a flavor of whoever takes on a specific topic). This will also derail other topics, as your question was only related at best faintly to the other topic.

Since I am now already here:

My proposal was a GeDialog with a timer and I have skteched up a very simple example below. The major hurdle for what you want to do is the fact that Cinema 4D has no event loop optimized for such thing, which is why you need your own, a timer.

What you should not do IMHO is hook into the standard messages of Cinema, as this will make it very hard to get a lag-free input, but I might be wrong, I have not tried. Due to the inaccuracy of clocked functions calls (be it GeDialog or just some system clock) when going the 100 ms, i.e., 10 FPS threshold, you also must work with deltas and cannot 'just move around a camera'.

In my example I also did not deal with recording stuff into key-frames. I would not do that while receiving user inputs, but just store the raw data instead (the camera matrices) and then write the data once the 'record' session has ended. Finally, it would be better to implement the whole thing in C++ because performance is key here and move the input (keyboard, gamepad, etc) polling away from the main thread into its own thread and then only update the viewport from the main thread based on that data. You will need a third-party input device library for that as our input polling is bound to the main thread.

Finally, when writing the data, you will have to optimize/quantize it, as putting in 1000 keys for moving 1000 frames in a straight line is a bit non-optimal ^^

Cheers,
Ferdinand

The test file:City.c4d
The result: https://www.youtube.com/watch?v=IMLhOtJdO2E
The code:

import c4d
import time

class CameraControlDialog (c4d.gui.GeDialog):
    """
    """
    # Translation velocity per second.
    TRL_VELOCITY = 500
    # Rotation velocity per second.
    ROT_VELOCITY = 0.01

    def CreateLayout(self):
        """
        """
        self.SetTitle("Camera Control Dialog")
        return True

    def InitValues(self):
        """
        """
        # Store the document, the render base draw (likely being the
        # perspective one, you could also retrieve the active base draw
        # instead) and the camera attached to it.
        self._doc = doc
        self._bd = doc.GetRenderBaseDraw()
        self._camera = (self._bd.GetSceneCamera(doc) or
                        self._bd.GetEditorCamera(doc))
        # Set the timer of the dialog to 40 ms, i.e., 25 FPS. We are
        # stretching here the limits of what is possible, because
        # GeDialog.Timer is not designed for such short intervals and we
        # will have to anticipate a very large error in the actual call
        # intervals.
        self.SetTimer(40)
        self._time = None
        return True

    @staticmethod
    def PollKey(key):
        """
        """
        res = c4d.BaseContainer()
        if not c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, ord(key), res):
            return False
        return res[c4d.BFM_INPUT_VALUE] == 1


    def SetTraslationVelocity(self, mg, dt):
        """
        """
        return mg.v3 * dt * self.TRL_VELOCITY

    def SetRotationVelocity(self, mg, dt):
        """
        """
        return mg *= c4d.utils.MatrixRotY(dt * self.ROT_VELOCITY)

    def Timer(self, msg):
        """
        """
        wKey, aKey, sKey, dKey = (self.PollKey("W"), self.PollKey("A"),
                                  self.PollKey("S"), self.PollKey("D"))

        # When no keys are pressed exit and clean out the time.
        if sum((wKey, aKey, sKey, dKey)) == False:
            self._lastTime = None
            return None

        # Initialize the time for the first frame a key is being pressed.
        if self._lastTime is None:
            self._lastTime = time.time()
            return None

        # The delta between now and the last evaluation
        dt = time.time() - self._lastTime
        mg = self._camera.GetMg()


        if aKey:
            mg = self.SetRotationVelocity(mg, dt)
        elif dKey:
            mg = self.SetRotationVelocity(mg, -dt)

        if wKey:
            mg.off += self.SetTraslationVelocity(mg, dt)
        elif sKey:
            mg.off += self.SetTraslationVelocity(mg, -dt)

        self._lastTime = time.time()
        self._camera.SetMg(mg)
        c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW)


if __name__ == '__main__':
    global dialog
    dialog = CameraControlDialog(doc)
    dialog.Open(c4d.DLG_TYPE_ASYNC)

Hi,

To make something like this you would need to create a Tool and a Tag. The Tool would be used to let you access the keyboard and mouse movements and then you would store the recorded information in the Tag. It is too complicated for me to describe how this would be done in one email.

But you should look at creating a tool, there are examples on github.

https://github.com/PluginCafe/cinema4d_cpp_sdk_extended/tree/master/plugins/cinema4dsdk/source/tool

Then create a tag

https://github.com/PluginCafe/cinema4d_cpp_sdk_extended/tree/master/plugins/cinema4dsdk/source/tag

And when your tool is "recording" you would store this data in the Tag so that it can be saved and used to drive a camera, for example.

Virtual Walkthrough is what I would have recommended as well, but it has now been removed from C4D. And you probably wouldn't get any better responsiveness if you were to write your own.

Best Regards,
Kent

Hi,

if you want to animate the camera and record it, you need to run the animation inside cinema4D and for each frame record the position of the camera. As the animation is running you can react to the messages EVMSG_TIMECHANGED or EVMSG_DOCUMENTRECALCULATED.

The problem is that you would need to create a simplified version of your scene so it will run smoothly.

If you just want to navigate only the viewport, it's more difficult. At cinema4D isn't a real time application, there's no regular event you can rely on to update/record other event. The closest way of doing it would be to have a GeDialog using the Time function, but you should step away from that solution for something you want to be smooth.

You could also "record" all the position of your camera and from that, create a camera morph setup. Create a camera each 25 frames from the data and add it to the morph setup. This allow to have a smooth animation and still be able to adjust some camera.

Cheers,
Manuel

@kbar Thank you so much Mr.Barber, I'm watching your tutorial these days. It's very helpful.
There is already real time recording which is called cappuccino in Animate bar.
All I have to do is the camera control.
Directly modify the position data seems not a good idea, cuz there is input rate limit.
However when people hold middle mouse to move camera, or hold LMB to move object, they move smoothly.
I'm thinking if I can remap this behavior of the mouse to keyboard, and move along Object local axis, not view space.
Thank you again.

@m_magalhaes Thank you mister, for the solutions! Actually there is real time recording in cinema 4d which is cappuccino in Animate bar.
And there is key smooth function called Key reducer, the algarithm is same to smooth the spline,
adaptively reduce key points and adjust the bezier on F-curve. Blender has same function called Decimate.
But there is no way to smooth the key position on F-curve without delete keys. Such function exists in Blender which called smooth keys.
Now I'm thinking only controlling camera with keyboard.There is actually way to fly the camera like in FPS game which is using 3D connexion's Space mouse.
So I think it's actually easy for cinema 4d official to support both keyboard and gamepad.Then c4d users who are also gamers would all cheers!!!
And thank you agian for offering ideas!

@ferdinand
Hello, sir. Recently I also have questions about object axis. I tried to use my keyboard WASD to move the camera along it's own axis, so camera will works like UE4 or a in game flying camera.
Cinema 4d originally provide a way to navigate as a flying camera, which is using 3D Connexion's Space mouse.
If Cinema 4d official can also make c4d support keyboard and gamepad, it would be super helpful for creating huge map and interior, and people can animate camera movement with gamepad by real time recording, like people recording cinematic camera motion by using a iphone with blender.

edit: This posting has been moved by @ferdinand from its original topic

Hello @aimidi,

without wanting to be rude: Please do not go shopping for answers in other topics. As lined out in the Forum Guidelines we talk internally about every posting marked as 'As-as-Question' anyways, so you will always get the input of the whole team (with a flavor of whoever takes on a specific topic). This will also derail other topics, as your question was only related at best faintly to the other topic.

Since I am now already here:

My proposal was a GeDialog with a timer and I have skteched up a very simple example below. The major hurdle for what you want to do is the fact that Cinema 4D has no event loop optimized for such thing, which is why you need your own, a timer.

What you should not do IMHO is hook into the standard messages of Cinema, as this will make it very hard to get a lag-free input, but I might be wrong, I have not tried. Due to the inaccuracy of clocked functions calls (be it GeDialog or just some system clock) when going the 100 ms, i.e., 10 FPS threshold, you also must work with deltas and cannot 'just move around a camera'.

In my example I also did not deal with recording stuff into key-frames. I would not do that while receiving user inputs, but just store the raw data instead (the camera matrices) and then write the data once the 'record' session has ended. Finally, it would be better to implement the whole thing in C++ because performance is key here and move the input (keyboard, gamepad, etc) polling away from the main thread into its own thread and then only update the viewport from the main thread based on that data. You will need a third-party input device library for that as our input polling is bound to the main thread.

Finally, when writing the data, you will have to optimize/quantize it, as putting in 1000 keys for moving 1000 frames in a straight line is a bit non-optimal ^^

Cheers,
Ferdinand

The test file:City.c4d
The result: https://www.youtube.com/watch?v=IMLhOtJdO2E
The code:

import c4d
import time

class CameraControlDialog (c4d.gui.GeDialog):
    """
    """
    # Translation velocity per second.
    TRL_VELOCITY = 500
    # Rotation velocity per second.
    ROT_VELOCITY = 0.01

    def CreateLayout(self):
        """
        """
        self.SetTitle("Camera Control Dialog")
        return True

    def InitValues(self):
        """
        """
        # Store the document, the render base draw (likely being the
        # perspective one, you could also retrieve the active base draw
        # instead) and the camera attached to it.
        self._doc = doc
        self._bd = doc.GetRenderBaseDraw()
        self._camera = (self._bd.GetSceneCamera(doc) or
                        self._bd.GetEditorCamera(doc))
        # Set the timer of the dialog to 40 ms, i.e., 25 FPS. We are
        # stretching here the limits of what is possible, because
        # GeDialog.Timer is not designed for such short intervals and we
        # will have to anticipate a very large error in the actual call
        # intervals.
        self.SetTimer(40)
        self._time = None
        return True

    @staticmethod
    def PollKey(key):
        """
        """
        res = c4d.BaseContainer()
        if not c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, ord(key), res):
            return False
        return res[c4d.BFM_INPUT_VALUE] == 1


    def SetTraslationVelocity(self, mg, dt):
        """
        """
        return mg.v3 * dt * self.TRL_VELOCITY

    def SetRotationVelocity(self, mg, dt):
        """
        """
        return mg *= c4d.utils.MatrixRotY(dt * self.ROT_VELOCITY)

    def Timer(self, msg):
        """
        """
        wKey, aKey, sKey, dKey = (self.PollKey("W"), self.PollKey("A"),
                                  self.PollKey("S"), self.PollKey("D"))

        # When no keys are pressed exit and clean out the time.
        if sum((wKey, aKey, sKey, dKey)) == False:
            self._lastTime = None
            return None

        # Initialize the time for the first frame a key is being pressed.
        if self._lastTime is None:
            self._lastTime = time.time()
            return None

        # The delta between now and the last evaluation
        dt = time.time() - self._lastTime
        mg = self._camera.GetMg()


        if aKey:
            mg = self.SetRotationVelocity(mg, dt)
        elif dKey:
            mg = self.SetRotationVelocity(mg, -dt)

        if wKey:
            mg.off += self.SetTraslationVelocity(mg, dt)
        elif sKey:
            mg.off += self.SetTraslationVelocity(mg, -dt)

        self._lastTime = time.time()
        self._camera.SetMg(mg)
        c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW)


if __name__ == '__main__':
    global dialog
    dialog = CameraControlDialog(doc)
    dialog.Open(c4d.DLG_TYPE_ASYNC)

@ferdinand Wow,thank you so much ferdinand!