Simple zoom-in / binoculars

Here’s a tute showing how to do both dolly zoom and the binocular effect:

# Panda3D dolly zoom and binoculars example [aurilliance 3/3/09]
# This code just shows how to create a 'dolly zoom' effect
# such as the one seen in the opening scene of half-life 2.
# This effect is achieved by simultanously decreasing the 
# field of view (FOV) and moving the camera forwards slightly.
#
# It also shows how to use Lerp intervals to create a nice, 
# smooth binoculars / weapon scope effect that zooms in on
# what the player is looking at.

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
from direct.task.Task import Task

import sys

# Function to print text to the screen
def genLabelText(text, i):
    return OnscreenText(text = text, pos = (-1.3, .95-.05*i), fg=(1,1,1,1), align = TextNode.ALeft, scale = .05, mayChange=1)

class World(DirectObject):
    def __init__(self):
        # Game state variables and window setup
        self.zoomed = 0
        self.controls_disabled = 1
        self.cam_height = 1.0
        
        p = WindowProperties()
        p.setCursorHidden(True)
        p.setSize(800, 600)
        base.win.requestProperties(p)
        
        # Set the FOV to a wide value and the camera
        # slightly forwards (to show the dolly zoom)
        base.camLens.setFov(120)
        base.camera.setPos(Point3(0,20,self.cam_height))
        
        # Add some text to the screen
        genLabelText("Panda3D Dolly Zoom and Binoculars example", 0)
        genLabelText("WASD + mouse to look and move, F to use binoculars", 1)
        genLabelText("Controls are enabled when the dolly zoom finishes", 2)
        
        # Set up control
        self.keyMap = {"left":0, "right":0, "up":0, "down":0, "zoom":0}
        self.accept("escape", sys.exit)
        self.accept("a", self.setKey, ["left",1])
        self.accept("d", self.setKey, ["right",1])
        self.accept("w", self.setKey, ["up",1])
        self.accept("s", self.setKey, ["down",1])
        self.accept("f", self.setKey, ["zoom",1])
        self.accept("a-up", self.setKey, ["left",0])
        self.accept("d-up", self.setKey, ["right",0])
        self.accept("w-up", self.setKey, ["up",0])
        self.accept("s-up", self.setKey, ["down",0])
        self.accept("f-up", self.setKey, ["zoom",0])
        base.disableMouse()
        
        # Load a few models to show off the effect
        self.box = loader.loadModel("box")
        self.box.reparentTo(render)
        self.box.setPos(-2,23,0)
        
        self.smiley = loader.loadModel("smiley")
        self.smiley.reparentTo(render)
        self.smiley.setPos(2,23,1)
        
        self.teapot = loader.loadModel("teapot")
        self.teapot.reparentTo(render)
        self.teapot.setPos(0,30,0)
        
        # Add a point light to light things up a bit
        plight = PointLight('plight')
        plight.setColor(VBase4(1,1,1,1))
        plnp = render.attachNewNode(plight)
        plnp.setPos(0,0,10)
        render.setLight(plnp)
        render.setShaderAuto()
        
        # Create and play the dolly zoom effect
        # It is set to zoom from FOV 120 to 45, moving the camera 5
        # units in 10 seconds. All these parameters can be adjusted
        # to create different effects / to suit your taste
        dollyZoomer = LerpFunc(self.fovSet, 10, 120, 45, 'easeOut')
        camMover = LerpPosInterval(base.camera, 10, Point3(0,15,self.cam_height), Point3(0,20,self.cam_height), blendType='easeOut')
        dollySequence = Sequence(Wait(3), Parallel(dollyZoomer, camMover), Func(self.setControls, 0))
        dollySequence.start()
        
        # Add the frame task to task list
        taskMgr.add(self.frame, "moveFunc")
    
    def setKey(self, key, value):
        self.keyMap[key] = value
    
    # Sets whether the keys and mouse are disabled or not
    def setControls(self, value):
        self.controls_disabled = value
    
    # This function is used (generally by a lerp function) to adjust the FOV
    def fovSet(self, t):
        base.camLens.setFov(t)
    
    # The frame task
    def frame(self, task):
        cam_height = 1.0
        cam_sensitivity = 0.1
        speed = 0.35
        strafe_speed = 0.2
        
        # Mouse Control
        md = base.win.getPointer(0)
        x = md.getX()
        y = md.getY()
        
        rotx, roty = 0, 0
        if base.win.movePointer(0, 400, 300):
            rotx -= (x - 400)*cam_sensitivity
            roty -= (y - 300)*cam_sensitivity
        if (roty < -80): roty = -80
        if (roty >  80): roty =  80
        
        # Exit here if the controls are still disabled,
        # we let the above code execute to prevent the player
        # moving the mouse before the dolly zoom finishes
        if self.controls_disabled:
            return Task.cont
        
        base.camera.setHpr(base.camera.getH()+rotx, base.camera.getP()+roty, 0)
        forward_dir = base.camera.getNetTransform().getMat().getRow3(1)
        strafe_dir = base.camera.getNetTransform().getMat().getRow3(0)
        forward_dir.normalize(); strafe_dir.normalize()
        forward_dir *= speed; strafe_dir *= strafe_speed
        
        # Key Movement
        if self.keyMap["up"]:
            base.camera.setPos(base.camera.getPos()+forward_dir)
        if self.keyMap["down"]:
            base.camera.setPos(base.camera.getPos()-forward_dir)
        if self.keyMap["left"]:
            base.camera.setPos(base.camera.getPos()-strafe_dir)
        if self.keyMap["right"]:
            base.camera.setPos(base.camera.getPos()+strafe_dir)
        
        # This handles the binoculars
        # It quickly lerps from FOV 45 (assumed to be the initial setting)
        # to 13 - the smaller the final value, the more it is 'zoomed in'
        if self.keyMap["zoom"]:
            if not self.zoomed:
                self.zoomed=1
                fovZoomer = LerpFunc(self.fovSet, 0.1, 45, 13, 'easeOut', [], "zoomer")
                fovZoomer.start()
        else:
            if self.zoomed:
                self.zoomed=0
                fovZoomer = LerpFunc(self.fovSet, 0.1, 13, 45, 'easeIn', [], "zoomer")
                fovZoomer.start()
        
        return Task.cont

w = World()
run()