Modular Vehicles Tutorial 4: Adding Driving Logic

In this tutorial, we're going through the final steps needed so that our vehicle can be driven by a controller with a camera attached to the driver's point of view.

Prerequisites

Modular Vehicles Tutorial 3: Adding Tire Models

Setting up the Vehicle Controller

In this section, we will create a Python script that receives the inputs of our hardware (in this case, a gamepad) to convert them to inputs of our vehicle system.

  1. From the Script section of the Toolbox, add a Dynamics Script. Close the following window.
  2. Rename (F2) the script "LAV Controller".
  3. Create a Scripts folder in you vehicle folder structure.
  4. Create a file text file and rename it LAV Controller.py. Add the following code to it and save it.

    LAV Controller
    from Vortex import Types
     
    MAX_GEAR = 8
    MIN_GEAR = -6
    DIFF_LOCK_TORQUE = 1000
     
     
    def on_simulation_start(self):
     
        self.engine = create_input(self, 'Engine Start', Types.Type_Bool, default_value=False)
        self.gearUp = create_input(self, 'Gear Up', Types.Type_Bool, default_value=False)
        self.gearDown = create_input(self, 'Gear Down', Types.Type_Bool, default_value=False)
        self.lock = create_input(self, 'Diff Lock', Types.Type_Bool, default_value=False)
        self.park = create_input(self, 'Park', Types.Type_Bool, default_value=False)
     
        self.engineON = create_output(self, 'Engine On', Types.Type_Bool, default_value=False)
        self.parkON = create_output(self, 'Park On', Types.Type_Bool, default_value=False)
        self.lockTorque = create_output(self, 'Lock Torque', Types.Type_Double, default_value=False)
        self.gear = create_output(self, 'Gear Value', Types.Type_Int, default_value=0)
     
        self.last_engine = False
        self.last_gearUp = False
        self.last_gearDown = False
        self.last_lock = False
        self.lockON = False
        self.last_park = False
     
    def pre_step(self):
     
        if self.engine.value and not self.last_engine:
            self.engineON.value = not self.engineON.value
            self.last_engine = True
        elif not self.engine.value:
            self.last_engine = False
     
        if self.park.value and not self.last_park:
            self.parkON.value = not self.parkON.value
            self.last_park = True
        elif not self.park.value:
            self.last_park = False
     
        if self.lock.value and not self.last_lock:
            self.lockON = not self.lockON
            self.last_lock = True
        elif not self.lock.value:
            self.last_lock = False
        self.lockTorque.value = DIFF_LOCK_TORQUE * self.lockON
     
        if self.gearUp.value and not self.last_gearUp:
            self.gear.value += 1
            if self.gear.value > MAX_GEAR:
                self.gear.value = MAX_GEAR
            self.last_gearUp = True
        elif not self.gearUp.value:
            self.last_gearUp = False
     
        if self.gearDown.value and not self.last_gearDown:
            self.gear.value -= 1
            if self.gear.value < MIN_GEAR:
                self.gear.value = MIN_GEAR
            self.last_gearDown = True
        elif not self.gearDown.value:
            self.last_gearDown = False
     
    def create_output(extension, name, o_type, default_value=None):
        """Create output field with optional default value, reset on every simulation run."""
        if extension.getOutput(name) is None:
            extension.addOutput(name, o_type)
        if default_value is not None:
            extension.getOutput(name).value = default_value
        return extension.getOutput(name)
     
     
    def create_parameter(extension, name, p_type, default_value=None):
        """Create parameter field with optional default value set only when the field is created."""
        if extension.getParameter(name) is None:
            field = extension.addParameter(name, p_type)
            if default_value is not None:
                field.value = default_value
        return extension.getParameter(name)
     
     
    def create_input(extension, name, i_type, default_value=None):
        """Create input field with optional default value set only when the field is created."""
        if extension.getInput(name) is None:
            field = extension.addInput(name, i_type)
            if default_value is not None:
                field.value = default_value
        return extension.getInput(name)
  5. From the LAV Controller script extension properties, assign the Script Path to the newly created LAV Controller.py.
  6. Step (F8) the simulation once for the Inputs and Outputs to appear in the script properties.
  7. From the Input Devices section of the Toolbox, insert a Joystick.
  8. From the Basics section of the Toolbox, add a Connection Container. Rename it "LAV Controller Connections".
  9. Insert all fields from the Vehicle Control VHL and the LAV Controller script.
  10. Insert the following Outputs from the Joystick:
    1. Button2
    2. Button4
    3. Button5
    4. Button6
    5. Button8
    6. AxisX
    7. AxisZ
    8. AxisZRot
  11. Make the following connections:
  12. Test (F7) the simulation and drive the vehicle with your gamepad.

Adding a Driver Camera

  1. From the Cameras section of the Toolbox, insert a Perspective camera. Rename it Driver Camera
  2. Open the Skin connection container.
  3. Insert the Parent Transform and connect it to the World Transform of the Chassis part.