bberak / react-native-game-engine

A lightweight Game Engine for React Native 🕹⚡🎮
MIT License
2.86k stars 172 forks source link

Recommended way for moving the viewport/camera #21

Closed dbramwell closed 5 years ago

dbramwell commented 5 years ago

Hi,

I've started playing around with RNGE and MatterJS, so far i'm really enjoying it. I've hit an issue when trying to move the viewport though. People using MatterJS seems to use Matter.Bounds for this, but it seems to use window/document functions which react-native obviously doesn't have access to.

I could just use an object with x,y in it and offset all other renderers by that amount, wondering if there's a better way though.

bberak commented 5 years ago

Hi @dbramwell,

Thanks for getting in touch. I've used a similar approach to the one you mentioned, but I offset the entire viewport - not the individual renderable entities. I wonder if a similar approach could work for you.

Here are the links to my code. Just a heads up that my example has an up-down gameplay, so if you're creating a sidescroller, you'll need to make some modification:

I first added a camera entity with a simple vertical offset:


return {
   ... other entities,
   camera: { offsetY: 0 }
}

Then I added a camera system to increase or decrease my vertical offset as required:


export default (entities, { screen }) => {
    let mario = entities.mario;
    let princess = entities.princess;
    let camera = entities.camera;
    let targetY = princess.position.y + camera.offsetY;
    let anchorY = screen.height * 0.65;
    let diff = anchorY - mario.body.position.y - camera.offsetY;

    if (targetY < 150 || diff < 0) {
        camera.offsetY += diff * 0.05;
    }

    return entities;
}

Finally, I overrode the default renderer to take into account the camera offsets and move the viewport:


import React, { Component } from "react";
import { View } from "react-native";

export default (state, screen) => {
        if (!state) return null;
    return (
        <View style={{marginTop: state.camera.offsetY}}>
            {Object.keys(state)
                .filter(key => state[key].renderer)
                .map(key => {
                    let entity = state[key];
                    if (typeof entity.renderer === "object")
                        return (
                            <entity.renderer.type
                                key={key}
                                {...entity}
                                screen={screen}
                            />
                        );
                    else if (typeof entity.renderer === "function")
                        return (
                            <entity.renderer
                                key={key}
                                {...entity}
                                screen={screen}
                            />
                        );
                })}
        </View>
    );
};

Once you've got those in place, just wire them into the GameEngine

<GameEngine
   systems={[ System1, SystemN, Camera ]}
   entities={{...}}
   renderer={CameraRenderer} 
/>

Here are the links to the full source code:

Let me know if that works or if you need to bounce back some more ideas.

Also, would love to see what you've been working on when its done.

Cheers!

bberak commented 5 years ago

Hi @dbramwell.

Let me know if I can assist more on this issue. Cheers.

dbramwell commented 5 years ago

Hi, No, that's great, thanks for the advice.

dalokey commented 4 years ago

@bberak can you expand further please. I am trying to move my viewPort on the x-axis. Can you expand on renderer={CameraRenderer}. I get an error "Cannot read property 'camera' of null", not sure why, because I defined camera as a property of entities. Do all the entities get defined as properties of the state argument in CameraRenderer?

Sorry, I am really trying to get my head around this as I really love this project but it is hard to find examples. It would be really great if you can show a working example.

Hope to hear from you.

dalokey commented 4 years ago

@bberak So I managed to fix it by doing the following

export default (state, screen) => {
    if(!state){
        // ignore
    } else{
        return (...)
    }
}

Not sure if this is best practice for this specific issue but does seem to work for me.

Awesome project! keep up the good work :)

bberak commented 4 years ago

Thanks @Dalomo,

Your method of checking whether the state is truthy is absolutely correct! This is now required due to a recent change that allows the GameEngine loop to run before the entities and layout have fully loaded (allowing for loading screens etc). I'm going to update the code above to reflect this (apologies for the confusion).

There is also a project showcasing a few examples which can also be run on the device. Most of the stuff should still be fairly relevant, but it has been a while since the last update: https://github.com/bberak/react-native-game-engine-handbook

Cheers!

dalokey commented 4 years ago

@bberak Thank you for getting back. And I will have a look at the handbook examples and see what I can pick up :)

bberak commented 4 years ago

No worries @Dalomo - all the best with your project :)