-
Posts
7,936 -
Joined
-
Last visited
Content Type
Blogs
Forums
Store
Gallery
Videos
Downloads
Everything posted by Rick
-
how do i get a list of entities "in contact" with another?
Rick replied to Charrua's topic in Programming
Yeah I figured the box can tell what it collided with so if it's a belt it tells the belt to add it to the belts list and when it stops being that specific belt it's able to tell that belt to remove it from its list. Things in games are almost always backwards from what you'd think in real life. -
With the engine as is you probably wouldn't (I did just to setup the idea of the 2nd code snippet where it was built in but the users wouldn't have to code it as each node would get it automatically). If LE had states built into entities then it would be a more generic way of changing states for any entity and people would be used to the idea of entity states to direct the flow to the functionality they want. For a simple door you're right why bother with that. But for an object that has more states having one input vs all possible it starts to make more sense from a visual node perspective. Also it's a common interface for all entities. However instead of the current arg method it would be best to have some kind of drop down that lists all possible states that one could change too and that hard coded value from the flowgraph is passed in for that connection.
-
how do i get a list of entities "in contact" with another?
Rick replied to Charrua's topic in Programming
Maybe something like: Box.lua Script.entered = false Script.exited = false Script.hadCollision = false function Script:Start() self.collidedBelt = nil end function Script:UpdatePhysics() if self.entered then if self.hadCollision == false then if self.exited == false then self.exited = true self.entered = false -- we aren't colliding with anything so we are either at rest (maybe) or falling if self.collidedBelt ~= nil then self.collidedBelt:RemoveItem(self) self.collidedBelt = nil end end end end self.hadCollision = false end function Script:Collision(entity, position, normal, speed) self.hadCollision = true if self.entered == false then if entity.script ~= nil then if entity.script.isBelt ~= nil then self.collidedBelt = entity -- add ourselves to the belt so it knows it has to push us around entity.script:AddItem(self) end end self.entered = true self.exited = false end end -
I don't want to edit my post above because it screws up the formatting I worked so hard to get . One of the bigger things to notice is that you end up with nice and small functions that are very concise. It also helps remove nested if's. In your example you go 3 layers deep. That's what happens when you're managing states in a more brute force manner. It's unavoidable almost when doing things that way. With states those are masked in the state manager class making the gameplay code easier to read and maintain. This was a simple door but the AI scripts have even more nesting levels and a lot more bigger functions doing more than they should. State managers help keep functionality separated into their own concise methods. Combined with adding a delayed state change handled automatically by the state manager I think it offers a good 2 for 1 deal Note the idea of automatically giving every entity node on the flowgraph a ChangeState(state) input. It could also benefit from automatically giving outputs for Enter()/Exit() for every state defined as well. This is where I think having the ability to add states via the editor would help at design time. It makes the code even less and cleaner.
-
Below is how it would maybe look like with the state manager class I have. The benefit is dropping the need for those flag variables in the script and less nested statements which makes it easier to read and follow. Script.enabled = true --bool "Enabled" Script.startingState = "Close" --text "Starting State" Script.openAngle = 90 --float "Open Angle" Script.closedAngle = 0 --float "Closed Angle" Script.openSound = "" --path "Open Sound" Script.closeSound = "" --path "Close Sound" function Script:Start() self.sounds = {} self.sounds.openSound = Sound:Load(self.openSound) self.sounds.closeSound = Sound:Load(self.closeSound) self.stateMgr = StateManager:Create(self) -- this automatically looks for functions like Open_Enter, Open_Exit, Open_Update and if there, calls them based on the state self.stateMgr:AddState("Open") self.stateMgr:AddState("Close") -- this just sets the active state but doesn't call the *_Enter() function for it (since in this case we wouldn't want the sound we are doing to play) -- and it ignores the enabled flag passed into self.stateMgr:Update() self.stateMgr:SetInitialState(self.startingState) end -- since in a door there are only 2 states this can be called from flowgraph to get the other state and pass it into ChangeState() below function Script:GetNegateState()--arg if self.stateMgr:GetActiveState() == "Open" then return "Close" end return "Open" end -- input from the flowgraph function Script:ChangeState(state)--in self.stateMgr:ChangeState(state) end function Script:Open_Enter() self.joint:SetAngle(self.openAngle) self.entity:EmitSound(self.sound.open) self.component:CallOutputs("Open") -- change to the close state in 5 seconds (if Open is called again before the state changes to Close the delayed function call will simply reset its timer) self.stateMgr:ChangeState("Close", 5 * 1000) end function Script:Close_Enter() self.join:SetAngle(self.closedAngle) self.entity:EmitSound(self.sound.close) self.component:CallOutputs("Close") end function Script:UpdateWorld() -- if self.enabled == false then no states will be changed even if self.stateMgr:ChangeState() is called self.stateMgr:Update(self.enabled) end If states were built into entities then the code gets a little cleaner. If that was the case then ideally ChangeState() would be an input to ALL entities on the flowgraph automatically. Script.enabled = true --bool "Enabled" Script.startingState = "Close" --text "Starting State" Script.openAngle = 90 --float "Open Angle" Script.closedAngle = 0 --float "Closed Angle" Script.openSound = "" --path "Open Sound" Script.closeSound = "" --path "Close Sound" function Script:Start() self.sounds = {} self.sounds.openSound = Sound:Load(self.openSound) self.sounds.closeSound = Sound:Load(self.closeSound) -- this automatically looks for functions like Open_Enter, Open_Exit, Open_Update and if there, calls them based on the state self.entity:AddState("Open") self.entity:AddState("Close") -- this just sets the active state but doesn't call the *_Enter() function for it (since in this case we wouldn't want the sound we are doing to play) -- and it ignores the enabled flag passed into self.stateMgr:Update() self.entity:SetInitialState(self.startingState) end -- since in a door there are only 2 states this can be called from flowgraph to get the other state and pass it into ChangeState() function Script:GetNegateState()--arg if self.entity:GetActiveState() == "Open" then return "Close" end return "Open" end function Script:Open_Enter() self.joint:SetAngle(self.openAngle) self.entity:EmitSound(self.sound.open) self.component:CallOutputs("Open") -- change to the close state in 5 seconds (if Open is called again before the state changes to Close the delayed function call will simply reset its timer) self.entity:ChangeState("Close", 5 * 1000) end function Script:Close_Enter() self.join:SetAngle(self.closedAngle) self.entity:EmitSound(self.sound.close) self.component:CallOutputs("Close") end function Script:UpdateWorld() -- wouldn't need anything state related here as that could be updated by the engine end I actually just thought about having ChangeState() accept a delayed time. I think that's a useful idea with the state manager class.
-
I suppose one way would be to have another parameter that is a function that needs to return a bool. Before the engine is ready to call the Close function it runs this parameter function and if that returns false it won't run Close? Sort functions sort of work the same way. table.sort takes a function and the return result helps determine the sort. In this case the return result helps determine if it should be ran. We could make it an anonymous inline function since those have access to any Scipt variables which a flag could be set if the door gets opened and that's what you'd return. entity:CallFunction("Close", self.closedelay, function return self.doornotclose end)
-
Yes coroutines can give us that style. I've done that before in le but this thread is about different functionality. It's solving a different problem than that. What you have there is cinema stuff. @game actually and index number would work. In C++ that could be a key to a map. In Lua a key to a table. It still means I'd have to be aware if I had multiple calls like this in a script to use a unique number per call which kind of sucks but I think there isn't any other way around it and at least it doesn't clutter up my Start function with a bunch of worker variables. None that I see in the code anyway as this function would automatically add them to the object and check for their existence at runtime.
-
what is timerindex in your example? Since you're passing a pointer in I assume it's a variable you have to make? The point of the system would be that you don't have to make such a variable yourself to track since it clutters up your code with worker stuff instead of having your code be more intent oriented. The intent of the code is to run some code every interval. Anything else you have to make or check for that to happen isn't part of the intent of your code from a person reading it. In C++ imagine having a lambda expression for the code you want to run vs doing an if statement with your function above. Just need to think of a way to automatically track the last time for that specific call.
-
After making a lot of "demos" over the years I find that in game creation there are a lot of points where you want to do something on a given interval. I'm playing around with converting monster.lua to a state machine and it does this in there as well. When in the idle state every 250ms it looks for a target. The part that's sort of a pain is that we make variables (I do the same thing) to hold the last check time. This is fine when it's 1 or 2 but when you have a lot of these kinds of checks or you do this in many games over and over again, it makes sense to think about different ways to do it that won't clutter up the script with "worker" type variables (the cleaner the script the way easier it is to manage later). I don't have the exact idea yet but I'm curious if others have ideas on a better way to handle something like this. It feels like it would be easier to somehow inline the command that says "every 250ms do this code". Something like: function Script:Idle_Update() DoEvery(250, function(self) self.target = self:ChooseTarget() if self.target then self.stateMgr:ChangeState("chase") end end) end Maybe something built into the entity itself that is specifically for running functions at intervals and it manages tracking that? It would just be nice to have to worry about game type stuff vs "system" type stuff when making games. Now I like making systems, but when I'm making games I prefer using systems and in this case, making a variable to track it and then checking the timing and updating the timing is just busy work distracting me from doing what I really want, which is run some code every X ms. Any thoughts/comments/concerns welcome.
-
I was giving an example by calling this "Event". When I try to implement this it tells me Create() is nil. However Event gets highlighted blue. I'm guessing it's an LE table that you're exposing and just don't have it documented anywhere?
-
I'm not using the flowgraph no. These are dynamically created entities so this style would work perfect as I have a manager entity that is spawning these entities so that manager entity has details it can pass to the dynamic entities.
-
So in my example with this idea the zombie script would call self.component:CallOutputs("onDead") when it dies and the player would call self.entity:AddOuput("onDead", self.zombieEntity, "ZombiDead") inside its Start() to subscribe to that output? Also I assume this would called Script:ZombieDead() and not a global function named ZombieDead(), The name AddOutput seems wrong. You're really connecting to an output. This does seem promising.
-
I'm intrigued. How would it be used in my simple example above? Can you show some code as an example?
-
Here is an example of what I use: Event.lua (the main event system) if Event ~= nil then return end Event = {} function Event:Create() local obj = {} obj.handlers = {} for k, v in pairs(Event) do obj[k] = via end return obj end function Event:Subscribe(owner, method) table.insert(self.handlers, { owner = owner, method = method }) end function Event:Raise() for i = 1, #self.handlers do self.handlers[i].method(self.handlers[i].owner) end end Here is an example usage: Zombie.lua import "Scripts/Systems/Event.lua" function Script:Start() self.onDead = Event:Create() end function Script:Hurt(dmg) self.health = self.health - dmg if self.health <= 0 then self.onDead:Raise() end end Player.lua Script.zombieEntity = nil --entity "Zombie" function Script:Start() -- subscribe to an event from another script self.zombieEntity.script.onDead:Subscribe(self, self.ZombieDead) end -- this would be called via an event from another script function Script:ZombieDead() System:Print("The zombie has died!") end
-
I was more thinking about having a system that makes it easy for us to make our own events for our gameplay scripts. An example could be creating an "onDead" event in one of my AI scripts. This script would trigger this event when the AI dies but we would code that triggering. Then from the editor we would be able to subscribe to that entities event from other entities so they can be informed of the event when it happens. Ideally by being able to specify a script function instead of one generic function to catch all events.
-
From my perspective those 3 systems are very different and used for different things but maybe I'm just not seeing the connection. I was thinking about redoing the soldier script with the state system but it's pretty large and would just take time to learn it's details and replicate it in the state system but maybe the door is easier to do. I still think behavior trees are best for ai. State machines can work for ai but they aren't as flexible so they aren't the ideal in this modern day of games. That's another thing I forgot to list above that I've done in LE and it would be nice to have built in.
-
I'm guess our terminology isn't lined up when saying event system as my event system wouldn't have a need for coroutines. I'm guessing you mean cinema type system when you say event as that for sure would use coroutines and I've done that before in le too. The term event in my threads context I was just meaning one script fires an event and other scripts can subscribe to said event and get a script function called when that event is raised. Things I've done and used in LE that I think could be helpful: - Cinema system that uses coroutines so the sequence of "events" can be coded in a more natural top down style. i.e. Do this, then this and this at the same time but wait for this, then do that. Etc - Event system between entity scripts. Would be cool if they were more part of the entity itself and maybe not require linking entities in the editor but can be subscribed between entities in the editor itself. Note that it should allow multiple subscriptions from multiple entities. - State system where states can be defined in a script to script functions and then you change state and it'll direct the flow to those functions. This helps remove huge nested if statements and just gives an overall better organization to the script. I'll create small demo projects for each system in the way I did them and maybe that'll help give you ideas on them.
-
I use an event system where a linked entity would raise an event (call a script function) to the hosting entity script. So I need to subscribe to that linked entities event. Can't do that in Start() since that entities Start() might not have been called yet (Start() is where my event object that handles all of this is created). So having a PostStart() is handy for that. It's a pretty common use case in Unity as well. I find event systems for communication works well and removes needing to know about the inner structure of the linked entity script (just the common event system interface is needed to be know and is common with all entities) and avoid double linking to communicate both ways.
-
This won't happen. All a person needs is a place to init linked entity stuff and that can only happen after all Start() functions are called. There is no need for anything else after that. Yes, I agree that would be a bad idea and with having a post start function solves this It's fine, looping over all entities after the map is loaded and calling PostStart() if one exists is the workaround I picked and it does what I need.
-
Draw a csg and change it's material to invisible (or I think there is a pink one (search in the Materials folder) that when applied doesn't show up in the game but does in the editor). To get the functionality of a trigger you'll still need to add a script and change the collision to trigger on the csg.
-
Sorry, I misspoke. After all entities scripts Start() functions are called in which we do initialization of that entity but Init() provides initialization of linked entities if needed. I know you don't like hearing this but Unity has this idea as well because it's a common thing to ask for in such a system.
-
That's not what the Init() function is meant for. Start() is called once on map load and is called on entities in no particular order, and Init() is called AFTER all entities are loaded. That's the main point of it. I may need to use script information from a linked entity but I can't inside Start() of another entity. Init() would be used for that specific need.
-
Yep, you're right. I just woke up when I posted that so wasn't thinking clearly. I used to do something like this a couple years ago and had forgotten.
-
@reepblue Yep, this is what should happen from the engine as altering the C++ project doesn't help for the the game launcher. Josh's work around is one way to do it to, but the suggestion was asking more the engine to do it for us.
-
We used to have something like this in LE 2 if I recall. It would be nice to have an event system in the Lua scripts. Somewhere where I can register to an event from another script and when that script fires said event my function that I registered will be raised.