simple 2d debug camera, aswell as 'camera helper'

since my recent addiction in building 2d video games in panda3d (odd, I know :wink: ) I found it incredibly annoying to use the default 3d debug camera in 2d video games… so instead I built a simple 2d-based camera, you can move your mouse to the outer regions of the screen and the camera moves that direction, it’s incredibly intuitive.

Also, it’ll swap out the default 3d camera enabled by default with a 2d-only orthographic one. Also I found it strange how annoying it was to pick (click-on) objects in the 3d render nodepath, so instead I built some glue code that enables clicking on objects with any of the three mouse buttons (leftClick, centerClick, rightClick) and throws whatever event you so desire. (aka: np.setTag(‘pick’, ‘myCustomEventName’)) it’s also fully capable of passing arguments to the function/method that’s bound to that event, keep in mind it also passes one of three possible button modes to the event, (see the bottom of the code "if name == ‘main’ area for a full example) using some code like "np.setPythonTag(‘pick-args’, [arg1, arg2, arg3, etc, etc])

Anywho, I thought it was pretty cool and maybe someone else will find it useful. :wink:

Here it is:

from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import OrthographicLens
from pandac.PandaModules import GeomNode, NodePath
from pandac.PandaModules import CollisionTraverser
from pandac.PandaModules import CollisionNode, CollisionRay, CollisionHandlerQueue

This class will do the following:
	Disable the default mouse driver
	Add a 2d-only mouse driver (outer bounds of window)
	Enables picking on any node under the render node,
	Usage is as follows:
	cam = simpleCam()

	After instancing, there are a few helper methods: <- shows the camera-picking ray
		cam.hide() <- hides the camera-picking ray
		cam.disableMouse() <- disables mouse input
			Also calls cam.disableCtrl()
		cam.enableMouse() <- enables mouse input (default)
			Also calls cam.enableMouse()
		cam.enableCtrl() <- Enables the use of control for Y depth (called by cam.enableMouse())
		cam.disableCtrl() <- Disables the use of control for Y depth (called by cam.disableMouse())

	For enabling picking on a nodepath, do the following:

	smiley = loader.loadModel('smiley') # loading the smiley
	smiley.reparentTo(render) # put it under the render node
	smiley.setTag('pick', 'smiley-onClick') # set the pick tag, and give an event name
	smiley.setPythonTag('pick-args', ['arg1', 2, 'etc...']) #(Optional): set the python tag 'pick-args' = list

	def myFunction(button, arg1, arg2, arg3): #make a function that takes button, arg1, arg2, and arg3
		print "Smiley was clicked with %s" % button # print which button was used
		# possible buttons: ('leftClick', 'centerClick', 'rightClick')

	base.accept('smiley-onClick', myFunction) # accept the event name we set in the pick tag

class simpleCam(DirectObject, object):
	def __init__(self):
		lens = OrthographicLens()
		lens.setFilmSize(20, 15)  # Or whatever is appropriate for your scene
		base.disableMouse() = NodePath('simpleCam-center')

		self.leftClick = False
		self.centerClick = False
		self.rightClick = False
		self.isPicking = False


		self.accept('mouse1', self.mouse, ['leftClick', True])
		self.accept('mouse1-up', self.mouse, ['leftClick', False])

		self.accept('mouse2', self.mouse, ['centerClick', True])
		self.accept('mouse2-up', self.mouse, ['centerClick', False])

		self.accept('mouse3', self.mouse, ['rightClick', True])
		self.accept('mouse3-up', self.mouse, ['rightClick', False])

		self.speed = 16.0
		self.border = 0.8

		if base.cTrav == False:
			base.cTrav = CollisionTraverser()

		self.pickerNode = CollisionNode('pickingRay')
		self.pickerNP = camera.attachNewNode(self.pickerNode)
		self.pickerRay = CollisionRay()
		self.myHandler = CollisionHandlerQueue()
		base.cTrav.addCollider(self.pickerNP, self.myHandler)

	def mouse(self, mode, state):
		self.__setattr__(mode, state)
		if state == True and self.isPicking == True:

	def show(self):

	def hide(self):

	def disableMouse(self):

	def enableMouse(self):
		taskMgr.add(self.cameraTask, 'cameraTask')

	def enableCtrl(self):
		self.accept('lcontrol',, [90])
		self.accept('lcontrol-up',, [0])

	def disableCtrl(self):

	def disablePicking(self):
		self.isPicking = False

	def enablePicking(self):
		self.isPicking = True

	def cameraTask(self, task):
		prop =
		x = prop.getXSize()
		y = prop.getYSize()
		if self.leftClick or self.centerClick or self.rightClick:
			if base.mouseWatcherNode.hasMouse():
				x = base.mouseWatcherNode.getMouseX()
				y = base.mouseWatcherNode.getMouseY()
				dt = globalClock.getDt()
				if x > self.border:, self.speed * dt)
				elif x < -self.border:, -self.speed * dt)

				if y > self.border:, self.speed * dt)
				elif y < -self.border:, -self.speed * dt)

				self.lastX, self.lastY = x, y
		return task.cont

	def picking(self, mode):
		if base.mouseWatcherNode.hasMouse():
			mpos = base.mouseWatcherNode.getMouse()
			self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

			if self.myHandler.getNumEntries() > 0:
				obj = self.myHandler.getEntry(0).getIntoNodePath()
				event = obj.getNetTag('pick')
				args = obj.getNetPythonTag('pick-args')

				if event:
					if len(args) > 0:
						tmp = [mode]
						for i in args: tmp.append(i)
						messenger.send(event, tmp)
						messenger.send(event, [mode])

if __name__ == '__main__':
	from direct.directbase import DirectStart
	cam = simpleCam()

	smiley = loader.loadModel('smiley')
	smiley.setTag('pick', 'smiley-onClick')
	smiley.setPythonTag('pick-args', ['arg1', 2, 'etc...'])

	def myFunction(button, arg1, arg2, arg3):
		print "Smiley was clicked with %s" % button

	base.accept('smiley-onClick', myFunction)

Good luck and lemme know if you need any help, I’d be more than happy to help you out :wink: