Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411490 Posts in 69371 Topics- by 58428 Members - Latest Member: shelton786

April 25, 2024, 02:46:26 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsLeilani's Island
Pages: 1 ... 42 43 [44] 45 46 ... 67
Print
Author Topic: Leilani's Island  (Read 411551 times)
Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #860 on: July 30, 2018, 12:28:45 PM »

Edit: Current list of collision system posts:

Collision System Part 2A: Solid Collision

Solid collision refers to the floors, walls and ceilings that Entities collide against when moving around the level.

Solid Collision Sources

There are two sources of solid collision shapes:

The Grid: the tiles that comprise the level. Each tile in the tileset has collision information defined. A tile can either be solid on all four sides, top-only only (can jump through the platform from beneath), or empty.

Entities: Each entity can also provide a solid collision shape - for example breakable blocks. Like the Grid tiles, these shapes can provide collision on all four sides, or top-only. The debug display shows that as well as their Body (orange rectangle), each breakable block has a solid collision shape (blue rectangle):



The Grid's solid collision is not drawn in the debug view; but you can imagine each tile that forms the rocky floor having a blue rectangle around it too.

Overview of a single game frame

Each frame a number of things happen to the Entities in a set order, which make up the process of moving the Entity and colliding it against the solid collision.

For each Entity
   UpdatePreMovement
For each Entity
   UpdateMovementY
For each Entity
   UpdateMovementX
For each Entity
   UpdatePostMovement

The main thing to note is that each function is called on *every* Entity before moving onto the next function. For example UpdateMovementY has happened for every Entity before UpdateMovementX happens.

I'll talk about it more below, but the core idea of the collision algorithm is that movement and collision on the X and Y axis are handled entirely separately. This allows for a lot of simplification of code.
   
UpdatePreMovement

Various things happen in this function, but the most important is that forces are applied to the Entity, affecting its velocity. Gravity is the most common example.

(The velocity of an Entity is a 2D vector containing the velocity for the X axis (horizontal) and Y axis (vertical). If the X velocity is 10.0 then the entity moves to the right 10.0 tiles per second.)

The force of gravity will increase the Y velocity of an Entity, causing it to accelerate downwards towards the floor (or a death pit).

Other forces can be applied during UpdatePreMovement, for example for the Leilani Entity only, if the player is holding the "Right" input then Leilani may accelerate on the X axis to simulate walking.

UpdateMovementY

This function is responsible for actually applying the Y component of the velocity to the Entity's position, and then resolving collisions with floors/ceilings that occur as a result of this.

Let's look at a diagram example of how this happens when Leilani jumps upwards into those breakable blocks.

