What to do?

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

What to do?

Post by albinopapa » December 10th, 2019, 4:56 am

Okay, I've run into this problem over and over and it has prevented me from finishing a project for the last time.

One of my biggest road blocks when programming something is design or architecture. I always felt I had a grasp on classes and program structure with the exception of programming patterns like the Observer pattern or ECS, but I always end up running into cases where I have to stop and think about how classes should interact. Should X be a derived class of Y? or should I create a bridge class that communicates between two other different but not entirely unrelated classes? Should I use interfaces?

Well, I can't seem to get anywhere because when I stop and think about it, I usually just drop the project and find something else that interests me.

While I know programming patterns can help make your classes more modular, and interfaces have the same affect and allows for testing different concrete classes, I just can't seem to make it work, so I'm going to do something a little more elementary.

Fuck encapsulation.
Fuck accessor functions
Fuck architecture

It's the easy road for awhile until I get my bearings with structs and functions...like C, but using C++ references, templates, function/operator overloading, namespaces and the STL. Public access all the way.

I did not come to this conclusion lightly, but I need to feel accomplished in something. Right now, I feel defeated and need a pick-me-up. Hopefully, I'll have something to show in a month's time. More realistically by the end of January. The time frame is based on previous experience with the project I'm working on so I have a good starting point and I've rewritten it thrice now.

Anyway, hope everyone enjoys their holidays and new years. I'll still be around for anyone who needs help, but I'm going to have my nose buried in my monitor over the next 6 weeks minus a few days for Christmas and the New Year.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 11th, 2019, 7:14 am

This was going to be an entirely different post, but I decided to share something I have discussed before.

First, a little preface, a little snippet of what my previous thoughts were.

I was looking for a way of handling my Enemy classes. All the basic enemies could all share the same code for behavior while only the data changed. This is because all the basic enemies had to do was follow some waypoints, then disappear, yes very basic. I tried runtime polymorphism in the beginning and of course it worked, but I am trying to get away from that for some unknown reason. I tried templates and using traits, but still had a bunch of redundant code. This is when I remembered, I spent quite a bit of time over this past spring if I recall learning about std::variant and all the ways to access the object stored in an std::variant. My first attempt didn't go so smoothly as I was just creating wrappers around member function calls. This lead to a lot of redundant code again. Well, during this rewrite I realized that since all the code is the same and just using different data, I could actually just put the code in a lambda and use the different Enemy classes as traits. This is something I've been wanting to try for a while, but wasn't sure how to go about it.

The method of using std::variant I chose was to mimic polymorphism through inhertance. In runtime polymorphism through inheritance, you have a base class that might store the base data; position, velocity, color, etc... Then each child class has their own member data and overridden functions to change behavior. For my project, there are no functions to override, because the code is the same for all enemies using each enemy's waypoints. This allows me to keep the "base data" in a Enemy "base class" and just wrap the behavor in a lambda for the visitor. Using decltype I can also access the constexpr data for each specific type that the variant holds.

Here's a sample:

Code: Select all

	struct Enemy1
	{ 
		static constexpr auto score_value = 30;
		static constexpr auto speed = 120.f;
		static constexpr auto aabb = RectF{ -16.f, -16.f, 16.f, 16.f };
		static constexpr auto waypoints = std::array{
			Vec2{ 0.0f, 0.0f },
			Vec2{ screenRect.Width(), 0.0f },
			Vec2{ screenRect.Width() * .5f, screenRect.Height() + aabb.Width() }
		};
	};
All of the data is static so it is accessible using the scoped type instead of an instance, which is important since the data is all constexpr. Since my instances aren't constexpr, the data accessed through an instance doesn't seem to be evaluated as a constant expression ( compiler complained when trying to use the data in a constant expression if accessed through an instance ).

Here's the "base class"

Code: Select all

	struct Enemy
	{
		void Update( float dt );
		void TakeDamage( float amount )noexcept;
		void Draw( Surface const& sprite, Graphics& gfx )const noexcept;

		std::variant<Enemy1, Enemy2, Enemy3, Enemy4, Enemy5> variant;
		Vec2 position = { 0.f, -16.f }, velocity;
		float health = 100.f;
		float damage = 25.f;
		int waypoint_index = 0;
	};
I can now create Enemy objects with a common interface similar to traditional polymorphism through inheritance. This allows me to store Enemy objects in an std::vector, while each Enemy will actually be one of the other Enemy# types.

Here's the Update function to give you an idea of what this rant is about:

Code: Select all

void Enemy::Update( float dt )
{
	auto do_update = [&](auto& entity_)
	{
		using type = std::decay_t<decltype( entity_ )>;

		const auto delta = position - type::waypoints[ waypoint_index ];

		velocity = delta.Normalize();
		position += ( velocity * ( type::speed * dt ) );

		const auto sqrDistToWaypoint = delta.LengthSq();
		constexpr auto minSqrDistFromWaypoint = sqr( type::aabb.Width() / 2.f );

		if( sqrDistToWaypoint < minSqrDistFromWaypoint ) ++waypoint_index;

		if( waypoint_index > type::waypoints.size() ) health = 0.f;
	};

	std::visit( do_update, variant );
}
The line: "using type = std::decay_t<decltype( entity_ )>;" is there for the compiler to give me a type so that I may use the static constexpr data in a constant expression. Other than this one line, I really don't even need the underlying entity_. All the other data is stored as data owned by Enemy, which I'm using as a sort of base class.

I thought this was a super neat and super clean way of dealing with std::variant and solve a problem I had with this project since day one. I didn't like how even though the interface AND the implementation was the same for every object, but because the data was different I had to create a base class and some override functions for each of the child classes. This made me teeter between putting all the Enemy# classes in the same header or in different headers. It seems like such a little thing, which is why I'm here in the first place. Right now, since all the enemy types are just static constexpr data structures, I just place them all in the same header as this Entity struct to make things easier and it makes sense as it's only data.

Alright, now on to weapons...I'll probably end up doing the same thing as all my weapons from the original had the same problem. Same implementation, different data and visual representation.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 15th, 2019, 6:53 am

Albinopapa wrote: I just can't seem to make it work, so I'm going to do something a little more elementary.

Fuck encapsulation.
Fuck accessor functions
Fuck architecture
Okay, well, currently my code is still all structs so all is public, but to keep some sanity I have decided to use member functions and because of that, and because of the variant stuffs, I still have accessors. Granted I could have just done this inline, but I like the way everything is coming together with a few exceptions.

For the enemies as shown above, all the code was the same the changes in behavior was data based. I've really taken this to an extreme and have been trying to shoehorn all my classes into submission using std::variant and changing behavior into data only...this didn't work out so well.

For instance, my Ammo classes. I wanted to have my projectiles to look different which required different drawing code. So I ended up making static draw functions for the individual types. I also wanted to make the plasma ball a little different. I wanted to have the damage of the plasma ball to be based on the charge, which would be based on how long the player held the fire button. This ended up being a little too difficult to implement using my design choices. So, the next best thing is to give the plasma ball enough health to still have an effect on the next enemy hit if the previous hit enemy dies.

As a side note, I have been using this drawing algorithm for a while, so I thought I'd share it with you all.

Code: Select all

// This is just one iteration of the nested loops usually for drawing a rectangle
// but this actually draws a shaded circle

// Get the radius ( can be skipped if your function already has access to the radius )
constexpr auto radius = aabb.Width() * .5f;

// Square the radius
constexpr auto sqrRadius = sqr( radius );

// X and Y here are the X and Y from the for loops going from 0 to Width
// of a bounding box around your circle, subtract radius to be between
// ( -radius, radius )
const auto ix = x - radius;
const auto iy = y - radius;

// Get the squared distance from of the current corrected position relative to center
const auto sqrDist = sqr( ix ) + sqr( iy );

// Check if the squared distance is less than the squared radius
if( sqrDist < sqrRadius )
{
	// Add the position back to the radius position
	const auto px = int( std::round( ix + position.x ) );
	const auto py = int( std::round( iy + position.y ) );
	
	// Get the percentage of squared radius we are from the middle
	// Subtracting from 1 will mean brightest near/in center
	// Not subtracting from 1 will make center invisible
	const auto alpha = uint8_t( 255.f * ( 1.f - ( sqrDist / sqrRadius ) ) );

	// Background color
	const auto bgcolor = gfx.GetPixel( px, py );

	// Alpha blend the object's color with the background color
	// using the alpha calculated previously
	const auto clr = AlphaBlend( Color( color, alpha ), bgcolor );

	gfx.PutPixel( px, py, clr );
}
You can also do this by performing the square root and dividing the radius by distance instead of squared radius by squared distance. If you do, it will be a linear gradient whereas using the squared version, you get an exponential gradient, more of a rounded sphere effect.

Just thought I'd share an interesting drawing effect that is pretty efficient.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: What to do?

Post by chili » December 16th, 2019, 3:10 am

Yo, but encapsulation rules tho ;)
Chili

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: What to do?

Post by Slidy » December 16th, 2019, 2:21 pm

I feel the same way sometimes where I am stuck thinking about how I want to structure my code.
Usually solve it in one of 2 ways:

1) Write out how I would want the interface / usage to be before actually writing the code for it, then work around that when implementing it

2) Abandon any thoughts about practices and just implement it in the most straightforward way possible (similar to what you seem to be doing now)

