Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Yal

609
Posts
11
Topics
1,441
Followers
24
Following
A member registered Aug 12, 2015 · View creator page →

Creator of

Recent community posts

Thank you! ^_^

It's the only project I've ever finished and not immediately being disappointed in.

1) Yes, all included assets can be used for commercial games. (It's right there in the description!)

2) Check out the level_clear script (which is called by playerstate_win_walkoffscreen when the player has left the flagpole and walks offscreen). Currently it just always calls level_goto_next, if you want different level transitions you'd replace that with your logic (e.g. going to a world map)

Finally got around to making a trailer! Enjoy the completely destroyed bitrate.

When I started work on this project was when we thought Silksong was gonna release for sure in 3 months (after that XBox Direct thing which claimed everything shown off would release "in this year" and Team Cherry commented on it with "that's what they said" when people asked if that implied a release date), I figured I needed to rush it out before that date or nobody would play it because everybody would go for the real deal... well, with the benefit of hindsight you can tell I didn't need to worry :P

I'm kinda unhappy with the end result due to all the corners I cut first for the jam deadline, then for the Silksong release date: areas are basically 1:1 references to Hollow Knight, every unlock giving you TWO new abilities means they're spread super thin, spells were a last second addition that's a nightmare to balance because there's a lot of damage variation depending on how iframes and hitboxes line up, and the map system also was an afterthought that makes it really annoying to add new rooms. (The map on the pause screen is a photo of my planning papers with a free parchment texture slapped on and originally it didn't show your position, I've manually added pixel coordinates for every room to allow that lol)

I've always been a fan of adding secrets, it was fun hiding basically half the game just to blow people's minds! (The cutoff point is where the original jam scope ended so when I realized I had some time to wrap the game up and improve it before Silksong was set to release, I basically added the second half in a vacuum and stapled it onto the original endpoint). I can't decide if my favorite secret is the five recursively more hidden treasure chests or the super-secret peek into Lake Suwa.

You've pretty much figured the story out, the last thing you missed is that the Mask of Lost Emotion was used to "turn off" Sakura's emotions (to stabilize her) but it worked too well and basically removed her entire personality as well so she's just a hollow shell. Or "hollow heart" for a title drop, I guess

The soundfonts I'm using is like 90% the default stuff that comes with Mixcraft 9, so mostly the Acoustica Instruments General MIDI VST. Retro samples are mostly my own hand-drawn waveforms from the YaruChipFont (which is a free itchio asset I've uploaded here as well, btw). Most of the magic is due to slapping on the right effects (compared to LMMS and Synthfont this is like 100 times easier in Mixcraft so I have like 5 reverbs and delays on everything just because I can :P)

Thanks, but I'm not interested in the collaboration.

Okay, I figured it out! The problem is the order mev_pause_items_use_actually_use does things (looks like something has changed in how Game Maker processes the Create event so the dialogue box taking top priority doesn't work now), rearranging it so we destroy the daddy menus before we spawn the dialogue box fixes the glitch:

(for this to work we need to get a reference to daddy.daddy first before we destroy this menu and its parent, otherwise we can't refer to it later - this is what new variable dd is for)

OK, I can see it now... seems to be a lot of weird things going on, a dialogue box gets created below the other menu and that's what's responding to the button presses (or rather it also affects the inventory menu so there's two active menus at once?)

The script that applies the selected item is mev_pause_items_use_actually_use, in that you'd want to remove this code at the end which destroys the current menu and the preceding one (the "use/throw away/hold" menu):

instance_destroy(daddy)
instance_destroy()

I don't see the buggy behavior in the engine source file (ggui_menu_handle also has a check that only runs the left/right handler if you've allocated a menu that's 2 at least slots wide so it shouldn't even attempt to move the cursor in the monster menu which is fully vertical) so it might be something you've changed?

(2 edits)

The enemy amp_id's start at AMP_FIRST_ENEMY and then go upwards from there, until they end at AMP_FIRST_ENEMY+PARTYSIZE_MAX_ENEMY.

So the first monster that was sent out is in global.active_monster_party[AMP_FIRST_ENEMY], the second is in global.active_monster_party[AMP_FIRST_ENEMY+c], and so on.

(IDs that don't have a monster in them has an amp_MONID of NONE, otherwise amp_MONID contains the monster ID and amp_LEVEL the level)

(PARTYSIZE_MAX_ENEMY is 12 by default but if you use the constant to figure out how long arrays you need it should work out regardless)

Both of these will need you to track some additional data, I think two global arrays with AMP_PARTYSIZE_ACTIVE slots will be enough: global.party_was_in_battle and global.party_last_move_selected.

  • In obj_battlecontrol's create event, fill these both with 0's in all slots.
  • For only earning EXP in battle:
    • In obj_battlecontrol's step event bcontrolstate_WIN_STEP section, change "if(amp_has_monster(exp_monster)){" to "if(amp_has_monster(exp_monster) && global.party_was_in_battle[exp_monster]){"
    • In obj_battlemonster's step event after the "//Monster's been seen in battle! Add to list." comment, add "if(amp_id < AMP_PARTYSIZE_ACTIVE){global.party_was_in_battle[amp_id] = true;}"
  • For remembering the last move selected:
    • In mev_battle_attack_select, right at the top add "global.party_last_move_selected[obj_battlecontrol.action_monster.amp_id] = menuvalue_x + 2*menuvalue_y"
    • In mev_battle_attack, at the end of the "if(validmoves > 0)" block somewhere, append a switch state like
if(obj_battlecontrol.action_monster.amp_id < AMP_PARTYSIZE_ACTIVE){
switch(global.party_last_move_selected[obj_battlecontrol.action_monster.amp_id]){
  case 0:
    menuvalue_x = 0
    menuvalue_y = 0
  break;
  case 1:
    menuvalue_x = 1
    menuvalue_y = 0
  break
  case 2:
    menuvalue_x = 0
    menuvalue_y = 1
  break
  case 3:
    menuvalue_x = 1
    menuvalue_y = 1
  break
}
}
(1 edit)

I played around a bit and got it working after:

  1. Halving the size of the DUN_GRID_SIZE_X / Y macros
  2. Lowering the view size to 256x160 (I was too lazy to do the maths on what half of 384 is) - this turned out to be necessary because the camera size read from the room viewport settings is used for some calculations and having a room size smaller than this breaks the scrolling code and can cause infinite loops during generation
  3. Stretching the tileset sprites to half their original size and updating the tileset assets to use 16x16 instead of 32x32 as the size setting
  4. Stretching the door sprite to half the original size
  5. Adding a debug message
show_debug_message(string("render room #{2} at {0},{1} ({3} x {4})",argument2,argument3,argument4,argument5,argument6))

 to the start of dungen_render_room and another one that just prints "done" to the end. (Not strictly necessary, but might help debug what's happening, especially if your changes causes infinite loops somewhere)


To convert this to isometric I think something like this would be the easiest way:

  • dungen_render_tile is what places individual tiles, change it so it computes coordinates using an isometric equation instead (easiest is multiplying two diagonal vectors (the two ground-plane directions in your isometric coordinate system) with length 1 "u" and "v" with the x and y coordinates, then adding the resulting x and y components together to convert back from UV coordinates to XY coordinates) and places isotiles there (the exact details depends on how you wanna implement them but the easiest is probably layer_sprite_create)
  • dungen_render_room has a segment at the end that places enemies and items in some random cells in the room, this would also need to convert the incoming XY coordinates to UV coordinates when deciding where to spawn things (you should do this when you set up the xx/yy variables only)
  • dun_room_pos_free is used to check for collisions both in the generation phase and when things move around, depending on how you alter how a room is rendered (e.g. if you move away from using compatibility tiles) this will also need to be changed to check for walls etc accordingly; likewise the code that spawns doors in dungen_render_room also needs to be updated to remove walls the way they're implemented (currently hardcoded to remove compatibility tiles specifically)

Hope this helps, there's probably a bunch of things I could do to separate the generation and rendering steps better...

(1 edit)

You can find the code for it in the mev_intro_starterselect_confirm script:

var amp = amp_get_new_party_id();
amp_generate_monster(amp,MONSTER_SPECIES,LEVEL)

If you wanna add it to the box instead, use amp_get_new_box_id to get the first free box slot instead of the first free party slot.

I'll look into it, probably it can be fixed by manually altering the "active" variable so the menu ignores keypresses during the fadeout.

Great, hopefully things go better this time! And as a rule of thumb, whenever you need to figure out what goes wrong, add more show_debug_message's in the code so you get all the info about what the game's trying to do.

Is this from loading a previously saved file or when creating a new file?

mev_title_continue_fileselected has some fallback code if the load fails which warps you to a specific spot (indoors area with coordinates set to the center of the lab), cc_intro_startgame is what sets your initial position from a new savefile - this is the same coordinates as the fallback when loading though.

To capture all room transitions in the debug messages you could open room_goto_fade and room_goto_fade_dontdestroy and add a line at the start (before the room is changed), this should point out if there's any room transitions happening so fast you don't get a chance to react:

show_debug_message(tsprintf("Room change % --> %",room_get_name(room),room_get_name(argument0)))

Did you add the debug message that prints the player's position after loading I suggested last time?

What I was thinking was, you'd enable DEBUG_MODE (line 5 of init_constants is "#macro DEBUG_MODE false", change it to "#macro DEBUG_MODE true") and then you'll get messages telling you what happens. E.g. if a door transition takes priority, the game will print "Came through a door, jump to LABEL" (so if you're not where you think you are, you can check that the label is correct) and it'll tell you "Player loaded! (room=NAME OF ROOM)" as well so you can tell where you ended up.

You could try adding additional show_debug_message's to e.g. print the player's position after loading, that should make it easier to find in the room editor:

show_debug_message(tsprintf("Player location: %, %",x,y))


As I said before, going through a door will place you at the door with the same name in the other room. So a drawback of this approach is that you can't link two doors in the same room together (because when looping over the door objects it'll find whichever of the two doors with that label is first in the instance list and always pick that). If you want to warp between places in the same room I'd probably create two new objects, "obj_teleport_entrance" and "obj_teleport_exit", which has a label the same way the doors have. Then in player's collision with teleport_entrance, run this code:

var target_label = other.label;
with(obj_teleport_exit){
  if(label == target_label){
     other.x = x;
     other.y = y;
     break;
  }
}

Now each teleport pair would use one entrance and one exit. If you want a teleport to be in both directions you'd have entrance A and exit B on one side and entrance B and exit A on the other side; make sure to place the exit some distance away from the entrance (otherwise the player gets stuck in an infinite loop of warping between them).

The issue might just be that GMS1 uses a too old graphics API, it's not had official support in years... not much I can do about that :(

The door code is in obj_player's collision event with obj_door. But they're meant to be usable without adding any code, instead open the instance in the room editor and check its "variables" window:

Here you select which room to go to and a "label" (whatever text you want), the player is automatically placed at the door object with the same label as the one they entered from. So you can label doors in a town based on which character's house they lead to, and so on. (The code that loads the player based on which door they used is in obj_player's Alarm 1 event)


Not sure why the tileset is so distorted but my theory is that it might've gotten broken if it's got a different size from the existing ones? (320 x 256). Height shouldn't matter but the 320 pixel width is since the tile indices are used for collision checking (left 160px (= 10 tiles) are walkable tiles, right 160px are walls).

Also make sure the tileset settings are correct!

These should also match the grid size of the tile layer you're placing these on (A), re-selecting which tileset to use on the layer (B) will refresh how the tile layer is drawn as well.


If you turn on the DEBUG_MODE flag you'll get some helpful printouts in the "Output" window when you load a room (check out the player's Create Event + Alarm 1 event for the code that handles where to spawn).

I think what's happening here is, you saved the game in the now-deleted room, and then when the room doesn't exist anymore the fallback code in mev_title_continue_fileselected which spawns you in the lab might not work anymore if you've changed the layout. (Or if you re-created a room with the same name but a different layout, the saved position will be used but now be in the middle of the ocean)

The menu event script for this is mev_battle_escape, you'd just need to remove the special case for trainer battles.

Though you might need to take further measures to prevent the player from immediately ending up in a rematch, cc_battlestart_trainer saves the player's position after they've entered the trainer's vision range. So you'd either need to keep track of a previous position somewhere so you can deposit the player out of harm's way, or maybe give the player a temporary invincibility to further battles after they get away (and while this countdown is ongoing obj_npc_trainer's User Event 2 code - which is where they look for players - will ignore them)

(1 edit)

If you haven't found it yet, try middle-clicking constant / function names to immediately open the script file where they're created. Super useful when navigating a large project. (So in this case, you could've clicked any of the monster_ constants to go to the file where they're defined, and that'd been the place to add new ones)

Also the ctrl-shift-F and ctrl-P hotkeys for opening global search / jump-to-asset can be useful if you know something's name but can't find it.

Easiest way would probably be to edit cis_intro_part2 and swap out the cc_intro_startermonsterselect / nickname logic; rather than making a monster choice menu and wait for your input, the startermonsterselect script would instead fill your party with the monsters you want:

var amp = amp_get_new_party_id();
amp_generate_monster(amp,monster_id,level)

First get the ID of the first empty slot in the party, then generate a monster there using its species and level, repeat as many times as you want. There's matching get-first-free-slot functions for the boxes and the temporary enemy slots, too.

To go through every monster like this, rather than hardcoding the monster_id you could loop from 0 to MONSTER_MAX.

Also note that if you really want the player to have every monster in the party, you probably want to increase PARTYSIZE_ACTIVE from 6.

Second term as in "global.weapon_data[c][pstat_UPGRADESMAX]".

Hmm, judging by the message everything wasn't set up as expected (the upgrades_obtained array is too short), it should be a two-dimensional array that's got as many slots on the first axis as the number of weapons the player can obtain, and the second axis should have PWD_LOCALDATA_MAX elements (now I'm realizing that's not actually correct, you want this number to be the max number of different upgrade choices any weapon can have - we're addressing by upgrade index and not by stat - but so far none of them has 12 upgrade paths so it should be fine).

But either way, global.weapon_upgrades_obtained should be at least a 6-by-12 element array, but it's only 2 elements along one axis, so something's weird there. The first step would be to print its contents with show_debug_message(global.weapon_upgrades_obtained) at the start of the function and see what's actually in it when the game crashes. (Note that it's not enough to just set array[5][11] to 0 when initializing it, that'll give you a 6 element array which contains a 12-element array in the 6th slot and the number 0 in the prior five slots - 2D arrays are just arrays of 1D arrays so if you want the inner arrays to have the same length you need to manually put one in every slot of the outer array)

Oh yeah, and another issue, you're checking global.weapon_upgrades_obtained[c], but you should check global.weapon_upgrades_obtained[c][d] because upgrade level is per-upgrade now instead of shared for the entire weapon. (You'll need to move this check into the loop over array_length(ups) as well)

Hmm, I might need to look into that, I know GMS1.x games have started having issues running recently...

Yeah, it's a bit stretched but it still looks pretty playable! (I also love the choice of Comic Sans as your OS font, lol)

GMS2 will probably fare even better in this regard, in my current project I started playing around with SDF fonts and the way they magically get higher quality in fullscreen is amazing, this will make GUI design a lot easier in the future.

I consider it complete (unless there's a game-breaking bug).

I would recommend looking into it if it's an option, GMS1 hasn't had official support in years and it's only a matter of time before its old graphics APIs will stop working entirely... :(

Currently the free license lets you export games to desktop platforms if the game's not sold, so there's basically nothing to lose just trying it out. (And I'd recommend getting GMS2 from the official download page since it's literally free)

(3 edits)

1) Currently in playerweapon_randomly_pick_some_random_upgrades (used to pick valid choices for levelups, chests etc) we check if a weapon hasn't reached max upgrade level with the line:

global.weapon_upgrades_obtained[c] < global.weapon_data[c][pstat_UPGRADESMAX]

To give each individual upgrade a separate counter you'd need to update both of these accordingly:

  • Make global.weapon_upgrades_obtained a 2D array where the second member has PWD_LOCALDATA_MAX elements that all are initialized to 0 at the start of a run, this is done in playerweapon_initialize_run.
  • In obj_player's Alarm 0 event, the UPGRADETYPE_UPGRADEWEAPON block, now you'd change the ++ line to instead increment global.weapon_upgrades_obtained[global.confirmed_upgrades[c][upgd_WEAPONINDEX]][global.confirmed_upgrades[c][upgd_INDEX]]
  • In the playerweapons script macro section (just before playerweapon_initialize_run) add a new constant dbupgd_MAXUPGRADES, for readability I recommend putting this as 0 and incrementing all other dbupgd_ stats by 1. Then at the start of every upgrade in the database, add the desired max level there. For instance the sword's upgradedata should now start off with [[5,[pstat_ATKPOWER,1],NONE,"Whetstone"...
  • In playerweapon_randomly_pick_some_random_upgrades now finally change the second term to global.weapon_data[c][pstat_POSSIBLEUPGRADES][d][dbupgd_MAXUPGRADES]

2) Yeah, currently triggerscripts are only used for things like the healing and money bag. I think the easiest way to implement this would be to make the actual update have no stat changes and no trigger script, and a max cap of 1 upgrades (see the answer to question 1) but the weapon's Player Shoot Script (e.g. pss_sword for the Hero Sword) checks if you have the upgrade, and if so alters the bullet's properties. The weapon script can check the variable currently_fired_weapon for the index in the player's active weapon list, and thus would check if global.weapon_upgrades_obtained[currently_fired_weapon][index_of_the_upgrade_in_the_list] > 0, for your convenience you'd probably want this upgrade to be first in the list so you don't need to count the already-confusing nested array slots.

I think for weapons using triggerscripts, you'd use them if the script does some one-off change that needs to work outside the weapon counter / PSS / stat system entirely, e.g. if the weapon creates a separate turret object rather than giving the player a weapon directly.

Thank you! Glad you liked it ^_^

Weird, everything looks like it should... does draw_flush exist in your version of GM? If it exists you should call that alongside d3d_transform_set_identity in triangles_break_batch (and maybe in draw_self_enemy as well) just to make sure the draw pipeline gets flushed.

What exactly does the updated code look like? Are you using draw_flush or d3d_set_identity?

I find it a bit weird that it still tries to allocate a giant buffer of 1/6th of everything else it's rendering when it's totally fine with thousands of smaller ones, are you sure you didn't miss adding the safety code anywhere?

(2 edits)

Looking at the script room_enter_menu_maintaining_game_state should give you some ideas, it does a couple of things:

  • save the screen to a surface so the levelup menu can have the game in the background,
  • set the room to persistent, and
  • set up the player alarm which later turns off persistence again once you return to that room. (This alarm also is what applies all confirmed upgrades once you're back in the room)

After doing all that it changes the room to the one with the levelup menu - the game basically pauses by changing to a different room, but using persistence to maintain the state of the original room.

Also note obj_upgradecontrol, its regular Draw event draws this screenshot in the background (and then the menu is drawn on top), your pause menu would probably do something similar (or at least draw the screenshot + some static text that reads "paused")

As long as you make sure global.confirmed_upgrades is empty you should be able to just reuse this functionality wholesale (change rooms to the pause screen with room_enter_menu_maintaining_game_state, keep track which room you were in before you swap, and go back to that room when done), but the only things that really are needed are setting the room to persistent before you leave  and setting up an alarm to turn off persistence once you're back.

playerweapons_init_all inits all the weapon data, normally it's created by the obj_setup (which sets up all the other data as well). Did you remove the blank first room that sets everything up (or change the starting room to a different one)?

global.weapon_data[wid] is probably what's not an array here, try printing it with a show_debug_message within playerweapon_register_new and it should tell you what it is (if it contains 'undefined' or crashes with an 'accessing array out of bounds' error you don't actually add the knife data as expected and then you most likely don't run the init script at all)

It would probably be possible to remove the playerweapons_init_all script entirely (code inside a script file but not inside a function body will be run when the game loads, this can be used to set up global variables earlier than normal and ensure that code is only ran once), the order of execution between different script files isn't guaranteed though so use with care.

(2 edits)

Yes, just having a script asset with the same name should work too

  • change "quads" to "argument0" in the script
  • if draw_flush doesn't exist (it was added late in 1.4) overwriting the world matrix (e.g. by d3d_transform_set_identity or matrix_set) should also force a flush of the geometry buffer

Blocks/walls should use that formula, yes (they do indeed add that many triangles). Maybe slap a ceil() around the expression for stability so it always becomes an integer even if a block is e.g. 1.5x its base size. (If you never stretch things out in the room editor it gets easier, then you can just use the hardcoded number 12 for blocks and 4 for floors)

I don't, but please ask any questions here and I'll answer them as soon as I'm able.

(3 edits)

I did some googling and it looks like the issue is that the batch of trianges becomes so big the players' graphics cards can't handle it. (800 MB VRAM sound very small these days but ah well...) 

So we need to reset the drawing batch every 1000 triangles. I tried to figure out the easiest way to do that and here's my idea.

1) Create a new script with the following function:

function triangles_break_batch(quads=6){
     global.triangles_so_far += 2*quads
     if(global.triangles_so_far >= 1000){
         global.triangles_so_far -= 1000
         draw_flush()
     }
}

2) Init global.triangles_so_far to 0 somewhere before any 3D drawing, e.g. In CONTROL's create event.

