Player character walks around a navmesh on touch.
parent
621d92a554
commit
04a3a748ce
|
@ -0,0 +1 @@
|
|||
/build
|
17
Makefile
17
Makefile
|
@ -2,7 +2,9 @@ PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
|||
|
||||
OBJS = pinetab2_framework.o game.o
|
||||
SPRITES = $(patsubst $(PROJECT_ROOT)sprites/%.xcf,%,$(wildcard $(PROJECT_ROOT)sprites/*.xcf))
|
||||
NAVMESHES = $(patsubst $(PROJECT_ROOT)navmesh/%.tmx,%,$(wildcard $(PROJECT_ROOT)navmesh/*.tmx))
|
||||
|
||||
BUILD_MODE := debug
|
||||
ifeq ($(BUILD_MODE),debug)
|
||||
CFLAGS += -g
|
||||
else ifeq ($(BUILD_MODE),run)
|
||||
|
@ -16,12 +18,13 @@ else
|
|||
$(error Build mode $(BUILD_MODE) not supported by this Makefile)
|
||||
endif
|
||||
|
||||
CXX := aarch64-linux-gnu-$(CXX)
|
||||
CC := aarch64-linux-gnu-$(CC)
|
||||
CXX := aarch64-linux-gnu-g++
|
||||
CC := aarch64-linux-gnu-gcc
|
||||
OBJCOPY := aarch64-linux-gnu-objcopy -O elf64-littleaarch64
|
||||
CFLAGS += -I/usr/include
|
||||
|
||||
OBJS += $(patsubst %,sprite_%.o,$(SPRITES))
|
||||
OBJS += $(patsubst %,navmesh_%.o,$(NAVMESHES))
|
||||
|
||||
all: pinetab2_framework $(patsubst %,%.png,$(SPRITES))
|
||||
|
||||
|
@ -35,12 +38,15 @@ pinetab2_framework: $(OBJS)
|
|||
%.o: $(PROJECT_ROOT)%.c
|
||||
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
|
||||
|
||||
%.o: %.c
|
||||
$(CXX) -c $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -iquote "$(PROJECT_ROOT)"
|
||||
|
||||
clean:
|
||||
rm -fr pinetab2_framework $(OBJS) $(EXTRA_CLEAN)
|
||||
|
||||
sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
|
||||
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
|
||||
(gimp-image-scale image 1280 800) ;\
|
||||
;(gimp-image-scale image 1280 800) ;\
|
||||
(gimp-image-rotate image ROTATE-90) ;\
|
||||
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
|
||||
(plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer 0 0 0 1 0 1 0 1 0 0) ; swap red and blue channels \
|
||||
|
@ -51,9 +57,12 @@ sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
|
|||
rm $(patsubst %.o,%.raw,$@)
|
||||
@# symbols are _binary_sprite_%_raw_start / _end / _size
|
||||
|
||||
navmesh_%.c: $(PROJECT_ROOT)navmesh/%.tmx $(PROJECT_ROOT)compile_navmesh.py
|
||||
python3 "$(PROJECT_ROOT)compile_navmesh.py" "$<" "$@" "$(basename $@)"
|
||||
|
||||
%.png: $(PROJECT_ROOT)sprites/%.xcf
|
||||
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
|
||||
(gimp-image-scale image 1280 800) ;\
|
||||
;(gimp-image-scale image 1280 800) ;\
|
||||
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
|
||||
(file-png-save 1 image layer "$@" "$@" 0 9 0 0 0 0 0) ;\
|
||||
) ;\
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/python3
|
||||
import lxml.etree, math, sys
|
||||
|
||||
_, inpath, outpath, basename = sys.argv
|
||||
|
||||
tree = lxml.etree.parse(inpath)
|
||||
|
||||
tris = []
|
||||
edge2tri = {}
|
||||
|
||||
for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"):
|
||||
basex = int(polygon.getparent().attrib["x"])
|
||||
basey = int(polygon.getparent().attrib["y"])
|
||||
points=[]
|
||||
for point in polygon.attrib["points"].split(" "):
|
||||
x,y=point.split(",")
|
||||
x,y=int(x)+basex,int(y)+basey
|
||||
points.append((x,y))
|
||||
#print(points)
|
||||
|
||||
ax,ay=points[0]
|
||||
bx,by=points[1]
|
||||
cx,cy=points[2]
|
||||
abx,aby=bx-ax,by-ay
|
||||
acx,acy=cx-ax,cy-ay
|
||||
cross = abx*acy-acx*aby
|
||||
# positive for clockwise winding order. Example clockwise winding order: (0,0), (1,0), (0,1)
|
||||
if cross < 0:
|
||||
points = points[0],points[2],points[1]
|
||||
bx,by,cx,cy=cx,cy,bx,by
|
||||
cross = -cross
|
||||
|
||||
assert len(points)==3, "triangles only"
|
||||
tri_id = len(tris)
|
||||
tris.append(points)
|
||||
|
||||
for edge in [(points[0],points[1]), (points[1],points[2]), (points[2],points[0])]:
|
||||
edge_id = tuple(sorted(edge))
|
||||
if edge_id not in edge2tri: edge2tri[edge_id] = []
|
||||
edge2tri[edge_id].append(tri_id)
|
||||
assert len(edge2tri[edge_id]) <= 2, "more than 2 triangles share edge "+str(edge_id)
|
||||
|
||||
pathfind_edges = [[] for _ in range(len(tris))]
|
||||
for edgetris in edge2tri.values():
|
||||
assert len(edgetris) in (1,2)
|
||||
if len(edgetris) == 1: continue
|
||||
t1,t2 = edgetris
|
||||
pathfind_edges[t1].append(t2)
|
||||
pathfind_edges[t2].append(t1)
|
||||
|
||||
pathfind_matrix = [[255 for _1 in range(len(tris))] for _2 in range(len(tris))]
|
||||
# pathfind_matrix[source][dest] is how to get to dest from source
|
||||
|
||||
for dest in range(len(tris)):
|
||||
# Find paths to tri by breadth-first search. We don't take the size of the triangle into account yet.
|
||||
openset = [dest]
|
||||
while len(openset)>0:
|
||||
cur, openset = openset[0], openset[1:]
|
||||
for adjacent in pathfind_edges[cur]:
|
||||
if adjacent != dest and pathfind_matrix[adjacent][dest] == 255:
|
||||
pathfind_matrix[adjacent][dest] = cur
|
||||
openset.append(adjacent)
|
||||
|
||||
|
||||
|
||||
out = open(outpath,"w")
|
||||
|
||||
out.write("#include \"compiled_structures.h\"\n")
|
||||
out.write(f"static const struct navmesh_tri {basename}_triangles[{len(tris)}] = {{\n");
|
||||
for tri_id,points in enumerate(tris):
|
||||
out.write("\t{\n\t\t{\n");
|
||||
for a,b,c in [(points[0],points[1],points[2]), (points[1],points[2],points[0]), (points[2],points[0],points[1])]:
|
||||
(ax,ay),(bx,by),(cx,cy) = a,b,c
|
||||
# edge a-b and c is the other point
|
||||
# Line equation Px+Qy+R=0 from https://math.stackexchange.com/questions/422602/convert-two-points-to-line-eq-ax-by-c-0
|
||||
P,Q,R=ay-by,bx-ax,ax*by-bx*ay
|
||||
# Line equation >0 for points inside the triangle.
|
||||
# Normalize so that P^2+Q^2=1 so that the equation result tells us the distance. This can be used to find the closest triangle to a point.
|
||||
norm=1/math.sqrt(P*P+Q*Q)
|
||||
P *= norm
|
||||
Q *= norm
|
||||
R *= norm
|
||||
|
||||
edge_id = tuple(sorted((a,b)))
|
||||
tris_on_edge = edge2tri[edge_id]
|
||||
assert len(tris_on_edge) in (1,2)
|
||||
assert tri_id in tris_on_edge
|
||||
if len(tris_on_edge) == 2:
|
||||
other_tri_id = [x for x in tris_on_edge if x!=tri_id][0]
|
||||
else:
|
||||
other_tri_id = -1
|
||||
|
||||
centx, centy = (ax+bx)/2, (ay+by)/2
|
||||
|
||||
out.write(f"\t\t\t{{{P},{Q},{R},{other_tri_id},{{{int(centx)},{int(centy)}}}}},\n")
|
||||
|
||||
|
||||
|
||||
out.write("\t\t},\n")
|
||||
out.write("\t\t{" + ",".join(f"{{{x},{y}}}" for x,y in points) + "},\n")
|
||||
out.write("\t},\n");
|
||||
out.write("};\n")
|
||||
out.write(f"static const unsigned char {basename}_pathfind[{len(tris)*len(tris)}] = {{\n")
|
||||
for dest in range(len(tris)):
|
||||
out.write("\t")
|
||||
for source in range(len(tris)):
|
||||
out.write(str(pathfind_matrix[source][dest])+",")
|
||||
out.write("\n")
|
||||
out.write("};\n")
|
||||
out.write(f"extern const struct navmesh {basename} = {{{len(tris)}, {basename}_triangles, {basename}_pathfind}};\n")
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef COMPILED_STRUCTURES_H_
|
||||
#define COMPILED_STRUCTURES_H_
|
||||
|
||||
|
||||
struct navmesh_point {
|
||||
int x, y;
|
||||
};
|
||||
struct navmesh_tri_edge {
|
||||
float a,b,c; // line equation a*x + b*y + c > 0 if point is in triangle, and value gives distance from line.
|
||||
int other_tri; // -1 if this edge is a border of navmesh, else triangle it connects to
|
||||
struct navmesh_point center; // intermediate point for use in routing. could be improved later.
|
||||
};
|
||||
struct navmesh_tri {
|
||||
struct navmesh_tri_edge edges[3];
|
||||
struct navmesh_point points[3];
|
||||
// edges[0]: points[0]-points[1]
|
||||
// edges[1]: points[1]-points[2]
|
||||
// edges[2]: points[2]-points[0]
|
||||
};
|
||||
struct navmesh {
|
||||
int num_tris;
|
||||
const struct navmesh_tri *tris;
|
||||
const unsigned char *pathfindgrid; // [dest*num_tris + source] gives index of next tri. Placeholder value is used when dest==source.
|
||||
};
|
||||
|
||||
|
||||
#endif /* COMPILED_STRUCTURES_H_ */
|
38
engine.h
38
engine.h
|
@ -1,4 +1,4 @@
|
|||
#define MAX_GAME_OBJECTS 300
|
||||
#define MAX_GAME_OBJECTS 100
|
||||
extern struct object {
|
||||
int id; // 0 if slot unused
|
||||
int x;
|
||||
|
@ -8,15 +8,51 @@ extern struct object {
|
|||
const char *pixels;
|
||||
} objects[MAX_GAME_OBJECTS];
|
||||
|
||||
#define MAX_GAME_SCRIPTS 100
|
||||
typedef void (*script_wake_fn)(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4);
|
||||
#define SCRIPT_HEADER \
|
||||
int id /* 0 if slot unused */; \
|
||||
int wakeupMode; \
|
||||
script_wake_fn wakeupFn; \
|
||||
int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */
|
||||
extern struct script {
|
||||
SCRIPT_HEADER
|
||||
|
||||
int vars[100];
|
||||
|
||||
} scripts[MAX_GAME_SCRIPTS];
|
||||
|
||||
struct script_player_walk {
|
||||
SCRIPT_HEADER
|
||||
int targetX;
|
||||
int targetY;
|
||||
int targetNavmeshTri;
|
||||
int currentNavmeshTri;
|
||||
};
|
||||
|
||||
#define SCRIPT_WAKEUP_VIDEO_FRAME 1
|
||||
#define SCRIPT_WAKEUP_OTHER_SCRIPT 2
|
||||
#define SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED 3 // Delivered to script that registers for SCRIPT_WAKEUP_OTHER_SCRIPT if that script is interrupted instead of completing.
|
||||
|
||||
|
||||
// engine
|
||||
void transition_scene(int scene); // Immediately stops all event processing for current scene, which is unloaded before this function returns
|
||||
void scene_add_clickrect(int id, int x, int y, int width, int height);
|
||||
void scene_add_object(int id, int x, int y, int width, int height, const char *pixels);
|
||||
void scene_set_navmesh(struct navmesh *navmesh);
|
||||
struct object *find_object_by_id(int id);
|
||||
struct script *scene_add_script(int id, bool interrupt_existing);
|
||||
struct script *scene_get_script(int id);
|
||||
bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4); // deliver to scripts with given wakeupMode and wakeupArg1. Returns true if scene changed.
|
||||
|
||||
// game-specific constants
|
||||
#define SCENE_LOBBY 1
|
||||
#define SCENE_MANAGERS_OFFICE 2
|
||||
|
||||
// The player ID and walk script are globally relevant
|
||||
#define OBJID_PLAYER 4
|
||||
#define OBJID_PLAYER_WALK_SCRIPT 5
|
||||
|
||||
// game.cpp
|
||||
void scene_setup(int scene);
|
||||
void onclick(int curscene, int objid);
|
||||
|
|
10
game.cpp
10
game.cpp
|
@ -6,19 +6,28 @@
|
|||
extern const char _binary_sprite_lobby_raw_start[];
|
||||
extern const char _binary_sprite_managers_office_raw_start[];
|
||||
|
||||
extern const char _binary_sprite_stickman_raw_start[];
|
||||
|
||||
extern struct navmesh navmesh_lobby;
|
||||
|
||||
#define OBJID_BACKGROUND 1
|
||||
#define OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY 2
|
||||
#define OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE 3
|
||||
// #define OBJID_PLAYER 4
|
||||
// #define SCRIPTID_PLAYER_WALK 5
|
||||
|
||||
void scene_setup(int scene) {
|
||||
switch(scene) {
|
||||
case SCENE_LOBBY:
|
||||
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start);
|
||||
scene_add_object(OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY, 273, 313, 76, 128, nullptr);
|
||||
scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
|
||||
scene_set_navmesh(&navmesh_lobby);
|
||||
break;
|
||||
case SCENE_MANAGERS_OFFICE:
|
||||
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start);
|
||||
scene_add_object(OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE, 273, 313, 76, 128, nullptr);
|
||||
scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -41,3 +50,4 @@ void onclick(int curscene, int objid) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="40" height="25" tilewidth="32" tileheight="32" infinite="0" nextlayerid="5" nextobjectid="19">
|
||||
<layer id="1" name="Tile Layer 1" width="40" height="25">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<imagelayer id="2" name="Image Layer 1">
|
||||
<image source="../build/default/lobby.png" width="1280" height="800"/>
|
||||
</imagelayer>
|
||||
<objectgroup id="3" name="Object Layer 1">
|
||||
<object id="1" x="273" y="313" width="76" height="128"/>
|
||||
<object id="3" x="908" y="322" width="53" height="117"/>
|
||||
</objectgroup>
|
||||
<objectgroup id="4" name="navmesh">
|
||||
<object id="10" x="213" y="441">
|
||||
<polygon points="0,0 135,85 191,-1"/>
|
||||
</object>
|
||||
<object id="11" x="213" y="441">
|
||||
<polygon points="0,0 -211,301 135,85"/>
|
||||
</object>
|
||||
<object id="12" x="2" y="741">
|
||||
<polygon points="0,1 345,-124 346,-215"/>
|
||||
</object>
|
||||
<object id="13" x="2" y="742">
|
||||
<polygon points="0,0 0,57 345,-125"/>
|
||||
</object>
|
||||
<object id="14" x="2" y="799">
|
||||
<polygon points="0,0 1278,1 345,-182"/>
|
||||
</object>
|
||||
<object id="15" x="348" y="617">
|
||||
<polygon points="-1,0 587,8 932,183"/>
|
||||
</object>
|
||||
<object id="16" x="860" y="440">
|
||||
<polygon points="0,0 77,118 180,0"/>
|
||||
</object>
|
||||
<object id="17" x="937" y="558">
|
||||
<polygon points="0,0 343,242 103,-118"/>
|
||||
</object>
|
||||
<object id="18" x="939" y="559">
|
||||
<polygon points="-2,-1 -4,66 341,241"/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
|
@ -17,14 +17,22 @@
|
|||
#include <sys/poll.h>
|
||||
#include <inttypes.h>
|
||||
#include "engine.h"
|
||||
#include "compiled_structures.h"
|
||||
|
||||
#define PLAYFIELD_WIDTH 1280
|
||||
#define PLAYFIELD_HEIGHT 800
|
||||
|
||||
|
||||
|
||||
static int touchscreen_fd;
|
||||
|
||||
static struct navmesh null_navmesh = {0};
|
||||
struct navmesh *cur_navmesh = &null_navmesh;
|
||||
struct object objects[MAX_GAME_OBJECTS];
|
||||
struct script scripts[MAX_GAME_SCRIPTS];
|
||||
bool need_rerender = false;
|
||||
|
||||
|
||||
|
||||
#define MAX_MT_SLOTS 16
|
||||
static struct mtslot {
|
||||
int x, y, touch, lasttouch;
|
||||
|
@ -131,7 +139,8 @@ struct sprite {
|
|||
static uint32_t *curfb; // used during render cycle
|
||||
static void blit(int x, int y, const struct sprite *spr) {
|
||||
uint32_t *inbase = spr->pixels;
|
||||
printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
|
||||
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
|
||||
y = PLAYFIELD_HEIGHT - spr->height - y;
|
||||
if(y >= PLAYFIELD_HEIGHT || x >= PLAYFIELD_WIDTH || y <= -spr->height || x <= -spr->width) return;
|
||||
if(spr->height > PLAYFIELD_HEIGHT) error(1, 0, "sprite bigger than playfield not supported"); // would need to clip top and bottom simultaneously - not implemented for now
|
||||
|
||||
|
@ -153,8 +162,9 @@ static void blit(int x, int y, const struct sprite *spr) {
|
|||
}
|
||||
}
|
||||
|
||||
struct object objects[MAX_GAME_OBJECTS];
|
||||
bool need_rerender = false;
|
||||
void scene_set_navmesh(struct navmesh *navmesh) {
|
||||
cur_navmesh = navmesh;
|
||||
}
|
||||
|
||||
static uint32_t scene_generation_count = 0;
|
||||
static int curscene;
|
||||
|
@ -162,6 +172,8 @@ void transition_scene(int scene) {
|
|||
scene_generation_count++;
|
||||
curscene = scene;
|
||||
memset(objects, 0, sizeof objects);
|
||||
memset(scripts, 0, sizeof scripts); // script state is local to scene, so no cancel notification
|
||||
cur_navmesh = &null_navmesh;
|
||||
scene_setup(scene);
|
||||
}
|
||||
|
||||
|
@ -182,6 +194,106 @@ void scene_add_object(int id, int x, int y, int width, int height, const char *p
|
|||
error(1, 0, "too many game objects");
|
||||
}
|
||||
|
||||
struct object *find_object_by_id(int id) {
|
||||
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
|
||||
if(objects[i].id == id)
|
||||
return &objects[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
struct script *scene_add_script(int id, bool interrupt_existing) {
|
||||
if(interrupt_existing && id != OBJID_PLAYER_WALK_SCRIPT) error(1, 0, "scene_add_script: interrupt not implemented");
|
||||
for(int i = 0; i < MAX_GAME_SCRIPTS; i++) {
|
||||
if(scripts[i].id == id && interrupt_existing) {
|
||||
// interrupt existing script?
|
||||
// player walk script is safe to trivially cancel, at least
|
||||
scripts[i].id = 0;
|
||||
}
|
||||
if(scripts[i].id == 0) {
|
||||
memset(&scripts[i], 0, sizeof(scripts[i]));
|
||||
scripts[i].id = id;
|
||||
return &scripts[i];
|
||||
}
|
||||
}
|
||||
error(1, 0, "too many game scripts");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *tri) {
|
||||
double fx = *x, fy = *y;
|
||||
int edgeHitMask = 0;
|
||||
|
||||
// Moving a point outside of one half-plane can move it into another half-plane, so check again.
|
||||
// If this happens, we must be in a corner region.
|
||||
// Repeated normal-vector moves don't necessarily bring us to the corner, but we can just hardcode the corner.
|
||||
for(int j = 0; j < 2; j++) {
|
||||
for(int i = 0; i < 3; i++) {
|
||||
double edgeval = fx*tri->edges[i].a + fy*tri->edges[i].b + tri->edges[i].c;
|
||||
if(edgeval <= 0) {
|
||||
// edge.a,b is normalized so that moving 1 unit of a and 1 unit of b changes edgeval by 1.
|
||||
fx -= tri->edges[i].a*edgeval;
|
||||
fy -= tri->edges[i].b*edgeval;
|
||||
edgeHitMask |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(edgeHitMask) {
|
||||
case 3: // hit edge 0-1 and 1-2
|
||||
*x = tri->points[1].x;
|
||||
*y = tri->points[1].y;
|
||||
return;
|
||||
case 5: // hit edge 0-1 and 2-0
|
||||
*x = tri->points[0].x;
|
||||
*y = tri->points[0].y;
|
||||
return;
|
||||
case 6: // hit edge 1-2 and 2-0
|
||||
*x = tri->points[2].x;
|
||||
*y = tri->points[2].y;
|
||||
return;
|
||||
}
|
||||
|
||||
// may be slightly outside of triangle due to rounding errors, but good enough for us
|
||||
*x = (int)(fx + 0.5);
|
||||
*y = (int)(fy + 0.5);
|
||||
}
|
||||
|
||||
static double point_to_point_dist(int x, int y, const struct navmesh_point *pt) {
|
||||
x -= pt->x;
|
||||
y -= pt->y;
|
||||
return sqrt(x*x + y*y);
|
||||
}
|
||||
|
||||
// Returns distance from triangle, 0 if inside.
|
||||
static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) {
|
||||
double edgedist[3]; // positive if outside
|
||||
for(int edge = 0; edge < 3; edge++)
|
||||
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c);
|
||||
if(edgedist[0] < 0 && edgedist[1] < 0 && edgedist[2] < 0) return 0; // inside
|
||||
if(edgedist[0] >= 0 && edgedist[1] >= 0) return point_to_point_dist(x, y, &tri->points[0]);
|
||||
if(edgedist[1] >= 0 && edgedist[2] >= 0) return point_to_point_dist(x, y, &tri->points[1]);
|
||||
if(edgedist[2] >= 0 && edgedist[0] >= 0) return point_to_point_dist(x, y, &tri->points[2]);
|
||||
if(edgedist[0] >= 0) return edgedist[0];
|
||||
if(edgedist[1] >= 0) return edgedist[1];
|
||||
if(edgedist[2] >= 0) return edgedist[2];
|
||||
return 0; // branch should be unreachable; maybe within epsilon of triangle border?
|
||||
}
|
||||
static int find_navmesh_tri(int x, int y, int tolerance) {
|
||||
int best_tri = -1;
|
||||
double best_dist = tolerance;
|
||||
for(int i = 0; i < cur_navmesh->num_tris; i++) {
|
||||
double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]);
|
||||
if(dist == 0)
|
||||
return i;
|
||||
if(dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_tri = i;
|
||||
}
|
||||
}
|
||||
return best_tri;
|
||||
}
|
||||
|
||||
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int);
|
||||
void handle_tap(int x, int y) {
|
||||
uint32_t gencount = scene_generation_count;
|
||||
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
|
||||
|
@ -190,7 +302,107 @@ void handle_tap(int x, int y) {
|
|||
onclick(curscene, objects[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
struct script_player_walk *player_walk_script = (struct script_player_walk *)scene_add_script(OBJID_PLAYER_WALK_SCRIPT, true);
|
||||
|
||||
struct object *player = find_object_by_id(OBJID_PLAYER);
|
||||
if(player) {
|
||||
int destTri = find_navmesh_tri(x, y, 50);
|
||||
if(destTri != -1) {
|
||||
int targetX = x, targetY = y;
|
||||
closest_point_inside_tri(&targetX, &targetY, &cur_navmesh->tris[destTri]);
|
||||
|
||||
player_walk_script->targetX = targetX;
|
||||
player_walk_script->targetY = targetY;
|
||||
player_walk_script->targetNavmeshTri = destTri;
|
||||
player_walk_script->wakeupMode = SCRIPT_WAKEUP_VIDEO_FRAME;
|
||||
player_walk_script->wakeupFn = update_player_walk_script_on_frame;
|
||||
|
||||
int sourceTri = find_navmesh_tri(player->x+player->width*2, player->y+player->height, 1000000);
|
||||
player_walk_script->currentNavmeshTri = sourceTri;
|
||||
printf("set course from triangle %d to %d\n", sourceTri, destTri);
|
||||
} else {
|
||||
player_walk_script->id = 0;
|
||||
}
|
||||
} else {
|
||||
player_walk_script->id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int move_towards(int cur, int target, int maxrate) {
|
||||
if(target > cur+maxrate) return cur+maxrate;
|
||||
if(target < cur-maxrate) return cur-maxrate;
|
||||
return target;
|
||||
}
|
||||
|
||||
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int) {
|
||||
struct script_player_walk *player_walk_script = (struct script_player_walk *)scr;
|
||||
if(!player_walk_script) return;
|
||||
struct object *player = find_object_by_id(OBJID_PLAYER);
|
||||
if(!player) {
|
||||
player_walk_script->id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(player_walk_script->currentNavmeshTri == -1) {
|
||||
// something weird happened. just teleport player and end movement.
|
||||
player->x = player_walk_script->targetX - player->width/2;
|
||||
player->y = player_walk_script->targetY - player->height;
|
||||
player_walk_script->id = 0;
|
||||
need_rerender = true;
|
||||
} else {
|
||||
int x = player->x+player->width/2;
|
||||
int y = player->y+player->height;
|
||||
int nmtri = player_walk_script->currentNavmeshTri;
|
||||
int nextX;
|
||||
int nextY;
|
||||
int nextTri = -1;
|
||||
if(nmtri == player_walk_script->targetNavmeshTri) {
|
||||
nextX = player_walk_script->targetX;
|
||||
nextY = player_walk_script->targetY;
|
||||
} else {
|
||||
nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri];
|
||||
nextX = -1;
|
||||
for(int edge = 0; edge < 3; edge++) {
|
||||
if(cur_navmesh->tris[nmtri].edges[edge].other_tri == nextTri) {
|
||||
nextX = cur_navmesh->tris[nmtri].edges[edge].center.x;
|
||||
nextY = cur_navmesh->tris[nmtri].edges[edge].center.y;
|
||||
goto center_found;
|
||||
}
|
||||
}
|
||||
// shouldn't happen
|
||||
player_walk_script->id = 0;
|
||||
return;
|
||||
center_found:;
|
||||
}
|
||||
if(nextX == x && nextY == y) {
|
||||
printf("in triangle %d, next triangle %d\n", nmtri, nextTri);
|
||||
if(x == player_walk_script->targetX && y == player_walk_script->targetY) {
|
||||
player_walk_script->id = 0;
|
||||
} else {
|
||||
player_walk_script->currentNavmeshTri = nextTri; // even if -1
|
||||
}
|
||||
} else {
|
||||
x = move_towards(x, nextX, 5);
|
||||
y = move_towards(y, nextY, 2);
|
||||
player->x = x - player->width/2;
|
||||
player->y = y - player->height;
|
||||
}
|
||||
need_rerender = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) {
|
||||
uint32_t gen = scene_generation_count;
|
||||
for(int i = 0; i < MAX_GAME_SCRIPTS; i++) {
|
||||
if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) {
|
||||
scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4);
|
||||
if(scene_generation_count != gen) return true; // scene changed - abort early
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
|
@ -296,7 +508,7 @@ int main(int argc, char **argv) {
|
|||
uint64_t dummy; // don't care about expiration count
|
||||
if(read(timerfd, &dummy, sizeof dummy) < 0) error(1, errno, "read (timerfd)");
|
||||
current_animframe++;
|
||||
printf("animation frame %u\n", (unsigned int)current_animframe);
|
||||
//printf("animation frame %u\n", (unsigned int)current_animframe);
|
||||
need_rerender = true;
|
||||
}
|
||||
if(polls[2].revents & POLLIN) {
|
||||
|
@ -319,20 +531,18 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
|
||||
if(!awaiting_vblank) {
|
||||
// return value ignored
|
||||
// What is this really doing? It's giving scripts the opportunity to trigger a video frame.
|
||||
// But if no script actually does, this can run over and over many times in the same video frame.
|
||||
// It's not saying a video frame happened, it's saying one can happen.
|
||||
deliver_script_wakeup(SCRIPT_WAKEUP_VIDEO_FRAME, 0, SCRIPT_WAKEUP_VIDEO_FRAME, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if(need_rerender && !awaiting_vblank) {
|
||||
cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer
|
||||
curfb = fbs[cur_buffer];
|
||||
|
||||
|
||||
//printf("%d %d\n", sizeof basement_pixels, 1280*800*4);
|
||||
/*struct sprite bgsprite = {
|
||||
.width = 1280,
|
||||
.height = 800,
|
||||
.pixels = (uint32_t*)_binary_sprite_lobby_raw_start
|
||||
};
|
||||
memset(curfb, 0, 1280*800*4);
|
||||
blit(mtslots[0].y-640, mtslots[0].x-400, &bgsprite);*/
|
||||
|
||||
for(int i = 0; i < MAX_GAME_OBJECTS; i++) {
|
||||
if(objects[i].id != 0 && objects[i].pixels) {
|
||||
struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels};
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue