• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[WIP] Multi-level platform system

Status
Not open for further replies.

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
This is quite similar to this system, they have similar purpose. And it's indeed not very advanced compared to that system (or perhaps not yet), but this one is a lot easier to use. Also it supports any shape of platform, just depends on the model you use.

This is all I can achieve for tonight, still gotta add bunch of complementary systems to make everything looks alright, like custom actor, advanced multi-level pathing, etc.

Handsome code:
JASS:
library PlatformSystem uses Table, UnitZ, TimerUtils
    
    // dest:
    // - must have free rotation
    // - must have no variation
    // - must have no pathing map
    // - must have no other dest around
    
    // ground:
    // - completely flat

    globals
        private constant real PRECISION = 16.0
        private constant real STEP_OFFSET = 75.0
        private constant real INTERVAL = 0.01
        private constant real OPEN_X = 0.0
        private constant real OPEN_Y = 0.0
    endglobals

    globals
        private constant player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
    endglobals
    
    struct PlatformObj
        
        readonly integer objId
        readonly integer destId
        readonly integer tWidth
        readonly integer tHeight
        
        readonly real width
        readonly real height
        readonly real sz
        readonly real rot
        readonly Table zMap
        
        private static unit dummy
        private static real locZ
        
        private method remap takes nothing returns nothing
        
            local destructable d = CreateDestructableZ(.destId, OPEN_X, OPEN_Y, 0, .rot, .sz, 0)
            local integer xOffset
            local integer yOffset
            local real x
            local real y = 0
            local real z //*
            local real sx = OPEN_X-.width/2
            local real sy = OPEN_Y-.height/2
            local real tx = sx+.width
            local real ty = sy+.height
            debug local string ms
            
            debug call BJDebugMsg(" ")
            loop
                exitwhen y > height
                set x = 0
                set yOffset = R2I(y/PRECISION)
                debug set ms = ""
                loop
                    exitwhen x > width
                    set xOffset = R2I(x/PRECISION)
                    set z = GetTerrainZ(sx+x, sy+y)-locZ
                    set .zMap.real[tWidth*yOffset+xOffset] = z
                    debug set ms = ms + R2SW(z, 5, 1) + " "
                    set x = x + PRECISION
                endloop
                debug call BJDebugMsg(ms)
                set y = y + PRECISION
            endloop
            call RemoveDestructable(d)
            set d = null
            
        endmethod
        
        static method create takes integer objId, integer destId, real angle, real size, real width, real height returns thistype
            
            local thistype this = allocate()
            
            set .objId = objId
            set .destId = destId
            set .sz = size
            set .rot = angle
            set .zMap = Table.create()
            set .width = width
            set .height = height
            set .tWidth = R2I(width/PRECISION)
            set .tHeight = R2I(height/PRECISION)
            call this.remap()
            
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            set locZ = GetTerrainZ(OPEN_X, OPEN_Y)
        endmethod
        
    endstruct
    
    struct Platform
        
        private unit obj
        private real mx
        private real my
        private real x
        private real y
        private real z
        private real z2
        private rect area
        private integer dex
        private PlatformObj plat
        
        private static timer tick = CreateTimer()
        private static integer count = -1
        private static Table rectDex
        private static Table unitState
        private static rect array rects
        private static group tempGroup = CreateGroup()
        private static group tempGroup2 = CreateGroup()
        
        private static method onPeriodic takes nothing returns nothing
            
            local timer t = GetExpiredTimer()
            local thistype this
            local integer xOffset
            local integer yOffset
            local integer hand
            local integer i
            local unit fog
            local real x
            local real y
            local real z
            local real tz
            
            set i = 0
            loop
                exitwhen i > count
                set this = rectDex.integer[GetHandleId(rects[i])]
                call GroupEnumUnitsInRect(tempGroup, .area, null)
                loop
                    set fog = FirstOfGroup(tempGroup)
                    exitwhen fog == null
                    call GroupRemoveUnit(tempGroup, fog)
                    
                    set hand = GetHandleId(fog)
                    if not unitState.boolean[hand] then
                        call GroupAddUnit(tempGroup2, fog)
                        
                        set x = GetUnitX(fog)-.mx
                        set y = GetUnitY(fog)-.my
                        set z = GetUnitZ(fog)
                        
                        set xOffset = R2I(x/PRECISION)
                        set yOffset = R2I(y/PRECISION)
                        
                        set tz = .plat.zMap.real[.plat.tWidth*yOffset+xOffset]
                        if tz > 0 then
                            set unitState.boolean[hand] = true
                            if z > .z+tz-STEP_OFFSET then
                                call SetUnitZ(fog, .z+tz+GetUnitDefaultFlyHeight(fog))//+(z-(.z+tz)))
                            endif
                        endif
                    endif
                endloop
                set i = i + 1
            endloop
            
            loop
                set fog = FirstOfGroup(tempGroup2)
                exitwhen fog == null
                call GroupRemoveUnit(tempGroup2, fog)
                set hand = GetHandleId(fog)
                if not unitState.boolean[hand] then
                    call SetUnitFlyHeight(fog, GetUnitDefaultFlyHeight(fog), 0)
                else
                    set unitState.boolean[hand] = false
                endif
            endloop
            set t = null
            
        endmethod
        
        static method create takes PlatformObj obj, real x, real y, real z returns thistype
            
            local thistype this = allocate()
            local thistype dex
            local integer i
            local integer j
            
            set .plat = obj
            set .x = x
            set .y = y
            set .z = z
            set .z2 = GetTerrainZ(.x, .y)
            set .mx = .x-.plat.width/2
            set .my = .y-.plat.height/2
            set .area = Rect(x-obj.width/2, y-obj.height/2, x+obj.width/2, y+obj.height/2)
            set .obj = CreateUnit(PASSIVE, obj.objId, x, y, .plat.rot)
            static if not LIBRARY_AutoFly then
                if UnitAddAbility(.obj, 'Amrf') and UnitRemoveAbility(.obj, 'Amrf') then
                endif
            endif
            call SetUnitZ(.obj, z)
            call SetUnitScale(.obj, .plat.sz, 1, 1)
            
            if UnitAddAbility(.obj, 'Aloc') and UnitRemoveAbility(.obj, 'Aloc') then
            endif
            call ShowUnit(.obj, false)
            call ShowUnit(.obj, true)
            
            set i = 0
            loop
                exitwhen i > count
                set dex = rectDex.integer[GetHandleId(rects[i])]
                if .z > dex.z then
                    exitwhen true
                endif
                set i = i + 1
            endloop
            set count = count + 1
            set j = count
            loop
                exitwhen j == i
                set rects[j] = rects[j-1]
                set j = j - 1
            endloop
            set .dex = i
            set rects[i] = .area
            set rectDex.integer[GetHandleId(.area)] = this
            
            if count == 0 then
                call TimerStart(tick, INTERVAL, true, function thistype.onPeriodic)
            endif
            
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            set rectDex = Table.create() 
            set unitState = Table.create()
        endmethod
        
    endstruct
    
