Intro
This is a new series following Cosplore3D, a raycaster game to learn 3D graphics. This project is part of 12 Months 12 Projects, a challenge I set myself.
We just did a bunch of work on the first level, Ankaran, but the gameplay is a bit dry, so we need to start actually implementing features that make the game fun.
Shooting Enemies
When we click, we should see if we shot an enemy.
func (w *Weapon) shoot(p *Player, enemies []*Enemy) {
var e *Enemy
var dx, dy, dis, angle float64
w.curMag--
for i := 0; i < len(enemies); i++ {
e = enemies[i]
dx = e.x - p.x
dy = e.y - p.y
dis = math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2))
angle = to_degrees(math.Acos(dx / dis))
if math.Asin(dy/dis) < 0 {
angle = -angle
}
// How much to the left or right of the player the enemy is
angle -= p.angle
angle = bound_angle(angle)
if angle < w.bulletSize || angle > 360-w.bulletSize {
e.health -= w.damage
return
}
}
}
That looks pretty simple, right? All we're doing is looking through all enemies (although, once we've successfully shot an enemy we don't keep looking. We calculate the angle between the enemy and the player, and then if the angle is small enough then we hit the enemy.
Enemies Follow The Player
Now it was fun killing all the enemies, then grabbing the cosmium, but it really doesn't mean anything against static beings. So now we should make it so that the enemy tries to follow the player.
func (e *Enemy) follow_target() {
var dx, dy, dis, angle float64
dx = e.x - e.target.x
dy = e.y - e.target.y
dis = math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2))
angle = to_degrees(math.Acos(dx / dis))
if math.Asin(dy/dis) < 0 {
angle = -angle
}
// How much to the left or right of the player the enemy is
angle = bound_angle(angle)
e.x -= math.Cos(to_radians(angle)) * e.speed
e.y -= math.Sin(to_radians(angle)) * e.speed
e.angle = angle
}
More Ammo
We have an Item
struct
, so we're going to use that for ammo as well. We need a way to give the player ammo whenever we collide with an ammo item. Here's my solution.
type Action func(*Game)
func giveAmmo(g *Game) {
g.player.weapon.curMag += 10
if g.player.weapon.curMag > g.player.weapon.mag {
g.player.weapon.curMag = g.player.weapon.mag
}
}
We can create a type of function that takes a pointer to the Game
as input. In this way, we can more easily give functionality to Item
s, but quite literally giving them functions.
Enemies Fight Back
Now we need to get the enemy to damage the player when it's too close.
func (e *Enemy) attack_target() {
var dx, dy, dis float64
dx = e.x - e.target.x
dy = e.y - e.target.y
dis = math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2))
if dis < e.attackRange {
e.attackCooldown--
if e.attackCooldown == 0 {
e.target.health -= e.damage
e.attackCooldown = e.roa
}
}
}
Using attackCooldown
, the enemy has to wait to attack again. And playing with this feature, I do lose health, and eventually my HUD shows no hearts.
Dying
What's the point of losing health if you can't die? So no we need to do something once we die. For now, I think we'll exit the game.
// Attempt to kill
if e.target.health <= 0 {
os.Exit(0)
}
I know that's quite a basic way to die, but I don't really know what I want to do when the player dies yet, whether I want it to instantly restart level, go back to some sort of menu, or anything like that, so for now this works and at least we have the logic.
Next
This gameplay is going quite well, but I feel too powerful being able to walk through walls, so we will have to fix that soon, most likely in the next post.