- Joined
- Nov 11, 2006
- Messages
- 7,591
One day, Craka_J and I were discussing ideas for Ardent Heroes. He said that it would be cool if you could go into "Observer Mode" when someone died, so that you could view another person's camera. I thought it was a pretty cool idea, but we weren't far enough in development so I never started that system.
I've seen this sort of system requested a few times over the years. It all arises from the issue that GetCameraField()/GetCameraTargetPositionX()/Y()/Z() are all local and return different values for different clients. It might return 100.00 for player 1, but for player 2 it might return 256, it really depends on where their camera is. Sadly, I never really looked into making the system, so I just told people that they would need to sync the values (and I mentioned that it would be slow). After doing some tests in DysfunctionaI's thread, I just decided I'd give it a go.
The main problem with syncing is that it is slow. You can't really get around that. Until Blizzard ports wc3 to Battle.net 2.0 (pls blizz), you can always expect average 250 - 400 ms on battle.net. LAN is a lot better, ~100-300 ms for me usually. But that doesn't mean that the system is not viable. I tested it, and it works fairly well on LAN. I decided that I might as well share it to (1) show the technique for those unfamiliar with it (2) in case someone might find it useful. Here is the code for the library that broadcasts PlayerCameraData:
Demo "ObserverMode":
I'll clean the code and add comments a little later. And maybe I'll make ObserverMode into an actual system. Right now it is just a demo. To test it, download the map, put it in your maps folder, open JNGP 2.0.X. to start the multiplayer emulation, load it on LAN and then press ESC on either computer side. The rest should be self explanatory. As one client moves the camera, the other client's camera should move as well (with a slight delay, depending on your connection speeds). You can also test on two separate computers like I did.
Right now, you first have to call
Credits to Xarian for some info on sync'ing. Enjoy, and hopefully someone finds a use for this. It could be really cool for altered melee/AoS maps. Basically any map with free cameras. Do not use this for RPG/fps/custom cameras, that is just nonsense--you already have control over where the camera is.
I've seen this sort of system requested a few times over the years. It all arises from the issue that GetCameraField()/GetCameraTargetPositionX()/Y()/Z() are all local and return different values for different clients. It might return 100.00 for player 1, but for player 2 it might return 256, it really depends on where their camera is. Sadly, I never really looked into making the system, so I just told people that they would need to sync the values (and I mentioned that it would be slow). After doing some tests in DysfunctionaI's thread, I just decided I'd give it a go.
The main problem with syncing is that it is slow. You can't really get around that. Until Blizzard ports wc3 to Battle.net 2.0 (pls blizz), you can always expect average 250 - 400 ms on battle.net. LAN is a lot better, ~100-300 ms for me usually. But that doesn't mean that the system is not viable. I tested it, and it works fairly well on LAN. I decided that I might as well share it to (1) show the technique for those unfamiliar with it (2) in case someone might find it useful. Here is the code for the library that broadcasts PlayerCameraData:
JASS:
library PlayerCameraData
/**
*
* EnableCameraBroadcast(player p, boolean flag)
* - Enables/disables the storage (and syncing) of camera data for that player.
* RegisterCameraDataUpdate(boolexpr b)
* - Fires the boolexpr whenever the camera data is resynced.
*
* PlayerCameraData.x[playerId] -> x-coordinate of camera for player
* PlayerCameraData.y[playerId] -> y-coordinate of camera for player
* PlayerCameraData.aoa[playerId] -> angle of attack for player
* PlayerCameraData.rotation[playerId] -> rotation for player
*
*/
globals
private constant string CACHE_NAME = "PlayerCameraData"
private constant string MISSION_PREFIX = "PCD_"
private gamecache cache
private player array playerStack
private integer array playerIndex
private integer stackIndex = 0
private trigger syncTrigger = CreateTrigger()
private trigger updateEvent = CreateTrigger()
endglobals
function RegisterCameraDataUpdate takes boolexpr b returns nothing
call TriggerAddCondition(updateEvent, b)
endfunction
private function SyncCameraData takes nothing returns nothing
local integer localStackIndex = stackIndex
local integer i = localStackIndex
local integer id
local string missionKey
if i == 0 then
return
endif
loop
exitwhen i == 0
set id = GetPlayerId(playerStack[i])
set missionKey = MISSION_PREFIX + I2S(id)
if GetLocalPlayer() == playerStack[i] then
call StoreReal(cache, missionKey, "x", GetCameraTargetPositionX())
call StoreReal(cache, missionKey, "y", GetCameraTargetPositionY())
call StoreReal(cache, missionKey, "aoa", GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK) * bj_RADTODEG)
call StoreReal(cache, missionKey, "rot", GetCameraField(CAMERA_FIELD_ROTATION) * bj_RADTODEG)
call SyncStoredReal(cache, missionKey, "x")
call SyncStoredReal(cache, missionKey, "y")
call SyncStoredReal(cache, missionKey, "aoa")
call SyncStoredReal(cache, missionKey, "rot")
endif
set i = i - 1
endloop
call TriggerSyncReady()
set i = localStackIndex
loop
exitwhen i == 0
set id = GetPlayerId(playerStack[i])
set missionKey = MISSION_PREFIX + I2S(id)
set PlayerCameraData.x[id] = GetStoredReal(cache, missionKey, "x")
set PlayerCameraData.y[id] = GetStoredReal(cache, missionKey, "y")
set PlayerCameraData.aoa[id] = GetStoredReal(cache, missionKey, "aoa")
set PlayerCameraData.rotation[id] = GetStoredReal(cache, missionKey, "rot")
set i = i - 1
endloop
call TriggerEvaluate(updateEvent)
call TriggerExecute(syncTrigger)
endfunction
function EnableCameraBroadcast takes player p, boolean flag returns nothing
local integer id = GetPlayerId(p)
if flag then
if playerIndex[id] != 0 then
return
endif
set stackIndex = stackIndex + 1
set playerStack[stackIndex] = p
set playerIndex[id] = stackIndex
if stackIndex == 1 then
call TriggerExecute(syncTrigger)
endif
elseif playerIndex[id] != 0 then
set playerStack[playerIndex[id]] = playerStack[stackIndex]
set stackIndex = stackIndex - 1
set playerIndex[id] = 0
endif
endfunction
private module Init
private static method onInit takes nothing returns nothing
set cache = InitGameCache(CACHE_NAME)
call TriggerAddAction(syncTrigger, function SyncCameraData)
endmethod
endmodule
struct PlayerCameraData extends array
static real array x
static real array y
static real array aoa
static real array rotation
implement Init
endstruct
function GetCameraTargetLocation takes player p returns location
local integer id = GetPlayerId(p)
return Location(PlayerCameraData.x[i], PlayerCameraData.y[i])
endfunction
endlibrary
Demo "ObserverMode":
JASS:
library ObserverMode initializer Init requires PlayerCameraData
globals
integer currentlyViewing = -1
constant real PAN_DURATION = 0.25
endglobals
private function Esc takes nothing returns nothing
local player p = GetTriggerPlayer()
if p == Player(0) then
if currentlyViewing == 1 then
call BJDebugMsg("|cffffcc00Player 1|r is no longer viewing |cffffcc00Player 2's|r camera.")
set currentlyViewing = -1
else
call BJDebugMsg("|cffffcc00Player 1|r is now viewing |cffffcc00Player 2's|r camera.")
set currentlyViewing = 1
endif
elseif p == Player(1) then
if currentlyViewing == 0 then
call BJDebugMsg("|cffffcc00Player 2|r is no longer viewing |cffffcc00Player 1's|r camera.")
set currentlyViewing = -1
else
call BJDebugMsg("|cffffcc00Player 2|r is now viewing |cffffcc00Player 1's|r camera.")
set currentlyViewing = 0
endif
endif
endfunction
private function OnCameraDataUpdate takes nothing returns boolean
local integer viewer = 0
if currentlyViewing == -1 then
return false
endif
if currentlyViewing == 0 then
set viewer = 1
endif
if GetLocalPlayer() == Player(viewer) then
call PanCameraToTimed(PlayerCameraData.x[currentlyViewing], PlayerCameraData.y[currentlyViewing], PAN_DURATION)
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, PlayerCameraData.aoa[currentlyViewing], PAN_DURATION)
call SetCameraField(CAMERA_FIELD_ROTATION, PlayerCameraData.rotation[currentlyViewing], PAN_DURATION)
endif
return false
endfunction
private function GameStart takes nothing returns nothing
call BJDebugMsg("Press |cffffcc00ESC|r to view the other player's camera.")
call EnableCameraBroadcast(Player(0), true)
call EnableCameraBroadcast(Player(1), true)
call RegisterCameraDataUpdate(Condition(function OnCameraDataUpdate))
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerRegisterPlayerEvent(t, Player(1), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddAction(t, function Esc)
call TimerStart(CreateTimer(), 0, false, function GameStart)
endfunction
endlibrary
I'll clean the code and add comments a little later. And maybe I'll make ObserverMode into an actual system. Right now it is just a demo. To test it, download the map, put it in your maps folder, open JNGP 2.0.X. to start the multiplayer emulation, load it on LAN and then press ESC on either computer side. The rest should be self explanatory. As one client moves the camera, the other client's camera should move as well (with a slight delay, depending on your connection speeds). You can also test on two separate computers like I did.
Right now, you first have to call
EnableCameraBroadcast
for the players you want to get global camera data for. This will let the system know that the player's data should be sync'd. The data is sync'd in batches -> it stores the current camera data in gamecache -> syncs it -> waits for syncing -> loads data and stores it in the struct -> repeat. Theoretically, maybe I could capture more camera movements by doing several batch syncs (e.g. sync every 0.1 seconds instead of after each sync is finished). It would require tweaking so that the gamecache data wouldn't get overwritten, but it could work. Although, it is not bad in its current state.Credits to Xarian for some info on sync'ing. Enjoy, and hopefully someone finds a use for this. It could be really cool for altered melee/AoS maps. Basically any map with free cameras. Do not use this for RPG/fps/custom cameras, that is just nonsense--you already have control over where the camera is.
Attachments
Last edited: