Warning: Constant WP_CRON_LOCK_TIMEOUT already defined in /home2/jemston4/public_html/wp-config.php on line 91

Warning: Constant AUTOSAVE_INTERVAL already defined in /home2/jemston4/public_html/wp-config.php on line 92

Warning: Constant WP_POST_REVISIONS already defined in /home2/jemston4/public_html/wp-config.php on line 93

Warning: Constant EMPTY_TRASH_DAYS already defined in /home2/jemston4/public_html/wp-config.php on line 94

Warning: Cannot modify header information - headers already sent by (output started at /home2/jemston4/public_html/wp-config.php:91) in /home2/jemston4/public_html/wp-includes/feed-rss2.php on line 8
JEMstone Games https://odc.wvs.mybluehost.me Indie Games and Tutorials Fri, 13 Mar 2020 16:02:47 +0000 en-US hourly 1 https://wordpress.org/?v=7.1 161651155 Creating Context Menus https://odc.wvs.mybluehost.me/creating-context-menus/?utm_source=rss&utm_medium=rss&utm_campaign=creating-context-menus https://odc.wvs.mybluehost.me/creating-context-menus/#respond Fri, 13 Mar 2020 16:02:47 +0000 https://jemstonegames.com/?p=142 Continue reading "Creating Context Menus"

]]>
This is going to be a quick and ugly tutorial on creating instance-specific menus, the type you might see in a city-builder when you right click on object. /u/The_Capptianm requested a tutorial on something like this. This is just for demonstration purposes, so we’re going to have a basic system that will create the menu as soon as the left mouse button is pressed. Here’s a video of the end product and a video version of this tutorial:

Step 1: Create Sprites

We’re going to create 3 Sprites, one for our people, one for our buildings, and one for our menu buttons. I’m going to make mine monochromatic so we can use draw_color_ext to allow our objects to differentiate themselves with different colors.

sBuilding

This is going to be a simple sprite used as a building. This one will be 64×64.

sPerson

Another simple sprite used to represent a person, this will be 32×32.

sButton

This sprite will be 64×18 and will use 3 pixels on each side as a shadow to make the button look 3D. We will draw text over it.

Step 2: Setting up the Room

This part doesn’t matter that much how you set it up. I left it with the default width and height and gave it a light green background. We’re going to have 3 Layers, from top to bottom should be: “GUI”, “Instances”, “Background”.

Step 3: Creating Objects

Planning Inheritance

The easiest way to accomplish menu systems is to set up a robust inheritance system. This allows you to program behavior for a group of objects and eliminates a lot of tedium. We want to have a group of buildings for our town and a group of people to live in our town. In this example we’re just going to use a small sample to show how it’s done. For our buildings we’ll want a House and a City Hall. For our civilians we’ll want a generic Person and an Alien. Both types of buildings will share certain characteristics and both civilians will share certain characteristics. All game objects will also share the behavior that when clicked will create a menu. To this end we will create a hierarchy that looks something like this:

Basic Plan for Game Object hierarchy

The Game Object

In my games I like to have a master Game object that handles a lot of the functionality of the game itself. This also creates a centralized hub to control settings and initialize global variables. We are going to add a Create Event to our Game object:

// Game_Create
randomize();
global.selected = noone;
enum Options {
    Move,
    Delete,
    Change,
    Tax
};
optionString = [
    "Move",
    "Delete",
    "Change",
    "Tax 'Em!"
];

We’re going to use a global variable global.selected to track the id of the currently selected object. This prevents us from accidentally trying to access the menu of multiple objects at a time.

Each object in our game that will have a menu will have an array named options that stores which menu options are available to it. Our Game object will store the “library” for these menu options and we’ll store this in an enum named Options. Right now, our available options are Move, Delete, Change, and Tax.

We’ll also need a way to display these options to the user, so we’ll create an array that stores the strings explaining what each option is. We’re going to call this array optionString and we’ll be able to access it from other objects by using Game.optionString[].

Place this object in your room.

The gameObjectParent object

