Player Bullets > 2D

Create player bullets on mouse click

Demonstrates how to create player bullets that will travel to a point determined by a mouse click on screen. This method works with a camera, using the screen to world position of the mouse click therefore the zoom can be changed. The player can move and the bullets will be destroyed when hitting the border rectangle. This can be used for both player bullets as well as enemy bullets, for enemy bullets the mouse click center point is substituted with the player center. WASD keys move player, left mouse button to shoot and up arrow key to change zoom. View on Github

Before you start
I am a self taught Go programmer and do it as a hobby, the code below is my own interpretation of how to do something, probably not the only way or the best way. This is intended as a resource to learn some basic Raylib and Go game dev skills. If you want to use any of the code anywhere else, feel free to do so.

go



package main

import (
	"math"

	rl "github.com/gen2brain/raylib-go/raylib"
)

/* MORE RAYLIB GO EXAMPLES ARE AVAILABLE HERE:

https://github.com/unklnik/raylib-go-more-examples

*/

var (
	bullets                        []xbullet     //SLICE OF BULLET STRUCTS
	shootTarget, cursorCam, cursor rl.Vector2    //TARGET, CURSOR CAMERA VS SCREEN POSITION, CURSOR
	playerRec                      rl.Rectangle  //PLAYER RECTANGLE
	spd                            = float32(10) //MAX SPEED
	attackT                        int32         //PAUSE BETWEEN BULLETS
	fps                            = int32(60)   //FRAMES PER SECOND
	camera                         rl.Camera2D   //CAMERA
	borderRec                      rl.Rectangle  //BOUNCING & BORDER RECTANGLES
	cntr                           rl.Vector2    //CENTER OF SCREEN
	scrW, scrH                     int
)

// STRUCT OF BULLET CONTAINING REC, DIRECTION X & Y, OFF BOOL
type xbullet struct {
	rec        rl.Rectangle
	dirX, dirY float32
	off        bool
}

func main() {

	rl.InitWindow(0, 0, "player bullets - raylib go - https://github.com/unklnik/raylib-go-more-examples")
	scrW, scrH = rl.GetScreenWidth(), rl.GetScreenHeight() //GET SCREEN SIZES
	rl.SetWindowSize(scrW, scrH)                           //SET WINDOW SIZE
	rl.SetWindowState(rl.FlagBorderlessWindowedMode)
	//rl.ToggleFullscreen() //UNCOMMENT IF YOU HAVE DISPLAY ISSUES WITH OVERLAPPING WINDOW BARS

	rl.HideCursor() //HIDE THE STANDARD MOUSE CURSOR

	cntr = rl.NewVector2(float32(scrW/2), float32(scrH/2)) //CALCULATE CENTER

	borderRec = rl.NewRectangle(cntr.X-float32(scrW/4), cntr.Y-float32(scrH/4), float32(scrW/2), float32(scrH/2)) //DEFINE BORDER RECTANGLE

	siz := float32(32)                                                //PLAYER SIZE
	playerRec = rl.NewRectangle(cntr.X-siz/2, cntr.Y-siz/2, siz, siz) //INITIAL PLAYER RECTANGLE

	camera.Zoom = 1.5                   //SETS CAMERA ZOOM
	camera.Target = cntr                //SET CAMERA TARGET
	camera.Offset.X = float32(scrW / 2) //ADJUST CAMERA FOR ZOOM
	camera.Offset.Y = float32(scrH / 2) //ADJUST CAMERA FOR ZOOM

	rl.SetTargetFPS(fps) //NUMBER OF FRAMES DRAWN IN A SECOND

	for !rl.WindowShouldClose() {

		cursor = rl.GetMousePosition()                    //GET MOUSE POSITION
		cursorCam = rl.GetScreenToWorld2D(cursor, camera) //GET MOUSE POSITION IN CAMERA SPACE WITH ZOOM

		upBullets() //UPDATE BULLET MOVEMENTS
		input()     //CAPTURE INPUT

		//TIMER
		if attackT > 0 { //PAUSE BETWEEN SHOTS TIMER
			attackT--
		}

		rl.BeginDrawing()

		rl.ClearBackground(rl.Black)

		rl.BeginMode2D(camera)

		rl.DrawRectangleLinesEx(borderRec, 4, rl.Green) //DRAWS BORDER REC

		rl.DrawRectangleLinesEx(playerRec, 8, rl.Magenta) //DRAW PLAYER REC

		//DRAW BULLETS
		for i := 0; i < len(bullets); i++ {
			rl.DrawRectangleLinesEx(bullets[i].rec, 2, rl.Yellow)
		}

		//DRAW CIRCLE TARGET INSTEAD OF CURSOR
		rl.DrawCircleLines(int32(cursorCam.X), int32(cursorCam.Y), 10, rl.Red)

		rl.EndMode2D()

		rl.DrawText("W A S D keys move", 10, 10, 20, rl.White)
		rl.DrawText("left mouse to shoot", 10, 40, 20, rl.White)
		rl.DrawText("up arrow key change zoom", 10, 70, 20, rl.White)

		rl.EndDrawing()
	}

	rl.CloseWindow()
}
func input() {
	//INPUT KEYS FOR PLAYER MOVEMENT SEE MOVEPLAYER FUNCTION
	if rl.IsKeyDown(rl.KeyW) {
		movePlayer(1)

	} else if rl.IsKeyDown(rl.KeyS) {
		movePlayer(3)
	}
	if rl.IsKeyDown(rl.KeyD) {
		movePlayer(2)
	} else if rl.IsKeyDown(rl.KeyA) {
		movePlayer(4)
	}

	//CREATE BULLET IF ATTACK TIMER IS ZERO
	if rl.IsMouseButtonPressed(rl.MouseLeftButton) && attackT == 0 {
		attackT = fps / 4
		shootTarget = cursorCam //POSITION FOR BULLET AIMING
		shoot()
	}

	//CHANGE ZOOM
	if rl.IsKeyPressed(rl.KeyUp) {
		if camera.Zoom == 2 {
			camera.Zoom = 1
		} else if camera.Zoom == 1.5 {
			camera.Zoom = 2
		} else if camera.Zoom == 1 {
			camera.Zoom = 1.5
		}
		camera.Target = cntr
		camera.Offset.X = float32(scrW / 2)
		camera.Offset.Y = float32(scrH / 2)
	}
}

// CREATE BULLET FUNCTION
func shoot() {
	zbullet := xbullet{}
	zbullet.rec = playerRec //DUPLICATE PLAYER RECTANGLE FOR BULLET

	//MAKE DUPLICATE RECTANGLE SMALLER
	zbullet.rec.X += playerRec.Width / 2
	zbullet.rec.Y += playerRec.Height / 2
	zbullet.rec.Width = zbullet.rec.Width / 2
	zbullet.rec.Height = zbullet.rec.Height / 2

	//CALCULATE X & Y SPEED TO MOVE TO SHOOT TARGET
	playerCntr := rl.NewVector2(playerRec.X+playerRec.Width/2, playerRec.Y+playerRec.Height/2)
	diffX := absdiff(playerCntr.X, shootTarget.X) //GET ABSOLUTE X DISTANCE FUNCTION BELOW
	diffY := absdiff(playerCntr.Y, shootTarget.Y) //GET ABSOLUTE Y DISTANCE FUNCTION BELOW

	if diffX > diffY {
		zbullet.dirX = spd                            //IF DIFFERENCE X IS LARGER X IS FULL SPEED
		zbullet.dirY = diffY / (diffX / zbullet.dirX) //CALCULATE Y SPEED
	} else {
		zbullet.dirY = spd                            //IF DIFFERENCE Y IS LARGER Y IS FULL SPEED
		zbullet.dirX = diffX / (diffY / zbullet.dirY) //CALCULATE X SPEED
	}

	//IF TARGET IS BEHIND PLAYER CHANGE X DIRECTION TO NEGATIVE
	if playerCntr.X > shootTarget.X {
		zbullet.dirX = -zbullet.dirX
	}

	//IF TARGET IS ABOVE PLAYER CHANGE Y DIRECTION TO NEGATIVE
	if playerCntr.Y > shootTarget.Y {
		zbullet.dirY = -zbullet.dirY
	}

	//ADD BULLET TO SLICE
	bullets = append(bullets, zbullet)
}

func upBullets() {

	clear := false //TO CLEAR BULLETS IF COLLISIONS
	for i := 0; i < len(bullets); i++ {

		if !bullets[i].off {
			checkRec := bullets[i].rec    //DUPLICATE RECTANGLE FOR NEXT COLLISIONS
			checkRec.X += bullets[i].dirX //MOVE DUPLICATE TO NEXT POSITION
			checkRec.Y += bullets[i].dirY

			//VECTOR 2 POINTS OF FOUR CORNERS OF BULLET RECTANGLE
			v1 := rl.NewVector2(checkRec.X, checkRec.Y)
			v2 := v1
			v2.X += bullets[i].rec.Width
			v3 := v2
			v3.Y += bullets[i].rec.Height
			v4 := v3
			v4.X -= bullets[i].rec.Width

			//CHECK IF VECTOR 2 HAS EXITED BORDER
			canmove := true
			if !rl.CheckCollisionPointRec(v1, borderRec) || !rl.CheckCollisionPointRec(v2, borderRec) || !rl.CheckCollisionPointRec(v3, borderRec) || !rl.CheckCollisionPointRec(v4, borderRec) {
				canmove = false
			}

			if canmove {
				bullets[i].rec = checkRec //IF NO EXITS MOVE BULLET
			} else {
				bullets[i].off = true //IF EXITED TURN BULLET OFF
				clear = true
			}
		}
	}

	//IF CLEAR IS ON REMOVE ALL OFF BULLETS FROM SLICE
	if clear {
		for i := 0; i < len(bullets); i++ {
			if bullets[i].off {
				bullets = remBullet(bullets, i)
			}
		}
	}

}
func movePlayer(direc int) {

	checkRec := playerRec //DUPLICATE PLAYER RECTANGLE

	//MOVE DUPLICATE IN THE DIRECTION OF KEYPRESS
	switch direc {
	case 1: //UP
		checkRec.Y -= spd
	case 2: //RIGHT
		checkRec.X += spd
	case 3: //DOWN
		checkRec.Y += spd
	case 4: //LEFT
		checkRec.X -= spd
	}

	//VECTOR 2 POINTS OF FOUR CORNERS OF PLAYER RECTANGLE
	v1 := rl.NewVector2(checkRec.X, checkRec.Y)
	v2 := v1
	v2.X += playerRec.Width
	v3 := v2
	v3.Y += playerRec.Height
	v4 := v3
	v4.X -= playerRec.Width

	//CHECK IF VECTOR 2 HAS EXITED BORDER
	canmove := true
	if !rl.CheckCollisionPointRec(v1, borderRec) || !rl.CheckCollisionPointRec(v2, borderRec) || !rl.CheckCollisionPointRec(v3, borderRec) || !rl.CheckCollisionPointRec(v4, borderRec) {
		canmove = false
	}

	if canmove { //IF NO EXITS MOVE PLAYER
		playerRec = checkRec
	}

}

// REMOVES BULLET FROM SLICE
func remBullet(slice []xbullet, s int) []xbullet {
	return append(slice[:s], slice[s+1:]...)
}

// GET ABSOLUTE DIFFERENCE
func absdiff(num1, num2 float32) float32 {
	num := float32(0)
	if num1 == num2 {
		num = 0
	} else {
		if num1 <= 0 && num2 <= 0 {
			num1 = getabs(num1)
			num2 = getabs(num2)
			if num1 > num2 {
				num = num1 - num2
			} else {
				num = num2 - num1
			}
		} else if num1 <= 0 && num2 >= 0 {
			num = num2 + getabs(num1)
		} else if num2 <= 0 && num1 >= 0 {
			num = num1 + getabs(num2)
		} else if num2 >= 0 && num1 >= 0 {
			if num1 > num2 {
				num = num1 - num2
			} else {
				num = num2 - num1
			}
		}
	}
	return num
}

// GET ABSOLUTE VALUE
func getabs(value float32) float32 {
	value2 := float64(value)
	value = float32(math.Abs(value2))
	return value
}


Want to give it a Go?

To start making games with Go and Raylib you will need:

You can, of course, use other code editors however VS Code is my own personal preference

Related Content