Jump to content

Josh

Staff
  • Posts

    23,296
  • Joined

  • Last visited

Blog Entries posted by Josh

  1. Josh
    The final step to our VR teleporter mechanic is to make the beam arc. This allows us to climb up to areas above our head.

    The trick to this is to take our single beam picking mechanic and split it up into a lot of little segments, for both the intersection test and the visual display. I decided to make a kind of simple physics simulation for this, rather than using bezier curves or another method. The basic idea is you have a starting position and a velocity. You move a point along that velocity, and then add gravity to the velocity and repeat. You don't have to worry about resistance or anything like that. The result is a nice arc that mimics a ball thrown into the air.
    --Loop through segments making an arc for n=1,self.maxbeamsteps do p1 = p0 + velocity * speed velocity.y = velocity.y + gravity end The other feature I added was smooth movement during teleportation. It's very fast, taking less than a second, but it does a really good job of communicating motion. I myself am sensitive to motion sickness and am able to use this method of locomotion with no problems.
    --Update offset position local pos = VR:GetOffset() local d = self.targetoffset:DistanceToPoint(pos) local speed = 1.0 if speed>d then speed=d end pos = pos + (self.targetoffset - pos):Normalize() * speed VR:SetOffset(pos) I also decided to make the teleportation beam and indicator green or red, depending on its state, because the meaning of that is universal and easily understood. Here's the complete script, in only 150 lines of code:
    function Script:Start() --Initialize VR if VR:Enable()==true then VR:SetTrackingSpace(VR.Roomspace) else System:Print("Error: Failed to initialize VR environment.") end --Create the player camera self.camera = Camera:Create() self.camera:SetPosition(self.entity:GetPosition(true)) self.camera:Translate(0,1.6,0) self.camera:SetRotation(self.entity:GetRotation(true)) VR:CenterTracking() --Create teleporter beam local beam = Sprite:Create() beam:SetViewMode(6) beam:SetSize(0.05,20) beam:SetColor(1,2,1) local mtl = Material:Load("Models/VR/teleport3.mat") if mtl~=nil then beam:SetMaterial(mtl) mtl:Release() mtl = nil end beam:Hide() --Create beam segments self.maxbeamsteps=32 self.beam = {} self.beam[1] = beam local n for n=2,self.maxbeamsteps do self.beam[n] = tolua.cast(self.beam[1]:Instance(),"Sprite") end --Load the teleport indicator prefab self.teleportindicator = Prefab:Load("Models/VR/teleport.pfb") self.teleportindicator:SetColor(1,2,1) self.teleportindicator:Hide() --Initialize offset self.targetoffset = VR:GetOffset() end function Script:UpdateWorld() local window = Window:GetCurrent() if window~=nil then if window:KeyHit(Key.F2) then VR:CenterTracking() end end --Check if teleporter is active and the button was released if self.teleportindicator:Hidden()==false then if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==false then local offset = VR:GetOffset() local pos = self.teleportindicator:GetPosition() local campos = self.camera:GetPosition(true) pos.x = pos.x - campos.x + offset.x pos.y = pos.y - 0.05 pos.z = pos.z - campos.z + offset.z self.targetoffset = pos end end --Hide beam and indicator self.teleportindicator:Hide() for n=1,self.maxbeamsteps do self.beam[n]:Hide() self.beam[n]:SetColor(2,1,1) end local controller = VR:GetControllerModel(VR.Right) if controller~=nil then --Activate teleporter if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==true then local world = self.entity.world local pickinfo = PickInfo() local p0 = controller:GetPosition(true) local velocity = Transform:Normal(0,0,-1,controller,nil) local speed = 1 local n local gravity = -0.1 --Loop through segments making an arc for n=1,self.maxbeamsteps do p1 = p0 + velocity * speed velocity.y = velocity.y + gravity self.beam[n]:Show() self.beam[n]:SetPosition((p0+p1)*0.5,true) self.beam[n]:AlignToVector(p1-p0,2) self.beam[n]:SetSize(0.05,p0:DistanceToPoint(p1)+0.02) if world:Pick(p0, p1, pickinfo, 0, true)==true then --Correct the length of the last beam segment self.beam[n]:SetSize(0.05,p0:DistanceToPoint(pickinfo.position)+0.02) self.beam[n]:SetPosition((p0+pickinfo.position)*0.5,true) --Cancel if slope is too steep local slope = 90 - Math:ASin(pickinfo.normal.y) if slope>35 then break end --Show the indicator self.teleportindicator:SetPosition(pickinfo.position) self.teleportindicator:Translate(0,0.05,0) self.teleportindicator:Show() --Recolor the beam for n=1,self.maxbeamsteps do self.beam[n]:SetColor(1,2,1) end break end p0 = p1 end end end --Update offset position local pos = VR:GetOffset() local d = self.targetoffset:DistanceToPoint(pos) local speed = 1.0 if speed>d then speed=d end pos = pos + (self.targetoffset - pos):Normalize() * speed VR:SetOffset(pos) end function Script:Detach() self.teleportindicator:Release() self.teleportindicator = nil for n=1,self.maxbeamsteps do self.beam[n]:Release() end self.beam = nil self.camera:Release() self.camera = nil end And here it is in action. This will be standard in Leadwerks Game Engine 4.5:
     
  2. Josh
    Internally, Leadwerks Editor uses an EventHandler class for every interface in the program. The material editor is a class extended from the EventHandler. So is the little window that has all the controls to calculate normals. So is every viewport.
    The event handler class has one important function:
    Event ProcessEvent(Event) Every EventHandler has access to events as they occur. This is how all program actions are handled in the editor.
    The plugin system will work by hooking into the event system. Each plugin will have a Lua script that receive events before the rest of the program sees them:
    function Script:ProcessEvent(event) return event end If the plugin makes no changes to the event then it simply returns the original event. The returned event is then sent to other event handlers.
    Here is an example of a plugin that would disable the close window button on the main window. Because the function returns nil the event is discarded before the main window ever evaluates it:
    function Script:ProcessEvent(event) if event.id == EVENT_WINDOWCLOSE and event.source == editor.mainwindow then return nil else return event end end Here is an example of a very mean plugin that would make it so that clicking the File > Open menu item in the main window quits the program:
    function Script:ProcessEvent(event) if event.id == EVENT_MENUEVENT then if event.source == editor.mainwindow then if event.extra == MENU_FILEOPEN then event.id = EVENT_WINDOWCLOSE end end end return event end Okay, now let's see if we can design a plugin for something people would actually want. Let's imagine we have a new visual material design system. The exact details of how it works are not important, it's just a system that overrides the default material editor. The design system would require materials to have a special file associated with them with the extension .DESIGN. If you open the material "brick.mat" we will look for a file in the same folder called "brick.design". If the design file is found we open the material in our special editor. If the design file is missing we will just fall back to the default material editor.
    Now let's see how our system can handle this:
    function Script:Start() --Create our interface self.window = CreateWindow("Material Designer",0,0,800,600,editor.mainwindow,WINDOW_CENTER + WINDOW_TITLEBAR + WINDOW_RESIZABLE) end function Script:ProcessEvent(event) if event.id == EVENT_FILEOPEN --Check for material files being opened if ExtractExt(event.extra)=="mat" --Look for design file local designfilename = StripExt(event.extra).".design" if FileType( designfilename ) == 1 then --Load the design file local stream = ReadFile(designfilename) if stream ~= nil then --Display our custom material editor self.window:Show() self.window:Activate() else Print("Error: Failed to load design file.") end --Discard the event return nil end end end return event end As you can see, this approach is extremely powerful. The event IDs and design rarely change, if ever, so this allows a lot of flexibility and at the same time gives us the optimal compatibility as changes are made to the core editor. With this approach to plugins you can literally do anything you want in the editor.
  3. Josh
    Leadwerks 5 is going to be developed alongside 4.5 with an extended period of beta testing and feedback.  My goal is to build the world's most advanced game design software, tailored to the needs of our community and clients.  Development will first focus on a new programming API updated for C++11 and then a completely new editor using Leadwerks GUI and based on the workflow developed with our current editor.
    The first beta will support the following features right away:
    Shared Pointers Unicode support 64-bit These features are already working.  Here is a working build you can try right now:
    Leadwerks5.zip
    This is the actual source code used to make this example.  Note that C++11 shared pointers have been implemented for all objects and the API has been simplified to make your code more readable:
    #include "Leadwerks.h" using namespace Leadwerks; int main(int argc, const char *argv[]) { auto window = CreateWindow(); auto context = CreateContext(window); auto world = CreateWorld(); auto camera = CreateCamera(world); camera->Move(0, 0, -3); auto light = CreateDirectionalLight(world); light->SetRotation(35, 35, 0); light->SetShadowMode(0); auto model = CreateBox(world); model->SetColor(0.0, 1.0, 0.92); while (not window->Closed()) { if (window->KeyHit(EscapeKey)) break; if (model) model->Turn(0, 1, 0); if (window->KeyHit(SpaceKey)) model = nullptr; world->Update(); world->Render(context); } return 0; } The beta will be available to developers who want to try the very latest game development technology, with a subscription plan of just $4.99 a month, available through our website.
  4. Josh
    An update is available on the beta branch on Steam that adds support for multiplayer games with the following features:
    NAT punch-through with relay server fallback. Connectionless peer-to-peer UDP messages with multiple channels and optional reliable flag. Public server list of available games to play. Voice-over-IP for in-game chat (and taunts). The new multiplayer system will open up a new range of game types that can be easily created with Leadwerks Game Engine.
    These features are still being tested and are only available in the Windows build right now.
  5. Josh
    I've expanded the turret AI to include a visibility test. This is performed initially when selecting a target, and is also performed on a continuous basis to make sure the target stays in view. To avoid overloading the engine I am only performing the visibility test at most once every 500 milliseconds. This provides near-instantaneous updating while preventing the picking from becoming a bottleneck and slowing down performance.
     
    Adding bullets was mostly a copy and paste job from the SoldierAI script. Because we have a self-contained tracer prefab already assembled, it is easy to reassign this prefab for use with the turret.
     

     
    Still left to do:
    The FPSGun script expects hit objects to either be animated nemies or physics objects. Some revision will be required to handle the turret objects, so that the bullets will hurt the object and also apply a force at the hit point, so that it can potentially tip over the turrets.
    Smoke needs to emit from the turret once it is killed, to signify that it is no longer operational.
    The script needs to detect when the turret is tipped over and deactivate the AI when this occurs.
    The model needs to be adjusted so the pivots point in the direction I want for the gun to point at the target object.

     
    My updated script is below.

    --Public Script.target = nil--enemy to shoot Script.range = 20 Script.health=100 Script.projectilepath = "Prefabs/Projectiles/tracer.pfb" Script.shoot1sound=""--path "Fire 1 sound" "Wav file (*.wav):wav|Sound" Script.shoot2sound=""--path "Fire 2 sound" "Wav file (*.wav):wav|Sound" Script.shoot3sound=""--path "Fire 3 sound" "Wav file (*.wav):wav|Sound" --Private Script.lastfiretime=0 Script.mode = "idle" Script.lastsearchtime=0 Script.searchfrequency = 500 Script.firefrequency = 50 function Script:Start() --Load sounds self.sound = {} self.sound.shoot = {} if self.shoot1sound~="" then self.sound.shoot[1] = Sound:Load(self.shoot1sound) end if self.shoot2sound~="" then self.sound.shoot[2] = Sound:Load(self.shoot2sound) end if self.shoot3sound~="" then self.sound.shoot[3] = Sound:Load(self.shoot3sound) end self.projectile = Prefab:Load(self.projectilepath) if self.projectile~=nil then self.projectile:Hide() end self.turret = self.entity:FindChild("turret") --Add muzzleflash to gun self.muzzle = self.entity:FindChild("muzzle") if self.muzzle~=nil then local mtl=Material:Create() mtl:SetBlendMode(5) self.muzzle:SetMaterial(mtl) mtl:Release() self.muzzleflash = Sprite:Create() self.muzzleflash:SetSize(0.35,0.35) local pos=self.muzzle:GetPosition(true) self.muzzleflash:SetPosition(pos,true) self.muzzleflash:SetParent(self.muzzle,true) mtl = Material:Load("Materials/Effects/muzzleflash.mat") if mtl then self.muzzleflash:SetMaterial(mtl) mtl:Release() mtl=nil end local light = PointLight:Create() light:SetRange(5) light:SetColor(1,0.75,0) light:SetParent(self.muzzleflash,flash) if light.world:GetLightQuality()<2 then light:SetShadowMode(0) end self.muzzleflash:Hide() end end function Script:Release() if self.projectile~=nil then self.projectile:Release() self.projectile = nil end end function TurretSearchHook(entity,extra) if entity~=extra then if entity.script~=nil then if type(entity.script.health)=="number" then if entity.script.health>0 then local d = extra:GetDistance(entity) if d<extra.script.range then if extra.script.target~=nil then if extra.script:GetDistance(extra.script.target)>d then if extra.script:GetTargetVisible(entity.script) then extra.script.target = entity.script end end else if extra.script:GetTargetVisible(entity.script) then extra.script.target = entity.script end end end end end end end end function Script:GetTargetVisible(target) if target==nil then target = self.target end if target==nil then return false end local pickinfo = PickInfo() local p0 = self.entity:GetAABB().center local p1 = target.entity:GetAABB().center local pickmode0 = self.entity:GetPickMode() local pickmode1 = target.entity:GetPickMode() self.entity:SetPickMode(0) target.entity:SetPickMode(0) local result = not self.entity.world:Pick(p0,p1,pickinfo,0,false,Collision.LineOfSight) self.entity:SetPickMode(pickmode0) target.entity:SetPickMode(pickmode1) return result end function Script:UpdateWorld() if self.health>=0 then local currenttime = Time:GetCurrent() --Stop shooting if target is dead if self.target~=nil then if self.target.health<=0 then self.target = nil end end --Search for target if self.target==nil then if currenttime - self.lastsearchtime > self.searchfrequency then self.lastsearchtime = currenttime local pos = self.entity:GetPosition(true) local aabb = AABB(pos - Vec3(self.range), pos + Vec3(self.range)) self.entity.world:ForEachEntityInAABBDo(aabb,"TurretSearchHook",self.entity) end end --Continuous visibility test if self.target~=nil then if currenttime - self.lastsearchtime > self.searchfrequency then self.lastsearchtime = currenttime if self:GetTargetVisible(self.target)==false then self.target = nil return end end end if self.target~=nil then --Motion tracking if self.turret~=nil then local p0 = self.turret:GetPosition(true) local p1 = self.target.entity:GetAABB().center local yplane = p1 - p0 yplane.y=0 self.turret:AlignToVector(yplane,1,0.1 / Time:GetSpeed(),0) end --Shoot if self.muzzle~=nil and self.projectile~=nil then if currenttime - self.lastfiretime > self.firefrequency then self.lastfiretime = currenttime local bullet = self.projectile:Instance() self.muzzleflash:Show() self.muzzleflash:EmitSound(self.sound.shoot[#self.sound.shoot]) self.muzzleflash:SetAngle(math.random(0,360)) self.muzzleflashtime=currenttime if bullet~=nil then bullet:Show() bullet:SetPosition(self.muzzle:GetPosition(true),true) bullet:SetRotation(self.muzzle:GetRotation(true),true) if bullet.script~=nil then bullet.script.owner = self if type(bullet.script.Enable)=="function" then bullet.script:Enable() end bullet:Turn(Math:Random(-3,3),Math:Random(-3,3),0) end end end end end end self:UpdateMuzzleFlash() end function Script:UpdateMuzzleFlash() if self.muzzleflashtime then if Time:GetCurrent()-self.muzzleflashtime<30 then self.muzzleflash:Show() else self.muzzleflash:Hide() end end end
  6. Josh
    My Gigabyte Brix pro lacks an audio in port and I was not able to find a USB microphone yesterday. However, I found a cheap webcam with audio support and have been using this to test voice recording, and it works great. Here is the final voice recording API:
    bool Sound::StartRecording(const int frequency = 22050, const int format = Sound::Mono8, const int buffersize = 20480) Sound* Sound::StopRecording() bool Sound::StopRecording(Lobby* lobby) lobby->SetVoiceFilter(const uint64 steamid, const bool block) And in Leadwerks 5:
    bool StartRecording(const int frequency = 22050, const int format = SOUND_MONO8, const int buffersize = 20480) shared_ptr<Sound> StopRecording() bool StopRecording(shared_ptr<Lobby> lobby) lobby->SetVoiceFilter(const uint64 steamid, const bool block) The first overload of the StopRecording function will return a Leadwerks sound object you can play, and then second version of the function will broadcast your recorded audio to all players in the specified lobby. The SetVoiceFilter() method in the Lobby class can be used to block some players from receiving your voice audio, so the game can make voice chat private for your team.
    The lobby and peer-to-peer networking system are also done, so in theory we have everything we need right now to make multiplayer games. An update will be out soon on the beta branch so you can start testing these new features.
  7. Josh
    We've added a new website feature called Projects to help teams collaborate on their games. A project can be created with several privacy features so you can use this for public open-source games everyone can participate in, or for your team's secret project. I myself have started a project I intend to develop to demonstrate Leadwerks multiplayer capabilities:
    You can add a forum, blog, and downloads section to your project and use it to host files, carry out discussions, and post updates to your team. The project creator can also moderate the content and has the ability to invite and approve new members.
    I hope you find this feature useful for team development.
  8. Josh
    Previously, I talked about the new peer-to-peer networking system that handles Nat punch-through and allows a sort of "floating" server that stays alive as long as at least one player is in the game.
    The lobby system allows you to broadcast to other players that you have a game available to join. The name might be somewhat misleading, as it does not require players to hang out in a chatroom before starting the game. My implementation functions more like a standard game server list.
    To create a new lobby and tell other players your game is available to join, call the following:
    Lobby* lobby = Lobby::Create(const int maxplayers = 32, const int type = Lobby::Public) Or in Leadwerks 5:
    auto lobby = CreateLobby(const int maxplayers = 32, const int type = LOBBY_PUBLIC) You can set attributes of your lobby that other users can read and display:
    lobby->SetKey("map","SuperArenaOfDeath") Other users can retrieve a list of lobbies as follows:
    int count = Lobby::Count(); for (int n=0; n<count; n++) { auto lobby = Lobby::Get(n); } Or in Leadwerks 5:
    int count = CountLobbies(); for (int n=0; n<count; n++) { auto lobby = GetLobby(n); } You can retrieve attributes of a lobby:
    std::string value = lobby->GetKey("map"); When you find the lobby you want, joining and leaving is easy:
    lobby->Join(); lobby->Leave(); When you have joined a lobby you can retrieve the lobby owner Steam ID, and the Steam IDs of all lobby members. This is what you use as the message destinations in the peer-to-peer messagng system:
    int64 steamid = lobby->GetOwner(); for (int n=0; n<lobby->CountMembers(); n++) { steamid = lobby->GetMember(); } Once you have joined a lobby and retrieved the steam IDs of the members you can start sending messages to the lobby owner or to other players in the game. Just like the P2P networking system, if the original creator of the lobby leaves, the ownership of that lobby is automatically passed off onto another player, and the lobby stays alive as long as one person is still participating. Once everyone leaves the lobby, it shuts down automatically.
  9. Josh
    Ladies and gentlemen, come one, come all, to feast your eyes on wondrous sights and behold amazing feats! It's "Cirque des Jeux", the next Leadwerks Game Tournament!

    How does it work?  For one month, the Leadwerks community builds small playable games.  Some people work alone and some team up with others.  At the end of the month we release our projects to the public and play each other's games.  The point is to release something short and sweet with a constrained timeline, which has resulted in many odd and wonderful mini games for the community to play.
    WHEN: The tournament begins Thursday, February 1, and ends on Wednesday, February 28th at the stroke of midnight.
    HOW TO PARTICIPATE: Publish your Circus-or-other-themed game to the Games Showcase before the deadline. You can work as a team or individually. Use blogs to share your work and get feedback as you build your game.
    Games must have a preview image, title, and contain some minimal amount of gameplay (there has to be some way to win the game) to be considered entries. It is expected that most entries will be simple, given the time constraints.
    This is the perfect time to try making a VR game or finish that idea you've been waiting to make!
    PRIZES: All participants will receive a limited-edition 11x17" poster commemorating the event. To receive your prize you need to fill in your name, mailing address, and phone number (for customs) in your account info.
    At the beginning of March we will post a roundup blog featuring your entries. Let the show begin!
  10. Josh
    Previously I talked about a voice recording API done through Steamworks. However, OpenAL already has a simple recording API that handles this. The only thing the Steamworks API adds is compression (presumably OGG) to send the data.
    I quickly implemented an OpenAL-based recording API, although I do not presently have any recording hardware device to test with. OGG compression can be added with the Ogg library that is already built into Leadwerks.
    Here is my modified recording API:
    static bool Sound::StartRecording(); static Sound* Sound::StopRecording(); Or in Leadwerks 5:
    bool StartRecording(); shared_ptr<Sound> StopRecording(); I'm not sure if I want sound broadcast to be automatic, or if I want this to be a general-purpose sound recording API with an example that compresses the audio data and writes it to a packet.
  11. Josh
    I recently picked up an Oculus Rift (CV1) in order to improve our support for this headset. It was easy to make my changes, but having the actual hardware on hand was invaluable in this process, and our support for the Rift is now much better because of this. I have now tried a total of five different VR headsets:
    HTC Vive Valve's duct-taped prototype that was the basis of the HTC Vive. Oculus Rift DK1 Oculus Rift DK2 Oculus Rift CV1 At one point Leadwerks Game Engine actually worked with the DK2 but at the time I chose not to release this feature.
    So I've worked with a wide range of VR hardware going back several years, and here's what I think.

    Ergonomics
    The Oculus Rift has improved substantially over earlier prototypes with comfortable straps, built-in headphones, and an easy setup. The Rift feels like a polished consumer product while the Vive feels not quite as refined. The most annoying thing about the Vive are the earbuds, which add an extra cord and get tangled up in the headset cord going to the computer. Even worse, one of my earbud covers fell off, I lost it, and now I am using the included smaller set so the headphones tend to fall out of my ears while I am playing.
    Winner: Oculus Rift
    Ease of Setup
    With the Oculus Rift, you just plug the headset into an HDMI port and a USB port, plug both motion trackers into USB ports, and you're done. The HTC Vive, on the other hand, can be overwhelming when you open the box. It uses a base station which increases the number of wires, and the lighthouse tracking units require some thought on how and where to put them. I bought two photography tripods and have my lighthouse units mounted on top of those.
    The Oculus Rift involves a total of three cables, while the HTC Vive uses twice as many.
    Winner: Oculus Rift
    Display
    Both headsets feature a 2160x1200 OLED screen. I feel like the screen-door effect is less severe on the Oculus but both are good. Contrast in lighting levels can cause reflections in the Vive that form concentric rings around your pupil. The Rift also has reflections but these take the form of "godrays" that appear to be moving in towards your eye.
    Winner: Oculus Rift
    Controllers
    The Vive controller is like a combination of a Wii wand and a Steam controller. It's cool to see the haptic touchpad from the Steam controller onto something else. You can do some cool things like split the touchpad up into four buttons. The Oculus Touch controllers are really nice. You can make several poses with your hands including closing and opening your hands and pointing your index finger. The controllers aren't ambidextrous and they feel very different from the Vive wands. Plus, they only take up about half the space when you set them down on any surface.  However, the pose they force your hand into feels unnatural and I can easily see how these would cause discomfort after extended usage.
    Winner: Tie
    Tracking
    The HTC Vive is built for room-scale tracking. You can freely walk around an area up to 15'x15' and only have to worry about tripping over your cord or running into a wall. The Rift, on the other hand, uses two forward-facing motion trackers with a much smaller 7'x7' area. The most annoying thing about the Rift is you have to keep facing the motion sensors or you will lose tracking. Some games continuously warn you to turn around and face the sensors, which really limits the kinds of games you can enjoy with the Rift. So with the Vive you just have to worry about not running into walls, but with the Rift you have a smaller area and you also have to consider your rotation in VR and in the real world while playing, which really breaks the immersion.
    Winner: HTC Vive
    Conclusion
    Both headsets are good. The Oculus Rift is a polished version of the original Oculus DK1, while the HTC Vive is a less refined headset with more advanced tracking capabilities. The Rift is $200 cheaper and is probably better for seated VR experiences, while the Vive allows room-scale games and experiences the Rift can't match with its limited tracking technology.
  12. Josh
    Leadwerks Game Engine 4.6 will feature a new peer-to-peer networking system that solves the NAT punch-through problem and provides an easy way to create a public list of game servers. Together with this and Leadwerks GUI which was released last year, we will soon have the tools to make a great deal of new types of games. Previously we have been focused on single-player games but the ability to create multiplayer games with fast reliable networking opens up a lot of new and fun possibilities.
    In many ways, multiplayer games are actually easier to make than single-player games. Most of your gameplay tends to arise through the interaction of the players, so making things fun centers around creating environmental challenges. Coding-wise, multiplayer games tend to use a small core program and don't need a lot of extensive per-object scripting.
    Since we have the tools to do it, there are two community projects I want to carry out this year. By "community project" I mean I am going to write the code, put it out there, and anyone who wants to join in is welcome.
    Gears Arena
    This is simply going to be a modern version of Quake 3 Arena, with the option to play in VR. It can serve as a basis for many different types of games, and a lot of fun can be had with level design alone. New game modes like CTF can be added, or new weapons. I fully intend to make a gun that shoots chickens that then attack your enemies, a well as a grenade launcher that shoots grenades that split into more grenades. It will be all the fun of modding without having to rip out a lot of pre-existing code from the base game.

    Gears Rally
    The code from the previous project can be easily reconfigured into a multiplayer racing game. Maybe we can even add weapons.

    Now that the base systems of networking and the GUI are nearly in place, both these projects will be achievable in a very small amount of code, and can serve as a basis of derivative games.
  13. Josh
    Being able to communicate with a gaming headset or microphone is an important part of fast-paced multiplayer gaming. Therefore, Leadwerks 4.6 will feature easy to use voice recording features that allow you to talk to your teammates or taunt your opponents, in addition to a new peer-to-peer networking system.
    The system is largely automated so that you only have to call a single command:
    Voice::SetRecording(true) Or in Leadwerks 5:
    SetVoiceRecording(true) In practical usage, you would just link this to a key press like this:
    SetVoiceRecording( window->GetKeyDown(KEY_V) ) To prevent opponents from hearing your team's communication you can disable voice broadcast to some users:
    Voice::SetMemberBroadcast(const uint64 steamid, const bool state) I'm not too sure about the naming of that last function, but you get the idea.
    That's really all there is too it. The engine will handle recording, compression, and broadcast of your voice so you can easily talk to other players in any Steam-based multiplayer game. When the other players receive the voice data it will automatically be converted into a sound and played. So you can enable voice communication in your game with just one command!
  14. Josh
    A big bug fix update just came out for 4.5. Unless there are any major problems then I am going to go back to Europe for a while. I will be focusing on multiplayer features, specifically the P2P, lobby, and voice chat features in Steamworks, as well as converting lots of models to make them ready-to-use with Leadwerks Game Engine.
    I am just going to bring my little Gigabyte Brix mini PC. If I need to I can have my big monster machines shipped to me overseas, but I'll start with this one and see how it goes.
  15. Josh
    A new build is available on the beta branch on Steam. This fixes a couple of issues.
    Oculus view projection is fixed. Added VR.AButton, VR.BButton, VR.GripAxis for compatibility with Oculus Touch controllers:
    https://www.leadwerks.com/community/topic/17052-vive-and-oculus-controls/ Fixed terrain collision bug:
    https://www.leadwerks.com/community/topic/16985-character-controller-falls-through-terrain/
  16. Josh
    An update is available on the beta branch on Steam.
    Fixed missing fall damage: https://www.leadwerks.com/community/topic/17005-i-dont-get-fall-damage/ Fixed LoadAnimation bug: https://www.leadwerks.com/community/topic/16982-modelviewer-load-animation/ May have fixed VR player script and tracking on Oculus Rift. If you have an Oculus Rift please try creating a new project from the VR template project and tell me your results. If there is still a problem I will go buy an Oculus headset, but OpenVR should make all headsets work correctly without testing each one.
  17. Josh
    I have Steam peer-to-peer networking commands implemented into Leadwerks 4.6. Here are the commands. Note this complex system has been boiled down to just three simple commands:
    class P2P { public: #ifdef LEADWERKS_5 static bool Send(uint64 steamid, const int messageid, shared_ptr<BankStream> data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, shared_ptr<Bank> data, const int channel = 0, const int flags = 0); static shared_ptr<Message> Receive(uint64 steamid, const int channel = 0); #else static bool Send(uint64 steamid, const int messageid, BankStream* data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, Bank* data, const int channel = 0, const int flags = 0); static Message* Receive(uint64 steamid, const int channel = 0); #endif static bool Send(uint64 steamid, const int messageid, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, std::string& data, const int channel = 0, const int flags = 0); static bool Send(uint64 steamid, const int messageid, const void* data, const int size, const int channel = 0, const int flags = 0); static bool Disconnect(const uint64 steamid); }; There isn't really any concept of "making a connection" here. You just start sending packets to any Steam user you want, and when you are done you can call Disconnect() to clean up some stuff. This system is completely peer-to-peer, so any player in a game can directly message any other player, although you probably want one central computer in charge of the game. NAT punch-through is supported, and in the event that it fails then messages will automatically be sent through a relay server.
    This is only part of the solution though. We still need a publicly readable list of what games are available to join.
    In a system like ENet, which is what our existing networking is built on, you have one server and multiple clients. The server can be hosted on one of the player's machines, or it can be a dedicated server. A dedicated server is a computer running the game that doesn't have a player on it. It will also skip rendering graphics so that it can relay messages between players faster. A dedicated server is always on, but these take a lot of extra work to develop and maintain, and if the dedicated server ever goes down the game is unplayable.
    A game hosted on one of the player's machines is nice because you don't have to set up a dedicated server for your game. Players themselves can create a new game with whatever settings they want, and other players can join them. However, when the player that created the game leaves, the game is terminated.
    Lobbies
    The Steam networking API supports a feature called lobbies. In games like Left 4 Dead, players will congregate in a lobby until it is full. A dedicated server is selected. The lobby is destroyed and the game moves over to a dedicated server to play. Lobbies can be selected by a player either through automatic matchmaking (I'm not a fan of this) or a list of lobbies can be retrieved.

    The name "lobby" is a little misleading because that is only one possible way this feature can be used. There is no reason a lobby can't be left open while the game is playing, in which case they function more like a regular public game server list. One interesting feature is that if the lobby creator leaves the game, a new lobby leader is automatically selected and the lobby lives on. This means that one user can create a game, other players can join it, and the original creator can leave the game without it shutting down. The new lobby leader can be used as the server that synchronizes the game state between all players, creating a sort of "floating server" that only disappears when all players leave the game.
    This is a huge improvement over both dedicated and hosted servers. A floating server isn't tied to a single machine, whether that be one player's computer or a dedicated server that has to be maintained constantly. This is the best solution to quickly get your multiplayer game up and running, and it will be available in Leadwerks Game Engine 4.6.
  18. Josh
    Here is the second iteration of our VR robotic arm concept. This can be used for the safe planning of robotic motions and early detection of potential problems. VR gives us intuitive manual control with six degrees of freedom along with stereoscopic vision for superior spatial awareness.
     
  19. Josh
    I spent a few days setting up a server application and attempting to implement NAT punch through. You can read the details of my adventures here.
    Here's what I think:
    I can probably get it working. There's a lot of weird hardware and configurations out there I can't realistically test. There will always be a certain failure rate which translates into angry users who I can't help. So I started to think about this backwards, by first asking what we want the user experience to be. Here's what we want:
    Fast and reliable P2P networking. Public listing of game servers. In-game voice communication. And we have three possible ways to get there:
    NAT punch-through with Leadwerks master server. Packet relay with Leadwerks master server. Steam networking. What method gives us the best user experience? Steam networking will ultimately produce the lowest failure rate with the P2P communication.
    What method is the easiest to implement? Steam networking already has the features we want working right now.
    The only downside is that it won't function outside of Steam. If you are making a fast-paced multiplayer game Steam would be your only option for publishing. If you are building an application that cannot use Steam, you cannot use this for networking. I believe anyone can utilize Steam networking during testing using the "Spacewar" test app, and then when you get your own Steam app ID you can move over to that.
    Considering these options, I think Steam networking is the right choice and I am going to proceed to implement this in Leadwerks 4.6. Accepting the limitation of Steam-only distribution for this is the way to move forward. We already are harnessing a lot of Steam features and it would be stupid not to take advantage of the most important one.
    With any luck, we'll all be fragging and chatting online within six weeks.

  20. Josh
    Based on a little observation, I've made the following changes to the publish routine on the beta branch:
    The FPS Player script has been updated so that the footstep sound files are explicitly named, so they don't get missed in the publish step. You will need to update your project to get the new script (or just copy FPSPlayer.lua to your project).
    There is now a checkbox to "Only include used files". If this is checked, then the new packing routine will be used, otherwise the old behavior of including all files is used.
    The file extension filter will now be used so you can include files like XML, TXT, or whatever else you want.
    The routine has been fixed to pick up terrain textures and include those.
    A few people suggested a "tagging" system so the user can mark files to be included or not. This is a very bad idea. The whole point of the selective file inclusion system is that the user doesn't want to maintain a clean directory. So adding a manual system to overcome the limitations of the automatic system...it would be easier just to delete the unwanted files. When your feature requests lead to more feature requests, to fix the problems of the previous feature, that is a strong sign of bad design. So we won't be going down that path.
    If you are using the selective file inclusion and you want to force a file to be included that would otherwise be impossible to detect, an easy way to do this is to just add a commented-out line in your main script that names the file:
    --myfile.tex Anyways, the new build is available now on the beta branch. Let me know how you like it.
  21. Josh
    This is a rough timeline of events I am planning in the new year. It is not complete, and may change, but this is just what I am thinking right now.
    January 15: Release Leadwerks 5 Alpha Zero subscription February 1: Launch Kickstarter campaign for development of Leadwerks 5 Alpha One February 1: Game Tournament March 3: Kickstarter campaign complete April 1: Leadwerks 4.6 released (vehicles, NAT punch-through) June 30: Summer Games Tournament June 30: Roll out Steam Workshop replacement August 1: Deliver Leadwerks 5 Alpha One One thing I really want to see is a multiplayer community project. Multiplayer games allow for much more interesting gameplay because your opponents are smart. It's easy to make fun variations of different game types. And it's easy to make contributions like a new map, a new weapon, or a new game type (as opposed to creating a whole new game).
    This is very fun for me because I actually got my start in computer software by making mods for the original Quake. You can still download "Checkered Flag - Gold Cup" mentioned here, by "Lucid Designs". I was not a programmer on this, I was a level designer. I don't see any of my maps in it though so I don't know if that exists anywhere:
    http://www.quaketerminus.com/addon.shtml

    IGN took down our old page for the mod. This is one of the reasons I want everyone to upload their games to the Games Showcase, so they will be preserved forever.
    To make networking work I am working on a NAT punch-through system right now.
  22. Josh
    It's been an eventful year.
    A new beautiful website has been rolled out, and our community software is updated to the latest version of Invision Power Board, which allowed us to move all file storage to Amazon S3 for basically free unlimited storage space for the site. Documentation moved into a new easy to use system. The Games Showcase was created. We had a totally kick-*** Halloween Game Tournament. Announced the development of Leadwerks 5, added unicode support, 64-bit build, and smart pointers, and am almost ready to release the first alpha. Finally release The Zone with the help of @AggrorJorn Released LeadwerksGUI Released the Steamless Enterprise Edition, which NASA bought some licenses for. And of course now you can make VR games with all versions of Leadwerks. Here's where I messed up:
    Taking a year to ship out the winter game tournament posters. The process for international shipping has actually gotten a lot easier as you can fill out all the information and print a label online now, which is a lot easier. I'm not going to say anything about a new tournament this year until a couple weeks have passed so people can actually get their goodies from the last one! Behind the scenes I have been working on a big super-secret project (not Leadwerks 5), but I don't know 100% if this is going to go through or not, so at this time I can't say anything about it.
    With everything that's going on I feel like we are in an in-between state right now, between the last big thing and the next. The rollout of Leadwerks 3/4 taught me a lot of things I can use for 5. But for now, more waiting is required before I decide my next move and how I am going to go about this.
    By the way, Merry Christmas everyone!
  23. Josh
    Explore our reimagining of the Chernobyl nuclear exclusion zone with The Zone asset pack.  This package contains over three gigabytes of high-quality game assets prepared to take advantage of the latest Leadwerks features.  Use our ready-made map (included) to start your game or create your own post-apocalyptic environment.
    Get it now on Steam with a discount during launch week.


    "The Zone" DLC includes the following assets:
    24 terrain textures 11 buildings (plus 2 background buildings) 4 types of bridges 2 types of fences 6 crates 3 cargo containers 12 signs 13 rocks 7 gravestones 8 plants 39 junk and debris models 20 furniture models Barriers Railway tracks Diesel locomotive and boxcar 2 skyboxes And much more... TECHNICAL SPECIFICATIONS
    Polygon Count: 30-46985
    Textures: Diffuse, Normal, Specular
    Texture Resolution: 64x64, 128x128, 256x256, 512x512, 1024x1024, 2048x2048
    Collision Shapes: Yes
    Source Files: FBX, MAX, PSD, JPG, PNG, BMP



     
  24. Josh
    The next step is to put our scene into VR and look at everything. I'm about six feet tall and the player model seems the same height, so that all looks right. The controllers, as you can see below, are all working with full articulation of all controls. The teleport indicator looks good too.
    Now we're going to start our VR player script. We start with a new script and initialize the VR environment in the Start() function. A camera is also created in code, and I deleted the camera from the scene.
    function Script:Start() if VR:Enable()~=true then Print("Error: Failed to initialize VR environment.") end self.camera = Camera:Create() end I wanted to look at the controller models and see if there was a child that indicated where the tip of the controller was, for aiming a beam. The code below will wait until a controller model is loaded and then print out the names of all its children.
    function Script:Start() if VR:Enable()~=true then Print("Error: Failed to initialize VR environment.") end self.camera = Camera:Create() self.partslisted=false end function Script:UpdateWorld() if self.partslisted==false then local model = VR:GetControllerModel(VR.Left) if model==nil then model = VR:GetControllerModel(VR.Right) end if model~=nil then self.partslisted = true local n for n=0,model:CountChildren()-1 do local subobject = model:GetChild(n) local name = subobject:GetKeyValue("name") System:Print(name) end end end end The result is below. Three blank lines mean that three subobjects in there have no names. You can tell what some of the subobjects are, but none of them look like what I am looking for.
    body button led lgrip rgrip scroll_wheel status sys_button trackpad trackpad_scroll_cut trackpad_touch trigger None of those look like what I am looking for, so I am just going to use the controller model itself and hope for the best. The code below creates a box sticking out of the controller.
    function Script:Start() if VR:Enable()==false then Print("Error: Failed to initialize VR environment.") end self.camera = Camera:Create() end function Script:UpdateWorld() local controller = VR:GetControllerModel(VR.Right) if controller~=nil then if self.beam==nil then self.beam = Model:Box(0.05,0.05,4) end if self.beam:GetParent()~=controller then self.beam:SetPosition(0,0,0) self.beam:SetRotation(0,0,0) self.beam:SetParent(controller,false) self.beam:Move(0,0,-2) end end end As a result, we've got this funny light saber-looking thing:

    Now let's replace the box with a sprite and apply our third teleport material to it. We'll make the sprite rotate around its own Z axis to face the camera using the SetViewMode() command.
    function Script:Start() if VR:Enable()==false then Print("Error: Failed to initialize VR environment.") end self.camera = Camera:Create() end function Script:UpdateWorld() local controller = VR:GetControllerModel(VR.Right) if controller~=nil then if self.beam==nil then self.beam = Sprite:Create() self.beam:SetViewMode(6) self.beam:SetSize(0.05,4) self.beam:SetColor(1,2,2) local mtl = Material:Load("Models/VR/teleport3.mat") if mtl~=nil then self.beam:SetMaterial(mtl) mtl:Release() mtl = nil end end if self.beam:GetParent()~=controller then self.beam:SetPosition(0,0,0) self.beam:SetRotation(0,0,0) self.beam:SetParent(controller,false) self.beam:Move(0,0,-2) end end end Now it really looks like a light saber!

    The next step is to add picking so we can use the beam to place the teleporter. I deleted the teleport indicator prefab from the map and loaded a copy of the prefab up in the Start() function:
    function Script:Start() if VR:Enable()==false then Print("Error: Failed to initialize VR environment.") end --Create the player camera self.camera = Camera:Create() --Load the teleport indicator prefab self.teleportindicator = Prefab:Load("Models/VR/teleport.pfb") self.teleportindicator:Hide() end function Script:UpdateWorld() local controller = VR:GetControllerModel(VR.Right) if controller~=nil then --Create the teleporter beam if self.beam==nil then self.beam = Sprite:Create() self.beam:SetViewMode(6) self.beam:SetSize(0.05,4) self.beam:SetColor(1,2,2) local mtl = Material:Load("Models/VR/teleport3.mat") if mtl~=nil then self.beam:SetMaterial(mtl) mtl:Release() mtl = nil end end --Reparent the beam, if needed if self.beam:GetParent()~=controller then self.beam:SetPosition(0,0,0) self.beam:SetRotation(0,0,0) self.beam:SetParent(controller,false) self.beam:Move(0,0,-2) end if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==true then local world = self.entity.world local pickinfo = PickInfo() local p0 = controller:GetPosition(true) local p1 = Transform:Point(0,0,-4,controller,nil) if world:Pick(p0, p1, pickinfo, 0, true)==true then self.teleportindicator:SetPosition(pickinfo.position) self.teleportindicator:Translate(0,0.05,0) self.teleportindicator:Show() end end end end We can move the teleporter indicator around. It climbs straight up walls and there are lots of problems, but you can see the beginnings of a teleporter:
    The next thing we want to do is add a normal check to prevent the teleporter from working if the picked slope is too high. You can get the slope in degrees of any normal with the following bit of code:
    local slope = 90 - Math:ASin(pickinfo.normal.y) We're also going to hide the indicator when a valid teleportation destination isn't picked. Here's the complete code:
    function Script:Start() if VR:Enable()==false then Print("Error: Failed to initialize VR environment.") end --Create the player camera self.camera = Camera:Create() --Create teleporter beam self.beam = Sprite:Create() self.beam:SetViewMode(6) self.beam:SetSize(0.05,20) self.beam:SetColor(1,2,2) local mtl = Material:Load("Models/VR/teleport3.mat") if mtl~=nil then self.beam:SetMaterial(mtl) mtl:Release() mtl = nil end self.beam:Hide() --Load the teleport indicator prefab self.teleportindicator = Prefab:Load("Models/VR/teleport.pfb") self.teleportindicator:Hide() end function Script:UpdateWorld() self.teleportindicator:Hide() self.beam:Hide() local controller = VR:GetControllerModel(VR.Right) if controller~=nil then --Reparent the beam, if needed if self.beam:GetParent()~=controller then self.beam:SetPosition(0,0,0) self.beam:SetRotation(0,0,0) self.beam:SetParent(controller,false) self.beam:Move(0,0,-10) end --Activate teleporter if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==true then local world = self.entity.world local pickinfo = PickInfo() local p0 = controller:GetPosition(true) local p1 = Transform:Point(0,0,-20,controller,nil) if world:Pick(p0, p1, pickinfo, 0, true)==true then local slope = 90 - Math:ASin(pickinfo.normal.y) if slope<35 then self.teleportindicator:SetPosition(pickinfo.position) self.teleportindicator:Translate(0,0.05,0) self.teleportindicator:Show() self.beam:Show() end end end end end And now we will add the actual teleport mechanic. We take the picked position, subtract the camera's current XZ position, and add to the existing VR offset. This took a little trial and error, but it works perfectly:
    --Check if teleporter is active and the button was released if self.beam:Hidden()==false then if VR:GetControllerButtonDown(VR.Right,VR.TouchpadButton)==false then local offset = VR:GetOffset() local pos = self.teleportindicator:GetPosition() local campos = self.camera:GetPosition(true) pos.x = pos.x - campos.x + offset.x pos.y = pos.y - 0.05 pos.z = pos.z - campos.z + offset.z VR:SetOffset(pos) end end Here it is in action. We can not yet teleport up to the highest blocks, but we can freely move around the map.
    Next we will make the beam arc so we can climb up to platforms above us.
  25. Josh
    Version 4.5  is updated on the beta branch on Steam.
    Fixed small projection error in VR mode. Added fog settings in editor and into map file format. Fixed Workshop browser back button not working. Added VR project template. Added VR player script Added friction to hinge joint script. Added ball and slider joint scripts. Joint scripts will now use the entity parent for the other joint connection (if it exists). We've only got about 24 hours to test this out before the release and there seems to be some problems compiling on Linux, at least for some people. I will be working hard to get this resolved for the big release. Thanks for your help!
×
×
  • Create New...