Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Games Spelunky clone - Open source 2D platformer

Discussion in 'Projects In Progress' started by TwiiK, Jul 21, 2020.

  1. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    Throughout the last year I've been spending a bit of time here and there making a Spelunky clone in Unity using the sprites from the original game.

    Spelunky, the remake, is one of my favorite games, and even though I've always felt the original game feels janky by comparison I just love the original sprites and seeing as the entire GameMaker source code is available with sprites and everything I thought this would be a fun project to take on.

    I have no intention of recreating the remake or the original game, and instead my goal is to do what I want where I want to while using the games as guides where appropriate. So far I've been using the remake as a guide for movement and feel while at the same time trying to improve it where I see fit. A concrete example of an area where I felt I could improve the game was ledge grabbing. In both the original game and the remake you automatically grab a ledge if you are falling next to it which is extremely undesirable in certain situations, like when trying to fall past a tiki trap and instead you grab onto it and get speared. At the moment in my version you need to move towards the ledge to grab it. I also have fully analog movement on controller just to see how that feels with regards to precise movement. Movement in general is more slow paced as I feel both the remake, and especially the original game, have very twitchy movement.

    I will share my progress in this thread and I'm also hoping people will aid me in making this project as good as it can be.

    The entire project is available on github:
    https://github.com/oyvind-stromsvik/spelunky

    You're free to do whatever you want with it with one exception. The sprites are the original sprites and whatever restrictions the original license imposes on them still applies here. Everything else I've made from scratch so feel free to use it in any capacity. I'd love it if people helped out with issues and pull requests as well. Later on I'll see if I can keep things updated with compiled releases as well for easier play testing.

    I wanted to start this thread and share my progress a lot sooner, but I wanted confirmation from Derek Yu or Mossmouth before I did just to be sure what I was doing with the sprites was okay, but I've been unable to get in contact with them so I finally just decided to proceed under the assumption that this is fine. I've made sure to put all the sprites in their own folder with my homemade license file in it: https://github.com/oyvind-stromsvik/spelunky/blob/master/Assets/Sprites/LICENSE.txt

    As stated in that license file the reason I haven't included the original license is because it applies to everything while I'm only using the sprites, everything else is made by me from scratch. Feel free to enlighten me if you feel my approach here is faulty.

    Anyway, here's what the game looks like at the moment: spelunky.png

    Project goals
    My goal with this project for the time being is to create a solid 2D character controller while also creating a sandbox like Spelunky experience. I want players to be able to freely set the size of the levels, the amount of monsters, loot etc. to tailor their experience as they wish. I'll also try to recreate as much as I can from the original game mainly because the sprites are so nice and it would be a shame not to use them, and recreating things from the original can also serve as very focused tasks to take on when I struggle with the motivation for other stuff.

    My ultimate goal is to improve my skill and knowledge in creating high quality code and Unity projects while also creating a polished game experience. My biggest issue in Unity so far has been that I feel my projects become unwieldy the moment the scale of them reaches a certain point. I've learned quite a bit since last I worked on this project so I'm hoping I can put some of of that knowledge into effect going forward. With that I mean how to structure the project, how to structure the code, name spaces, state machines, how to write maintainable code etc.

    Controls
    The controls at the moment are WASD for movement, left shift for sprinting, space for jumping, up for ropes and right arrow for bombs. I know these are unorthodox platformer controls on PC, but as a FPS player primarily this felt more natural to me and I felt it better emulated the normal controller layouts as well. There's also controller support, Xbox 360 controllers at least, but I'll make sure the controls are rebindable etc. in the future. On the Xbox 360 controller I think the controls are the default Spelunky remake controls.

    References
    For the time being my character controller is based on this tutorial series by Sebastian Lague. Even though it was made for Unity 5 I still haven't been able to find a better 2D character controller for Unity:


    Articles I used as reference when converting my character controller to use state machines (which I'm still in the progress of doing):
    https://noahbannister.blog/2017/12/19/building-a-modular-character-controller/
    https://unity3d.college/2017/05/26/unity3d-design-patterns-state-basic-state-machine/

    Here you'll find everything you want from the original game including a playable executable and the GameMaker source code:
    https://spelunkyworld.com/original.html

    The original Spelunky thread on TIGSource (a fun and interesting read):
    https://forums.tigsource.com/index.php?topic=4017.0

    Informative and interactive website showcasing the level generation in the original Spelunky:
    http://tinysubversions.com/spelunkyGen/

    A version of the original game playable directly in the browser:
    https://tinysubversions.com/game/spelunky/

    An improved version of the original game:
    https://yancharkin.itch.io/spelunky-classic-hd

    My github repository:
    https://github.com/oyvind-stromsvik/spelunky
     
    Last edited: Jan 7, 2022
    NotaNaN, spark-man, Mark_01 and 2 others like this.
  2. munchou

    munchou

    Joined:
    Apr 18, 2020
    Posts:
    52
    Good luck with your project!

    I used to enjoy playing Spelunker (was it that name? not sure), although I wish there were more things to do as it became very boring after some time.
    And thanks for sharing :)
     
  3. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    I've fixed dozens of issues now and improved quite a few things in general. I want to make everything as solid as I can with the framework I already have before I start adding more content.

    I'm still not very happy with the code base. Currently there are two things that are really bothering me:

    The first things is that there are a ton of magic numbers all over for adjusting player position based on tile position etc. And I'm not sure how to best set pivots etc. to avoid this. Currently a lot of things have the pivot in the bottom center like the player etc., but the tiles have the pivot in the bottom left. I feel like if everything had the same pivot maybe things would be easier, but I'm not sure. A lot of the magic numbers can be fixed by just referring to tile width / height instead of hardcoding it, but still it's fiddly as hell to find the exact offsets for various things. And everything changes based on player facing which is annoying.

    Secondly I'm not happy at all with the current state system for the player character. There's way to many ways of setting everything. I feel there should only be a single way of setting velocity, input, graphics, audio etc. The state system is definitely a huge improvement over how it was before I added it. I was fairly easily able to add the "enter door" state which worked exactly how I wanted it to without much work at all. But it's still a mess and I have no idea if it's a good implementation to begin with or not. I evaluated a few different solutions before I ended up on this one, but I have very little experience with state systems.

    It will be interesting to see if I'm able to solve these issues on my own or not.

    New features:
    1. You can now enter the exit door and regenerate the level. Currently this just reloads the scene, but it works for now and makes it more pleasant to test the game.
    2. Bomb explosions can now chain if they overlap.
    Refactors/tweaks/changes:
    1. Made it so that ropes draw in front of ladders.
    2. Made it so that if we're climbing on a rope that's in front of a ladder and then transition to the ladder we automatically switch climbing animations.
    3. Made ropes draw in order depending on how high up they are on the screen. Note: I've later realized this doesn't do what I want and I will have to revisit this. I want ropes to draw based on the order they are placed so that a new rope always overlaps an existing rope.
    4. Made ropes pass through one way platforms both when you throw them up and when they extend down.
    5. Made bombs fall through one way platforms. Note: It's like this in the original. Not sure whether I like it or not, it's not very intuitive.
    6. Deleted the character controller class because I actually wasn't using it. I previously refactored it into a "physics object" class so that I could use the same pseudo-physics behavior for bombs and other objects as well. Will probably have to tweak this going forward and maybe refactor out a more specific character controller on top of this.
    7. Put one way platforms on their own layer to fix a few issues.
    8. Moved everything into a project specific folder and namespace all the scripts.
    9. Improved the sound effects a bit (subjective). Note: The reason I'm not using the original sound effects is because I can't stand "8-bit" sound effects. :p
    10. Moved explosion logic from the bomb object to the explosion object.
    11. Adjusted rope/ladder hit boxes so that it's not possible to stand between two and not grab either.
    12. Due to the above I also refactored the logic for grabbing a ladder so that we grab the closest one and not the first one in the list of available ones.
    Bug fixes:
    1. Fixed the bug where the player would attach to the wrong tile when grabbing ledges next to ladders. The problem was actually that we were grabbing the ledge itself, which shouldn't be grabbable.
    2. If two bombs are on the same tiles and explode at different times the second bomb don't fall down after the first bomb explodes the tiles
    3. Fixed exception that happened if we were transitioning into the hang state on a tile that was just exploded by a bomb.
    4. Fixed the bomb fuse sound being overridden by the bomb bounce sound if a bomb bounces after the fuse has ignited.
     
    NotaNaN likes this.
  4. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    2 questions for anyone capable of answering:

    1. How would i go about passing data to a state when changing to that state?

    Example: This is how I'm currently switching from the "in air" state to the "hanging" state when the player grabs a ledge:
    Code (CSharp):
    1. player.hangingState.colliderToHangFrom = hit.collider;
    2. player.hangingState.grabbedWallUsingGlove = true;
    3. player.stateMachine.AttemptToChangeState(player.hangingState);
    colliderToHangFrom and grabbedWallUsingGlove are just public fields on the state used by the state to determine the hanging behavior. It's crappy. Ideally I would like to pass this data to the state as I enter it. Or maybe there are even better ways of doing this, but that's how I would want to improve this at the moment at least. But it must then be generic in some way, right? Which I can't figure out.

    All my state related code can be seen here: https://github.com/oyvind-stromsvik/spelunky/tree/master/Assets/Spelunky/Scripts/Player/States

    2. How the frick do I avoid sounds from becoming louder if multiple of them happen at the same time?

    I made it possible to chain bomb explosions by letting exploding bombs detonate any bombs within the explosion radius. This works well, but the explosion sound becomes louder and louder the more bombs explode at the same time. This is actually an issue that I've had with Unity since the beginning of time, but one I've never made a serious enough project to bother investigating.

    But honestly, isn't this something Unity should just handle for me? Why do I have to guard against destroying my player's ears or speakers? :p

    I've tried experienced with effects like normalize and compressor in the audio mixer, but these F*** with the sounds too much to be usable. My other idea is to make my own audio manager which keeps track of all sounds playing and if duplicates are scheduled to play at the same time it ignores the remaining or spreads them out over multiple frames or something (if that even helps). But that seems like a lot of work for something I feel Unity should already have a solution for.
     
    NotaNaN likes this.
  5. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,095
    Are your bomb explosion sounds audio files volume normalized ?
    I normalize all my sound files, and mess with volume in game.
     
  6. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    I don't see how that would help me. Whether a sound file is normalized or not it would still become louder if you played multiple of it at the same time.
     
  7. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    New features:
    1. Added pushable blocks.
    2. Added a climbing glove that lets you grab onto any wall.
    3. If you're crouching when placing a rope it now extends down from your position. Allowing you to climb down for when I add fall damage.
    4. Removed the global light and added a point light to the camera to give a vignette effect.
    Refactors/tweaks/changes:
    1. Refactored collision logic so that blocks actually work and are able to collide with the player, stack with each other, stack on top of the player (later they should crush the player) etc. Mainly switched from single raycasts to raycastall so that I don't end up missing collisions.
    2. Ropes now draw in the order they are placed rather than based on how high up they are. This was actually easy to solve by just having a static counter on the ropes which I increase for each placed rope and use that for the layer order.
    Warning for anyone play testing: The block collision sound plays when blocks spawn as the level is generated and due to the audio "bug" where overlapping sounds increase in volume it can be very loud. I will need to find a fix for that.

    I also have another question, this time related to the physics/raycast/cast system:

    I've spent a lot of time experimenting with using Collider2D.Cast, Rigidbody2D.Cast, Physics2D.BoxCast and Collider2D.OverlapCollider as alternatives to the raycasts I'm currently using. My thinking was that doing a single box cast was both more performant and also more intuitive than doing a row of raycasts to detect collisions. Not to mention that a row of raycasts potentially could miss thin objects. BUT all of the above methods return imperfect collisions/distances. There's always a gap. Using raycasts they way I do currently I get absolutely perfect collisions. My character is always at 16.0, 32.0, 64.0 etc. coordinates when standing on tiles, moving against tiles, blocks etc. Especially in a pixel perfect game I value that a lot, but in general it just looks and feels cleaner.

    - Why is the wrong distance returned for the above methods? Why is there a gap?

    I've found responses like these: https://forum.unity.com/threads/2d-...nce-values-are-incorrect.625294/#post-4191172

    But that makes absolutely no sense to me. I'm not using the physics system. I'm just doing casts to detect collisions so I can handle them myself. Why does the actual methods return the wrong distances?

    It's not even possible to set contact offset to 0 as he suggests and even when you start setting it close to 0 things start failing in spectacular ways.

    - Why is contact offset a thing for the above methods, but not for Physics2D.RayCast?

    This makes no sense to me. Contact offset is clearly not used for raycasts, but it's involved in all of the above. And just so I make it perfectly clear. I would very much like it not to be. The way raycasts work is the correct way in my opinion. I'm not performing any collision handling using the physics system. I just need precise and correct distances returned so that I can handle the collisions myself.

    I looked at over a dozen kinematic 2d character / platformer controller projects I found on github and every single one that used the above methods had gaps / wrong distances returned for the collision detections.

    Fix this S***, Unity! :p
     
    NotaNaN likes this.
  8. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,730
    Almost 1.5 years since the last update... right.

    After taking a long break from Unity I have in fact been working quite a bit on this, especially the last month. My goal for 2022 is to do a lot more game dev. In fact I'm now only working 80% in my day job and I'm dedicating that extra day to game dev.

    I can't really remember what state the project was in 1.5 years ago and I've had a few mega refactor commits since then where I couldn't remember what the changes were so the commits have gibberish messages, so I don't really have the commit history to go on either.

    Anyhow, this is where the project is now and what I think I've change since the last update:
    1. Physics have been refactored a lot, and as a result of that improved a lot as well. Collisions with enemies, blocks, pushing blocks, being crushed by falling blocks etc. all behave as you'd expect now. There are now OnCollisionEnter and Exit events for physics which behave similarly to Unity's built-in physics.
    2. You can be crushed by falling blocks now.
    3. More or less everything that isn't a Tile in the project is now an Entity. Entities have classes for handling physics, health and visuals.
    4. The SpriteAnimator is now moved out of the project into its own package so that I can share it between projects: https://github.com/oyvind-stromsvik/upm-sprite-animator (I really enjoy this approach and one of my main goals with this project is to make a solid 2D controller so hopefully at some point that can be generalized and moved into it's own package as well)
    5. The project has been upgraded from version 2019.4 to 2021.1. I previously felt like I needed to stay on the LTS versions, but so much stuff happens in the tech releases and they feel just as stable to me. Especially this project which actually uses one of the new SRPs benefits from staying up to date imo. Anyway, I've upgraded all my projects from 2019.4 to 2021.1 and I'm sticking with this for now. Everything seems just as stable as before and I have access to a lot of new features now.
    6. Fixed a ton of bugs. The goal is that everything that's in the project should be solid and behave as expected. I've temporarily removed the spikes, arrow traps and climbing glove for this reason as none of them were implemented properly or implemented at all.
    7. I don't think there were any enemies in the game 1.5 years ago. There are now cavemen, snakes, spiders and bats.
    8. There's treasure you can pickup.
    9. The level generator has been tweaked slightly to allow it to generate 1x1 levels etc. for easier testing and debugging.
    10. I added this gizmos package to be able to draw gizmos that are visible in builds as well, to make testing and debugging easier: https://github.com/popcron/gizmos
    11. Added a quick'n'dirty debug UI to show stats about the player.
    I think that's the gist of things.

    And just as a reminder, the project is open source and hosted on Github: https://github.com/oyvind-stromsvik/spelunky

    Feel free to open issues or pull requests. One of my goals with this project is to try and improve my coding skills, and any criticism or feedback would help with that. For example I'm currently fiddling with whether I should stick exclusively to Unity components for building up my entities, inheritance, a mix of both, something else, how I would use interfaces etc. Just in general trying to create a solid foundation that is fun to work with and easy to add features and content to. I want a code base where it's clear where you'd set or change a value and ideally no other way of changing it so that things don't get out of control later on. Call order is another thing I'm trying to figure out. Like having multiple Update loops that run alongside each other just seems like a recipe for disaster to me. Ideally I would want a single game manager which updated everything in the game so that you could trace everything that happens back to a single source. But in general I haven't release a game before nor have I worked on any substantial games so I don't really have any experience with what works and what doesn't. I'm just trying things and seeing what makes sense for me.

    I recently also discovered this: https://spelunky.fyi/mods/overview/

    Which is a really cool mod loader for Spelunky 2 with a lot of cool debugging features built in. It's a really great resource for seeing how stuff works in that game. My goal with this project isn't to recreate Spelunky in any way, but Spelunk is a really solid platformer and I'm clearly basing this project around it so it's nice to be able to see how it works.

    Lastly here's a screenshot of how the game looks currently:
    spelunky.png
     
    Last edited: Jan 7, 2022
    NotaNaN and Kurt-Dekker like this.