• 🏆 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!

Panel System

Status
Not open for further replies.

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Intro:
Hello, atm I'm working on a custom UI system. If you are more familiar with DGUI, then yes, the basic idea is probably the same: displaying an object right at the camera source (eye) point so it looks like an additional user interface. I haven't tried DGUI yet so I can't be sure what the differences are.

But my system is made to work just like panels in other programming languages (java, c#, vb, etc.): you can add multiple components/contents into your panel and allowing you to control their rendering order. It also works for any camera setting (target distance, AoA, rotation, etc.). Your UI will always appear on your screen at given coordinates.

Maybe you have already knew Inventory & Equipment UI System v1.2.0 by TriggerHappy. The goal of my system is to generalize the functionality of his system's UI: allowing you to make your own customized interface freely for any kind of purpose (custom inventory, custom dialog, custom menu, custom tooltips, etc.)

I picked it from my Garden's Tale project and have been improving it as decently as I could over the past few hours, it surely still need a lot of optimizations. And it isn't working in multiplayer yet, still need to sync the camera settings on refresh (maybe on next update). Next, I will also add "custom text" plug in allowing you to write texts on your panel freely.

Brainstorming:
brainstormed.jpg


Usage Example:
ss-jpg.260444

efg-jpg.261651


Rendering Demo:
asd.jpg



Code:
JASS:
library PanelSystem uses UnitZ, Queue
 
    /*  Panel System v1.0 by Quilnez
 
        The system allows you to display stuffs on your screen.
        Useful for building and displaying your very own custom UI.
 
        API
 
        struct Panel
 
            method operator opacity= takes integer a returns nothing
                - Modify panel's transparency (0-255)
            method operator opacity takes nothing returns integer
                - Get panel's transparency
            method operator scale= takes real r returns nothing
                - Modify panel's scale
            method operator scale takes nothing returns real
                - Get panel's scale
     
            method refresh takes nothing returns nothing
                - Update panel's position on current camera setting
            method clear takes nothing returns nothing
                - Remove all contents from the panel
            method show takes boolean b returns nothing
                - Show/hide the panel from screen
            method addContent takes integer id, integer texture, real xOffset, real yOffset, integer level returns PanelContent
                - Add new content to the panel
     
            static method create takes integer id, integer background, real xOffset, real yOffset, real width, real height returns Panel
                - Instantiate new panel
 
        struct PanelContent
 
            method operator level= takes integer i returns nothing
                - Move the content to a new level
            method operator scale= takes real r returns nothing
                - Modify the content's scale
            method operator scale takes nothing returns real
                - Get the content's scale
 
            method destroy takes nothing returns nothing
                - Destroy/remove the content
            method refresh takes nothing returns nothing
                - Update content's position on it's parent
            method move takes real xOffset, real yOffset returns nothing
                - Move the content
            method show takes boolean b returns nothing
                - Show/hide the content
            method setColor takes integer r, integer g, integer b, integer a returns nothing
                - Modify content's vertex coloring
     
            static method create takes Panel parent, integer id, integer texture, real xOffset, real yOffset, integer level returns thistype
                - Instantiate new content
     
    */
 
    // Configuration
    globals
        // General panel and contents size
        private constant real GENERAL_SIZE = 0.0625
        // Maximum number of rendering level
        private constant integer MAX_LEVEL = 50
    endglobals
 
    // System code
 
    globals
        private constant real LEVEL_GAP = 0.0002
        private constant real CAMERA_GAP = 100.+LEVEL_GAP*MAX_LEVEL
        private constant real HP  = bj_PI/2
        private constant real TAU = bj_PI*2
        private constant player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
    endglobals
 
    private function AngularDifference takes real a, real b returns real
 
        local real r = RAbsBJ(a-b)
 
        if r <= bj_PI then
            return r
        else
            return TAU-r
        endif
 
    endfunction

    private function ChangeTexture takes unit whichUnit, integer whichTexture returns nothing
 
        local real angle = GetUnitFacing(whichUnit)*bj_DEGTORAD
        local destructable d = CreateDestructable(whichTexture, GetUnitX(whichUnit)+100*Cos(angle), GetUnitY(whichUnit)+100*Sin(angle), 0, 0, 1)
 
        call IssueTargetOrderById(whichUnit, 852511, d)
        call RemoveDestructable(d)
        set d = null
 
    endfunction
 
    struct PanelContent
 
        private unit u
        readonly integer red
        readonly integer green
        readonly integer blue
        readonly integer alpha
 
        readonly real xOffset
        readonly real yOffset
        readonly real zOffset
        readonly real size
 
        readonly Panel parent
 
        method operator level= takes integer i returns nothing
            set .zOffset = LEVEL_GAP*i
        endmethod
 
        method operator scale= takes real r returns nothing
            set .size = r*GENERAL_SIZE
        endmethod
 
        method operator scale takes nothing returns real
            return .size/GENERAL_SIZE
        endmethod
 
        method refresh takes nothing returns nothing
 
            local real a
            local real d
            local real z
            local real cx
            local real cy
            local real cz
 
            // Apply x offset
            set cx = .parent.x+.xOffset*.parent.scale*Cos(.parent.hA-HP)
            set cy = .parent.y+.xOffset*.parent.scale*Sin(.parent.hA-HP)
            set cz = .parent.z
 
            // Apply y offset
            set a = AngularDifference(.parent.vA-HP, bj_PI+HP)
            set z = Cos(a)*.yOffset*.parent.scale
            set d = Sin(a)*.yOffset*.parent.scale
            set cx = cx+d*Cos(.parent.hA)
            set cy = cy+d*Sin(.parent.hA)
            set cz = cz+z
 
            // Apply z offset
            set d = .zOffset*Cos(.parent.vA)
            set z = .zOffset*Sin(.parent.vA)
 
            call SetUnitX(.u, cx-d*Cos(.parent.hA))
            call SetUnitY(.u, cy-d*Sin(.parent.hA))
            call SetUnitZ(.u, cz+z)
            call SetUnitScale(.u, .size*.parent.scale, 1, 1)
            call SetUnitVertexColor(.u, .red, .green, .blue, R2I(.alpha*.parent.opacity/255.))
 
        endmethod
 
        method move takes real xOffset, real yOffset returns nothing
            set .xOffset = xOffset
            set .yOffset = yOffset
        endmethod
 
        method show takes boolean b returns nothing

            if b then
                call SetUnitScale(.u, .size, 1, 1)
            else
                call SetUnitScale(.u, 0, 0, 0)
            endif
 
        endmethod
 
        method setColor takes integer r, integer g, integer b, integer a returns nothing
 
            set .red = r
            set .green = g
            set .blue = b
            set .alpha = a
 
        endmethod
 
        method destroy takes nothing returns nothing
 
            call RemoveUnit(.u)
            set .u = null
            call deallocate()
 
        endmethod
 
        static method create takes Panel parent, integer id, integer texture, real xOffset, real yOffset, integer level returns thistype
 
            local thistype this = allocate()
 
            set .parent = parent
            set .xOffset = xOffset
            set .yOffset = yOffset
 
            set .u = CreateUnit(PASSIVE, id, 0, 0, 0)
            static if not LIBRARY_AutoFly then
                if UnitAddAbility(.u, 'Amrf') and UnitRemoveAbility(.u, 'Amrf') then
                endif
            endif
 
            set .scale = 1
            set .level = level
            call .setColor(255, 255, 255, 255)
            call ChangeTexture(.u, texture)
            call PauseUnit(.u, true)
            call SetUnitScale(.u, .size, 1, 1)
            call .refresh()
 
            return this
        endmethod
 
    endstruct
 
    private struct ContentQueue extends array
        PanelContent content
        implement Queue
    endstruct
 
    struct Panel
 
        private unit box
        private integer op
 
        readonly real x
        readonly real y
        readonly real z
 
        readonly real vA
        readonly real hA
 
        readonly real xOffset
        readonly real yOffset
 
        readonly real width
        readonly real height
        readonly real size
 
        private ContentQueue c
 
        method operator opacity= takes integer a returns nothing
 
            local ContentQueue node = .c.first
 
            set .op = a
            loop
                exitwhen node == 0
                call node.content.refresh()
                set node = node.next
            endloop
 
        endmethod
 
        method operator opacity takes nothing returns integer
            return .op
        endmethod
 
        method operator scale= takes real r returns nothing
 
            local ContentQueue node = .c.first
 
            set .size = r*GENERAL_SIZE
            loop
                exitwhen node == 0
                call node.content.refresh()
                set node = node.next
            endloop
 
        endmethod
 
        method operator scale takes nothing returns real
            return .size/GENERAL_SIZE
        endmethod
 
        method refresh takes nothing returns nothing
 
            local real offset
            local real x = GetCameraTargetPositionX()
            local real y = GetCameraTargetPositionY()
            local real cz = GetCameraTargetPositionZ()
            local real x2
            local real y2
            local real z2
            local real a
            local real d
            local real d2
            local real z
            local ContentQueue c = .c.first
 
            set d2 = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)-CAMERA_GAP
            set .hA = GetCameraField(CAMERA_FIELD_ROTATION)
            set .vA = GetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK)
            set .vA = (.vA-AngularDifference(.vA, bj_PI+HP)*2)-bj_PI
            set d = d2*Cos(.vA)
            set z = d2*Sin(.vA)
            set .x = x-d*Cos(.hA)
            set .y = y-d*Sin(.hA)
 
            set a = AngularDifference(.vA-HP, bj_PI+HP)
            set d = .yOffset*Sin(a)
            set z = z+.yOffset*Cos(a)
            set .x = .x+d*Cos(a)
            set .y = .y+d*Sin(a)
            set .x = .x+.xOffset*Cos(.hA+HP)
            set .y = .y+.xOffset*Sin(.hA+HP)
            set .z = cz+z
 
            call SetUnitX(.box, .x)
            call SetUnitY(.box, .y)
            call SetUnitZ(.box, .z)
            call SetUnitScale(.box, .size, 1, 1)
            call SetUnitVertexColor(.box, 255, 255, 255, .opacity)
 
            loop
                exitwhen c == 0
                call c.content.refresh()
                set c = c.next
            endloop
 
        endmethod
 
        method clear takes nothing returns nothing
 
            local ContentQueue node = .c.first
 
            loop
                exitwhen node == 0
                call node.content.destroy()
                set node = node.next
            endloop
 
        endmethod
 
        method show takes boolean b returns nothing
 
            local ContentQueue node = .c.first
 
            loop
                exitwhen node == 0
                call node.content.show(b)
                set node = node.next
            endloop

            if b then
                call SetUnitScale(.box, .size, 1, 1)
            else
                call SetUnitScale(.box, 0, 0, 0)
            endif
 
        endmethod
 
        method addContent takes integer id, integer texture, real xOffset, real yOffset, integer level returns PanelContent
 
            local PanelContent content
 
            if level < 0 then
                set level = 0
            endif
            set content = PanelContent.create(this, id, texture, xOffset, yOffset, level)
            set .c.enqueue().content = content
 
            return content
        endmethod
 
        method destroy takes nothing returns nothing
 
            call clear()
            call RemoveUnit(.box)
            set .box = null
            call deallocate()
 
        endmethod
 
        static method create takes integer id, integer background, real xOffset, real yOffset, real width, real height returns thistype
 
            local thistype this = allocate()
 
            set .box = CreateUnit(PASSIVE, id, 0, 0, 0)
            static if not LIBRARY_AutoFly then
                if UnitAddAbility(.box, 'Amrf') and UnitRemoveAbility(.box, 'Amrf') then
                endif
            endif
            call ChangeTexture(.box, background)
            call PauseUnit(.box, true)
 
            set .scale = 1
            set .opacity = 255
            set .width = width
            set .height = height
            set .xOffset = xOffset
            set .yOffset = yOffset
            call .refresh()
 
            return this
        endmethod
 
    endstruct
 
endlibrary

For the newest file always check my latest update post. This file below as well as the code may be outdated by now.
 

Attachments

  • PanelSystem.w3x
    41.8 KB · Views: 66
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Update:
- Now with optimal precision
- Added some documentation
- Added dtor method
- Some minor improvements
- Reduced requirements

But well, honestly I still can't be certain at all how I should approach this system. Should I separate the panel system from the UI system? Or should I make a "parenting" system instead allowing to attach panels over panels (with this one the code size can be much smaller and neater, but at cost of some efficiency)?

Later this system will require a custom camera system to make it looks all smooth even with moving camera and to avoid synchronization because it's slow.
 
Last edited:
Looks good.

However I'm planning to simply re-write DGUI and use it in my library. It doesn't need much work.

Keep in mind SetUnitX and SetUnitY do not desync when used locally. This can be a massive performance increase if you only update the UI for the local player. The lag was unbearable on lower end PCs when all players had their UI's open for my inventory. Now it works fine with setting units position locally, but you need to be careful with it.
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Keep in mind SetUnitX and SetUnitY do not desync when used locally. This can be a massive performance increase if you only update the UI for the local player.
Hoo.. I think you are right, good sir! :D
I tested it with 3 players and didn't desync.
asd2.jpg

I really didn't know that before. lol
Thank you so much for telling me this!

