diff --git a/assets/images/Guns.png b/assets/images/Guns.png new file mode 100644 index 0000000..06da7da Binary files /dev/null and b/assets/images/Guns.png differ diff --git a/assets/tiles/Level3.tmx b/assets/tiles/Level3.tmx index ad20191..31d04c3 100644 --- a/assets/tiles/Level3.tmx +++ b/assets/tiles/Level3.tmx @@ -91,7 +91,7 @@ - + diff --git a/lib/game/game.dart b/lib/game/game.dart index 8b315c1..5513134 100644 --- a/lib/game/game.dart +++ b/lib/game/game.dart @@ -4,19 +4,34 @@ import 'dart:math'; import 'package:flame/components.dart'; import 'package:flame/events.dart'; import 'package:flame/game.dart'; +import 'package:flame/palette.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hold_labs/game/level.dart'; +import 'package:hold_labs/game/player.dart'; class HoldLabsGame extends FlameGame - with HasCollisionDetection, HasKeyboardHandlerComponents { + with + HasCollisionDetection, + HasKeyboardHandlerComponents, + MouseMovementDetector, + TapCallbacks, + SecondaryTapDetector, + ScrollDetector, + PanDetector { HoldLabsGame() : super( camera: CameraComponent.withFixedResolution(width: 320, height: 180), ); Level? currentLevel; + final Vector2 mousePosition = Vector2.zero(); + + final _redGunPaint = BasicPalette.red.paint()..strokeWidth = 4; + final _blueGunPaint = BasicPalette.blue.paint()..strokeWidth = 4; + + bool isFiring = false; @override Color backgroundColor() { @@ -35,6 +50,7 @@ class HoldLabsGame extends FlameGame 'Tiles.png', 'PortalPad.png', 'Buttons.png', + 'Guns.png', ], ); @@ -52,7 +68,77 @@ class HoldLabsGame extends FlameGame void changeLevel(int levelId) { currentLevel?.removeFromParent(); - currentLevel = Level(min(levelId, 3)); + currentLevel = Level(min(levelId, 4)); world.add(currentLevel!); } + + @override + void onMouseMove(PointerHoverInfo info) { + mousePosition.setFrom(camera.globalToLocal(info.eventPosition.global)); + } + + @override + void onPanDown(DragDownInfo info) { + isFiring = true; + } + + @override + void onPanUpdate(DragUpdateInfo info) { + isFiring = true; + mousePosition.setFrom(camera.globalToLocal(info.eventPosition.global)); + } + + @override + void onPanStart(DragStartInfo info) { + isFiring = true; + } + + @override + void onPanEnd(DragEndInfo info) { + isFiring = false; + } + + @override + void onPanCancel() { + isFiring = false; + } + + @override + void onSecondaryTapDown(TapDownInfo info) { + if (currentLevel?.isMounted ?? false) { + if (currentLevel!.player.hasGun) { + currentLevel?.player.switchGun(); + } + } + } + + @override + void onScroll(PointerScrollInfo info) { + if (currentLevel?.isMounted ?? false) { + if (currentLevel!.player.hasGun) { + currentLevel?.player.switchGun(); + } + } + } + + @override + void render(Canvas canvas) { + super.render(canvas); + if (currentLevel?.isMounted ?? false) { + final player = currentLevel!.player; + + if (player.hasGun && isFiring) { + if ((currentLevel!.player.results?.isActive ?? false) && + currentLevel!.player.results!.intersectionPoint != null) { + canvas.drawLine( + camera.localToGlobal(currentLevel!.player.gunPosition).toOffset(), + camera + .localToGlobal(currentLevel!.player.results!.intersectionPoint!) + .toOffset(), + player.gunType == GunType.hot ? _redGunPaint : _blueGunPaint, + ); + } + } + } + } } diff --git a/lib/game/gun_pickup.dart b/lib/game/gun_pickup.dart new file mode 100644 index 0000000..421148c --- /dev/null +++ b/lib/game/gun_pickup.dart @@ -0,0 +1,30 @@ +import 'dart:async'; + +import 'package:flame/collisions.dart'; +import 'package:flame/components.dart'; +import 'package:hold_labs/game/game.dart'; + +class GunPickup extends PositionComponent with HasGameReference { + GunPickup({super.position}); + + late final SpriteAnimationComponent _guns; + + @override + Future onLoad() async { + _guns = SpriteAnimationComponent.fromFrameData( + game.images.fromCache('Guns.png'), + SpriteAnimationData.sequenced( + amount: 2, + stepTime: 0.3, + textureSize: Vector2.all(16), + ), + ); + + size = _guns.size; + + await add(_guns); + await add( + RectangleHitbox(collisionType: CollisionType.passive), + ); + } +} diff --git a/lib/game/h_object.dart b/lib/game/h_object.dart index 6e278d6..26cd418 100644 --- a/lib/game/h_object.dart +++ b/lib/game/h_object.dart @@ -95,6 +95,7 @@ class HObject extends PositionComponent with HasPaint, Snapshot { _temperatureFactor -= _temperatureChangeRate * dt; _updateTemperature(); position.y = lerpDouble(_coldHeight.y, _hotHeight.y, _temperatureFactor)!; + isCooling = false; } if (isHeating) { @@ -102,6 +103,7 @@ class HObject extends PositionComponent with HasPaint, Snapshot { _updateTemperature(); position.y = lerpDouble(_coldHeight.y, _hotHeight.y, _temperatureFactor)!; + isHeating = false; } } diff --git a/lib/game/level.dart b/lib/game/level.dart index 67f634c..2c15bbe 100644 --- a/lib/game/level.dart +++ b/lib/game/level.dart @@ -7,6 +7,7 @@ import 'package:flame_audio/flame_audio.dart'; import 'package:flame_tiled/flame_tiled.dart'; import 'package:hold_labs/game/button.dart'; import 'package:hold_labs/game/game.dart'; +import 'package:hold_labs/game/gun_pickup.dart'; import 'package:hold_labs/game/h_object.dart'; import 'package:hold_labs/game/platform.dart'; import 'package:hold_labs/game/player.dart'; @@ -16,7 +17,7 @@ class Level extends PositionComponent with HasGameReference { Level(this.levelId); final int levelId; - late final Player _player; + late final Player player; final _loadedAudio = []; @override @@ -66,9 +67,9 @@ class Level extends PositionComponent with HasGameReference { await add(portal); if (object.class_ == 'Start') { - _player = Player(position: object.position, priority: 1); - await add(_player); - game.camera.follow(_player.cameraTarget); + player = Player(position: object.position, priority: 1); + await add(player); + game.camera.follow(player.cameraTarget); } else { final portalHitbox = RectangleHitbox( collisionType: CollisionType.passive, @@ -123,6 +124,10 @@ class Level extends PositionComponent with HasGameReference { } } break; + case 'Guns': + final gunsPickup = GunPickup(position: object.position); + await add(gunsPickup); + break; } } } @@ -191,7 +196,7 @@ class Level extends PositionComponent with HasGameReference { .last; if (audioPath != null) { FlameAudio.bgm.pause(); - _player.moveLock = true; + player.moveLock = true; await FlameAudio.audioCache.load(audioPath); _loadedAudio.add(audioPath); @@ -199,7 +204,7 @@ class Level extends PositionComponent with HasGameReference { final audioplayer = await FlameAudio.playLongAudio(audioPath); audioplayer.onPlayerComplete.listen( (event) { - _player.moveLock = false; + player.moveLock = false; FlameAudio.bgm.resume(); }, ); @@ -260,12 +265,12 @@ class Level extends PositionComponent with HasGameReference { if (holdInput) { FlameAudio.bgm.pause(); - _player.moveLock = true; + player.moveLock = true; } final audioplayer = await FlameAudio.playLongAudio(filename); audioplayer.onPlayerComplete.listen((event) { - _player.moveLock = false; + player.moveLock = false; FlameAudio.bgm.resume(); }); } diff --git a/lib/game/player.dart b/lib/game/player.dart index 39c9583..e266c11 100644 --- a/lib/game/player.dart +++ b/lib/game/player.dart @@ -1,17 +1,22 @@ import 'dart:async'; +import 'dart:math'; import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; +import 'package:flame/geometry.dart'; import 'package:flame/sprite.dart'; import 'package:flame_audio/flame_audio.dart'; import 'package:flutter/services.dart'; import 'package:hold_labs/game/game.dart'; +import 'package:hold_labs/game/gun_pickup.dart'; import 'package:hold_labs/game/h_object.dart'; import 'package:hold_labs/game/platform.dart'; enum PlayerAnimation { idle, run, jump, hit } +enum GunType { hot, cold } + class Player extends PositionComponent with HasGameReference, CollisionCallbacks, KeyboardHandler { Player({super.position, super.priority}) : super(anchor: Anchor.center); @@ -43,6 +48,14 @@ class Player extends PositionComponent double vAxisValue = 0; final cameraTarget = PositionComponent(); + final _gunHolder = PositionComponent(); + late final SpriteGroupComponent _gunComponent; + + bool hasGun = false; + + RaycastResult? results; + GunType? get gunType => _gunComponent.current; + Vector2 get gunPosition => _gunComponent.absolutePosition; @override Future onLoad() async { @@ -88,6 +101,26 @@ class Player extends PositionComponent anchor: Anchor.center, ), ); + + _gunHolder.position = size * 0.5; + await add(_gunHolder); + + _gunComponent = SpriteGroupComponent( + current: GunType.hot, + position: Vector2(8, 0), + anchor: Anchor.center, + sprites: { + GunType.hot: Sprite( + game.images.fromCache('Guns.png'), + srcSize: Vector2.all(16), + ), + GunType.cold: Sprite( + game.images.fromCache('Guns.png'), + srcSize: Vector2.all(16), + srcPosition: Vector2(16, 0), + ), + }, + ); } @override @@ -137,6 +170,10 @@ class Player extends PositionComponent if ((cameraTarget.position - absoluteCenter).length2 > 0.5) { cameraTarget.position.setValues(x, y); } + + if (hasGun) { + _updateGunPosition(); + } } @override @@ -169,6 +206,10 @@ class Player extends PositionComponent position += separationVector; } + } else if (other is GunPickup) { + other.removeFromParent(); + hasGun = true; + _addGuns(); } super.onCollision(intersectionPoints, other); } @@ -190,4 +231,50 @@ class Player extends PositionComponent } return true; } + + void _addGuns() { + _gunHolder.add(_gunComponent); + } + + void _updateGunPosition() { + if (_gunComponent.isMounted) { + final dir = game.mousePosition - position; + _gunHolder.angle = + (-dir.angleToSigned(_upVector)) * scale.x.sign - (pi * 0.5); + + if (game.isFiring) { + final maxDistance = dir.normalize(); + + final ray = Ray2( + origin: _gunComponent.absolutePosition, + direction: dir, + ); + + results = game.collisionDetection + .raycast(ray, maxDistance: maxDistance, out: results); + if (results?.isActive ?? false) { + final other = results!.hitbox?.parent; + if (other is HObject) { + switch (_gunComponent.current) { + case GunType.hot: + other.isCooling = false; + other.isHeating = true; + break; + case GunType.cold: + other.isCooling = true; + other.isHeating = false; + break; + case null: + break; + } + } + } + } + } + } + + void switchGun() { + _gunComponent.current = + _gunComponent.current == GunType.cold ? GunType.hot : GunType.cold; + } } diff --git a/tiled-project/Level3.tmx b/tiled-project/Level3.tmx index 9199a1a..aa2a564 100644 --- a/tiled-project/Level3.tmx +++ b/tiled-project/Level3.tmx @@ -92,7 +92,7 @@ - +