Selaa lähdekoodia

JS arrow key events: snake works

Karl Zylinski 2 kuukautta sitten
vanhempi
sitoutus
924c2d6d53
5 muutettua tiedostoa jossa 262 lisäystä ja 132 poistoa
  1. 10 0
      .sublime/karl2d.sublime-project
  2. 10 0
      TODO.md
  3. 24 10
      examples/gamepad/gamepad.odin
  4. 113 113
      examples/snake/snake.odin
  5. 105 9
      window_js.odin

+ 10 - 0
.sublime/karl2d.sublime-project

@@ -51,6 +51,11 @@
 					"shell_cmd": "odin run . -vet -strict-style -keep-executable -debug",
 					"working_dir": "$project_path/../examples/snake"
 				},
+				{
+					"name": "snake (web)",
+					"shell_cmd": "odin run build_web_example -- snake",
+					"working_dir": "$project_path/../examples"
+				},
 				{
 					"name": "box2d",
 					"shell_cmd": "odin run . -vet -strict-style -keep-executable",
@@ -91,6 +96,11 @@
 					"shell_cmd": "odin run . -vet -strict-style -keep-executable",
 					"working_dir": "$project_path/../examples/gamepad"
 				},
+				{
+					"name": "gamepad (web)",
+					"shell_cmd": "odin run build_web_example -- gamepad",
+					"working_dir": "$project_path/../examples"
+				},
 				{
 					"name": "api_doc_builder",
 					"shell_cmd": "odin run api_doc_builder",

+ 10 - 0
TODO.md

@@ -1,4 +1,14 @@
 ## TODO
+
+High-level TODO before beta release:
+- Complete JS window impl
+- Fix render target drawing on gl and webgl
+- Make text rendering look good
+- Figure out what to do with the delta time stuff, built in or separate?
+- Look into webgl performance (bunnymark)
+- Understand texture format choices in webgl and gl backend and fix anything that is wrong
+
+
 * Can we reuse memory for const buffers and union blocks between shaders? Just create reasonably sized ones and fetch based on size or something.
 * should gamepad come from separate interface than window?
 	* keyboard input could also come from some input interface, but

+ 24 - 10
examples/gamepad/gamepad.odin

@@ -66,21 +66,35 @@ gamepad_demo :: proc(gamepad: k2.Gamepad_Index, offset: k2.Vec2) {
 
 main :: proc() {
 	context.logger = log.create_console_logger()
+
+	init()
+
+	for !k2.shutdown_wanted() {
+		step(0)
+	}
+
+	shutdown()
+}
+
+init :: proc() {
 	k2.init(1000, 600, "Karl2D Gamepad Demo")
 	k2.set_window_position(300, 100)
+}
 
-	for !k2.shutdown_wanted() {
-		k2.process_events()
-		k2.clear(k2.BLACK)
+step :: proc(dt: f32) -> bool {
+	k2.process_events()
+	k2.clear(k2.BLACK)
 
-		gamepad_demo(0, {0, 0})
-		gamepad_demo(1, {500, 0})
-		gamepad_demo(2, {0, 300})
-		gamepad_demo(3, {500, 300})
+	gamepad_demo(0, {0, 0})
+	gamepad_demo(1, {500, 0})
+	gamepad_demo(2, {0, 300})
+	gamepad_demo(3, {500, 300})
 
-		k2.present()
-		free_all(context.temp_allocator)
-	}
+	k2.present()
+	free_all(context.temp_allocator)
+	return true
+}
 
+shutdown :: proc() {
 	k2.shutdown()
 }

+ 113 - 113
examples/snake/snake.odin

@@ -26,6 +26,15 @@ move_direction: Vec2i
 game_over: bool
 food_pos: Vec2i
 
+food_sprite: k2.Texture
+head_sprite: k2.Texture
+body_sprite: k2.Texture
+tail_sprite: k2.Texture
+
+food_eaten_at: time.Time
+started_at: time.Time
+prev_time: time.Time
+
 place_food :: proc() {
 	occupied: [GRID_WIDTH][GRID_WIDTH]bool
 
@@ -65,7 +74,7 @@ main :: proc() {
 	context.logger = log.create_console_logger()
 
 	when ODIN_DEBUG {
-		track: mem.Tracking_Allocatorx	
+		track: mem.Tracking_Allocator
 		mem.tracking_allocator_init(&track, context.allocator)
 		context.allocator = mem.tracking_allocator(&track)
 
@@ -79,157 +88,148 @@ main :: proc() {
 		}
 	}
 
+	for !k2.shutdown_wanted() {
+		time_now := time.now()
+		dt := f32(time.duration_seconds(time.diff(prev_time, time_now)))
+		prev_time = time_now
+		step(dt)
+	}
+}
+
+
+init :: proc() {
 	k2.init(WINDOW_SIZE, WINDOW_SIZE, "Snake")
 	k2.set_window_position(300, 300)
 
-	SHADER_SOURCE :: #load("shader.hlsl")
+	prev_time = time.now()
 
-	shader := k2.load_shader_from_memory(SHADER_SOURCE, SHADER_SOURCE, {
-		.RG_32_Float,
-		.RG_32_Float,
-		.RGBA_8_Norm,
-		.RG_32_Float,
-	})
+	restart()
 
-	prev_time := time.now()
+	food_sprite = k2.load_texture_from_bytes(#load("food.png"))
+	head_sprite = k2.load_texture_from_bytes(#load("head.png"))
+	body_sprite = k2.load_texture_from_bytes(#load("body.png"))
+	tail_sprite = k2.load_texture_from_bytes(#load("tail.png"))
 
-	restart()
+	food_eaten_at = time.now()
+	started_at = time.now()
+}
 
-	food_sprite := k2.load_texture_from_file("food.png")
-	head_sprite := k2.load_texture_from_file("head.png")
-	body_sprite := k2.load_texture_from_file("body.png")
-	tail_sprite := k2.load_texture_from_file("tail.png")
+step :: proc(dt: f32) -> bool {
+	k2.process_events()
 
-	food_eaten_at := time.now()
-	started_at := time.now()
+	if k2.key_is_held(.Up) || k2.gamepad_button_is_held(0, .Left_Face_Up) {
+		move_direction = {0, -1}
+	}
 
-	for !k2.shutdown_wanted() {
-		time_now := time.now()
-		dt := f32(time.duration_seconds(time.diff(prev_time, time_now)))
-		prev_time = time_now
-		total_time := time.duration_seconds(time.diff(started_at, time_now))
-		k2.process_events()
+	if k2.key_is_held(.Down) || k2.gamepad_button_is_held(0, .Left_Face_Down) {
+		move_direction = {0, 1}
+	}
 
-		if k2.key_is_held(.Up) || k2.gamepad_button_is_held(0, .Left_Face_Up) {
-			move_direction = {0, -1}
-		}
+	if k2.key_is_held(.Left) || k2.gamepad_button_is_held(0, .Left_Face_Left) {
+		move_direction = {-1, 0}
+	}
 
-		if k2.key_is_held(.Down) || k2.gamepad_button_is_held(0, .Left_Face_Down) {
-			move_direction = {0, 1}
-		}
+	if k2.key_is_held(.Right) || k2.gamepad_button_is_held(0, .Left_Face_Right) {
+		move_direction = {1, 0}
+	}
 
-		if k2.key_is_held(.Left) || k2.gamepad_button_is_held(0, .Left_Face_Left) {
-			move_direction = {-1, 0}
+	if game_over {
+		if k2.key_went_down(.Enter) {
+			restart()
 		}
+	} else {
+		tick_timer -= dt
+	}
 
-		if k2.key_is_held(.Right) || k2.gamepad_button_is_held(0, .Left_Face_Right) {
-			move_direction = {1, 0}
-		}
+	if tick_timer <= 0 {
+		next_part_pos := snake[0]
+		snake[0] += move_direction
+		head_pos := snake[0]
 
-		if game_over {
-			if k2.key_went_down(.Enter) {
-				restart()
-			}
-		} else {
-			tick_timer -= dt
+		if head_pos.x < 0 || head_pos.y < 0 || head_pos.x >= GRID_WIDTH || head_pos.y >= GRID_WIDTH {
+			game_over = true
 		}
 
-		if tick_timer <= 0 {
-			next_part_pos := snake[0]
-			snake[0] += move_direction
-			head_pos := snake[0]
+		for i in 1..<snake_length {
+			cur_pos := snake[i]
 
-			if head_pos.x < 0 || head_pos.y < 0 || head_pos.x >= GRID_WIDTH || head_pos.y >= GRID_WIDTH {
+			if cur_pos == head_pos {
 				game_over = true
 			}
 
-			for i in 1..<snake_length {
-				cur_pos := snake[i]
-
-				if cur_pos == head_pos {
-					game_over = true
-				}
-
-				snake[i] = next_part_pos
-				next_part_pos = cur_pos
-			}
-
-			if head_pos == food_pos {
-				snake_length += 1
-				snake[snake_length - 1] = next_part_pos
-				place_food()
-				food_eaten_at = time.now()
-			}
-
-			tick_timer = TICK_RATE + tick_timer
-		}
-
-		k2.clear({76, 53, 83, 255})
-		k2.set_shader(shader)
-
-		camera := k2.Camera {
-			zoom = f32(WINDOW_SIZE) / CANVAS_SIZE,
+			snake[i] = next_part_pos
+			next_part_pos = cur_pos
 		}
-		
-		k2.set_camera(camera)
-		food_sprite.width = CELL_SIZE
-		food_sprite.height = CELL_SIZE
-
-		time_since_food := time.duration_seconds(time.diff(food_eaten_at, time_now))
 
-		if time_since_food < 0.5 && total_time > 1 {
-			k2.override_shader_input(shader, 3, k2.Vec2{f32(math.cos(total_time*100)*4), f32(math.sin(total_time*120 + 3)*4)})
+		if head_pos == food_pos {
+			snake_length += 1
+			snake[snake_length - 1] = next_part_pos
+			place_food()
+			food_eaten_at = time.now()
 		}
 
-		k2.draw_texture(food_sprite, {f32(food_pos.x), f32(food_pos.y)}*CELL_SIZE)
+		tick_timer = TICK_RATE + tick_timer
+	}
 
-		k2.override_shader_input(shader, 3, nil)
+	k2.clear({76, 53, 83, 255})
 
-		for i in 0..<snake_length {
-			part_sprite := body_sprite
-			dir: Vec2i
+	camera := k2.Camera {
+		zoom = f32(WINDOW_SIZE) / CANVAS_SIZE,
+	}
+	
+	k2.set_camera(camera)
+	food_sprite.width = CELL_SIZE
+	food_sprite.height = CELL_SIZE
 
-			if i == 0 {
-				part_sprite = head_sprite
-				dir = snake[i] - snake[i + 1]
-			} else if i == snake_length - 1 {
-				part_sprite = tail_sprite
-				dir = snake[i - 1] - snake[i]
-			} else {
-				dir = snake[i - 1] - snake[i]
-			}
+	k2.draw_texture(food_sprite, {f32(food_pos.x), f32(food_pos.y)}*CELL_SIZE)
 
-			rot := math.atan2(f32(dir.y), f32(dir.x)) * math.DEG_PER_RAD
 
-			source := k2.Rect {
-				0, 0,
-				f32(part_sprite.width), f32(part_sprite.height),
-			}
+	for i in 0..<snake_length {
+		part_sprite := body_sprite
+		dir: Vec2i
+
+		if i == 0 {
+			part_sprite = head_sprite
+			dir = snake[i] - snake[i + 1]
+		} else if i == snake_length - 1 {
+			part_sprite = tail_sprite
+			dir = snake[i - 1] - snake[i]
+		} else {
+			dir = snake[i - 1] - snake[i]
+		}
 
-			dest := k2.Rect {
-				f32(snake[i].x)*CELL_SIZE + 0.5*CELL_SIZE,
-				f32(snake[i].y)*CELL_SIZE + 0.5*CELL_SIZE,
-				CELL_SIZE,
-				CELL_SIZE,
-			}
+		rot := math.atan2(f32(dir.y), f32(dir.x)) * math.DEG_PER_RAD
 
-			k2.draw_texture_ex(part_sprite, source, dest, {CELL_SIZE, CELL_SIZE}*0.5, rot)
+		source := k2.Rect {
+			0, 0,
+			f32(part_sprite.width), f32(part_sprite.height),
 		}
 
-		if game_over {
-			k2.draw_text("Game Over!", {4, 4}, 25, k2.RL_RED)
-			k2.draw_text("Press Enter to play again", {4, 30}, 15, k2.BLACK)
+		dest := k2.Rect {
+			f32(snake[i].x)*CELL_SIZE + 0.5*CELL_SIZE,
+			f32(snake[i].y)*CELL_SIZE + 0.5*CELL_SIZE,
+			CELL_SIZE,
+			CELL_SIZE,
 		}
 
-		score := snake_length - 3
-		score_str := fmt.tprintf("Score: %v", score)
-		k2.draw_text(score_str, {4, CANVAS_SIZE - 14}, 10, k2.RL_GRAY)
-		k2.present()
+		k2.draw_texture_ex(part_sprite, source, dest, {CELL_SIZE, CELL_SIZE}*0.5, rot)
+	}
 
-		free_all(context.temp_allocator)
+	if game_over {
+		k2.draw_text("Game Over!", {4, 4}, 25, k2.RL_RED)
+		k2.draw_text("Press Enter to play again", {4, 30}, 15, k2.BLACK)
 	}
 
-	k2.destroy_shader(shader)
+	score := snake_length - 3
+	score_str := fmt.tprintf("Score: %v", score)
+	k2.draw_text(score_str, {4, CANVAS_SIZE - 14}, 10, k2.RL_GRAY)
+	k2.present()
+
+	free_all(context.temp_allocator)
+	return true
+}
+
+shutdown :: proc() {
 	k2.destroy_texture(head_sprite)
 	k2.destroy_texture(food_sprite)
 	k2.destroy_texture(body_sprite)

+ 105 - 9
window_js.odin

@@ -47,19 +47,21 @@ js_init :: proc(
 
 	// The browser window probably has some other size than what was sent in.
 	if .Resizable in flags {
-		add_global_event_listener(.Resize, js_window_event_resize)
+		add_window_event_listener(.Resize, js_window_event_resize)
 		update_canvas_size(s.canvas_id)
 	} else {
 		js_set_size(window_width, window_height)
 	}
 
-	add_event_listener(.Mouse_Move, js_window_event_mouse_move)
-	add_event_listener(.Mouse_Down, js_window_event_mouse_down)
-	add_event_listener(.Mouse_Up, js_window_event_mouse_up)
+	add_canvas_event_listener(.Mouse_Move, js_window_event_mouse_move)
+	add_canvas_event_listener(.Mouse_Down, js_window_event_mouse_down)
+	add_canvas_event_listener(.Mouse_Up, js_window_event_mouse_up)
+
+	add_window_event_listener(.Key_Down, js_window_event_key_down)
+	add_window_event_listener(.Key_Up, js_window_event_key_up)
 }
 
-// These events fire even when the tab isn't in focus
-add_global_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event)) {
+add_window_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event)) {
 	js.add_window_event_listener(
 		evt, 
 		nil, 
@@ -68,7 +70,7 @@ add_global_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event
 	)
 }
 
-add_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event)) {
+add_canvas_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event)) {
 	js.add_event_listener(
 		s.canvas_id, 
 		evt, 
@@ -78,6 +80,36 @@ add_event_listener :: proc(evt: js.Event_Kind, callback: proc(e: js.Event)) {
 	)
 }
 
+js_window_event_key_down :: proc(e: js.Event) {
+	if e.key.repeat {
+		return
+	}
+
+	key := key_from_js_event(e)
+	append(&s.events, Window_Event_Key_Went_Down {
+		key = key,
+	})
+}
+
+js_window_event_key_up :: proc(e: js.Event) {
+	key := key_from_js_event(e)
+	append(&s.events, Window_Event_Key_Went_Up {
+		key = key,
+	})
+}
+
+key_from_js_event :: proc(e: js.Event) -> Keyboard_Key {
+	log.info(e)
+	switch e.key.code {
+	case "ArrowUp": return .Up
+	case "ArrowDown": return .Down
+	case "ArrowLeft": return .Left
+	case "ArrowRight": return .Right
+	case "Enter": return .Enter
+	}
+	return .None
+}
+
 js_window_event_resize :: proc(e: js.Event) {
 	update_canvas_size(s.canvas_id)
 }
@@ -128,7 +160,69 @@ js_window_handle :: proc() -> Window_Handle {
 }
 
 js_process_events :: proc() {
-	
+	//for gamepad_idx in 0..<4 {
+		/*prev_state := s.gamepad_state[gamepad_idx]
+		if js.get_gamepad_state(s.gamepad_state[gamepad_idx], &gs) && gs.connected {
+			log.info(gs)
+		}*/
+//	}
+
+	/*
+
+
+	for gamepad in 0..<4 {
+		gp_event: win32.XINPUT_KEYSTROKE
+
+		for win32.XInputGetKeystroke(win32.XUSER(gamepad), 0, &gp_event) == .SUCCESS {
+			button: Maybe(Gamepad_Button)
+
+			#partial switch gp_event.VirtualKey {
+			case .DPAD_UP:    button = .Left_Face_Up
+			case .DPAD_DOWN:  button = .Left_Face_Down
+			case .DPAD_LEFT:  button = .Left_Face_Left
+			case .DPAD_RIGHT: button = .Left_Face_Right
+
+			case .Y: button = .Right_Face_Up
+			case .A: button = .Right_Face_Down
+			case .X: button = .Right_Face_Left
+			case .B: button = .Right_Face_Right
+
+			case .LSHOULDER: button = .Left_Shoulder
+			case .LTRIGGER:  button = .Left_Trigger
+
+			case .RSHOULDER: button = .Right_Shoulder
+			case .RTRIGGER:  button = .Right_Trigger
+
+			case .BACK: button = .Middle_Face_Left
+			
+			// Not sure you can get the "middle button" with XInput (the one that goe to dashboard)
+
+			case .START: button = .Middle_Face_Right
+
+			case .LTHUMB_PRESS: button = .Left_Stick_Press
+			case .RTHUMB_PRESS: button = .Right_Stick_Press
+			}
+
+			b := button.? or_continue
+			evt: Window_Event
+
+			if .KEYDOWN in gp_event.Flags {
+				evt = Window_Event_Gamepad_Button_Went_Down {
+					gamepad = gamepad,
+					button = b,
+				}
+			} else if .KEYUP in gp_event.Flags {
+				evt = Window_Event_Gamepad_Button_Went_Up {
+					gamepad = gamepad,
+					button = b,
+				}
+			}
+
+			if evt != nil {
+				append(&s.events, evt)
+			}
+		
+		*/
 }
 
 js_get_events :: proc() -> []Window_Event {
@@ -176,7 +270,8 @@ js_is_gamepad_active :: proc(gamepad: int) -> bool {
 		return false
 	}
 
-	return false
+	gs: js.Gamepad_State	
+	return js.get_gamepad_state(gamepad, &gs) && gs.connected 
 }
 
 js_get_gamepad_axis :: proc(gamepad: int, axis: Gamepad_Axis) -> f32 {
@@ -204,6 +299,7 @@ JS_State :: struct {
 	width: int,
 	height: int,
 	events: [dynamic]Window_Event,
+	gamepad_state: [MAX_GAMEPADS]js.Gamepad_State,
 }
 
 s: ^JS_State