endlibrary
Issues:
- Doesn't work well with flying unit (yet)
- Still looking

To do:
- Allow to modify platform object's size
- Allow to rotate platform
- Remove unneeded scripts
- Enable/disable platform feature
- Add additional functioons (get unit platform level, check is unit on platform, etc)
- Add platform events (enters, falls from, etc.)
- Add per rect platform generation feature
- Complete complementary systems

Complementary systems:
- A 3D pathing system
- A custom actor system (to simulate falling physics, etc.)
- A custom arrow key movement and camera system (which is adapted to this platform system)

Note:
I use the witcher's camera and movement system for the demonstration. You can use arrow key to move around. Also try double-tapping the arrow key as well ;)

Feedback pls ;)

EDIT:
Lil update, added wandering units.
 

Attachments

  • Platform System.w3x
    74.1 KB · Views: 74
  • Platform System w NPC.w3x
    77.7 KB · Views: 124
Last edited:
This is very inconvenient to use.

Instead of having to manually create objects and platforms via script, this should be able to read out destructables placed artistically directly in the editor.


After all, you can get the platform Z and the ground Z individually by just enumerating all walkable destructables in the checked area, hiding them and then performing a LocationZ check with and without the destructables hidden.
Your Z map should not be processed including the platforms, but excluding the platforms, as this is independent of the destructable render state.

Your approach seems very counter-intuitive to me.
Instead of adjusting the FlyHeight of a unit to the height of the platform, you should actually adjust it to the ground and make the platforms default walkable. This would also allow it to use the mouse instead of forced arrow-key movement requirements.



So, in short, this is how I'd do it:

- Have a simple register function for destructable IDs
- All platforms must be set to "walkable" in the OE
- Create the "ground z" map at map init by simply moving a location around with all destructables hidden
- Adjust the fly height of units periodically according to the difference of current unit Z to the stored ground Z
- Basicly, if units walks "under" a bridge, you apply negative fly height to them instead of applying positive fly height when they are on top of the bridge; this is not only easier to code, but looks better aswell (as any mouse clicks will now have the correct Z-depth logic).
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I don't really understand what you mean. But perhaps this will answer it.
This system is made to realize the platform system mentioned by Nestharus here. Where the system allows units to move on different level of platform (piled platforms). This system basically works like destructable, but it allows units to walk underneath the object. So using terrain is obviously not possible here.

Instead of having to manually create objects and platforms via script, this should be able to read out destructables placed artistically directly in the editor.
Added to upcoming feature. Thanks. :)

I'm still brainstorming a lot things tho, perhaps you can help me with some opinions. ;)
I can replace all unit object data and use a single dummy unit instead. It's much more convenient of course that user don't need to create another unit object for every platform object. But it's at cost that the user won't be able to modify both of model's pitch and roll angle. Does that sounds okay to you?

- Adjust the fly height of units periodically according to the difference of current unit Z to the stored ground Z
This will result in higher unit's fly height, than it's normal (default) fly height. Because terrain's height z is automatically adjusted upon walkable destructables. You shouldn't use any fly height adjustment at all if you are using walkable destructable.

- Basicly, if units walks "under" a bridge, you apply negative fly height to them instead of applying positive fly height when they are on top of the bridge; this is not only easier to code, but looks better aswell (as any mouse clicks will now have the correct Z-depth logic).
Units can't have negative fly height, and can't be lower than the ground or the top-most platform (the terrain z). Tested.
Even if negative fly height is possible, the mouse click issue will still occur, just think about it.
 
Last edited:
I don't really understand what you mean. But perhaps this will answer it.
This system is made to realize the platform system mentioned by Nestharus here. Where the system allows units to move on different level of platform (piled platforms). This system basically works like destructable, but it allows units to walk underneath the object. So using terrain is obviously not possible here.
I understand what you are trying to achieve. But I don't think that having more than two "layers" (ground layer and bridge layer) makes a lot of sense mechanics-wise, since the engine of WC3 just doesn't support it anyway (no mouse-cursor collision on right-clicks, unit shadows always drawn on the ground, bad clipping behaviour, attack range checks ignore z-difference, etc.).

For most users, a simple two layer system would be sufficient and come with a huge number of advantages:
- easier internal logic as the ground Z can be reliably calculated with just moving a location around and hiding walkable destructables; no dummies, no extra models needed, independent of render states.
- binary collision is possible by using air-only pathing blockers (ground layer: use ground pathing blockers; bridge layer: use air pathing blockers and morph the unit between ground and air pathing via crowform)
- correct right-click collision with mouse movement (a walkable destructable will correctly display the click animation on top of the model, instead of drawing it on the ground)


Your system uses a flawed approach in that you simulates a walkable platform by adjusting the units fly height positively above ground. Instead, you should adjust it negatively under an existing platform, to follow the z-depth logic of rendering.

I can replace all unit object data and use a single dummy unit instead. It's much more convenient of course that user don't need to create another unit object for every platform object. But it's at cost that the user won't be able to modify both of model's pitch and roll angle. Does that sounds okay to you?
Why do you need a dummy unit? Just move a location on top of your walkable destructable and read out the Location Z and build a grid table from that. Then hide the destructables and read the location Z again to get the ground height.


I attached a drawing on the logic I would use for a two-layer pathing system. It has virtually no flaws and is 100% mouse-movement compatible, except for two things:
- warcraft attack mechanics will allow unit A to fire on unit B
- units on the top layer will have no collision with each other, as they are flying
 

Attachments

  • Two-Layer-System.jpg
    Two-Layer-System.jpg
    53.2 KB · Views: 114

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I hope you notice my edits above. Your approach is unfortunately impossible. First, lower fly height than terrain's z is impossible. Secondly, for a multi-level platform system, the mouse clicking issue is normally unfixable, in any way no matter what, if you think about it carefully and if you are experienced enough with terrain characteristic. For example, you can't click underneath the terrain surface so it would just result in another mouse clicking issue. And you can only click on terrain's surface.

And when you call my approach is flawed, you need to mention what flaw does it have? As far as I believe, my approach is pure flawless. Naive, yes. But flawed, I don't think so.
that you simulates a walkable platform by adjusting the units fly height positively above ground
That's not a flaw. That's an approach, which apparently, more possible and not worse than yours.

Anyway, I'm also brainstorming how to work around the mouse clicking issue. But the only one solution I think may work, is only possible to be realized if we are able to get mouse's current UV coordinate, which is only possible using Sharpcraft atm.

And please, make a new post instead of editing the old one. ;)
 
Last edited:
I hope you notice my edits above. Your approach is unfortunately impossible. First, lower fly height than terrain's z is impossible.
I am pretty sure that this works. Have you properly adjusted the "minimum fly height" of the unit in the OE before adjusting the fly height?

Secondly, for a multi-level platform system, the mouse clicking issue is normally unfixable, in any way no matter what, if you think about it carefully and if you are experienced enough with terrain characteristic. For example, you can't click underneath the terrain surface
That is correct, but also desirable. If I click on top of the bridge, I want the curser to show it's marker on top of the bridge. If I want to click "under" the bridge, of course I have to adjust the camera so that I can actually click there.
... unless you mean that if I adjust the camera (for example to a third person view) and click "under" the bridge, the arrows are actually drawn on top of the bridge aswell. I don't know if that happens, tbh, haven't tested that yet, but I always assumed it draws the arrows based on UV coordinates, not XY coordinates on clicks.

so it would just result in another mouse clicking issue. And you can only click on terrain's surface.
It still makes more sense to have the arrows drawn on the top layer by default than the reverse, since the top layer usually obstructs the view of the ground layer anyway.

And when you call my approach is flawed, you need to mention what flaw does it have? As far as I believe, my approach is pure flawless. Naive, yes. But flawed, I don't think so.
It's terribly inconvenient to use as you have to manually enter the bounds of the platforms. Also, as I said, collision is a problem (both mouse and unit collision).

That's not a flaw. That's an approach, which apparently, more possible and not worse than yours.
If negative fly height is not possible, then yes, it is more possible than mine. But I never heard of fly height being restricted to positive values only before.
Have you remembered to adjust the "overfly height" value of the destructables?

Anyway, I'm also brainstorming how to work around the mouse clicking issue. But the only one solution I think may work, is only possible to be realized if we are able to get mouse's current UV coordinate, which is only possible using Sharpcraft atm.
Manually spawning the click model via trackables? Could be painful in multiplayer, but might be a solution in singleplayer.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I am pretty sure that this works. Have you properly adjusted the "minimum fly height" of the unit in the OE before adjusting the fly height?
No, it doesn't. I changed the min fly height to -900 and still didn't work.

... unless you mean that if I adjust the camera (for example to a third person view) and click "under" the bridge, the arrows are actually drawn on top of the bridge aswell.
Sadly, that's what will happen in that case.

It's terribly inconvenient to use as you have to manually enter the bounds of the platforms. Also, as I said, collision is a problem (both mouse and unit collision).
Indeed. But you gave me a great idea in the previous post, which will make things much more convenient. And I will try to get rid of that "manually enter the platform area". I think it's possible after I write a library to have angled rect.

If negative fly height is not possible, then yes, it is more possible than mine. But I never heard of fly height being restricted to positive values only before.
Have you remembered to adjust the "overfly height" value of the destructables?
I did a pretty decent researches around modifying fly height but still failed to find a way to have negative fly height. But, I haven't tested that fly over trick, it would be awesome if works. I gonna test this.

Manually spawning the click model via trackables? Could be painful in multiplayer, but might be a solution in singleplayer.
Using Sharpcraft you don't need trackable. There is custom native to give you the exact XY and UV coordinate of the cursor. Along with other awesome custom natives. ;)
 
Using Sharpcraft you don't need trackable. There is custom native to give you the exact XY and UV coordinate of the cursor. Along with other awesome custom natives. ;)
There's a huge downside with sharpcraft, though:
It requires sharpcraft. Trackables work for everyone.

I think DGUI has a pretty nice code for detecting UV coordinates via billboarded trackables and camera vector projection. Might be a nice reference here.
 
Status
Not open for further replies.
Top