thehankinator Posted June 13, 2015 Share Posted June 13, 2015 *UPDATE* 6/22/15 I've added code for simple peeking functionality. This is a tutorial on how to implement a rudimentary sneaking mechanic to your game. The monster will only see the player if they in front of it, while taking into consideration of the height of the player. This means the player could crouch behind an object, not under one. I assume you are comfortable and familiar with lua script and Leadwerks 3.5, therefore I will not be covering lua syntax nor how to do basic things in Leadwerks. The code samples below will have the new code surrounded by comments, the rest is there just to give you reference to what section of the code we are working in. I broke this problem down into two sub tasks. Crouch to FPSPlayer Peek to FPSPlayer Field of view check to MonsterAI Initial setup Open your project. In the asset tab, navigate to the FPSPlayer.lua script. Make a copy of it, call it FPSPlayerSneak.lua. Next at the top of FPSPlayerSneak.lua, we need to add the variables that we will need. Script.mouseDifference = Vec2(0,0) Script.playerMovement = Vec3(0,0,0) Script.tempJumpForce = 0 --CROUCH BEGIN Script.crouched = false Script.crouchheight = 1.0 --CROUCH END --PEEK BEGIN Script.peek_translate = 0 Script.peek_distance = 0.5 --PEEK END function Script:CycleWeapon(direction) Script.crouched This flag indicates if the player is currently crouching. You could set this flag if you wanted the player to start crouched. Script.crouchheight This is the height of the player when crouching. You could adjust this if you wanted the player to be taller or shorter while crouched Script.peek_translate This variable keeps track of how far we are peeking, no need to play with this one Script.peek_distance Defines the distance the player can peek, increase it to lean further Add crouch Toward the bottom of the UpdatePhysics function we have two things that need to be done. First, check for the control key then toggle a flag indicating if the player should be crouched or not. --Give the player an extra boost when jumping playerMovement = playerMovement * 1.6 end --CROUCH BEGIN -- Check for crouching if window:KeyHit(Key.ControlKey) then self.crouched = not self.crouched end --CROUCH END --With smoothing --Position camera at correct height and playerPosition self.entity:SetInput(self.camRotation.y, playerMovement.z, playerMovement.x, jump , false, 1.0, 0.5, true) A few lines down, we need to position the camera to the required height based on the crouch flag newCameraPos = Vec3(playerPos.x, newCameraPos.y, playerPos.z) --CROUCH BEGIN local target_height --determine the height of the camera if self.crouched then target_height = self.crouchheight else target_height = self.eyeheight end --calculate the new camera position if newCameraPos.y<playerPos.y + target_height then newCameraPos.y = Math:Curve(playerPos.y + target_height, newCameraPos.y, self.camSmoothing) else newCameraPos.y = playerPos.y + target_height end --CROUCH END self.camera:SetPosition(newCameraPos) Add peek We will continue working in FPSPlayerSneak.lua First we need to remap the E key from use to something else, for the sake of this tutorial I am going to use G. Scroll down the UpdateWorld() function and free up that E key. --PEEK BEGIN if window:KeyHit(Key.G) then --PEEK END if self.carryingEntity then self:DropEntityCarrying() Now that both the Q and E keys are available to use, we need to check them then set the direction we are going to shift the camera. -- Check for crouching if window:KeyHit(Key.ControlKey) then self.crouched = not self.crouched end --CROUCH END --PEEK BEGIN if window:KeyDown(Key.Q) then self.peek_translate = Math:Curve(-self.peek_distance, self.peek_translate, self.camSmoothing) elseif window:KeyDown(Key.E) then self.peek_translate = Math:Curve(self.peek_distance, self.peek_translate, self.camSmoothing) else self.peek_translate = 0 end --PEEK END --With smoothing Finally at the bottom of this function we are going to translate the camera to the side in the direction we are peeking self.camera:SetPosition(newCameraPos) --PEEK BEGIN self.camera:SetPosition(Transform:Point(self.peek_translate,0,0,self.camera,nil)) --PEEK END end Field of view check to MonsterAI In the asset tab, navigate to the MonsterAI.lua script. Make a copy of it, call it MonsterAIFOV.lua. Open that up and find the ChooseTarget() function. local d = self.entity:GetDistance(entity) local pickinfo=PickInfo() --LOS BEGIN local entity_pos if entity.script.camera ~= nil then--if there is a camera object entity_pos = entity.script.camera:GetPosition(true)--use the camera position else entity_pos = entity:GetPosition(true)--otherwise, use the entity's position end --cast a ray from the monster's eye to the target position if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity_pos,pickinfo,0,false,Collision.LineOfSight)==false then --when we reach here, the target is in line of sight --now we need to check if the target is in the monster's field of view. --we are going to measure the angle from the direction the monster is looking --to the target. local dir_facing = Transform:Normal(0,0,1,self.entity,nil):Normalize() local dir_to_target = (entity:GetPosition() - self.entity:GetPosition()):Normalize() local angle = math.acos(dir_facing:Dot(dir_to_target)) local window=Window:GetCurrent() --compare the resulting angle in radians, this determines the monster's field of view --or if the player is running(thanks nick.ace!) if angle > 2.0943951 or window:KeyDown(Key.Shift) then --~120 or degees return entity.script end end --LOS END end The math in this section is kind of confusing. I got adapted the formulas from the following website. http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-2/ Wrap up Set your monster to use MonsterAIFOV.lua, this should give you the FOV check. Then, set your player object to use FPSPlayerCrouch.lua, this will allow you to crouch when you press the control key. Feedback welcome. 3 Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 13, 2015 Share Posted June 13, 2015 This is really cool, however, would it be possible to make it so that the monster hears you if you run? Just a little more realism woulnd't hurt would it? Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 13, 2015 Share Posted June 13, 2015 Yeah, just add another condition to it: --compare the resulting angle in radians, this determines the monster's field of view local window=Window:GetCurrent() if angle > 2.0943951 or window.KeyDown(Key.Shift) then --~120 degees return entity.script end You may even want to put the sprint detection code somewhere else under different conditions, but you'll need to decide what you want. 2 Quote Link to comment Share on other sites More sharing options...
thehankinator Posted June 13, 2015 Author Share Posted June 13, 2015 Yeah, just add another condition to it: --compare the resulting angle in radians, this determines the monster's field of view local window=Window:GetCurrent() if angle > 2.0943951 or window.KeyDown(Key.Shift) then --~120 degees return entity.script end You may even want to put the sprint detection code somewhere else under different conditions, but you'll need to decide what you want. Great solution, my first ideas were far more complex. Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 13, 2015 Share Posted June 13, 2015 Thank you very much, this works amazingly well Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 14, 2015 Share Posted June 14, 2015 I found tiny problem, the monster can still see me when I'm crouched behind a big wall... Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 14, 2015 Share Posted June 14, 2015 This line should be changed from: if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity_pos,pickinfo,0,false,Collision.LineOfSight)==false then to if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then The closest flag should be true because otherwise the raycast could pick anything on that ray instead of just the closest. Also, the height isn't changing, so you need to account for that. local pick_height=self.eyeheight if self.crouched then pick_height=self.crouchheight end if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,pick_height,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then This will change the height at which the pick will end. Quote Link to comment Share on other sites More sharing options...
shadmar Posted June 14, 2015 Share Posted June 14, 2015 Here is a shadow/light detector for sneaking (cpp is needed to build your own exe to expose a function to lua) http://www.leadwerks.com/werkspace/topic/11991-csg-shape-to-mimic-light/#entry87032 Quote HP Omen - 16GB - i7 - Nvidia GTX 1060 6GB Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 14, 2015 Share Posted June 14, 2015 The closest flag should be true because otherwise the raycast could pick anything on that ray instead of just the closest. Also, the height isn't changing, so you need to account for that. local pick_height=self.eyeheight if self.crouched then pick_height=self.crouchheight end if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,pick_height,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then This will change the height at which the pick will end. I don't get where to put this Here is a shadow/light detector for sneaking (cpp is needed to build your own exe to expose a function to lua) http://www.leadwerks.com/werkspace/topic/11991-csg-shape-to-mimic-light/#entry87032 I'll take a look at it, thank you. Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 14, 2015 Share Posted June 14, 2015 local d = self.entity:GetDistance(entity) local pickinfo=PickInfo() --LOS BEGIN local entity_pos if entity.script.camera ~= nil then--if there is a camera object entity_pos = entity.script.camera:GetPosition(true)--use the camera position else entity_pos = entity:GetPosition(true)--otherwise, use the entity's position end --cast a ray from the monster's eye to the target position local pick_height=self.eyeheight if self.crouched then pick_height=self.crouchheight end if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,pick_height,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then --when we reach here, the target is in line of sight --now we need to check if the target is in the monster's field of view. --we are going to measure the angle from the direction the monster is looking --to the target. local dir_facing = Transform:Normal(0,0,1,self.entity,nil):Normalize() local dir_to_target = (entity:GetPosition() - self.entity:GetPosition()):Normalize() local angle = math.acos(dir_facing:Dot(dir_to_target)) --compare the resulting angle in radians, this determines the monster's field of view local window=Window:GetCurrent() if angle > 2.0943951 or window.KeyDown(Key.Shift) then --~120 degees return entity.script end end --LOS END end Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 14, 2015 Share Posted June 14, 2015 This is giving me an error whenever I start playing Lua Error: error in function 'new'.|argument #2 is 'number'; '[no object]' expected. Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 14, 2015 Share Posted June 14, 2015 Nevermind, I forgot that self.crouched and the other variables are for the player. There's a few more steps you'll need to do: Inside of the player's Start() method put this: player=self.entity This allows you to reference the player in any script. Next, replace these values in the script above: self.crouched should be player.script.crouched self.crouchedheight should be player.script.crouchedheight self.eyeheight should be player.script.eyeheight That should work. Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 14, 2015 Share Posted June 14, 2015 Now the game starts, but when I crouch the same error pops up >_< I'll put my code here, in case you find something that's wrong. if entity.script.health>0 then local d = self.entity:GetDistance(entity) local pickinfo=PickInfo() --LOS BEGIN local entity_pos if entity.script.camera ~= nil then--if there is a camera object entity_pos = entity.script.camera:GetPosition(true)--use the camera position else entity_pos = entity:GetPosition(true)--otherwise, use the entity's position end --cast a ray from the monster's eye to the target position local pick_height=player.script.eyeheight if player.script.crouched then pick_height=player.script.crouchedheight end if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,pick_height,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then --when we reach here, the target is in line of sight --now we need to check if the target is in the monster's field of view. --we are going to measure the angle from the direction the monster is looking --to the target. local dir_facing = Transform:Normal(0,0,1,self.entity,nil):Normalize() local dir_to_target = (entity:GetPosition() - self.entity:GetPosition()):Normalize() local angle = math.acos(dir_facing:Dot(dir_to_target)) --compare the resulting angle in radians, this determines the monster's field of view local window=Window:GetCurrent() if angle > 2.0943951 then --~120 degees return entity.script end end --LOS END end end end end Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 14, 2015 Share Posted June 14, 2015 It should be player.script.crouchheight not player.script.crouchedheight, sorry about that. Quote Link to comment Share on other sites More sharing options...
thehankinator Posted June 14, 2015 Author Share Posted June 14, 2015 I found tiny problem, the monster can still see me when I'm crouched behind a big wall... Is it possible your wall is not set to "Scene" in the physics tab? Collision.LineOfSight only collides with Scene objects. This line should be changed from: if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity_pos,pickinfo,0,false,Collision.LineOfSight)==false then to if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,1.6,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then The closest flag should be true because otherwise the raycast could pick anything on that ray instead of just the closest. Also, the height isn't changing, so you need to account for that. local pick_height=self.eyeheight if self.crouched then pick_height=self.crouchheight end if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,pick_height,0),entity_pos,pickinfo,0,true,Collision.LineOfSight)==false then This will change the height at which the pick will end. The Pick() is checking for anything between the two points then only does the FOV check if there was nothing so the closest flag should not matter. The height of the monster shouldn't be changing (in this tutorial anyway) but it is casting the ray to the camera. When the player crouches, the camera's position changes. So there should be no need to do any additional checks. 1 Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 14, 2015 Share Posted June 14, 2015 Yeah sorry about that, I forgot. I usually test for character collision using the closest pick, but you obviously chose to do it a different way. You are right though, the pick position height variable should be switched: local enemyheight=1.6 if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,enemyheight,0),entity_pos+Vec3(0,pick_height,0),pickinfo,0,true,Collision.LineOfSight)==false then Quote Link to comment Share on other sites More sharing options...
thehankinator Posted June 14, 2015 Author Share Posted June 14, 2015 Yeah sorry about that, I forgot. I usually test for character collision using the closest pick, but you obviously chose to do it a different way. You are right though, the pick position height variable should be switched: local enemyheight=1.6 if self.entity.world:Pick(self.entity:GetPosition()+Vec3(0,enemyheight,0),entity_pos+Vec3(0,pick_height,0),pickinfo,0,true,Collision.LineOfSight)==false then What's wrong with the Pick points? The p0 point is the monster's head, in the Crawler's case "self.entity:GetPosition()+Vec3(0,1.6,0)" pretty much nail's it. The p1 point (entity_pos) will be the exact position of the camera (ie the players head). Quote Link to comment Share on other sites More sharing options...
lxFirebal69xl Posted June 14, 2015 Share Posted June 14, 2015 I found out what was wrong, the monster can see the player through models, but not CSG brushes. Quote Link to comment Share on other sites More sharing options...
nick.ace Posted June 15, 2015 Share Posted June 15, 2015 What's wrong with the Pick points? The p0 point is the monster's head, in the Crawler's case "self.entity:GetPosition()+Vec3(0,1.6,0)" pretty much nail's it. The p1 point (entity_pos) will be the exact position of the camera (ie the players head). Oh I see. Sorry I kept getting confused with some of the points and stuff for some reason. Quote Link to comment Share on other sites More sharing options...
thehankinator Posted June 16, 2015 Author Share Posted June 16, 2015 I found out what was wrong, the monster can see the player through models, but not CSG brushes. I was able to get this working. To test I used "cryogen_propb.mdl" from the scifi construction kit. The trick was that the editor would drop this model in as a Prop collision type. To fix this I went to the Scene tab, found the model (in my case it was called "cryogen_propb") expanded it to expose it's children. From there I found a child called "Form162"(yours will probably have a different name), when I selected it the whole model turned red in the editor viewports. It was this object that I changed the Collision type to Scene under the Physics tab. It seems like you have to specifically select the correct node and change this setting. I tried selecting the parent and all children and it wouldn't work. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.