The next thing we want is a “Master Parent” object for all interactive objects in our game. The two properties every object will have in this game is a color and an array of options. The color will be used, as explained above, to draw the sprite as a specific color instead of the generic white that we created originally. To store these properties, we’ll use myColor and options. In our Create Event put:

// gameObjectParent_Create
myColor = c_white;
options = [];

We also want a way to convey to the user which object is currently selected, so in the Draw event we’ll put this code:

// gameObjectParent_Draw
if( global.selected == id){
    var xOffset = sprite_width * 0.1;
    var yOffset = sprite_height * 0.1;
    draw_sprite_ext( sprite_index, image_index, x - xOffset, y - yOffset, 1.2, 1.2, 0, c_black, 1);
}
draw_sprite_ext( sprite_index, image_index, x, y, 1, 1, 0, myColor, 1);

This will check if this object is currently selected. If it is, it draws a black, slightly larger version of the sprite behind our current sprite which will create a ghetto outline effect.

The last thing we want to do with this object is make it so that when it’s clicked, it sets global.selected to itself and then create a menu. We don’t have a menu object yet, so for now we’ll set the Mouse Left Pressed Event to this:

// gameObjectParent_LeftPressed
// TODO: Delete any menus that exist
global.selected = id;
// TODO: Create a menu

Building Parent Object

Next up we’re going to create oBuilding which is a generic building in our game. Our goal with this is to simply define behaviors which are unique from its parent object (gameObjectParent) and will be universal with all buildings in our game. To set this object up, we’re going to create a folder called Buildings and in this folder we will create oBuilding. Set its sprite to sBuilding and its parent to gameObjectParent. In the create event we will call event_inherited(); and then add an additional line of code defining the options that all buildings will share. Our code should look like this:

// oBuilding_Create
event_inherited();
options = [Options.Move, Options.Delete];

This means that all buildings will be allowed to be moved and deleted by default. In this example we will be redefining options in each child object, but you could also just append new options to the end of the array for children objects if you’d prefer.

oHouse and oCityHall

Next, we are going to create 2 specific types of buildings. These are oHouse for a simple house and oCityHall for all of our town’s important work to be done. oHouse will only be different from its parent object in that it will always be blue. To achieve this, we create oHouse in our Buildings folder, set its Sprite to sBuilding and its parent object to oBuilding. In our Create Event we will put:

// oHouse_Create
event_inherited();
myColor = c_blue;

Because we don’t define options in this object, it will allow options to be defined by its parent object. This means that oHouse.options is equal to [Options.Move, Options.Delete] because that’s what is defined in oBuilding. oCityHall will be unique in that its menu will enable the player to collect taxes and it will be yellow to make it stand out. Its code:

// oCityHall_Create
event_inherited();
myColor = c_yellow;
options = [Options.Move, Options.Delete, Options.Tax];

At this point if you want to test the functionality of global.selected you can place some of these buildings in your room’s Instances Layer and run the game. You’ll notice as you select buildings they will show that they are selected by drawing a black border. We haven’t programmed the menu functionality yet, though, so even though the options variable is set, you won’t be able to see it in game.

oPerson and oAlien

In our town we’ll have many generic people. We’ll also have a couple of aliens trying to blend in. We’re going to create a folder named People and in that folder we will create oPerson with sprite sPerson and parent gameObjectParent. People are diverse so we’ll have the color of our person be completely random and we’ll also decide which options should be available to our citizens. Place this in the Create Event:

// oPerson_Create
event_inherited();
myColor = make_color_rgb( irandom(255), irandom(255), irandom(255));
options = [Options.Move, Options.Delete, Options.Change];

Our alien will behave exactly the same as our person, but aliens aren’t that diverse naturally so we’ll create oAlien in our People folder with sprite sPerson and parent oPerson and in it’s Create Event we’ll make sure our aliens always start out green:

// oAlien_Create
event_inherited();
myColor = c_green;

Step 4: Creating a Menu System

oMenu and Finish gameObjectParent

Now we have the basic building blocks to our game, but we have no menu system yet. There are many ways to go about this, but our method is going to be to create a menu object that then creates a button object for each of the options available. First, we’ll define oMenu. In the Create Event we will set the following settings and variables:

// oMenu_Create
options = [];
initialized = false;

padding = 4;
w = 64 + padding * 2;
h = padding * 2;

What we want is to be able to have our game objects create oMenu and define the options. We set options to a blank array just so that it’s declared and won’t cause any undefined sort of behavior. We’ll switch initialized to true after options is set by the calling object and the buttons for those options have been created. padding stores the number of pixels we want between the edge of the menu and the side of the button. w and h will store the width and height of our menu. w is set to 64 because that’s the width of our sButton Sprite and we add padding * 2 because we want that padding on both sides of the button.

Now that we know how we want the menu object to work, let’s go back to our gameObjectParent object and finish defining its behavior for the Mouse Left Pressed Event:

// gameObjectParent_LeftPressed
if( instance_exists( oMenu)){
    instance_destroy( oMenu);
}
global.selected = id;
var myMenu = instance_create_layer( x + sprite_width, y, "Instances", oMenu);
myMenu.options = options;

First, we destroy any menus that already exist, then we set which object is currently selected, then we create an oMenu object and set its options to the options we have stored in our local options variable. Now we need to finish programming the behavior of oMenu. In oMenu‘s Step Event we want it to check if it’s already been initialized and if it hasn’t, create the buttons for the player to interact with.

// oMenu_Step
if( !initialized){
    initialized = true;
    h += 20 * array_length_1d( options);
    for( var i = 0; i < array_length_1d( options); ++i){
        var myButton = instance_create_layer( x + padding, y + padding + 20 * i, "GUI", oButton);
        myButton.option = options[i];
    }
}

First, we check if this menu has already been initialized and if it hasn’t, we set the height to now include as many buttons as the selected objects has options. The 20 is used here because our sButton‘s height is 18 and we want a 2 pixel margin at the bottom of the button. Finally, we loop through each option and create a button for it on the GUI Layer in our room. We’ll define how the button behaves in a moment. Remember, we don’t need to program any interaction with the menu because the interaction is either handled by the game objects (by selecting a game object) or by the buttons (by clicking a button).

The next behavior we’ll want to program for the menu is the Draw Event. This is going to be a fairly straightforward box that’s drawn around the buttons.

// oMenu_Draw
draw_set_color( c_black);
draw_rectangle( x, y, x + w, y + h, false);

The last thing we want to take into account is that when the menu is destroyed, we want to make sure it takes the buttons with it and that it no longer indicates that anything is selected. To this end we will create a Destroy Event and add the following code:

instance_destroy( oButton);
global.selected = noone;

If you want to test the game’s behavior to this point, create an object named oButton and then launch your game. You should see that when you select an object, a black box is now drawn beside it and will be a different size depending on how many options are available to the object you selected. We haven’t created buttons yet, so that won’t be working but we will soon!

oButton

Create oButton if you haven’t already and give it the sButton sprite. We don’t really need a Create Event but I like to include one anyway just to help prevent undefined errors and to make troubleshooting easier, so our Create Event will just have one line of code:

// oButton_Create
option = -1;

Next, we’ll draw the button so that we can finally see what we’re working with. We’ll also want to draw text on the button to tell the user what the button does, so create a font to be used on the button and name it fMenu. I defined my font as Size 10 Arial. In the Draw Event of your oButton put this code:

// oButton_Draw
draw_self();
draw_set_font( fMenu);
draw_set_color( c_black);
draw_text( x + 3, y + 1, Game.optionString[option]);

You might be a bit confused by the part that reads Game.optionString[option]. If you recall, we created an array in our Game object named optionString. The strings stored in that array correspond to the options defined in the Option enum, so if we created a button with option = Option.Move; then Game.optionString[option] would equal “Move”. Go ahead and execute the game from here and you should now see your menus drawn next to your selected object complete with different buttons for each of the options! A nice bit of polish you can add is to change the draw_self() part of oButton‘s Draw Event to be:

if(
    median( x, x + sprite_width, mouse_x) == mouse_x
    && median( y, y + sprite_height, mouse_y) == mouse_y
){
    draw_sprite_ext( sprite_index, image_index, x, y, 1, 1, 0, c_yellow, 1);
}else{
    draw_self();
}

This makes it so the button is drawn highlighted if the mouse is over the button and if it’s not the button is just drawn regular.

The very last thing we need to do for this tutorial is to define behavior when the buttons are pressed and it’s SUPER easy. All we need to do is create a Mouse Left Pressed Event in your oButton. In that event we’ll write:

// oButton_LeftPressed
switch( option){
    case Options.Move:
        (global.selected).x = irandom( room_width - 64);
        (global.selected).y = irandom( room_height - 64);
        instance_destroy(oMenu);
        break;
    case Options.Delete:
        instance_destroy(global.selected);
        instance_destroy(oMenu);
        break;
    case Options.Change:
        (global.selected).myColor = make_color_rgb( irandom( 255), irandom( 255), irandom( 255));
        break;
    case Options.Tax:
        show_message( "Noooo! Why, m'lord?!?!?!?!");
        break;
}

We’re using a switch statement to determine which option this button represents. If it’s a “Move” button, we move the object to a random location in the room. If it’s a “Delete” button, we destroy the object. If it’s a “Change” button (only available to People and Aliens), the object will change color. If it’s a “Tax ‘Em!” button, the people will cry out in protest.


And that’s all there is to it! If you have any questions please feel free to ask. If there’s something you think I need to elaborate on more let me know or if there’s something I messed up on please point it out.

]]>
https://odc.wvs.mybluehost.me/creating-context-menus/feed/ 0 142
Fog of War https://odc.wvs.mybluehost.me/fog-of-war/?utm_source=rss&utm_medium=rss&utm_campaign=fog-of-war https://odc.wvs.mybluehost.me/fog-of-war/#respond Thu, 02 May 2019 18:05:50 +0000 https://jemstonegames.com/?p=50 Continue reading "Fog of War"

]]>
This is a programming problem I’ve never really been sure how to solve. I’ve been curious about it since back when I had a pro version of Gamemaker 5 and found some people on gamemakergames.com that wanted to collaborate on creating an RTS “just like Starcraft.” It was overly ambitious, especially considering there were 5 of us, all under 15 I think, and none of us were artists. We ended up having a ground unit and an air unit that could be clicked, moved, and updated their depth accordingly but that’s about it.

Since then, I continually toy with the idea of starting an RTS project but I’ve never actually tackled the problem of Fog of War so this was both exciting and daunting for me. I had heard of two methods in the past: The most obvious, using a binary ds_grid that tracks which areas have been explored or can be seen, and the second being using surfaces and subtractive blendmodes to alter what’s drawn.

I really wanted something resembling Starcraft Brood War’s fog of war system. At this point, I am planning on this being a single-player RTS game, so there’s no reason to only use SC2’s multiplayer form of fog of war that has no “Unexplored Terrain” layer. Also, if I do implement multiplayer, I think I prefer an experience using unexplored terrain. I’m not trying to topple SC2 or even BW as a competitive RTS so I would be fine with a fun, exploratory RTS experience.

Brood War Fog of War
Notice the 3 visibilities: Totally obscured, terrain/structures only, and full vision.

Implementation

My first idea was to create two ds_grids in a FogOfWar object, Unexplored and Forgotten. I created an all-black sprite that was 32×32 to be drawn for every grid square that was true (meaning it was not being revealed at the time). Every 30 frames the game would update the grid by taking each unit location and using ds_grid_set_disk using the unit’s “SightRadius”.

A very pixel fog of war
First attempt at Fog of War

Overall, it’s the effect I want but it’s far too blocky. I tried adjusting the grid square size from 32 down to 4 but it seemed the right balance of size and performance at 16×16.

Next, I thought I’d use an oversized spherical sprite so that it would bleed over and kind of blend into the background. I figured overlapping sprites wouldn’t matter much and what would be left is a nice smooth edge.

Same idea, less hard
Passable….

This isn’t perfect but it wasn’t bad. I was fully ready to accept this as the solution. That is until…

Crappy light fog of war
Nevermind.

Turns out the “Forgotten” fog of war layer looked bad with this sprite idea. Now, I decided it was time to venture out into the gpu_set_blendmode functions and try this overlapping bitmap surface approach. Now, getting into surfaces can start eating up memory, especially if my plan is to use 2 surfaces in addition to the application surface. If I decide I’m going to draw a surface the size of the maps, this could be hugely problematic. My first plan was only to draw the surface the size of the screen. Then, what I do is clear the surface to black, set the blend mode to be subtractive. Then I cycle through every unit and draw a white blended sphere scaled to the size of the unit’s SightRadius and then scaled again to account for the surface scaling. After that, I draw that surface over the game and boom! It looked great.

Unfortunately, as soon as a unit moved outside of the camera area, it lost its vision (because it was no longer being checked to draw its circle). I decided to double the size of the surface and center it around the camera, but now I’m dealing with massive surfaces all over again and not feeling very comfortable with the implementation.

At this point, I decided to make the surface the size of the room scaled down by a factor of 8. I did the same thing as before with drawing the spheres and using subtractive blending but this time with a larger, scaled-down surface. It was looking better and better!

Fog of War using subtractive blending.
Using a white sphere and subtractive blending I was now able to create softer edges.

Looking at the pixelation though, I thought wouldn’t it be great if I could just use interpolation on that surface and not on the rest of the game. Wait a minute, I can!

Subtractive blending with surface pixel interpolation.
Beautiful!

This was so pretty and smooth, I loved it. But now I had to face a problem I knew was looming: Surfaces are volatile so while they can create nice visual effects, you can’t count on them to store data. If I were to use this method exclusively, I couldn’t count on the Unexplored layer to remember where the player had already explored.

I decided to take what I’d learned from using this method to revisit my old method of drawing the ds_grids square by square. This time, I created a surface for them, scaled them down, cleared the surface to white, then drew a single pixel for each grid square: opaque black if it hadn’t been explored and 50% alpha black if it had been explored but wasn’t currently visible. I drew the surface to the application surface using subtractive blending and the result was not what I expected…

Learning what subtractive blending actually does
That’s not what I meant!

See, it was now using that blending to blend it with the brown dirt and giving me a red fog of war. On top of that, it now had this ugly white fringe to it.

Next, I decided to use the smooth method for the “Forgotten” layer and the grid method for the “Unexplored” layer. I figured this would give me a smooth border for my immediate vision and a blockier border for the unexplored area which would also update a little bit more infrequently.

Combining Fog of War methods
Again, part of it looks good…

You can see that it doesn’t really look too bad other than that ugly white fringe.

Eye-hurting blur
No more white border, now I need glasses.

I managed to get rid of the fringe by changing the some color settings, but now having the different levels of blur gave a really uncomfortable feeling. I tweaked these values and resolutions, tried enabling and disabling blending in different areas. Finally, I found that by extending the reach of the SightRadius when clearing the ds_grid for the Unexplored layer, it created a more smooth transition between layers. I also decreased the time between updates from every 30 frames to every 20 frames.

#region Draw to Forgotten Surface

surface_set_target( ForgottenSurface);

draw_set_color( c_black);
draw_rectangle( 0, 0, room_width / FOW_SCALE, room_height / FOW_SCALE, false);
draw_set_color( c_white);

gpu_set_blendmode( bm_subtract);

with( oActor){
	var r = SightRadius / 128 / FOW_SCALE;
	draw_sprite_ext( sFogOfWar, 0, x / FOW_SCALE, y / FOW_SCALE, r, r, 0, c_white, 1);
}

gpu_set_blendmode( bm_normal);

surface_reset_target();

#endregion

#region Draw Surfaces

gpu_set_texfilter(true);

draw_surface_ext( ForgottenSurface, 0, 0, FOW_SCALE, FOW_SCALE, 0, c_white, 0.75);
draw_surface_ext( UnexploredSurface, 0, 0, FOW_GRIDSIZE, FOW_GRIDSIZE, 0, c_black, 1);

gpu_set_texfilter(false);

#endregion

I was finally happy with the result and it seems very performant.

Fog of War in action
Eureka!

It’s not perfect, but I’m happy with it. It still gives the effect I need and it will allow me to control what’s visible or not.

Going Forward

One of the major obstacles ahead of me is that there will be certain line-of-sight blockers, such as walls and ledges. I’m not sure how I’ll be able to handle those in an efficient way. I think I may need to do checks that only update when a unit is moved, but those kind of checks might also cause a performance hit.

I think what I may end up doing is revisiting the idea of having the surface only be the size of the screen but allow units outside of the screen to draw to the edges of the surface. Once I introduce variable terrain I’ll probably have to pick this back up and then again when it’s time to optimize.

]]>
https://odc.wvs.mybluehost.me/fog-of-war/feed/ 0 50
Camera https://odc.wvs.mybluehost.me/camera/?utm_source=rss&utm_medium=rss&utm_campaign=camera https://odc.wvs.mybluehost.me/camera/#respond Wed, 01 May 2019 18:20:00 +0000 https://jemstonegames.com/?p=45 Continue reading "Camera"

]]>
Second biggest make-or-break component of an RTS is the camera system. If you can’t quickly see what you need to see, the controls for your units aren’t really as important – on account of you can’t reliably see them when you need to. Again, I came up with a list of goals for the camera:

  • Moving the mouse to the edge of the screen should pan the camera in that direction.
  • Using the arrow keys should quickly move the camera.
  • Pressing Middle Mouse should allow you to drag the camera.
  • Double tapping a ControlGroup hotkey should center the camera on that control group.
  • Pressing Spacebar with a unit or group selected should center the camera on your selection.
  • Pressing CTRL or SHIFT + F1-F12 should save the current screen location.
  • Pressing F1-F12 without those modifier keys should center the screen on the saved location.
  • The mouse wheel should allow zooming in and out.

Zooming may or may not be in the final implementation since for a 2D game I’m not sure how much zooming will really add – I don’t plan to allow a massive amount of zoom-out but I may revisit that as well.

Camera Movement
Edge and Drag Scrolling

Implementation

I decided to create a Camera object that will move the camera to always be centered on itself. This will allow saving screen locations to only have to save an x and y coordinate and also centering the camera on a specific spot or unit will be as easy as moving the object.

Edge Scrolling

This actually turned out to be trickier than I expected. I decided that since I was confining the cursor to the window, I should use the window_* functions to get the window dimensions and cursor location, and if the cursor was outside of the confines of the window or right on the very edge of the window, to start scrolling. What I found though was that when the cursor was outside of the window, I could grab the cursor and put it back inside of the window but it could just jump back out and as long as it wasn’t inside of the window, my game wouldn’t respond to it.

Using some debugging I found that window_mouse_get_* would not acknowledge it was outside of the window and would just save the cursor’s last location. However, display_mouse_get_* would allow the cursor location to update outside of the game window. This meant that I had to compare the cursor’s location on the display with the relative location of the window on the display and then determine if the cursor was out of bounds. After that, though, I was happy with the way the scrolling worked.

Key Scrolling

Super easy to implement, just pressing a key moves the Camera. What I found at this stage though was that even though I was clamping where the camera was actually at, the Camera object wasn’t clamped which caused a weird behavior of it not actually being where you’d think it was. That was easily fixed by clamping Camera’s location instead of the “camera.”

Saving Camera Locations

This worked pretty similarly to the Selection saving, except the camera locations all hold an array of the x and y coordinates that are jumped to.

for( var i = 0; i < 12; ++i){
	if( keyboard_check_pressed(vk_f1 + i)){
		// Save
		if( keyboard_check( vk_shift) || keyboard_check( vk_control)){
			SavedLocation[i] = [x, y];
		}else if( SavedLocation[i] != noone){
			var sl = SavedLocation[i];
			x = sl[0];
			y = sl[1];
		}
	}
}

Boy, that’s easy!

Zooming

I set a ZoomMin and ZoomMax variable that constrain the zoom based on the width of the camera. This was simply changing the camera size. I screwed it up at first though because i was trying to compensate for the new camera size by adding half of the difference in size. That kept scrolling to the bottom right though. Eventually I realized that I’m already centering the camera so the compensation is done automatically. Duh.

Control Group Double Tap

For this I had to go back into the Selection code and add a timer that detects when the user is double-tapping. If it’s a double tap, the camera will center on the average of all of the selected units’ location. I may at a later point introduce an algorithm that tries to center the camera on the biggest cluster of units but we’ll see if that’s necessary. One thing I noticed while testing this is that my Select All that was working before was no longer working. After a lot of confusion, I realized that my Select All code was using the viewport variables instead of the camera variables so it would only ever select units in the top left side of the room instead of where the camera was. I’m starting to notice a theme of the cause of all my problems (me).

Spacebar

This was just using the same code as the double tap but averaging the current selection instead of the control group. Super easy.

Drag Scrolling

This was actually something I had kind of forgotten about until this point. It’s weird, because when I’m playing I use this a lot. To implement this, when the middle mouse is pressed down, I save the location of the cursor relative to the window, hide the cursor, and then center the cursor. From then on, as long as the middle mouse is still down, I take the offset of the mouse from the center and multiply it by a DragScrollSpeed multiplier and then recenter the mouse. When the button is released, we return the cursor to the original spot and resume business as usual.

if( mouse_check_button_pressed( mb_middle)){
	DragScrollOriginX = window_mouse_get_x();
	DragScrollOriginY = window_mouse_get_y();
	window_mouse_set( window_get_width() / 2, window_get_height() / 2);
	window_set_cursor( cr_none);
}else if( mouse_check_button( mb_middle)){
	var midx = window_get_width() / 2;
	var midy = window_get_height() / 2;
	x += (window_mouse_get_x() - midx) * DragScrollSpeed;
	y += (window_mouse_get_y() - midy) * DragScrollSpeed;
	window_mouse_set( midx, midy);
}

if( mouse_check_button_released( mb_middle)){
	window_mouse_set( DragScrollOriginX, DragScrollOriginY);
	window_set_cursor( cr_default);
}

The thing that caught me up here was that setting the cursor location doesn’t actually take effect until the end of the step / cycle. This mean that my screen always jumped to the original offset before working properly. This was easily remedied by using else if instead of just ifs, but it’s good to know nonetheless.

Improvements to Selection

I noticed while testing the Camera that selecting moving units was really hard. I decided this was most likely because, if you’ll recall, I was using the left mouse release to check for units selected no matter what. When you play, you expect the unit you press down on to be the one you selected. I went back into the selection code and added a little piece that saves the unit you originally click on (if any) and will use that as long as you’re not drag clicking

Conclusion

So I’m now very happy with the camera and unit selection. It feels very fluid and natural. The Camera is contained to two events and 5 scripts totalling around 150 lines of code. I did have to expand the Selection a little bit, but so far I have both components very well self-contained!

]]>
https://odc.wvs.mybluehost.me/camera/feed/ 0 45
Initial Commit https://odc.wvs.mybluehost.me/initial-commit/?utm_source=rss&utm_medium=rss&utm_campaign=initial-commit https://odc.wvs.mybluehost.me/initial-commit/#respond Tue, 30 Apr 2019 18:45:19 +0000 https://jemstonegames.com/?p=37 Continue reading "Initial Commit"

]]>
I’ve wanted to make my own RTS game since I first played Starcraft back in ~2001. It is a huge undertaking though, so I’ve always thought it’s too ambitious. Sometimes I’d sit down to start, make part of a system, get overwhelmed, and save it never to be looked at again. This time, though, I’m attacking it with a plan!

Components

Instead of charging out headlong into what I expect my final project to be, I’ve decided to get every component of a good RTS working on its own in its own isolated project. The goal with these isolated projects is to keep all of the elements as self-contained as possible to minimize coupling. After I have all of the components independently developed, I’ll be able to import them and begin design of the game itself.

Step 1: Selection

The biggest asset to a good RTS game is an intuitive, responsive, and flexible selection system. Once you get in your flow while playing, you want to be able to issue commands to the right units as quickly as possible. If you can’t easily get the selection you want, it interrupts the flow and creates a frustrating experience.

Planning

Using the above information, I created the following goals for my selection system:

  • Selection will prioritize groups of units or a single building
  • A single click with the left mouse button will select a single unit
  • A drag-click will follow the selection priority
  • Pressing CTRL while clicking will select all of the same type of unit on the screen
  • Pressing SHIFT while clicking will modify your current selection. If any of the units selected here are not already selected, all newly selected units will be added to your current selection. If all of the units selected are already selected, these units will be removed from your current selection
  • Double-click will have the same effect as CTRL+click
  • Pressing CTRL+SHIFT will combine these effects

Additionally, we need to be able to save our selections to be recalled. To that end we will use:

  • Control Groups will be stored in keyboard row 1-9+0
  • CTRL+# will create and/or replace that control group with your current selection
  • SHIFT+# will add your current selection to that control group
  • Simply pressing the number will recall the stored control group

Implementation

I decided that to store which units are selected, I will create an object called Selection with a ds_list called Selected. The units will also need to remember whether or not they’re selected so our Selection object will have to make sure those two sets of information are synced. By creating a new ds_list when the left mouse button is released, we can compare the newly selected units with our currently selected units and modify our selection according to which modifier keys are depressed at the time.

Implementing the control groups was much easier than the mouse controls. Selection has an array called ControlGroup that holds 10 ds_lists. When saving a selection, we copy our Selected ds_list over to the ControlGroup[i] ds_list. When recalling a selection we do the opposite.

for( var i = 0; i < 10; ++i){
	if( keyboard_check_pressed( ord( string(i)))){
		// Add to group
		if( keyboard_check( vk_shift)){
			for( var j = 0; j < ds_list_size( Selected); ++j){
				var a = Selected[| j];
				if( !a.ControlGroup[i]){
					ds_list_add( ControlGroup[i], a);
					a.ControlGroup[i] = true;
				}
			}
		// Set group
		}else if( keyboard_check( vk_control)){
			var cg = ControlGroup[i];
			for( var j = 0; j < ds_list_size( cg); ++j){
				(cg[| j]).ControlGroup[i] = false;
			}
			ds_list_clear(cg);
			for( var j = 0; j < ds_list_size( Selected); ++j){
				var a = Selected[| j];
				a.ControlGroup[i] = true;
				ds_list_add( ControlGroup[i], a);
			}
		// Recall group
		}else{
			selection_clear();
			var cg = ControlGroup[i];
			for( var j = 0; j < ds_list_size(cg); ++j){
				ds_list_add( Selection, cg[| j]);
				(cg[| j]).Selected = true;
			}
		}
	}
}

Each game object has a boolean corresponding to the ds_lists Selection.Selected and Selection.ControlGroup[0-9].

Test Environment

For the test environment I created a simple hierarchy which is a simplified version of what I plan to use in the final product: Two objects oGuy and oBuilding have a parent oActor. This set up allows Selection to verify that a selection is selectable while still allowing the two types to be handled differently.

Each actor will have a script called RightClick that stores what should be their default behavior when the right mouse button is clicked. In this case, buildings do nothing and oGuys will move towards the click. When an actor knows it is selected, it will draw a green circle beneath itself.

Testing functionality of new Selection system

I was able to create this component using 1 object (4 Events) and 2 scripts totalling less than 200 lines of code.

]]>
https://odc.wvs.mybluehost.me/initial-commit/feed/ 0 37