I have published 11 tutorials on making a game from scratch, while the game was intended to be a rougue-like it has evolved away from the genre. I aim to add interesting features which might help others. While this may never be a fun to play or complete game it hopefully will aid others in producing their own games.
If you are interested in checking out the changes to the Chunk management and also the loading and saving methods then check out this pull request (I will create posts about these changes in the future.):
https://github.com/tyler6699/evoscape/pull/7
This tutorial will add a weapon to the Hero that points toward the mouse cursor position. The gun points right or left and swaps hands depending on the mouse position.
Entity Class
New variables are added to Entity that will allow rotation and horizontal flipping of
a texture, this means we only need one image for the weapon, also we will add an active flag and an array of guns called weapons. The active flag can be used later to determine if the gun or weapon has been drawn. The array will allow multiple weapons in a future tutorial.
The drawRotated method uses spriteBatch draw that will allow the angle and rotation point of our entity.
// New Variables public float angle; public Boolean flipX = false; public Boolean flipY = false; public boolean active; public ArrayList weapons; // New draw function which we will over ride public void drawRotated(SpriteBatch batch){ if(texture != null) batch.draw(texture, pos.x, pos.y, 0, 0, width, height,1, 1, angle, 0, 0, (int)width, (int)height, flipX, flipY); }
All of the available parameters for the draw method we are calling.
x the x-coordinate in screen space y the y-coordinate in screen space originX the x-coordinate of the scaling and rotation origin relative to the screen space coordinates originY the y-coordinate of the scaling and rotation origin relative to the screen space coordinates width the width in pixels height the height in pixels scaleX the scale of the rectangle around originX/originY in x scaleY the scale of the rectangle around originX/originY in y rotation the angle of counter clockwise rotation of the rectangle around originX/originY srcX the x-coordinate in texel space srcY the y-coordinate in texel space srcWidth the source with in texels srcHeight the source height in texels flipX whether to flip the sprite horizontally flipY whether to flip the sprite vertically
At the moment the gun class is a basic entity that only draws to the screen, I will look at shooting in the next tutorial. What we care about is the position of the mouse retaliative to the hero, is it to the left or right side and at what angle.
The Gun class has x and y offsets for the origin and also the x position, these values allow us to move the gun into the hand of the hero. The gun can be set to the Hero’s x position by default and moved left or right accordingly, knowing the size of the gun and hero we can work out how far to move it so it appears in the correct position.
package uk.co.carelesslabs.weapons; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import uk.co.carelesslabs.Media; import uk.co.carelesslabs.entity.Entity; public class Gun extends Entity { float originXOffset; // OriginX Offset float originYOffset; // OriginY Offset float xPos; // X offset for gun position float xMinPos; // X Position offset facing left float xMaxPos; // X Position offset facing right public Gun(float originXOffset, float xMinRight, float xMaxRight){ texture = Media.gun; width = texture.getWidth(); height = texture.getHeight(); active = true; originYOffset = height/2; this.originXOffset = originXOffset; this.xMinPos = xMinRight; this.xMaxPos = xMaxRight; } public void drawRotated(SpriteBatch batch){ if(angle > 90 && angle < 270){ // 6 to 12 Clockwise or LEFT xPos = xMinPos; flipY = true; } else { // 12 to 6 clockwise or RIGHT xPos = xMaxPos; flipY = false; } // When the gun is to the right of the hero we move // the it by xMaxPos (7) and when // its to the left we move it byxMinPos (-1) if(texture != null) batch.draw(texture, pos.x + xPos, pos.y, originXOffset, originYOffset, width, height, 1, 1, angle, 0, 0, (int)width, (int)height, flipX, flipY); } }
There is a new Vector3 added to the hero class, previously the hero was not aligned to the centre of the screen, this new variable will be updated to the hero position but half of the hero width +/- from the x value so that the hero appears centred.
A new ArrayList of ‘Gun’ called weapons is setup and initiated, we add a Gun to the list, the origin X Offset is set to 1 and the min and max x offsets set the -1 and 7. This sets up the rotation point and the left/right positions relative to the hero.
public Hero(Vector3 pos, Box2DWorld box2d){ super(); cameraPos = new Vector3(); // new variable type = EntityType.HERO; width = 8; height = 8; texture = Media.hero; speed = 30; inventory = new Inventory(); reset(box2d, pos); // Weapon weapons = new ArrayList(); weapons.add(new Gun(1, -1, 7)); }
The Hero draw function now loops through the weapon array and will call drawRotated for any active gun.
The cameraPos variable is also updated in this draw method, it is set to the same as the hero position and then the x value has half of the Hero width added on.
@Override public void draw(SpriteBatch batch){ if(shadow != null) batch.draw(shadow, pos.x, pos.y, width, height); if(texture != null) batch.draw(texture, pos.x, pos.y, width, height); // Loop all weapons and draw the active ones for(Gun g : weapons){ if(g.active){ g.drawRotated(batch); } } ... // Update Camera Position cameraPos.set(pos); cameraPos.x += width / 2; }
Gun position
The gun by default is positioned at Hero.pos.x, it it was not moved using the x offset this is how it would look:
Once it is in game and rotating you can see if flips but stay in the right hand of the Hero:
The small yellow cross shows the point at which the entity rotates around, placing it here makes the movement seem more real than if it was centred.
When the gun needs to face right flip is set to false and it is rendered hero x + 7. When the gun is facing left the texture is flipped and moved left by 1. The texture flips from the x point so the flip alone moves the texture left by the width. If you change the origin X value you can see how it affects the rotation (Not all weapons would have the same rotation point)
When the Hero update is called we should update all active weapons so they move with the Hero, we also update the angle, I found that the mouse angle from the centre of the screen did not match the draw function, taking 90 degrees away made these match.
public void update(Control control) { .... // Update weapons for(Gun g : weapons){ if(g.active){ g.updatePos(pos.x, pos.y); g.angle = control.angle - 90; } }
If you wanted a Hero that faced the mouse (8 directions) we can implement a mouse angle to compass position, while this is not used in the tutorial it may be useful to someone and we can use it in the future. Here are the ENUM values, the order of these is important.
/** * Compass directions */ public enum Compass { S, SE, E, NE, N, NW, W, SW }
The new angle and facing variables will be set/updated within the the Control class. On mouseMoved we have the x and y values for the current mouse position which can be used to calculate the angle between the screen centre and the cursor:
angle = (float) Math.toDegrees(Math.atan2(screenX – (screenWidth/2), screenY – (screenHeight/2)));
if the angle is negative then we add 360:
angle = angle < 0 ? angle += 360: angle;
To turn this angle into a compass direction:
direction = (int) Math.floor((angle / 45) + 0.5) & 7;
This will produce 0 to 7 as a result, we can then pick N ENUM from the list, 0 is S 1 is SE and so on:
facing = Compass.values()[direction];
// ACTIONS public boolean interact; public float angle; public int direction; public Compass facing; @Override public boolean mouseMoved(int screenX, int screenY) { float flippedY = screenHeight - screenY; mousePos.set(screenX, flippedY); // Set angle of mouse angle = (float) Math.toDegrees(Math.atan2(screenX - (screenWidth/2), screenY - (screenHeight/2))); angle = angle 0){ Rumble.tick(Gdx.graphics.getDeltaTime()); camera.translate(Rumble.getPos()); } else { camera.position.lerp(hero.cameraPos, .2f); // use new Vector3 variable<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span> }
Hopefully there are a few useful snippets of code in the tutorial, remember there are always other ways to achieve a goal and often more efficient methods.
Next time I will add bullets and firing, thanks for reading!