Demo Castlevania NES Remake

Hyomoto

Member
Castlevania NES Remake
by Hyomoto
Hello fellow Gamemaker community! You may recognize me from some of my previous projects, most notably my currently suspended remake of Final Fantasy on NES. If you are seeing a pattern here, congratulations! You are one hundred percent correct! I guess I just really like old games, and I like tinkering on projects like these. I'm currently tinkering away on a new prototype, and I wanted to share some of it with the community. Unlike my FF project I don't really have an end goal here other than to have fun and maybe learn, refine or apply some new coding techniques. I chose this game specifically because it's mechanics are relatively simple and the game is reasonably short, and like most NES games the graphics and sound aren't terribly difficult to acquire. Plus, I just really like old Nintendo games.

Full disclosure: I never played Castlevania as a kid, and I really don't know much about it. I didn't like Symphony of the Night, loading was nightmarish, and though I own a number of games in the franchise I've just never really been a big fan of it. So unlike Final Fantasy for NES which I am a huge fan of, I really don't know much of anything about Castlevania. Also, unlike Final Fantasy which has been torn apart and documented HEAVILY, there isn't really much information on how Castlevania works under the hood. There's maybe *just* enough documentation for me to remake it mostly faithful, but a lot of my work for far is based on pixel and timing measurements.

But let's get to the meat of this post, I put up a playable demo, but the main focus was I wanted to open a thread where I can talk about some of the challenges and solutions I employed in making it. In a way, that's kind of what I like doing most: helping other people. I've always enjoyed reading about other creator's work, so perhaps there's someone out there that will find mine helpful. This is all Gamemaker specific, so if you want to learn something about the game, or even just how to make programs in GM2, maybe this will help you out. I'm going to try out a format where I talk about different subjects in different posts and compile them in this thread so it will evolve as time goes on. If you have any questions, feel free to ask!

Controls:
Left, Right, Up, Down - Move
Z - Whip
A - Jump
Tab - Select
Enter - Start

Pressing up and whip uses your special weapon, most weapons cost 1 heart, stopwatch costs 5.
You have to press Jump while holding up or down to get on the stairs, this prevents the annoying 'getting on the stairs when I definitely didn't want to' of the original, also known as 'stair boss'.
In "Redux" mode, pressing Select uses your special item.
You can load your last checkpoint by pressing Select at the "press start key" message.
Download Castlevania
Current Progress: Engine 99%, Stages 99%
Latest build Indev​
 
Last edited:

Hyomoto

Member
Rendering Stuff
The prototype started out as a quick "NES" style engine I could build a game on top of. Underneath the Castlevania exterior, there's just a generic engine powering it. Now, it's not fair to say something as lofty as it emulates an NES, my goal isn't to build an emulator. Instead it's easy to just look at it as a platform layer with tools designed to make it easier to mimic retro-styled games, and that includes the rendering style. Something newcomers to Gamemaker may not think too much about is how graphics are rendered to the screen. I certainly didn't. But there's a certain benefit to having a hand in what the render is doing. Not least of which you'll know what the hell the render is doing. Since I wasn't planning on doing anything too fancy, I put together a really small rendering pipeline with the following goals:
1. A background, foreground, sprite and 'system' draw layers
2. A per-pixel palette shader
3. Automatic screen resolution handling
layers.png
The render itself takes place on four distinct layers, but each layer has some tricks up it's sleeve. Each layer is rendered to it's own surface, and then composited together to be drawn during the post-draw step by making use of the layer_script_begin function.

SYSTEM layer - In some NES games the score, lives, etc... is shown behind the player, and in some games it is drawn in front. Now, something you may or may not know about the GM2 rendering pipeline is you really can do whatever you want with it. You can put your layers in any order, and you can draw them in any order. You can assign code to each layer in your render, and if you were to, say, draw each layer to it's own surface, for example, you'd have a stack of surfaces you can do whatever you want with. Once you've drawn to a surface, it's pretty trivial to hit a switch in code that will draw the layers in any order that you want, regardless of how they've been set up in the room editor. In this case, the system layer can be drawn last, or first.
FOREGROUND and BACKGROUND layers - These are pretty self-explanatory, but using the tricks from the system layer, it is possible to achieve some interesting affects by messing around with the draw order. Generally however, these layers are meant to be used as described.
SPRITES layer - Now here's where things get a bit interesting. Many old users of Gamemaker are probably very familiar with the old 'depth' system. When GM2 came along it got rid of it, mostly, and there have been a number of topics on how best to handle depth rendering at this point. Personally I prefer the sorted ds_list approach, but with a bit of a twist. By default Gamemaker will render everything with the 'visible' flag set, and ignore everything else. However, that doesn't mean you can't draw these instances, it just means they aren't drawn by default. In this case when an object needs to be rendered it is created using a special function that will add it to a sorted list in a different, visible object that will then handle rendering it. The fun part of this is the object you assign to the render could be another render. If you had a window of buttons and objects, those could all belong to their own render, which would then be drawn by another render. This makes it much easier to handle complex object batches, though it's a bit overkill for a project like this one. That said, it's easy to do and adds a ton of flexibility so why not?

On Room Start, we do this:
Code:
var _layers        = layer_get_all();

surfaceLayers    = [
    0,
    0,
    0,
    0
];
for ( var _i = 0; _i < array_length_1d( _layers ); _i++ ) {
    var _name        = layer_get_name( _layers[ _i ] );
    var _surface    = 0;
    var _script;
 
    switch ( string_copy( _name, 1, 2 ) ) {
        case "FG"    : _script    = render_surface_foreground; _surface    = surface_foreground; break;
        case "BG"    : _script    = render_surface_background; _surface    = surface_background; break;
        case "IN"    : _script    = render_surface_instances; layer_instance    = _layers[ _i ]; _surface    = surface_instances; break;
        case "SY"    : _script    = render_surface_interface; layer_interface    = _layers[ _i ]; _surface    = surface_interface; break;
        default        : layer_set_visible( _layers[ _i ], false ); continue;
  
    }
    layer_script_begin( _layers[ _i ], _script );
    layer_script_end( _layers[ _i ], render_surface_reset );
 
}
This will attach a script to each layer that meets the naming convention. There's a small benefit to this I didn't talk about, but it also means I can use as many layers as I want, and they will all be rendered together to a single matching surface. This can help a lot with scene compositing in the room editor as you can mix tiles more freely without having make a bunch of compound tiles, but when it comes time to render it will treat all those layers as a single one.

The pixel palette shader is something you can find on the GM marketplace, but how it ends up being implemented can vary. In my case, the render object has a draw call that looks like this:
Code:
var _x        = x - camera.x + ( bbox_right - bbox_left ) * isMirrored;
var _y        = y - camera.y;
var _mirror    = ( isMirrored ? -1 : 1 );

if paletteSprite != noone { pal_swap( paletteSprite, paletteIndex ) }
    draw_sprite_ext( sprite_index, frame, _x, _y, _mirror, 1, 0, c_white, 1 );
shader_reset();
I'll probably cover this better at another point, but I can choose to assign a palette to any object in the render, and if it has one it will use it for palette swapping. If not, it will just draw the sprite as-is. You might also notice this deals with sprite flipping and camera positioning as well. The camera is just an object that can be told to follow another object, or given a position to move to. It will then perform that task independently of everything else. Since everything is drawn using the camera's coordinates, it's a lot like using views but without using views. I've never learned how to use GM2's cameras and I've never liked using Gamemaker's views, so this is just how I choose to solve the problem.

And lastly, automatic resolution handling is not all that interesting but it looks a bit like this:
Code:
window_bias        = ini_get( "system", "window_bias", 0.8 );
render_width    = 256;
render_height    = 224;

var _window_x    = ( display_get_width() - window_width ) div 2;
var _window_y    = ( display_get_height() - window_height ) div 2;

window_set_rectangle( _window_x, _window_y, window_width, window_height );

surface_resize( application_surface, render_width, render_height );
What this code does is simply try to find the closest fit to 80% of your monitor's vertical space. It's not a really elegant or fascinating routine, but it makes sure that the window will be a comfortable size for the user without swallowing the whole screen. Additionally, the game render size is decoupled from the window size, which allows me to avoid scaling artifacts. Anyways, that's the basics of the render for today. Until next time!
 
Last edited:

RangerX

Member
Is it that I didn't noticed and your FF remake is done ??
Anyhow, good choice of another iconic game there! I love Castlevania.
 

Hyomoto

Member
Sound and Music
So, some of the code that I use in projects now actually originated in the past. Once you've built yourself a reasonably good working thing there's little reason not to keep using it. A few years ago I got into writing really dull and uninteresting code, but the idea was to try and figure out how to make things that were, as I called them at the time, program agnostic. The proper term it turns out is 'decoupled', but the meaning is pretty simple: how do you build parts of the program that are not program specific? The render I just talked about is a good example, it displays graphics to the screen but has no interest or even understand of what any of them are or their significance to the project, it's job is quite simply to provide a graphics pipeline. When it comes to sound and music, the object I use actually originated when I opted into the GM2 beta. The original release was limited to the unregistered function, which meant that you were very highly restrained in the number of sprites, sounds, scripts and other objects that you were allowed to use. My Tetrix project is a recreation of the Tetris project I built under those conditions. Tetris doesn't have a lot of sounds, but it probably has more than you might expect. Once you start adding music tracks it's pretty easy to bump into that 10 sound limit. So I ended up writing an object that would play and loop music from a single sound file, and I would just pack all the music into that. It actually worked really nicely, and it's a technique I still use to loop my music in my games. At it's core, it's just there to make handling music and sound easier and handles the following tasks:
1. Play, stop, loop, and pause music with support for fading in and out
2. Provide sound 'channels' and interrupt logic
3. Handle volume/changes to volume
The music is actually the most complicated part of the object, as it has a whole logic engine making determinations about what should be happening. I really do need to get in there and clean it up some day, but like I said: when you've built a reasonably good working thing there's little reason to not keep using it, but that's why I won't be posting the code here. Like the render, it can be dropped in and out without breaking the project. As I said before, my goal with those projects I was working on a few years back was how to make code that was like a ghost, if you deleted the object in question it would simply stop working rather than crash the program. Granted, that's not really a imperative since it's not unreasonable for the game to expect all of it's pieces to be there, but it does mean that if you need to change, break, add or remove something that it isn't quite so difficult to get things up and running again. A big part of coding is cleanup. Once you've implemented something you really need to go back and do maintenance on everything you've been touching. When all your pieces are very independent, the amount of things any one piece can break is lessened and that's the goal.

As for the sound effects, as I said this is a NES-style engine so one of the "features" of that system was 4-channel sound. That's not quite true, but it is close enough. To recreate that I have four sound channels and I have the sound effects play in the channel they would have if it were actually NES hardware. That preserves the original feeling even if the music has more character since the sounds don't have to play in the same channel. You'll notice that the use of 'with' here, and the purpose is pretty simple: if for some reason there is no sound_manager, the code will simply not run. And second, it's more efficient than using sound_manager.variable a bunch of times. As I said before, this isn't a vital way to work, it's just the way I prefer to structure my engine code:
Code:
/// @func null sound_play( sfx, channel );
/// @param sfx
/// @param channel
var _sound        = argument0;
var _channel    = argument1;

with ( sound_manager ) {
    var _current    = soundChannels[ _channel ];
  
    if ( !is_undefined( _current ) && audio_is_playing( _current ) ) || _sound == STOP_SOUND { audio_stop_sound( _current ) }
    if _sound != STOP_SOUND { soundChannels[ _channel ] = audio_play_sound( _sound, 10, false ) }
  
}
Lastly we have volume. This one is actually pretty simple and simply makes use of GM2's built-in audio groups. When you add sounds to your project, they are typically added to the default group, but you can set them aside into your own custom groups. I simply put all the music into one and the sound effects into the other. Because 8-bit games don't tend to have very long or large music or sound files, it's not terribly difficult to load them all into memory anyways. After that the entire group volume can be changed with audio_group_set_gain(). So, if you'd like to add a way for the player to easily change volume, and have it even affect sounds that are playing ... well, there you go!

That's pretty much it for the sound and music. I'll talk a bit about the controller object next time. It's something I've covered a bit in my Final Fantasy thread, and I even have it for sale as an asset on the store, but once that is done I'll start talking about how I implemented the various game systems. Until next time!
 

Hyomoto

Member
Right, so it's late and I have business in the morning but I wanted to show off what I've accomplished. I still have quite a bit of work to do but I've nearly implemented all the little underlying systems that allow levels to be, well, levels. The issue I was having with Simon is I had implemented him as a sort of hierarchical state machine. While this honestly could work, and would just be a bit cumbersome to use, it did lead to some annoyances in making him do things. Namely, as the state stack grew information was lost and it was impossible to recover. This forced me to use a lazy implementation of things like collision detection. So I changed him ever so slightly to use a two state machine. The first state describes Simon as he is: walking, standing, ducking, etc... The hierarchical state refers to compound actions such as attacking, getting hit or picking up a powerup. Not all the systems have been fully hooked back up yet, but the bottom line is that rather than asking what state Simon came from, the system now asks what state he is in, and preserves data when those states overlap. Maybe I'll make a visual for this or talk about it one day.

The other big thing I did, and that you can see in the video, is I added support for platforms. This necessitated a complete rewrite of all of the collision code and made me want to punch it. To be fair, it's not collision that is difficult, it's how to I implement collision in a way that works within the model that I've put together. Basically there are two forces that make up collision, entity collisions and tile collisions. Let's have a look at the room editor!
screenshot1.png
For purposes of having nice, simple and quick collisions that align to the 8x8 tile grid everything is built out of I can just paint where the player shouldn't be. When the room starts, this layer is converted into an array I can access more rapidly, that makes these types of collisions pretty fast. And considering they are the most common, they probably should be. However, for collisions with entities like a moving platform, well: they could be anywhere. Or moving. Or invisible. What it comes down to is there are options, and unlike tiles I don't have the luxury of simply checking where they ought to be. I finally settled on a nice quick solution that essentially paints Simon as a line, and if that line intersects with the bounds of the platform, a collision takes place. To take things one step further, I can also specify what collisions are valid. In this case, the only place you can collide with this platform is above. That's how I'm able to jump upwards, pass through it, and land on top. It is, if needed, possible to define collisions from the other directions. The last part of this is I need to know when Simon is on the ground, but I extended this a bit so that if he lands on a platform it will tell me which one it is. That way when the platform updates it's position, it's also possible to update Simon's as well.

Now, I did a bunch of other work too including blocking out most of the next level which I'll show off eventually but for now I wanted to talk a little bit about these:
screenshot1.png screenshot1.png
Before GM2 I never used the room editor: it was a piece of crap. With GM2 though, there's quite a bit of stuff we can accomplish! The group on the left is used to define the level the player is on. When the room starts a pulse is sent out to the level object. If it exists, it will sends out it's own pulse to any object in it's group. The purpose of doing so can vary wildly, but what you see here is the most basic setup: it's starts playing music, and sets up the camera boundaries. The group on the right is a bit more complicated, but when the player leaves the first screen in level 1, they walk into the castle, a sound effect plays and then the screen transitions. In this case, when the player touches the 'E' (it's a level exit, by the way), it will activate the 'T' triggers on either side. The one one the left disables the secret behind the castle door, and the one on the right enables the SFX trigger. Now, none of this is actually seen by the player when the game is running since these objects are ignored by the render, but when building the level I can avoid using messy and complex scripts in favor of a very visual level building toolkit. Right now there are three types of triggers, sound effects, music, camera settings, level exists, spawners, and those good old 'secret' blocks to reveal a pork chop (or wall chicken). These all work off of a simple messaging system which calls out to anyone in it's group to perform it's task.

Anyways, that's all for today. Sorry about the poor layout, aimless rambling and no music in the video! I am guilty of throwing this one together at the last minute. Until next time!
 

Hyomoto

Member
What's an Object mean to You?
There's been a high volume of questions recently talking about parenting, but there seems to be some confusion as to what a parent is or even what you should use one for. The simplest way of looking at the parent-child relationship is categorization, and one of the more common uses is to slowly give shape to our game worlds. We break down our ideas into abstractions, then slowly build them into concrete forms:
download.png
This this case we've defined the concept of an 'animal', and said all animals have brains and a number of legs. Now, we could have just defined humans, dogs and cats as animals, but we've decided that a special category of animal, the pet, all have four legs and a number of fleas. The useful part of this is we can ask for 'an oAnimal' and anything that fits that criteria, whether it be human, dog or cat, will work. We could also request, specifically, a pet, or even narrow our search down to just humans. The categorization helps us define the boundaries of our objects and ensures, such as the example above, that when dealing with animals we can always expect them to have a brain and a number of legs. However, that's pretty basic stuff right there and I've only included it in case you just so happen to be reading all of this for the first time, the place where inheritance like this can really shine is through behavior. Behavior can mean a lot of things but the most primitive, and basic, is something that you can rely on to be true about every object in your program. In my case there is the creatively named, and quite important, "object". It simply ensures that every object in the program has the following attributes:
Code:
objName        = "object";
objParent    = undefined;
objPriority    = 0;
addToDebug    = false;
visible        = false;

// variable definitions
debugMe
isHidden
Some of this is largely just debug information, objName is used when I log something so I know which object it originated from, and addToDebug decides whether or not this object is added to that lovely debug interface you can see in my videos. That's information for a later day, the big three here are objPriority, objParent and visible = false. Since I control all the rendering manually, I want every object to be invisible by default. These items are then skipped when GM goes to render everything. objPriority and objParent are essentially who should render this object, and at what depth. Unlike GMS1.4, my depth starts at 0, and higher numbers mean drawn later (or higher up if you would like). However priority is relative to the render which is drawing that object, the renders themselves are drawn in order of their priority. What it comes down to is I don't have to worry about putting the HUD at 1000 to make sure it draws on top of everything. I'd just make a new render higher than the world render and add the objects to that, the renders themselves would then be drawn in order and voila. Done. Castlevania isn't that complex so it isn't needed, but that's the benefit of having a wide-open platform: it does what you need it to do and nothing else. I use a ds_list depth sorting method, so the renders are responsible for calling the draw event on each object:
Code:
for ( var _i = 0; _i < ds_list_size( objects ); _i++ ) {
    var _id    = objects[| _i ] & $FFFFFFFF;
 
    with ( _id ) { if !isHidden { event_perform( ev_draw, 0 ) } }
 
}
Since the built-in name of 'visible' is already taken, the isHidden variable tells the render whether or not to skip drawing this particular object. The objParent variable may not do quite what you think, as it has nothing to do with inheritance, but rather is used in the Clean Up event to remove itself from the object it currently belongs to. The most common use of this is the render, once an object is destroyed the render no longer needs to draw it and so it has to be removed from the ds_list. What it comes down to is that, from my perspective, I've added quite a bit of functionality to each object in my game but most of it is automatically managed in the background by virtue of inheriting this object. And that's what I'm talking about when I say you can inherit behaviors. Now the engine breaks down into two parts, the system objects which handle music, rendering, controllers, etc... basically the low-level tasks the game runs on top of and I've talked a little about before, and the game objects which starts with 'obj_world':
hierarchy.png
I may get further into the differences here but basically everything that exists in the game world, whether it be a trigger, sound effect or an enemy, it inherits from obj_world. It contains logic pertaining to pausing the game, setting up objects in the render when created as part of the room, and like object before it, some variables that I expect all objects in the game world to have mostly concerning how the object is rendered. If you remember before, I said the render is what will call the draw event for any object it holds, the draw logic itself is inherited from obj_world as anything in the game world can be visible (even things which aren't normally meant to be seen have this characteristic, since being able to see them is a good helper when it comes to debugging). Again, as part of our behavior tree we've now inherited how to be part of the engine, and how to be a part of the game world. The other benefit to this hierarchy is scope: in the given example I can call everything in the engine, everything in the game, or even further down the list going so far as to identify a single instance if needed. Now you may notice I have obj_world also split into two groups: obj_entity and another list. Game objects are things like triggers, sound effects, level exits, camera controls, spawners, etc... these are all objects that are part of the game world but they are very simple objects, with specific purposes. They need to exist as part of the world, but they aren't what the player normally thinks of (or even sees) when they play the game. The objects I showed in my last post are all those types of objects. obj_entity on the other hand is the entry point to being something tangible, but generally speaking the primary behavior of an obj_entity is that it can collide with other things. Whether that be the ground or another entity, if an object needs to move, it's probably an entity. And note the collide with portion of that sentence, technically I can define an object to collide with anything, and in some cases that is needed, but to add nuance: the player collides with a block, the block does not collide with the player. Hence the player is an entity and the block is just a world object.

That's probably enough for now. Until next time!
 
Last edited:

Hyomoto

Member
After a particularly taxing, and relatively stupid, string of bugs yesterday I'll be honest: I don't have a bit topic that I want to cover. So let's keep this one to this simple stuff: the enemies work, the collisions work, and the stage logic works. I spent a lot of time cleaning up the code base after several particularly nasty bugs came out of nowhere, and everything appears to be back and working as expected. Basically the foundation is done, there's nothing left I need to build to finish the project. At this point it is, quite literally, just down to adding content. There's a lot I'm not showing in this video, I've finished the entirety of stages 1-3, and if I'd just sit down and work on the ghost spawners (I'll be honest, I'm baffled as to how they work) I'd have an entire playable demo. My hope was to get that done today, who knows if I will, but for now I'm going to get a celebratory bacon cheeseburger and recharge my batteries. Here's a picture of the stage:
stage1_3.png
Until next time!
 
N

nlolotte

Guest
Looks awesome dude, really good work. As a huge fan of the series I can definitely see the jump and weapon arcs all seem very true to the original!
 

hogwater

Member
Thanks for making this thread, it's nice to get some insight into how people work.

How do you create a score display like that, where you start with a set amount of zeroes and then fill it in as the score goes up?
 

Hyomoto

Member
@hogwater - Well, hopefully the answer won't disappoint since it's not very complicated but I'll happily pull back the curtain:
Code:
/// @func string_number_places( number, places )
/// @param number
/// @param places
var _string    = string( argument0 );
var _length    = argument1 - string_length( _string );

return string_repeat( "0", _length ) + _string;
The interface itself might interest you too then, the only things it redraws every frame are your score, time and hearts.
Code:
draw_set_font( font01 );
draw_set_color( c_white );

if update    == INTERFACE_UPDATE_FULL {
    surface_set_target( surface );
        draw_clear( c_black );
      
        draw_text( 0, 8, "SCORE-" );
        draw_text( 0, 16, "PLAYER" );
        draw_text( 0, 24, "ENEMY" );
      
        draw_text( 104, 8, "TIME" );
        draw_text( 184, 8, "STAGE" );
        draw_text( 232, 8, string_number_places( game.stage, 2 ) );
      
        draw_text( 168, 16, "^-" );
        draw_text( 168, 24, "P-" );
      
    surface_reset_target();
  
    update--;
}
if update    == INTERFACE_UPDATE_QUICK {
    surface_set_target( surface );
        for ( var _p = 0; _p < 2; _p++ ) {
            var _health    = ( _p == 0 ? game.playerHealth : game.enemyHealth );
            var _y        = 16 + _p * 8;
          
            for ( var _i = 0; _i < 16; _i++ ) {
                var _pip    = ( _i >= _health ? 2 : _p );
                draw_sprite( gfx_health, _pip, 56 + _i * 4, _y );
              
            }
          
        }
        draw_sprite( gfx_box, 0, 128, 16 );
      
        if !is_undefined( game.playerSpecial ) {
            var _sprite    = object_get_sprite( game.playerSpecial );
            var _x        = 144;
            var _y        = 26;
          
            draw_sprite( _sprite, 0, _x, _y );
          
        }
       draw_text( 184, 24, string_number_places( game.playerLives, 2 ) );

    surface_reset_target();
  
    update    = false;
  
}
draw_surface( surface, 0, 0 );

draw_text( 48, 8, string_number_places( game.playerScore, 6 ) );
draw_text( 142, 8, string_number_places( game.time, 4 ) );
draw_text( 184, 16, string_number_places( game.playerHearts, 2 ) );
 

Hyomoto

Member
Alright, it's done. The entire first three stages are functionally complete. Some things could use tweaking, and there's a few little flourishes still missing but it's playable all the way through with all the items, enemies and etc...
I'm going to take a wonderful break, but next post will be about the development again. I might toss a demo up as well, but for now it's a video.
 

Hyomoto

Member
Let's Talk Collisions
So, if I were to start this whole project over again I'd probably think of a better way to handle collisions. It's not that the current way is bad, and the underlying logic was ... on the right path, it's just that the logic is little widespread. Now, when I'm talking collisions here I'm not talking about level geometry, mostly. That's a whole other topic, instead I'm talking about things like whip detection, picking up items and enemies damaging the player on touch. If you read my earlier posts you know collision detection is part of every obj_entity, but the way it's implemented is a little more ambiguous than that. The interesting thing about parenting is that you can use it to define behavioral patterns, but more explicitly you can define things that are true about a particular instance. In the case of obj_entity, they are inherited by almost everything: enemies, items, weapons, candles, etc... and behavior just isn't the same between them. For example, candles are entities, and they can be collided with, but they don't actually collide with anything. As part of an entity, I can define the type of collisions they should have:
Code:
if collisionBehavior != noone { script_execute( collisionBehavior ) }
Rather than explicitly tying collision detection to obj_entity, instead the potential for collision detection is assigned. The primary use is to remove it from objects that don't need it, but it also allows me to write special logic if the need arises. Not to dig into another topic altogether, but that's largely how my trigger objects work, they are a shell of behaviors, and I simply plug the behaviors in they need. Back to collisions however, the simplest types look like this:
Code:
/// @desc Collision Behavior
var _id                = id;
var _collideMax        = collisionMax;
var _collideWith    = collisionWith;
var _collideGroup    = collisionGroup;

with ( collisionType ) {
    if _collideWith & collisionGroup == 0 || _id == id { continue }
  
    if rectangle_in_rectangle( bbox_left, bbox_top, bbox_right, bbox_bottom, _id.bbox_left, _id.bbox_top, _id.bbox_right, _id.bbox_bottom ) > 0 {
        _id.meeting        = id;
        meeting            = _id;
      
        with ( _id ) { event_user( 2 ) }
        event_user( 3 );
      
        if _collideMax - 1 == 0 { break } else { _collideMax-- }
      
    }
  
}
Let's break down what we're talking about here: each object has a domain of objects it can collide with, this is used to simplify collision further for things like enemies that can only collide with the player. Why have them search for collisions with candles when they can't do anything with them? This also allows this script to also be further tailored based on how it's called. Next up is the _collideWith and collisionGroup. Sometimes the net that needs to be cast is a bit wide, for example weapons have to be able to collide with all sorts of things. However, only the whip can collide with the secret blocks you can break, and enemy attacks don't collide with enemies any more than player attacks collide with Simon. The whip has the same collision group as the destructible blocks it can destroy, along with enemies and candles. This is done through the use of a simple bitmask allowing groups to overlap as needed. It also, sometimes, might have to check itself because it belongs to a group it's checking, and this is a simple way to also allow it to ignore itself. Then we get into the meat and potatoes: we perform a check if the bounding boxes of the two objects overlap, and if so we set the 'meeting' variable for both objects then run some events. The collision event for the the initiator, and the collidee event for what was hit. This allows objects to have special behaviors depending on what they hit or are hit by, and when I said above that I'd do this system differently, this is a good example. It's a nice robust system, but it's barely used. Most collision effects are handled by the initiator, and my original intention was the opposite of that. It's not a bad system, I just haven't implemented it quite as consistently or strongly as I might like. Last, but not least, we have the max collisions. For reasons some things can only handle a limited number of collisions, whether for performance or mechanical reasons and once that number has been reached the checks are terminated. Though this may not work quite as intended, the with loop is a little odd, I'm not entirely sure break performs entirely as expected.

Now, to extrapolate on this, you might say, "That's all well and good, but I don't see anything about damage or anything else in there! How does this accomplish anything?" Well, that's where those collision events that are attached to each object come into play. If you've noticed nothing else, hopefully you've picked up on the fact that this code is all very generic. It really doesn't have much to do with anything except checking if two objects can, and have, collided. There's no mention of power-ups, invincibility frames, health loss or anything else in here. So, let's take a look at the collision event on the whip:
Code:
/// @desc On Collision
if attack {
    if meeting.object_index == obj_secret_block {
        if brick = false { brick = true } else { exit }
      
    }
    event_inherited();
  
}
Now, not to dig too far into how the whip actually works the generic idea here is that the whip is only 'active' during certain frames, which is determined by the 'attack' variable. When true it will check if the object being hit in this case is a secret block, and if we haven't yet hit a secret block, set it to true, otherwise ignore the collision. Again, you might be frustrated: where is the code where you actually attack things??!? DID YOU EVEN WRITE CODE TO DO THAT?!? Again, this is all behavior that is exclusive to the whip. The axe doesn't need to perform these types of checks, and so it doesn't. The 'generic' attacking logic is one step up, in obj_weapon:
Code:
/// @desc On Collision
if object_is_ancestor( meeting.object_index, obj_destructable ) && meeting.iFrame == 0 {
    if meeting.hitPoints > 0 {
        var _overlap_x    = ( min( bbox_right, meeting.bbox_right ) + max( bbox_left, meeting.bbox_left ) ) div 2;
        var _overlap_y    = ( min( bbox_bottom, meeting.bbox_bottom ) + max( bbox_top, meeting.bbox_top ) ) div 2;
      
        instance_create_for( sprite_render, _overlap_x, _overlap_y, layer_instance, effect_hit );
      
        meeting.hitPoints    -= damage;
        meeting.iFrame        = 24;
      
        with ( meeting ) { event_user( 4 ) }
      
    }
  
}
And there we have it, after all of the other code has been run, this is the code that finally does two things: checks if the object hit is destructable, checking if the object is invincible, spawning the 'hit' effect, subtracting the damage from the unfortunate recipient's health and finally running the 'On Damage' event for the object that was just struck. This is where that 'meeting' variable I set earlier comes in, it allows the striker, and strikee, to have knowledge of what they hit/were hit by, and make determinations. In this case it uses the position of the two objects to spawn the hit effect, as well as call back to the thing that was just hit. Again, this is another place I might go a slightly different route, but it is effective as is. It allows each object to deal with death and destruction in it's own way, if needed. And to borrow the whip as a specific example, even though it uses the same generic collision detection as everything else, it's been highly tailored to provide a very specific output. It can hit blocks other weapons cannot, it has special attacking rules, and will even produce unique results. If I wanted, I could even make the enemies respond to the exact weapon they are hit with since they have some knowledge of that. The through-line in all of this is what I've been talking about in the other posts: each object only has the logic that it needs, it shares all of it's code with all the other objects like it, then extends it out in places where it happens to be unique. Sometimes that's as simple as the graphic. All items Simon can pick up share 99% of the same code, they have different graphics and different code on pickup. Everything else about them is identical.

That's probably enough harping on that for one day, hopefully you found this interesting or illuminating and until next time!

P.S. I did upload a playable version, it simply contains everything I posted in the previous video but if you want to give it a try, feel free. And if you have and feedback or find any bugs, please let me know!
 
Last edited:

Hyomoto

Member
Let's Talk Collisions (the other kind)
The collision routines took me the longest to get right, and it's because it's one of those things that started out nice and simple but kept evolving. There's probably a lesson in that. Either way, I started with a tile-based collision system for most things, and later extended that to allow instances to serve as solid surfaces. This was needed for things like platforms that would be more difficult using tiles, and I also extended it to cover breakable blocks to simplify their implementation. Believe it or not, while the tile collisions are probably faster, the instance collisions are simpler. But let's talk a little bit about why I chose the systems that I did before getting into the guts. I'm sure you've had this happen before: you want a character to move towards a position and then, bam! They hit a wall. What should you do? The tutorial for GM2 suggests you walk back the player position until you find a free space. This creates two considerations in my mind: first off, if I want the player to be able to continue, horizontally or vertically, if they are not obstructed, and walking them back to a free spot is either going to stop me any time I touch anything or make continuing more complex. The second is just performance: why am I using a naive search against every possible obstruction? While this method could be implemented with instances, tiles are particularly useful if you are willing to align your collisions to the grid because you can just paint them in the room editor. In my case, when the room starts I check all 'data' layers and compress their tile ids into an array of bit masks. The purpose of doing this is a limitation of GM/it's simpler, but essentially I just flag a bit in the following fashion:
Code:
1 << ( tile_id - 1)
The benefit of doing so is now I can compare the player to the tiles they intersect with, and only the tiles they intersect with. I can take this one step further by also only checking tiles in the direction the player is going and also walking forwards for collisions, rather than backwards for a free space. The reason for that last part is perhaps the most important: since the tiles are all grid aligned, I don't need to find a free spot, I only need to find a collision and I can then simply place the player where they should be based on the position of the tile they collided with. In Castlevania I use 8x8 tiles for collision purposes, but since max acceleration when falling is only 5, even if the system can handle much higher speeds, generally speaking only a few tiles ever need to be checked, and often only one. That's a considerable improvement. As for the platforms, well, when it comes down to it I haven't come up with a particularly useful approach to checking for collisions with solid objects. I do have some ideas, such as you could order the objects by 'zones' and only check the objects that are part of that zone, but generally speaking there aren't too many platforms in Castlevania so I've taken the simplest route. In the same way I check for collisions with tiles, I check the side of the bounding box in the direction the player is moving, and if it is within the bounding box of a block, a quick bit of math will place it where it collides. I also perform these checks independently for horizontal and vertical movement, that way if the player hits something while jumping, they will continue in the unobstructed direction. I'll be honest, I'm not sure this is really the best way of handling this but it works and the performance is sufficient for Castlevania. There's something to be said for that as well though, many people ask what the best way to do something is. Now, I'm sure John Carmack is a pretty good source of information, but while it's definitely worthwhile to learn and improve: if what you make works, there may be no reason to "do better". You can increment a variable in increasingly convoluted ways, and simpler is often best for performance reasons, but when you are done if everything works the way you need it to: sometimes that's a good place to stop.
Code:
if freeze { _moveX = 0 }
else if ( collide & COLLIDE_LEFT && _moveX < 0 ) || ( collide & COLLIDE_RIGHT && _moveX > 0 ) {
    var _points        = ( _myHeight ) div 8 + 1;
    var _row_size    = ( _myHeight ) / _points;
    var _column        = ( _moveX < 0 ? bbox_left : bbox_right );
    var _block_x    = _column div 8;
    var _move        = _column + _moveX;
    var _distance    = ( _moveX > 0 ? ceil( _move ) : floor( _move ) ) div 8 - _block_x;
    var _direction    = sign( _distance );
    var _touch        = false;
  
    for ( var _p = 1; _p <= abs( _distance ); _p++ ) {
        var _next    = _block_x + _p * _direction;
      
        if _next < 0 || _next > _max_width { continue }
      
        for ( var _y = 0; _y <= _points; _y++ ) {
            var _block_y    = ( bbox_top + _row_size * _y ) div 8;
          
            if _block_y < 0 || _block_y > _max_height { continue }
          
            if debugMe { instance_create_for( sprite_render, _next * 8, _block_y * 8, layer_instance, obj_debug_tile ) }
          
            if system.collisions[ _block_y, _next ] & TILE_SOLID {
                x        = ( _block_x + _p ) * 8 - ( _moveX > 0 ? ( bbox_right - bbox_left + 1 ) + floor( bbox_left - x ) : 8 + floor( bbox_left - x ) );
                _moveX    = 0;
                _touch    = true;
              
                break;
              
            }
          
        }
        if _touch {
            onWall    = true;
            //moveX    = 0;
            break;
          
        }
      
    }
    var _px    = ( _moveX > 0 ? bbox_right : bbox_left ) + _moveX;
  
    with ( obj_block ) {
        if isSolid && line_in_rectangle( _px, _self.bbox_top, _px, _self.bbox_bottom, bbox_left, bbox_top, bbox_right, bbox_bottom ) {
            if _moveX == 0 || ( ( _self.collide & COLLIDE_LEFT == 0 || collide & COLLIDE_RIGHT == 0 ) && _moveX < 0 ) || ( ( _self.collide & COLLIDE_RIGHT == 0 || collide & COLLIDE_LEFT == 0 ) && _moveX > 0 ) { continue }
          
            _moveX    = ( _moveX > 0 ? ( _self.bbox_right + 1 - bbox_left ) : ( _self.bbox_left - 1 - bbox_right ) );
            //_self.moveX    = 0;
            _self.onWall= true;
          
        }
      
    }
  
}
There are a few things in here that might interest you if you've just been focused on making collisions happen. In this case things that can collide have a collide variable, which is just another bit mask that specifies which directions they collide with things in. In Castlevania's case, Simon Belmont doesn't actually collide with things in an upwards direction so the engine actually ignores collisions for him in that direction. On that same note, platforms only have collisions when you are falling. This means that Simon will be blocked by walls in every direction except up, and will only collide with platforms when he falls downwards onto them. This allows the classic 'jump up through platforms' behavior of oh so many games. The other thing you might notice is the 'freeze' check at the top. This is used for things like stopping the enemies when you use the 'stopwatch' power up, or when Simon picks up a whip upgrade and 'freezes' in place. I'd also point out this whole block of code is also behind a check if the game is paused. That means things can be paused globally, or individually, rather easily. And lastly you may notice the 'onWall' (vertical has onFloor), which is used by things like the enemy AI to turn itself around when it hits wall.

So what about the game itself? The entire first level is done, I finished up the last of Simon's behaviors. He now has a hard landing event, I rewrote the controller input logic to get rid of duplication and condense everything, and started the bones of a 'Redux' mode. Similar to the idea I had with Final Fantasy of having a CLASSIC mode, I do want to preserve the original as much as possible. However, I'm not nearly as big of a fan or a purist of Castlevania as I am for Final Fantasy. And I can say there is some stuff in Castlevania that just, from a design perspective, doesn't make a lot of sense and the Redux mode will have a few changes, possibly even some new stages since out of all the work that goes into this project, making levels is actually the least time consuming. As for what's next I'm going to try and get the next 3 stages done and I'll update the demo to include them, that will also include the Redux mode. Right now the major feature of Redux is this:
redux.png
The placement of the rosaries and invisibility potions generally don't make any sense, so in Redux you can actually hold onto it and use it when you'd like. You can only hold one at a time, and I may add a heart requirement to it eventually, but for now it lets you use the powerup when you think it might be useful rather than the often completely useless locations they are currently placed. Until next time!
 

Hyomoto

Member
Just a quick little update today, I finished with stages 4 - 6. That's 1/3 of the game down. As I said before I included the new 'Redux' option as well, which basically just allows you to store the Rosary or Invisibility potion and use it when you'd like rather than only on pickup. In the future I did put in a backbone for hooking in custom stages and the like, but for now it is basically just that feature. I'm interested in performance mainly, so let me know if it seems to run oddly. Obviously if you find any bugs I'd appreciate it. Another round of dousing and the game is pretty stable, but given the odd ones that pop up still I'm sure it's got something waiting in the wings for me. Next I think I'm going to write about how the enemy spawner logic works, but until next time, enjoy!

Download Castlevania
 

Hyomoto

Member
Putting enemies in your level
This might seem a little mundane, "@Hyomoto," you say, "I'll just drop the enemies into my level!" Well that may be the case, but it definitely comes with some caveats and limitations. The big one is that, depending on how you set things up, all your enemies are going to start when your room does. This is why I use a spawner object to place all my enemies in the world. In general, Castlevania has 2 types of spawns, off-screen and on-screen. The off screen spawns are generally an area that while Simon is in, the enemies will continue to spawn in from the left or right side of the screen. After staring at the logic for a while I tried to approximate this the best I could, but it seems to be that it will attempt to spawn the enemy at the same Y coordinate as Simon, in the direction he is facing. If you are facing right, enemies spawn in from the right, and if you face left, they come in from the left. This one is actually pretty tricky because I WISH all types of these spawns were that reliable. Now I'm not sure I talked about this before but almost every 'world object' has a 'trigger key'. The simplest way to think of this is a radio frequency, when something happens such as a trigger goes off, it calls out on that frequency. And object that is on that frequency will hear it and perform it's action. In the case of area spawners, they have a 'spawn key' that links them with their boundaries:
spawn key.png
The middle object is the spawner, and you'll notice the 'targets' to his left and right. The two on the same plane belong to the spawner (the third belongs to another nearby spawner not pictured). Now here's where I do get to pat myself on the back a little bit. I could have made a 'left' hand target and a 'right' hand target. However, that just adds another object for me to manage. Why not just do this instead:
Code:
if spawnKey != 0 {
    var _key    = spawnKey;
    var _left    = room_width;
    var _right    = 0;
 
    with ( obj_spawn_marker ) {
        if spawnKey    == _key {
            _left    = min( _left, x );
            _right    = max( _right, x );
         
        }
     
    }
    areaLeft    = _left;
    areaRight    = _right;
 
    if debugMe { logID( id, " spawn area - ", _left, " <=> ", _right ) }
 
}
When the spawner is enabled, it will check all the spawn markers, find which ones share it's 'frequency' or spawn key, and then decide which one is the most left and which one is the most right. Those become the boundaries. If I wanted to add a Y axis to this it would be pretty easy to just do the same thing. Anyways, as long as Simon is in these areas, the spawn logic for that spawner is enabled and the enemies will appear to make his journey hell.

The other type of spawner is the on-screen spawner, and the logic here is pretty simple. It only spawns the enemy once you come into range. It will also 'reset' if it leaves the screen so that an enemy can spawn again when you return. Now this does create an issue if we were to apply that at face value. What happens if the enemy is on screen and we were to say, walk back and forth rapidly? It would keep triggering the spawner and we'd end up with a whole mess of enemies. In this case the way it works is it will always spawn an enemy when it first comes into view, but it doesn't reset unless there are fewer than 1 enemy on screen. This does create situations where you can prevent them from resetting even if you leave the screen, but it's incredibly close to how the original game worked. I did run into an interesting issue with this spawner that concerns order of operations. Generally a lot of logic doesn't matter if it runs first or second, whether you move first or the enemy does make a difference, but generally it's imperceptible. However, if the camera updates before the player, it will make it look like it 'lags' behind the player movement. In the case of on-screen spawners, there is a single frame where it's possible the camera may not update to Simon's position. The player would never see this so it doesn't matter, visually. However, if Simon were to start on the right side of the level and there was a spawner on the left side of the level, it would cause it to be set off. This also wouldn't normally matter because the enemy would despawn and the spawner would reset before Simon saw it Unless the enemy also doesn't despawn. Wow, what highly contrived, and very specific scenario! Well, that's what I ran into just yesterday.

When you start the room all spawners are off. The point of this is so that the level can be fully prepared before the player is given control and the world starts. Then all spawners are turned on. In this case the spawner was updating before the camera, and thus was on screen. The enemy doesn't despawn and so while testing the level I was rather surprised by a skeleton ambushing me in a part of the level with no skeletons! The problem is one people might be familiar with in GM: how do I ensure X happens before Y? In this case, pushing the logic to the camera follow logic is already in the End Step because it has to happen after Simon has moved. The solution therefore was to let everything happen before turning spawners on again by pushing the spawners activation into the End Step. The solution isn't difficult, but it is funny how you run into these unexpected and weird scenarios sometimes. Since all levels up to this point were either left-to-right, OR had enemies that would despawn if they did happen to spawn off-screen, I simply hadn't run into that limitation.

Progress Update! Stages 7, 8, and 9 are done and I'm working on the boss. I'll probably have a new build up later today. So if you like the project there's that to look forward to. Until next time!
 

Hyomoto

Member
I apologize for the two quick posts, new build is up. Check the OP. Contains levels 1 - 9. Let me know if something breaks, or if you break something :D

EDIT: Well, it turns out I broke tons of stuff, so I just patched the build and ran through it. It contains fixes for the following problems, aka it's now actually playable. Very sorry about that:
Code:
041418b -
    * Fixed crash picking up Invisibility Potion on Classic
    * Fixed crash when a Fireball is created
    * Fixed crash on level2_1 start
    * Fixed Vampire Bat soft lock
    * Fixed Medusa Head soft lock
    * Fixed Ghost palette
    * Fixed Fireball death behavior
    * Adjusted bat flight path to be less erratic
    * Adjusted fishman behavior to be less erractic
    * Fixed enemies sticking to walls
    * Adjusted vampire bat fight
        - Changed attack to a swooping arc
        - Added tell just before attacking
    * Fixed hidden block graphics on level2_1
    * Fixed hidden block graphics on level2_2
    * Fixed hidden block graphics on level2_3
EDIT2 : My, I am good at this. Turns out the last build was in debug mode, so uh... sorry about that. The current build should work as expected. If it doesn't I GIVE UP, I'M NO GOOD AT THIS.
 
Last edited:

Hyomoto

Member
Things really are coming into the home stretch, I finished the last two 'basic' enemies and stages 9 - 12. There's now only 2 bosses, 6 stages and some outstanding QA stuff to address. New build is in the OP.
Code:
041618a -
    * Added stage 10
    * Added stage 11
    * Added stage 12
    * Added eagle enemy
    * Added bone dragon enemy
    * Added boss Frankenstein and Igor
    * Fixed projectiles could be deflected with the back of the whip
    * Fixed enemy health doesn't show full on level start
    * Fixed level doesn't restart if Simon is knocked into the water from damage
Some under the hood changes may have broken things, it turned out the work around I was using to let the player 'hit' projectiles was allowing them to be defeated in unintended ways so I had to switch their class to destructible so they could leverage the code already used to inflict damage and destroy things. This wasn't actually that big of a change, it only required me to move the weapon setup event but I could have missed something, or there may be an unexpected interaction. As always, if you find and bugs or have other issues, please let me know. Until next time!
 
This looks really amazing! I have been away for awhile, came looking to see how the Final Fantasy remake was going. Only I find you remaking one of my other most favorite NES games, so COOL!!
 

Hyomoto

Member
This looks really amazing! I have been away for awhile, came looking to see how the Final Fantasy remake was going. Only I find you remaking one of my other most favorite NES games, so COOL!!
Luckily this one will be finished. Should have three more stages tomorrow, might be a bit longer for the last three. But it's almost done. If you'd like to tinker with it, how would you like to design a stage?
 

Hyomoto

Member
I should probably get around to doing some sort of development update, but for now you'll just have to accept a new build, a list of fixes/changes and a video of me failing to beat the game. Build is in the original post as usual. Enjoy!
Code:
042118a-
    * Added stage 13
    * Added stage 14
    * Added stage 15
    * Added axe knight enemy
    * Added grim reaper boss
    * Added extra lives at 30000, 50000, 130000, etc...
    * Adjusted leather whip damage
    * Adjusted passability on stage 10 for fishmen
    * Fixed simon doesn't always die from fatal damage
    * Fixed starting door in stage 7 allowing Simon to enter the void
    * Fixed broken respawn and continue point in stage 10
    * Fixed cross uses wrong palette in weapon bar
    * Fixed cross uses wrong palette as item pickup
    * Fixed soft lock if Simon is killed and then thrown into a pit
    * Fixed timer doesn't restart on level end
 
042118b-
    * Fixed timer doesn't restart on level end
    * Fixed possible issue causing controllers to not function properly
    * Fixed screen not scaling properly
    * Fixed crash when using stop watch on bone dragon
    * Fixed bone dragon isn't affected by stop watch
 
042118c-
    * Fixed grim reaper palette
    * Added item drops to enemies
    * Added multiplier as item drop
 
Last edited:

Hyomoto

Member
I'm not sure how much time I'll have available to work on this in the next few weeks so I wanted to address a few QoL concerns people have voiced, and try to get in some of the missing functionality. The major change is adding save support. Castlevania isn't a long game, but it can be a hard game, especially if you haven't played it before. In that spirit, the game will now save your progress at each continue point. On the main menu, where it says "PRESS START KEY", if you ignore that and press SELECT (or Tab), it will return to the last continue point you were at when you left off. I'm sure for many this leaves quite a bit to be desired, it doesn't preserve lives, weapons, score or anything like that, it's as if you chose 'CONTINUE' at the game over screen. This is partly because I don't feel like enabling save scumming, but also just because a more robust system would take much longer to code and I wanted to at least give you the ability to continue where you left off. Anyways, build is in OP and here's the patch notes:
Code:
042218a-
    * Fixed bosses drop items
    * Fixed joystick support
    * Fixed being able to spam START on the main menu
    * Fixed bad respawn point on stage 5
    * Added basic save support, press SELECT on main menu
    * Adjusted weapon drops become another item when you already have that weapon
    * Adjusted enemy drop rates
    * Adjusted music compression, saves 15mb of download and HDD space
 
Last edited:

Hyomoto

Member
Resolution and Scaling
Hey, so let's talk about this one, eh? Something that's been true about most my projects for a while now is that they use a form of adaptive scaling, and GMS2 has made it much easier to implement than previous versions of GM. To renew a line of discussion I've brought up before, the draw pipeline in GMS2 is generally very powerful once you start to dig around inside of it. I do not claim to be an expert in this field but I've spent a bit of time coming up with solutions to what I consider to be the resolution problem, and given how many people ask this question it's probably not a terrible one to try and answer. So let's talk about that by starting with a little bit of information about GM's draw pipeline. First up, we have our draw events. The pre-draw and post-draw events are unique in that you draw directly to the back buffer. This is just the term used to represent what you actually see on screen. In GM we are drawing everything to a surface, the application surface by default, then drawing that entire surface to the back buffer. This is so the player won't be able to see the scene being constructed. We can mess with those defaults however, and draw the screen ourselves, and that's what I do in Castlevania.

First off, I start by determining the internal resolution. For an NES game, the internal render is a paltry 256x224 ( or 240 ) pixels. This is the image as it is drawn before being written to the back buffer. This is kind of nice because by being so small it makes it quite a bit easier to maintain pixel-perfect scaling. To clarify, pixel perfect simple means we are scaling our image by an integer. That is to say every pixel is drawn at twice it's size, or three times, or four times, a whole number. If you were to scale it 1 1/2 times, only 50% of the pixels would be drawn at twice their normal size, and this would cause the image to look weirdly compressed. So that helps me out quite a bit. The second thing I do is determine a 'window bias', it's just a percentage of the screen I want the window to occupy. Now, I could scale the screen by a fixed amount. In fact, I have an internal override for just that purpose that I use when recording videos to keep file sizes down, but the issue with that is the window would be a different size at different resolutions. Ideally the window should be the same on any screen. While this isn't perfectly possible due to the wide gamut of resolutions, I can get pretty close:
Code:
window_scale    = ( forceScale > 0 ? forceScale : floor( ( display_get_height() / render_height ) * window_bias ) );
window_width    = render_width * window_scale;
window_height    = render_height * window_scale;
All I'm doing here is finding the nearest pixel-perfect resolution to the bias I've given. By default Castlevania uses 70% of the monitor's vertical resolution. Once I've done that, it's pretty simple to calculate the window size and placement:
Code:
window_set_rectangle( _window_x, _window_y, window_width, window_height );
However, as I said I don't rely on the game to scale for me. If I left this as is I'd get an odd image like:
scaling1.png scaling1.png
Well, that's clearly not quite what I'm looking for. So by turning off automatic application surface rendering, I can draw it however I want during the post draw event and voila: thanks to the calculations I've already made I can have a beautiful pixel-perfect image. This works out pretty well, and since fullscreen switching takes time I tend to do all development in windowed mode. However, I knew I'd need to get around to fullscreen mode eventually. I definitely didn't want to use the built in scaling, not that I could thanks to my custom scaling for Windowed mode, but there's another issue: it's pretty trivial to scale the window to an integer, but fullscreen? Well, 1080 / 224 is 4.8. That would ruin the picture and give fullscreen a poorer quality than the windowed mode. Now, I could just round up and draw the image offset, but that would cut off part of the image. Luckily, this is a problem people have been dealing with since there have been monitors on which to show games. And in the case of the NES, the actual internal resolution was 240, the extra 16 pixels were used as a buffer to account for differences in televisions and prevent (or at least reduce) the image being cut off. This was called 'overscan'. And why not use this here? Now, interestingly in the case of 1080p, whether or not I use overscan I'll lose 8 pixels. In fact, the higher the resolution, the less of a difference it makes. However, on a 720p display, 240 goes in perfectly at 3 times, where as 224 would result in a loss of 22 pixels! That's it for today, but next time I want to talk about this:
scaling1a.jpg
Black bars are for weirdos! Until next time!​
 
Last edited:

Hyomoto

Member
I wanted to talk briefly about the 'cabinet' art that shows up when you go full screen but I have some other stuff instead. To not go completely against my word though, since I draw Castlevania directly to the back buffer during the post draw step, there's nothing preventing me from, say, drawing art before I draw the screen. Now what might make this a little more interesting is the art is scaled, but only vertically. It is then offset based on how wide your monitor is. The end result is that you get a satisfying set of art along the edges instead of ugly black borders and it tailors itself to the resolution of the monitor being played on. Neat. Right, so full disclosure, as far as I know this has not been a particularly popular project (not that mine are anyways) but I'm just not feeling it right this moment. Which is hilarious because the game is 99% done. I just have to finish the Dracula fight and then put in the ending credits, the latter of which would take about twenty minutes. I'll get around to it, just not right this second. However, to absolve myself the guilt of not doing so, I am releasing the 'indev' version which has a bunch of bug fixes, QA polish and full screen support. Here's the patch notes:
Code:
-
    * Fixed crash when picking up amulet
    * Fixed stair jitter
    * Fixed Simon can jump after walking off a ledge
    * Fixed audio levels on track 4
    * Fixed audio levels on track 5
    * Fixed stopwatch doesn't affect enemies properly
    * Fixed multiplier drops if you have no sub weapon or the stopwatch
    * Added stage 16
    * Added stage 17
    * Added stage 18
    * Added flame splash to holy water
    * Added fullscreen support
    * Added fullscreen border artwork
    * Added CRT shader
    * Adjusted drop rates for enemies again
    * Removed debug options from preferences
    * Removed window_bias from preferences
    * Removed enemy drops from amulet kills
    * Removed enemy drops from suicide kills
Along with that if you want to do some .ini editing, you can adjust the following options:
window_bias - affects how large the windowed mode is ( it defaults to 0.7, it's a percentage of the height of your monitor the window should take up )
crt_enable - setting this to true will enable the CRT effect
crt_distort - this affects the 'roundness' of the monitor, defaults to 0.1, don't suggest going higher but do with it what you will, 0 on the other hand will make the image flat but give it scanlines
use_attract - if you hate the border artwork, set this to false and it won't be there anymore

And that's all I have for you, a lot of bug fixes, the last three levels and a possible crash when you enter Dracula's room. Consider that a victory, since Castlevania is not an easy game. But, if someone does really care about this then post a picture of reaching Dracula and I'll finish the boss fight. Until next time!
 
J

Jafman

Guest
Looks great! I also love how you go into great details and code on how you pulled it off. Thanks for all your efforts!

Um.. I broke it in <10 minutes? At first I couldn't even climb level 1's stairs using a keyboard, so I tried gamepad and found you must press jump and up to begin ascending? I'm like ok I can get used to that, onward! Went through the first door to find I was unable to jump or fall, then proceeded to walk on the air across the big wall where the route goes down and underneath it.

At this point I restart to find graphical glitches on the main screen. Now I'm starting to feel like I'm being messed with! :eek: Might've been because I accidently left one instance of the game open and opened another instance. So I kill em' all, try again and it's a blue screen with the awesome side art and that's it.

One last try, I got the same walk on air glitch (seems inevitable no matter how I reach level 1-3) and walked past the wall thinking I'll one day touch the ground. Nope, walked straight to the boss who won't get triggered and fight me. Not even an axe to the boss while it slept worked.

I'm really impressed with this and hope you can replicate what's going down? Or more appropriate what isn't going down? lool
 

Hyomoto

Member
@Jafman - Well, that's what I get for releasing an indev build. The gravity is fixed now, and I apologize for that. The technicalities behind it are complex, but the fix was simple. What it comes down to is it only affects doors that don't lead into a new room, and since the last levels I worked on don't have that characteristic, it didn't come up in testing. It's a dumb case of 'I broke it, didn't know I broke it and didn't think to test that far'. You'd think I'd learn, but nope. As for the graphical glitches, I can't say. I tried restarting in a few places, but the game handled it normally so that one I can't comment on. Even if there was corruption, there are events that should 'fix' it, if you try it again, and it happens, if you can get a screenshot that would help a lot.
 
J

Jafman

Guest
@Hyomoto No biggie, it was still fun regardless. :) Here's what's happening:

So this is full screen, then I thought to try windowed and restart, back to normal! Switching between modes seems to do the trick too.

edit: Did get more glitches with switching modes some more along with resetting using Ctrl+R.

You know what, nothing wrong with your game at all. I'm more entertained it's in there!
 
Last edited by a moderator:
It's very interesting to come in and see what's new with the project, and I really love the detail you go into explaining how it works. Glad to see you're almost done.
 

Hyomoto

Member
@Jafman - Those pictures do not show for me. I get the feeling it's because those links are insanely large. I tried to root them out but holy moly, they go on forever.
 
J

Jafman

Guest
@Hyomoto Whoops! I'm using Google Drive to host images and once in a while they break. I'm up for a pm if you'd like to get them sooner than I can figure out what's going on.
 
I did a gameplay video review for you!
I really hate to say this, but that was the worst review I've ever seen. It's fine if you're not familiar with Castlevania (I was curious to see a fresh take on the game), but you went into this title without the foresight that this was nearly a 1:1 remake and started commenting on Konami's work itself rather than Hyomoto's efforts, such as the graphics, music, and the logistics of the broken walls. You commented on some light shimmering as the use of a CRT shader was completely missed by you. After that, you started chastising the jumping mechanics that you obviously and admittedly are not familiar with, to the point where halfway through the video you just gave up and started showing an amateurish and condescending tutorial on the "correct" way to do jumping in a platformer, and that you would help Hyomoto out by giving him your code if he wanted, which was further insulting as it wasn't even your own original code that you were pushing. Then, you randomly started talking about getting help on your own game's music which had absolutely no place in the review. Please, structure your videos better and consider doing some proper research, editing, and using a script instead of just turning on your recorder and rambling until you feel you've said and done enough.
 
N

nlolotte

Guest
@nlolotte - I believe you are speaking about how the camera transitions between the rooms? I'd hate to go off on a large rant about something that has nothing to do with your question :D
Thanks for getting back to me.

Yes, I am just curious about how you managed the camera in general. Especially between doors where it slides over, pauses and resumes to be a normal follow camera.
 

Hyomoto

Member
@Siavellez - The fun of remaking old games is you come to appreciate some of their quirks, but I'd be lying if I said that Castlevania doesn't have some frustrating mechanics. The knock-back after being hit is a known offender, and the lack of control during jumping can make them very precise. I made a few concessions to fix stair boss (the problem that causes you to get on the stairs when you try to use your special weapon at the bottom) but it's the original, warts and all. The remake is as authentic as I could make it, going so far as to try and include some of the exploits speed runners use. If you aren't familiar with and nostalgic for it, the original Castlevania is going to have plenty to complain about, I take no offense whatsoever.

However, you have committed at least one faux paus in my eyes: you didn't read the description. I can't promise that would have solved anything, but it clearly lays out the controls for the game. There are many amateur developers who do not think about things like that so you might not always be able to, but, in this case, I have described how to play, how many levels the game has, and how to get on the stairs. Hell, I've written several pages worth of documentation on how the game was made. You don't have to read it, but if you want to review someone's work it is considered polite to at least read the main post. Other than that, I wish you luck in your YouTube aspirations!
 
Last edited:

Hyomoto

Member
@nlolotte - A quirk I have as a GM user is I've always hated views, and even though I think GM2 probably has a pretty decent camera system: I've honestly never used it! So, in my case the camera is a pretty simple object with some very modest code:
Code:
var _offsetX    = ( type == camera_center ? render_width  div 2 : 0 );
var _offsetY    = ( type == camera_center ? render_height div 2 : 0 );

if !is_undefined( follow ) {
    if instance_exists( follow ) {
        var _newX    = clamp( follow.x + followOffsetX, maxLeft, maxRight );
        var _newY    = clamp( follow.y + followOffsetY, maxUp, maxDown );
    
        if abs( camera.x - _newX ) < 1 { _newX = camera.x }
        if abs( camera.y - _newY ) < 1 { _newY = camera.y }
    
        camera.x    = ( _newX > camera.x ? ceil( _newX ) : floor( _newX ) );
        camera.y    = ( _newY > camera.y ? ceil( _newY ) : floor( _newY ) );
    
    }
 
}
The camera has some bounds it falls into, the maximum left, right, up and down it can pan, and then you can define a 'target'. The camera will follow that target. When it comes time to draw the instances such as enemies and candles, their x and y's are offset by the camera position onto the sprite surface. That surface is then drawn over the room layer surfaces. So honestly, the transitions are very, very simple! When the door opens, the target for the camera is cleared so it will no longer follow Simon, control is paused for the player, spawners are paused and enemies destroyed if they exist, and controller takes over moving Simon to the right, then pans the camera, and finally triggers the bounds of the camera to be set, and attaches the camera to Simon again.
Code:
/// @desc Is Collidee (door collision event)
if !triggerEnabled { exit }

with ( obj_enemy ) { instance_destroy() }

with ( actor_simon ) {
    state_set( state_simon_stand, undefined );
 
}
game.allowSpawning    = false;
controller.focus    = undefined;
camera.follow        = undefined;
camera.maxLeft        = 0;
camera.maxRight        = room_width;
active                = true;
collisionGroup        = 0;
Code:
/// @desc No Pause Event (door)
if active {
    var _TXTOCAM    = 228;
  
    if transition < 128 {
        camera.x += 1 * ( isMirrored ? -1 : 1 );
      
    }
    if ( transition - _TXTOCAM < 128 && transition - _TXTOCAM >= 0 ) {
        camera.x += 1 * ( isMirrored ? -1 : 1 );
      
    }
    switch ( transition ) {
        case 128 : frame++; break;
        case 138 : frame++; sound_play( sfx_door, CHANNEL_TRI1 ); break;
        case 144 : frame++; break;
        case 154 : simon_autowalk( 1, 10, isMirrored ); break;
        case 202 : simon_stopwalk(); break;
        case 210 : frame--; break;
        case 218 : frame--; sound_play( sfx_door, CHANNEL_TRI1 ); break;
        case 228 : frame--; break;
        case 356 :
            game.trigger    = triggerKey;
            //game.stage        = stageNumber;
          
            trigger_activate( obj_level_start, triggerKey );
            with ( obj_level_start ) {
                if triggerKey != other.triggerKey { continue }
              
                event_perform( ev_step, ev_step_begin );
              
            }
            with ( actor_simon ) {
                controller.focus    = id;
                camera.follow        = id;
                noGravity            = false;
              
            }
            interface_update( INTERFACE_UPDATE_FULL );
          
            game.allowSpawning    = true;
          
            break;
          
        default : break;
      
    }
    transition++;
  
}
Honestly when I wrote Castlevania I really didn't go for a lot of subtlety in the code, so I mostly just brute forced the effects I want. However, you might find more interesting how each area works: once Simon steps through the door, a lot of things happen that isn't part of the door code, and that feeds into the trigger system, which you can see in "trigger_activate( obj_level_start, triggerKey )"

I showed this before, but levels themselves look like this in the room editor:

All spawners, candles, doors, etc... exist when the room starts, but nothing is currently "active". Those icons "above" the level are things like checkpoint data, music queues, and environmental triggers. Each area, for example, has a camera bounds object attached to it:
room1.png
Notice a "triggerKey" here. I talked about this with the spawners, but Castlevania works on a very rudimentary listener pattern. Basically, an object will send out a trigger to every object. Any object on that wavelength will respond. In this case, if you were to call triggerKey 100, this camera would activate to set the new bounds of the camera. The door, when it closes, calls out to triggerKey 100. Every object in this area of the level that needs to be activated is on trigger 100, and that includes the checkpoint. When the map starts however, it calls out on a different trigger:
room2.png
In this case, 99, which in turn triggers key 100 and sets up the camera bounds. The reason for doing this is things like resetting the music and spawners aren't supposed to happen when you go through the door, and shouldn't be activated then. So this is a proxy trigger that allows those events to take place, but still call the other behavior needed like, in this case, the camera bounds.

Hopefully you find some of this useful :) If you still have questions, don't hesitate to ask, I'll be glad to answer them ( even if I have a tendency to probably explain more than you asked! )
 
Last edited:
N

nlolotte

Guest
@nlolotte - A quirk I have as a GM user is I've always hated views, and even though I think GM2 probably has a pretty decent camera system: I've honestly never used it! So, in my case the camera is a pretty simple object with some very modest code:
Code:
var _offsetX    = ( type == camera_center ? render_width  div 2 : 0 );
var _offsetY    = ( type == camera_center ? render_height div 2 : 0 );

if !is_undefined( follow ) {
    if instance_exists( follow ) {
        var _newX    = clamp( follow.x + followOffsetX, maxLeft, maxRight );
        var _newY    = clamp( follow.y + followOffsetY, maxUp, maxDown );
   
        if abs( camera.x - _newX ) < 1 { _newX = camera.x }
        if abs( camera.y - _newY ) < 1 { _newY = camera.y }
   
        camera.x    = ( _newX > camera.x ? ceil( _newX ) : floor( _newX ) );
        camera.y    = ( _newY > camera.y ? ceil( _newY ) : floor( _newY ) );
   
    }
 
}
The camera has some bounds it falls into, the maximum left, right, up and down it can pan, and then you can define a 'target'. The camera will follow that target. When it comes time to draw the instances such as enemies and candles, their x and y's are offset by the camera position onto the sprite surface. That surface is then drawn over the room layer surfaces. So honestly, the transitions are very, very simple! When the door opens, the target for the camera is cleared so it will no longer follow Simon, control is paused for the player, spawners are paused and enemies destroyed if they exist, and controller takes over moving Simon to the right, then pans the camera, and finally triggers the bounds of the camera to be set, and attaches the camera to Simon again.
Code:
/// @desc Is Collidee (door collision event)
if !triggerEnabled { exit }

with ( obj_enemy ) { instance_destroy() }

with ( actor_simon ) {
    state_set( state_simon_stand, undefined );
 
}
game.allowSpawning    = false;
controller.focus    = undefined;
camera.follow        = undefined;
camera.maxLeft        = 0;
camera.maxRight        = room_width;
active                = true;
collisionGroup        = 0;
Code:
/// @desc No Pause Event (door)
if active {
    var _TXTOCAM    = 228;
 
    if transition < 128 {
        camera.x += 1 * ( isMirrored ? -1 : 1 );
     
    }
    if ( transition - _TXTOCAM < 128 && transition - _TXTOCAM >= 0 ) {
        camera.x += 1 * ( isMirrored ? -1 : 1 );
     
    }
    switch ( transition ) {
        case 128 : frame++; break;
        case 138 : frame++; sound_play( sfx_door, CHANNEL_TRI1 ); break;
        case 144 : frame++; break;
        case 154 : simon_autowalk( 1, 10, isMirrored ); break;
        case 202 : simon_stopwalk(); break;
        case 210 : frame--; break;
        case 218 : frame--; sound_play( sfx_door, CHANNEL_TRI1 ); break;
        case 228 : frame--; break;
        case 356 :
            game.trigger    = triggerKey;
            //game.stage        = stageNumber;
         
            trigger_activate( obj_level_start, triggerKey );
            with ( obj_level_start ) {
                if triggerKey != other.triggerKey { continue }
             
                event_perform( ev_step, ev_step_begin );
             
            }
            with ( actor_simon ) {
                controller.focus    = id;
                camera.follow        = id;
                noGravity            = false;
             
            }
            interface_update( INTERFACE_UPDATE_FULL );
         
            game.allowSpawning    = true;
         
            break;
         
        default : break;
     
    }
    transition++;
 
}
Honestly when I wrote Castlevania I really didn't go for a lot of subtlety in the code, so I mostly just brute forced the effects I want. However, you might find more interesting how each area works: once Simon steps through the door, a lot of things happen that isn't part of the door code, and that feeds into the trigger system, which you can see in "trigger_activate( obj_level_start, triggerKey )"

I showed this before, but levels themselves look like this in the room editor:

All spawners, candles, doors, etc... exist when the room starts, but nothing is currently "active". Those icons "above" the level are things like checkpoint data, music queues, and environmental triggers. Each area, for example, has a camera bounds object attached to it:
View attachment 24188
Notice a "triggerKey" here. I talked about this with the spawners, but Castlevania works on a very rudimentary listener pattern. Basically, an object will send out a trigger to every object. Any object on that wavelength will respond. In this case, if you were to call triggerKey 100, this camera would activate to set the new bounds of the camera. The door, when it closes, calls out to triggerKey 100. Every object in this area of the level that needs to be activated is on trigger 100, and that includes the checkpoint. When the map starts however, it calls out on a different trigger:
View attachment 24189
In this case, 99, which in turn triggers key 100 and sets up the camera bounds. The reason for doing this is things like resetting the music and spawners aren't supposed to happen when you go through the door, and shouldn't be activated then. So this is a proxy trigger that allows those events to take place, but still call the other behavior needed like, in this case, the camera bounds.

Hopefully you find some of this useful :) If you still have questions, don't hesitate to ask, I'll be glad to answer them ( even if I have a tendency to probably explain more than you asked! )
Thank you for your time and information. I really appreciate the detail. I'll attempt to get my camera system working and message you if I have any trouble. Thank you again :)
 
R

Red Phantom

Guest
I really hate to say this, but that was the worst review I've ever seen. It's fine if you're not familiar with Castlevania (I was curious to see a fresh take on the game), but you went into this title without the foresight that this was nearly a 1:1 remake and started commenting on Konami's work itself rather than Hyomoto's efforts, such as the graphics, music, and the logistics of the broken walls. You commented on some light shimmering as the use of a CRT shader was completely missed by you. After that, you started chastising the jumping mechanics that you obviously and admittedly are not familiar with, to the point where halfway through the video you just gave up and started showing an amateurish and condescending tutorial on the "correct" way to do jumping in a platformer, and that you would help Hyomoto out by giving him your code if he wanted, which was further insulting as it wasn't even your own original code that you were pushing. Then, you randomly started talking about getting help on your own game's music which had absolutely no place in the review. Please, structure your videos better and consider doing some proper research, editing, and using a script instead of just turning on your recorder and rambling until you feel you've said and done enough.
"I really hate to say this, but that was the worst review I've ever seen."
---> If you hate to say it then don't say it at all. Your negativity is not appreciated.
There's nothing amateurish about that tutorial.
There's nothing condescending about that.
"Then, you randomly started talking about getting (opinions) on your own game's music which had absolutely no place in the review."
---> I can put whatever content I want in my videos
"Instead of just turning on your recorder and rambling until you feel you've said and done enough."
---> The only one being insulting is you.
If you're thinking so negatively and with the intention of insulting, then just don't do it next time.
 
Last edited by a moderator:

GarlicGuy

Member
Nice! The castlevania series has always been one of my favorites! Loved playing it on NES -SNES - PS1.

I saw “remake” and got excited thinking you were adding better graphics and new creative physics...:p
Looks like you’re making a sort of game maker Rom of castlevania 1?

Anyway what you’re doing is still awesome! Great job! Love the attention to the old NES detail.
 

Hyomoto

Member
Nice! The castlevania series has always been one of my favorites! Loved playing it on NES -SNES - PS1.

I saw “remake” and got excited thinking you were adding better graphics and new creative physics...:p
Looks like you’re making a sort of game maker Rom of castlevania 1?

Anyway what you’re doing is still awesome! Great job! Love the attention to the old NES detail.
Well, there is a "redux" mode that changes up the gameplay a little bit, but the stages, enemies, items, weapons, graphics and sound are all as close to the original as I could make them. It's started as a programming exercise rather than a viable product, after all: you could just play Castlevania and it would be perfect. So it's niche is very low!

Thank you for your kind comments.
 
"I really hate to say this, but that was the worst review I've ever seen."
---> If you hate to say it then don't say it at all. Your negativity is not appreciated.
There's nothing amateurish about that tutorial.
There's nothing condescending about that.
"Then, you randomly started talking about getting (opinions) on your own game's music which had absolutely no place in the review."
---> I can put whatever content I want in my videos
"Instead of just turning on your recorder and rambling until you feel you've said and done enough."
---> The only one being insulting is you.
If you're thinking so negatively and with the intention of insulting, then just don't do it next time.
It's fine if you're upset with how I responded, but try to understand the meaning behind the post rather than just coming back at me because you didn't like what I had to say. I was a bit offended in Hyomoto's place which is why my post was more negative than critical, but everything I say still stands. You didn't understand how he was trying to recreate Castlevania, even going so far as to give it a CRT look (the diagonal shimmering you thought was a light object following the player). As a result, rather than doing at least some research before starting the video since you stated that you know nothing about Castlevania, you spend a good chunk of the review commenting on Konami's decisions and criticizing the jumping as if it was Hyomoto's terrible skills and programming rather than the mechanics of the original, to the point where you turned the video into a tutorial using someone else's code that you have in your own game, suggesting that Hyomoto contact you to receive a copy of it to "fix" his game. If you can't see the ignorant and condescending nature of those actions, then your videos aren't going to be very well received, I'm afraid.

And yes, you can put whatever the heck you want in your videos, obviously, but you can't falsely advertise your video and then blame the viewers for not meeting their expectation. Half the video was your tutorial and help on your game, and with little editing, even things like you switching to the forums to find the controls were also unnecessary. What kind of place does all of that have in a video review of someone else's game? If you opened up a video of a turn-based RPG tutorial in GMS2 and it talked more about the action side of combat, with the other half of the video being a display of the guy's model train collection, would you consider that a worthwhile video because he can do whatever he wants? Come on, now. That's why I asked that you consider preparing for your future videos and doing some proper editing instead of just turning on the recorder for 15 minutes and calling it a video.
 
Castlevania NES Remake
by Hyomoto
Hello fellow Gamemaker community! You may recognize me from some of my previous projects, most notably my currently suspended remake of Final Fantasy on NES. If you are seeing a pattern here, congratulations! You are one hundred percent correct! I guess I just really like old games, and I like tinkering on projects like these. I'm currently tinkering away on a new prototype, and I wanted to share some of it with the community. Unlike my FF project I don't really have an end goal here other than to have fun and maybe learn, refine or apply some new coding techniques. I chose this game specifically because it's mechanics are relatively simple and the game is reasonably short, and like most NES games the graphics and sound aren't terribly difficult to acquire. Plus, I just really like old Nintendo games.

Full disclosure: I never played Castlevania as a kid, and I really don't know much about it. I didn't like Symphony of the Night, loading was nightmarish, and though I own a number of games in the franchise I've just never really been a big fan of it. So unlike Final Fantasy for NES which I am a huge fan of, I really don't know much of anything about Castlevania. Also, unlike Final Fantasy which has been torn apart and documented HEAVILY, there isn't really much information on how Castlevania works under the hood. There's maybe *just* enough documentation for me to remake it mostly faithful, but a lot of my work for far is based on pixel and timing measurements.

But let's get to the meat of this post, I put up a playable demo, but the main focus was I wanted to open a thread where I can talk about some of the challenges and solutions I employed in making it. In a way, that's kind of what I like doing most: helping other people. I've always enjoyed reading about other creator's work, so perhaps there's someone out there that will find mine helpful. This is all Gamemaker specific, so if you want to learn something about the game, or even just how to make programs in GM2, maybe this will help you out. I'm going to try out a format where I talk about different subjects in different posts and compile them in this thread so it will evolve as time goes on. If you have any questions, feel free to ask!

Controls:
Left, Right, Up, Down - Move
Z - Whip
A - Jump
Tab - Select
Enter - Start

Pressing up and whip uses your special weapon, most weapons cost 1 heart, stopwatch costs 5.
You have to press Jump while holding up or down to get on the stairs, this prevents the annoying 'getting on the stairs when I definitely didn't want to' of the original, also known as 'stair boss'.
In "Redux" mode, pressing Select uses your special item.
You can load your last checkpoint by pressing Select at the "press start key" message.
Download Castlevania
Current Progress: Engine 99%, Stages 99%
Latest build Indev​
Not working the download :c
 

Metzger

Member
Hey wow great work! I love castlevania games played them since I got a nes back in 89.
Your is very faithfully done excellent work.
I'm also doing a castlevania Simons Quest thou I'm doing more of a remake than a copy.
Mine ain't as neatly done as your but if you wish to check it out feel free been doing it slowly for many years now =D
Simons Curse
 
Top