• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Mouse-controlled camera rotation on locked cams

Status
Not open for further replies.
I once made a system that abused GetCameraTargetPosition and periodic repanning with very close CameraBounds to detect arrow-key and mouse-to-border camera movement on locked-hero cams.
However, I could never make it look smooth enough due to a lot of flaws with the camera natives (SetCameraBounds was too slow and SetCameraTargetPosition and PanCameraToTimed both interrupt player initiated camera movements).

This is how the original algorithm worked:

Code:
- Adjust camera bounds to the unit to pan the camera with 0 radius
(unfortunately it's not possible to use any of the panning natives, as those always interrupt any player controlled camera movement)
- wait until the camera bounds got updated (takes between 0.05 and 0.1 seconds from my tests)

- Adjust the camera bounds with a small detection margin (like 2-5 coordinate units) based on the last no-radius panning coordinates*
- wait until the camera bounds got updated

- as the update most likely happened just before the next cycle hits, the camera might have already moved through the camera movement initiated by the player
- you can now detect the camera movement via GetCameraTargetPosition()
- convert the X/Y movement direction into U/V (screen) movement direction and rotate the screen based on that
- repeat from the top


Just to go sure: this algorithm definitely works (as I said, I made such a system in the past)! But it doesn't look smooth on moving units, as only half of the update cycles of the camera bounds can be used to actually pan the camera back to the hero. The second cycle is always wasted for detecting camera movement without possible repanning on the hero.
The problem is the line marked with (*). I could technically already pan the camera to the unit here (along with the small radius), but this would make camera movement in the opposite quadrant of the unit walking direction un-detectable, as adjusting camera bounds will make the camera always move to the closest valid position within the camera bounds from the current position, not to the actual center of the camera bounds, which means it is already in one of the corners even without the player moving the camera, so there's no way to detect this particular moving direction.



So I came up with this algorithm, to double the panning rate of the camera:

Code:
- Adjust the camera bounds with a small detection margin only in X direction; lock Y direction to the unit
- wait until camera bounds got updated
- detect the movement in X direction
- convert the X movement direction into U/V (screen) movement direction and rotate the camera

- Adjust the camera bounds again with a small detection margin, but this time in Y direction; lock X direction to the unit
- wait until camera bounds got updated
- detect the movement in Y direction
- convert the Y movement direction into U/V (screen) movement direction and rotate the camera

- repeat

As you can see, I basicly get rid of one waiting interval of non-panning, as, assuming moving the camera does ALWAYS change BOTH x and y coordinates (so 0°, 90°, 180° and 270° rotation must be prohibited), there's always one coordinate that can be detected freely. The periodic adjustment of camera bounds with detection margin can always be based on the actual position of the unit instead of the position of the last re-centering process without detection margin.


There's a drawback to this approach, however: moving the mouse to the upper or lower border of the screen will, again, cause X and Y coordinates to change, always resulting in a left/right rotation as if you moved the mouse to those borders instead.
This can be overcome if we keep the decision of rotating the camera on hold until both X and Y movement direction are known (and can be translated into U/V coordinates). As Up/down and Left/right will never in the same quadrant, there can't be a false positive anymore.

This is the adjusted code for this method:
Code:
- Adjust the camera bounds with a small detection margin only in X direction; lock Y direction to the unit
- wait until camera bounds got updated
- detect the movement in X direction
- store the X movement direction into a variable

- Adjust the camera bounds again with a small detection margin, but this time in Y direction; lock X direction to the unit
- wait until camera bounds got updated
- detect the movement in Y direction
- convert the Y movement direction and the stored X movement into U/V (screen) movement direction and rotate the camera

- repeat


This effectively halves the detection rate again (to the same rate as with the old algorithm), but the panning rate still remains doubled, so it definitely looks smoother.


What do you guys think?



Btw, this approach is lightning-fast in multiplayer, as GetCameraTargetPosition gets updated a lot more frequently than the Arrow-Key events. So this basicly also comes with an in-built highly-responsive Arrow-Key event in case you don't mind the locked camera.
It all depends on how high the panning FPS can get.
 
Level 12
Joined
Mar 13, 2012
Messages
1,121
So this basicly also comes with an in-built highly-responsive Arrow-Key event
Does this work without visual glitches? The only requirements would be then

-locked camera
-player must keep his mouse away from the corners

right? Would be great for a funmap of mine to steer vehicles..
 
Does this work without visual glitches? The only requirements would be then

-locked camera
-player must keep his mouse away from the corners

right? Would be great for a funmap of mine to steer vehicles..
Yeah as long as the player doesn't move the mouse to the corners, it works.


Unfortunately, I had another problem I didn't think of before with my new approach. I'll try to fix it, but it requires some serious hacking.


Until then, this is the "vanilla version" with the old (simple) approach used.
It requires to play a little with the INTERVAL constant to find the optimal number.

Note that it doesn't turn or sway the camera yet (for testing purposes). It just detects the mouse-to-border events.

JASS:
library MouseCam initializer init

globals
	private integer Key = 0
	private unit U = null

	private constant real BOUNDOFFSET = 1
	private constant real INTERVAL = 0.05
	private constant real ROTATIONSPEED = 1
	private constant real AOASPEED = 1
	private constant real PRECISION = 0.1

	private constant integer KEY_NONE = 0
	private constant integer KEY_LEFT = 1
	private constant integer KEY_UP = 2
	private constant integer KEY_RIGHT = 3
	private constant integer KEY_DOWN = 4

	private boolean toggle = false
	private real StoredX = 0
	private real StoredY = 0

	private boolean initialized = false
endglobals

function ApplyCameraForPlayer takes unit u, player p returns nothing
        if GetLocalPlayer() == p then
	        set U = u
        endif
endfunction

private function CamMovementToArrowKey takes real dx, real dy returns integer
	local real AngleCamera = GetCameraField(CAMERA_FIELD_ROTATION) * bj_RADTODEG
	local real AngleDirection = Atan2(dy, dx) * bj_RADTODEG
	local real Delta = AngleCamera - AngleDirection

	if (RAbsBJ(dx) < PRECISION) and (RAbsBJ(dy) < PRECISION) then //within precision margin, assume no camera movement
		return KEY_NONE
	endif

	set Delta = Delta + 45

	if Delta > 360 then
		set Delta = Delta - 360
	elseif Delta < 0 then
		set Delta = Delta + 360
	endif

	return R2I(Delta/90.0)
endfunction


private function Periodic takes nothing returns nothing
	local real x = GetUnitX(U)
	local real y = GetUnitY(U)
	if GetUnitTypeId(U) != 0 then
		set toggle = not toggle

		if toggle then
			set Key = CamMovementToArrowKey(GetCameraTargetPositionX()-StoredX, GetCameraTargetPositionY()-StoredY)
			call SetCameraBounds(x, y, x, y, x, y, x, y)
			set StoredX = x
			set StoredY = y
		else
			call SetCameraBounds(StoredX-BOUNDOFFSET, StoredY-BOUNDOFFSET, StoredX-BOUNDOFFSET, StoredY+BOUNDOFFSET, StoredX+BOUNDOFFSET, StoredY+BOUNDOFFSET, StoredX+BOUNDOFFSET, StoredY-BOUNDOFFSET)
		endif
	else
		set Key = KEY_NONE
	endif
	if Key == KEY_LEFT then
		call BJDebugMsg("Left")
	elseif Key == KEY_UP then
		call BJDebugMsg("Up")
	elseif Key == KEY_RIGHT then
		call BJDebugMsg("Right")
	elseif Key == KEY_DOWN then
		call BJDebugMsg("Down")
	endif
endfunction

private function init takes nothing returns nothing
	local timer t = CreateTimer()
	call TimerStart(t, INTERVAL, true, function Periodic)
	set t = null
endfunction


endlibrary
 
Last edited:
What is the other problem?
Well it's actually just a derivate from the initial problem, which is detecting camera movement when the walking direction of the unit focused at is at the opposite quadrant of the camera movement direction, as setting camera bounds will make the camera always move to the closest point in bounds, not the center of bounds.

If I use the approach posted in the thread opener to use up/down detection to measure left/right every second iteration, not only do I lose the actual up/down detection (which would be acceptable in some cases), but it also looks less smooth if the character is moving straight vertical or horizontal.


The script I posted above is still the best and most versatile solution imho and detects both directions. But it's not smooth enough.
 
Status
Not open for further replies.
Top