The initial state: Leilani is just below the blocks. The green arrow indicates the direction of her velocity (the arrow doesn't correctly represent the magnitude of her velocity, it's just a rough indication that she's moving upwards.)



Applying the velocity to Leilani's position moves her upwards. We calculate the position that she should be in at the end of this frame, let's call this the Target position, which is represented by the red dotted outline. The Current position is the orange rectangle.



We need to prevent Leilani from intersecting with the solid collision above her. This check is done by taking three positions along the top of her Body.

For each of the three positions along the top of the Body at the Current position, imagine a line that stretches up to the corresponding position on the Body at the Target position.



Since the end position of each of these arrows is inside a solid collision shape, we know that there's an object in the way that Leilani needs to collide with. Since Leilani is moving upwards, we know that we're looking for a ceiling. So the position of the ceiling must be the bottom of the solid collision shape.

This is one of the times where handling collision only on one axis at a time makes handling the collisions very simple! Even if Leilani had some X velocity in this example, we can entirely ignore it. UpdateMovementY is only concerned with movement on a single axis. If the entity is moving upwards, it'll collide with ceilings. If the entity is moving downards, it'll collide with floors.

So we now know where the ceiling is. In this case, all three of the collision checks have found a ceiling at the same height above us:



Side note: It would also be possible for the collision checks to find objects at differing heights:



In this case we would only pay attention to the ceiling that's nearest to Leilani, and ignore the others.

The final result of this is that we position Leilani against the ceiling, so she doesn't clip into it!



The green arrow also shows that her velocity is now pointing downwards, so she will bounce off the ceiling in a satisfying way. The resulting velocity of a collision can depend on various factors, depending on the gameplay state or properties of the entity involved. In the case of the Leilani jumping against a ceiling, her Y velocity is set to a fixed value, so she will always bounce off ceilings at the same speed regardless of how fast she was moving upwards. This feels quite satisfying and also predictable.

(You may have noticed that I didn't deal with the part of this sequence where the act of jumping into the breakable blocks smashes them - this will be covered in a later part of the devlog.)

UpdateMovementX

This is the same as UpdateMovementY, but for the X axis. The logic and methods of checking for collisions are essentially the same.

Let's use this opportunity to show how it looks when Leilani is moving at a diagonal.

Again, the initial state, this time with Leilani moving both upwards and to the right:



As before, UpdateMovementY calculates her Target position above her:



And then detects the ceiling, preventing her from clipping into it:



Note that Leilani bounced off the ceiling which caused her Y velocity to now point down. But the X component of her velocity is the same as before so she also continues to move to the right.

UpdateMovementX calculates a new Target position:



And then detects the walls, moving Leilani against the walls:



When Leilani collides with a wall and is not rolling, her X velocity is entirely cancelled out, as you can see in the image - her velocity is now pointing directly downwards, so next frame she'll simple fall downwards from this corner. As with ceiling collisions, the behaviour of how the velocity is altered by a collision can vary, for example if the entity we're dealing with was an enemy that had been stunned and kicked by Leilani, then it would rebound off the wall instead.

UpdatePostMovement

This function mainly does visual / cosmetic things. The main example is that now the entity has finished all its movement and collision, UpdatePostMovement will update the sprite so that it displays the appropriate animation and is in the correct position.

I think that's enough for now... Part 2B will cover some additional intricacies that can happen at this stage of collisions.
« Last Edit: June 12, 2021, 07:42:02 AM by Ishi » Logged

MondayHopscotch
Level 0
**


View Profile
« Reply #861 on: July 30, 2018, 03:20:28 PM »

I've never even considered updating the axes of movement separately. I can see how that can greatly simplify trying to detect/resolve collisions.

I've read about the 'collision point' method, but I've never tried implementing it. I may have to revisit the idea, because it looks to have worked quite well for you so far.

Have you ever had issues where collisions are missed due to doing them that way? This example shows a contrived scenario where it would have an objective, though likely unnoticeable difference:


The first frame is the starting position and velocity, and you'd expect to hit the ledge (shown in black) and be deflected straight upward. However, by doing vertical first, then horizontal, you would happily pass right through. Obviously, frame rate and max velocity play into this even being possible, but I wanted to illustrate my point. I'm mostly curious if this kind of thing has ever impacted the design or direction of your game.

And just out of curiosity, in this post https://forums.tigsource.com/index.php?topic=46289.msg1124613#msg1124613 you talk about soft collisions. Did you create this nice soft collision effect by moving the outer vertical collision points in toward the middle of Leilani for her top collisions? (thereby ignoring collisions at the very edge of her body and letting the horizontal collisions do their job and snapping her out of the block)

Anyhow, thanks for the post! This sort of stuff fascinates me to no end.
Logged
Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #862 on: July 31, 2018, 08:29:12 AM »

Have you ever had issues where collisions are missed due to doing them that way? This example shows a contrived scenario where it would have an objective, though likely unnoticeable difference:


The first frame is the starting position and velocity, and you'd expect to hit the ledge (shown in black) and be deflected straight upward. However, by doing vertical first, then horizontal, you would happily pass right through. Obviously, frame rate and max velocity play into this even being possible, but I wanted to illustrate my point. I'm mostly curious if this kind of thing has ever impacted the design or direction of your game.

Yep that diagram is correct, in this case the corner would be missed. When the game is running at 60fps the movement on each frame is generally small enough that it's not noticeable, so it's not something I've ever been concerned about.

I don't remember if there was a reason why I chose to do vertical first rather than horizontal first - whichever one is done first would have a similar case where collisions could be missed, so I don't think it matters too much.

And just out of curiosity, in this post https://forums.tigsource.com/index.php?topic=46289.msg1124613#msg1124613 you talk about soft collisions. Did you create this nice soft collision effect by moving the outer vertical collision points in toward the middle of Leilani for her top collisions? (thereby ignoring collisions at the very edge of her body and letting the horizontal collisions do their job and snapping her out of the block)

I think I tried to implement the soft collisions that way at one point, but might have run into some strange side effects or something. I'll cover how they work properly in the next post Smiley
Logged

Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #863 on: August 02, 2018, 01:05:38 PM »

Edit: Current list of collision system posts:

Collision System Part 2B: Solid Collision Additions

Soft Corner Collision - Ceiling

In this post I demonstrated a soft collision feature. The old gif still demonstrates it nicely:



This allows Entities to smoothly brush past edges that they only slightly clipped into, rather than rebounding off it as if it was a fully solid object. It's surprising just how much of a positive difference this can make to the feel of the game, as long as it's not too over the top (you don't want to make soft collision so aggressive that the player actually has trouble hitting the blocks that they do want to collide with).

So here's our initial state, during the UpdateMovementY phase, where we expect Leilani not to bump into the block above her, but to brush past it:



The soft collisions are implemented pretty simply. There are two main conditions for it. It begins at this stage of the collision process where we do our three collision checks to find obstacles in our way:



The first condition of soft collision is that only one of the three collision checks found an obstacle; also that collision check must be the one on the left or the one on the right. This indicates that we are colliding with something near the edge.

In this example, only the leftmost collision check hit something.

For the second condition here, we check how far the X position of the collision check is from the right-hand side of the collision shape that it found. I'm indicating this by the dark blue area in this diagram:



If the X position of the collision check is close enough to the right-hand side of the collision shape - we can do a soft collision! We simply push the Entity's X position so it lines up with the right-hand side of the shape. We then end the UpdateMovementY phase for this Entity pretending that nothing happened.



Soft Corner Collision - Top edge of walls

Soft corner collision also works when moving sideways! The process is essentially the same regardless of which axis is being considered.

Here's a quick example of how the soft corner collision allows Leilani to bypass single-tile gaps when she's rolling quickly:

The initial state at the start of the frame. Leilani has rolled off the left-hand block:



The UpdateMovementY phase moves Leilani downwards:



The UpdateMovementX phase considers moving her sideways:



At this point she is in danger of colliding with the right-hand block and falling ungracefully down the gap. But since she only clipped with the very top of the block, soft collision can push her upwards:



Jump-through Collision

I've mentioned that collision shapes can be top-only (also known as jump-through platforms). Entities can pass through top-only collision shapes when travelling upwards, but will land on them like a solid floor when travelling downwards. This is pretty straightforward but worth covering with some quick diagrams:

When travelling upwards, top-only collision shapes are entirely ignored:



When travelling downwards, top-only collision shapes are treated like normal:



...but only if the bottom of the Entity's Body at the Current Y position is higher than the top of the top-only collision shape. If the Entity was already slightly beneath the collision shape, then it's ignored.



----------------------

End of Part 2

Well, that's all the time I've got! I've gotta get back to actually working on the game Wink

Note that the devlog post linked above, as well as talking about soft collision, also talks about collision priority, which allows certain block types to be bumped more easily by Leilani. I'll cover that in more detail during Part 3 of the collision series, where I'll be talking about Interactions between Entities.
« Last Edit: June 12, 2021, 07:42:13 AM by Ishi » Logged

MondayHopscotch
Level 0
**


View Profile
« Reply #864 on: August 02, 2018, 01:19:44 PM »

Any articles I've seen talking about the notion of collision point systems definitely undersold the method... You have a surprisingly intuitive and simple solution to these edge cases (no pun intended). The flexibility of your approach is definitely making me consider second-guessing how I'm approaching collision detection and resolution in my current project (currently using GJK/EPA algorithms).

Thanks for the write up - This collision series is great so far!
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #865 on: August 02, 2018, 03:18:45 PM »

Any articles I've seen talking about the notion of collision point systems definitely undersold the method... You have a surprisingly intuitive and simple solution to these edge cases (no pun intended). The flexibility of your approach is definitely making me consider second-guessing how I'm approaching collision detection and resolution in my current project (currently using GJK/EPA algorithms).

Thanks for the write up - This collision series is great so far!

Ishi has a really good understanding of exactly how his algorithms are working. The thing about collision detection and physics is: 90% of contribution to success is picking solid algorithms and understanding how they work together. Ishi is picking very understandable and solid algorithms, and as shown by his cool gifs, it works really well!

GJK/EPA are pretty complicated algorithms. Personally I would highly recommend to *anyone* using EPA to immediately rethink their plan and scrap it. EPA is one of those algorithms that I'd say is a "dead end", i.e. a bad choice. For my own collision system (not released) I use GJK along with cute_c2.h. The concept is to use GJK inside of some kind of Time of Impact loop, and to use cute_c2.h as a fall-back (cute_c2 has a GJK implementation, and the other fallback options). The overall algorithm I just describe is actually nearly identical to Ishi's work here -- just a little more generalized/abstracted.

A big reason I really love this devlog is Ishi has a good sense on practicality, and there's a lot to learn from him in this regard. I'm excited to see what he writes about entities/components/serialization and the like Smiley Ishi has proven very good at taking well-understood tech to create something really fun and of high quality. Personally I think this is a common theme transcending his entire project.
Logged
MondayHopscotch
Level 0
**


View Profile
« Reply #866 on: August 02, 2018, 09:14:14 PM »

GJK/EPA are pretty complicated algorithms. Personally I would highly recommend to *anyone* using EPA to immediately rethink their plan and scrap it. EPA is one of those algorithms that I'd say is a "dead end", i.e. a bad choice. For my own collision system (not released) I use GJK along with cute_c2.h. The concept is to use GJK inside of some kind of Time of Impact loop, and to use cute_c2.h as a fall-back (cute_c2 has a GJK implementation, and the other fallback options). The overall algorithm I just describe is actually nearly identical to Ishi's work here -- just a little more generalized/abstracted.

A big reason I really love this devlog is Ishi has a good sense on practicality, and there's a lot to learn from him in this regard. I'm excited to see what he writes about entities/components/serialization and the like Smiley Ishi has proven very good at taking well-understood tech to create something really fun and of high quality. Personally I think this is a common theme transcending his entire project.

I can't argue against that GJK and EPA are much more complicated in concept. I'm actually using them right now just to play around with the algorithms and try to understand them -- There's no game on top of them yet. I'm pretty good with maths, so I have a pretty good grasp of what and how they work. But these kinds of algorithms aren't for everyone - because if you don't understand the math, debugging issues will become impossible.

That said, the intuitive and elegant simplicity of what Ishi is doing is worth a ton when it comes to ease of use (specifically ease of extending functionality, as is shown by the soft collisions). It's funny, because seeing it all laid out and explained leaves me saying, "that makes perfect sense." Honestly, I feel that is the kind of reaction anyone would want when explaining how they've done something.  Hand Thumbs Up Right

« Last Edit: August 03, 2018, 04:56:47 PM by MondayHopscotch » Logged
Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #867 on: August 11, 2018, 11:22:47 AM »

Thanks for the nice words. I do try to aim for simple and practical solutions! Smiley I'll continue with the collision devlog posts soon - I began writing the next one, but was feeling a bit drained at the time and haven't got back to finishing it yet.

In the meantime, I haven't talked about any new features for a while.

Boost rings

I've had a mechanic in the game for a while where Leilani can trigger a series of "rainbow drop" collectables. Collecting each one before it hits the ground can give a reward of some kind.

Here's a very old example gif:



The problem I always had with this was what the trigger for the sequence should be. I added the floating ring for Leilani to jump through to begin the sequence - they are basically a copy of the Red Rings in the Mario games. I was never really happy with it.

I recently came to a decision - the trigger is still going to be a ring, but it'll act as a booster too, so it's multifunctional. There will be generic boost rings for moving around the level, but also rainbow-styled rings which act the same but begin the rainbow drop sequence.

The boost rings require Leilani to be rolling to trigger them, and enemies can also be boosted through them, so they already feel like a pretty solid game mechanic.

I took a lot of gifs of the rings as I developed them, so you can see how the mechanic came together:


First pass, with really basic functionality and basic sprite. The ring boosts Leilani in the same way that the tube cannons (on the left) do.


Tweaked the values for the ring boost so Leilani travels a more sensible distance.


Began polishing the phase where the ring grabs Leilani, it now brings her to the centre of the ring.


Slighty improved sprite and more polish on the grab phase.


Further improved sprite. If Leilani touches the ring on the edge, she swings around into the centre of the ring in a satisfying way.


First pass animations.


Adding sequence support to the rings so they can appear on demand, or trigger other things to happen.


Added support for enemies to interact with the rings.


First pass at adding diagonal rings.


Added interactions for when Leilani collides with the rings without rolling - she bumps off the ring.


Added animations to diagonal rings.

Finally, here's part of a level that makes use of the ring. I also added a visual effect when something boosts through the ring.



Phew, that was a lot of gifs! I didn't yet get around to actually making the rainbow version of the ring to use at the start of the rainbow drop sequences. I think it'll really work though - I like the idea of beginning the sequence by boosting Leilani in the right direction for her to collect the first drop.
Logged

Ashedragon
Level 2
**



View Profile WWW
« Reply #868 on: August 11, 2018, 08:03:19 PM »

...... I freaking love all of that. <'3 I feel like it's weird how her momentum drops so quickly after the ring stops taking effect, though. Might be a balancing act between having the curve look nice when the ring stops mattering vs not making her feel weird and floaty, if that makes any sense?
Logged

Ted
Level 4
****


View Profile WWW
« Reply #869 on: August 24, 2018, 12:05:06 AM »

This looks really satisfying! It's so cool to see the progress for mechanics in this much detail. Your posts are so informative, it's really inspiring!! Hand Thumbs Up Left Gomez Hand Thumbs Up Right
Logged

MegaTiny
Level 1
*


Wew lad


View Profile
« Reply #870 on: August 24, 2018, 03:46:59 AM »

I always love seeing how much work goes into stuff like this. Simple mechanic that opens up so many level design possibilities  Hand Clap
Logged

Sea Jay
Level 0
**


View Profile WWW
« Reply #871 on: August 24, 2018, 11:07:25 AM »

This game looks like something I'd enjoy very much. Any idea when it might come out and where?
Logged
Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #872 on: September 03, 2018, 11:27:56 AM »

This game looks like something I'd enjoy very much. Any idea when it might come out and where?

Currently focused on an eventual Steam release, maybe other storefronts / platforms but no guarantees. Not sure on a date sorry, just chipping away at the game until it feels like it's done! I won't lie, there's quite a way to go yet, though progress is good.
Logged

Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #873 on: September 03, 2018, 01:34:09 PM »

Edit: Current list of collision system posts:

Collision System Part 3A: Interactions

Interactions in Leilani's Island are a general way for two Entities to affect one another. The general flow is:

  • Entity A sends an Interaction to Entity B
  • Entity B may do something, depending on what the Interaction is
  • Entity B sends a Reaction back
  • Entity A may do something, depending on what the Reaction is

I already talked about these a bit in a previous post! I recommend reading that post before continuing.

And here's the image from that post, for easy reference:



Directions in Interactions

In that post, I describe the Interaction as simply being a type of Interaction - e.g. INTERACTION_COLLECT_DIRECT. However Interactions also contain a direction vector. This is a really nice general way of passing a bit of additional information along with the interaction, as it's very common to want to do the same Interaction in multiple directions.

For example, in the following interactions:

  • INTERACTION_COLLECT_DIRECT: The direction is not used.
  • INTERACTION_TOUCH_ROLLING_ATTACK: The direction will either be (1,0) for a rolling attack from the left, or (-1,0) for a rolling attack from the right. The recipient of the Interaction uses this direction to know which way to move as a result of being attacked.

The Reaction that's returned does not have a direction, simply the Reaction type, e.g. REACTION_NONE, REACTION_TOUCH_HURT.

Different ways to use Interactions

In the examples in that post, the Interactions are sent from one Entity to another, as the result of an overlapping collision between the two Entities. I'll talk about this type of collision a little more in Part 4 of this devlog series (Entity-Entity collision).

Interactions can also be used as part of the Solid collision that I've covered in this devlog series so far. So let's look more in detail at how that works.

Interactions during Solid collision - Breaking a block



Part 2 of this series covered how collision with a block like this is detected, and how we respond to the collision by preventing Leilani from moving through the block. But how does the block end up being smashed as a result of the collision?



At this part of the collision process during UpdateMovementY, we've found the Solid collision shape that is obstructing Leilani's jump. Remember that the Solid collision shape could either be a static part of the level's tile Grid, or could be an Entity. In this case, it's an Entity, the breakable block.

If the collision shape was provided by the Grid (the tiles that make up the level) then we don't need to do anything special. But for Entities, another step is inserted.

Before UpdateMovementY finishes up the collision and rebounds Leilani off the block, a function is called on the Entity that's being moved (in this case, the Leilani Entity).

Code:
bool cEntityLeilani::PreSolidCollisionWithEntity(cEntity *pOtherEntity, cVector2 direction)

This function gives an Entity chance to Interact with the Entity that's providing the Solid collision shape that's obstructing our movement. The information passed into the function is:

  • pOtherEntity: This is the breakable block that's above Leilani.
  • direction: This is (0,-1) because the collision occurred while moving upwards during UpdateMovementY.

The Leilani Entity deals with this situation like so: if we have hit an Entity from underneath, try bumping it.

Code:
// Hit things from underneath
if(direction.y < 0.0f)
{
eInteractions bumpInteraction = (m_Power == POWER_SMALL ? INTERACTION_BUMP_WEAK : INTERACTION_BUMP_STRONG);
eReactions bumpReaction = pOtherEntity->Interact(bumpInteraction, direction, this);

if(bumpReaction == REACTION_BUMP_BUMPED_REBOUND)
{
return true;
}
}

The game has various BUMP types of Interactions, the primary two being INTERACTION_BUMP_WEAK and INTERACTION_BUMP_STRONG. WEAK is used by Small Leilani, and STRONG is used by Big Leilani. WEAK bump can't smash large blocks, but STRONG bump can.

The block receives this Interaction. It's a small block, so it can be smashed by the bump, regardless of which strength of bump it is. So the block will be smashed as a result of this Interaction. Note that the block doesn't smash immediately - it'll wait until the UpdatePostMovement phase (see Part 2A), and then will actually deactivate itself and spawn particle effects, etc. This helps to keep the collisions consistent, because the block remains existent and solid even if multiple Entities collide with it and interact with it in the same frame.

The direction of the Interaction is (0,-1). This direction is used to adjust the particle effect that is spawned when the block is smashed - the block pieces will fly in a generally upwards direction. (Note that other types of block will make more use of the direction of the Interaction - see Part 3B of the series.)

The block sends a Reaction back - REACTION_BUMP_BUMPED_REBOUND. I'll discuss the various types of BUMP Reaction in the next part of the devlog. The important thing to note here is the REBOUND part - this indicates that the Entity that smashed the block should treat the collision with the block like a normal Solid collision, and bounce back off it.

A quick look at this Interaction and Reaction in the same diagram form as I used previously:



So finally, we can return to the normal flow of Solid collision handling. UpdateMovementY ends up finishing its collision handling as normal, by positioning Leilani's head up against the underside of the block, and adjusting her velocity so she begins falling downwards.



In Part 3B I'll cover some variations on the situation of Leilani colliding with a block, and see how the Interaction, Reaction and collision response changes.
« Last Edit: June 12, 2021, 07:42:30 AM by Ishi » Logged

JobLeonard
Level 10
*****



View Profile
« Reply #874 on: September 04, 2018, 01:47:31 AM »

Do you use any kind of system to quickly assign a collection of interactions to entities? Because I imagine it would get very tedious very quickly to manually have to code in all possible interactions and reactions for each unique entity type.
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #875 on: September 04, 2018, 12:10:41 PM »

At an un-named studio I had to deal with a really cruddy system for assigning pairwise interactions. No coding involved, but it was pretty much a big N^2 pairwise list... A to B, A to C, A to D, B to A, B to B, B to C, B to D, etc. It was a horrible process. The tool didn't mirror A to B or B to A, and entries were 99% of the time duplicated here. Since custom code couldn't be written for interactions, in subtle cases the distinction of A first or B first actually mattered -- for example a metal player walking on wood, or a wood player walking on metal. Each instance would play a different sound (players were vehicles of different materials).

Editing was mostly like editing an XML document except in GUI form.

Totally inflexible, a huge mess, and it took forever to make or change anything.

I guess I'm a fan of writing these kinds of things as plain C/C++ code  Well, hello there!
« Last Edit: September 04, 2018, 02:31:27 PM by qMopey » Logged
Ishi
Pixelhead
Level 10
******


coffee&coding


View Profile WWW
« Reply #876 on: September 05, 2018, 03:07:02 AM »

Do you use any kind of system to quickly assign a collection of interactions to entities? Because I imagine it would get very tedious very quickly to manually have to code in all possible interactions and reactions for each unique entity type.

This is made easier using both C++ inheritance, and by combining similar things into the same entity class but specifying a different type. A rough example of some of the inheritance structure, and types of entities:

Code:
cEntity
    +--- cEntityPlayer
    +--- cEntityPowerup (multiple types: flower, fire leaf, bubble berry, pineapple)
    +--- cEntityPickup (multiple types: shells, computer chips, rainbow drops)
    +--- cEntityEnemy
    |       +--- cEntityMaca (the little duck guys that are pretty weak)
    |       +--- cEntityCrab (the crabs that are spiky on top)
    |       +--- cEntityKickable (multiple types: bombs, coconuts, fruit, spiky balls, batteries)
    |       +--- cEntitySpinner (the spinning top enemies)
    |       +--- ... etc
    |
    +--- ... etc

cEntity doesn't provide any interactions or anything like that, so it's like a clean slate. It does provide the movement, physics, management of sprites and particle effects, that kind of thing.

cEntityPlayer is Leilani, so there's lots going on in this class as you can imagine, including lots of interactions with other entities, as well as receiving many different types of interactions from other entities.

cEntityPowerup and cEntityPickup are examples of multiple types of game objects being combined into the same entity. When these entities are created it typically looks something like this:

Code:
pEntity = new cEntityPickup(setup, cEntityPickup::TYPE_SHELL);
pEntity = new cEntityPickup(setup, cEntityPickup::TYPE_CHIP);
pEntity = new cEntityPickup(setup, cEntityPickup::TYPE_RAINBOWDROP);

So the various types of pickup share the same code, with behaviour (and sprites etc) changed here and there depending on the type that was specified.

cEntityEnemy is a beefy class. This handles the majority of the enemy behaviour and interactions. It has a set of generic enemy behaviours - walking, turning around, rolling, stunned, etc. The enemy is set up with certain parameters that govern its behaviour, for example walking speed, and whether the enemy is a robot or non-robot. These parameters are passed into the constructor by the classes that inherit from cEntityEnemy. It's not possible to create an instance of cEntityEnemy directly, only the inherited classes can be created.

cEntityEnemy also has some functions which can be overridden by the inheriting class, in order to further change its behaviour. A simple example of this is GetTopArmour():

Code:
virtual eArmour GetTopArmour() const { return m_EnemySetup.IsRobot ? ARMOUR_HARD : ARMOUR_SOFT; }

The cEntityEnemy version of this function returns ARMOUR_HARD or ARMOUR_SOFT depending on if the enemy is a robot.

cEntityMaca is a very simple class, thanks to macas being one of the simplest enemy types. They don't have any special conditions and don't override the GetTopArmour function. However, cEntityCrab does: crabs are spiky on top because of their claws.

Code:
cEntityEnemy::eArmour cEntityCrab::GetTopArmour() const
{
if(IsStunned() || IsRolling())
{
return ARMOUR_SOFT;
}

return ARMOUR_SPIKES;
}

It returns ARMOUR_SPIKES if the crab is in a state where jumping on it will hurt Leilani.

So when cEntityPlayer jumps on the enemy, it uses the INTERACTION_TOUCH_ATTACK_TOP interaction on the enemy. The enemy calls GetTopArmour() to decide how to respond.

  • ARMOUR_SPIKES: respond with REACTION_TOUCH_HURT which hurts Leilani
  • ARMOUR_SOFT: respond with REACTION_TOUCH_SOFT_REBOUND which allows Leilani to bounce off
  • ARMOUR_HARD: respond with REACTION_TOUCH_HARD_REBOUND which allows Leilani to bounce off, but tends to ping her off to the left or right at the same time

So you can see that for simple enemies like cEntityMaca and cEntityCrab there are quick and easy ways to adjust some interactions and reactions, but the vast majority of code is shared, so there's no need to rewrite things.

cEntityKickable is a very multi-purpose class, it's used for inactive objects that can be interacted with in a similar way to enemies, for example coconuts, bombs, batteries etc. As with cEntityPickup and cEntityPowerup the type of kickable is passed in when creating the entity.

Generally they can all be bounced on, rolled, kicked by Leilani, so that's why they inherit from cEntityEnemy. They set up their parameters in such a way that they do not do the normal enemy behaviour like walking around. Certain types like the bomb have lots of bespoke code (bomb timer, beeping animations and sounds, exploding) that should probably be a separate class, rather than being a different type within cEntityKickable, but it works fine.

cEntitySpinner has behaviour that's much further removed from a standard enemy. It spins (like a spinning top) and accelerates towards Leilani. It can't be stunned, rolled or kicked around like a normal enemy, so it has lots of bespoke code to handle all this. It still inherits from cEntityEnemy because it does certain enemy-like things, for example when all on-screen enemies are destroyed when the level is completed, spinners will also be destroyed.
Logged

ProgramGamer
Administrator
Level 10
******


aka Mireille


View Profile
« Reply #877 on: September 05, 2018, 03:39:05 PM »

I can't help but notice that you have some unusual prefixes on your various type and variable names here. Would you mind going a bit more in detail about what your naming convention is if you haven't already talked about it?
Logged

Trixler
Level 0
**



View Profile WWW
« Reply #878 on: September 05, 2018, 03:47:14 PM »

This so inviting and playful, great job on the progress. Love the color palette you chose.I'm not much into pixel art but this just bring back some found memories and with that said would play this game.
Logged
EJlol
Level 0
**


View Profile
« Reply #879 on: September 06, 2018, 12:22:29 AM »

I can't help but notice that you have some unusual prefixes on your various type and variable names here. Would you mind going a bit more in detail about what your naming convention is if you haven't already talked about it?

If I had to guess:
c: Class
p: Pointer
e: Enum
s: Struct
m_: member variable

The m_ prefix goes before the other prefixes. So for example if you have a pointer as a member variable the variable would be named m_pVariableName.
« Last Edit: September 06, 2018, 12:38:18 AM by EJlol » Logged
Pages: 1 ... 42 43 [44] 45 46 ... 67
Print
Jump to:  

Theme orange-lt created by panic