OppiCam is a camera system designed especially for RPGs but can very well be used on any other map. This version currently consists of two main vJass libraries, each containing a proper camera system. The system also contains two optional plugin libraries that add keyboard control to the cameras. For these plugins to work Anitarf's ArrowKeys is required which is also included in this map.
Library ThirdPersonCam:
ThirdPersonCam adds a dynamic third person camera to the game. Its main purpose is to enable the user a highly configurable camera without the usual limitations of third person cameras. Using this camera you can basically use any kind of terrain on your map without caring about the cam falling below the terrain or clipping parts of the terrain.
Library FixedCam:
FixedCam adds a camera similar to the one used in games like the Resident Evil series. The camera stays at a fixed point and follows a given unit. This cam is especially useful in small buildings or caves etc. where you are forced to navigate through very narrow passages.
'OppiCamfunctionsandvariables'
library 'ThirdPersonCam':
// Public functions:functionEnableThirdPersonCamtakesplayerwhichPlayer, unitwhichUnit, realfirstPanreturnsnothing// Locks whichPlayer's camera to whichUnit over firstPan seconds. Use 0 for an// instant pan. To deactivate the cam again pass a null unit and any real value to // the function. The camera will also stop working if whichUnit is nulled while the // camera is activated.endfunctionfunctionIsThirdPersonCamActivetakesintegerpreturnsboolean// Returns true if the camera is activated for Player(p).endfunctionfunctionResetThirdPersonCamAoAtakesintegerpreturnsnothing// Resets ControlableAoA to default for Player(p).endfunctionfunctionResetThirdPersonCamRottakesintegerpreturnsnothing// Resets ControlableRot to default for Player(p).endfunction// Public variables:realarrayThirdPersonCamAoA// This sets ControlableAoA for Player(array number).realarrayThirdPersonCamRot// This sets ControlableRot for Player(array number).// Private constants:privateconstantrealDISTANCE_AOA_MINprivateconstantrealDISTANCE_DISTANCE_MINprivateconstantrealDISTANCE_AOA_MAXprivateconstantrealDISTANCE_DISTANCE_MAX// These values define a linear, mathematical function for the camera's target // distance. You can assign both the max and min distance to two angles of attack.// All values inbetween will be interpolated linearly.privateconstantrealOFFSET_AOA_MINprivateconstantrealOFFSET_OFFSET_MINprivateconstantrealOFFSET_AOA_MAXprivateconstantrealOFFSET_OFFSET_MAX// These values work just like the DISTANCE values and define a linear function for // the distance of an offset point in front of the unit the camera pans to. The // camera does not pan to the unit directly in order to keep the unit in the lower // half of the screen especially at higher (technically lower) angles.privateconstantrealZ_OFFSET// This is the camera's z-offset when panning to a unit whose flying height is 0.// The unit's flying height will be added to the camera's z-offset automatically.privateconstantrealTIMEOUT// Timeout for the periodic camera timer. Smaller values make the camera react// faster but also affect performance.privateconstantrealTERRAIN_SAMPLING// This is the interval in which the terrain is sampled periodically to calculate // the anti-clipping angle. Smaller values make the calculations more accurate but // also heavily affect performance.privateconstantrealPAN_DURATION// Duration over which camera fields are set periodically. Higher values make the// camera movements smoother but also slower.privateconstantrealANGLE_ABOVE_TERRAIN// The camera will try to keep the camera at least ANGLE_ABOVE_TERRAIN degrees above// the terrain.privateconstantrealDEFAULT_AOA// Default value for the controlable camera angle.privateconstantrealDEFAULT_ROT// Default value for the controlable camera rotation.privateconstantrealFIELD_OF_VIEW// Camera's field of view.privateconstantrealFAR_Z// Camera's clipping distance.privateconstantrealCLIFF_DISTANCE// This distance is added to the camera target distance for the angle calculations// to avoid that the cursor targets a cliff that is actually behind the camera eye.endlibrarylibrary 'FixedCam':
// Public functions:functionEnableFixedCamtakesplayerwhichPlayer, unitwhichUnit, realx, realy, realz, realfirstPanreturnsnothing// Activates FixedCam for whichPlayer focusing on whichUnit from Location(x, y) at a// z-offset of z units over firstPan seconds. Use 0 for an instant pan. To// deactivate the cam again pass a null unit and any real values to the function. // The camera will also stop working if whichUnit is nulled while the camera is // activated.endfunctionfunctionIsFixedCamActivetakesintegerpreturnsboolean// Returns true if the camera is activated for Player(p).endfunctionfunctionResetFixedCamAoAtakesintegerpreturnsnothing// Resets ControlableAoA to default for Player(p).endfunctionfunctionResetFixedCamRottakesintegerpreturnsnothing// Resets ControlableRot to default for Player(p).endfunction// Public variables:realarrayFixedCamAoA// This sets ControlableAoA for Player(array number).realarrayFixedCamRot// This sets ControlableRot for Player(array number).// Private constants:privateconstantrealTIMEOUT// Timeout for the periodic camera timer. Smaller values make the camera react// faster but also affect performance.privateconstantrealPAN_DURATION// Duration over which camera fields are set periodically. Higher values make the// camera movements smoother but also slower.privateconstantrealFIELD_OF_VIEW// Camera's field of view.privateconstantrealFAR_Z// Camera's clipping distance.endlibrary
Plugin libraries:
'OppiCamPluginsfunctionsandvariables'
library 'TPCKeyboardPlugin' requiresArrowKeys, ThirdPersonCam:
// Private constants:privateconstantbooleanINVERTED// Flips the horizontal arrow keys when set to true.privateconstantrealTIMEOUT// Timeout for the periodic keyboard timer.privateconstantrealMIN_AOA// Minimum controlable angle of attack.privateconstantrealMAX_AOA// Maximum controlable angle of attack.privateconstantrealMAX_ROT// Maximum controlable camera rotation difference.privateconstantrealAOA_INTERVAL// Value added to the angle of attack per TIMEOUT and per keyboard event.privateconstantrealROT_INTERVAL// Value added to the rotation per TIMEOUT and per keyboard event.endlibrarylibrary 'FCKeyboardPlugin' requiresArrowKeys, FixedCam:
// Private constants:privateconstantrealTIMEOUT// Timeout for the periodic keyboard timer.privateconstantrealMAX_AOA// Maximum controlable angle of attack difference.privateconstantrealMAX_ROT// Maximum controlable camera rotation difference.privateconstantrealAOA_INTERVAL// Value added to the angle of attack per TIMEOUT and per keyboard event.privateconstantrealROT_INTERVAL// Value added to the rotation per TIMEOUT and per keyboard event.endlibrary
Change log
1.2d:
Fixed the twitchy bug
1.2c:
No more public prefixes
1.2b:
Added CLIFF_DISTANCE constant
Added CappedReal function for the keyboard plugins
1.2:
Both keyboard plugins now require Anitarf's ArrowKeys which is included in the map
Some minor code changes. No changes in functionality
1.1c:
Updated some outdated documentation again. Code still the same
1.1b:
Removed some outdated functions and documentation
Changed how firstPan works slightly (thanks to Fled)
1.1:
Reworked most of the code
Fixed some bugs in the interpolation functions
Added a "firstPan" value to the Use-functions allowing smoother activation
Moved the keyboard functions to seperate plugins
Both cameras now feature several public functions and variables that can be accessed by the user at any time
Added a more detailed manual
Terrain sampling interval is now a constant
Angle above terrain is now a constant
Interpolation of DISTANCE and OFFSET values is now capped to the given values
1.0b:
Added text macros to shorten the code significantly
Added a list of functions that should not be redeclared
Both libraries now require "OppiCamBase"
Basic functionality still the same
Tutorial: How to set up OppiCam if you don't know jack about Jass
For some reason this system is attracting a lot of GUI users who really want to use this system but don't know anything or just very few about (v)Jass. The following tutorial is intended to help these people setting up an OppiCam in their map.
1. Meeting the requirements
In order to use this or any vJass script you need a vJass compiler that converts vJass code into standard Jass so wc3 can read it. The most common vJass compiler out there is JassHelper. However I recommend the tool pack Jass NewGen Pack which includes JassHelper and several other useful mapping tools (like TESH for Jass syntax highlighting). Just extract the Jass NewGen Pack somewhere and run NewGen WE.exe instead of the normal worldeditor.
Keep in mind that from now on you will always have to save your map before you can test it as long as you are using vJass in your map.
2. Copying the libraries
Libraries are chunks of vJass code that add new functionality to your map. Usually libraries are placed in "triggers".
Disclaimer: When speaking of "triggers" here I actually mean what GUI users call a trigger. These are not actual trigger objects as in localtriggert = CreateTrigger().
To do so open your trigger editor and create a new trigger (ctrl + T). Click on this new trigger and convert it into Jass by clicking Edit -> Convert to custom text. Your trigger should now looks like this (names may vary):
Delete all of it so you have an empty trigger in custom script format. Now copy the script you want to use from above and paste it into this trigger. Make sure you copy everything from and including library to endlibrary.
You can now call functions and variables that are provided by the copied library. Rinse and repeat for every library you want to copy.
Hint: If you downloaded the demo map you can just directly copy its libraries to your map by copying the according triggers.
For the main functionality of the third person cam you only need the library ThirdPersonCam. If you also want keyboard control for the camera you also need TPCKeyboardPlugin and Anitarf's ArrowKeys.
For the fixed cam it's FixedCam and FCKeyboardPlugin + ArrowKeys respectively.
You can also copy the function and variable lists if you need them. If you do, make sure you don't have those activated as they're just for documentation.
3. Activating the camera
After only copying the libraries you won't notice any difference ingame as this system doesn't activate itself. For the camera to be activated you have to set up a trigger that determines when the camera should be used.
For this you need custom script i.e., a line of Jass in your GUI trigger. The functions you need for activation are callEnableThirdPersonCam(Player, Unit, PanDuration) for the third person camera and callEnableFixedCam(Player, Unit, X, Y, Z, PanDuration) for the fixed camera respectively (X and Y are absolute coordinates for the camera whereas Z determines the camera's offset above the terrain).
Example:
In this example I set up a trigger that activates a third person cam as soon as a hero enters a region, attaching the camera to that hero.
Trigger:
ActivateThirdPersonCam
Events
Unit - A unit enters YourRegion <gen>
Conditions
((Entering unit) is A Hero) Equal to True
Actions
Set CameraUnit = (Entering unit)
Set CameraPlayer = (Owner of CameraUnit)
Custom script: call EnableThirdPersonCam(udg_CameraPlayer, udg_CameraUnit, 0)
Instead of creating two new variables you can also directly use the Jass functions GetOwningPlayer(GetEnteringUnit()) and GetEnteringUnit() if you know how to use them. In the custom script part you can use a different value for 0 if you don't want the camera to be activated instantly. A value of 2 for example will let the camera slowly pan to the unit over 2 seconds.
Naturally you can use any type of event, conditions and actions here.
4. Deactivating the camera
You may want to deactivate the camera at a certain point in the game. If not, just skip this step.
To deactivate either of the cameras for a player you just have to pass a null unit to the enabling function i.e., callEnableThirdPersonCam(Player, null, 0) for the third person camera and callEnableFixedCam(Player, null, 0, 0, 0, 0) for the fixed camera.
Example:
In this example I deactivate the camera as soon as a player's (previously in a variable stored) hero dies.
Trigger:
DeactivateThirdPersonCam
Events
Unit - A unit dies
Conditions
(Dying unit) Equal to Hero[(Player number of (Owner of (Dying unit)))]
Actions
Set CameraPlayer = (Owner of (Dying unit))
Custom script: call EnableThirdPersonCam(udg_CameraPlayer, null, 0)
5. Calibrating the camera
There are several constants that change the camera's look and feel. You find a full list and descriptions in the function and variable list above under "private constants".
To change them, just replace their value in the according script.
Example:
In this example I feel like the third person camera is too low and should be higher. So I go to the ThirdPersonCam library's calibration section (labeled "CONSTANTS") and look for a constant that might change this:
In this case the constant I'm looking for is Z_OFFSET, so I just increase it:
JASS:
privateconstantrealZ_OFFSET = 200
6. Learn Jass!
Ok, not quite necessary but still recommended.
7. Profit!
You are now (hopefully) able to set up an OppiCam in your map and enjoy the intense pleasures of this wondrous system. And if you're kind, you even give credits.
Hmm. Very nice. I really liked this camera, especially being able to change the angle of attack so fluidly. The fixed camera is also pretty cool, it really did remind me of RE. Nice work.
It behaves a little strangely near cliffs. I was able to get the camera to continuously try to give me view of my footman. The white box on the minimap was constantly resizing and it caused my camera to sort of rotate around until I moved him away from the cliff. Granted, I had to really try to get the camera to bug out a little.
This is incredible. I just tried both cameras recently and I find it very fluid and non-chunky like. Even tho as Veev said it is buggable (to an extent), it's still very rare in a normal game. +rep for awesome system
very nice system, havent tried it but is it possible to make it rotate to the footman's facing angle slower? tat would be awesome. (or not rotate at all)
very nice system, havent tried it but is it possible to make it rotate to the footman's facing angle slower? tat would be awesome. (or not rotate at all)
PAN_DURATION: Duration over which camera fields are set periodically. Higher values make the camera movements smoother but also slower.
If you want to make it not rotate at all you'll have to modify the system yourself (as I have done) because Oppi hates it not rotating/panning so he didn't include that option into the system. Although I think he should.
Either comment out the lines callSetCameraField(CAMERA_FIELD_ROTATION, GetUnitFacing(Unit[p]) + ControlableRot[p], PAN_DURATION) for rotation and callPanCameraToTimed(panx, pany, PAN_DURATION) for panning.
The other option is to add a boolean array check for each player so that they can turn it on/off whenever they want to (which is what I do).
Tried to but when trying to edit the code in and saving the edit the post just shows up the same as before... Also when I now try to edit the post again the edit box shows up empty as if I've written nothing. Bug on the forums maybe?
Edit: Added the ThirdPersonCam code. Strangely I can only post one of the libraries. Is there any character limit for posts?
Quote:
Originally Posted by Veev
It behaves a little strangely near cliffs. I was able to get the camera to continuously try to give me view of my footman. The white box on the minimap was constantly resizing and it caused my camera to sort of rotate around until I moved him away from the cliff. Granted, I had to really try to get the camera to bug out a little.
What do you mean by "sort of rotate around"? You mean vertically? The anti-clipping part of the camera isn't perfect because it would just be too performance-heavy if I was making it 100% safe. But I think it works as it is as long as you don't really try to bug the cam :).
Quote:
Originally Posted by MaD[Lion]
very nice system, havent tried it but is it possible to make it rotate to the footman's facing angle slower? tat would be awesome. (or not rotate at all)
Fleddy is right. Try PAN_DURATION.
Quote:
Originally Posted by Fledermaus
If you want to make it not rotate at all you'll have to modify the system yourself (as I have done) because Oppi hates it not rotating/panning so he didn't include that option into the system. Although I think he should.
I hate it because it cripples the system. If you don't want the camera to rotate or even pan to the unit you wouldn't need a system like that. And I will still try to track down and kill everyone that tries to "unlock" that cam (same still applies to you Fled).
Yes, by "sort of rotate around" it was moving around my hero in a circular fashion, trying to find him. But, like I said, the camera was only doing that because I was trying pretty hard to bug it out.
It's nice but it doesn't work for flying units about a cliff (I changed the subject to an Orc Wind Rider).
Could you elaborate "doesn't work" a bit?
I just tried a gryphon rider and it worked fine. Ok Blizzard's flying height smoothing causes GetUnitFlyHeight to not always return the correct flying height but that can easily be solved by adding 'Arav' to the unit. Although adding that to the system would limitate the user quite a bit imo so I left it out.
I have only looked at the third person camera code so far. My thoughts:
You have a lot of unneeded function calls, functions which you only call once and could easily inline, getting rid of code bloat such as the loc struct. You could argue that the functions are there to help organize the code and make it more readable but since your code is entirely uncommented you can hardly use readability as an argument.
GetHighestPointInLine isn't really what you need here. You need the highest angle of attack, a lower point that's closer to the unit could get more in the way of the camera than a slightly higher point that's further away.
I disagree with the hardcoded reset command, especially because it uses the clumsy and ugly chat messages. Instead you should provide a public function for resetting the orientation and let the user decide how and when to run it with external code. In fact, you could do the same for all camera controls, moving all the arrow keys code to an optional module.
I think it would be better if you only interpolated values between OFFSET_AOA_1 and 2, while if the angle is greater than OFFSET_AOA_1 the offset would simply get capped at OFFSET_OFFSET_1. That way you would also no longer need the OFFSET_LIMIT constant.
The code isn't quite streamlined yet, I already mentioned the unneeded function calls, then there's some unneeded local variables etc. You could further reduce the code bloat by using some external libraries such as ArrowKeys.
Edit: I was reminded of a function for getting a precise camera z offset that Toadcop posted in this thread. I see you use a different method, I haven't been able to test how it works yet so I don't know if either of the two methods is better than the other, I just thought I'd link to it for your consideration. I notice you smooth out your camera movement anyway while that function was designed to get an instant camera lock so it might not apply.
Edit: I was reminded of a function for getting a precise camera z offset that Toadcop posted in this thread. I see you use a different method, I haven't been able to test how it works yet so I don't know if either of the two methods is better than the other, I just thought I'd link to it for your consideration. I notice you smooth out your camera movement anyway while that function was designed to get an instant camera lock so it might not apply.
That link doesn't work for me but what I am doing to get a precise camera z offset is pretty simple once you figured out the code: GetCameraField(CAMERA_FIELD_ZOFFSET) - ((GetCameraTargetPositionZ() - GetLocationZ(Loc)) - (Z_OFFSET + GetUnitFlyHeight(Unit[p])))
First I get (GetCameraTargetPositionZ() - GetLocationZ(Loc)
This returns the actual difference between the camera target's z (that includes the camera's smoothened z-offset) and the terrain height of the target location. So basically what this returns is the camera's real z offset. Let's call this RealZ.
Then I get the difference between RealZ and the actual z offset I want: RealZ - (Z_OFFSET + GetUnitFlyHeight(Unit[p])) (let's set the flying height to 0 to simplify it a bit). Let's call this value "TooHigh"
If this value is positive then the camera is actually too high meaning I have to reduce the current "non-real" but controlable z offset by this difference. GetCameraField(CAMERA_FIELD_ZOFFSET) - TooHigh
That's it... not that complicated imo.
Quote:
Originally Posted by Anitarf
You have a lot of unneeded function calls, functions which you only call once and could easily inline, getting rid of code bloat such as the loc struct. You could argue that the functions are there to help organize the code and make it more readable but since your code is entirely uncommented you can hardly use readability as an argument.
GetHighestPointInLine isn't really what you need here. You need the highest angle of attack, a lower point that's closer to the unit could get more in the way of the camera than a slightly higher point that's further away.
True and that's exactly what the function does. Its name is just a bit outdated. Sorry about that. Guess I'll just inline that function into the camera function anyway. That will also get rid of the struct as you've mentioned.
Quote:
Originally Posted by Anitarf
I disagree with the hardcoded reset command, especially because it uses the clumsy and ugly chat messages. Instead you should provide a public function for resetting the orientation and let the user decide how and when to run it with external code. In fact, you could do the same for all camera controls, moving all the arrow keys code to an optional module.
That's actually a pretty good idea. I'll give it a try.
Quote:
Originally Posted by Anitarf
I think it would be better if you only interpolated values between OFFSET_AOA_1 and 2, while if the angle is greater than OFFSET_AOA_1 the offset would simply get capped at OFFSET_OFFSET_1. That way you would also no longer need the OFFSET_LIMIT constant.
This might be another good idea. I guess I'll keep my original method for the DISTANCE values though as it's easier for the user to adjust the camera settings then.
Quote:
Originally Posted by Anitarf
The code isn't quite streamlined yet, I already mentioned the unneeded function calls, then there's some unneeded local variables etc. You could further reduce the code bloat by using some external libraries such as ArrowKeys.
I don't think I need such a complex library featuring key sequences and stuff. And many local variables that seem unneeded at first (like angleofattack in ApplyCam) are just used to reduce function calls. Although I'm not entirely sure if that's always the fastest way.
Edit: Ok I just noticed unitx, unity, panx, pany are actually not absolutely necessary. I'll watch out more for stuff like that.
Toadcops CamZ function just brakes Blizzards stupid camera smoothning, so you can get more accurate Z heigths.
It has caused some weird bugs to me, but you should maybe test it and see if it is better for you.