The lag was unbearable on lower end PCs when all players had their UI's open for my inventory. Now it works fine with setting units position locally, but you need to be careful with it.
So one UI for all players then, it will be so much lighter indeed. But it can be a bit tricky in managing icon's visibility but I'm sure you can handle it. :)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Updated:
- The camera system is mostly done
- Improved demo map (added third person camera system)
- Fixed a critical error in panel's y-axis offset calculation

So far it runs quite smoothly, except there are sometimes some little camera flickings which I still don't know what's causing it.
efg.jpg


To do:
- Make multiplayer demo map
- Add simple inventory interface demo
- Finish the camera system
 

Attachments

  • PanelSystem.w3x
    327.8 KB · Views: 80
Last edited:
I've basically finished re-writing DGUI. Here's the implementation in case you're curious:

JASS:
scope Example initializer Init
  
    globals
        private UIPicture array Background
        private constant real BACKGROUND_X = -0.3
        private constant real BACKGROUND_Y = 0.3
        private constant real BACKGROUND_W = 140.
        private constant real BACKGROUND_H = 70.
        private constant real BACKGROUND_SIZE = 0.26
      
        private UIButton array Button
        private constant real BUTTON_W = .1
        private constant real BUTTON_H = .1 * SCREEN_ASPECT_RATIO
      
        private UIText array Text
      
        private boolean array Displayed
    endglobals
  
    function ShowCustomInterface takes player p, boolean flag returns nothing
        local User user = User
      
        set Displayed[user.id] = false
      
        call Background[user.id].show(flag, PlayerCamera[user.id])
        call Button[user.id].show(flag, PlayerCamera[user.id])
        call Text[user.id].show(flag, PlayerCamera[user.id])
    endfunction
  
    private function OnUpdate takes nothing returns nothing
        local User user
        local integer i = 0
      
        loop
            exitwhen i == User.AmountPlaying
          
            set user = User.fromPlaying(i)
          
            if (PlayerCamera[user.id].applyCameraForPlayer(user.handle, false)) then
                call Background[user.id].update()
                call Button[user.id].update()
                call Text[user.id].update()
            endif

            set i = i + 1
        endloop
    endfunction

    private function OnLeftClick takes nothing returns nothing
        local UIButton btn = GetTriggerButton()
        local integer pid = btn.customValue
      
        call PauseUnit(PlayerHero[pid], true)
        call IssueImmediateOrder(PlayerHero[pid], "stop")
        call PauseUnit(PlayerHero[pid], false)
        call SelectUnit(PlayerHero[pid], true)
      
        call BJDebugMsg("OnLeftClick")
    endfunction
  
    private function OnRightClick takes nothing returns nothing
        local UIButton btn = GetTriggerButton()
        local integer pid = btn.customValue
      
        call BJDebugMsg("OnRightClick")
    endfunction
  
    private function CreateInterfaces takes nothing returns nothing
        local User user
        local integer i = 0
      
        loop
            exitwhen i == User.AmountPlaying
          
            set user = User.fromPlaying(i)
          
            set Background[user.id] = UIPicture.createEx(BACKGROUND_X, BACKGROUND_Y, 2, BACKGROUND_SIZE, Interface.WINDOW_DUMMY, BACKGROUND_W, BACKGROUND_H, Interface.getRaceBorders(GetPlayerRace(user.handle)))
          
            set Button[user.id] = UIButton.create(-0.025, 0.07, BUTTON_W, BUTTON_H, 0.5, 'D000')
            set Button[user.id].onLeftClick = Filter(function OnLeftClick)
            set Button[user.id].onRightClick = Filter(function OnRightClick)
            set Button[user.id].customValue = i
          
            set Text[user.id] = UIText.create(BACKGROUND_X + 0.15, BACKGROUND_Y, 1)
            call SetTextTagText(Text[user.id].text, "Custom User Interface v1.0", 12 * 0.0023)
          
            call ShowCustomInterface(user.handle, true)
          
            set i = i + 1
        endloop

        call TimerStart(GetExpiredTimer(), 0.03, true, function OnUpdate)
    endfunction
  
    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.0, false, function CreateInterfaces)
    endfunction

