Jump to content

Rick

Members
  • Posts

    7,936
  • Joined

  • Last visited

Posts posted by Rick

  1. @gamecreator It's a tough balance because being the only coder on a team like that can be overwhelming too. Perhaps 2 coders is ideal but with very clear separate ideas on what they work on. One being player oriented and another world event oriented (this would be the single scripts like buttons to open doors, puzzle things, etc). That would probably work better as they won't step on each others toes. In such a situation the player coder always has the hardest job though. Correct movement (some games can be elaborate in this), inventories (this is a whole thing of it's own), weapons, etc are no easy task to get all done by 1 person. In bigger AAA games each of those things I just listed would be done by a handful of people because they can all get complex and be elaborate over time. I really think an interface for proper communication between these systems is the most challenging but most beneficial thing that could happen to a community project.

     

    The fact that still today LE doesn't have any sort of generally accepted inventory system that people are using and tweaking is not good. After all this time we have nothing that is shared by anyone and works well? Go to Unity asset store and type Inventory Systems. There are 54 and a reasonable amount are free. LE has nothing. Aggro tried making a generic one but he found out the LE editor and no universal way of communication between things just makes it difficult to create a good system in LE. It's a shame really.

  2. Just my notes. I initially signed up, but honestly after seeing how the design of the game was taking off I could tell it was just too big of a design to really get completed. This tends to be the biggest issue with community projects, and all projects in general honestly. The scope really started to creep and it became too much to realistically get done by a bunch of random people over the internet.

     

    The good thing about these community projects is different techniques get used on each one and little by little we learn.One big issue is working styles. Everyone has such a different way of working and that can make things difficult for a game where consistency across everyone is really needed to make things fit together. When a person is paid well they are happy to conform to a standard, but when they aren't paid at all they tend to just want to do their own thing.

     

    The Leadwerks game engine is so open ended and doesn't try to funnel people into a certain way of creating a game and that makes it challenging in a setting like a community project.

     

    The one common theme between all of this generally seems to the scope of the project though. When you try to keep the scope down it means you don't need that many people and usually these things get 5-10 people who jump on right away. Then if the scope is small there is nothing to do for most so they fade away. However, if the scope is large then things get out of hand real quick. The larger the scope the more communication needed between people and since this is a hobby and time varies what could take an hour in person if it was a job turns into days and them motivation gets lost and a downward spiral happens.

     

    When I tried leading a community project I wanted to try a very different approach to see how that would work. I had a framework for coding that I tried to get people to work within. The idea was each person could work on isolated code from anyone else and as long as the inputs/outputs matched up (I was the one to communicate this between the programmers) things would connect perfectly. The idea was much like lego blocks. As the architect I would determine all the blocks needed and assign the work to programmers. Then I'd connect the blocks of code they wrote. This had various degrees of success but often it came down to people had the idea of the final project and everyone wanted to add their take on it and had an opinion of what should be done instead of what was asked of them. That's the hard part with a community project. Everyone wants to also be the designer as well (very justifiable) but when everyone on the project becomes a designer bad things happen. In the real world people do their role and things work out much better that way. This is true for anything in life big enough to warrant multiple people working on it. If every construction crew member thought they should put their architect skills at work while making the building, the thing would never get done and even if it did, it would look way different than the original design by the actual architect. While the construction crew might think things don't make sense while building, it's their job to build the thing, not question or redesign it. This idea is very difficult with a community game project but I feel is really the best way to do one as it is a best way to build a building. Same concepts.

     

    I still think a fairly tight game framework to which everyone on the project works within is the best way to get it done. LE is too open for that at the moment. Yes, it's nice to have a lua script per object, but games require interaction which means scripts need to often communicate. There is no defined way that works across many people working on scripts at the same time. Accessing script variables or methods directly from other scripts leads to issues when those things change by another programmer or aren't defined when you need it. It's best if scripts can be done in isolation and tested in isolation and the communication between then can be hooked up later with minimal effort. It's all about the communication. In both people and code.

     

     

    • Like 3
  3. Changing font is a state kind of action. When you change it, it's then set for all drawing of text. So you have to change it, draw your text, then change it back to what it was.

     

    What timer are you talking about? You have some timer code somewhere? We'd have to see it.

  4. What I would do is create a new map called StartGame.map or something like that. You won't have any 3D stuff in it (maybe you would later if you decide to have the start menu 3D background?). In the map add a pivot and attach a script to it. In this script would be where you draw all this stuff in the PostRender() function of the script if you're using images for the buttons or in the Start() function if you want to use LE's UI. Then when buttons are clicked you load the new map that is your actual game. You can see how to load a new map in Main.lua file but I think you just set a certain variable to the map name and then it'll load it.

  5. 13 hours ago, martyj said:

    For your last question, we hide everything on the Veg System and use real Instanced models instead.

    So you're using the veg system for positions/rotation only? If you are replacing every tree in the veg system with an instance of a model, that's what you said right? I would think you would lose performance of the veg system then if you did that right as that has billboards and such where your model instances wouldn't or are you doing your own LOD type thing and that's what the distance check is for?

     

    I was thinking you were replacing the veg trees to instance models JUST around the player since if possible that would really be all you need to do, but the problem is being able to tab into the veg system to do something like that.

  6. 8 hours ago, martyj said:

    I had to do this for my game, it's possible, just not with the vegetation system exactly.

    You have to convert vegetation instances into real Entity objects.

    Due to performance reasons, you can't convert every vegetation object to an entity, so you have to be smart, and look at the closest objects to the player.

    I used a pool of about 50 objects and moved them to the closest trees around a player.

    I would sticky a vegetation entity to a specific tree as well, so if you started cutting one tree down, and you moved a little bit, you could go back to that tree and finish it off.

    Here is the C++ code. (Headers not included)

     

    VegetationToEntity

    -- Converts a vegetation layer into multiple VegetatedModel classes

    
    bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
    	{
    		auto it = std::search(
    				strHaystack.begin(), strHaystack.end(),
    				strNeedle.begin(),   strNeedle.end(),
    				[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
    		);
    		return (it != strHaystack.end() );
    	}
    
    	void VegetationToEntity::LoadVegetationLayer(World* world)
    	{
    		App* app = App::GetApp();
    		if (app == nullptr)
    		{
    			return;
    		}
    
    		if (world->terrain == nullptr)
    		{
    			return;
    		}
    
    		std::map<std::string, bool> limitEntityMap = {
    				{ "Antelope", true },
    				{ "Deer", true },
    				{ "Lamb", true },
    				{ "Sheep", true },
    				{ "Wolf", true },
    		};
    
    		std::map<std::string, std::string> vegNameEntMap = {
    				{ "oak", "OakTree" },
    				{ "maple", "MapleTree" },
    				{ "birch", "BirchTree" },
    				{ "green", "GreenheartTree" },
    				{ "giantwood", "RedwoodTree" },
    				{ "weep",  "WillowTree" },
    				{ "mud", "MudHole" },
    				{ "antelope", "Antelope" },
    				{ "fallowdeer", "Deer" },
    				{ "lamb", "Lamb" },
    				{ "sheep", "Sheep" },
    				{ "wolf", "Wolf" },
    		};
    
    		int vegCount = world->terrain->CountVegetationLayers();
    		for (int i = 0; i < vegCount; i++)
    		{
    			VegetationLayer* layer = world->terrain->GetVegetationLayer(i);
    			std::string modelPath = layer->modelpath;
    
    			std::string parentName = "";
    			System::Print(modelPath);
    			for (auto it = vegNameEntMap.begin(); it != vegNameEntMap.end(); it++)
    			{
    				System::Print(it->first);
    				if(findStringIC(modelPath, it->first))
    				{
    					parentName = it->second;
    					break;
    				}
    			}
    
    			if (parentName.empty())
    			{
    				continue;
    			}
    
    			Entity* model = world->FindEntity(parentName);
    			if (model == nullptr)
    			{
    				continue;
    			}
    
    			// Hide Layer
    			layer->viewrange = 0;
    
    			if (limitEntityMap[parentName])
    			{
    				models.push_back(new VegetatedModel(layer, model, parentName));
    				continue;
    			}
    
    			Vec3 scale = model->GetScale();
    
    			AABB aabb = world->aabb;
    			std::vector<Mat4> instances;
    			layer->GetInstancesInAABB(aabb, instances);
    			System::Print("Loading Layer: " + parentName + " of Size: " + std::to_string(instances.size()));
    			for (unsigned int j = 0; j < instances.size(); j++)
    			{
    				Mat4 instance = instances[j];
    
    				Entity* copy = model->Instance(true, false);
    				copy->SetMatrix(instance, true);
    
    				copy->SetScale(scale);
    			}
    		}
    	}
    	// Called manually in App::Loop
    	void VegetationToEntity::UpdateWorld()
    	{
    		for (auto it = models.begin(); it != models.end(); it++)
    		{
    			VegetatedModel* model = *it;
    			model->UpdateWorld();
    		}
    	}



    The next class is where the work comes in. VegetatedModel represents a single Vegetation Layer as N number of entities. This will auto-populate the entities around the players positions as the player moves.
     

    
    VegetatedModel::VegetatedModel(VegetationLayer* layer, Entity* model, std::string className)
    	{
    		this->layer = layer;
    		this->cameraPosit = Vec3(0);
    
    		for (int i = 0; i < VEG_MODEL_ENTITY_COUNT; i++)
    		{
    			Entity* copy = model->Instance(true, false);
    			WFModel* model = Binder::InitCtor(copy, className);
    
    			this->realEntities[i] = new VegetatedEntity(model, copy);
    		}
    	}
    
    	void VegetatedModel::UpdateWorld()
    	{
    		Player* p = Player::GetPlayer();
    		if (p == NULL)
    		{
    			return;
    		}
    
    		Vec3 playerPosit = p->GetPosition();
    		if (this->cameraPosit.DistanceToPoint(playerPosit) < 4)
    		{
    			return;
    		}
    
    		this->cameraPosit = playerPosit;
    
    		int boxSizeX = 30;
    		int boxSizeZ = 30;
    		AABB scope = AABB(playerPosit.x-boxSizeX, -1000, playerPosit.z-boxSizeZ, playerPosit.x+boxSizeX, 1000, playerPosit.z+boxSizeZ);
    
    		World* world = World::GetCurrent();
    		std::vector<Vec3> possiblePoints(0);
    		std::vector<Mat4> instances(0);
    		this->layer->GetInstancesInAABB(world->aabb, instances);
    		for (unsigned int j = 0; j < instances.size(); j++)
    		{
    			Mat4 mat = instances[j];
    			possiblePoints.push_back(mat.GetTranslation());
    		}
    
    		std::sort(possiblePoints.begin(), possiblePoints.end(), VegetatedModel::PointSort);
    
    		// First VEG_MODEL_ENTITY_COUNT as we want to show only the closest point
    		if (possiblePoints.size() > VEG_MODEL_ENTITY_COUNT)
    		{
    			possiblePoints.erase(possiblePoints.begin() + VEG_MODEL_ENTITY_COUNT, possiblePoints.end());
    		}
    		
    		std::vector<VegetatedEntity*> entitiesNeedingPoints;
    		std::vector<Vec3> pointsToRemove;
    		int Count = Math::Min(possiblePoints.size(), VEG_MODEL_ENTITY_COUNT);
    		for (int i = 0 ; i < Count; i++)
    		{
    			VegetatedEntity* vegEntity = this->realEntities[i];
    			Entity* ent = vegEntity->GetEntity();
    			
    			bool found = false;
    			for (int j = 0; j < Count; j++)
    			{
    				Vec3 posit = possiblePoints[j];
    				if (vegEntity->GetVegetationPosition() == posit)
    				{
    					found = true;
    					pointsToRemove.push_back(posit);
    					break;
    				}
    			}
    
    			if (!found)
    			{
    				entitiesNeedingPoints.push_back(vegEntity);
    			}
    		}
    
    		std::vector<Vec3> newPossiblePoints;
    		for (int i = 0; i < possiblePoints.size(); i++)
    		{
    			Vec3 possiblePoint = possiblePoints[i];
    			bool found = false;
    			for (auto it = pointsToRemove.begin(); it != pointsToRemove.end(); it++)
    			{
    				Vec3 pointRemove = *it;
    				if (possiblePoint == pointRemove)
    				{
    					found = true;
    					break;
    				}
    			}
    
    			if (!found)
    			{
    				newPossiblePoints.push_back(possiblePoint);
    			}
    		}
    		possiblePoints = newPossiblePoints;
    
    		int numEntitiesToSet = Math::Min(possiblePoints.size(), entitiesNeedingPoints.size());
    		for (int i = 0; i < numEntitiesToSet; i++)
    		{
    			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
    			Entity* ent = vegEntity->GetEntity();
    			WFModel* model = vegEntity->GetWFModel();
    
    			Vec3 point = possiblePoints[i];
    			vegEntity->SetVegetationPosition(point);
    			model->Reset();
    		}
    
    		// Remove unused entities
    		for (unsigned int i = numEntitiesToSet; i < entitiesNeedingPoints.size(); i++)
    		{
    			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
    			Entity* ent = vegEntity->GetEntity();
    			//vegEntity->SetVegetationPosition(Vec3(0, -123, 0));
    			//ent->Hide();
    		}
    	}
    
    	bool VegetatedModel::PointSort(Vec3 posit1, Vec3 posit2)
    	{
    		Player* p = Player::GetPlayer();
    		if (p == NULL)
    		{
    			return 0;
    		}
    
    		double d1 = p->GetPosition().DistanceToPoint(posit1);
    		double d2 = p->GetPosition().DistanceToPoint(posit2);
    
    		return d1 < d2;
    	}



     

     

    This looks cool but it seems it may not be that efficient? If I'm reading it correctly you're loading the tree model for every vegetation model that exists on startup. Then inside each of those models you're checking the distance to the player to determine if you should do something. Just thinking that's a lot of checks happening per game loop. Could you not go the opposite direction and do AABB check in a range around the player, then loop only through those to make them real models vs veg? Keep that list so the next iteration when you loop again you can tell which ones are no longer in the list and change those back to veg vs model? Seems like you'd loop through a lot less veg overall each iteration.

     

    Where are you able to manipulate the veg system to hide specific trees? I didn't think they were accessible that way?

     

     

  7. So in the scene graph you have a camera object as a child of your player pivot?

     

    If that's the case then from your script that is attached to your player pivot you'd do something like this in the Start() function:

    self.camera = self.entity:GetChild(0)

     

    This assumes you only have 1 child to your player pivot and it's the camera. If you have more than one child I think you can use self.entity:FindChild("camera_name_here")

    Scripts don't automatically get variables like you're thinking because they are children or parents of things, but there are ways to get them with GetChild(), FindChild(), GetParent() calls.

     

    This isn't 100% ideal though. Ideally you'd create a script parameter and drag and drop the camera in the scene graph to that parameter. To do this at the top of this script add:

     

    Script.camera = nil --entity

    Then when you select your player entity in the scene graph you'll see the camera parameter show up. Drag and drop the camera to this slot and now you've made a link between the camera entity and this script camera variable. Now you can use self.camera in your script. You can do this with any entity btw.

  8. Close. You don't load cameras separately, just the models really. So let's say you have 10 different models in your game.

     

    In a loop just for clarity:

    LoadModel1

    Update, Progress bar to 10%

    LoadModel2

    Update Progress bar to 20%

    *repeat until you loaded all models

    Load Map (this will be fast now because you already loaded the models and when this goes to load those models it'll use the instances in memory vs going to disk).

     

    Note that you wouldn't manually set the % like this. You'd want to figure out how many models you need to load per map and calculate the % as you're looping over them all.

     

    Note this is a "hack" though. We shouldn't have to do something like this and hopefully Turbo handles this better.

    • Like 1
  9. Yue, what gamecreator is saying is that the biggest time to load an entire map is loading all the models that are in the map. The LE map doesn't actually have the mdl files inside it. The map file just has information about what mdl files to load (mostly). In LE if a model is already loaded the next load call on it will just be creating another instance of the model (this means it doesn't have to go to disk to do this. it just looks at the already loaded model in memory and copies it.). Creating instances are fast. So if BEFORE you load your map you manually call load on all the mdl files you need then calling the map load will be faster. Since you're manually calling the mdl files to load you can loop over that process and make the progress bar, then at the end call your map load. Yes, there will be a slightly below still when you load the map but it'll be MUCH smaller and probably not noticeable by the user. So it sort of simulates map loading progress bar. This does have the side effect of needing to manually know which models to load for each map. Ideally one wouldn't load every model in the game if it's not used in a map. This is really why the map file should just be json so we can easily open and manipulate it ourselves. Even add fields to it.

    • Confused 1
  10. The loading bar idea should be noted for the new engine. Make the map format json or something so the information about what should be loaded can be loaded first then it's easy to see how many models there are and then the engine could expose a callback after each resource is loaded from the map and let a full iteration of the engine happen after each one so we can display our own progress bar.

    • Like 1
  11. "The program will just silently close with no error message."

     

    Look at the definition of coroutine.resume() because it returns 2 values and 1 of them is if there is an error. I noticed the debugger doesn't show these so if there is an error I write it out to console so at least I can see the error.

    • Thanks 1
  12. My FSM is free. I would be willing to help yes.

     

    You for sure want to go with text vs images but yes that does mean you'll want some kind of generic code (library) to deal with all the issues that come with that (resolutions, line breaks etc). This can get sticky because it's all about font size and in LE you have to load fonts to get different sizes which is a pain. I have a UI library (it's sort of complicated now) where I have a label control where you size the rectangle where the text should fit and on load of the game it loops through a bunch of font sizes and loads them and tries to figure out what size fits best inside the rectangle and the rectangle itself is resolution independent (the size of it is relative to the screen resolution so it always looks the same on any resolution). You'll have the same issue with your images though because the size of them aren't resolution independent and you've had to account for that if you want that way too.

     

    I can release my UI library. It's pretty powerful as it handles all this stuff for you but that comes at a cost of complexity and I don't have any tutorials on it. I have some time tomorrow so I'll upload it then and make a youtube video on it.

    • Like 2
  13. Just off the top of my head, having anything bug draw logic in PostRender() would be not ideal. Have your logic in UpdateWorld() and use PostRender() to just draw 2d images or text of the results. This means don't have window:KeyDown() in PostRender().

     

    The second thing is using window:KeyDown(). The code is fairly messy to read but remember that KeyDown() will register true each frame if it's held down and it's hard for a person to press a key and have KeyDown() see it for only 1 frame. Try using KeyHit() instead maybe because that only registers the key press for 1 frame no matter if they hold it down. So that might help solve your issue of the question getting answered the same when they press a key.

     

    As far as structure goes, this is clearly, I think you know this, a mess of code which isn't very flexible and hard to debug. There is so much code duplication as well which will lead to hard to find bugs as soon as you start changing anything.

     

    You should probably go back to the drawing board with how to approach the requirements. Anytime you find yourself using just a **** load of boolean flags it's a good indication there is a better way. A bunch of boolean flags generally means you're trying to control state. So something to possibly look into is a state machine.

     

    The practical idea of state machines is that it allows you to change states and each state can be nicely isolated. In the case of a Lua library I made for LE this means each state has functions. Enter/Exit/Update(). When you change from 1 state to another the state you were in calls the Exit() function you defined for that state, then calls the Enter() function for the new state you changed to, then continuly calls Update() for the new state until you change state in which the same thing happens. Exit the old state, enter the new state, then Update() on the new state over and over again where you are looking for the user input or something to change to another state. To look into this go to the Workshop from the LE editor and search on state machine and you'll see my FSM. Download it. In the description I have links to videos explaining how it works.

     

    Something else to look at is the structure of your data. In this case it seems like you have questions that can lead to other questions and each question can have 3 answers. So think about how you could structure your data for that. This would be a table that holds question and answers and nested tables under as well to any level you want.

    • Like 2
  14. Do you think the indie person messing around (the main client base of LE) really cares this much about speed? I can get all the speed in the world but if I have to jump through all these hoops to get gameplay then it doesn't matter to me. I care more about less effort on my part to get something good up and running than I do speed. It doesn't matter how fast my game runs if I don't really even have a game because it takes so long to do certain things or the things aren't easy to use or flexible enough for my use case.

    The below isn't to slam your creations it's to show my opinion on the state of LE.

    Let's look at in game UI. The flexibility of the current UI comes at the cost of learning a proprietary library that really isn't easily flexible for all sorts of needs. Why? html/css is becoming the norm for game UI and for good reason. Everyone under the sun knows some html/css and it's the most discussed technology topic on the internet so if you don't know how to do something it's sure that you can easily find a stackoverflow post on how to do it. It's the ultimate in flexibility at pretty low cost. There are already tons of libraries out there (bootstrap) that help make it even easier and it's easier to get dynamic interesting UI vs gray buttons. You can defend your system all you want but in the world of game engines this UI is very low on that list. Why reinvent the UI wheel when html/css/javascript has done such a great job with it and has a massive history to draw from for knowledge.

    There is no question you like doing gfx programming. This is clearly your favorite part about a game engine and it shows in your blogs and posts. You can just see the excitement coming off those pages. You view this as the most important part and to a large extent it is, but you can't piece mail these other systems just to get a bullet point on a list of what the product can do and expect this to become any sort of massively successful product. Those other systems require just as much attention you give to the gfx programming side of things, but it seems like it's not your strongest skillset since you don't have as strong a passion for them. From my perspective understanding that and addressing that in some way will go a long way for LE.

     

×
×
  • Create New...