3) Put this new triangles_break_batch function at the start of the Draw event of all terrain objects. You'd compute "quads" like so,

//For a block or wall
triangles_break_batch((image_xscale + image_yscale + image_xscale*image_yscale)*2)
//For a floor or slope
triangles_break_batch(image_xscale*image_yscale*2)

I'm not a lawyer, but as far as I'm aware in all countries that has signed the Berne Convention, you automatically get copyright on everything you create (with some exceptions: companies will own copyright on things their employees create during work hours, generative AIs and animals cannot own copyright to things they create, and violating copyright and trademarks when creating something will void copyright on your custom additions)

So TL;DR: "No, you get the copyright automatically."

(1 edit)

To pass over a value, in the mev_ script you'd do something like this:

with(instance_create_depth(x,y,depth-1,obj_gguimenu)){
     daddy       = other.id
     my_monster  = other.menuvalue_y
     //Create frames, elements and events like normal
}

(Similar to the scripts for doing stuff to party monsters, but instead of referring to the AMP we refer to a raw monster ID)

(1 edit)

It's input_blank to initially create all the variables used by the input system (so this goes in the Create event of the new object), then input_get to update them using whatever the current active input device is (so this would go in the start of the Step event, so the variables get updated before you do anything actually controlling the player).

As for how the input variables are used: the gist of it is that there's a pair of k_ / p_ variables for each action. k_l is the current status of the 'left' input, k_atkL is the left-hand weapon attack, and so on - there's a matching "p_" variable for each "k_" variable which is how many previous steps it has been pressed, this can be used to detect a press/release or e.g. holding down a button a certain time to charge an attack.

(The global array "global.input_key" is the mapping of which key/gamepad input is used for each action, this is what is updated when you remap the controls)

The entrance is located at this point: