Inventory System for Level Actors
* Added an inventory system for actors as a replacement to the arbitrary key/value data store. Colored keys now add themselves to the player's inventory, and colored doors check the inventory. * Inventory is a map[string]int between doodad filenames (red-key.doodad) and quantity (0 for key items/unlimited qty). * API methods to add and remove inventory. * Items HUD appears in Play Mode in lower-left corner showing doodad sprites of all the items in the Player's inventory.
This commit is contained in:
parent
3cb99ad5f8
commit
c3d7348843
|
@ -1,6 +1,7 @@
|
|||
|
||||
function main() {
|
||||
var color = Self.Doodad.Tag("color");
|
||||
var keyname = "key-" + color + ".doodad";
|
||||
|
||||
// Layers in the doodad image.
|
||||
var layer = {
|
||||
|
@ -34,8 +35,9 @@ function main() {
|
|||
return;
|
||||
}
|
||||
|
||||
var data = e.Actor.GetData("key:" + color);
|
||||
if (data === "") {
|
||||
// Do they have our key?
|
||||
var hasKey = e.Actor.HasItem(keyname) >= 0;
|
||||
if (!hasKey) {
|
||||
// Door is locked.
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ function main() {
|
|||
var color = Self.Doodad.Tag("color");
|
||||
|
||||
Events.OnCollide(function(e) {
|
||||
e.Actor.SetData("key:" + color, "true");
|
||||
Self.Destroy();
|
||||
if (e.Settled) {
|
||||
e.Actor.AddItem(Self.Doodad.Filename, 0);
|
||||
Self.Destroy();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
121
pkg/play_inventory.go
Normal file
121
pkg/play_inventory.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/uix"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// setupInventoryHud configures the Inventory HUD.
|
||||
func (s *PlayScene) setupInventoryHud() {
|
||||
s.invenFrame = ui.NewFrame("Inventory")
|
||||
s.invenDoodads = map[string]*uix.Canvas{}
|
||||
s.invenFrame.Configure(ui.Config{
|
||||
BorderStyle: ui.BorderRaised,
|
||||
BorderSize: 2,
|
||||
Background: render.RGBA(128, 128, 128, 60),
|
||||
})
|
||||
|
||||
// Items label.
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "Items:",
|
||||
Font: balance.LabelFont,
|
||||
})
|
||||
label.Compute(s.d.Engine)
|
||||
|
||||
// Configure the label tall enough to cover typical 32x32 item doodads.
|
||||
// This pushes the Frame height tall enough.
|
||||
label.Configure(ui.Config{
|
||||
Height: 36,
|
||||
Width: label.Size().W + 2,
|
||||
})
|
||||
s.invenFrame.Pack(label, ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 2,
|
||||
PadY: 4,
|
||||
})
|
||||
|
||||
// Add the inventory frame to the screen frame.
|
||||
s.screen.Place(s.invenFrame, ui.Place{
|
||||
Left: 40,
|
||||
Bottom: 40,
|
||||
})
|
||||
|
||||
// Hide inventory if empty.
|
||||
if len(s.invenItems) == 0 {
|
||||
s.invenFrame.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
// computeInventory adjusts the inventory HUD when the player's inventory changes.
|
||||
func (s *PlayScene) computeInventory() {
|
||||
items := s.Player.ListItems()
|
||||
if len(items) != len(s.invenItems) {
|
||||
// Inventory has changed! See which doodads we have
|
||||
// and which we need to load.
|
||||
var seen = map[string]interface{}{}
|
||||
for _, filename := range items {
|
||||
seen[filename] = nil
|
||||
|
||||
if _, ok := s.invenDoodads[filename]; !ok {
|
||||
// Cache miss. Load the doodad here.
|
||||
doodad, err := doodads.LoadFile(filename)
|
||||
if err != nil {
|
||||
s.d.Flash("Inventory item '%s' error: %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
canvas := uix.NewCanvas(doodad.ChunkSize(), false)
|
||||
canvas.LoadDoodad(doodad)
|
||||
canvas.Resize(render.NewRect(
|
||||
doodad.ChunkSize(), doodad.ChunkSize(),
|
||||
))
|
||||
s.invenFrame.Pack(canvas, ui.Pack{
|
||||
Side: ui.W,
|
||||
|
||||
// TODO: work around a weird padding bug. item had too
|
||||
// tall a top margin when added to the inventory frame!
|
||||
PadX: 8,
|
||||
})
|
||||
s.invenDoodads[filename] = canvas
|
||||
}
|
||||
|
||||
s.invenDoodads[filename].Show()
|
||||
}
|
||||
|
||||
// Hide any doodad that used to be in the inventory but now is not.
|
||||
for filename, canvas := range s.invenDoodads {
|
||||
if _, ok := seen[filename]; !ok {
|
||||
canvas.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Recompute the size of the inventory frame.
|
||||
// TODO: this works around a bug in ui.Frame, at the bottom of
|
||||
// compute_packed, a frame Resize's itself to fit the children but this
|
||||
// trips the "manually set size" boolean... packing more items after a
|
||||
// computer doesn't resize the frame. So here, we resize-auto it to
|
||||
// reset that boolean so the next compute, picks the right size.
|
||||
s.invenFrame.Configure(ui.Config{
|
||||
AutoResize: true,
|
||||
Width: 1,
|
||||
Height: 1,
|
||||
})
|
||||
s.invenFrame.Compute(s.d.Engine)
|
||||
|
||||
// If we removed all items, hide the frame.
|
||||
if len(items) == 0 {
|
||||
s.invenFrame.Hide()
|
||||
} else {
|
||||
s.invenFrame.Show()
|
||||
}
|
||||
|
||||
// Cache the item list so we don't run the above logic every single tick.
|
||||
s.invenItems = items
|
||||
}
|
||||
|
||||
// Compute the inventory frame so it positions and wraps the items.
|
||||
s.screen.Compute(s.d.Engine)
|
||||
}
|
|
@ -31,6 +31,7 @@ type PlayScene struct {
|
|||
|
||||
// UI widgets.
|
||||
supervisor *ui.Supervisor
|
||||
screen *ui.Frame // A window sized invisible frame to position UI elements.
|
||||
editButton *ui.Button
|
||||
|
||||
// The alert box shows up when the level goal is reached and includes
|
||||
|
@ -53,6 +54,11 @@ type PlayScene struct {
|
|||
antigravity bool // Cheat: disable player gravity
|
||||
noclip bool // Cheat: disable player clipping
|
||||
playerJumpCounter int // limit jump length
|
||||
|
||||
// Inventory HUD. Impl. in play_inventory.go
|
||||
invenFrame *ui.Frame
|
||||
invenItems []string // item list
|
||||
invenDoodads map[string]*uix.Canvas
|
||||
}
|
||||
|
||||
// Name of the scene.
|
||||
|
@ -66,6 +72,10 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
|||
s.scripting = scripting.NewSupervisor()
|
||||
s.supervisor = ui.NewSupervisor()
|
||||
|
||||
// Create an invisible 'screen' frame for UI elements to use for positioning.
|
||||
s.screen = ui.NewFrame("Screen")
|
||||
s.screen.Resize(render.NewRect(d.width, d.height))
|
||||
|
||||
// Level Exit handler.
|
||||
s.SetupAlertbox()
|
||||
s.scripting.OnLevelExit(func() {
|
||||
|
@ -112,6 +122,9 @@ func (s *PlayScene) Setup(d *Doodle) error {
|
|||
})
|
||||
s.supervisor.Add(s.editButton)
|
||||
|
||||
// Set up the inventory HUD.
|
||||
s.setupInventoryHud()
|
||||
|
||||
// Initialize the drawing canvas.
|
||||
s.drawing = uix.NewCanvas(balance.ChunkSize, false)
|
||||
s.drawing.Name = "play-canvas"
|
||||
|
@ -382,6 +395,9 @@ func (s *PlayScene) Loop(d *Doodle, ev *event.State) error {
|
|||
if err := s.drawing.Loop(ev); err != nil {
|
||||
log.Error("Drawing loop error: %s", err.Error())
|
||||
}
|
||||
|
||||
// Update the inventory HUD.
|
||||
s.computeInventory()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -398,6 +414,10 @@ func (s *PlayScene) Draw(d *Doodle) error {
|
|||
// Draw out bounding boxes.
|
||||
d.DrawCollisionBox(s.Player)
|
||||
|
||||
// Draw the UI screen and any widgets that attached to it.
|
||||
s.screen.Compute(d.Engine)
|
||||
s.screen.Present(d.Engine, render.Origin)
|
||||
|
||||
// Draw the Edit button.
|
||||
var (
|
||||
canSize = s.drawing.Size()
|
||||
|
|
|
@ -3,6 +3,8 @@ package uix
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
|
@ -33,12 +35,17 @@ type Actor struct {
|
|||
isMobile bool // Mobile character, such as the player or an enemy
|
||||
noclip bool // Disable collision detection
|
||||
hitbox render.Rect
|
||||
data map[string]string
|
||||
inventory map[string]int // item inventory. doodad name -> quantity, 0 for key item.
|
||||
data map[string]string // arbitrary key/value store. DEPRECATED ??
|
||||
|
||||
// Animation variables.
|
||||
animations map[string]*Animation
|
||||
activeAnimation *Animation
|
||||
animationCallback otto.Value
|
||||
|
||||
// Mutex.
|
||||
muInventory sync.RWMutex
|
||||
muData sync.RWMutex
|
||||
}
|
||||
|
||||
// NewActor sets up a uix.Actor.
|
||||
|
@ -64,6 +71,7 @@ func NewActor(id string, levelActor *level.Actor, doodad *doodads.Doodad) *Actor
|
|||
Actor: levelActor,
|
||||
Canvas: can,
|
||||
animations: map[string]*Animation{},
|
||||
inventory: map[string]int{},
|
||||
}
|
||||
|
||||
// Give the Canvas a pointer to its (parent) Actor so it can draw its debug
|
||||
|
@ -96,6 +104,87 @@ func (a *Actor) SetNoclip(v bool) {
|
|||
a.noclip = v
|
||||
}
|
||||
|
||||
// AddItem adds an item doodad to the actor's inventory.
|
||||
// Item name is usually the doodad filename.
|
||||
func (a *Actor) AddItem(itemName string, quantity int) {
|
||||
a.muInventory.Lock()
|
||||
a.inventory[itemName] = quantity
|
||||
a.muInventory.Unlock()
|
||||
}
|
||||
|
||||
// RemoveItem removes a quantity of an item from the actor's inventory.
|
||||
//
|
||||
// Provide a quantity of 0 to remove the item completely.
|
||||
// Otherwise provides a number greater than zero and you will subtract this
|
||||
// quantity from the item. If the item then is at <= zero, it is removed from
|
||||
// inventory.
|
||||
func (a *Actor) RemoveItem(itemName string, quantity int) bool {
|
||||
a.muInventory.RLock()
|
||||
defer a.muInventory.RUnlock()
|
||||
|
||||
if _, ok := a.inventory[itemName]; ok {
|
||||
// If quantity is zero, remove the item entirely.
|
||||
if quantity <= 0 {
|
||||
delete(a.inventory, itemName)
|
||||
} else {
|
||||
// Subtract the quantity from inventory. If we have run down to
|
||||
// zero left, remove the item entirely.
|
||||
a.inventory[itemName] -= quantity
|
||||
if a.inventory[itemName] <= 0 {
|
||||
delete(a.inventory, itemName)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasItem checks the actor's inventory for the item and returns the quantity.
|
||||
//
|
||||
// A return value of -1 means the item was not found.
|
||||
// The value 0 indicates a key item (one with no quantity).
|
||||
// Values >= 1 would be consumable items.
|
||||
func (a *Actor) HasItem(itemName string) int {
|
||||
a.muInventory.RLock()
|
||||
defer a.muInventory.RUnlock()
|
||||
|
||||
if quantity, ok := a.inventory[itemName]; ok {
|
||||
return quantity
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ListItems returns a sorted list of the items in the actor's inventory.
|
||||
func (a *Actor) ListItems() []string {
|
||||
a.muInventory.RLock()
|
||||
defer a.muInventory.RUnlock()
|
||||
|
||||
var (
|
||||
result = make([]string, len(a.inventory))
|
||||
i = 0
|
||||
)
|
||||
for k := range a.inventory {
|
||||
result[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// Inventory returns a copy of the actor's inventory struct.
|
||||
func (a *Actor) Inventory() map[string]int {
|
||||
a.muInventory.RLock()
|
||||
defer a.muInventory.RUnlock()
|
||||
|
||||
var result = map[string]int{}
|
||||
for k, v := range a.inventory {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetBoundingRect gets the bounding box of the actor's doodad.
|
||||
func (a *Actor) GetBoundingRect() render.Rect {
|
||||
return doodads.GetBoundingRect(a)
|
||||
|
@ -121,7 +210,10 @@ func (a *Actor) SetData(key, value string) {
|
|||
if a.data == nil {
|
||||
a.data = map[string]string{}
|
||||
}
|
||||
|
||||
a.muData.Lock()
|
||||
a.data[key] = value
|
||||
a.muData.Unlock()
|
||||
}
|
||||
|
||||
// GetData gets an arbitrary field from the actor's K/V storage.
|
||||
|
@ -132,7 +224,10 @@ func (a *Actor) GetData(key string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
a.muData.RLock()
|
||||
v, _ := a.data[key]
|
||||
a.muData.RUnlock()
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user