FirstPersonCamera3D updated to v1.9
First of all, I just want to say thanks for all the feedback I got from you guys. So, THANKS!
A few MAJOR changes have been made to the FirstPersonCamera3D class:
Change made to initialize method
In order for the class to work, the initialize method now accepts the viewport instance as it’s first argument instead of a reference to the stage (the other two arguments are explained in the next paragraph).
Movement logic revamped
The entire code for the actual moving around has been totally revamped, hence, removing its dependency to Tweener or any other tween engine for that matter. Also, all ENTER_FRAMES have been removed and movement is calculated in the “look” method. With the new logic, moving in diagonal directions is way smoother also.
Along with the revamp came two new properties which will greatly increase the way we use movement: “speed” and “friction“. By adding these properties, we can simulate various types of movement, anywhere from gliding on ice to trying to move in quicksand!
Speed could be a number 0 – 1 where 0 is full stop and 1 is really really fast. Defaults to .2
Friction is a number 0 – 1 where 1 does not allow you to move and 0 is really really slippery. Defaults to .1
These properties can be directly set in the “initialize” method as so:
… where the second argument is “speed” and the third argument is “friction”
Change in the “mapKeys” method
The “mapKeys” methods can now accept any of the following:
FirstPersonCamera3D.WASD; //maps the wasd keys
FirstPersonCamera3D.WASD_AND_ARROWS; //maps both arrows and wasd keys
New moveBounds property
Another newly added property is “moveBounds”. This property limits the cameras movement to a set of predetermined boundries. Essentially, its like placing the camera within 4 walls. This is the syntax:
For several hours I tinkered around with the class trying to set up collision detection, but then I figured that the best way to deal with collisions is to actually have some sort of “plugin” system where you could essentially integrate the FirstPersonCamera3D object with your Physics Engine of choice! This may be coming sooner than you think
Here’s the new class:
{
import flash.display.Stage;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.utils.*;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.core.math.Number3D;
import org.papervision3d.view.Viewport3D;
/**
* Camera3D object with First Person functionality
* @author Reynaldo Columna
*
*/
public class FirstPersonCamera3D extends Camera3D
{
/**
* Holds the key codes for the arrow keys
*/
public static const ARROWS:Object = {left:37, right:39, forward:38, back:40};
/**
* Holds the key codes for the "w", "a", "s" and "d" keys
*/
public static const WASD:Object = {left:65, right:68, forward:87, back:83};
/**
* Holds the key codes for both the "w", "a", "s" and "d" keys and the arrow keys
*/
public static var WASD_AND_ARROWS:Array;
protected static const UP_ARROW:int = 38;
protected static const LEFT_ARROW:int = 37;
protected static const RIGHT_ARROW:int = 39;
protected static const DOWN_ARROW:int = 40;
private static var FORWARD_KEY:int;
private static var LEFT_KEY:int;
private static var RIGHT_KEY:int;
private static var BACK_KEY:int;
protected var _sensitivity:Number;
protected var _maxRotationX:Number;
protected var _maxRotationY:Number;
protected var _stageReference:Stage;
protected var _viewportReference:Viewport3D;
protected var _friction:Number;
protected var _speed:Number;
protected var _mode:String;
protected var _moveBounds:Object;
private var mouseDownPositionX:Number;
private var mouseDownPositionY:Number;
private var currentRotationY:Number;
private var currentRotationX:Number;
private var velocity:Number3D;
private var movingForward:Boolean;
private var movingLeft:Boolean;
private var movingRight:Boolean;
private var movingBack:Boolean;
private var doManualLook:Boolean;
private var useBothSetsOfKeys:Boolean;
/**
* Constructs the camera just as you would with a Camera3D object
* @param fov
* @param near
* @param far
* @param useCulling
* @param useProjection
*
*/
public function FirstPersonCamera3D(fov:Number=60, near:Number=10, far:Number=5000, useCulling:Boolean=false, useProjection:Boolean=false)
{
super(fov, near, far, useCulling, useProjection);
}
/**
* initializes the camera functionality
* @param $viewport viewport
* @param $speed 0 - 1 where 0 is full stop and 1 is really really fast! Defaults to .2
* @param $friction 0 - 1 where 1 does not allow you to move and 0 is really really slippery! Defaults to .1
*
*/
public function initialize($viewport:Viewport3D, $speed:Number = 0.2, $friction:Number = 0.1):void
{
viewportReference = $viewport;
stageReference = viewportReference.containerSprite.stage;
friction = map($friction, 0, 1, 1, 0);
speed = map($speed, 0, 1, 0, 100);
movingForward = false;
movingLeft = false;
movingRight = false;
movingBack = false;
moveBounds = null;
velocity = new Number3D();
useBothSetsOfKeys = false;
FirstPersonCamera3D.WASD_AND_ARROWS = [FirstPersonCamera3D.ARROWS, FirstPersonCamera3D.WASD];
addListeners();
}
/**
* Maps the keys to be used for movement. If no keys are specified, the arrow keys are used as defauilt.
* @param keys An object with the following values: forward, back, left and right. Each should be a key code.
*
*/
public function mapKeys(keys:* = null):void
{
if(keys == FirstPersonCamera3D.WASD_AND_ARROWS){
useBothSetsOfKeys = true;
keys = null
}
FirstPersonCamera3D.FORWARD_KEY = ((keys != null) ? keys.forward : FirstPersonCamera3D.UP_ARROW) || FirstPersonCamera3D.UP_ARROW;
FirstPersonCamera3D.BACK_KEY = ((keys != null) ? keys.back : FirstPersonCamera3D.DOWN_ARROW) || FirstPersonCamera3D.DOWN_ARROW;
FirstPersonCamera3D.LEFT_KEY = ((keys != null) ? keys.left : FirstPersonCamera3D.LEFT_ARROW) || FirstPersonCamera3D.LEFT_ARROW;
FirstPersonCamera3D.RIGHT_KEY = ((keys != null) ? keys.right : FirstPersonCamera3D.RIGHT_ARROW) || FirstPersonCamera3D.RIGHT_ARROW;
}
/**
* Unregisters mapped keys
*
*/
public function removeKeyMapping():void
{
FirstPersonCamera3D.FORWARD_KEY = FirstPersonCamera3D.BACK_KEY = FirstPersonCamera3D.LEFT_KEY = FirstPersonCamera3D.RIGHT_KEY = -1;
}
/**
* Makes the camera "look" around based on the mouse position
* @param $sensitivity Number 0 to 1 representing how sensible the camera is to the mouse movement.
* @param $maxRotationX Number representing the max horizontal rotation (0 to 360)
* @param $maxRotationY Number representing the max vertical rotation (0 to 360)
*
*/
public function look($sensitivity:Number = 0.5, $maxRotationX:Number = 90, $maxRotationY:Number = 90):void
{
sensitivity = $sensitivity;
maxRotationX = $maxRotationX;
maxRotationY = $maxRotationY;
if(mode == "auto"){
var horizontalDegrees:Number = map(stageReference.mouseX, 0, stageReference.stageWidth, maxRotationX * -1, maxRotationX);
var verticalDegrees:Number = map(stageReference.mouseY, 0, stageReference.stageHeight, maxRotationY * -1, maxRotationY);
rotationX += (verticalDegrees - rotationX) * sensitivity;
rotationY += (horizontalDegrees - rotationY) * sensitivity;
}else if(mode == "manual"){
if(doManualLook){
var horizontalDistance:Number = stageReference.mouseX - mouseDownPositionX;
var verticalDistance:Number = stageReference.mouseY - mouseDownPositionY;
var finalRotationX:Number = currentRotationX + (verticalDistance * (sensitivity * .5));
var finalRotationY:Number = currentRotationY + (horizontalDistance * (sensitivity * .5));
rotationX += (finalRotationX - rotationX) * sensitivity;
rotationY += (finalRotationY - rotationY) * sensitivity;
}
}
renderMovement();
}
private function addListeners():void
{
stageReference.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
stageReference.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp);
}
private function removeListeners():void
{
stageReference.removeEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown);
stageReference.removeEventListener(KeyboardEvent.KEY_UP, handleKeyUp);
stageReference.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
stageReference.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
}
private function map(value:Number, min1:Number, max1:Number, min2:Number, max2:Number):Number
{
return min2 + (max2 - min2) * (value - min1) / (max1 - min1);
}
private function renderMovement():void
{
if(movingBack){
velocity.z -= speed * Math.cos(rotationY * (Math.PI / 180));
velocity.x -= speed * Math.sin(rotationY * (Math.PI / 180));
}
if(movingForward){
velocity.z += speed * Math.cos(rotationY * (Math.PI / 180));
velocity.x += speed * Math.sin(rotationY * (Math.PI / 180));
}
if(movingLeft){
velocity.z += speed * Math.sin(rotationY * (Math.PI / 180));
velocity.x -= speed * Math.cos(rotationY * (Math.PI / 180));
}
if(movingRight){
velocity.z -= speed * Math.sin(rotationY * (Math.PI / 180));
velocity.x += speed * Math.cos(rotationY * (Math.PI / 180));
}
velocity.multiplyEq(friction);
x += velocity.x;
z += velocity.z;
if(moveBounds != null){
if(x < moveBounds.left) x = moveBounds.left;
if(x > moveBounds.right) x = moveBounds.right;
if(z < moveBounds.back) z = moveBounds.back;
if(z > moveBounds.front) z = moveBounds.front;
}
}
public function clear():void
{
removeListeners();
stageReference = null;
viewportReference = null;
}
/*
Properties
*/
/**
* Sets or returns the look sensitivity
* @return Number
*
*/
public function get sensitivity():Number { return _sensitivity }
public function set sensitivity(value:Number):void { _sensitivity = value; }
/**
* Sets or returns the maximum horizontal rotation
* @return Number
*
*/
public function get maxRotationX():Number { return _maxRotationX }
public function set maxRotationX(value:Number):void { _maxRotationX = value; }
/**
* Sets or returns the maximum vertical rotation
* @return Number
*
*/
public function get maxRotationY():Number { return _maxRotationY }
public function set maxRotationY(value:Number):void { _maxRotationY = value; }
/**
* Sets or returns a reference to the stage
* @return Number
*
*/
public function get stageReference():Stage { return _stageReference }
public function set stageReference(value:Stage):void { _stageReference = value; }
/**
* Sets or returns a reference to the viewport
* @return
*
*/
public function get viewportReference():Viewport3D { return _viewportReference }
public function set viewportReference(value:Viewport3D):void { _viewportReference = value; }
/**
* Sets or returns the friction
* @return
*
*/
public function get friction():Number { return _friction }
public function set friction(value:Number):void { _friction = value; }
/**
* Returns or set the speed of movement
* @return
*
*/
public function get speed():Number { return _speed }
public function set speed(value:Number):void { _speed = value; }
/**
* Sets or returns the camera mode. The mode can be either auto or manual. Setting the mode to
* auto will make the camera look around automatically based on the mouse movement. Setting it
* to manual will make the camera look around only when you click and drag.
* @return
*
*/
public function get mode():String
{
return _mode
}
public function set mode(value:String):void
{
_mode = value;
switch(_mode.toLowerCase())
{
case "manual" :
stageReference.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
stageReference.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
break
case "auto" :
stageReference.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
stageReference.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
break
}
}
/**
* Sets or gets an object whith values that contraing the camera movement with bounds. The values are
* "left", "right", "back" and "front"
* @return
*
*/
public function get moveBounds():Object { return _moveBounds; }
public function set moveBounds( value:Object ) { _moveBounds = value; }
/*
Handlers
*/
private function handleKeyDown(evt:KeyboardEvent):void
{
switch(evt.keyCode)
{
case FirstPersonCamera3D.FORWARD_KEY:
movingForward = true;
break;
case FirstPersonCamera3D.BACK_KEY:
movingBack = true;
break;
case FirstPersonCamera3D.LEFT_KEY:
movingLeft = true;
break;
case FirstPersonCamera3D.RIGHT_KEY:
movingRight = true;
break;
}
if(useBothSetsOfKeys){
switch(evt.keyCode)
{
case FirstPersonCamera3D.WASD.forward:
movingForward = true;
break;
case FirstPersonCamera3D.WASD.back:
movingBack = true;
break;
case FirstPersonCamera3D.WASD.left:
movingLeft = true;
break;
case FirstPersonCamera3D.WASD.right:
movingRight = true;
break;
}
}
}
private function handleKeyUp(evt:KeyboardEvent):void
{
switch(evt.keyCode)
{
case FirstPersonCamera3D.FORWARD_KEY:
movingForward = false;
break;
case FirstPersonCamera3D.BACK_KEY:
movingBack = false;
break;
case FirstPersonCamera3D.LEFT_KEY:
movingLeft = false;
break;
case FirstPersonCamera3D.RIGHT_KEY:
movingRight = false;
break;
}
if(useBothSetsOfKeys){
switch(evt.keyCode)
{
case FirstPersonCamera3D.WASD.forward:
movingForward = false;
break;
case FirstPersonCamera3D.WASD.back:
movingBack = false;
break;
case FirstPersonCamera3D.WASD.left:
movingLeft = false;
break;
case FirstPersonCamera3D.WASD.right:
movingRight = false;
break;
}
}
}
private function handleMouseDown($event:MouseEvent):void
{
mouseDownPositionX = stageReference.mouseX;
mouseDownPositionY = stageReference.mouseY;
currentRotationY = rotationY;
currentRotationX = rotationX;
doManualLook = true;
}
private function handleMouseUp($event:MouseEvent):void
{
doManualLook = false;
}
}
}
[EDIT]
General setup :
firstPersonCamera.initialize(viewport, .2, .2);
firstPersonCamera.y = 750;
firstPersonCamera.focus = 11;
firstPersonCamera.mode = "manual";
firstPersonCamera.moveBounds = {front:3500, back:-3500, left:-3500, right:3500};
firstPersonCamera.mapKeys(FirstPersonCamera3D.WASD_AND_ARROWS);
.
.
.
override protected function onRenderTick(event:Event=null):void
{
firstPersonCamera.look(0.5);
renderer.renderScene(scene, firstPersonCamera, viewport);
}
Awesome! Thank you. Saved me a ton of work. Camera controls have be a huge pain as of late and this solved my problems in 15 minutes.
thx man! great work
i’ll be waiting for de collision detection
Hi Rey, Thank you for your FirstPersonCamera3D. I have used it and it is nice & really useful,
but I have problem when I’m going to load the swf that I created from using your class. What I wanted to do is to make a preloader for all my SWF.
Thanks
Hey, thanks for the great Camera!
i was just wondering is it possible to include collision detection in papervision at all.
Lets say in our fps we’re standing inside a room which is a cube.
trying to detect collision detection with hitTestObject, would not work since we’re in the middle of the cube. We could go with a subcube inside that room, that serves as our walkable area… which seems to be only a good way for a very easy architecture. How would we however then adapt the movement of the camera when we hit a wall?
i just can’t seem to figure it out myself.
Maybe someone has thought about this before?
thanks
So what should I change in the code to import a 3d room (lets say, from sketchup) and be able to move through it.
Also, where do I use the code snippet of your “General Setup” because it doesn’t recognize “firstPersonCamera”.
Great Class, thanks for sharing. Have you thought of making a variation where you can ‘steer’ the camera,
so pressing left rotates the camera view anti-clockwise, and so on? Rather than the camera always looking at a fixed point?
Hi,
I get a 1046 error:
1046: Type was not found or was not a compile-time constant: SoundTransform.
any idea?
Thanx,
J.
Hi!
first of all thanks for the class!!
but I´m receiving an error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at org.papervision3d.cameras::FirstPersonCamera3D/set mode()
it points to the stage reference. the error comes even after ADDED_TO_STAGE is dispatched in the document class…
Any Idea would be great!!
Thanks for the wonderful blog!
bruno
I found the solution:
I was initializing the class after mode was set… and of course the eventListeners didn´t have a stage reference yet..
Thanks again for sharing!
bruno