Duke4.net Forums: Rock Sockem - Duke4.net Forums

Jump to content

  • 3 Pages +
  • 1
  • 2
  • 3
  • You cannot start a new topic
  • You cannot reply to this topic

Rock Sockem  "Because piggybacking on other threads is bad form."

User is offline   MusicallyInspired 

  • The Sarien Encounter

#61

What API did you say you were using for this? Or is it straight from scratch? SDL or anything?
0

User is offline   Kawa 

#62

View PostMusicallyInspired, on 19 December 2016 - 07:20 AM, said:

What API did you say you were using for this? Or is it straight from scratch? SDL or anything?
It's XNA, with FMOD Ex on audio dooty.
0

User is offline   Kawa 

#63

Note to self: Texture2D.FromStream() does not like DeflateStream.

But with that hurdle jumped, I now have zip file support, like in my Roguelike project. Which means that the game'll load file F from the following locations, in order:
  • the data folder, if it exists;
  • any zip file in the game's root directory;
  • RockSockem.zip specifically.

Text, text-like data, textures, music, everything. Pushing the actual file count down to only three total -- RockSockem.exe, fmodex.dll, and RockSockem.zip -- and allowing easy mods and DLC. For example, with a customized data\rock.png, you can have shenanigans like this:
Posted Image
Or download an SC-55 music pack (yeah right) and just drop it in there, as-is, and it'll Just Workâ„¢.
2

User is offline   Kawa 

#64

I've replaced the XML-based Tiled map reader with its JSON counterpart, using my own library. This, combined with my implementation of RFC 6902 JSON Patch allows such things like replacing a level's music: given test.json in the main zip, and test.json.patch in the data folder or another zip containing [{"op":"replace", "path":"/properties/music", "value":"stalker"}], this will do exactly what you might guess.
1

User is offline   Kawa 

#65

More work in progress:

//menu.json
{
  "main" : {
    "header" : "Main Menu",
    "items" : [
      [ "New Game", [ "page", "newgame" ] ],
      [ "Exit", [ "exit" ] ]
    ]
  },
  "newgame" : {
    "header" : "Select an Episode",
    "items" : [
      [ "Testing Track", [ "start", 0 ] ],
      [ "Back", [ "page", "main" ] ]
    ]
  }
}


Posted Image

This will later look up what levels comprise "Testing Track" in the levels.json file.
1

User is offline   HulkNukem 

#66

Where's your 200,000 USD Kickstarter?
1

User is offline   Kawa 

#67

Posted Image
menu.json:
{
  //Page
  "main" : {
    "header" : "Main Menu", //shown on top of screen
    "items" : [
      [ "New Game", "newgame" ],
      /* Simple items are detected and parsed like
      { "caption" : <0>, "actions" : [ [ "page", <1> ] ] }
      as a shorthand. */
      {
        "caption" : "Exit",
        "actions" : [ [ "exit" ] ]
      },
      {
        "type" : "text", //script is presumed
        "caption" : "\rShoutouts to Simpleflips."
      }
    ]
  },
  "newgame" : {
    "header" : "Select an Episode",
    "escape" : "main", //pressing Escape switches back to this page
    //and maybe adds a small "Esc: Back" in the corner?
    "items" : [
      {
        "caption" : "Testing Track",
        "actions" : [
          [ "setlevels", ["map","level2","hahano"] ],
          [ "page", "charselect" ]
        ]
      }
    ]
  },
  "charselect" : {
    "header" : "Select a Character",
    "escape" : "newgame",
    "items" : [
      /* Items like this are added by the program on load.
      {
        "caption" : "Rock Sockem",
        "actions" : [
          [ "setcharacter", 0, "rocksockem" ],
          [ "startgame" ]
        ]
      } */
    ]
  }
}

/* THOUGHTS
* optional left/top parameters on items?
* checkboxes and sliders!
*/


players.json:
{
  "rocksockem" : {
    "name" : "Rock Sockem",
    "sheet" : "rock.png",
  },
  "mesagranada" : {
    "name" : "Mesa Granada",
    "sheet" : "mesa.png",
  }
}

/* THOUGHTS
* instead of sheet/portrait/whatever file references, have only a "base" reference where the value "rock" resolves into rock.png, rock_prt.png, rock_etc.png?
*/


