Jump to content


  • Content Count

  • Joined

  • Last visited

Everything posted by Josh

  1. Add an empty script to the brush?
  2. DPI scaling and the 2D drawing and GUI system were an issue I was a bit concerned about, but I think I have it worked out. This all goes back to the multi-monitor support that I designed back in September. Part of that system allows you to retrieve the DPI scale for each display. This gives you another piece of information in addition to the raw screen resolution. The display scale gives you a percentage value the user expects to see vector graphics at, with 100% being what you would expect with a regular HD monitor. If we scale our GUI elements and font sizes by the display scale we can adjust for screens with any pixel density. This shot shows 1920x1080 fullscreen with DPI scaling set to 100%: Here we see the same resolution, with scaling set to 125%: And this is with scaling set to 150%: The effect of this is that if the player is using a 4K, 8K, or any other type of monitor, your game can display finely detailed text at the correct size the user expects to see. It also means that user interfaces can be rendered at any resolution for VR. Rather than trying to automatically scale GUI elements I am giving you full control over the raw pixels. That means you have to decide how your widgets will be scaled yourself, and program it into the game interface, but there is nothing hidden from the developer. Here is my code I am working with now to create a simple game menu. Also notice there is no CreatePanel(), CreateButton(), etc. anymore, there is just one widget you create and set the script for. I might add an option for C++ actors as well, but since these are operating on the main logic thread there's not really a downside to running the code in Lua. local window = ActiveWindow() if window == nullptr then return end local framebuffer = window:GetFramebuffer() if framebuffer == nil then return end self.gui = CreateGUI(self.guispritelayer) --Main background panel self.mainpanel = CreateWidget(self.gui,"",0,0,framebuffer.size.x,framebuffer.size.y) self.mainpanel:SetScript("Scripts/GUI/Panel.lua", true) local scale = window.display.scale.y local w = 120 local h = 24 local sep = 36 local x = framebuffer.size.x / 6 local y = framebuffer.size.y / 2 - sep * 3 self.resumebutton = CreateWidget(self.mainpanel,"RESUME GAME",x,y,w,h) self.resumebutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.resumebutton:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.label2 = CreateWidget(self.mainpanel,"OPTIONS",x,y,w,h) self.label2:SetScript("Scripts/GUI/Hyperlink.lua", true) self.label2:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.quitbutton = CreateWidget(self.mainpanel,"QUIT", x,y, w,h) self.quitbutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.quitbutton:SetFontSize(14 * window.display.scale.y) w = 400 * scale h = 550 * scale self.optionspanel = CreateWidget(self.mainpanel,"QUIT", (framebuffer.size.x- w) * 0.5, (framebuffer.size.y - h) * 0.5, w, h) self.optionspanel:SetScript("Scripts/GUI/Panel.lua", true) self.optionspanel.color = Vec4(0.2,0.2,0.2,1) self.optionspanel.border = true self.optionspanel.radius = 8 * scale self.optionspanel.hidden = true
  3. For finer control over what 2D elements appear on what camera, I have implemented a system of "Sprite Layers". Here's how it works: A sprite layer is created in a world. Sprites are created in a layer. Layers are attached to a camera (in the same world). The reason the sprite layer is linked to the world is because the render tweening operates on a per-world basis, and it works with the sprite system just like the entity system. In fact, the rendering thread uses the same RenderNode class for both. I have basic GUI functionality working now. A GUI can be created directly on a window and use the OS drawing commands, or it can be created on a sprite layer and rendered with 3D graphics. The first method is how I plan to make the new editor user interface, while the second is quite flexible. The most common usage will be to create a sprite layer, attach it to the main camera, and add a GUI to appear in-game. However, you can just as easily attach a sprite layer to a camera that has a texture render target, and make the GUI appear in-game on a panel in 3D. Because of these different usages, you must manually insert events like mouse movements into the GUI in order for it to process them: while true do local event = GetEvent() if event.id == EVENT_NONE then break end if event.id == EVENT_MOUSE_DOWN or event.id == EVENT_MOUSE_MOVE or event.id == EVENT_MOUSE_UP or event.id == EVENT_KEY_DOWN or event.id == EVENT_KEY_UP then gui:ProcessEvent(event) end end You could also input your own events from the mouse position to create interactive surfaces, like in games like DOOM and Soma. Or you can render the GUI to a texture and interact with it by feeding in input from VR controllers. Because the new 2D drawing system uses persistent objects instead of drawing commands the code to display elements has changed quite a lot. Here is my current button script. I implemented a system of abstract GUI "rectangles" the script can create and modify. If the GUI is attached to a sprite layer these get translated into sprites, and if it is attached directly to a window they get translated into system drawing commands. Note that the AddTextRect doesn't even allow you to access the widget text directly because the widget text is stored in a wstring, which supports Unicode characters but is not supported by Lua. --Default values widget.pushed=false widget.hovered=false widget.textindent=4 widget.checkboxsize=14 widget.checkboxindent=5 widget.radius=3 widget.textcolor = Vec4(1,1,1,1) widget.bordercolor = Vec4(0,0,0,0) widget.hoverbordercolor = Vec4(51/255,151/255,1) widget.backgroundcolor = Vec4(0.2,0.2,0.2,1) function widget:MouseEnter(x,y) self.hovered = true self:Redraw() end function widget:MouseLeave(x,y) self.hovered = false self:Redraw() end function widget:MouseDown(button,x,y) if button == MOUSE_LEFT then self.pushed=true self:Redraw() end end function widget:MouseUp(button,x,y) if button == MOUSE_LEFT then self.pushed = false if self.hovered then EmitEvent(EVENT_WIDGET_ACTION,self) end self:Redraw() end end function widget:OK() EmitEvent(EVENT_WIDGET_ACTION,self) end function widget:KeyDown(keycode) if keycode == KEY_ENTER then EmitEvent(EVENT_WIDGET_ACTION,self) self:Redraw() end end function widget:Start() --Background self:AddRect(self.position, self.size, self.backgroundcolor, false, self.radius) --Border if self.hovered == true then self:AddRect(self.position, self.size, self.hoverbordercolor, true, self.radius) else self:AddRect(self.position, self.size, self.bordercolor, true, self.radius) end --Text if self.pushed == true then self:AddTextRect(self.position + iVec2(1,1), self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) else self:AddTextRect(self.position, self.size, self.textcolor, TEXT_CENTER + TEXT_MIDDLE) end end function widget:Draw() --Update position and size self.primitives[1].position = self.position self.primitives[1].size = self.size self.primitives[2].position = self.position self.primitives[2].size = self.size self.primitives[3].size = self.size --Update the border color based on the current hover state if self.hovered == true then self.primitives[2].color = self.hoverbordercolor else self.primitives[2].color = self.bordercolor end --Offset the text when button is pressed if self.pushed == true then self.primitives[3].position = self.position + iVec2(1,1) else self.primitives[3].position = self.position end end This is arguably harder to use than the Leadwerks 4 system, but it gives you advanced capabilities and better performance that the previous design did not allow.
  4. I don't really think teams of people will form spontaneously in a for-profit project. This only works with free mods, sometimes. Every person you let in is a liability and a mouth to feed. It you have something of value it's cheaper to contract out the parts you need help with, pay up-front, and don't worry about revenue splits.
  5. Josh

    Leadwerks 5 beta Update

    No but now is a good time to implement this. Smart pointers make this much much easier.
  6. This guy was doing Mick Gordon's music before Mick Gordon: 


  7. What's new EAX audio effects for supported hardware. Source class renamed to "Speaker". Plane joint for 2D physics, so now you can make Angry Birds with Vulkan graphics. Fixed DPI issues with fullscreen mode. Added impact noise to barrels, fixed Lua collision function not being called. Script functions now start with "Entity:" instead of "Script:", i.e. Entity:Update() instead of Script:Update(). Additionally, four examples can be run showing various functionality. Double-click on the .bat files to launch a different demo: First-person shooter game. 2D physics demonstration. Advanced 2D drawing with text, rotation, and scaling. Multi-camera setup.
  8. Get your GAMEFUEL now, lol: https://www.gamefuel.com/

    1. Show previous comments  4 more
    2. gamecreator


      Of course, now that Josh is a millionaire, I'm sure Leadwerks and its future versions will soon be free.

    3. Marcousik


      And I hoped we could get a free remake of the "fuel" game :lol:

    4. Josh


      I just thought it was hilariously cheesy. I kind of want a variety pack though.

    5. Show next comments  3 more
  9. That's the wonderful thing about smart pointers, problems like this go away.
  10. I am also adding a Speaker::Copy() command for you.
  11. I've been doing some work on the sound system in Leadwerks 5 beta, and I added EAX effects in. If you have a dedicated sound card this can be used to add some nice reverb effects that make your sound environment sound a lot more real: Here's the simplest usage: auto fx = LoadSoundEffect("Sound/FX/sewerpipe.json"); auto listener = CreateListener(world); listener->SetEffect(fx); This will apply the effect to all mono sources. Stereo sources are assumed to be music or GUI noises, and will be unaffected. Eventually, the way I see this being used is a script attached to a CSG brush that changes the listener's EAX effect when the player enters and leaves the volume, but the above shows the API approach. I exported all the EAX presets into JSON files like so. You can load one of the existing files, or if you are feeling really creative you can try making your own: { "AirAbsorptionGainHF": 0.99426, "DecayHFLimit": 0, "DecayHFRatio": 0.89, "DecayLFRatio": 0.41, "DecayTime": 2.76, "Density": 1.0, "Diffusion": 0.82, "EchoDepth": 0.17, "EchoTime": 0.13, "Gain": 0.316228, "GainHF": 0.281838, "GainLF": 0.0891251, "HFReference": 2854.4, "LateReverbGain": 0.891251, "LateReverbPan": [0.0, 0.0, 0.0], "LFReference": 107.5, "LateReverbDelay": 0.02, "ModulationDepth": 0.0, "ModulationTime": 0.25, "ReflectionsDelay": 0.029, "ReflectionsGain": 0.354813, "ReflectionsPan": [0.0, 0.0, -0.0], "RoomRolloffFactor": 0.0 } Here's the full list of available presets: CastleSmallroom CastleMediumroom CastleLongpassage CastleLargeroom CastleHall CastleCupboard CastleCourtyard CastleAlcove FactoryAlcove FactoryShortPassage FactoryMediumRoom FactoryLongPassage FactoryLargeRoom FactoryHall FactoryCupboard FactoryCourtyard FactorySmallRoom IcepalaceAlcove IcepalaceShortPassage IcepalaceMediumRoom IcepalaceLongPassage IcepalaceLargeroom IcepalaceHall IcepalaceCupboard IcepalaceCourtyard IcepalaceSmallRoom SpacestationAlcove SpacestationMediumRoom SpacestationShortpassage SpacestationLongPassage SpacestationLargeRoom SpacestationHall SpacestationCupboard SpacestationSmallRoom WoodenAlcove WoodenShortPassage WoodenMediumRoom WoodenLongPassage WoodenLargeRoom WoodenHall WoodenCupboard WoodenSmallRoom WoodenCourtyard SportEmptyStadium SportSquashCourt SportSmallSwimmingPool SportLargeSwimmingPool SportGymnasium SportFullStadium SportStadiumTannoy Workshop SchoolRoom PractiseRoom Outhouse Caravan Dome Tomb PipeSmall DomeSaintPauls PipeLongThing PipeLarge PipeResonant OutdoorsBackyard OutdoorsRollingPlains OutdoorsDeepCanyon OutdoorsCreek OutdoorsValley MoodHeaven MoodHell MoodMemory DrivingCommentator DrivingPitGarage DrivingInCarRacer DrivingInCarSports DrivingFullGrandstand DrivingEmptyGrandstand DrivingTunnel CityStreets CitySubway CityMuseum CityLibrary CityUnderpass Dustyroom Chapel SmallWaterRoom I might consider implementing Steam Audio in the future (formerly Phonon) but for now OpenAL does everything I want.
  12. "Speaker" is definitely a better name for the class than "Source". I'm taking that.
  13. Josh

    Leadwerks 5 beta Update

    It's also good for VR.
  14. Josh

    Leadwerks 5 beta Update

    Although looking at their discussion, I would NOT use this for cascaded shadow mapping. The near stage will have far fewer objects in view than the further ones, and the extra geometry would have a much bigger impact than the savings of multiview rendering.
  15. Josh

    Leadwerks 5 beta Update

    Yes, it is core in Vulkan 1.1, with a minimum of six passes supported.
  16. Josh

    Leadwerks 5 beta Update

    It also seems like the startup speed is much much faster now. I figured out that vkCreateInstance() and the vkGetExtensions() command (I can't remember the name right now) each take about one second, and the startup time total is about two seconds and you're in the game. If you were loading a larger scene that took more time to load, you could kick off the rendering thread by calling World::Render() immediately after it is created, before the scene is loaded. This would cause the Vulkan initialization to be processed on the rendering thread while the scene is loading on the main thread. This would save the ~2 seconds of rendering initialization time, because it seems like the rest is pretty much instantaneous.
  17. Is this after it has been running a long time?
  18. An update for Leadwerks 5 is now available. The Vulkan data transfer system has been revised and is now simpler but uses more memory. Data is likely to be quadruple-buffered, but it's a fairly small amount of data and this isn't a big concern. I fixed a bad bug where multiple threads were accessing a global variable in the Mat4::GetQuaternion function. This fixes the object flashing glitch that was visible in previous builds. The engine is updated to the latest version of Newton Dynamics and kinematic joints work correctly now. The upvector joint is removed and a plane joint has been added for 2D physics, but I don't think it will work yet. Object picking up is implemented in the player controller script. I switched out the default scene with a new one using some of @TWahl 's materials. Added an FPSWeapon script that loads a gun and makes it sway. Entity::AddScript() can now be called in the Start() function of another script with no problems. Fullscreen windows are now working. The window / display system is changed a bit. New display commands: std::vector<shared_ptr<Display> > ListDisplays() std::vector<iVec2> Display::GraphicsModes() You must pass a display object in the window creation command now. Here's how I do it in Lua: --Get the primary display local displaylist = ListDisplays() local display = displaylist[1]; --Get the display's highest resolution graphics mode gfxmodes = display:GraphicsModes() gfx = gfxmodes[#gfxmodes] --Create a window local fullscreenmode = false local window if fullscreenmode then window = CreateWindow(display, "My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN) else window = CreateWindow(display, "My Game", 0, 0, 1280 * display.scale.x, 720 * display.scale.y, WINDOW_CENTER + WINDOW_TITLEBAR) end And in C++: const bool fullscreenmode = false; //Get the primary display auto displays = ListDisplays(); auto display = displays[0]; //Create a window shared_ptr<Window> window; if (fullscreenmode) { auto gfxmodes = display->GraphicsModes(); auto gfx = gfxmodes[gfxmodes.size() - 1]; window = CreateWindow(display, L"My Game", 0, 0, gfx.x, gfx.y, WINDOW_FULLSCREEN); } else { Vec2 displayscale = display->GetScale(); window = CreateWindow(display, L"My Game", 0, 0, 1280 * displayscale.x, 720 * displayscale.y, WINDOW_TITLEBAR | WINDOW_RESIZABLE | WINDOW_CENTER); } The speed of the point light shadow updating is unbelievably fast. Point light shadows in Leadwerks 4 are very expensive to update because they require six different render passes for each of the six cubemap faces, but in Leadwerks 5 beta with Vulkan they are basically free. I'm sure it will slow down if I add enough points lights and have them all constantly updating, but I don't see any difference at all in the framerate right now when shadows are active. If you are having any trouble with their appearance you can set the global variable MULTIPASS_CUBEMAP to false in C++ at the very beginning of your program. This script can be used to display performance statistics. At this time it only shows the framerate but I can expand on this in the future. function Script:Start() self.statsEnabled = true self.textcache = {} self.font = LoadFont("Fonts/arial.ttf") self.fontsize = 16 self.textalignment = TEXT_LEFT self.world:EnableStats(self.statsEnabled) self:BindKey(KEY_F11, self.ToggleStats) end function Script:ToggleStats() self.statsEnabled = not self.statsEnabled self.world:EnableStats(self.statsEnabled) end function Script:Update() --Return if disabled or font missing if self.statsEnabled == false or self.font == nil then return end --Hide previously used sprite if self.displayfps ~= nil then self.displayfps:Hide() end --Retrieve the framerate and convert to string --Convert to integer to limit the amount of different string values local fps = tostring(math.ceil(self.world.renderstats.framerate - 0.5)).." FPS" --Check for cached version and create it if it doesn't exist if self.textcache[fps] == nil then self.textcache[fps] = CreateText(self.world, self.font, fps, self.fontsize, self.textalignment, 1) self.textcache[fps]:SetPosition(4,4) self.textcache[fps]:SetColor(0,1,0,0.75) end --Set current sprite and show self.displayfps = self.textcache[fps] self.displayfps:Show() end It may seem like a lot of code just to draw a bit of text onscreen, but the benefit is extreme performance. Instead of drawing one character at a time like the Leadwerks renderer does, this creates persistent text objects and reuses them when needed. That cuts the performance cost of displaying text down to basically zero, making it great for complex GUIs and game interfaces.
  19. Windows game bar slows down Vulkan and causes intermittent pauses. Disable this before trying Leadwerks 5 beta.

    1. reepblue


      I've read that causes slowdown in a lot of other games. Doesn't help that it's auto enabled. 🙃

    2. gamecreator


      I thought maybe Windows Aero had similar issues but searches bring up other causes.

    3. Josh


      The anti malware service executable can also take over your computer usage, and it pauses itself when the performance analyzer opens, so it hides as soon as you check to see why things are so slow. Sneaky program that is as bad as malware.

  20. I think the screen recorder locks it at 60.
  21. The best way to test the new engine is to use it to make something. I am messing around with the beginnings of a new first-person shooter template. I'm telling everyone involved "We are remaking Doom, but a little differently" and it actually works really well. Everyone understand what it should look like, and there is no need to establish a new visual style. We can tell when we have it right, and when we have it wrong. And the original game gives us a sort of benchmark for quality and performance we can measure against. I have two scripts for the player. One is for basic movement and interaction with objects. Another one handles the display and movement of the player's weapon. I opted to use entirely programmatic motion for the weapon sway, and I don't plan on showing any hands. These will work together because Leadwerks 5 allows you to add multiple scripts to any entity. Here is the current weapon script: function Script:Start() self.modelfile = "Models/Weapons/Ronan Rifle/scene.gltf" self.weaponposition = Vec3(0.12, -0.4, 0.42) self.weaponswayspeed = 0.25 self.weaponswayticks = 0 --Load weapon self.weapon = LoadModel(self.world, self.modelfile) if self.weapon == nil then return end self.weapon:CastShadows(false) self.weapon:SetPosition(self.weaponposition) self.weaponrotation = self.weapon:GetRotation() end function Script:Update() if self.weapon == nil then return end --Parent to camera if not already done if self.weapon.parent == nil then if type(self.camera) == "userdata" then self.weapon:SetParent(self.camera,false) else DebugWarning("FPSWeapon: self.camera must be an entity.") end end --Adjust speed based on ground velocity self.weaponswayspeed = 0.1 if self:GetAirborne() == false then local speed = self.velocity.xz:Length() self.weaponswayspeed = self.weaponswayspeed + 0.75 * math.min(speed / 3, 1) end --Weapon sway self.weaponswayticks = self.weaponswayticks + 16.666666 * self.weaponswayspeed local pitch = math.cos(self.weaponswayticks / 100) * 0.25 local yaw = math.sin(self.weaponswayticks / 100) * 0.25 local sway = math.sin(self.weaponswayticks / 100) * 0.004 local bob = -math.cos(self.weaponswayticks / 50) * 0.002 self.weapon:SetRotation(self.weaponrotation + Vec3(pitch, yaw, 0)) self.weapon:SetPosition(self.weaponposition + Vec3(sway, bob, 0)) end An interesting bit is the swizzle and properties, which makes Lua more flexible than in the past: local speed = self.velocity.xz:Length() In Leadwerks 4 we would have had to type all this: local speed = self.entity:GetVelocity():xz():Length() Here is the very first pass, with some materials courtesy of @TWahl. I wanted much faster movement with this one, so the player runs by default and you hold the shift key to walk more slowly.
  22. I took a closer look at Doom and it looks like they are performing a raycast in front of the player and making the bullet trajectory point to that point, even though that makes it go out crooked from the gun.
  23. An update is available for the Leadwerks 5 beta. Shadow updating is fixed so the lights no longer turn black during the update whenever a shadow changes. We're now using multiview to draw all six faces of a cube shadow map in one single pass! Point light shadow updates are something that used to be a considerable bottleneck in Leadwerks 4, and their performance impact is now very insignificant. Thanks to Sascha Williams for his excellent Vulkan examples. Joints are finished, a new upvector joint is added to lock the Y axis to a vector, all collision shape types are now supported, and body mass centers are fixed for rigid body physics will work correctly.
  24. Josh

    Player Crouch

    Very fluid!
  • Create New...