entities, tiles and player walking, need y sorting
This commit is contained in:
parent
b656f16c84
commit
5b4dab6292
9 changed files with 432 additions and 0 deletions
10
Makefile
Normal file
10
Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
run:
|
||||
odin run src -out:game.exe -debug -use-separate-modules -sanitize:address
|
||||
|
||||
build:
|
||||
odin build src -out:game.exe -debug -use-separate-modules
|
||||
|
||||
clean:
|
||||
rm *.exe
|
||||
rm -rf *.dSYM/
|
||||
|
||||
BIN
assets/background.png
Normal file
BIN
assets/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/player.png
Normal file
BIN
assets/player.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 B |
BIN
assets/sell_button.png
Normal file
BIN
assets/sell_button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 279 B |
BIN
assets/tile_dirt_dry.png
Normal file
BIN
assets/tile_dirt_dry.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 185 B |
BIN
assets/tile_dirt_wet.png
Normal file
BIN
assets/tile_dirt_wet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 188 B |
BIN
assets/tomato.png
Normal file
BIN
assets/tomato.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 B |
12
ols.json
Normal file
12
ols.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
|
||||
"collections": [
|
||||
],
|
||||
"enable_references": true,
|
||||
"enable_inlay_hints": true,
|
||||
"enable_rename:": true,
|
||||
"enable_semantic_tokens": true,
|
||||
"enable_document_symbols": true,
|
||||
"enable_hover": true,
|
||||
"enable_snippets": true
|
||||
}
|
||||
410
src/main.odin
Normal file
410
src/main.odin
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
package game
|
||||
|
||||
import sa "core:container/small_array"
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:math/linalg"
|
||||
import "core:slice"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
GAME_SCREEN_WIDTH: f32 = 480
|
||||
GAME_SCREEN_HEIGHT: f32 = 270
|
||||
|
||||
SELL_BUTTON_POS :: rl.Vector2{432, 16}
|
||||
|
||||
TILE_SIZE :: 16
|
||||
game_render_buffer: rl.RenderTexture2D
|
||||
game_camera := rl.Camera2D {
|
||||
zoom = 1,
|
||||
}
|
||||
ui_camera := rl.Camera2D {
|
||||
zoom = 1,
|
||||
}
|
||||
|
||||
screen_scale: f32
|
||||
world_mouse: rl.Vector2
|
||||
screen_game_area: rl.Rectangle
|
||||
game_render_buffer_area := rl.Rectangle{0, 0, GAME_SCREEN_WIDTH, -GAME_SCREEN_HEIGHT}
|
||||
|
||||
player_sprite: []u8 : #load("../assets/player.png")
|
||||
tomato_sprite: []u8 : #load("../assets/tomato.png")
|
||||
sell_button_sprite: []u8 : #load("../assets/sell_button.png")
|
||||
background_sprite: []u8 : #load("../assets/background.png")
|
||||
tile_dirt_dry_sprite: []u8 : #load("../assets/tile_dirt_dry.png")
|
||||
tile_dirt_wet_sprite: []u8 : #load("../assets/tile_dirt_wet.png")
|
||||
|
||||
player_spritesheet_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(player_sprite),
|
||||
auto_cast len(player_sprite),
|
||||
)
|
||||
tomato_spritesheet_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(tomato_sprite),
|
||||
auto_cast len(tomato_sprite),
|
||||
)
|
||||
sell_button_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(sell_button_sprite),
|
||||
auto_cast len(sell_button_sprite),
|
||||
)
|
||||
background_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(background_sprite),
|
||||
auto_cast len(background_sprite),
|
||||
)
|
||||
sell_button_texture: rl.Texture2D
|
||||
background_texture: rl.Texture2D
|
||||
|
||||
tile_dirt_dry_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(tile_dirt_dry_sprite),
|
||||
auto_cast len(tile_dirt_dry_sprite),
|
||||
)
|
||||
tile_dirt_wet_image := rl.LoadImageFromMemory(
|
||||
".png",
|
||||
raw_data(tile_dirt_wet_sprite),
|
||||
auto_cast len(tile_dirt_wet_sprite),
|
||||
)
|
||||
tile_dirt_dry_texture: rl.Texture2D
|
||||
tile_dirt_wet_texture: rl.Texture2D
|
||||
|
||||
player_animation: Animation
|
||||
tomato_animation: Animation
|
||||
|
||||
player_handle: EntityHandle
|
||||
|
||||
tiles: [dynamic]Tile
|
||||
animations: [dynamic]^Animation
|
||||
|
||||
entities: [256]Entity
|
||||
|
||||
main :: proc() {
|
||||
rl.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
|
||||
rl.InitWindow(1280, 720, "Raylib Minimal")
|
||||
rl.SetTargetFPS(rl.GetMonitorRefreshRate(rl.GetCurrentMonitor()))
|
||||
|
||||
game_render_buffer = rl.LoadRenderTexture(
|
||||
auto_cast GAME_SCREEN_WIDTH,
|
||||
auto_cast GAME_SCREEN_HEIGHT,
|
||||
)
|
||||
rl.SetTextureFilter(game_render_buffer.texture, rl.TextureFilter.POINT) // Texture scale filter to use
|
||||
|
||||
player_animation = Animation {
|
||||
texture = rl.LoadTextureFromImage(player_spritesheet_image),
|
||||
num_frames = 2,
|
||||
frame_length = 0.5,
|
||||
loop = true,
|
||||
}
|
||||
tomato_animation = Animation {
|
||||
texture = rl.LoadTextureFromImage(tomato_spritesheet_image),
|
||||
num_frames = 4,
|
||||
frame_length = 2,
|
||||
loop = false,
|
||||
offset = {TILE_SIZE / 2, TILE_SIZE},
|
||||
}
|
||||
sell_button_texture = rl.LoadTextureFromImage(sell_button_image)
|
||||
background_texture = rl.LoadTextureFromImage(background_image)
|
||||
|
||||
tile_dirt_dry_texture = rl.LoadTextureFromImage(tile_dirt_dry_image)
|
||||
tile_dirt_wet_texture = rl.LoadTextureFromImage(tile_dirt_wet_image)
|
||||
|
||||
e := create_entity(
|
||||
Entity {
|
||||
pos = {GAME_SCREEN_WIDTH / 2, GAME_SCREEN_HEIGHT / 2},
|
||||
kind = .Player,
|
||||
data = PlayerData{animation = player_animation},
|
||||
},
|
||||
)
|
||||
player_handle = entity_to_handle(e^)
|
||||
|
||||
for !rl.WindowShouldClose() {
|
||||
free_all(context.temp_allocator)
|
||||
update()
|
||||
draw()
|
||||
}
|
||||
|
||||
rl.UnloadRenderTexture(game_render_buffer) // Unload render texture
|
||||
rl.CloseWindow()
|
||||
}
|
||||
|
||||
update :: proc() {
|
||||
// :scaling update
|
||||
screen_scale = min(
|
||||
f32(rl.GetScreenWidth()) / GAME_SCREEN_WIDTH,
|
||||
f32(rl.GetScreenHeight()) / GAME_SCREEN_HEIGHT,
|
||||
)
|
||||
mouse := rl.GetMousePosition()
|
||||
virtual_mouse := rl.Vector2 {
|
||||
(mouse.x - (f32(rl.GetScreenWidth()) - (GAME_SCREEN_WIDTH * screen_scale)) * 0.5) /
|
||||
screen_scale,
|
||||
(mouse.y - (f32(rl.GetScreenHeight()) - (GAME_SCREEN_HEIGHT * screen_scale)) * 0.5) /
|
||||
screen_scale,
|
||||
}
|
||||
virtual_mouse = rl.Vector2Clamp(
|
||||
virtual_mouse,
|
||||
{},
|
||||
rl.Vector2{GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT},
|
||||
)
|
||||
world_mouse = rl.GetScreenToWorld2D(virtual_mouse, game_camera)
|
||||
screen_game_area = rl.Rectangle {
|
||||
(f32(rl.GetScreenWidth()) - (GAME_SCREEN_WIDTH * screen_scale)) * 0.5,
|
||||
(f32(rl.GetScreenHeight()) - (GAME_SCREEN_HEIGHT * screen_scale)) * 0.5,
|
||||
GAME_SCREEN_WIDTH * screen_scale,
|
||||
GAME_SCREEN_HEIGHT * screen_scale,
|
||||
}
|
||||
|
||||
// :animation update
|
||||
for &e in entities {
|
||||
if .Allocated not_in e.flags {continue}
|
||||
|
||||
#partial switch &data in e.data {
|
||||
case TileData:
|
||||
update_animation(&data.animation)
|
||||
case PlayerData:
|
||||
update_animation(&data.animation)
|
||||
}
|
||||
}
|
||||
|
||||
// :sell
|
||||
hover_sell_button := rl.CheckCollisionPointRec(
|
||||
world_mouse,
|
||||
{SELL_BUTTON_POS.x, SELL_BUTTON_POS.y, TILE_SIZE * 2, TILE_SIZE * 2},
|
||||
)
|
||||
if rl.IsMouseButtonPressed(.LEFT) && hover_sell_button {
|
||||
// todo: sell
|
||||
fmt.println("Sell button pressed!")
|
||||
}
|
||||
|
||||
// :player update
|
||||
player := handle_to_entity(player_handle)
|
||||
if player != nil {
|
||||
pd, ok := player.data.(PlayerData);if ok {
|
||||
vel: rl.Vector2
|
||||
if rl.IsKeyDown(.D) do vel.x += 1
|
||||
if rl.IsKeyDown(.A) do vel.x -= 1
|
||||
if rl.IsKeyDown(.W) do vel.y -= 1
|
||||
if rl.IsKeyDown(.S) do vel.y += 1
|
||||
if vel.x == -1 do pd.animation.flip = true
|
||||
if vel.x == 1 do pd.animation.flip = false
|
||||
player.pos += vel * 100 * rl.GetFrameTime()
|
||||
}
|
||||
|
||||
}
|
||||
if rl.IsMouseButtonDown(.LEFT) {
|
||||
spawn_tile_under_mouse()
|
||||
}
|
||||
}
|
||||
|
||||
draw :: proc() {
|
||||
rl.BeginTextureMode(game_render_buffer)
|
||||
rl.BeginMode2D(game_camera)
|
||||
rl.BeginDrawing()
|
||||
|
||||
rl.DrawTextureV(background_texture, {}, rl.WHITE)
|
||||
// :animation draw
|
||||
//for t in tiles {
|
||||
// rl.DrawRectangle(auto_cast t.pos.x, auto_cast t.pos.y, TILE_SIZE, TILE_SIZE, rl.BLACK)
|
||||
// draw_animation(t.animation, t.pos)
|
||||
//}
|
||||
for e in entities {
|
||||
if .Allocated not_in e.flags {continue}
|
||||
|
||||
#partial switch &data in e.data {
|
||||
case TileData:
|
||||
draw_tile(e)
|
||||
case PlayerData:
|
||||
draw_player(e)
|
||||
}
|
||||
}
|
||||
|
||||
rl.DrawTextureV(sell_button_texture, SELL_BUTTON_POS, rl.WHITE)
|
||||
rl.EndMode2D()
|
||||
rl.EndTextureMode()
|
||||
|
||||
rl.ClearBackground(rl.BLACK)
|
||||
rl.DrawTexturePro(
|
||||
game_render_buffer.texture,
|
||||
game_render_buffer_area,
|
||||
screen_game_area,
|
||||
{},
|
||||
0,
|
||||
rl.WHITE,
|
||||
)
|
||||
rl.EndDrawing()
|
||||
}
|
||||
|
||||
Animation :: struct {
|
||||
texture: rl.Texture2D,
|
||||
num_frames: int,
|
||||
frame_timer: f32,
|
||||
current_frame: int,
|
||||
frame_length: f32,
|
||||
flip: bool,
|
||||
loop: bool,
|
||||
done: bool,
|
||||
offset: rl.Vector2,
|
||||
}
|
||||
|
||||
update_animation :: proc(a: ^Animation) {
|
||||
a.frame_timer += rl.GetFrameTime()
|
||||
|
||||
if a.frame_timer > a.frame_length && !a.done {
|
||||
a.current_frame += 1
|
||||
a.frame_timer = 0
|
||||
|
||||
if a.current_frame == a.num_frames {
|
||||
if a.loop {
|
||||
a.current_frame = 0
|
||||
} else {
|
||||
a.current_frame += -1
|
||||
a.done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_animation :: proc(a: Animation, pos: rl.Vector2) {
|
||||
width := f32(a.texture.width)
|
||||
height := f32(a.texture.height)
|
||||
|
||||
source := rl.Rectangle {
|
||||
x = f32(a.current_frame) * width / f32(a.num_frames),
|
||||
y = 0,
|
||||
width = width / f32(a.num_frames),
|
||||
height = height,
|
||||
}
|
||||
|
||||
if a.flip {
|
||||
source.width = -source.width
|
||||
}
|
||||
|
||||
dest := rl.Rectangle {
|
||||
x = pos.x + a.offset.x,
|
||||
y = pos.y + a.offset.y,
|
||||
width = width / f32(a.num_frames),
|
||||
height = height,
|
||||
}
|
||||
|
||||
rl.DrawTexturePro(a.texture, source, dest, {dest.width / 2, dest.height}, 0, rl.WHITE)
|
||||
}
|
||||
|
||||
Tile :: struct {
|
||||
id: int,
|
||||
pos: rl.Vector2,
|
||||
animation: Animation,
|
||||
}
|
||||
|
||||
spawn_tile_under_mouse :: proc() {
|
||||
tile_position := get_tile_position(world_mouse)
|
||||
old_tile: bool
|
||||
for e in entities {
|
||||
if .Allocated not_in e.flags {continue}
|
||||
if e.kind == .Tile && e.pos == tile_position {
|
||||
old_tile = true
|
||||
}
|
||||
}
|
||||
if !old_tile {
|
||||
create_entity(
|
||||
Entity {
|
||||
pos = tile_position,
|
||||
kind = .Tile,
|
||||
data = TileData{has_plant = true, plant_id = 0, animation = tomato_animation},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get_tile_position :: proc(wpos: rl.Vector2) -> rl.Vector2 {
|
||||
return {math.floor(wpos.x / TILE_SIZE) * TILE_SIZE, math.floor(wpos.y / TILE_SIZE) * TILE_SIZE}
|
||||
}
|
||||
|
||||
EntityKind :: enum {
|
||||
Player,
|
||||
Tile,
|
||||
Item,
|
||||
}
|
||||
|
||||
PlayerData :: struct {
|
||||
animation: Animation,
|
||||
}
|
||||
|
||||
ItemData :: struct {
|
||||
id: int,
|
||||
count: int,
|
||||
animation: Animation,
|
||||
}
|
||||
|
||||
TileData :: struct {
|
||||
has_plant: bool,
|
||||
is_watered: bool,
|
||||
plant_id: int,
|
||||
animation: Animation,
|
||||
}
|
||||
|
||||
EntityData :: union {
|
||||
PlayerData,
|
||||
TileData,
|
||||
ItemData,
|
||||
}
|
||||
|
||||
Entity :: struct {
|
||||
pos: rl.Vector2,
|
||||
kind: EntityKind,
|
||||
handle: EntityHandle,
|
||||
flags: bit_set[EntityFlags],
|
||||
data: EntityData,
|
||||
}
|
||||
latest_entity_handle: int
|
||||
|
||||
EntityHandle :: int
|
||||
EntityFlags :: enum {
|
||||
nil,
|
||||
Allocated,
|
||||
}
|
||||
|
||||
create_entity :: proc(entity: Entity) -> ^Entity {
|
||||
for &e in entities {
|
||||
if .Allocated not_in e.flags {
|
||||
e = entity
|
||||
e.flags = {.Allocated}
|
||||
latest_entity_handle += 1
|
||||
return &e
|
||||
}
|
||||
}
|
||||
assert(false, "Failed to allocate entity, not enough space!!!!")
|
||||
return nil
|
||||
}
|
||||
|
||||
destroy_entity :: proc(e: ^Entity) {
|
||||
e^ = Entity{}
|
||||
}
|
||||
|
||||
handle_to_entity :: proc(handle: EntityHandle) -> ^Entity {
|
||||
for &e in entities {
|
||||
if handle == e.handle && .Allocated in e.flags {
|
||||
return &e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
entity_to_handle :: proc(e: Entity) -> EntityHandle {
|
||||
return e.handle
|
||||
}
|
||||
|
||||
|
||||
draw_player :: proc(e: Entity) {
|
||||
data, ok := e.data.(PlayerData)
|
||||
draw_animation(data.animation, e.pos)
|
||||
}
|
||||
draw_tile :: proc(e: Entity) {
|
||||
data, ok := e.data.(TileData)
|
||||
//rl.DrawRectangle(auto_cast e.pos.x, auto_cast e.pos.y, TILE_SIZE, TILE_SIZE, rl.BLACK)
|
||||
rl.DrawTextureV(tile_dirt_dry_texture, e.pos, rl.WHITE)
|
||||
draw_animation(data.animation, e.pos)
|
||||
}
|
||||
draw_item :: proc(e: Entity) {
|
||||
data, ok := e.data.(ItemData)
|
||||
draw_animation(data.animation, e.pos)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue