OPTIMIZED Lighting Controller for GameMaker -- Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
two days dad a to make Kiki hello Joe welcome to another game maker tutorial my name is Alex let's get started today we are going to be doing a lighting controller now if you are familiar with surfaces and their basics at all then this should be a pretty straightforward tutorial for you if not I definitely recommend you go check out my other tutorial on how to create the minute cave effect and that's a very good introduction into surfaces and different functions we can do with surfaces and how we can use them to create cool visual effects and this is going to essentially basically be a part two to that video where we show how we can optimize this a little bit better and kind of bring this all into one object rather than having our logic kind of spread out and a bunch of different objects the way we did in that video so as you can see here we have an example of what the game is going to look like now I'm just using a I'm just using an existing project that I'm working on but you can see the lighting effect here is actually what we're gonna be working on and not only just how to create the lighting effect but how we can optimize it and just get it really nice neat and organized and we're gonna go ahead and start from scratch so I'm gonna have like I said I'm using kind of a base framework project to build this on top of but we're gonna do the whole lighting system from scratch that way you guys can see everything that it is that I go through to do to make this work so I'm gonna go ahead and actually inside of our controller objects I'm gonna go ahead and just delete this lighting controller so that we can start from the beginning together so the first thing we want to do actually is to create our lighting controller now I know I just deleted it but again just so we can work on this together and create it from scratch so let's go ahead and create an object and I'm gonna name this object obj lighting controller all right and the lighting controller is pretty much just how we're gonna set up our surfaces and where we're gonna draw our initial shade our shadow where we're gonna then subtract our lights from to get that effect so first thing we want to do is let's go ahead and add a create events and inside this create event let's go ahead and just define and create our surface on this a surface equals a surface creates and we're gonna just set this equal to the room width and the room height now the reason we're doing this is because we're creating kind of these canopy lights that we saw at the beginning that kind of poke through the forest stop and so these lights are kind of the whole room the whole ambience of the forest level that we're in so this surface is just gonna be the size of our room whenever we create any sort of data structure any sort of object anything that's gonna take up any space in memory the first thing we always want to do is immediately destroy that object so whereas a surface free and we're gonna remove our surface so in any situation in which our lighting controller is destroyed we don't have the surface floating around in memory we have successfully managed our garbage so let's go ahead and create our draw event and this is where all the juice is gonna happen where we're gonna do 90% of our logic and what we're gonna do is we're just gonna set up our initial shadow and then we're gonna go through add little features to it and then optimize it and you'll see how we can kind of bring all of our code into this one lighting controller and it really affords us a lot of flexibility a lot of optimization and a lot of simplicity because then you don't have to start chasing around different objects when you want to create a new effect so the first thing I'm going to do is I'm just gonna go ahead and draw our surface and we're just going to draw like a nice kind of just dark grey shadow over a whole surface just to give us a dark effect so we want to say if surface does not exist our surface we want to create it so the reason we do this is because surfaces are volatile and so if you ever you'll notice this that if you've ever worked or surfaces before when you toggle between fullscreen if you have any surfaces that are kind of residual on the screen they get erased when your toggle full-screen and that's because any sort of resizing of our our display and and stuff like that really kind of breaks the surface it's I don't know all the details behind it but it's just what we kind of would classify as a volatile data structure and so because of that we want to make sure that we always are checking for its existence before we modify it otherwise it could get destroyed at any point and we're gonna get some runtime errors so we've successfully said surface equals surface create if it does not exist already and now we just want to set the target so we're gonna say surface set target surface and all this is gonna look really familiar if we did our last tutorial of the minute cave effect but I'm going to show you how we're gonna change this up in just a moment and so whenever we set the service we want to say surface reset target and now we're gonna draw everything that we want in between these two lines of code these are going to be the main there's going to be everything that we're gonna be drawing onto the surface so I'm just gonna go ahead and clear the surface right away that's always a good practice and I'm gonna say we're gonna clear alpha with C black and an alpha of just zero and now what we're gonna do so we've successfully wiped away our surface nice and clean and now let's go ahead and draw just a shadow over the whole screen so I say draw a set color see black draw a set alpha I'm gonna set this to something large just so we see it right away and we aren't sure we aren't wondering oh hey did this effect work we're gonna just set a really drastic effect so we see it happening as soon as we see the screen load so then what we're gonna do is we're gonna reset all of these values back to their defaults after we're done using them so that these residual effects of our color and our alpha don't affect other draw events so we always want to reset them after we're done using them and now we've set it so let's go ahead and just draw a simple rectangle over our whole screen so we're gonna say zero-zero room with real heights perfect so now whoa and we have to also define if we want to give an outline we're gonna say zero outline or false and this will give us a filled in rectangle all right so now we've successfully set the target we've cleared the surface we've drawn to the surface and we've reset our target back to our application surface so now on our application surface what we want to say is draw surface and because the surface is the size of the room we can just set it to zero zero all right so if I go ahead and hit play you're gonna notice that we actually don't get anything drawn to the screen now there's a reason why this happened and part of the reason why I wanted to make this video is to video is to kind of show this bug that I encountered while trying to create this and how I figured out how to fix it I actually saw a reddit post aw I can't take credit for it but it took me a little bit of digging so I wanted to share this with you guys so one of the reasons why we're not seeing our surface even though we've done everything correctly is because we are using a three-dimensional camera in this game so if I go ahead and open up my camera object you'll see that I'm setting up a view and projection matrix so this is not always done in every project sometimes you might have a very simple view or camera setup where you aren't using Gamemakers 3d cameras however if you are using a 3d camera in your game you need to you need to basically force the camera to update whenever you are drawing this surface I'm not sure the reason for it I don't know why it happens but we have to do it so all we have to do after we have reset the target and before we draw the surface we want to call a couple of functions to just update our three-dimensional camera so the first thing I'm going to do is I'm going to say if instance exists obj camera now again if you aren't using a three-dimensional camera you can go ahead and skip at this part but I just want to cover this real quick so we're gonna say camera sets view matrix and the reason we do this is because in our create event of our camera we have the view matrix getting set this way I will link to a video in the description of how to create a basic 3d camera like this and that way if you would like to implement it as well you can follow along a little more closely in this video okay so the first thing we want to do is obj camera so we're gonna set the view matrix what's the camera we're gonna assign it to we're gonna say OPG camera camera or say obj camera dot view matrix okay so now what we've done is we've just updated our view matrix now let's go ahead and update the projection matrix we're just gonna call the same function here and now we need to apply this so RIS a camera apply obj camera camera so we're just we're a setting all of our settings to our camera instantaneously so if we don't do this it gets set next frame but then we've already tried to draw our surface next frame and we get in this endless loop of it never actually gets fully updated and our surface never gets the scene so by doing this and plugging this in here we are simply updating our view matrix updating our projection matrix applying those settings immediately and then now we're gonna draw the surface afterwards so if I go ahead and hit play perfect all right so now we have a nice dark shadow drawn all over our room let's go ahead and poke some lights through there so I'm gonna come in and I'm gonna create a light object I'm gonna say obj can't be light and I have this sprite created here which is just a nice long gradient rectangle of a white light now you can do this with any light effect that you would like you can draw these sprites dynamically but for this example we just have this very simple faded rectangle drum so I'm gonna go ahead and in our canopy light I'm gonna draw that and drag that sprite right in there and in our main room we are going to create a new little object layer and rename this I'm just gonna call this lights okay cool so let's go ahead and drag some of these lights in here so I'm just gonna drag one and we're gonna kind of rotate it and just kind of copy and paste a few of these in here just to get a nice lighting effect so if I go ahead and run the project now we should see these lights in here but we're gonna see immediately that they are part of the shadow that's kind of getting covered right so they are there in the room they look okay right like we theoretically could just leave it like this we could even take out the this dark gray shadow that we've drawn over it and these lights would look okay but we're gonna kind of take them up a little bit more and instead of just drawing them here what we want to do is subtract them from that surface now normally if you followed our last video that we did on the minute cave effect I would have gone into my draw event here and then I would do a couple things like this let's just go ahead and do it and I'll demonstrate why this is not a good idea so we're gonna go ahead and just draw to it so let's copy and paste this code because we're gonna borrow a lot of it and we're gonna delete all this right here now the reason I'm deleting this sorry so we're gonna delete this shadow because we don't want to draw the shadow and the reason we're going to delete this is because we don't want the lights to clear the surface every frame we're gonna leave the light controller to do that so now what we've done is we're going to say oh we need to associate this to obj lighting controller and we're gonna go ahead and pop these all right here and we also don't want to draw the surface because the lighting controller is already drawing the surface and we don't need to update the camera because the lighting controller is already updating the camera so what we are doing here is we're basically just saying hey this light here look for the lighting controller get its surface and then we're going to draw to that surface and that's just this surface up here that we've created so this is kind of traditionally how we would do it we would come in here and we'd say GPU set blend mode BM subtract right and then we would say draw self and I mean theoretically that's all we have to do so if we run it we should see what we did in our last minute tutorial where we just kind of had the objects themselves drop to the surface but wait a minute okay now where's our lights now this is the problem with doing it like this is what happens is we start to split up our draw events all over our different objects our canopy is drawing to the surface our lighting controller is drawing to the surface let's say we implement five new lights right each light individually draws itself to the surface what you start to run into is you start to run into issues with draw order not going the way you want it to so maybe this you know maybe the lighting controller draws first and the canopy gets drawn second but maybe one of the other lights you put in gets drawn before the obj light controller right so you get a lot of ordering issues and it's really hard to manually control that order of events when all the objects are kind of split up not only that it gives us less control because it gives us less control over kind of the cohesiveness of the lights interacting together the lights interacting with the lighting controller so this gets kind of problematic where we just get this big jumble of code it becomes messy and it doesn't even work so how are we gonna fix this well what we're gonna do is we're gonna take all of the drawing logic out of every light that we create and we're gonna move it into our lighting controller the name of our object obj lighting controller suggests that it is a controller for all of our lights it's not a shade controller it's not a dark forest to you effect controller it's designed to be larger than that to control more than that and so what we're gonna do is we're gonna utilize this object that we've created obj lighting controller by moving all of our draw logic for our lights into this controller object so now what does that mean we're gonna do we are gonna delete everything from this draw event and but we're gonna keep the draw event here and we're gonna keep it empty because remember if we remove this draw event then game maker will see that it doesn't have a draw event and it will give it the default draw event however if we leave it here but leave it empty this will force the object to not draw anything so we're gonna leave it this way because we don't want the light to draw itself we want the surface control or the lighting controller to draw the lights now we use objects because this allows us to then come in here into the room editor and drag our lights around and get the look and feel that we want so we want to keep the objects and not destroy the objects either because theoretically we could just come in here and draw sprites without this object but by having these objects if it gives us more customization in our room and we're also gonna get the integration of this surface that we've created and optimize it so how do we do that you ask all right well let's do it so we've let's go and have some comments here we're gonna clear so this is clearing our surface here we have drawn our shadow draw we'll just call this forest shadow so now what we can do is we can start to draw our other things in here and this allows us to totally circumvent that issue I mentioned earlier of not knowing the draw order or how to customize it because we are explicitly calling the draw events in the order that we want so we know that first we're gonna draw the shadows next we're gonna draw the canopy lights so right a cough drop canopy lights and I'm gonna paste in the code that we had before and I'm just gonna kind of delete some stuff so I'm gonna say GPU set blend mode draw self and we don't need to reset this target so we just need to say with obj canopy lights and now we're gonna wrap all of this code inside of the context of the canopy light that we've just defined and if we want to be more specific this instance exists so we say obj canopy light before we try to operate on it let's make sure that it does exist always a good idea okay so now we're saying if that existence exists let's move our context into it now we're inside the camp you light let's set the blend mode of the GPU which is a global property we're gonna draw our self which is the canopy light and then we're gonna reset the blend mode so now let's go ahead and run it huh and here we go so now we see that that's very similar to our last effect but the lights do pop a little bit more and instead of being an additive draw effect on our shadow that we had before we've now created a subtractive draw effect but we're gonna take this one step further and I'm gonna show you how we can optimize it even more one thing we can think about is so the way that the application surface works is that it constantly draws and clears and draws and clears every frame and this generates this sense of movement because we don't see the last frames hanging onto the surface because they get cleared every frame hopefully this makes sense the reason I bring this up is because our lights are not dynamic at least currently they're not dynamic so we don't need to draw the light surface we don't need to draw the lights to the light surface every frame because we're essentially creating a stamp right and then we draw the stamp on the screen okay well we don't need to create the stamp every frame we need to create the stamp once and then draw the stamp on the screen every frame so what does that mean well it just means we need to draw these lights to our surface once to create the surface and then we can draw the surface every frame this essentially reduces all of our draw calls it means that the lights only get drawn once and then we just draw a new sprite our surface so if you have a thousand lights in your room well that thousand lights draw call is only gonna happen once onto the surface and then we're going to be drawing just the surface every frame because the lights are static the lights are not changing at any point now this obviously changes a little bit if we have dynamic lights so I'll show you how we can do that in just a second but I'm gonna show you how we can mmm we can maximize our performance with this static light system we've created by reducing the number of draw calls and it's very simple to do all we have to do is in our create event we're gonna create a variable called updates lighting or actually we'll say update surface and we're gonna set this true to create because we want the surface to be updated on the first frame and then after that we're gonna turn it off and all we're gonna do is we're gonna say if update surface so if we want to update our surface we are going to do all of this code and so that means all we're gonna do is draw this code to our surface one time and then we're gonna just draw our surface every frame so what we want to do is at the bottom of it we're gonna say updates surface equals a false so we come into this block of code we say is update surface equal to true it is let's draw everything to our surface and then let's update surface to false that way next time we come into this draw frame we don't actually execute this code we only execute this code because there are so few lights in this project you're really not going to see a big performance improvement in our numbers but if you start to implement a hundred thousand lights of these static lights doing it like this is going to show is gonna save you a ton of resources at the end of the day and you can see that everything looks fine everything looks great right nothing is broken and we've you know we've got this nice-looking this light nice looking light effect now let's say you do have dynamic effects we can create a very simple script to update this surface and allow us to implement dynamic lights so in order to do that we're going to come in here and we're gonna create a new script and we're gonna call it updates my fingers are offset update lighting surface and we're just gonna run a couple checks to make sure everything exists we're gonna say if our lighting control if our lighting controller does not exist we're gonna exit out of this and now we can just go ahead and copy some of our pic copy and paste some of our code here right if our surface doesn't exist let's go ahead and create the surface and but remember this context is to that of the lighting controller so let's go ahead and drop that there so now we've scoped our variables to the lighting controller and we're gonna create the surface there and all we have to do now is just say obj lighting controller dot update surface which is the variable we just created equals true so if we ever call update lighting surface all that's gonna do is just set this variable equal to true and then when we come back into our draw event we will have a suit we will go through and draw all of our lights to the surface again first we'll clear the surface so we aren't doubling up but then we'll draw all of our lights to the surface again and it will then turn itself off and it will just wait for us to call update lighting surface again in the future and then at that point so if you have a light that flickers well every time you turn off that light just call update lighting surface if you have or if you have a light that's gonna turn off or rotate you can just call update lighting surface now keep in mind if you have a thousand lights and you want to update one light we're gonna want to add some more functionality to this so that we're only updating one light instead of a thousand lights just to change the visual effect of one light but we can get to that in just a second let's go ahead and make this a little more dynamic rather than hard coding this let's say you had a hundred different lights you wanted to draw to your surface and subtract from your surface well doing this manually wouldn't be that great so let's come into our create event and let's it create an array we're gonna call this our lights a nice and original name and we're gonna say obj canopy light is gonna be our only light in here but if we wanted to we could insert you know telephone booth I don't know why this was the first thing I thought of lamppost obj bus headlights I don't know I'm just coming up with random things but the point is that if you had a whole bunch of lights that you wanted to have be part of this surface light controller then we could just drop them all into an array and inside of our draw event instead of hard-coding it like this we're gonna say for fair I equals 0 I is less than array length 1d and we're gonna say lights and now what we're gonna do is we're going to just say light object equals lights at index I and now we're gonna copy this code here and instead of checking for our canopy light we're gonna check for our light object we're gonna move our scoping into our light object and then we're gonna do all of our drawing so gonna say draw all of our lights so now if we run it this will dynamically go through all of our lights and draw them to our surface now we only have one light object so it's not that exciting but let's say we wanted to do something I don't know totally weird we can just say obj player is gonna now be a light this probably I don't know if this is actually gonna work we'll see what happens hey and there we go lookit we kind of drew our player as a light source to the surface there and again because we aren't updating the surface this only gets kind of stamped onto our surface like one time and then it just is diastatic but I could very easily come in here we're just gonna demonstrate this I'm going to say if keyboard check pressed and I don't know I'm just gonna tie it to something PE that sounds good I'm gonna say updates lighting surface so now let's run it again and just take a look at what how we can implement this dynamic update we've created so I'm running oh there it is I hit P now I've reset my surface but that's actually a really cool effect that I did not intend on making in this video but it looks really cool when I press it really fast okay so here's I think that this is a kind of a good example of what I was talking about where it just allows us to put anything into this array and we immediately get full dynamic control over it we don't have to sit here and copy and paste this code saves us a ton of time and it looks neater it's way cleaner and who doesn't love clean code am i right okay so let's go ahead and try to implement the one thing I mentioned earlier where I said okay let's say you have a thousand objects in here times 1,000 old 10,000 light objects let's say you have 10,000 light objects in this array and you want to update one of them okay so what we're gonna do is we're gonna come into this update lighting surface and we're gonna add a couple parameters so we're gonna say at per am I'm going to say object index and when I say pram clear surface and I'm gonna put a question mark just to indicate that this is a true or false question so now I'm going to say they're object equals argument zero and clear equals argument one so now we've come in here and we've updated the instead of just calling obj light controller updates surface we're gonna also add a couple other variables and we're gonna change some of the logic that we created a little bit so I'm gonna say obj lighting controller dot update the surface object now this is not a variable we've created yet but we're gonna create it right now so I'm gonna come up to my lighting controller I'm gonna say update surface object equals undefined and we're also going to copy this line update surface and we're going to call this clear and we're gonna set this equal to false for now just on default so now when we come into here we're gonna add some conditionals under these a little bit so we're gonna say clear surface if update surface clear so now we're only clear if this is set to true and we're gonna clear the surface and then set it back equal to false now what we're gonna do is we're also going to check if update surface object is not equal to undefined that means we've specified a very specific object that we want to update and not just update all of them we're gonna just do that so here instead of drawing all of our lights we're first gonna check we're gonna say if update surface object and just by setting it if this this is the same thing as saying not equal to undefined so I'm just gonna say if there is some value associated to this we're gonna update just update just that one object else if there isn't well if we don't have any very specific object specified then we're gonna update everything but if we do we want to just update that one so we're gonna copy this and we're gonna call our code right here on this now again we are starting to kind of block ourselves in a little bit because then you would have to copy and paste this every single time so let's make our code a little more dynamic let's go ahead and open up this and instead of this being an object index we're gonna say this is now an object index array so now you can pass in an array of objects so let's rename this objects because that makes a little bit more sense and now we're gonna pass pass in an object array update surface objects let's change the name of this too to be also objects indicating plurality okay so now we're going to say if update service objects and array length 1d update surface objects is greater than zero so we have some so we have some value there and it is larger than zero now what we're gonna do is we're gonna do the same thing we did here I'm just gonna copy and paste that here but instead of getting from our our light controller lights array we're gonna get from our objects our update surface objects array and now this should do the same thing update specific objects so now let's say you want to update only five lights and a scene all you would have to do is let's go ahead and make our player another light just for the sake of demonstrating obj player and I don't really have anything else in the room that's exciting so we'll just leave that as is so now what we could do is when I hit P here well we're looking for a couple parameters we're gonna first come to our script again making it more dynamic I'm kind of all over the place guys is you can tell I only really planned out the first half the video the second half of the video I'm just kind of totally going off off the cuff right now and it's getting pretty wild but let's go ahead and make these optional parameters so now the player doesn't even the developer doesn't have to add these if they don't want to you have to add one or both but if the player adds men in there we're gonna kind of set some default values so in order to do that we're going to say if argument count and if this doesn't make sense don't worry about it this is kind of just extra fluff to make your code a little bit nicer a little more dynamic and a little more scalable I'm gonna be making a video on this specifically in the future but if the object so if there is at least one arguments we're gonna set objects equal to our first argument if not we're gonna set it equal to undefined and if argument count is greater than or equal to two we're gonna set clear equal to that parameter and if not I think we want to default we're gonna default clear to true yeah and I hate the way this looks always so I'm gonna wrap this in a region and I'm gonna call this arguments that way I can collapse that region and it's gonna look it nice and pretty so okay so now this call right here I close this out this call right here with no parameters is totally valuable valid and we can call this and it will default it to no specific objects and we won't clear the surface or we will clear the surface so this will just do everything that we had done up to this point already the same way nothing has changed so here I press P we update all of our lights lighting objects just the same way we would have in the past but now if I want to I can say we're gonna pass in an array here and I could just say obj canopy lights light and I'm gonna say oh let's actually this was actually be cooler where's the obj player because that is our light and we're gonna say well it's not clear the surface when we update it so we're just going to update the surface with our obj player lights and but we're gonna keep the old surface there so if I press ok so to fix that bug game maker didn't like that I had just update if update surface objects because it is an array it doesn't like that a value type so we just have to make sure if update surface objects is not equal to undefined and we have some items in there so now if I run the game and I run I press P you can see that we are creating new stamps now we are losing everything beforehand because every time we call update surface and we aren't clearing it we also redraw our shadow our shade like our canopy shade effect on there so if we wanted to we could come into our update surface we could create another we could say update shadow and that's gonna be some questionable optional value and we're gonna update our argument count collection I'm going to say if argument count is greater than equal to 3 and we're gonna create a new variable we're gonna say update surface shadow and we're gonna set that equal to false for now now if the player doesn't enter a parameter we're gonna default to updating shadow yes yeah so we're going to say if update surface shadow and now if we go ahead and we need to update our step event where we call that so when we update lighting surface for that just for the sake of demonstrating the dumb things we can do we're gonna say don't update that shadow when we call update surface so now we're just going to update the player objects light that we've created and so if we what oh what are we doing oh we have to set that true by default so yeah we actually want these to be true on creation that way it actually gets executed at least once to set up our scene and then then we can erase it later okay so cool we've done this and now you can see we can stamp our player all the way through here and this actually is a really cool effect that I don't even know what we would call this effect but I like it slow motion stamping we can do that too so yeah I think that's everything for this video I went way way overboard I got super distracted so anyway guys thank you so much I hope you enjoyed the video I hope you found it useful I know that I tend to be very distracted and code and what seems like very kind of tedious ways you look at all this and you say Alex why would I do this I have to type so much more code you're doing all this extra stuff I could just hard code this it's so much easier but when you look at this from a long-term big-picture perspective these types of functions that we're creating these type of code that we're creating being highly optimized and highly organized and well designed is designed for you as a developer to make your life easier at the end of the day now instead of having to go ahead and copy and paste and hard code all this stuff I can just tweak it few values here add a few variables there maybe add a new item to my list here and everything becomes so much easier to use so I hope you guys enjoyed if you liked it please like the video subscribe follow me on Twitter I am posting updates everywhere if you want to support the channel there's links in the bio I'll also link to some of the other tutorials that were great and helping me get to this point of understanding and I will see you guys in the next video you [Music]
Info
Channel: GentooGames
Views: 1,457
Rating: undefined out of 5
Keywords: gamemaker, tutorial, light, lighting, controller, surface, how to, how, to, game, make, maker, development, dev, indie, optimize, optimized, glow, lights, create, draw, draw calls, calls, forest, canopy, update, shaun spalding, shawn, shawn spalding, shaun, spalding, heartbeast, heart, beast, pixelatedpope, pixelated, pope, pixel, friendlycosmonaut, friendly, cosmonaut, studio, gamemaker studio, studio 2, gamemaker studio 2, teaching, lesson, tutoring, tutor, teach, can
Id: BTSgUvw7orI
Channel Id: undefined
Length: 38min 40sec (2320 seconds)
Published: Tue Aug 27 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.