endscope
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I have been looking for the source problem, so far it looks like SetCameraTargetController is what causing it, or maybe it's a chain reaction from somewhere else. Still can't find the solution tho.

@TriggerHappy: Have you managed to fix the camera flickering then?

I've basically finished re-writing DGUI. Here's the implementation in case you're curious:
Looks neat!
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I think I'm doing it just like what you suggested. But it's still flicking. Or maybe I'm doing it wrong?

I'm done with my little inventory system, and just found that what's tricky with using "local UI" is managing the icon's texture, am I right? At my inventory's tiny scale, it's already quite troublesome, can't imagine what you have been through in your inventory system :)
 
Yes because ShowUnit desyncs for units which were moved locally, I have to set each texture to a transparent one for everyone but the local player, and I also set the dummies scale to zero when not in use, AND I set the units transparency to 100%. I could probably just use scale but so far it's working great.

Anyway I'm not sure of the flickering.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
As far as I know, locally modifying unit's vertex color may lead to desync in some occasion (not sure what occasion tho, I experienced it once ago when I attempted a custom unit invisibility thing). So I'd prefer to use scale instead.

No scale should be better because you can still the mouse highlighted with transparency. I haven't really tested it though.
And that's true.
 
Moving units locally? What if the dummy unit is affected by anything gameplay wise? That should in theory cause a desync.

Example: what if a unit applying an aura comes within range of a UI unit? As soon as the locally moved unit gets affected by an aura, it should definitely desync.
All in all, I expect this to be very vulnerable to OE AoE spells.

Also, have you thought about spline-interpolating between GetCameraTargetPosition calls in multiplayer to reduce the stuttering? That way the UI will "sway" a bit when accelerating the camera, but it should get rid of all stuttering.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Moving units locally? What if the dummy unit is affected by anything gameplay wise? That should in theory cause a desync.

Example: what if a unit applying an aura comes within range of a UI unit? As soon as the locally moved unit gets affected by an aura, it should definitely desync.
All in all, I expect this to be very vulnerable to OE AoE spells.

Also, have you thought about spline-interpolating between GetCameraTargetPosition calls in multiplayer to reduce the stuttering? That way the UI will "sway" a bit when accelerating the camera, but it should get rid of all stuttering.
At the moment, I have two versions of this system, the first one does not move unit locally (it is not uploaded yet, it's heavier, does not desync). And the other one (this one) does move units locally, it still does cause desync atm but, if what @TriggerHappy said was true, then it can be easily fixed by adding the Ghost (visible) ability to the dummy units.

The desync only happen for dummy units without locust ability btw, that probably causes pathing-related desync.

And the camera stuttering issue you mentioned does not exist, we use custom camera system to fix it. But without custom camera system, there no real fix to the issue, because the camera refresh rate is "way faster" than what can be achieved only by using timer.

EDIT:
I thought you posted in the resource thread. Here is the link btw: Panel System v2.0
 
Last edited:
Status
Not open for further replies.
Top