data\players.json.patch:
[ {
  "op" : "add",
  "path" : "/dukenukem",
  "value" : {
    "name" : "Duke Nukem",
    "sheet" : "duke.png",
  }
} ]

0

User is offline   K1n9_Duk3 

#68

Your project has inspired me to go back and work on my old Keengine project again. (Well, that and that fact that someone is actually working on a game using my engine.)

I rewrote the whole actor vs. tile collision detection and finally ended up with some very robust code. Unfortunately, the code isn't very fast, but it's fast enough for 60 fps with 300+ active actors on an old Core2 Q6600 @ 2.4 GHz. The original 2D Duke games only ran at 16-20 fps, but I have no idea what you're aiming at. I can share the general ideas, should you ever feel like adding slopes to your engine.
0

User is offline   Kawa 

#69

I'm aiming for and getting a nice 60-ish FPS. Share away.

Edit: does this make sense?
{
  "techbot" : {
    //DN1 style Techbot.
    // * Takes one shot to destroy.
    // * Simply walks around, turns around at walls.
    // * Avoids gaps -- will turn around to avoid them.
    // * Spawns shrapnel when destroyed.
    "imagebase" : "techbot",
    "health" : 1,
    "flags" : [ "dumbwalk", "avoidgaps", "mechanical" ],
    "celloffset" : [ 8, 16],
    "boundingwidth" : 16,
    "atlas" : [
      //left/top/width/height coords into techbot.png
      [ 0, 0, 16, 16 ],
      [ 16, 0, 16, 16 ],
      [ 32, 0, 16, 16 ],
      [ 40, 0, 16, 16 ]
    ],
    "animations" : [
      //indices into atlas
      [ 0, 1, 2 ],
      [ 3 ]
    ],
    "stateanims" : [
      //indices into animations
      //idle walk jump fall die
      0,     0,   0,   0,   1
    ]
  }
}


This post has been edited by Kawa: 30 December 2016 - 02:08 PM

0

User is offline   K1n9_Duk3 

#70

Okay, here it is:

Actor vs. Tile Collisions

First, let's take a look at the tile collision detection in Duke Nukem II:

Posted Image

On the right, you can see a bunch of boxes as placed in my level editor. On the left, you see how those boxes "land" in the game.

Every actor object in Duke2 has a hitbox that is used for all kinds of collision detection. To check if the object is blocked in a direction, the game checks the attibutes of the adjacent tiles. In pseudocode that would be something like this:

let X and Y be the tile coordinates of the bottom-left corner of the actor's hitbox
let W and H be the width and height of the hitbox, in tile units

FUNCTION BlockedLeft:Boolean()
	FOR i = 0 To H-1
		IF TileIsBlocking(X-1, Y-i) THEN RETURN TRUE
	NEXT
	RETURN FALSE
END FUNCTION

FUNCTION BlockedRight:Boolean()
	FOR i = 0 To H-1
		IF TileIsBlocking(X+W, Y-i) THEN RETURN TRUE
	NEXT
	RETURN FALSE
END FUNCTION

FUNCTION BlockedUp:Boolean()
	FOR i = 0 To W-1
		IF TileIsBlocking(X+i, Y-H) THEN RETURN TRUE
	NEXT
	RETURN FALSE
END FUNCTION

FUNCTION BlockedDown:Boolean()
	FOR i = 0 To W-1
		IF TileIsBlocking(X+i, Y+1) THEN RETURN TRUE
	NEXT
	RETURN FALSE
END FUNCTION


This code works perfectly fine for flying objects
The way "slopes" work in Duke Nukem II is that walking actors are allowed to move left/right even if the lowest tile to the left/right is blocking, as long as there is enough headroom to move up 1 tile.

The following functions return TRUE if the object could not move. (Note that the loops start with i=1, not i=0, to ignore the lowest tile)

FUNCTION WalkLeft:Boolean()
	FOR i = 1 To H-1
		IF TileIsBlocking(X-1, Y-i) THEN RETURN TRUE
	NEXT
	IF NOT TileIsBlocking(X-1, Y)
		X = X-1
		IF NOT BlockedDown() THEN Y = Y+1
	ELSE IF NOT BlockedUp() AND NOT TileIsBlocking(X-1, Y-H)
		X = X-1
		Y = Y-1
	ELSE
		RETURN TRUE
	END IF
	RETURN FALSE
END FUNCTION

FUNCTION WalkRight:Boolean()
	FOR i = 1 To H-1
		IF TileIsBlocking(X+W, Y-i) THEN RETURN TRUE
	NEXT
	IF NOT TileIsBlocking(X+W, Y)
		X = X-1
		IF NOT BlockedDown() THEN Y = Y+1
	ELSE IF NOT BlockedUp() AND NOT TileIsBlocking(X+W, Y-H)
		X = X-1
		Y = Y-1
	ELSE
		RETURN TRUE
	END IF
	RETURN FALSE
END FUNCTION


That is all you need for tile-based movement and "fake" slopes. Adapting the whole thing for pixel-perfect movement is surprisingly easy. Simply make everything in this pseudocode use pixel units instead of tile units!
Let's assume your tiles are 16x16 pixels. Your PixelIsBlocking() function could look like this:

FUNCTION PixelIsBlocking:Boolean(x:Int, y:Int)
	tx = x / 16
	ty = y / 16
	px = x & 15
	py = y & 15
	tile = GetTile(tx, ty)
	blockType = GetBlockType(tile)
	
	RETURN TexelIsBlocking(blockType, px, py)
END FUNCTION


How you implement the TexelIsBlocking() check is left up to you. You can use lookup tables (basically a 16x16 bitmap for each blocking type) or a switch statement. A switch statement could look like this:

switch (blockType)
{
	case 0:
		return false;
	case 1:
		return true;
	case 2:
		return (px <= py);
	case 3:
		return (px <= 15-py);
	...
}


The last thing you need to do is to make sure everything moves in single pixel units and that each object moves by 1 pixel in any direction only if it's not blocked in that direction.

If this basic algorithm was fast enough for 8x8 pixel tiles on a 286 CPU, it should work fine for pixel-perfect movement on modern CPUs.

There are even some possible optimizations. If you have certain blockTypes that are blocking every pixel and some that block no pixel, you can rewrite things to check the tiles at the egde of the hitbox first. If you find a completely blocking tile, then the object is blocked in that direction. If all tiles are completely empty, the object is not blocked in that direction. If you find a "sloped" tile, you need to run the per-pixel-version.

Note: This implementation is actually a bit different from what I used in Keengine, but the basic idea of using single-pixel movement is the same.

Edit: Fuck. The code needs to use Y-i and not Y+i.

This post has been edited by K1n9_Duk3: 31 December 2016 - 01:33 PM

1

User is offline   Kawa 

#71

Now that the first week of January is just about over, I'm once again free to work on whatever the hell I feel like (often nothing) instead of focusing on a single project as I'd promised. Though I've certainly been productive as hell! To celebrate, I added input remapping support to Rock Sockem. There's no interface yet, only a simple JSON file that's copied from defaults in the zip on first load, but it works. And with that, I also added gamepad support.

As for slopes... I return a bounding box for whatever solid tile we've hit, and this box is in pixel coordinates. There was code in the old project I built this from that let me step on higher boxes if the height difference wasn't too big. What if the part in the check where the hit tile's bounding box is determined were to recognize it as a sloping tile and adjust the top of the bounding box according to the actor's horizontal position on it? Standing next to a / slope and carefully walking into that tile, it'd return a sliver-thin box that's just a pixel higher up than our present altitude. This is allowed, so we can step into the tile and raise a pixel. Taking another minute step to the right, we're hitting the same "solid" tile, which now returns a somewhat higher-up bounding box. This is still an allowed altitude difference so we can step in and raise further up. A sixteen pixel difference in altitudes? No go, that's a wall.
1

User is offline   K1n9_Duk3 

#72

View PostKawa, on 07 January 2017 - 10:01 AM, said:

As for slopes... I return a bounding box for whatever solid tile we've hit, and this box is in pixel coordinates. There was code in the old project I built this from that let me step on higher boxes if the height difference wasn't too big. What if the part in the check where the hit tile's bounding box is determined were to recognize it as a sloping tile and adjust the top of the bounding box according to the actor's horizontal position on it? Standing next to a / slope and carefully walking into that tile, it'd return a sliver-thin box that's just a pixel higher up than our present altitude. This is allowed, so we can step into the tile and raise a pixel. Taking another minute step to the right, we're hitting the same "solid" tile, which now returns a somewhat higher-up bounding box. This is still an allowed altitude difference so we can step in and raise further up. A sixteen pixel difference in altitudes? No go, that's a wall.


That sound like it should work. Adjusting the top of the bounding box based on the actors horizontal position is basically the same thing that the TexelIsBlocking() function in my code example is supposed to handle. If all your tiles only ever have a sloped top, that might be enough. Just remember to check if the actor can move up before you let him walk into the tile and raise him. In the Commander Keen-style games, both the top and the bottom of the tiles may be "sloped", which makes things more complicated.

I think that allowing actors to raise more than 1 pixel (angle of 45°) is rather unrealistic, especially if you leave the horizontal speed unchanged. The only situation where such movement would make sense to me would be an actual set of "stairs". In any case, you will need to make sure that the walking animation fits the current situation, so that the actors won't appear to walk on thin air.
1

User is offline   Kawa 

#73

Thanks for your support on that theory. I'll need at least five beers before I dare try it, though.

What I do dare to implement while sober: a key remap interface. Imagine my momentary confusion when I change "down" from to S and suddenly I can't select the next item anymore. It even saves. All it doesn't do right now is let you remap the gamepad controls -- you'll still need to edit a file for that.
0

User is offline   Kawa 

#74

First attempt at non-tile-aligned floors: failure. Having a specific tile report its top is 14 pixels lower than it'd normally be, I can't walk onto it. Jumping on it breaks the thing in a way that defies description. I may need to put this on my bitbucket and invite others to try :lol:
0

User is offline   Kawa 

#75

Replaced the XNA BoundingBox uses with my own BoundingRects. Much more lightweight in contrast, trading a whole bunch of comparison methods and three-dimensionality for type tags -- solid, actor, slope, hazard...

Also, added support for Base64-encoded gzip-compressed tilemap data.
0

User is offline   Kawa 

#76

So I'm just thinking about control schemes. Being able to remap it aside*, what should the default gamepad scheme be? I've done some research...

The Super NES, WiiU, DS, and 3DS all have their action buttons with X as the top button, Y on the left, A on the right, and B on the bottom of the cross layout.
The Xbox and Dreamcast, and thus the XInput-compatible Logitech I use to develop with, has each pair inverted, with X on the left and B on the right.

Regardless of the order, according to TV Tropes' Stock Control Settings, jump is usually the bottom, fire the right, and run on the left. Something about letting you run and jump with just your thumb, I dunno. That'd mean that jump would be B on a SNES, and A on XInput, though the physical location seems more important than the label.

What are your thoughts on this? Also, what would/should the buttons to select and go back in a menu be?

* You can't remap the gamepad. Yet.
0

User is offline   MusicallyInspired 

  • The Sarien Encounter

#77

Posted Image

Bottom is definitely jump. I usually like the left button to be fire, but if there's a run button then yes that changes to the right because I do like to run and jump with just the thumb. For menus, bottom button for select, right button for back.
2

User is offline   Kawa 

#78

Thank you for your input. I like the pic.
0

User is offline   K1n9_Duk3 

#79

I don't use a controller for 2D platformers, but I own 2 gamepads that I have used for games like Grim Fandango, Escape from Monkey Island and basically any DRM-free LEGO game since LEGO Star Wars.

What I have learned is that you should not assume anything about the layout of the buttons. That's because, even though both the gamepads I own were made by the same company, the buttons are actually mapped to different numbers. On one gampad, the top button is number 2, left is 1, right is 4 and bottom is 3. On the other gamepad the top button is number 4, left is 1, right is 3 and bottom is 2. If you're worried about usability issues like those mentioned above, there is no way around remapping. Unless you're being a lazy dick and decide that an XBOX controller is the one and only controller you are going to support in your game.
0

User is offline   Kawa 

#80

Not so much "lazy dick" as "I simply only have code for XInput". Which, compared to DirectInput, is objectively much easier to work with. It's not exactly a hardware issue either, my Logitech has a switch on the back.

I'm told the XInput spec highly suggests having the bottom face button be A, and green if it all colored, which is also nice.

Plus, Rock Sockem runs on XNA so supporting anything else besides XInput would be... ill-advised to attempt. My other project, Noxico, isn't XNA, but the above stands.

This post has been edited by Kawa: 24 January 2017 - 02:51 PM

0

User is offline   K1n9_Duk3 

#81

View PostKawa, on 24 January 2017 - 02:47 PM, said:

Not so much "lazy dick" as "I simply only have code for XInput".

Oh, sorry. I wasn't talking about your project there.

I just had the experience that some games that were kinda designed for gamepad controls (like Tales of Monkey Island and Sam & Max The Devil's Playhouse) simply won't recognize either of my gamepads, which still pisses me off because the mouse/keyboard controls in these games were just horrible. I wouldn't mind if there's no option to re-map the buttons, as long as the game would at least recognize the gamepad and pretend it was the expected type of controller (as far as button/axis mappings are concerned). Since I didn't have any problems with the LEGO games, I assumed that either the developers were just too lazy to add proper gamepad support, or somebody is trying to make me go and buy an XBOX controller. Either way, whoever wrote the core game controller code was being lazy and/or a dick.

I wouldn't expect a free, fan-made game to fully support any kind of input device available on the market, and I certainly wouldn't get upset if it didn't support my gamepads. Commercial games with broken controls on the other hand :P ...

As I said before, I don't use gamepads for 2D platformers. As long as your game has proper keyboard controls, I'll be happy.
0

User is offline   Kawa 

#82

All due respect to other developers, DirectInput is deprecated. And there are third-party solutions to make DirectInput controllers work in XInput-only applications if you really really want to.
0

User is offline   Hendricks266 

  • Weaponized Autism

  #83

All due respect to Microsoft, XInput is not a full replacement for DirectInput.
1

User is offline   Kawa 

#84

No remapping of gamepad controls yet, but we do have this little detail now:
Posted Image

If I unplug my gamepad, it switches to "Enter - Select Escape - Back", and back to this when I plug it in. The exact keys are of course adjusted to match what you set up.
4

#85

That looks bad ass!!!
So how's it going with the enemy types?
0

User is offline   MusicallyInspired 

  • The Sarien Encounter

#86

Really cool.
0

User is offline   Kawa 

#87

View PostAP Dukefan94, on 25 January 2017 - 02:50 PM, said:

That looks bad ass!!!
So how's it going with the enemy types?

None are implemented yet. I'm not sure but I think it's a fear that's holding me back. All I have so far is a definition and sprites for one of the Techbots from DN1.
0

User is offline   K1n9_Duk3 

#88

When you get around to implementing the enemy types, I recommend using either separate lists for different types of actors/enemies or at least a segmented list, where different types of actor objects are grouped together.

Having all actor objects in a single list will make it much simpler to keep references between the objects alive (as was discussed before). However, if you are dealing with several hundred actor objects per level, being able to access certain types of actor objects quickly without having to iterate through the entire actor list will become crucial for overall performance.

In a simple game like Duke 1 and 2, you really don't want to iterate though a list of hundreds of actors just to find the few player shots that are currently active. Imagine you have 400 actor objects. To check if a single actor is hit by a player shot, for example, would take one full iteration through the list (all 400 items). Checking this for every actor will lead to something around 400x400 = 160,000 iterations. Now let's assume you can only have up to six player shots active at the same time (that's the limit in Duke 2 btw.). Being able to iterate only through the list (or list segment) containing the player's shots would reduce this to a maximum of 400x6 = 2,400 iterations.

You can optimize this even further by simply not updating actors that are off-screen. In Duke 1 and 2, there are many enemy types that won't update until they become visible (including the simple techbot that you created the definition for). Some of them stay active when they go off-screen, some become inactive again (like your techbot). By having the off-screen actors be inactive, you save a lot of CPU time because you are not updating them and therefore you are not checking if they are hit by any player shots. And by having them not move, you save CPU time on the actor vs. tile collision detection, too.

A segmented list is also a nice way to define which objects will be drawn on top or behind other objects. Unless you're using z-buffering or something like that.

I hope this information is/was helpful. It's definitely something that I did wrong when I worked on ReDuke. ReDuke will happily iterate though all actor objects to find the player shots or the enemy shots IIRC.

This post has been edited by K1n9_Duk3: 16 February 2017 - 02:49 PM

3

Share this topic:


  • 3 Pages +
  • 1
  • 2
  • 3
  • You cannot start a new topic
  • You cannot reply to this topic


All copyrights and trademarks not owned by Voidpoint, LLC are the sole property of their respective owners. Play Ion Fury! ;) © Voidpoint, LLC

Enter your sign in name and password


Sign in options