- Joined
- Nov 7, 2014
- Messages
- 571
Ghostly is a library that enables the player camera to "free float" inside the map.
Because you can't detect mouse movement in [ordinary] War3, the keys that can be detect
are used, i.e: the arrow keys (left, right, up, down) and the Escape (Esc) key.
The Esc key toggles between 2 modes. The 1st mode is "looking-around" and
the 2nd is "moving-around". There's an automove flag that propels the player's
camera forward so that he would only have to "look around" and not have to switch
between the two modes.
How is it usefull? Well... I think it's quite fun and could be used
by terrain makers to have an in-game look (i.e not just looking inside the world-editor)
of their well... terrain from different angles.
So in order to use Ghostly you have to import it into your map:
but becuase it uses 2 more libraries you have to import them too... oh well:
OnKeyEvent:
edit:
Added a much improved version (ghostly-mem-hack-demo.w3x) that uses mem/hack. Currently does not work in the latest released patch (1.27b).
PS: All your camera angles are belong to us!
Because you can't detect mouse movement in [ordinary] War3, the keys that can be detect
are used, i.e: the arrow keys (left, right, up, down) and the Escape (Esc) key.
The Esc key toggles between 2 modes. The 1st mode is "looking-around" and
the 2nd is "moving-around". There's an automove flag that propels the player's
camera forward so that he would only have to "look around" and not have to switch
between the two modes.
How is it usefull? Well... I think it's quite fun and could be used
by terrain makers to have an in-game look (i.e not just looking inside the world-editor)
of their well... terrain from different angles.
So in order to use Ghostly you have to import it into your map:
JASS:
library Ghostly uses OnKeyEvent, UnitZ
static if false then
## Ghostly
#### How to use
First get a Ghost from a player:
player p = Player(0)
Ghost g = Ghost(GetPlayerId(p))
Then get/set the Ghost properties:
g.p: Is the player who''s Ghost g is.
g.dummy: Is the underlying unit that is used to anchor the camera.
This is required because by default the arrow keys move the
camera unless SetCameraTargetController is called (which
takes a unit).
NOTE: Make sure to copy the dummy unit (DUMMY_ID = 'e000')
from the map.
It is actually the one from the Caster System so it is
likely that you already have it.
g.cx, g.cy, g.cz: specify the position of the Ghost on the map
g.camera_speed: how fast the Ghost moves in units per second
g.rot: short for rotation, aka yaw angle
g.rot_speed: how fast the Ghost is rotating (left-right) in units per second
g.aoa: short for angle of attack, aka pitch angle
g.aoa_speed: how fast the Ghost is rotating (up-down) in units per second
g.farz: how far the war3 engine "sees"/renders from g.cx, g.cy, g.cz
g.target_distance: the distance from the actual position of the camera
to the anchored g.dummy
g.field_of_view: the field of view of the camera
g.is_moving: when set the Ghost is in "move-around" mode
g.enabled: if not set the player is using the "default" camera
g.is_automoving: when set the player is propelled forward automatically
endif
globals
private constant real DT = 1.0 / 64
private constant integer DUMMY_ID = 'e000'
private constant boolean DEFAULT_IS_MOVING = false // set to false for rotating
private constant boolean ENABLE_ON_STARTUP = true // else Ghostly(GetPlayerId(p)).enabled = true
private constant boolean DEFAULT_IS_AUTOMOVING = false
private constant boolean SET_CAMERA_BOUNDS_TO_WORLD_BOUNDS = false
private constant boolean CAN_MOVE_THROUGH_TERRAIN = false
// starting position of the camera
private constant real DEFAULT_CX = 0
private constant real DEFAULT_CY = 0
private constant real DEFAULT_CZ = 500
private constant real DEFAULT_CAMERA_SPEED = 1000
private constant real DEFAULT_ROT = 90
private constant real DEFAULT_ROT_SPEED = 60.0
private constant real DEFAULT_AOA = 304
private constant real DEFAULT_AOA_SPEED = 60.0
private constant real DEFAULT_FARZ = 5000
private constant real DEFAULT_TARGET_DISTANCE = 0
private constant real DEFAULT_FIELD_OF_VIEW = 120
// The camera get's stuck at this height...
private constant real CAMERA_MAX_Z = 4332 // ~4332
endglobals
// Without this function this library would suck!!
// [url]http://www.wc3c.net/showthread.php?p=1029058[/url]
// <3 Tc =)
private function SetCameraZ takes real z returns nothing
set z = GetCameraField(CAMERA_FIELD_ZOFFSET) + z - GetCameraTargetPositionZ()
call SetCameraField(CAMERA_FIELD_ZOFFSET, z, -0.01)
call SetCameraField(CAMERA_FIELD_ZOFFSET, z, 0.01)
endfunction
private function cos takes real deg returns real
return Cos(deg * bj_DEGTORAD)
endfunction
private function sin takes real deg returns real
return Sin(deg * bj_DEGTORAD)
endfunction
globals
private timer ticker = CreateTimer()
private real bound_left
private real bound_right
private real bound_top
private real bound_bottom
// private real margin_left
// private real margin_right
// private real margin_top
// private real margin_bottom
private real camera_left
private real camera_right
private real camera_top
private real camera_bottom
private real camera_up
endglobals
struct Ghost extends array
player p
unit dummy
real cx
real cy
real cz
real camera_speed
real rot
real rot_speed
real aoa
real aoa_speed
real farz
real target_distance
real field_of_view
boolean is_moving
boolean enabled
boolean is_automoving
private static method create takes Ghost this, player p returns Ghost
set this.p = p
set dummy = CreateUnit(p, DUMMY_ID, 0, 0, 0)
// set dummy = CreateUnit(p, 'hfoo', 0, 0, 0)
call UnitAddAbility(dummy, 'Amrf')
call UnitRemoveAbility(dummy, 'Amrf')
call UnitAddAbility(dummy, 'Aloc')
call SetUnitPathing(dummy, false)
set cx = DEFAULT_CX
set cy = DEFAULT_CY
set cz = DEFAULT_CZ
set camera_speed = DEFAULT_CAMERA_SPEED
set rot = DEFAULT_ROT
set rot_speed = DEFAULT_ROT_SPEED
set aoa = DEFAULT_AOA
set aoa_speed = DEFAULT_AOA_SPEED
set farz = DEFAULT_FARZ
set target_distance = DEFAULT_TARGET_DISTANCE
set field_of_view = DEFAULT_FIELD_OF_VIEW
set is_moving = DEFAULT_IS_MOVING
set enabled = ENABLE_ON_STARTUP
set is_automoving = DEFAULT_IS_AUTOMOVING
return this
endmethod
method update_camera takes nothing returns nothing
// bounds checks x, y
if cx < camera_left then
set cx = camera_left
elseif cx > camera_right then
set cx = camera_right
endif
if cy > camera_top then
set cy = camera_top
elseif cy < camera_bottom then
set cy = camera_bottom
endif
call SetUnitX(dummy, cx)
call SetUnitY(dummy, cy)
// call SetUnitPosition(dummy, cx, cy)
// call SetUnitFacing(dummy, rot)
// bounds check z
static if CAN_MOVE_THROUGH_TERRAIN then
if cz > camera_up then
set cz = camera_up
endif
else
if cz < GetTerrainZ(cx, cy) + 128 then
set cz = GetTerrainZ(cx, cy) + 128
elseif cz > camera_up then
set cz = camera_up
endif
endif
call SetUnitZ(dummy, cz)
if is_moving and is_automoving then
// Follow that direction vector!
set cx = cx + cos(rot) * cos(aoa) * camera_speed * DT
set cy = cy + sin(rot) * cos(aoa) * camera_speed * DT
set cz = cz + sin(aoa) * camera_speed * DT
endif
if GetLocalPlayer() == p then
// prevent Home/End and PageUp/Down fiddling with the camera
call SetCameraField(CAMERA_FIELD_FARZ, farz, 0)
call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, target_distance, 0)
call SetCameraField(CAMERA_FIELD_FIELD_OF_VIEW, field_of_view, 0)
call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)
// The camera seems to shake more if we don't use
// SetCameraTargetController(...)
// call SetCameraPosition(cx, cy)
// so we use it, instead
call SetCameraTargetController(dummy, 0, 0, false)
call SetCameraZ(cz)
endif
endmethod
method left takes nothing returns nothing
if is_moving and not is_automoving then
set cx = cx + cos(rot + 90) * camera_speed * DT
set cy = cy + sin(rot + 90) * camera_speed * DT
else
set rot = rot + rot_speed * DT
if GetLocalPlayer() == p then
call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
endif
endif
endmethod
method right takes nothing returns nothing
if is_moving and not is_automoving then
set cx = cx + cos(rot - 90) * camera_speed * DT
set cy = cy + sin(rot - 90) * camera_speed * DT
else
set rot = rot - rot_speed * DT
if GetLocalPlayer() == p then
call SetCameraField(CAMERA_FIELD_ROTATION, rot, 0)
endif
endif
endmethod
method up takes nothing returns nothing
if is_moving and not is_automoving then
set cx = cx + cos(rot) * cos(aoa) * camera_speed * DT
set cy = cy + sin(rot) * cos(aoa) * camera_speed * DT
set cz = cz + sin(aoa) * camera_speed * DT
else
set aoa = aoa + aoa_speed * DT
if GetLocalPlayer() == p then
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)
// call AdjustCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa_speed * DT, 0.01)
endif
endif
endmethod
method down takes nothing returns nothing
if is_moving and not is_automoving then
set cx = cx - cos(rot) * cos(aoa) * camera_speed * DT
set cy = cy - sin(rot) * cos(aoa) * camera_speed * DT
set cz = cz - sin(aoa) * camera_speed * DT
else
set aoa = aoa - aoa_speed * DT
if GetLocalPlayer() == p then
call SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, aoa, 0)
// call AdjustCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, -aoa_speed * DT, 0.01)
endif
endif
endmethod
private static method on_key takes player p, integer key, boolean is_down returns nothing
local Ghost g = Ghost(GetPlayerId(p))
if not g.enabled then
return
endif
if key == KEY_LEFT and is_down then
call g.left()
// elseif key == KEY_LEFT then // and not is_down
elseif key == KEY_RIGHT and is_down then
call g.right()
// elseif key == KEY_RIGHT then // and not is_down
elseif key == KEY_UP and is_down then
call g.up()
// elseif key == KEY_UP then // and not is_down
elseif key == KEY_DOWN and is_down then
call g.down()
// elseif key == KEY_DOWN then // and not is_down
elseif key == KEY_ESC then
set g.is_moving = not g.is_moving
call ClearSelectionForPlayer(g.p)
endif
endmethod
implement onkeyevent
private static method tick takes nothing returns nothing
local integer i
local player p
local integer pid
local Ghost g
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
set p = Player(i)
set pid = i
set g = Ghost(pid)
if g.enabled then
if is_player_key_down[pid][KEY_LEFT] then
call g.left()
endif
if is_player_key_down[pid][KEY_RIGHT] then
call g.right()
endif
if is_player_key_down[pid][KEY_UP] then
call g.up()
endif
if is_player_key_down[pid][KEY_DOWN] then
call g.down()
endif
call g.update_camera()
endif
set i = i + 1
endloop
endmethod
private static method onInit takes nothing returns nothing
local integer i
static if SET_CAMERA_BOUNDS_TO_WORLD_BOUNDS then
call SetCameraBoundsToRect(GetWorldBounds())
endif
set bound_left = GetCameraBoundMinX()
set bound_right = GetCameraBoundMaxX()
set bound_top = GetCameraBoundMaxY()
set bound_bottom = GetCameraBoundMinY()
// call BJDebugMsg(R2S(bound_left))
// call BJDebugMsg(R2S(bound_right))
// call BJDebugMsg(R2S(bound_top))
// call BJDebugMsg(R2S(bound_bottom))
// set margin_left = GetCameraMargin(CAMERA_MARGIN_LEFT)
// set margin_right = GetCameraMargin(CAMERA_MARGIN_RIGHT)
// set margin_top = GetCameraMargin(CAMERA_MARGIN_TOP)
// set margin_bottom = GetCameraMargin(CAMERA_MARGIN_BOTTOM)
// // call BJDebugMsg(R2S(margin_left))
// // call BJDebugMsg(R2S(margin_right))
// // call BJDebugMsg(R2S(margin_top))
// // call BJDebugMsg(R2S(margin_bottom))
set camera_left = bound_left //- margin_left
set camera_right = bound_right //+ margin_right
set camera_top = bound_top //+ margin_top
set camera_bottom = bound_bottom //- margin_bottom
set camera_up = CAMERA_MAX_Z
// initialize all ghosts
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
// creating ghosts out of thin air :P
call Ghost.create(Ghost(i), Player(i))
set i = i + 1
endloop
call TimerStart(ticker, DT, true, function thistype.tick)
// Is this global/for all players or player specific?
// Is there a difference between 1 and 100?
// If there is, I can't notice it, lets
// be on the smoother side anyway.
call CameraSetSmoothingFactor(100)
// The default seems to be 0?
endmethod
endstruct
endlibrary
but becuase it uses 2 more libraries you have to import them too... oh well:
OnKeyEvent:
JASS:
library OnKeyEvent
// An example of how to use the onkeyevent module:
//
// struct MyStruct extends array
//
// private static method on_key takes player p, integer key, boolean is_down returns nothing
// call BJDebugMsg(GetPlayerName(p))
//
// if key == KEY_LEFT and is_down then
// call BJDebugMsg("key left down")
//
// elseif key == KEY_LEFT then // and not is_down
// call BJDebugMsg("key left up")
//
//
// elseif key == KEY_RIGHT and is_down then
// call BJDebugMsg("key right down")
//
// elseif key == KEY_RIGHT then // and not is_down
// call BJDebugMsg("key right up")
//
//
// elseif key == KEY_UP and is_down then
// call BJDebugMsg("key up down")
//
// elseif key == KEY_UP then // and not is_down
// call BJDebugMsg("key up up")
//
//
// elseif key == KEY_DOWN and is_down then
// call BJDebugMsg("key down down")
//
// elseif key == KEY_DOWN then // and not is_down
// call BJDebugMsg("key down up")
//
//
// elseif key == KEY_ESC then
// call BJDebugMsg("esc key pressed")
//
// endif
//
// endmethod
// implement onkeyevent
//
// endstruct
globals
/* export */ constant integer KEY_LEFT = 0
/* export */ constant integer KEY_RIGHT = 1
/* export */ constant integer KEY_DOWN = 2
/* export */ constant integer KEY_UP = 3
private constant integer MAX_PLAYERS = 12 // bj_MAX_PLAYERS
// meant to be used inside a timer handler function / loop
/* export */ boolean array is_player_key_down[MAX_PLAYERS][4]
constant integer KEY_ESC = 4
endglobals
private module OnKeyEvent
static trigger arrow_key_trigger = CreateTrigger()
static trigger esc_key_trigger = CreateTrigger()
static player p
static integer key
static boolean is_down
private static method on_arrow_key takes nothing returns boolean
// EVENT_PLAYER_ARROW_LEFT_DOWN = ConvertPlayerEvent(261) // 0
// EVENT_PLAYER_ARROW_LEFT_UP = ConvertPlayerEvent(262) // 1
// EVENT_PLAYER_ARROW_RIGHT_DOWN = ConvertPlayerEvent(263) // 2
// EVENT_PLAYER_ARROW_RIGHT_UP = ConvertPlayerEvent(264) // 3
// EVENT_PLAYER_ARROW_DOWN_DOWN = ConvertPlayerEvent(265) // 4
// EVENT_PLAYER_ARROW_DOWN_UP = ConvertPlayerEvent(266) // 5
// EVENT_PLAYER_ARROW_UP_DOWN = ConvertPlayerEvent(267) // 6
// EVENT_PLAYER_ARROW_UP_UP = ConvertPlayerEvent(268) // 7
local integer eid = GetHandleId(GetTriggerEventId()) - 261
set p = GetTriggerPlayer()
set key = eid / 2
set is_down = eid == 2 * key
set is_player_key_down[GetPlayerId(p)][key] = is_down
call TriggerEvaluate(arrow_key_trigger)
return false
endmethod
private static method on_esc_key takes nothing returns boolean
set p = GetTriggerPlayer()
set key = KEY_ESC
set is_down = true
call TriggerEvaluate(esc_key_trigger)
return false
endmethod
private static method onInit takes nothing returns nothing
local player p
local trigger t
local integer i
set t = CreateTrigger()
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
set p = Player(i)
if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_LEFT_DOWN)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_LEFT_UP)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_RIGHT_DOWN)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_RIGHT_UP)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_DOWN_DOWN)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_DOWN_UP)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_UP_DOWN)
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_ARROW_UP_UP)
endif
set i = i + 1
endloop
call TriggerAddCondition(t, Condition(function thistype.on_arrow_key))
set t = CreateTrigger()
set i = 0
loop
exitwhen i >= bj_MAX_PLAYERS
set p = Player(i)
if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_END_CINEMATIC)
endif
set i = i + 1
endloop
call TriggerAddCondition(t, Condition(function thistype.on_esc_key))
set t = null
endmethod
endmodule
private struct OnKeyEventDummy extends array
implement OnKeyEvent
endstruct
module onkeyevent
private static method call_on_key_event_handler takes nothing returns boolean
call thistype.on_key(OnKeyEventDummy.p, OnKeyEventDummy.key, OnKeyEventDummy.is_down)
return false
endmethod
private static method onInit takes nothing returns nothing
static if thistype.on_key.exists then
call TriggerAddCondition(OnKeyEventDummy.arrow_key_trigger, Condition(function thistype.call_on_key_event_handler))
call TriggerAddCondition(OnKeyEventDummy.esc_key_trigger, Condition(function thistype.call_on_key_event_handler))
endif
endmethod
endmodule
endlibrary
JASS:
UnitZ:
[code=jass]
library UnitZ
globals
private location loc = Location(0., 0.)
endglobals
function GetTerrainZ takes real x, real y returns real
call MoveLocation(loc, x, y)
return GetLocationZ(loc)
endfunction
// function GetUnitZ takes unit u returns real
// return GetTerrainZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u)
// endfunction
// function SetUnitZ takes unit u, real z returns nothing
// call SetUnitFlyHeight(u, z - GetTerrainZ(GetUnitX(u), GetUnitY(u)), 0.0)
// endfunction
// inline the GetTerrainZ call
function GetUnitZ takes unit u returns real
call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction
function SetUnitZ takes unit u, real z returns nothing
call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
call SetUnitFlyHeight(u, z - GetLocationZ(loc), 0.0)
endfunction
endlibrary
edit:
Added a much improved version (ghostly-mem-hack-demo.w3x) that uses mem/hack. Currently does not work in the latest released patch (1.27b).
PS: All your camera angles are belong to us!
Attachments
Last edited: