DEV Community

Chig Beef
Chig Beef

Posted on

Ankaran, Level 1 (Cosplore3D Pt:7)

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. In the last post we put images on the walls and gave the graphics quite an upgrade. Today, we are going to create the first actual level, to as far of an extent as we can.

Level As Text

Now, I won't show you the actual text document I made to store the level, and I haven't made a visual level editor yet, so I will just describe it. All the walls are an orange rock-lick material. The ground is orange, and the sky is blue, denoting an atmosphere. We start in a small room, with the cosplorer behind us. Down a corridor there is a blue blob enemy. As we defeat the enemy and travel down the bent corridor we find a large room, with multiple blob. Lastly there is a room twice the size that holds the boss (not implemented yet). Now, according to lore, there isn't a boss, so this creature is only going to be slightly stronger. This is a start.

Orange world with a few blue blobs

Mouse Movement

Now, I like looking left and right with my keyboard, but it would be a lot better to do that with the mouse. All we need to this is use the mouse's current position, and its previous position. Now, we just need to add the difference in the x coordinate between the 2 positions, and multiply by the Player's haste. Now, most of this is simple, except if my mouse goes off screen I don't move anymore, so we need to bind it to the window. Turns out this is our solution.

ebiten.SetCursorMode(ebiten.CursorModeCaptured)
Enter fullscreen mode Exit fullscreen mode

Turns out reading documentation is all you need to fix programming issues. Although, I have no idea why my mouse position ended up over a trillion and it was a consistent issue, but I'm not going to worry about that because the game is working. Also, I added the ability to use "a" and "d" to move left and right.

Sound

Now, whenever I try to make some nice, atmospheric music, it ends up sounding goofy, and this happened again. If you want to listen to what I created the file is on the GitHub. But now we have to implement it. Now, there is an example on house to use sound with ebiten, but it's very bloated because it's trying to showcase a lot of different features, which will make it hard to work with. Now, since there was a lot of random code everywhere and I hated it I won't go into detail with it. The audio player for ebiten doesn't work with .wav, but works with .ogg, so just keep that in mind. If I'm honest, the API for utilizing audio could always be improved, and I'm pretty sure there's a comment on one of the example's code that says something similar, but currently the API feels really bare.

Items

A part of the lore that we can't miss is the presence of cosmium, which is an item that the player must retrieve to power the ship. for this we need to make a new struct for items.

type Item struct {
    x      float64
    y      float64
    images []*ebiten.Image
}
Enter fullscreen mode Exit fullscreen mode

This is similar to the Enemy struct, which uses a slice of images to work with looking at the enemy from different angles (which isn't implemented yet). Now we need to place the item in the map.

Cosmium in the world

Now, I know what you're thinking, "that shading looks amazing, wasn't the square root too slow to calculate that value for all the pixels?" Now, if you thought that, you were right, but I found a fix. Here is the code for the fast inverse square root.

func fastInvSqrt(x float32) float32 {
    i := *(*int32)(unsafe.Pointer(&x))
    i = 0x5f3759df - i>>1
    y := *(*float32)(unsafe.Pointer(&i))
    //return y * (1.5 - 0.5*x*y*y)
    return y
}
Enter fullscreen mode Exit fullscreen mode

If you've ever seen the fast inverse square root code then this should look familiar, and you may remember that there are two iterations of y * (1.5 - 0.5*x*y*y). One of the optimizations that made that code was reducing it to one iteration. For my code, I figured, "what if I get rid of both iterations." Turns out, if you do, my code runs at a good speed, and everything looks fine. Now, I know this is probably the wrong thing I should be improving performance on, and the issue is likely somewhere else, but it sped up my code significantly, so I'm not complaining.

Picking Up Items

Now, when we collide with an item, we need to make it disappear.

func (i *Item) check_collide(p *Player) bool {
    dx := i.x - p.x
    dy := i.y - p.y
    dis := math.Sqrt(math.Pow(dx, 2) + math.Pow(dy, 2))

    return dis < float64(tileSize)/2
}
Enter fullscreen mode Exit fullscreen mode

That seems simple enough.

func (p Player) check_collide(lLevel) {
    aliveItems := []int{}
    for i := 0; i < len(l.items); i++ {
        if l.items[i].check_collide(p) {

        } else {
            aliveItems = append(aliveItems, i)
        }
    }
    newItems := make([]Item, len(aliveItems))

    for i := 0; i < len(aliveItems); i++ {
        newItems[i] = l.items[aliveItems[i]]
    }

    l.items = newItems
}
Enter fullscreen mode Exit fullscreen mode

Now we can use that function we made before to loop over every item and only keep the ones that haven't been picked up, beautiful.

Next

I'm tired of seeing a gun on screen and not being able to shoot. There have been multiple times where I've tried to click and kill the enemies and then I realized, I can't. We will be implementing that in the next post. We could also start implementing the enemy's ability to follow the player, and damage the player.

Top comments (0)