In both cases what I come up with is rarely the final product, but actually writing the code gives you some insight into the bigger picture and gives you a little foresight that you wouldn't have had otherwise. With that foresight, you can tweak or refactor your architecture if you really feel it's necessary.

For the example you've presented, I would personally go for a data-driven approach. Perhaps parsing it from a file (with hot-reloading if you're feeling fancy), or generating it at run-time and making it modifiable via UI. I am a big fan of that type of approach since it allows you to iterate over different values for the enemies and try out different stuff very quickly, rather than having to change the values in the code then recompile again.

Good luck.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 16th, 2019, 6:37 pm

I would personally go for a data-driven approach. Perhaps parsing it from a file (with hot-reloading if you're feeling fancy)
This seems to be my only idea of "Data Driven Design". It's the only thing I've heard of in reference to that design pattern, the loading from file part.

Loading values from a file, instead of a bunch of constexpr stuffs, has crossed my mind as of late. I know that creating/editing paths for the enemies would be iteratively faster for development because of recompiling if I did load these values from a file, but I need to get the code out before my desire to code diminishes again.

I usually go for something like #1, where I design interfaces for a few classes and that's gotten me where I am now, stuck thinking about "now how do I put all these pieces together?". So now it's on to #2. Code with reckless abandon, screwing convention and see how everything is going to come together. Then maybe, just maybe my eyes will see something that I've been missing over the last 7 wasted years.

I think I think ( intentional ) about stupid stuff too often when coding. I try to be more clever than I really am. I have tried different design patterns with absolutely no luck in understanding how they are suppose to work. I refuse to use a 3rd party library without understanding at least how it works. I have brought this up before, but something like the MVC pattern is something that I think I kind of like if I get a broad understanding of it, but actually taking people's descriptions of it, I just can't seem to stick to the rules. One of the reasons is I find many different interpretations of what each idea is for M.V.C.

As an example, in some source material, the Model is nothing more than a data layer. The Controller is the application business logic and the View is the display and user input logic. Other sources say, and this is one that Chili has pointed me to, is the Model is the business logic, the Controller is inputs for the Model such as literal controllers ( mouse, keyboard, game pads ) or maybe AI decisions and the View is just for display.

So on the one had, you have a View that handles displaying the Model and affecting the Model through user interactions. You have the Controller that is in charge of controlling the View such as updates or changing the View altogether like screen transitions maybe, still a little confused on the controllers roll here.

On the other hand, the Model is like a simulation step and the Controller handles changing values for each step such as user/ai inputs and the View is simply there to display the values from Model.

Another thing about MVC that confuses me is not knowing if I should be creating three different classes for every object in the application ( EnemyModel, EnemyController, EnemyView, HeroModel, HeroController, HeroView ). If that's the case, I'll drop MVC like a bad habit and never look back. That's too much boilerplate for me, and it seems like it would require a lot more bookkeeping.

ECS would be another design pattern I can't seem to wrap my head around. My last attempt at it seemed like it would be slow as fuck since I was having to search through a vector of Components just to find it's position, velocity or health. I know a Data Oriented Design here would be way faster and probably SIMD/cache friendly, but I find it tiresome and messy working with SoAs. I might take another stab at it thought, I've learned a few things since my last attempt. I know that the Entities are suppose to be numbers/ids, but I don't know what it represents so I don't see the difference in using a pointer or using an identifier. To me the address of the Entity would be it's identifier.

I have tried event messages, but what I usually come across is an enum and void* pair. The message and the payload. While with care this approach works, I have no clue how to debug something like this. Even with templates, it's difficult sometimes, but I can still rely on the call stack most of the time and I can still examine the values of the templated object I passed to a function to see if I've messed up. If I'm type erasing by converting to a void*, I would have no clue what it is I passed in. Most of the time I can't seem to find a common interface to be able to use inheritance, but creating functor structures that can have any constructor signature and a common operator() signature is one way I could work around such a mess. I don't think I've tried that route.

Yeah, long story short, I'm going to continue on this path for a while until something clicks or the game is finished. I've tried learning several different design patterns through the years with little to no success.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 16th, 2019, 6:37 pm

@Chili, don't worry, private: will be coming back. I've come across some places where I am currently forcing the same pattern I used for the enemies into all my classes and it's creating it's own restrictions. This is in turn forcing me to use accessor functions anyway since I don't know the type until I use the std::visit function, I can't just use the members directly. Not only that, but it restricts how diverse the behavior of different objects can be. For instance, I originally wanted to have a plasma gun charge up it's shot and give the projectile a differing damage amount based on the charge. This means you could either rapidly fire weaker plasma balls or slowly shoot stronger ones. With the design I've chosen, I made all the weapons have the same behavior based on a timed delay. The upside being there's one single interface ( Weapon ) while each representation of a weapon ( Gun, MachineGun, PlasmaGun ) is just the parameters for the Weapon interface. I could have done the same thing I did for the drawing and have a static function in these representations, but the Gun and MachineGun are basically the same thing with the exception of firing rate, and I'm trying to avoid code duplication. For some reason, I'm also trying to avoid branching on specific types. While if constexpr isn't branching, I don't want to allow myself to take that route. Once I do, I'll start using it everywhere and probably get burned out thinking I'm doing something wrong. I like the results I've gotten so far even if they aren't what I originally planned.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 16th, 2019, 6:46 pm

I have to implement the first level's boss and I'll be ready to tweak things such as health and damage values. This is where DDD would have come in handy. Something I'm dreading is coming up with variety of ship designs and enemy behavior. I think what will help me make these decisions is giving the enemies/ships different tiers. Does any one remember the game 1942/1943? This is the style of game I'm going for, but in space. In it, there were planes that just flew at you, some that shot bullets, some that released bombs ( I think ) and then the bosses where usually large planes where you had to take out it's engines and turrets. I know there are a few recent games of this genre where there is an insane amount of projectiles firing around the screen and I'm not sure I want to attempt something that challenging to play...yet. I don't think I could play test something like that LOL. Something a little more mild, but mostly similar is what I'm going for, somewhere between the games of old and new, casual yet entertaining for thirty minutes or so.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: What to do?

Post by albinopapa » December 19th, 2019, 1:49 am

Shoot'n'Scoot rough draft.

Okay, here's the rough draft of the first level. I can't believe it still took me 9 days to only get this far, but I'm liking it so far.

[ Controls ]
Menu:
Arrows Up/Down to change selection
Enter/Return to select the highlighted option

Game:
Arrows to move
Control to fire
1 = Gun
2 = MachineGun
3 = PlasmaGun

Previously this was a group game by Cameron, Luis and myself way back in 2013.

Originally, this game had a shop where you could buy your weapons and get upgrades, but before adding all that back in, I asked my son which would he prefer, Shop or Drops, and he said drops. So I guess I'll end up going that route. Going that route, weapon choice will be based on what items you pick up through the levels whereas if I go the shop route, you'll use your points as currency.

Things I'd like to add:
Something that I have seen in other games of this genre are orbs that spin around your ship. They could be used for defense as other entities would be dealt damage by hitting them, but also I'd like to have them be able to shoot projectiles themselves as well.

I need to have better collision detection as currently all collisions are detected through rectangle vs rectangle collisions, so false positives do happen a lot.

I still need to change the behavior of the ships as I don't think coming up with 40+ patterns is going to be feasible for me. I'll probably do what I mentioned in the previous post about doing it like the games 1942/1943.

Some kind of story would be helpful as well. Just to add a little context and motivation for the player.

I need to add sounds of course.

Currently, when entities overlap, damage is taken and given each frame of the collision. This will have to be changed, perhaps making the entity invincible for a few seconds or giving each entity a flag stating that it already collided with an object and reset the flag once it leaves that object's bounding box, though this might be more difficult to implement since it could be colliding with multiple objects at once.

I'd like to make the asteroids alter their direction when hitting another object. On a previous version of this project years ago, I had it so that asteroids would bounce in opposite directions if hitting another asteroid of the same size. You could shoot the asteroid to slow it down and also alter it's path slightly since bullets have way less mass than the asteroids. I have lost that version some time ago when I had a hard drive failure and forgot to sync it with github.

Going back to using sprites, I also want to be able to rotate entities, especially enemies and projectiles. Not so important for the player as you're bound to the screen with no camera movement.

Of course there is going to be more levels than the one I currently have, I just have to come up with more scenarios for missions and patterns for enemies, as well as a few more bosses.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: What to do?

Post by Slidy » December 19th, 2019, 8:32 am

albinopapa wrote:
December 16th, 2019, 6:37 pm
ECS would be another design pattern I can't seem to wrap my head around.
I had a little trouble understanding it at first too, what helped me with that was looking at open-source ECS library implementations and example usages of them. In the end, I didn't really like the very strict separation of everything to Entities, Components, and Systems so I ended up creating something more like a component system, but still utilized a lot of the DOD ideas in my implementation.

I don't think you have to have a clear-cut implementation of patterns, they're more like guidelines than rules :D. Feel free to take something and make it your own so you're comfortable with using it.

albinopapa wrote:
December 19th, 2019, 1:49 am
Shoot'n'Scoot rough draft.

Okay, here's the rough draft of the first level. I can't believe it still took me 9 days to only get this far, but I'm liking it so far.
Just tried it out, very sexy :o
I'm a big fan of these shoot 'em up type games, used to be addicted to Space Impact on those old Nokia phones when I was younger :P

Keep us posted on your progress.

Post Reply